--- /dev/null
+# Architecture & maintainability review — aukio-3d
+
+Scope: 152 main-source files, ~31.7k LOC at `/home/n0/workspace/Aukio/aukio-3d`.
+Read fully: ViewPanel, TexturedTriangle, AbstractCompositeShape, OctreeVolume, RenderAggregator,
+RenderingContext, AbstractCoordinateShape, ShapeCollection, SolidPolygon, LineInterpolator,
+PolygonBorderInterpolator, SegmentRenderingContext, AbstractShape; skimmed the rest.
+Dead-code claims verified by grep across aukio-3d (main+test), aukio-3d-demos, aukio,
+aukio-environment-fo4 (no reflection usage found in consumers; demos touch 64 engine classes,
+aukio 24, fo4 17 — the API surface is genuinely exercised).
+
+---
+
+## [HIGH] 1. ViewPanel is a god class — 10 distinct responsibilities
+
+`gui/ViewPanel.java` (1611 lines). Responsibilities, with evidence:
+
+1. AWT/Swing integration — `getPreferredSize/getMinimumSize/getMaximumSize` (420-432),
+ `paint/update` overrides (537-543), `initializeCanvas` (545-556), `addNotify` (558-562),
+ `ensureBufferStrategy` (564-594).
+2. Frame-loop & FPS pacing — `renderLoop` (1444-1456), `maintainTargetFps` (1464-1485),
+ `ensureThatViewIsUpToDate` (1494-1521), `noteFrameBlitted` (987-1001).
+3. Pipeline scheduling (transform/sort/bin/paint triple-buffered passes) — `renderFrame`
+ (598-652), `transformPass` (1094-1132), `submitPaintPass` (1146-1273),
+ `flushPendingPaint` (800-877), `flushCompletedPasses` (889-901), `PendingPaint`/`PresentJob`
+ (240-266).
+4. Presentation thread + mailbox — `presentFrame` (671-684), `presentLoop` (703-742),
+ `blitFrame` (744-791), mailbox fields (198-208), `PRESENT_RATE_LIMIT_FPS` (223-224).
+5. Stereo configuration & eye-offset math — fields (1029-1039), `transformPass` IPD offset
+ (1100-1104), stereo accessors (1046-1080).
+6. Thread-pool lifecycle — `getOrCreateTransformExecutor` (1295-1321),
+ `defaultRenderThreadCount` (1284-1287), plus the dead `renderExecutor` machinery
+ (see finding 7).
+7. Input device hot-plug — `initializeHeadTracking` (1346-1352), `initializeSpaceMouse`
+ (1360-1366).
+8. Developer tools integration — `showDeveloperToolsPanel` (516-535), segment-boundary
+ overlay drawn inline into the framebuffer (849-869).
+9. Lighting & GI lifecycle — `lightingManager` (145), `enableGlobalIllumination` (490-510).
+10. Raster helpers that belong to the renderer — `clearSegmentPixels` (911-926),
+ `combineMouseResults` (928-941).
+
+Refactor sketch: extract (a) `PipelineScheduler` owning renderFrame/transformPass/
+submitPaintPass/flush* + pendingPaints/passCounter/frameContexts (~550 lines), (b)
+`FramePresenter` owning mailbox/presentLoop/blitFrame/PresentJob (~150 lines), (c)
+`DeviceHotplug` for headtrack+spacemouse init/stop (~60 lines). ViewPanel keeps AWT glue,
+public API delegating to the three. The four participants communicate through
+RenderingContext fields that already exist.
+
+Risk: HIGH but contained to the engine — no rasterizer math touched, so golden pixels are
+untouched by construction. The risk is concurrency regression in the pipeline itself; mitigated
+by the `aukio3d.pipeline=false` kill switch (232-233) and the existing
+ParallelTransformTest/SegmentBinningTest. Move code verbatim first (no logic edits),
+verify with the demos' HouseGoldens + Fo4Shot runs.
+
+## [HIGH] 2. Package structure is fictional — total cycle, renderer↔gui inverted
+
+The import graph over top-level packages contains every possible cycle: gui↔renderer,
+geometry↔renderer, geometry↔gui, math↔gui, math↔geometry (computed over all 152 files).
+
+Root cause: the renderer's core types live in `gui`:
+- `gui/RenderingContext.java`, `gui/SegmentRenderingContext.java`, `gui/HiZPyramid.java`,
+ `gui/CullingStatistics.java`, `gui/ThreadActivityRecorder.java`.
+- 15 renderer files import `gui.*`, e.g. `renderer/raster/RenderAggregator.java:7`,
+ `renderer/raster/ShapeCollection.java:9-12` (imports gui.CullingStatistics, gui.Camera,
+ **gui.ViewPanel** — the renderer depends on the AWT Canvas),
+ `renderer/raster/shapes/basic/texturedpolygon/TriangleMeshBlock.java:6` (gui.HiZPyramid).
+- 5 gui files import renderer (ViewPanel:21-23, RenderingContext:11+18, GuiComponent:14-16,
+ LookAndFeel:7, TextEditComponent:13) → direct gui↔renderer cycle.
+- geometry depends upward: `geometry/BspTree.java:7` and `geometry/Plane.java:8` import
+ renderer...SolidPolygon; `geometry/Frustum.java:7` imports gui.Camera;
+ `geometry/Point3D.java:7` imports renderer.octree.IntegerPoint (for one convenience
+ constructor, Point3D.java:121).
+- `math/Vertex.java:9` imports gui.RenderingContext.
+
+Refactor sketch (mechanical, move-only): relocate RenderingContext, SegmentRenderingContext,
+HiZPyramid, CullingStatistics, ThreadActivityRecorder, StereoEye to `renderer.raster` (or a new
+`renderer.core`); move `gui.Camera`+`gui.ViewSpaceTracker` to geometry/math (Camera is pure
+transform state). Break geometry→renderer by moving BspTree to
+renderer...shapes.basic.solidpolygon (it stores SolidPolygon) or abstracting its element type.
+Fix Point3D→IntegerPoint by moving that constructor to IntegerPoint or a small adapter.
+ShapeCollection's ViewPanel dependency is only `viewPanel.getCamera()`
+(ShapeCollection.java:302-305) — the Camera overload already exists (314), so the ViewPanel
+overload is a convenience that could delegate from gui side or take a Camera.
+After moves, the intended order math ← geometry ← renderer ← gui/headless becomes real.
+
+Risk: LOW for correctness (no code changes, only moves + import updates), MEDIUM for consumers:
+three downstream repos must update imports. Public API breakage is the cost; do it in one sweep
+with a consumer-side sed. Golden tests unaffected (no FP code moves between classes in a way
+that changes execution order — class relocation has no runtime effect).
+
+## [HIGH] 3. Verified dead code — 6 classes + 2 fields (~340 lines)
+
+Grepped simple-name references across engine main+test and all three consumer repos:
+
+- `geometry/Circle.java` (30 LOC) — no references anywhere.
+- `gui/ViewUpdateTimerTask.java` (31 LOC) — no references; legacy of the pre-render-thread
+ design (its `run()` calls the now-package-private `ensureThatViewIsUpToDate`).
+- `gui/humaninput/Connexion3D.java` (48 LOC) — standalone /dev/hidraw experiment with its own
+ `main()`; superseded by `gui/spacemouse/SpaceNavigatorHid`.
+- `renderer/octree/raytracer/RayHit.java` (56 LOC) — unreferenced even inside the raytracer.
+- `renderer/raster/shapes/composite/solid/SolidPolygonMesh.java` (60 LOC) — unreferenced.
+- `renderer/raster/shapes/composite/wireframe/WireframeDrawing.java` (75 LOC) — unreferenced.
+- `ViewPanel.renderExecutor` + `ensureExecutorMatchesThreadCount()` (ViewPanel.java:121,
+ 1327-1339, 1406): created, resized, shut down — but **never used to submit a single task**;
+ `submitPaintPass` uses `getOrCreateTransformExecutor()` (1155). Legacy of the pre-ForkJoinPool
+ design. ~35 dead lines including the eager `Executors.newFixedThreadPool(...)` allocation at
+ construction time (121) — a real thread pool allocated and thrown away per ViewPanel.
+- `ViewPanel.renderFrameCount` (596, incremented at 607): static, write-only, never read.
+
+Refactor sketch: delete all of the above.
+Risk: ZERO for the classes/fields (no references exist, no reflection in consumers). Keep a
+note that Connexion3D's main() is a hardware experiment if the maintainer wants it archived.
+
+## [HIGH] 4. AbstractCompositeShape carries an embeddable CSG engine + parallel-transform machinery
+
+`renderer/raster/shapes/composite/base/AbstractCompositeShape.java` (1295 lines). Responsibilities:
+
+1. Sub-shape registry + group visibility (88-133, 179-195, 335-377, 756-764).
+2. Render-list caching & triangulation (775-821, 886-910).
+3. CSG boolean engine (~240 lines, fully self-contained): `extractSolidPolygons` (304-315),
+ `union` (549-574), `subtract` (597-631), `intersect` (654-683), `clonePolygons` (694-700),
+ `replaceSolidPolygons` (709-725), `mergeNonPolygonChildrenFrom` (735-748).
+4. Bounding-box aggregation (230-280).
+5. Frustum culling inline in `transform` (912-997; AABB corner transform at 929-971).
+6. Parallel-transform machinery (~280 lines): weight-cache fields (1026-1077),
+ `getTransformWeight` (1095-1120), `shouldForkTransform` (1132-1140),
+ `transformChildrenParallel` (1192-1275), constants (1003-1020, 1147).
+7. Bulk property fan-out via instanceof chains — 8 sites: `setColor` (397-409),
+ `setShadingEnabled` (494-504), `setBackfaceCulling` (514-526),
+ `setMouseInteractionController` (422-432), plus 254, 308-311, 478, 791-807, 851-856.
+
+Also: `transformChildrenParallel(…, aggregator, …)` takes a **dead parameter** — its own
+javadoc admits it (1187-1190: "unused in the parallel path"), and the body never uses it.
+
+Refactor sketch: (a) move the CSG block to a new `Csg` utility class in the same package —
+all methods take/return `List<SolidPolygon>` plus the registry mutations already isolated in
+`replaceSolidPolygons`/`mergeNonPolygonChildrenFrom`; AbstractCompositeShape keeps three
+one-line delegating methods for API compatibility. (b) Extract the weight/fork machinery into
+`ParallelTransformPlanner` (fields + getTransformWeight + shouldForkTransform + chunking),
+leaving `transform()` as orchestration. (c) Drop the dead `aggregator` parameter from
+`transformChildrenParallel`. Result: ~1295 → ~700 lines.
+
+Risk: LOW-MEDIUM. CSG is call-graph-isolated (only BspTree + registry), no FP-order-sensitive
+output (CSG is geometry, not rasterization — pixel risk none; geometry-identical because the
+code moves verbatim). The parallel planner touches concurrency — move verbatim, keep field
+semantics; verified by ParallelTransformTest.
+
+## [MEDIUM] 5. RenderAggregator: cohesive, but three separable machines
+
+`renderer/raster/RenderAggregator.java` (861 lines). Responsibilities:
+
+1. Shape queue + merge: `queueShapeForRendering` (703-706), `mergeFrom` (716-720),
+ `mergeAllParallel` (734-795), `reset` (831-838).
+2. Sorting with three strategies (~210 lines): `tryRadixSort` (187-228), `parallelMergeSort`
+ (235-280), `runSortTasks` (283-308), `mergeRuns` (311-322), `awaitAll` (324-334), driver
+ `sort` (131-174).
+3. Tile binning CSR (~280 lines): fields (400-418), `matchingBinIndex` (427-439), `matchAxis`
+ (452-463), `binForTiles` (500-523), `buildBins` (543-593), `binPhase` (596-634),
+ `binRangeCsr` (642-683).
+4. Two-pass paint orchestration: `paintSorted` (353-366), `paintRange` (370-397).
+
+Refactor sketch: extract `ShapeSortMachine` (sort strategies + scratch arrays) and
+`TileBinMachine` (CSR fields + build/match), aggregator keeps queue + paint and holds one of
+each. Also drop `implements Serializable` on the comparator (844) — nothing serializes it.
+Note the sort order contract (Z desc, shapeId asc) is the bit-exactness backbone for
+binning/paint; extraction must move code verbatim.
+
+Risk: LOW (pure control flow, no FP math; order preserved by construction). Verified by
+SegmentBinningTest + HouseGoldens.
+
+## [MEDIUM] 6. Rasterizer duplication: 7 span writers, 3 interpolator classes, 2 blend formulas
+
+Interpolators — three classes with byte-identical cores:
+`LineInterpolator` (solidpolygon), `PolygonBorderInterpolator` (texturedpolygon),
+`PerspectiveBorderInterpolator` (texturedpolygon). Identical: `containsY` (LineInterpolator:
+84-88 = PolygonBorderInterpolator: 85-89 = PerspectiveBorderInterpolator: 101-105), the
+`getX` formula (`round(p1.x + (width*(y-p1.y))/height)`, LineInterpolator:100-105 vs
+PolygonBorderInterpolator:129-134), `setPointsZW` (LineInterpolator:130-134 =
+PolygonBorderInterpolator:182-186 = PerspectiveBorderInterpolator:137+).
+
+Span/line writers — 7 sites: TexturedTriangle's `drawHorizontalLinePerspectiveZ` (223-405),
+`drawHorizontalLineSdf` (968-1086), `drawHorizontalLinePerspectiveSdf` (1093-1263),
+`drawHorizontalLineZ` (1312-1429); SolidPolygon's `drawHorizontalLine` (305-386); Line's two
+single-pixel painters (180-233, 242-298). Within TexturedTriangle alone:
+- the SDF bilinear-fetch + coverage block is byte-identical twice (1023-1066 vs 1197-1240, ~45 lines);
+- the adaptive-interval ladder is byte-identical twice (322-335 vs 1163-1177);
+- the edge-pair Y-scan loop appears 4× (698-708, 927-937, 950-960, 1293-1303), a 5th in
+ SolidPolygon (456-468);
+- the yTop/yBottom clamp block appears 2× in-file (467-486 vs 596-615), a 3rd in SolidPolygon
+ (425-440);
+- the swap-left/right + clamp preamble appears 4× (239-260, 986-994, 1117-1127, 1327-1346).
+
+Two *different* alpha-blend approximations exist: SolidPolygon/Line use
+`((dest*bgAlpha) + src*alpha) >> 8` (SolidPolygon:376-378, Line:223-225, 289-291);
+TexturedTriangle uses `dest + ((alpha*(src-dest) - dest) >> 8)` (386-388 and 3 more sites).
+**They are not pixel-equivalent** — unifying span writers would change golden output.
+
+Refactor sketch (ordered by safety):
+(a) SAFE: unify the three interpolators into one `EdgeInterpolator` — keep the exact FP
+expression shapes (`(width * (y - p1.y)) / height`, midpoint for `|height| < EPSILON`), only
+collapsing storage. No arithmetic changes → bit-exact.
+(b) SAFE: extract the SDF bilinear fetch + coverage evaluation into one private static helper
+called from both SDF writers — it is already byte-identical, so extraction cannot change
+output if the expression text moves verbatim (watch implicit constant folding: keep `int`
+casts and shifts identical).
+(c) SAFE: single-source the edge-pair Y-scan loop as a small static helper taking the three
+interpolators + a span callback — control-flow only.
+(d) UNSAFE without re-golden: merging the two blend formulas. Don't — instead add a comment at
+each site naming the formula and why they differ, or standardize deliberately and re-bless
+goldens as a separate, announced change.
+
+Risk: (a)-(c) LOW if done as verbatim text motion (FP op order preserved); (d) is a visible
+output change — treat as a feature, not a refactor. HouseGoldens/Fo4Shot are the gate.
+
+## [MEDIUM] 7. RenderingContext: ~45 fields in the wrong package
+
+`gui/RenderingContext.java` (707 lines). 43 instance fields + 2 statics; 22 public non-final
+(measured). Natural clusters:
+
+- Framebuffer: bufferedImage (158), pixels (91), depth (99), graphics (78), segmentGraphics
+ (85), width/height (126/131).
+- Projection: centerCoordinate (137), projectionScale (144), nearPlaneDistance (184),
+ frustum (289), viewerPosition (298).
+- Tile/viewport geometry: tilesX/tilesY/viewportCount/numRenderSegments (63-72),
+ renderMinY/MaxY (150/156, **final**), renderMinX/MaxX (274/281, **mutable** — asymmetric,
+ mutated post-construction at ViewPanel:1224-1225), stereo* (255-267).
+- Pipeline bookkeeping: vertexSlot (176), frameNumber (190), transformCycleId (166),
+ presentGate (344), depthPass (109), transformExecutor (324), transformCoordinator (333),
+ lastTransformTaskCount (350).
+- Culling: subpixelCullingThreshold/Epoch (198/208), cullingStatistics (306),
+ occlusionPyramid (316).
+- Mouse picking (~120 lines incl. methods 627-705): mouseEvent (222),
+ objectPreviouslyUnderMouseCursor (213), currentObjectUnderMouseCursor (226),
+ currentMouseTextureU/V (231-232).
+- Services smuggled through: developerTools (237), debugLogBuffer (243), lightingManager (250).
+
+Belongs elsewhere: mouse-picking state+methods → `MousePickState` (owned by context, one
+field); `presentGate` → pipeline glue owned by the scheduler/ViewPanel (it is only written at
+ViewPanel:617-619 and awaited in the paint continuation); `depthPass` → RenderAggregator
+passes it to shapes via context purely as a side-channel; `lastTransformTaskCount` →
+diagnostics bundle; `lightingManager`/`developerTools`/`debugLogBuffer` → a small
+`FrameServices` bag would cut constructor/copy-constructor duplication.
+
+Also note the two copy constructors (442-470, 482-521) enumerate fields by hand and already
+diverge (the pass copy omits frustum "by design" — comment 520 — and silently drops
+presentGate/lastTransformTaskCount). Every new field must be added in up to 3 places; this is
+where the next pipeline bug comes from.
+
+Refactor sketch: (1) move class to renderer.raster (see finding 2); (2) extract MousePickState
+(whole methods move); (3) move presentGate to the scheduler; (4) cluster remaining fields into
+final sub-objects (Framebuffer, ViewportGeometry) so the copy constructors shrink to field
+copies of immutable parts + shallow shares.
+
+Risk: LOW-MEDIUM — all mechanical, but the class is read by ~every shape; keep accessors as
+delegates so shape code (`renderBuffer.pixels`, `.depth`, `.renderMinX`…) is untouched until a
+second pass updates call sites. No FP code → goldens safe. The public-field style means
+consumers may read these fields directly; keep the same field names visible during transition.
+
+## [MEDIUM] 8. OctreeVolume: 560-line copy-paste tracer + fully public internals
+
+`renderer/octree/OctreeVolume.java` (1102 lines), used only by demos' OctreeDemo.
+
+- Raw storage exposed: `public int[] cell1..cell8` (42-56), `public int cellAllocationPointer`
+ (61), `usedCellsCount` (64), `masterCellSize` (67), plus `initWorld` (298) re-allocating the
+ arrays under any concurrent reader. Invariants (cell state encoding -1/-2, 0 = null child)
+ are unenforceable.
+- `doesIntersect` (133-248): 7 near-identical ~16-line face slabs.
+- `traceCell` (512-1100): 8 octant branches × up to 7 recursive-probe stanzas each (~12 lines
+ per stanza) — ~560 lines of mechanical copy-paste with hand-maintained visit orders
+ ("// 6 8 3 5 2 4 1", 533).
+- `getNewCellPointer` (335-350): linear-scan allocator with wraparound; infinite-loops when the
+ buffer is full (no exhaustion check) — a latent hang, not just style.
+
+Refactor sketch: (a) represent the 8 children as `int[][] children` or one `int[8]` per cell
+indexed by octant — the putCell sub-cube selection (411-461) and traceCell collapse to loops
+over octant index with a precomputed per-ray-octant visit-order table (8 orders × 8 octants,
+64 entries, generated once). That alone removes ~500 lines. (b) Encapsulate arrays; add
+exhaustion failure in getNewCellPointer (return -1 / throw) instead of an infinite loop.
+(c) Optionally extract the ray-trace half (doesIntersect/traceCell) into `OctreeRayWalker`.
+
+Risk: LOW correctness-wise for (b) and the hang fix; MEDIUM for (a) — visit order affects
+*which* cell is returned first for ties, and OctreeDemo visuals depend on it. Not covered by
+golden tests (octree is not in the golden harnesses), so verify by side-by-side OctreeDemo
+screenshots. This subsystem is demo-only; alternatively quarantine it as-is and spend effort
+elsewhere.
+
+## [LOW] 9. AbstractCoordinateShape: slot-explosion + internal duplication
+
+- 12 scalar fields + 3 lists encode "screen state × 3 slots" (83-110, 165-175); accessors
+ repeat the same 3-way ternary 7 times (218-271).
+- `setSlotScreenState` (126-148) and the write block inside `transform` (503-521) are the same
+ 3-way branch twice — `transform` could call `setSlotScreenState(slot, …)` (one-line fix).
+- Stale docs: six accessors say "slot (0 or 1)" (226, 236, 246, 256, 266, 276) though slots are
+ 0/1/2.
+
+Refactor sketch: introduce `private final ScreenState[] slots = {new ScreenState(), …}` (z,
+minY, maxY, minX, maxX, clippedVertices) — collapses 15 fields to 1 array + deletes 7 branchy
+accessors. Keep public method signatures. Risk: ZERO pixel risk (pure storage, no FP
+expressions), LOW merge risk; internal-only.
+
+## [LOW] 10. API surface & documentation drift
+
+- `ViewPanel.setFrameRate` (957) vs `getTargetFPS` (966) — setter/getter name mismatch
+ (setTargetFPS expected).
+- TexturedTriangle javadoc links to methods that no longer exist: `{@link
+ #drawHorizontalLinePerspective}` (214), `{@link #drawHorizontalLine}` (318, 1090, 1308,
+ 1354, 1381) — the class has only the …Z/…Sdf variants (verified: no `drawHorizontalLine(`
+ definition). Javadoc build emits warnings for these.
+- Stale pipeline comments on the most subtle code: "Double-buffered frame contexts" (ViewPanel
+ 160) over a 3-element array (165); "Parity (0/1)" (167) while `frameParity = (frameParity +
+ 1) % 3` (645); duplicated/orphaned javadoc stub above `getInputManager` (394-402).
+- ShapeCollection slot docs say "(0 or 1)" (428, 469); a commented-out code block + TODO
+ (336-337); slot-0-only helpers (`sortShapes()` 421, `getQueuedShapeCount` 499,
+ `getBinSizes` 520) beside slot-parameterized siblings — test-only callers, fine, but mark
+ them as such.
+- Direct field poke across classes: `AbstractCompositeShape.setColor` writes
+ `((Line) shape).color = color` (404) into Line's public field (Line.java:61) instead of a
+ setter — bypasses any future invalidation logic.
+- `RenderingContext.bufferedImageType` (48) — constant not in CONSTANT_CASE.
+- `RenderAggregator.ShapesZIndexComparator implements Serializable` (844) — nothing
+ serializes it; also `import java.io.Serializable` (10).
+
+Risk: ZERO-LOW. All are comment/name/mechanical fixes; renaming setFrameRate would break
+consumers — prefer adding a correctly-named alias and deprecating.
+
+---
+
+## Verdict
+
+**Yes — the architecture is sound for a software rasterizer of this size.** The load-bearing
+structure (transform/sort/bin/paint phases, triple-buffered pipeline with per-slot vertex
+state, painter + z-buffer two-pass visibility, Hi-Z, SoA TriangleMeshBlock) is deliberately
+designed, and unusually well documented: the comments record *measurements and dates* (e.g.
+ViewPanel:113-116, 219-221, 1299-1309; AbstractCompositeShape:1009-1012), which is exactly the
+evidence-based culture that keeps a performance codebase honest. The problems are not the
+design but **entropy concentrated in identifiable places**: ViewPanel's ten responsibilities,
+RenderingContext's 45-field blob sitting in the wrong package (which drags the whole import
+graph into cycles), and hand-maintained copy-paste in the span writers and the octree tracer.
+None of these threaten correctness today; all of them raise the cost of the *next* change.
+The recommended program is: (1) delete the verified dead code (free), (2) package relocation +
+RenderingContext/ViewPanel extraction (mechanical, no FP risk), (3) safe de-duplication only —
+interpolators, identical SDF block, scan-loop helper — leaving the two blend formulas alone,
+(4) extract CSG and the transform planner from AbstractCompositeShape. Every step except
+octree rework is gateable by the existing golden harnesses with bit-exact expectations.
--- /dev/null
+# aukio-3d performance review — what's left on the table
+
+Read-only static analysis, 2026-09-20. Scope: span writers, AoS-vs-SoA split, sort/bin/transform,
+memory layout, threading, SDF text path, GI/octree. All line numbers against current HEAD.
+
+Known-good state (verified, not re-litigated): parallel chunk transform with pooled scratch,
+radix sort over packed long keys, CSR tile bins, tiled MT paint with two-pass z-buffer,
+Hi-Z block culling, subpixel epoch cache, TriangleMeshBlock SoA transform loop.
+
+---
+
+## Tier 1 — multi-ms/frame class at 500k queued triangles
+
+### 1. Radix sort runs single-threaded; the executor handed to `sort()` is ignored in the radix path
+`RenderAggregator.sort(ExecutorService)` (`RenderAggregator.java:162-165`):
+```java
+if (executor != null && sortedCount >= PARALLEL_SORT_THRESHOLD) {
+ if (!tryRadixSort(sortedArray, sortedCount)) // <- executor NOT passed
+ parallelMergeSort(sortedArray, sortedCount, comparator, executor);
+```
+`tryRadixSort` (`RenderAggregator.java:187-228`) is one serial loop: key build
+(`:193-196`, one virtual `getZ(slot)` per shape), then `RadixLongSort.sortPairs`
+(`RadixLongSort.java:66-98`) = 8 LSD passes, each streaming 500k×(8B key + 4B idx) read +
+12B scatter-write ≈ 96 MB of traffic on ONE core, then a serial random-gather permute
+(`:224-226`) plus a full `System.arraycopy` back. This is the measured ~17 ms sort.
+Bonus: `tryRadixSort` ends `return true` unconditionally — `parallelMergeSort` is dead code.
+
+- Why slow: serial memory-bound passes on one core while 17 workers idle (sort sits between
+ drain and bin in the paint continuation — `ViewPanel.java:1200-1205` — so it directly
+ delays paint-task submission every pass).
+- Change sketch: parallel LSD radix on the same pool — per-chunk 256-bin histograms (parallel),
+ serial 256×chunks prefix, parallel stable scatter with per-(chunk,digit) offsets. Fixed chunk
+ boundaries + stability = bit-identical output to today (keys exact, equal-key order preserved,
+ tie-run shapeId fix unchanged). Also: permute into the *other* grow-only buffer and swap
+ references instead of `arraycopy` back (saves one 4 MB serial copy). Even simpler first step:
+ parallelize only the key build by folding it into `mergeAllParallel`'s already-parallel copy
+ (`RenderAggregator.java:755-790`) — compute `zSortKey` while copying each part.
+- Impact class: sort 17 ms → ~4-6 ms wall. The largest single remaining pipeline lever.
+
+### 2. Per-triangle raster setup is recomputed per overlapped tile, per frame
+`TexturedTriangle.paintFlat` (`TexturedTriangle.java:621-628`):
+```java
+final double edge12 = projectedPoint1.getDistanceTo(projectedPoint2); // sqrt
+final double edge13 = projectedPoint1.getDistanceTo(projectedPoint3); // sqrt
+final double edge23 = projectedPoint2.getDistanceTo(projectedPoint3); // sqrt
+final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
+final TextureBitmap mipmap = texture.getMipmapForScale(scaleFactor);
+```
+plus the perspective setup (`:673-682`, three `1d/z` divides + muls) and the per-span curvature
+ladder (`:322-335`, ~7 divides per span). All of this depends only on transform-phase outputs,
+yet `paint()` runs once per overlapped tile — a triangle in 4 tiles pays it 4×.
+Worse on the object path: `paintTriangle` (`:492-497`) computes the same three `getDistanceTo`
+values, then `paintFlat` recomputes them — 6 sqrt per tile-paint for non-SDF triangles.
+And `MeshTriangle.paint` (`MeshTriangle.java:109`) calls `block.origTtd(index)`
+(`TriangleMeshBlock.java:444-456`) = 3 sqrt over the *final, build-time* `uv[]` array,
+recomputed every paint of every tile of every frame.
+
+- Why slow: sqrt ~15-20c each; at ~150k painted triangles × ~1.5 tiles × (3-6 sqrt + divides)
+ ≈ 30-60M cycles/frame aggregate paint-side setup that is definitionally redundant.
+- Change sketch (bit-exact — same expressions, evaluated once instead of N times):
+ compute `scaleFactor`/mip level/affine-vs-perspective flag once per triangle per slot in
+ `TriangleMeshBlock.transform` (mesh path) / `AbstractCoordinateShape.transform` (object path),
+ stash in per-slot handle state, pass into `paintFlat`. Precompute `origTtd` into a
+ `double[]` at block build (uv is final). In `paintTriangle`, pass the already-computed
+ `scaleFactor` down instead of recomputing.
+- Impact class: ~1-3 ms/frame aggregate at FO4 queue sizes; larger for big-screen triangles
+ (text quads, terrain near the camera) that span tens of tiles.
+
+### 3. Scanline edge evaluation: per-getter divisions + per-scanline edge re-selection
+`PerspectiveBorderInterpolator.getSU/getSV/getSW/getZW` (`PerspectiveBorderInterpolator.java:112-148`)
+each call `interpolationT()` = `(currentY - p1.y) / height` — a **double division per getter**.
+`drawHorizontalLinePerspectiveZ` (`TexturedTriangle.java:233-259`) calls getX + 4 channel getters
+per edge per scanline: up to 10 divisions/scanline if C2 doesn't CSE them across the inlined
+getters, 4 if it does (getX's `(width * (currentY - p1.y)) / height` is a different expression
+than `width*t`, so it never shares). Same shape in `PolygonBorderInterpolator`
+(`:98-119,129-134,189-194`) and `LineInterpolator.getX/getZW` (`:100-105,144-149`).
+Additionally `containsY` (`PerspectiveBorderInterpolator.java:101-105`) recomputes
+`Math.min/max(p1.y,p2.y)` per call, and the triangle y-loop (`TexturedTriangle.java:698-708`)
+re-tests 2-3 `containsY` per scanline to re-derive which edge pair is active — when the pair
+only changes once, at the middle vertex.
+
+- Why slow: double div ~13-20c; even at the CSE-friendly 4/scanline that's 50-80c/scanline of
+ division alone. At 500k queued tris with mean height ~5-15 scanlines, several M scanlines/frame
+ → multiple ms of pure edge math, concentrated on exactly the small distant triangles that
+ dominate the FO4 scene.
+- Change sketch:
+ a) bit-exact: cache `minY/maxY` at `setPoints`; make one explicit `t = (currentY-p1.y)/height`
+ per edge per scanline and pass it to the four channel reads (identical expression →
+ identical bits, and no longer JIT-CSE-dependent).
+ b) needs-tolerance: express `x = p1.x + width*t` (removes the second div; differs by ≤1ulp
+ from `(width*dy)/height` — golden tolerance should absorb, verify).
+ c) bit-exact with care: split the y-loop into [yTop..yMid] and (yMid..yBottom] halves with the
+ edge pair fixed per half (the current inclusive `containsY` semantics pin which pair owns
+ the seam scanline — replicate). Removes ~3 containsY + min/max per scanline.
+- Impact class: ~2-5 ms/frame aggregate at small-triangle-heavy views.
+
+---
+
+## Tier 2 — ~0.5-2% frame time each, cheap and safe
+
+### 4. Depth margin costs 2 muls + 1 sub per pixel even though it defaults to 0
+All four z-buffered span writers test
+```java
+if (zw > depth[offset] - RenderingContext.DEPTH_MARGIN_DZ * zw * zw)
+```
+`TexturedTriangle.java:356` (perspective-Z), `:1386` (affine-Z), `SolidPolygon.java:354,370`.
+`DEPTH_MARGIN_DZ` (`RenderingContext.java:120-121`) parses `-Daukio.zbuffer.margin`, default `"0"`.
+It's `static final` so C2 folds the constant to 0.0, but `0.0 * zw * zw` is NOT eliminable
+(NaN semantics), so every z-tested pixel pays two dependent muls + a sub for a disabled feature.
+
+- Change sketch (bit-exact: `0.0*zw*zw == +0.0` and `d - 0.0 == d` exactly for finite zw, which
+ near-plane clipping guarantees): hoist once per span —
+ `final boolean strict = DEPTH_MARGIN_DZ == 0;` and branch to a margin-free loop copy, or
+ duplicate the pixel loops under one perfectly-predicted branch. If margin>0 is ever used,
+ the quadratic term can also be strength-reduced (`m += (2*c*dzw)*zw + c*dzw*dzw`, both
+ coefficients span-invariant) — that variant reassociates FP, so tolerance-check it.
+- Impact class: ~1-1.5c/pixel on every z-tested pixel; at 2-4M tested pixels/frame ≈ 0.5-1%
+ frame time, for near-zero code risk.
+
+### 5. Hi-Z pyramid build is a serial full-depth-buffer sweep on the render thread; `occluded()` is `synchronized`
+`ViewPanel.flushPendingPaint` (`:825-829`) calls `HiZPyramid.buildFrom` on the render thread
+after each pass. `buildFrom` (`HiZPyramid.java:61-108`) min-pools the entire float depth buffer
+(8×8 tiles) single-threaded: 8.3 MB @1080p, ~14.7 MB @1440p, ~58 MB at the 4920×2960 stereo
+buffer — ~1-2 ms, up to ~5 ms, serial, before the next transform fork can start.
+And `occluded()` (`:121`) is `synchronized` — every `TriangleMeshBlock.transform` on every
+parallel chunk thread (`TriangleMeshBlock.java:184-216`) takes the same monitor, serializing
+block tests against each other and against the multi-ms build.
+
+- Change sketch: build level 0 per tile in the paint workers' epilogue (depth just written is
+ still in L2), reduce upper levels serially (tiny); publish the pyramid as an immutable
+ per-build snapshot behind a volatile reference and drop `synchronized` from `occluded()`
+ (reads are all from the snapshot). Telemetry `AtomicLong`s → LongAdder.
+- Impact class: ~1-2% @1440p mono, ~5% at 4K-class buffers; removes a contention edge that
+ scales with block count × worker count.
+
+### 6. SDF text path: 257×Math.pow + LUT allocation + 2×hypot + 2×log per triangle **per tile** per frame
+`TexturedTriangle.paintSdf` (`:789-961`) runs per tile-paint (a text canvas quad overlapping
+N tiles paints N times). Per call:
+- `Math.hypot` ×2 for footprints (`:820-821`) — hypot pays overflow-safe scaling;
+ `sqrt(x*x+y*y)` is 3-5× cheaper (≤1ulp difference: tolerance-check),
+- auto-gamma does `Math.log` ×2 (`:863-864`),
+- when minified (any text past arm's length): `covLut = new int[257]` + 257×`Math.pow`
+ (`:865-869`). For a terminal-sized canvas (~2 triangles × 50-100 tiles) that's
+ ~200 × (257 pow + 1 KB alloc) ≈ 2M cycles per frame per canvas.
+
+- Change sketch: cache the LUT — gamma is a smooth function of `maxFootprint`; quantize gamma
+ to 1/256 steps and keep a per-thread last-LUT (or a tiny `ConcurrentHashMap<Integer,int[]>`).
+ Hoist footprint/gamma/aaK to once per triangle per frame (paint-margin-free, so it's
+ tile-invariant). This is computation caching, not allocation pooling.
+- Inner loop: the fixed-point bilinear (`:1037-1038` and `:1211-1212`) uses 8 int muls; the
+ two-lerp form `top = m00*(256-fx)+m10*fx; bot = m01*(256-fx)+m11*fx; d=(top*(256-fy)+bot*fy)>>16`
+ is **bit-identical** (integer associativity; worst case 33.4M < 2^31, no overflow) at 4 muls.
+ Halves the mask-eval multiply count on every text pixel.
+- Impact class: workspace/terminal views (aukio TerminalPanel renders through this path):
+ ~1-3 ms/frame with several text canvases; FO4: negligible.
+
+---
+
+## Tier 3 — smaller or situational
+
+### 7. Two-pass paint visits every bin entry twice
+`RenderAggregator.paintSorted` (`:353-366`) iterates each tile's bin twice;
+`paintRange` (`:370-397`) virtual-calls `paint()` on every entry in both passes and the shape
+early-outs (`TexturedTriangle.paintFlat:563-565`, `SolidPolygon.paint:703-705`) only after a
+volatile texture load + field reads. One of the two visits is always waste: ~queue×tiles
+no-op dispatches per frame (~750k at 500k×1.5 tiles ≈ 0.5-1% frame).
+- Sketch: split each bin into [opaque|alpha] segments at CSR build time (two counts per tile —
+ binning order within each class preserved, so pixels are bit-identical); pass 1 walks the
+ opaque segment reversed, pass 2 the alpha segment forward.
+
+### 8. Mip-chain lazy build races across paint threads; level selection loops per paint
+`Texture.getDownscaledBitmap` (`Texture.java:278-291`) checks `downSampled[i] == null` and
+builds unsynchronized while 18 paint threads may first-touch the same level on the same frame
+→ duplicated full box-filter chain builds per texture (startup stutter on texture-heavy loads),
+and the array-slot write has no happens-before edge. `getDownscaleMipmapLevel` (`:180-189`)
+loops ≤8 iterations per triangle per tile-paint.
+- Sketch: synchronize per-texture on build (or prebuild chains at texture-load time in the
+ FO4 `TextureSource`); publish via the array only after construction (final fields make the
+ bitmap itself safe). Replace the halving loop with an exponent-based level pick
+ (`Math.getExponent`) — verify threshold equivalence for exact powers of two.
+
+### 9. Octree ray tracer (OctreeDemo path): allocation-per-face-test and non-parametric traversal
+`OctreeVolume.doesIntersect` (`OctreeVolume.java:133-248`) allocates a `new Point3D` (or
+`clone()`) on *every* face candidate — up to 6 allocations per cell visit — and
+`traceCell` (`:512-1100`) orders children by the **origin's octant**, not by parametric
+distance along the ray. Because the first solid hit returns immediately, this is both slower
+(no near-first pruning; every level re-runs 6-division slab tests per child from scratch) and
+suspect (can return a farther voxel over a nearer one when the origin is outside the parent
+cell). `RayTracer.run` (`RayTracer.java:157-175`) allocates a `Ray` + 2 `Point3D` + `Color`
+per pixel and does 3 divides/pixel for view-plane interpolation (`(x3p * x) / width` — should
+be incremental adds); `traceRay` then fires 6 shadow rays × 2 Point3D + Ray allocations per
+light. `getNewCellPointer` (`:335-350`) is a linear scan for a free cell — O(cells) per
+allocation, so filling big voxel volumes is O(n²).
+- Sketch (only if OctreeDemo matters; nothing in FO4 touches this): slab test with precomputed
+ per-ray inverse direction, hit point computed once from the winning t (no per-face allocs),
+ Revelles-style parametric child ordering, free-list for cells.
+- Impact: background/progressive demo renderer — not frame-critical.
+
+### 10. GI package: BVH build and query constant factors
+`TriangleBvh.build` (`TriangleBvh.java:92-96`) sorts at every tree level with a comparator →
+O(n log² n) build per snapshot rebuild (median selection via quickselect is O(n log n) and
+lambda-free). `rayBox` (`:172-198`) does 6 divisionss per node visit + NaN fixup branches —
+precompute `1/dx,1/dy,1/dz` once per ray and multiply (not bit-identical to division; GI
+output feeds lightmaps, not golden pixels — still verify). `nearestNode` (`:123-140`) visits
+left before right unconditionally; ordering children by nearer-box-first makes the `hit.t`
+bound prune the far child far more often (typically ~2× fewer node visits on nearest-hit).
+`GlobalIllumination.updateComposites` (`:610-637`) fills invalid lightmap texels with a
+`while(progressed)` whole-map rescan — O(texels × map diameter); a BFS/multi-source queue is
+O(texels). Per-sample `new double[3]` returns (`:537`, `:890`) churn on the GI threads —
+background threads, so low stakes, but free to remove.
+- Impact: GI convergence latency and background CPU burn only; render path is unaffected
+ (render-side GI cost is already ~zero by design).
+
+---
+
+## Question 2 answer: how much AoS is left, and is extending SoA worth it?
+
+FO4 (the 500k-tri target): **~100% mesh blocks.** `NifSceneBuilder.java:169-222` (nif parts) and
+`WorldStreamer.java:1309-1348` (terrain cells) both emit `TriangleMeshBlock` by default
+(`aukio.fo4.meshBlocks`, default true); the `new TexturedTriangle` fallbacks
+(`NifSceneBuilder.java:244`, `WorldStreamer.java:1420`) are behind the same flag.
+`SolidPolygon` is imported in WorldStreamer but never instantiated for geometry.
+
+AoS `Vertex`-graph shapes (SolidPolygon, Line, Billboard, SDF text quads, lightmapped
+triangles) carry 8-9 heap objects per vertex with per-slot pointer chasing — they remain in:
+- demos/benchmarks: SolidCubesTest 16³ cubes ≈ 49k triangles, LitSolidCubesTest, sphere scenes;
+- the Aukio workspace UI (TerminalPanel → TextCanvas → 2 SDF triangles per panel);
+- CSG/lightmapped content where per-shape objects are semantically needed.
+
+Verdict: extending SoA to a `SolidPolygonMeshBlock` would only move the cube/sphere demos —
+not the FO4 number. **Not worth it** for the stated target; the remaining AoS cost is in
+small-N UI/demo scenes where per-shape setup, not pointer chasing, dominates anyway.
+
+## Question 4 answer: memory layout
+
+Pixel buffer and depth buffer are both row-major with identical addressing
+(`y*width + x` everywhere) — span writes are unit-stride in both, tile clears use
+`Arrays.fill` per row (intrinsic-vectorized). No column-major striding anywhere in the hot
+paths. Texture reads (`ity*texW+itx`) stride by texture row — inherent to UV mapping and
+already mitigated by the mip chain. The only structural note: pixels and depth are two
+separate arrays, so each z-tested pixel write touches two cache lines; interleaving them
+would break `BufferedImage` blit interop — not recommended.
+
+## Question 5 answer: thread utilization
+
+Tiles = 10×workers, near-square (`ViewPanel.java:1548-1552`, TILES_PER_THREAD=10) with one
+task per tile on a work-stealing ForkJoinPool (75% of cores, measured choice) — sound.
+Per-pass `CountDownLatch(segments)` is one await per frame, negligible. Two notes: the paint
+continuation (`ViewPanel.java:1177-1268`) blocks a worker on `previousGate.await`
+(`CountDownLatch` — no FJP compensation) — at most a few parked workers, fine; and the
+serial sort inside that continuation is the real utilization gap (finding 1).
+
+---
+
+## Verdict
+
+The architecture is **not** at the pure-Java ceiling. The big structural pieces are right
+(SoA transform, radix keys, CSR bins, tiled two-pass z, Hi-Z), but there is one large and
+several medium wins left, all compatible with bit-exactness or within stated tolerance:
+
+**Top 3:**
+1. **Parallelize the radix sort** (it's single-threaded inside a pool it was explicitly
+ handed): ~17 ms → ~4-6 ms at 500k shapes. `RenderAggregator.java:162-228`.
+2. **Hoist per-triangle raster setup out of per-tile paint** (mip metric sqrt×3, `origTtd`,
+ perspective divides — computed once per slot, not once per tile-overlap):
+ `TexturedTriangle.java:492-497,621-682`, `MeshTriangle.java:109`. Plus the guaranteed-safe
+ half of the scanline-divide fix (cached min/max, one explicit shared `t` per edge).
+3. **Kill the default-disabled depth-margin math in the four z-span writers**
+ (strict-loop specialization, bit-exact) — ~0.5-1% of frame for a few lines;
+ and **de-serialize the Hi-Z build + lock-free `occluded()`** — ~1-2% at 1440p, more at 4K.
+
+(3 is two cheap items bundled; if forced to pick three single items: parallel radix, setup
+hoisting, scanline edge-evaluation restructure.)
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.geometry;
-
-/**
- * A circle in 2D space defined by a center point and radius.
- *
- * @see Point2D
- */
-public class Circle {
-
- /**
- * The center point of the circle.
- */
- Point2D location;
-
- /**
- * The radius of the circle.
- */
- double radius;
-
- /**
- * Creates a circle with default values.
- */
- public Circle() {
- }
-
-}
*/
private static final int TILES_PER_THREAD = 10;
- /** The executor service for parallel rendering. */
- private ExecutorService renderExecutor = Executors.newFixedThreadPool(defaultRenderThreadCount());
/** Number of render threads. Can be changed at runtime via {@link #setNumRenderThreads(int)}. */
private volatile int numRenderThreads = defaultRenderThreadCount();
/**
}
}
- private static int renderFrameCount = 0;
private void renderFrame() {
ensureBufferStrategy();
- ensureExecutorMatchesThreadCount();
if (bufferStrategy == null || renderingContext == null) {
debugLogBuffer.log("[VIEWPANEL] renderFrame ABORT: bufferStrategy=" + bufferStrategy + ", renderingContext=" + renderingContext);
return;
}
- renderFrameCount++;
ThreadActivityRecorder.setFrameParity(frameParity);
// Install this frame-use's present gate and capture the previous
return transformExecutor;
}
- /**
- * Recreates the executor service if the thread count has changed since last creation.
- * Called from the render thread only (no synchronization needed).
- */
- private void ensureExecutorMatchesThreadCount() {
- if (renderExecutor == null || renderExecutor.isShutdown()) {
- renderExecutor = Executors.newFixedThreadPool(numRenderThreads);
- return;
- }
- if (renderExecutor instanceof java.util.concurrent.ThreadPoolExecutor) {
- final java.util.concurrent.ThreadPoolExecutor tpe = (java.util.concurrent.ThreadPoolExecutor) renderExecutor;
- if (tpe.getCorePoolSize() != numRenderThreads) {
- tpe.shutdown();
- renderExecutor = Executors.newFixedThreadPool(numRenderThreads);
- }
- }
- }
-
/**
* Starts the head tracking hot-plug manager: RayNeo glasses are
* detected when plugged in (even after startup) and head look-around
dropped.gate.countDown();
}
pendingPaints.clear();
- renderExecutor.shutdownNow();
if (transformExecutor != null) {
transformExecutor.shutdownNow();
}
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.gui;
-
-/**
- * Timer task that updates view.
- *
- * Tries to keep constant FPS.
- */
-public class ViewUpdateTimerTask extends java.util.TimerTask {
-
- /** The view panel to update. */
- public ViewPanel viewPanel;
-
- /**
- * Creates a new timer task for the given view panel.
- *
- * @param viewPanel the view panel to update
- */
- public ViewUpdateTimerTask(final ViewPanel viewPanel) {
- this.viewPanel = viewPanel;
- }
-
- @Override
- public void run() {
- viewPanel.ensureThatViewIsUpToDate();
- }
-
-}
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.gui.humaninput;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-
-/**
- * I have Space Mouse Compact 3D Connexion mouse: https://3dconnexion.com/us/product/spacemouse-compact/
- *
- * I discovered that it is possible to read raw data from it by reading /dev/hidraw4 file.
- *
- * TODO: reverse engineer the data format and implement a driver for it.
- */
-
-public class Connexion3D {
-
- /**
- * Creates a new Connexion3D instance.
- */
- public Connexion3D() {
- }
-
- /**
- * Reads raw data from the 3Dconnexion device for testing purposes.
- *
- * @param args command line arguments (ignored)
- * @throws IOException if the device cannot be read
- */
- public static void main(final String[] args) throws IOException {
-
- final BufferedReader in = new BufferedReader(new FileReader(
- "/dev/hidraw4"));
-
-
- // for testing purposes
- while (true) {
- System.out.print(in.read() + " ");
- System.out.println("\n");
- }
-
- // in.close();
-
- }
-}
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.renderer.octree.raytracer;
-
-/**
- * Records the result of a ray-octree intersection test.
- *
- * <p>A {@code RayHit} stores the 3D world-space coordinates where a {@link Ray}
- * intersected an octree cell, along with a pointer (index) to the intersected cell
- * within the {@link eu.svjatoslav.aukio.e3d.renderer.octree.OctreeVolume}'s internal
- * cell arrays.</p>
- *
- * @see Ray
- * @see RayTracer
- * @see eu.svjatoslav.aukio.e3d.renderer.octree.OctreeVolume
- */
-public class RayHit {
-
- /**
- * The x coordinate of the intersection point in world space.
- */
- float x;
-
- /**
- * The y coordinate of the intersection point in world space.
- */
- float y;
-
- /**
- * The z coordinate of the intersection point in world space.
- */
- float z;
-
- /**
- * The index (pointer) into the octree's cell arrays identifying the cell that was hit.
- */
- int cellPointer;
-
- /**
- * Creates a new ray hit record.
- *
- * @param x the x coordinate of the intersection point
- * @param y the y coordinate of the intersection point
- * @param z the z coordinate of the intersection point
- * @param cellPointer the index of the intersected cell in the octree's cell arrays
- */
- public RayHit(final float x, final float y, final float z,
- final int cellPointer) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.cellPointer = cellPointer;
- }
-}
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite.solid;
-
-import eu.svjatoslav.aukio.e3d.geometry.Point3D;
-import eu.svjatoslav.aukio.e3d.renderer.raster.Color;
-import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
-import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
-
-import java.util.List;
-
-/**
- * A renderable mesh composed of SolidPolygon triangles.
- *
- * <p>This is a generic composite shape that holds a collection of triangles.
- * It can be constructed from any source of triangles, such as procedural
- * geometry generation or loaded mesh data.</p>
- *
- * <p><b>Usage:</b></p>
- * <pre>{@code
- * // From list of triangles
- * List<SolidPolygon> triangles = ...;
- * SolidPolygonMesh mesh = new SolidPolygonMesh(triangles, location);
- *
- * // With fluent configuration
- * shapes.addShape(mesh.setShadingEnabled(true).setBackfaceCulling(true));
- * }</pre>
- *
- * @see SolidPolygon the triangle type for rendering
- */
-public class SolidPolygonMesh extends AbstractCompositeShape {
-
- private int triangleCount;
-
- /**
- * Creates a mesh from a list of SolidPolygon triangles.
- *
- * @param triangles the triangles to include in the mesh
- * @param location the position in 3D space
- */
- public SolidPolygonMesh(final List<SolidPolygon> triangles, final Point3D location) {
- super(location);
- this.triangleCount = 0;
-
- for (final SolidPolygon triangle : triangles) {
- addShape(triangle);
- triangleCount++;
- }
- }
-
- /**
- * Returns the number of triangles in this mesh.
- *
- * @return the triangle count
- */
- public int getTriangleCount() {
- return triangleCount;
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Aukio 3D engine. Author: Svjatoslav Agejenko.
- * This project is released under Creative Commons Zero (CC0) license.
- */
-package eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite.wireframe;
-
-import eu.svjatoslav.aukio.e3d.geometry.Point3D;
-import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.line.Line;
-import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.line.LineAppearance;
-import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
-
-/**
- * A freeform polyline drawing tool that connects sequential points with line
- * segments. Points are added one at a time via {@link #addPoint(Point3D)};
- * each new point is connected to the previously added point by a line.
- *
- * <p>The first point added establishes the starting position without drawing
- * a line. Each subsequent point creates a new line segment from the previous
- * point to the new one.</p>
- *
- * <p>This shape is useful for drawing paths, trails, trajectories, or
- * arbitrary wireframe shapes that are defined as a sequence of vertices.</p>
- *
- * <p><b>Usage example:</b></p>
- * <pre>{@code
- * LineAppearance appearance = new LineAppearance(2, Color.YELLOW);
- * WireframeDrawing drawing = new WireframeDrawing(appearance);
- * drawing.addPoint(new Point3D(0, 0, 0));
- * drawing.addPoint(new Point3D(100, 50, 0));
- * drawing.addPoint(new Point3D(200, 0, 0));
- * shapeCollection.addShape(drawing);
- * }</pre>
- *
- * @see LineAppearance
- * @see AbstractCompositeShape
- */
-public class WireframeDrawing extends AbstractCompositeShape {
-
- /** The line appearance used for all segments in this drawing. */
- final private LineAppearance lineAppearance;
-
- /** The most recently added point, used as the start of the next line segment. */
- Point3D currentPoint;
-
- /**
- * Constructs a new empty wireframe drawing with the given line appearance.
- *
- * @param lineAppearance the line appearance (color, width) used for all
- * line segments added to this drawing
- */
- public WireframeDrawing(final LineAppearance lineAppearance) {
- super();
- this.lineAppearance = lineAppearance;
- }
-
- /**
- * Adds a new point to the drawing. If this is the first point, it sets the
- * starting position. Otherwise, a line segment is created from the previous
- * point to this new point.
- *
- * <p>The point is defensively copied, so subsequent modifications to the
- * passed {@code point3d} object will not affect the drawing.</p>
- *
- * @param point3d the point to add to the polyline
- */
- public void addPoint(final Point3D point3d) {
- if (currentPoint != null) {
- final Line line = lineAppearance.getLine(currentPoint, point3d);
- addShape(line);
- }
-
- currentPoint = new Point3D(point3d);
- }
-
-}