feat: add debug tools and documentation
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Fri, 20 Mar 2026 20:51:20 +0000 (22:51 +0200)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Fri, 20 Mar 2026 20:51:20 +0000 (22:51 +0200)
- Add F12 debug panel with toggleable diagnostic features
- Add alternate segments rendering for overdraw detection
- Add WindingOrderDemo example
- Add comprehensive Javadoc to geometry and shape classes
- Add rendering loop documentation with screenshots
- Add package-info.java for all major packages
- Rename DebugSettings to DeveloperTools
- Fix frame number tracking and buffer strategy initialization

77 files changed:
AGENTS.md
doc/Developer tools.png [new file with mode: 0644]
doc/Example.png [new file with mode: 0644]
doc/Minimal example.png [new file with mode: 0644]
doc/Winding order demo.png [new file with mode: 0644]
doc/index.org
doc/rendering-loop.org [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Box.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Circle.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point2D.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point3D.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Polygon.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/Rectangle.java
src/main/java/eu/svjatoslav/sixth/e3d/geometry/package-info.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/Camera.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/DebugLogBuffer.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperTools.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperToolsPanel.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/gui/GuiComponent.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/TextPointer.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewFrame.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewSpaceTracker.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewUpdateTimerTask.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/Connexion3D.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardHelper.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardInputHandler.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/MouseInteractionController.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/WorldNavigationUserInputTracker.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/package-info.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Character.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/LookAndFeel.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Page.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/TextEditComponent.java
src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java
src/main/java/eu/svjatoslav/sixth/e3d/math/Rotation.java
src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java
src/main/java/eu/svjatoslav/sixth/e3d/math/TransformStack.java
src/main/java/eu/svjatoslav/sixth/e3d/math/Vertex.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/IntegerPoint.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/OctreeVolume.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/CameraView.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/LightSource.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/RaytracingCamera.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/RenderAggregator.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/lighting/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineAppearance.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineInterpolator.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/LineInterpolator.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/PolygonBorderInterpolator.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/CanvasCharacter.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/TextCanvas.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/wireframe/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/slicer/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java
src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/package-info.java [new file with mode: 0644]
src/test/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java [new file with mode: 0644]

index 3ad2276..4299ec5 100644 (file)
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -121,7 +121,7 @@ All Java files must start with this exact header:
 - `ShapeCollection` is the root container with `RenderAggregator` and `TransformStack`
 - `RenderAggregator` collects projected shapes, sorts by Z-index, paints back-to-front
 - `ViewPanel` (extends `JPanel`) drives render loop, notifies `FrameListener` per frame
-- Backface culling uses screen-space normal Z-component
+- Backface culling uses signed area in screen space: `signedArea < 0` = front-facing
 
 ## Color
 
@@ -143,6 +143,6 @@ All Java files must start with this exact header:
 3. **Mutable geometry:** `Point3D`/`Point2D` are mutable — clone when storing references that shouldn't be shared
 4. **Render pipeline:** Shapes must implement `transform()` and `paint()` methods
 5. **Depth sorting:** Set `onScreenZ` correctly during `transform()` for proper rendering order
-6. **Backface culling:** Uses screen-space Z-component of normal; positive = front-facing
-7. **Polygon winding:** Counter-clockwise winding for front-facing polygons (when viewed from outside)
+6. **Backface culling:** Uses signed area in screen space; `signedArea < 0` = front-facing (CCW)
+7. **Polygon winding:** CCW in screen space = front face. Vertex order: top → lower-left → lower-right (as seen from camera). See `WindingOrderDemo` in sixth-3d-demos.
 8. **Testing:** Write JUnit 4 tests in `src/test/java/` with matching package structure
diff --git a/doc/Developer tools.png b/doc/Developer tools.png
new file mode 100644 (file)
index 0000000..e6c3208
Binary files /dev/null and b/doc/Developer tools.png differ
diff --git a/doc/Example.png b/doc/Example.png
new file mode 100644 (file)
index 0000000..7094240
Binary files /dev/null and b/doc/Example.png differ
diff --git a/doc/Minimal example.png b/doc/Minimal example.png
new file mode 100644 (file)
index 0000000..b2ceac7
Binary files /dev/null and b/doc/Minimal example.png differ
diff --git a/doc/Winding order demo.png b/doc/Winding order demo.png
new file mode 100644 (file)
index 0000000..e9f9e25
Binary files /dev/null and b/doc/Winding order demo.png differ
index c882fd4..4b7eafc 100644 (file)
@@ -66,7 +66,7 @@
 :ID:       a31a1f4d-5368-4fd9-aaf8-fa6d81851187
 :END:
 
-[[file:example.png]]
+[[file:Example.png]]
 
 *Sixth 3D* is a realtime 3D rendering engine written in pure Java. It
 runs entirely on the CPU — no GPU required, no OpenGL, no Vulkan, no
@@ -159,7 +159,7 @@ Add Sixth 3D to your pom.xml:
 :ID:       564fa596-9b2b-418a-9df9-baa46f0d0a66
 :END:
 
-Here is a minimal working example:
+Here is a [[https://www2.svjatoslav.eu/gitweb/?p=sixth-3d-demos.git;a=blob;f=src/main/java/eu/svjatoslav/sixth/e3d/examples/MinimalExample.java;h=af755e8a159c64b3ab8a14c8e76441608ecbf8ee;hb=HEAD][minimal working example]]:
 
 #+BEGIN_SRC java
   import eu.svjatoslav.sixth.e3d.geometry.Point3D;
@@ -188,17 +188,26 @@ Here is a minimal working example:
           shapes.addShape(box);
 
           // Position your camera
-          viewFrame.getViewPanel().getCamera().setLocation(new Point3D(0, -100, -300));
+          viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300));
 
-          // Update the screen
-          viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+          // Start the render thread
+          viewFrame.getViewPanel().ensureRenderThreadStarted();
       }
   }
 #+END_SRC
 
-Compile and run *MyFirstScene* class. New window should open that will
+Compile and run *MyFirstScene* class. A new window should open that will
 display 3D scene with red box.
 
+This example is available in the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][demo applications]] project. Run it directly:
+
+: java -cp sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.MyFirstScene
+
+You should see this:
+
+[[file:Minimal example.png]]
+
+
 *Navigating the scene:*
 
 | Input               | Action                              |
@@ -214,7 +223,16 @@ Movement uses physics-based acceleration for smooth, natural
 motion. The faster you're moving, the more acceleration builds up,
 creating an intuitive flying experience.
 
-* In-depth understanding
+* Defining scene
+:PROPERTIES:
+:ID:       4b6c1355-0afe-40c6-86c3-14bf8a11a8d0
+:END:
+
+- Note: To understand main render loop, see dedicated page: [[file:rendering-loop.org][Rendering
+  loop]]
+
+- Study [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][demo applications]] for practical examples.
+
 ** Vertex
 
 #+BEGIN_EXPORT html
@@ -244,7 +262,6 @@ position.
 - A triangle = 3 vertices, a cube = 8 vertices
 - Vertex maps to [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Point3D.html][Point3D]] class in Sixth 3D engine.
 
-
 ** Edge
 
 #+BEGIN_EXPORT html
@@ -412,35 +429,92 @@ A *mesh* is a collection of vertices, edges, and faces that together define the
 
 #+BEGIN_EXPORT html
 <svg viewBox="0 0 320 240" width="320" height="240">
-  <rect width="320" height="240" fill="#f8f8f8"/>
-  <polygon points="80,50 30,180 130,180" fill="rgba(48,160,80,0.15)" stroke="#30a050" stroke-width="1.5"/>
-  <path d="M95,80 C 120,80 130,130 110,155" fill="none" stroke="#30a050" stroke-width="1.5" stroke-dasharray="4 2"/>
-  <polygon points="110,155 115,143 104,148" fill="#30a050"/>
-  <text x="97" y="122" fill="#30a050" font-size="10" font-weight="700" font-family="monospace">CCW</text>
+  <defs>
+    <marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5"
+            markerWidth="8" markerHeight="8" orient="auto-start-reverse">
+      <path d="M 0 0 L 10 5 L 0 10 z" fill="#30a050"/>
+    </marker>
+    <marker id="arrow-red" viewBox="0 0 10 10" refX="10" refY="5"
+            markerWidth="8" markerHeight="8" orient="auto-start-reverse">
+      <path d="M 0 0 L 10 5 L 0 10 z" fill="rgba(208,64,64,0.5)"/>
+    </marker>
+  </defs>
+  <rect width="320" height="240" fill="#061018"/>
+  <!-- Green front-face triangle: V1=top, V2=bottom-left, V3=bottom-right -->
+  <polygon points="80,50 130,180 30,180" fill="rgba(48,160,80,0.15)" stroke="#30a050" stroke-width="1.5"/>
+  <!-- CCW arrow: arc from near V1, curves LEFT and DOWN toward V2 -->
+  <path d="M70,72 A 52,52 0 0,0 37,155" fill="none" stroke="#30a050" stroke-width="1.5" stroke-dasharray="4 2" marker-end="url(#arrow-green)"/>
+  <text x="34" y="120" fill="#30a050" font-size="10" font-weight="700" font-family="monospace">CCW</text>
   <circle cx="80" cy="50" r="3" fill="#30a050"/>
   <circle cx="30" cy="180" r="3" fill="#30a050"/>
   <circle cx="130" cy="180" r="3" fill="#30a050"/>
-  <text x="58" y="44" fill="#666" font-size="9" font-family="monospace">V₁</text>
-  <text x="14" y="198" fill="#666" font-size="9" font-family="monospace">V₂</text>
-  <text x="132" y="198" fill="#666" font-size="9" font-family="monospace">V₃</text>
+  <text x="78" y="44" fill="#aaa" font-size="9" font-family="monospace">V₁</text>
+  <text x="14" y="198" fill="#aaa" font-size="9" font-family="monospace">V₂</text>
+  <text x="132" y="198" fill="#aaa" font-size="9" font-family="monospace">V₃</text>
   <text x="36" y="220" fill="#30a050" font-size="11" font-weight="700" font-family="monospace">FRONT FACE ✓</text>
+  <!-- Red back-face triangle -->
   <polygon points="240,50 290,180 190,180" fill="rgba(208,64,64,0.06)" stroke="rgba(208,64,64,0.3)" stroke-width="1.5" stroke-dasharray="6 3"/>
-  <path d="M225,80 C 200,80 190,130 210,155" fill="none" stroke="rgba(208,64,64,0.5)" stroke-width="1.5" stroke-dasharray="4 2"/>
-  <polygon points="210,155 205,143 216,148" fill="rgba(208,64,64,0.5)"/>
-  <text x="210" y="122" fill="rgba(208,64,64,0.6)" font-size="10" font-weight="700" font-family="monospace">CW</text>
+  <!-- CW arrow: arc from near V1, curves RIGHT and DOWN -->
+  <path d="M250,72 A 52,52 0 0,1 283,155" fill="none" stroke="rgba(208,64,64,0.5)" stroke-width="1.5" stroke-dasharray="4 2" marker-end="url(#arrow-red)"/>
+  <text x="268" y="120" fill="rgba(208,64,64,0.6)" font-size="10" font-weight="700" font-family="monospace">CW</text>
   <line x1="228" y1="108" x2="252" y2="132" stroke="rgba(208,64,64,0.4)" stroke-width="3"/>
   <line x1="252" y1="108" x2="228" y2="132" stroke="rgba(208,64,64,0.4)" stroke-width="3"/>
   <text x="186" y="220" fill="rgba(208,64,64,0.7)" font-size="11" font-weight="700" font-family="monospace">BACK FACE ✗</text>
-  <text x="195" y="234" fill="#999" font-size="9" font-family="monospace">(culled — not drawn)</text>
+  <text x="195" y="234" fill="#aaa" font-size="9" font-family="monospace">(culled — not drawn)</text>
 </svg>
 #+END_EXPORT
 
-The order in which a triangle's vertices are listed determines its *winding order*. Counter-clockwise (CCW) typically means front-facing. *Backface culling* skips rendering triangles that face away from the camera — a major performance optimization.
-
-- CCW winding → front face (visible)
-- CW winding → back face (culled)
+The order in which a triangle's vertices are listed determines its
+*winding order*. In Sixth 3D, screen coordinates have Y-axis pointing
+*down*, which inverts the apparent winding direction compared to
+standard mathematical convention (Y-up). *Counter-clockwise (CCW)* in
+screen space means front-facing. *Backface culling* skips rendering
+triangles that face away from the camera — a major performance
+optimization.
+
+- CCW winding (in screen space) → front face (visible)
+- CW winding (in screen space) → back face (culled)
+- When viewing a polygon from outside: define vertices in *counter-clockwise* order as seen from the camera
 - Saves ~50% of triangle rendering
-- Normal direction derived from winding order via =cross(V₂-V₁, V₃-V₁)=
+- Implementation uses signed area: =signedArea < 0= means front-facing
+  (in Y-down screen coordinates, negative signed area corresponds to
+  visually CCW winding)
+
+
+*Minimal Example: WindingOrderDemo*
+
+The [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][sixth-3d-demos]] project includes a [[https://www2.svjatoslav.eu/gitweb/?p=sixth-3d-demos.git;a=blob;f=src/main/java/eu/svjatoslav/sixth/e3d/examples/WindingOrderDemo.java][winding order demo]] to
+demonstrate how winding order affects backface culling:
+
+#+BEGIN_SRC java
+// WindingOrderDemo.java - validates CCW winding = front face
+public class WindingOrderDemo {
+    public static void main(String[] args) {
+        ViewFrame viewFrame = new ViewFrame();
+        ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
+
+        double size = 150;
+
+        // CCW winding: top → lower-left → lower-right
+        Point3D upperCenter = new Point3D(0, -size, 0);
+        Point3D lowerLeft = new Point3D(-size, +size, 0);
+        Point3D lowerRight = new Point3D(+size, +size, 0);
+
+        SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN);
+        triangle.setBackfaceCulling(true);
+
+        shapes.addShape(triangle);
+
+        viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, 0, -500));
+        viewFrame.getViewPanel().ensureRenderThreadStarted();
+    }
+}
+#+END_SRC
+
+Run this demo: if the green triangle is visible, the winding order is
+correct (CCW = front face)
+
+[[file:Winding order demo.png]]
 
 In Sixth 3D, backface culling is *optional* and disabled by default. Enable it per-shape:
 - [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.html#setBackfaceCulling(boolean)][SolidPolygon.setBackfaceCulling(true)]]
@@ -471,6 +545,139 @@ Color custom = new Color(255, 128, 64, 200); // semi-transparent orange
 Color hex = new Color("FF8040CC"); // same orange with alpha
 #+END_SRC
 
+* Developer tools
+:PROPERTIES:
+:CUSTOM_ID: developer-tools
+:ID:       8c5e2a1f-9d3b-4f6a-b8e7-1c4d5f7a9b2e
+:END:
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Developer tools.png]]
+
+Press *F12* anywhere in the application to open the Developer Tools panel.
+This debugging interface helps you understand what the engine is doing
+internally and diagnose rendering issues.
+
+The Developer Tools panel provides real-time insight into the rendering
+pipeline with two diagnostic toggles and a live log viewer that's always
+recording.
+
+** Render frame logging (always on)
+
+Render frame diagnostics are always logged to a circular buffer. When you
+open the Developer Tools panel, you can see the complete rendering history.
+Each frame goes through 6 phases:
+
+| Phase | Description                                    |
+|-------+------------------------------------------------|
+| 1     | Canvas cleared with background color           |
+| 2     | Shapes transformed (3D → screen coordinates)   |
+| 3     | Shapes sorted by depth (back-to-front)         |
+| 4     | Segments painted (parallel rendering)          |
+| 5     | Mouse hit results combined                     |
+| 6     | Blit to screen (buffer strategy show)          |
+
+Log entries include:
+- Frame number and timestamp
+- Shape count and queued shapes
+- Buffer strategy status (contents lost/restored)
+- Blit retry count for page-flip diagnostics
+
+Example log output:
+#+BEGIN_EXAMPLE
+22:44:55.202 [VIEWPANEL] renderFrame #1105 START, shapes=26
+22:44:55.202 [VIEWPANEL] Phase 1 done: canvas cleared
+22:44:55.202 [VIEWPANEL] Phase 2 done: shapes transformed, queued=26
+22:44:55.202 [VIEWPANEL] Phase 3 done: shapes sorted
+22:44:55.210 [VIEWPANEL] Phase 4 done: segments painted
+22:44:55.210 [VIEWPANEL] Phase 5 done: mouse results combined
+22:44:55.211 [VIEWPANEL] Phase 6 done: blit complete (x1)
+#+END_EXAMPLE
+
+
+Use this for:
+- Performance profiling (identify slow phases)
+- Understanding rendering order
+- Diagnosing buffer strategy issues (screen tearing, blank frames)
+- Verifying shapes are actually being rendered
+
+** Show polygon borders
+
+Draws yellow outlines around all textured polygons to visualize:
+- Triangle tessellation patterns
+- Perspective-correct texture slicing
+- Polygon coverage and overlap
+
+This is particularly useful when debugging:
+- Texture mapping issues
+- Perspective distortion problems
+- Mesh density and triangulation quality
+- Z-fighting between overlapping polygons
+
+The yellow borders are rendered on top of the final image, making it
+easy to see the underlying geometric structure of textured surfaces.
+
+** Render alternate segments (overdraw debug)
+
+Renders only even-numbered horizontal segments (0, 2, 4, 6) while
+leaving odd segments (1, 3, 5, 7) black.
+
+The engine divides the screen into 8 horizontal segments for parallel
+multi-threaded rendering. This toggle helps detect overdraw (threads writing outside their allocated segment).
+
+If you see rendering artifacts in the black segments, it indicates
+that threads are writing pixels outside their assigned area — a clear
+sign of a bug.
+
+** Live log viewer
+
+The scrollable text area shows captured debug output in real-time:
+- Green text on black background for readability
+- Auto-scrolls to show latest entries
+- Updates every 500ms while panel is open
+- Captures logs even when panel is closed (replays when reopened)
+
+Use the *Clear Logs* button to reset the log buffer for fresh
+diagnostic captures.
+
+** API access
+
+You can access and control developer tools programmatically:
+
+#+BEGIN_SRC java
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.DeveloperTools;
+
+ViewPanel viewPanel = ...; // get your view panel
+DeveloperTools tools = viewPanel.getDeveloperTools();
+
+// Enable diagnostics programmatically
+tools.showPolygonBorders = true;
+tools.renderAlternateSegments = false;
+#+END_SRC
+
+This allows you to:
+- Enable debugging based on command-line flags
+- Toggle features during automated testing
+- Create custom debug overlays or controls
+- Integrate with external logging frameworks
+
+** Technical details
+
+The Developer Tools panel is implemented as a non-modal =JDialog= that:
+- Centers on the parent =ViewFrame= window
+- Runs on the Event Dispatch Thread (EDT)
+- Does not block the render loop
+- Automatically closes when parent window closes
+
+Log entries are stored in a circular buffer (=DebugLogBuffer=) with
+configurable capacity (default: 10,000 entries). When full, oldest
+entries are discarded.
+
+Each =ViewPanel= has its own independent =DeveloperTools= instance,
+so multiple views can have different debug configurations simultaneously.
+
 * Source code
 :PROPERTIES:
 :CUSTOM_ID: source-code
@@ -494,6 +701,8 @@ Color hex = new Color("FF8040CC"); // same orange with alpha
 
 ** Understanding the Sixth 3D source code
 
+- Study how [[id:4b6c1355-0afe-40c6-86c3-14bf8a11a8d0][scene definition]] works.
+- Understand [[file:rendering-loop.org][main rendering loop]].
 - Read online [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/][JavaDoc]].
 - See [[https://www3.svjatoslav.eu/projects/sixth-3d/graphs/][Sixth 3D class diagrams]]. (Diagrams were generated by using
   [[https://www3.svjatoslav.eu/projects/javainspect/][JavaInspect]] utility)
@@ -513,11 +722,11 @@ Color hex = new Color("FF8040CC"); // same orange with alpha
 + Partial region/frame repaint: when only one small object changed on
   the scene, it would be faster to re-render that specific area.
 
-  + Once partial rendering works, in would be easy to add multi-core
-    rendering support. So that each core renders it's own region of
+  + Once partial rendering works, it would be easy to add multi-core
+    rendering support. So that each core renders its own region of
     the screen.
 
-+ Anti-aliasing. Would improve text readability. If antialiazing is
++ Anti-aliasing. Would improve text readability. If antialiasing is
   too expensive for every frame, it could be used only for last frame
   before animations become still and waiting for user input starts.
 
@@ -550,7 +759,7 @@ Very high-level idea description:
       + Dynamically detect and replace invisible objects from the
         scene with simplified bounding box.
 
-      + Dynamically replace boudnig box with actual object once it
+      + Dynamically replace bounding box with actual object once it
         becomes visible.
 
     + Dynamically unload unused textures from RAM.
diff --git a/doc/rendering-loop.org b/doc/rendering-loop.org
new file mode 100644 (file)
index 0000000..274851c
--- /dev/null
@@ -0,0 +1,223 @@
+#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme
+#+TITLE: Rendering Loop - Sixth 3D
+#+LANGUAGE: en
+#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}
+#+LATEX_HEADER: \usepackage{parskip}
+#+LATEX_HEADER: \usepackage[none]{hyphenat}
+
+#+OPTIONS: H:20 num:20
+#+OPTIONS: author:nil
+
+#+begin_export html
+<style>
+  .flex-center {
+    display: flex;
+    justify-content: center;
+  }
+  .flex-center video {
+    width: min(90%, 1000px);
+    height: auto;
+  }
+  .responsive-img {
+    width: min(100%, 1000px);
+    height: auto;
+  }
+</style>
+#+end_export
+
+[[file:index.org][Back to main documentation]]
+
+* Rendering loop
+:PROPERTIES:
+:CUSTOM_ID: rendering-loop
+:ID:       a1b2c3d4-e5f6-7890-abcd-ef1234567890
+:END:
+
+The rendering loop is the heart of the engine, continuously generating
+frames on a dedicated background thread. It orchestrates the entire
+rendering pipeline from 3D world space to pixels on screen.
+
+** Main loop structure
+
+The render thread runs continuously in a dedicated daemon thread:
+
+#+BEGIN_SRC java
+while (renderThreadRunning) {
+    ensureThatViewIsUpToDate();  // Render one frame
+    maintainTargetFps();         // Sleep if needed
+}
+#+END_SRC
+
+The thread is a daemon, so it automatically stops when the JVM exits.
+You can stop it explicitly with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#stop()][ViewPanel.stop()]].
+
+** Frame rate control
+
+The engine supports two modes:
+
+- *Target FPS mode*: Set with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#setFrameRate(int)][setFrameRate(int)]]. The thread sleeps
+  between frames to maintain the target rate. If rendering takes
+  longer than the frame interval, the engine catches up naturally
+  without sleeping.
+
+- *Unlimited mode*: Set =setFrameRate(0)= or negative. No sleeping —
+  renders as fast as possible. Useful for benchmarking.
+
+** Frame listeners
+
+Before each frame, the engine notifies all registered [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/FrameListener.html][FrameListener]]s:
+
+#+BEGIN_SRC java
+viewPanel.addFrameListener((panel, deltaMs) -> {
+    // Update animations, physics, game logic
+    shape.rotate(0.01);
+    return true;  // true = force repaint
+});
+#+END_SRC
+
+Frame listeners can trigger repaints by returning =true=. Built-in listeners include:
+- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/Camera.html][Camera]] — handles keyboard/mouse navigation
+- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.html][InputManager]] — processes input events
+
+* Rendering phases
+
+Each frame goes through 6 phases. Open the Developer Tools panel (F12)
+to see these phases logged in real-time:
+
+** Phase 1: Clear canvas
+
+The pixel buffer is filled with the background color (default: black).
+
+#+BEGIN_SRC java
+Arrays.fill(pixels, 0, width * height, backgroundColorRgb);
+#+END_SRC
+
+This is a simple =Arrays.fill= operation — very fast, single-threaded.
+
+** Phase 2: Transform shapes
+
+All shapes are transformed from world space to screen space:
+
+1. Build camera-relative transform (inverse of camera position/rotation)
+2. For each shape:
+   - Apply camera transform
+   - Project 3D → 2D (perspective projection)
+   - Calculate =onScreenZ= for depth sorting
+   - Queue for rendering
+
+This is single-threaded but very fast — just math, no pixel operations.
+
+** Phase 3: Sort shapes
+
+Shapes are sorted by =onScreenZ= (depth) in descending order:
+
+#+BEGIN_SRC java
+Collections.sort(queuedShapes, (a, b) -> Double.compare(b.onScreenZ, a.onScreenZ));
+#+END_SRC
+
+Back-to-front sorting is essential for correct transparency and
+occlusion.  Shapes further from the camera are painted first.
+
+** Phase 4: Paint shapes (multi-threaded)
+
+The screen is divided into 8 horizontal segments, each rendered by a separate thread:
+
+#+BEGIN_EXPORT html
+<svg viewBox="0 0 400 200" width="400" height="200">
+  <rect width="400" height="200" fill="#061018"/>
+  <rect x="10" y="5" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="30" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="55" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="80" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="105" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="130" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="155" width="380" height="22" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <rect x="10" y="180" width="380" height="15" fill="#1a3a4a" stroke="#30a050" stroke-width="1"/>
+  <text x="20" y="21" fill="#30a050" font-size="11" font-family="monospace">Segment 0 (Thread 0)</text>
+  <text x="20" y="46" fill="#30a050" font-size="11" font-family="monospace">Segment 1 (Thread 1)</text>
+  <text x="20" y="71" fill="#30a050" font-size="11" font-family="monospace">Segment 2 (Thread 2)</text>
+  <text x="20" y="96" fill="#30a050" font-size="11" font-family="monospace">Segment 3 (Thread 3)</text>
+  <text x="20" y="121" fill="#30a050" font-size="11" font-family="monospace">Segment 4 (Thread 4)</text>
+  <text x="20" y="146" fill="#30a050" font-size="11" font-family="monospace">Segment 5 (Thread 5)</text>
+  <text x="20" y="171" fill="#30a050" font-size="11" font-family="monospace">Segment 6 (Thread 6)</text>
+  <text x="20" y="193" fill="#30a050" font-size="11" font-family="monospace">Segment 7 (Thread 7)</text>
+</svg>
+#+END_EXPORT
+
+Each thread:
+- Gets a =SegmentRenderingContext= with Y-bounds (minY, maxY)
+- Iterates all shapes and paints pixels within its Y-range
+- Clips triangles/lines at segment boundaries
+- Detects mouse hits (before clipping)
+
+A =CountDownLatch= waits for all 8 threads to complete before proceeding.
+
+**Why 8 segments?** This matches the typical core count of modern CPUs.
+The fixed thread pool (=Executors.newFixedThreadPool(8)=) avoids the
+overhead of creating threads per frame.
+
+** Phase 5: Combine mouse results
+
+During painting, each segment tracks which shape is under the mouse cursor.
+Since all segments paint the same shapes (just different Y-ranges), they
+should all report the same hit. Phase 5 takes the first non-null result:
+
+#+BEGIN_SRC java
+for (SegmentRenderingContext ctx : segmentContexts) {
+    if (ctx.getSegmentMouseHit() != null) {
+        renderingContext.setCurrentObjectUnderMouseCursor(ctx.getSegmentMouseHit());
+        break;
+    }
+}
+#+END_SRC
+
+** Phase 6: Blit to screen
+
+The rendered =BufferedImage= is copied to the screen using
+[[https://docs.oracle.com/javase/21/docs/api/java/awt/image/BufferStrategy.html][BufferStrategy]] for tear-free page-flipping:
+
+#+BEGIN_SRC java
+do {
+    Graphics2D g = bufferStrategy.getDrawGraphics();
+    g.drawImage(renderingContext.bufferedImage, 0, 0, null);
+    g.dispose();
+} while (bufferStrategy.contentsRestored());
+
+bufferStrategy.show();
+Toolkit.getDefaultToolkit().sync();
+#+END_SRC
+
+The =do-while= loop handles the case where the OS recreates the back
+buffer (common during window resizing). Since our offscreen
+=BufferedImage= still has the correct pixels, we only need to re-blit,
+not re-render.
+
+* Smart repaint skipping
+
+The engine avoids unnecessary rendering:
+
+- =viewRepaintNeeded= flag: Set to =true= only when something changes
+- Frame listeners can return =false= to skip repaint
+- Resizing, component events, and explicit =repaintDuringNextViewUpdate()=
+  calls set the flag
+
+This means a static scene consumes almost zero CPU — the render thread
+just spins checking the flag.
+
+* Rendering context
+
+The [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/RenderingContext.html][RenderingContext]] holds all state for a single frame:
+
+| Field | Purpose |
+|-------+---------|
+| =pixels[]= | Raw pixel buffer (int[] in RGB format) |
+| =bufferedImage= | Java2D wrapper around pixels |
+| =graphics= | Graphics2D for text, lines, shapes |
+| =width=, =height= | Screen dimensions |
+| =centerCoordinate= | Screen center (for projection) |
+| =projectionScale= | Perspective scale factor |
+| =frameNumber= | Monotonically increasing frame counter |
+
+A new context is created when the window is resized. Otherwise, the
+same context is reused — =prepareForNewFrameRendering()= just resets
+per-frame state like mouse tracking.
index e461531..abb48b0 100644 (file)
@@ -7,22 +7,36 @@ package eu.svjatoslav.sixth.e3d.geometry;
 import static java.lang.Math.abs;
 
 /**
- * Same as: 3D rectangle, rectangular box, rectangular parallelopiped, cuboid,
- * rhumboid, hexahedron, rectangular prism.
+ * A 3D axis-aligned bounding box defined by two corner points.
+ *
+ * <p>Also known as: 3D rectangle, rectangular box, rectangular parallelepiped,
+ * cuboid, rhomboid, hexahedron, or rectangular prism.</p>
+ *
+ * <p>The box is defined by two points ({@link #p1} and {@link #p2}) that represent
+ * opposite corners. The box does not enforce ordering of these points.</p>
+ *
+ * <p><b>Example usage:</b></p>
+ * <pre>{@code
+ * Box box = new Box(new Point3D(0, 0, 0), new Point3D(100, 50, 200));
+ * double volume = box.getWidth() * box.getHeight() * box.getDepth();
+ * box.enlarge(10);  // expand by 10 units in all directions
+ * }</pre>
+ *
+ * @see Point3D
  */
 public class Box implements Cloneable {
 
     /**
-     * The first point of the box.
+     * The first corner point of the box.
      */
     public final Point3D p1;
     /**
-     * The second point of the box.
+     * The second corner point of the box (opposite corner from p1).
      */
     public final Point3D p2;
 
     /**
-     * Creates a new box with two points at the origin.
+     * Creates a new box with both corner points at the origin.
      */
     public Box() {
         p1 = new Point3D();
@@ -30,7 +44,10 @@ public class Box implements Cloneable {
     }
 
     /**
-     * Creates a new box with two points at the specified coordinates.
+     * Creates a new box with the specified corner points.
+     *
+     * @param p1 the first corner point
+     * @param p2 the second corner point (opposite corner)
      */
     public Box(final Point3D p1, final Point3D p2) {
         this.p1 = p1;
@@ -74,27 +91,38 @@ public class Box implements Cloneable {
         return this;
     }
 
+    /**
+     * Creates a copy of this box with cloned corner points.
+     *
+     * @return a new box with the same corner coordinates
+     */
     @Override
     public Box clone() {
         return new Box(p1.clone(), p2.clone());
     }
 
     /**
-     * @return The depth of the box. The depth is the distance between the two points on the z-axis.
+     * Returns the depth of the box (distance along the Z-axis).
+     *
+     * @return the depth (always positive)
      */
     public double getDepth() {
         return abs(p1.z - p2.z);
     }
 
     /**
-     * @return The height of the box. The height is the distance between the two points on the y-axis.
+     * Returns the height of the box (distance along the Y-axis).
+     *
+     * @return the height (always positive)
      */
     public double getHeight() {
         return abs(p1.y - p2.y);
     }
 
     /**
-     * @return The width of the box. The width is the distance between the two points on the x-axis.
+     * Returns the width of the box (distance along the X-axis).
+     *
+     * @return the width (always positive)
      */
     public double getWidth() {
         return abs(p1.x - p2.x);
index 2ac177d..18dcbee 100644 (file)
@@ -5,12 +5,14 @@
 package eu.svjatoslav.sixth.e3d.geometry;
 
 /**
- * Circle in 2D space.
+ * A circle in 2D space defined by a center point and radius.
+ *
+ * @see Point2D
  */
 public class Circle {
 
     /**
-     * The center of the circle.
+     * The center point of the circle.
      */
     Point2D location;
 
@@ -19,4 +21,10 @@ public class Circle {
      */
     double radius;
 
+    /**
+     * Creates a circle with default values.
+     */
+    public Circle() {
+    }
+
 }
index a6bb6a1..6e6ac97 100755 (executable)
@@ -7,22 +7,51 @@ package eu.svjatoslav.sixth.e3d.geometry;
 import static java.lang.Math.sqrt;
 
 /**
- * Used to represent point in a 2D space or vector.
+ * A mutable 2D point or vector with double-precision coordinates.
  *
- * @see Point3D
+ * <p>{@code Point2D} represents either a position in 2D space or a directional vector,
+ * with public {@code x} and {@code y} fields for direct access. It is commonly used
+ * for screen-space coordinates after 3D-to-2D projection.</p>
+ *
+ * <p>All mutation methods return {@code this} for fluent chaining:</p>
+ * <pre>{@code
+ * Point2D p = new Point2D(10, 20)
+ *     .add(new Point2D(5, 5))
+ *     .invert();
+ * // p is now (-15, -25)
+ * }</pre>
+ *
+ * @see Point3D the 3D equivalent
  */
 public class Point2D implements Cloneable {
 
-    public double x, y;
+    /** X coordinate (horizontal axis). */
+    public double x;
+    /** Y coordinate (vertical axis, positive = down in screen space). */
+    public double y;
 
+    /**
+     * Creates a point at the origin (0, 0).
+     */
     public Point2D() {
     }
 
+    /**
+     * Creates a point with the specified coordinates.
+     *
+     * @param x the X coordinate
+     * @param y the Y coordinate
+     */
     public Point2D(final double x, final double y) {
         this.x = x;
         this.y = y;
     }
 
+    /**
+     * Creates a point by copying coordinates from another point.
+     *
+     * @param parent the point to copy from
+     */
     public Point2D(final Point2D parent) {
         x = parent.x;
         y = parent.y;
@@ -30,9 +59,10 @@ public class Point2D implements Cloneable {
 
 
     /**
-     * Add other point to current point. Value of other point will not be changed.
+     * Adds another point to this point. The other point is not modified.
      *
-     * @return current point.
+     * @param otherPoint the point to add
+     * @return this point (for chaining)
      */
     public Point2D add(final Point2D otherPoint) {
         x += otherPoint.x;
@@ -41,19 +71,28 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * @return true if current point coordinates are equal to zero.
+     * Checks if both coordinates are zero.
+     *
+     * @return {@code true} if current point coordinates are equal to zero
      */
     public boolean isZero() {
         return (x == 0) && (y == 0);
     }
 
+    /**
+     * Creates a new point by copying this point's coordinates.
+     *
+     * @return a new point with the same coordinates
+     */
     @Override
     public Point2D clone() {
         return new Point2D(this);
     }
 
     /**
-     * Copy coordinates from other point to current point. Value of other point will not be changed.
+     * Copies coordinates from another point into this point.
+     *
+     * @param otherPoint the point to copy coordinates from
      */
     public void clone(final Point2D otherPoint) {
         x = otherPoint.x;
@@ -61,11 +100,11 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * Set current point to middle of two other points.
+     * Sets this point to the midpoint between two other points.
      *
-     * @param p1 first point.
-     * @param p2 second point.
-     * @return current point.
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return this point (for chaining)
      */
     public Point2D setToMiddle(final Point2D p1, final Point2D p2) {
         x = (p1.x + p2.x) / 2d;
@@ -73,15 +112,21 @@ public class Point2D implements Cloneable {
         return this;
     }
 
+    /**
+     * Computes the angle on the X-Y plane between this point and another point.
+     *
+     * @param anotherPoint the other point
+     * @return the angle in radians
+     */
     public double getAngleXY(final Point2D anotherPoint) {
         return Math.atan2(x - anotherPoint.x, y - anotherPoint.y);
     }
 
     /**
-     * Compute distance to another point.
+     * Computes the Euclidean distance from this point to another point.
      *
-     * @param anotherPoint point to compute distance to.
-     * @return distance from current point to another point.
+     * @param anotherPoint the point to compute distance to
+     * @return the distance between the two points
      */
     public double getDistanceTo(final Point2D anotherPoint) {
         final double xDiff = x - anotherPoint.x;
@@ -91,18 +136,18 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * Calculate length of vector.
+     * Computes the length of this vector (magnitude).
      *
-     * @return length of vector.
+     * @return the vector length
      */
     public double getVectorLength() {
         return sqrt(((x * x) + (y * y)));
     }
 
     /**
-     * Invert current point.
+     * Inverts this point's coordinates (negates both x and y).
      *
-     * @return current point.
+     * @return this point (for chaining)
      */
     public Point2D invert() {
         x = -x;
@@ -111,7 +156,7 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * Round current point coordinates to integer.
+     * Rounds this point's coordinates to integer values.
      */
     public void roundToInteger() {
         x = (int) x;
@@ -119,9 +164,10 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * Subtract other point from current point. Value of other point will not be changed.
+     * Subtracts another point from this point. The other point is not modified.
      *
-     * @return current point.
+     * @param otherPoint the point to subtract
+     * @return this point (for chaining)
      */
     public Point2D subtract(final Point2D otherPoint) {
         x -= otherPoint.x;
@@ -130,19 +176,18 @@ public class Point2D implements Cloneable {
     }
 
     /**
-     * Convert current point to 3D point.
-     * Value of the z coordinate will be set to zero.
+     * Converts this 2D point to a 3D point with z = 0.
      *
-     * @return 3D point.
+     * @return a new 3D point with the same x, y and z = 0
      */
     public Point3D to3D() {
         return new Point3D(x, y, 0);
     }
 
     /**
-     * Set current point to zero.
+     * Resets this point's coordinates to (0, 0).
      *
-     * @return current point.
+     * @return this point (for chaining)
      */
     public Point2D zero() {
         x = 0;
index 6f93616..7cfd8e0 100755 (executable)
@@ -118,7 +118,9 @@ public class Point3D implements Cloneable {
 
 
     /**
-     * Creates new current point by cloning coordinates from parent point.
+     * Creates a new point by cloning coordinates from the parent point.
+     *
+     * @param parent the point to copy coordinates from
      */
     public Point3D(final Point3D parent) {
         x = parent.x;
@@ -140,9 +142,11 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Add coordinates of current point to other point. Value of current point will not be changed.
+     * Adds coordinates of current point to one or more other points.
+     * The current point's coordinates are added to each target point.
      *
-     * @return current point.
+     * @param otherPoints the points to add this point's coordinates to
+     * @return this point (for chaining)
      */
     public Point3D addTo(final Point3D... otherPoints) {
         for (final Point3D otherPoint : otherPoints) otherPoint.add(this);
@@ -159,7 +163,10 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Copy coordinates from other point to current point. Value of other point will not be changed.
+     * Copies coordinates from another point into this point.
+     *
+     * @param otherPoint the point to copy coordinates from
+     * @return this point (for chaining)
      */
     public Point3D clone(final Point3D otherPoint) {
         x = otherPoint.x;
@@ -183,7 +190,9 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * @return true if current point coordinates are equal to zero.
+     * Checks if all coordinates are zero.
+     *
+     * @return {@code true} if current point coordinates are equal to zero
      */
     public boolean isZero() {
         return (x == 0) && (y == 0) && (z == 0);
@@ -234,7 +243,9 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * @return length of current vector.
+     * Computes the length (magnitude) of this vector.
+     *
+     * @return the vector length
      */
     public double getVectorLength() {
         return sqrt(((x * x) + (y * y) + (z * z)));
@@ -253,13 +264,14 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Rotate current point around center point by angleXZ and angleYZ.
+     * Rotates this point around a center point by the given XZ and YZ angles.
      * <p>
      * See also: <a href="https://marctenbosch.com/quaternions/">Let's remove Quaternions from every 3D Engine</a>
      *
-     * @param center  center point.
-     * @param angleXZ angle around XZ axis.
-     * @param angleYZ angle around YZ axis.
+     * @param center  the center point to rotate around
+     * @param angleXZ the angle in the XZ plane (yaw) in radians
+     * @param angleYZ the angle in the YZ plane (pitch) in radians
+     * @return this point (for chaining)
      */
     public Point3D rotate(final Point3D center, final double angleXZ,
                           final double angleYZ) {
@@ -348,9 +360,10 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Subtract other point from current point. Value of other point will not be changed.
+     * Subtracts another point from this point.
      *
-     * @return current point.
+     * @param otherPoint the point to subtract
+     * @return this point (for chaining)
      */
     public Point3D subtract(final Point3D otherPoint) {
         x -= otherPoint.x;
@@ -365,9 +378,10 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Translate current point along X axis by given increment.
+     * Translates this point along the X axis.
      *
-     * @return current point.
+     * @param xIncrement the amount to add to the X coordinate
+     * @return this point (for chaining)
      */
     public Point3D translateX(final double xIncrement) {
         x += xIncrement;
@@ -375,9 +389,10 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Translate current point along Y axis by given increment.
+     * Translates this point along the Y axis.
      *
-     * @return current point.
+     * @param yIncrement the amount to add to the Y coordinate
+     * @return this point (for chaining)
      */
     public Point3D translateY(final double yIncrement) {
         y += yIncrement;
@@ -385,9 +400,10 @@ public class Point3D implements Cloneable {
     }
 
     /**
-     * Translate current point along Z axis by given increment.
+     * Translates this point along the Z axis.
      *
-     * @return current point.
+     * @param zIncrement the amount to add to the Z coordinate
+     * @return this point (for chaining)
      */
     public Point3D translateZ(final double zIncrement) {
         z += zIncrement;
index d5f558e..50f9dc6 100644 (file)
@@ -5,17 +5,29 @@
 package eu.svjatoslav.sixth.e3d.geometry;
 
 /**
- * Utility class for polygon operations.
+ * Utility class for polygon operations, primarily point-in-polygon testing.
+ *
+ * <p>Provides static methods for geometric computations on triangles and other polygons.</p>
+ *
+ * @see Point2D
  */
 public class Polygon {
 
+    /**
+     * Creates a new Polygon utility instance.
+     */
+    public Polygon() {
+    }
+
 
     /**
-     * Checks if point is on the right side of the line.
-     * @param point point to check
-     * @param lineP1 line start point
-     * @param lineP2 line end point
-     * @return true if point is on the right side of the line
+     * Checks if a point is on the right side of a directed line segment.
+     * Used internally for ray-casting in point-in-polygon tests.
+     *
+     * @param point  the point to test
+     * @param lineP1 the start point of the line segment
+     * @param lineP2 the end point of the line segment
+     * @return {@code true} if the point is on the right side of the line
      */
     private static boolean intersectsLine(final Point2D point, Point2D lineP1,
                                           Point2D lineP2) {
@@ -40,6 +52,18 @@ public class Polygon {
         return point.x >= crossX;
     }
 
+    /**
+     * Tests whether a point lies inside a triangle using the ray-casting algorithm.
+     *
+     * <p>This method casts a horizontal ray from the test point and counts intersections
+     * with the triangle edges. If the number of intersections is odd, the point is inside.</p>
+     *
+     * @param point the point to test
+     * @param p1    the first vertex of the triangle
+     * @param p2    the second vertex of the triangle
+     * @param p3    the third vertex of the triangle
+     * @return {@code true} if the point is inside the triangle
+     */
     public static boolean pointWithinPolygon(final Point2D point,
                                              final Point2D p1, final Point2D p2, final Point2D p3) {
 
index 966d366..23c2079 100644 (file)
@@ -8,21 +8,25 @@ import static java.lang.Math.abs;
 import static java.lang.Math.min;
 
 /**
- * Rectangle class.
+ * A 2D axis-aligned rectangle defined by two corner points.
+ *
+ * <p>The rectangle is defined by two points ({@link #p1} and {@link #p2}) that represent
+ * opposite corners. The rectangle does not enforce ordering of these points.</p>
+ *
+ * @see Point2D
+ * @see Box the 3D equivalent
  */
 public class Rectangle {
 
     /**
-     * Rectangle points.
+     * The corner points of the rectangle (opposite corners).
      */
     public Point2D p1, p2;
 
     /**
-     * Creates new rectangle with given size.
-     * The rectangle will be centered at the origin.
-     * The rectangle will be square.
+     * Creates a square rectangle centered at the origin with the specified size.
      *
-     * @param size The size of the rectangle.
+     * @param size the width and height of the square
      */
     public Rectangle(final double size) {
         p2 = new Point2D(size / 2, size / 2);
@@ -30,31 +34,47 @@ public class Rectangle {
     }
 
     /**
-     * @param p1 The first point of the rectangle.
-     * @param p2 The second point of the rectangle.
+     * Creates a rectangle with the specified corner points.
+     *
+     * @param p1 the first corner point
+     * @param p2 the second corner point (opposite corner)
      */
     public Rectangle(final Point2D p1, final Point2D p2) {
         this.p1 = p1;
         this.p2 = p2;
     }
 
+    /**
+     * Returns the height of the rectangle (distance along the Y-axis).
+     *
+     * @return the height (always positive)
+     */
     public double getHeight() {
         return abs(p1.y - p2.y);
     }
 
     /**
-     * @return The leftmost x coordinate of the rectangle.
+     * Returns the leftmost X coordinate of the rectangle.
+     *
+     * @return the minimum X value
      */
     public double getLowerX() {
         return min(p1.x, p2.x);
     }
 
+    /**
+     * Returns the topmost Y coordinate of the rectangle.
+     *
+     * @return the minimum Y value
+     */
     public double getLowerY() {
         return min(p1.y, p2.y);
     }
 
     /**
-     * @return rectangle width.
+     * Returns the width of the rectangle (distance along the X-axis).
+     *
+     * @return the width (always positive)
      */
     public double getWidth() {
         return abs(p1.x - p2.x);
index 9423419..e00e5fa 100644 (file)
@@ -1,5 +1,7 @@
-package eu.svjatoslav.sixth.e3d.geometry;
-
 /**
- * Goal is to provide basic geometry classes.
- */
\ No newline at end of file
+ * Provides basic geometry classes for 2D and 3D coordinates and shapes.
+ *
+ * @see eu.svjatoslav.sixth.e3d.geometry.Point2D
+ * @see eu.svjatoslav.sixth.e3d.geometry.Point3D
+ */
+package eu.svjatoslav.sixth.e3d.geometry;
\ No newline at end of file
index 0baf2fa..2f0b842 100644 (file)
@@ -59,6 +59,9 @@ public class Camera implements FrameListener {
      */
     private final Point3D movementVector = new Point3D();
     private final Point3D previousLocation = new Point3D();
+    /**
+     * Camera acceleration factor for movement speed. Higher values result in faster acceleration.
+     */
     public double cameraAcceleration = 0.1;
     /**
      * The transform containing camera location and orientation.
@@ -81,6 +84,11 @@ public class Camera implements FrameListener {
         transform = sourceView.getTransform().clone();
     }
 
+    /**
+     * Creates a camera with the specified transform (position and orientation).
+     *
+     * @param transform the initial transform defining position and rotation
+     */
     public Camera(final Transform transform){
         this.transform = transform;
     }
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/DebugLogBuffer.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DebugLogBuffer.java
new file mode 100644 (file)
index 0000000..59e5f2e
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.gui;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Circular buffer for debug log messages with optional stdout passthrough.
+ *
+ * <p>Always captures log messages to a fixed-size circular buffer.
+ * When {@link #passthrough} is enabled, messages are also printed to stdout.</p>
+ *
+ * <p>This allows capturing early initialization logs before the user opens
+ * the {@link DeveloperToolsPanel}. When the panel is opened, the buffered history
+ * becomes immediately visible.</p>
+ *
+ * @see DeveloperToolsPanel
+ */
+public class DebugLogBuffer {
+
+    private static final DateTimeFormatter TIME_FORMATTER =
+            DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+
+    private final String[] buffer;
+    private final int capacity;
+    private volatile int head = 0;
+    private volatile int count = 0;
+    private volatile boolean passthrough = false;
+
+    /**
+     * Creates a new DebugLogBuffer with the specified capacity.
+     *
+     * @param capacity the maximum number of log entries to retain
+     */
+    public DebugLogBuffer(final int capacity) {
+        this.capacity = capacity;
+        this.buffer = new String[capacity];
+    }
+
+    /**
+     * Logs a message with a timestamp prefix.
+     *
+     * <p>If passthrough is enabled, also prints to stdout.</p>
+     *
+     * @param message the message to log
+     */
+    public void log(final String message) {
+        final String timestamped = LocalDateTime.now().format(TIME_FORMATTER) + " " + message;
+
+        synchronized (this) {
+            buffer[head] = timestamped;
+            head = (head + 1) % capacity;
+            if (count < capacity) {
+                count++;
+            }
+        }
+
+        if (passthrough) {
+            System.out.println(timestamped);
+        }
+    }
+
+    /**
+     * Returns all buffered log entries in chronological order.
+     *
+     * @return a list of timestamped log entries
+     */
+    public synchronized List<String> getEntries() {
+        final List<String> entries = new ArrayList<>(count);
+
+        if (count < capacity) {
+            for (int i = 0; i < count; i++) {
+                entries.add(buffer[i]);
+            }
+        } else {
+            for (int i = 0; i < capacity; i++) {
+                final int index = (head + i) % capacity;
+                entries.add(buffer[index]);
+            }
+        }
+
+        return entries;
+    }
+
+    /**
+     * Clears all buffered log entries.
+     */
+    public synchronized void clear() {
+        head = 0;
+        count = 0;
+    }
+
+    /**
+     * Returns whether passthrough to stdout is enabled.
+     *
+     * @return {@code true} if logs are also printed to stdout
+     */
+    public boolean isPassthrough() {
+        return passthrough;
+    }
+
+    /**
+     * Enables or disables passthrough to stdout.
+     *
+     * <p>When enabled, all subsequent log messages will be printed
+     * to stdout in addition to being captured in the buffer.</p>
+     *
+     * @param passthrough {@code true} to enable passthrough
+     */
+    public void setPassthrough(final boolean passthrough) {
+        this.passthrough = passthrough;
+    }
+
+    /**
+     * Returns the current number of log entries in the buffer.
+     *
+     * @return the number of entries
+     */
+    public synchronized int size() {
+        return count;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperTools.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperTools.java
new file mode 100644 (file)
index 0000000..4be604c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.gui;
+
+/**
+ * Per-ViewPanel developer tools that control diagnostic features.
+ *
+ * <p>Each {@link ViewPanel} has its own DeveloperTools instance, allowing
+ * different views to have independent debug configurations.</p>
+ *
+ * <p>Settings can be toggled at runtime via the {@link DeveloperToolsPanel}
+ * (opened with F12 key).</p>
+ *
+ * @see ViewPanel#getDeveloperTools()
+ * @see DeveloperToolsPanel
+ */
+public class DeveloperTools {
+
+    /**
+     * If {@code true}, textured polygon borders are drawn in yellow.
+     * Useful for visualizing polygon slicing for perspective-correct rendering.
+     */
+    public volatile boolean showPolygonBorders = false;
+
+    /**
+     * If {@code true}, only render even-numbered horizontal segments (0, 2, 4, 6).
+     * Odd segments (1, 3, 5, 7) will remain black. Useful for detecting
+     * if threads render outside their allocated screen area (overdraw detection).
+     */
+    public volatile boolean renderAlternateSegments = false;
+
+    /**
+     * Creates a new DeveloperTools instance with all debug features disabled.
+     */
+    public DeveloperTools() {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperToolsPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperToolsPanel.java
new file mode 100644 (file)
index 0000000..c209fc1
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.gui;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+/**
+ * Developer tools panel for toggling diagnostic features and viewing logs.
+ *
+ * <p>Opens as a popup dialog when F12 is pressed. Provides:</p>
+ * <ul>
+ *   <li>Checkboxes to toggle debug settings</li>
+ *   <li>A scrollable log viewer showing captured debug output</li>
+ *   <li>A button to clear the log buffer</li>
+ * </ul>
+ *
+ * @see DeveloperTools
+ * @see DebugLogBuffer
+ */
+public class DeveloperToolsPanel extends JDialog {
+
+    private static final int LOG_UPDATE_INTERVAL_MS = 500;
+
+    /** The developer tools being controlled. */
+    private final DeveloperTools developerTools;
+    /** The log buffer being displayed. */
+    private final DebugLogBuffer debugLogBuffer;
+    /** The text area showing log messages. */
+    private final JTextArea logArea;
+    /** Timer for periodic log updates. */
+    private final Timer updateTimer;
+    /** Flag to prevent concurrent log updates. */
+    private volatile boolean updating = false;
+
+    /**
+     * Creates and displays a developer tools panel.
+     *
+     * @param parent           the parent frame (for centering)
+     * @param developerTools   the developer tools to control
+     * @param debugLogBuffer   the log buffer to display
+     */
+    public DeveloperToolsPanel(final Frame parent, final DeveloperTools developerTools,
+                       final DebugLogBuffer debugLogBuffer) {
+        super(parent, "Developer Tools", false);
+        this.developerTools = developerTools;
+        this.debugLogBuffer = debugLogBuffer;
+
+        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+        setLayout(new BorderLayout(8, 8));
+
+        final JPanel settingsPanel = createSettingsPanel();
+        add(settingsPanel, BorderLayout.NORTH);
+
+        logArea = new JTextArea(20, 60);
+        logArea.setEditable(false);
+        logArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+        logArea.setBackground(Color.BLACK);
+        logArea.setForeground(Color.GREEN);
+        final JScrollPane scrollPane = new JScrollPane(logArea);
+        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+        add(scrollPane, BorderLayout.CENTER);
+
+        final JPanel buttonPanel = createButtonPanel();
+        add(buttonPanel, BorderLayout.SOUTH);
+
+        pack();
+        setLocationRelativeTo(parent);
+
+        updateTimer = new Timer(LOG_UPDATE_INTERVAL_MS, new ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                updateLogDisplay();
+            }
+        });
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowOpened(final WindowEvent e) {
+                debugLogBuffer.setPassthrough(true);
+                updateLogDisplay();
+                updateTimer.start();
+            }
+
+            @Override
+            public void windowClosed(final WindowEvent e) {
+                updateTimer.stop();
+                debugLogBuffer.setPassthrough(false);
+            }
+        });
+    }
+
+    private JPanel createSettingsPanel() {
+        final JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+        panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+
+        final JCheckBox showBordersCheckbox = new JCheckBox("Show polygon borders");
+        showBordersCheckbox.setSelected(developerTools.showPolygonBorders);
+        showBordersCheckbox.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(final ChangeEvent e) {
+                developerTools.showPolygonBorders = showBordersCheckbox.isSelected();
+            }
+        });
+
+        final JCheckBox alternateSegmentsCheckbox = new JCheckBox("Render alternate segments (overdraw debug)");
+        alternateSegmentsCheckbox.setSelected(developerTools.renderAlternateSegments);
+        alternateSegmentsCheckbox.addChangeListener(new ChangeListener() {
+            @Override
+            public void stateChanged(final ChangeEvent e) {
+                developerTools.renderAlternateSegments = alternateSegmentsCheckbox.isSelected();
+            }
+        });
+
+        panel.add(showBordersCheckbox);
+        panel.add(alternateSegmentsCheckbox);
+
+        return panel;
+    }
+
+    private JPanel createButtonPanel() {
+        final JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+        final JButton clearButton = new JButton("Clear Logs");
+        clearButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(final ActionEvent e) {
+                debugLogBuffer.clear();
+                logArea.setText("");
+            }
+        });
+
+        panel.add(clearButton);
+
+        return panel;
+    }
+
+    private void updateLogDisplay() {
+        if (updating) {
+            return;
+        }
+        updating = true;
+        try {
+            final List<String> entries = debugLogBuffer.getEntries();
+            final StringBuilder sb = new StringBuilder();
+            for (final String entry : entries) {
+                sb.append(entry).append('\n');
+            }
+            logArea.setText(sb.toString());
+
+            final JScrollBar vertical = ((JScrollPane) logArea.getParent().getParent())
+                    .getVerticalScrollBar();
+            vertical.setValue(vertical.getMaximum());
+        } finally {
+            updating = false;
+        }
+    }
+}
\ No newline at end of file
index cd3a357..82b979d 100644 (file)
@@ -95,24 +95,47 @@ public class GuiComponent extends AbstractCompositeShape implements
         return true;
     }
 
+    /**
+     * Returns the wireframe border box for this component.
+     *
+     * @return the border wireframe box
+     */
     public WireframeBox getBorders() {
         if (borders == null)
             borders = createBorder();
         return borders;
     }
 
+    /**
+     * Returns the depth of this component's bounding box.
+     *
+     * @return the depth in pixels
+     */
     public int getDepth() {
         return (int) containingBox.getDepth();
     }
 
+    /**
+     * Returns the height of this component's bounding box.
+     *
+     * @return the height in pixels
+     */
     public int getHeight() {
         return (int) containingBox.getHeight();
     }
 
+    /**
+     * Returns the width of this component's bounding box.
+     *
+     * @return the width in pixels
+     */
     public int getWidth() {
         return (int) containingBox.getWidth();
     }
 
+    /**
+     * Hides the focus border around this component.
+     */
     public void hideBorder() {
         if (!borderShown)
             return;
index 625a897..28097b0 100644 (file)
@@ -113,6 +113,11 @@ public class RenderingContext {
      * UI component that mouse is currently hovering over.
      */
     private MouseInteractionController currentObjectUnderMouseCursor;
+    /**
+     * Developer tools for this rendering context.
+     * Controls diagnostic features like logging and visualization.
+     */
+    public DeveloperTools developerTools;
 
     /**
      * Creates a new rendering context for full-screen rendering.
@@ -176,13 +181,15 @@ public class RenderingContext {
         this.bufferedImage = parent.bufferedImage;
         this.pixels = parent.pixels;
         this.graphics = parent.graphics;
+        this.developerTools = parent.developerTools;
     }
 
     /**
      * Resets per-frame state in preparation for rendering a new frame.
-     * Clears the mouse event and the current object under the mouse cursor.
+     * Increments the frame number and clears the mouse event state.
      */
     public void prepareForNewFrameRendering() {
+        frameNumber++;
         mouseEvent = null;
         currentObjectUnderMouseCursor = null;
     }
@@ -222,6 +229,8 @@ public class RenderingContext {
      * Called when given object was detected under mouse cursor, while processing {@link #mouseEvent}.
      * Because objects are rendered back to front. The last method caller will set the top-most object, if
      * there are multiple objects under mouse cursor.
+     *
+     * @param currentObjectUnderMouseCursor the object that is currently under the mouse cursor
      */
     public synchronized void setCurrentObjectUnderMouseCursor(MouseInteractionController currentObjectUnderMouseCursor) {
         this.currentObjectUnderMouseCursor = currentObjectUnderMouseCursor;
@@ -238,7 +247,9 @@ public class RenderingContext {
     }
 
     /**
-     * @return <code>true</code> if view update is needed as a consequence of this mouse event.
+     * Handles mouse events for components and returns whether a view repaint is needed.
+     *
+     * @return {@code true} if view update is needed as a consequence of this mouse event
      */
     public boolean handlePossibleComponentMouseEvent() {
         if (mouseEvent == null) return false;
index 91cd6e6..4ad8b4d 100755 (executable)
@@ -24,15 +24,29 @@ public class TextPointer implements Comparable<TextPointer> {
      */
     public int column;
 
+    /**
+     * Creates a text pointer at position (0, 0).
+     */
     public TextPointer() {
         this(0, 0);
     }
 
+    /**
+     * Creates a text pointer at the specified row and column.
+     *
+     * @param row    the row index (0-based)
+     * @param column the column index (0-based)
+     */
     public TextPointer(final int row, final int column) {
         this.row = row;
         this.column = column;
     }
 
+    /**
+     * Creates a text pointer by copying another text pointer.
+     *
+     * @param parent the text pointer to copy
+     */
     public TextPointer(final TextPointer parent) {
         this(parent.row, parent.column);
     }
index 6704e21..4a78009 100755 (executable)
@@ -40,6 +40,7 @@ public class ViewFrame extends JFrame implements WindowListener {
 
     private static final long serialVersionUID = -7037635097739548470L;
 
+    /** The embedded 3D view panel. */
     private final ViewPanel viewPanel;
 
     /**
@@ -83,8 +84,10 @@ public class ViewFrame extends JFrame implements WindowListener {
             setExtendedState(JFrame.MAXIMIZED_BOTH);
         }
         setVisible(true);
+        validate();
 
-        getViewPanel().ensureRenderThreadStarted();
+        // Render thread will be started by ViewPanel's componentShown/componentResized listeners
+        // after all frame listeners have been registered by the caller
 
         addResizeListener();
         addWindowListener(this);
index dcb354e..0e35b3e 100755 (executable)
@@ -79,20 +79,35 @@ public class ViewPanel extends Canvas {
     private static final int NUM_BUFFERS = 2;
     private static final int NUM_RENDER_SEGMENTS = 8;
 
+    /** The input manager handling mouse and keyboard events. */
     private final InputManager inputManager = new InputManager(this);
+    /** The stack managing keyboard focus for GUI components. */
     private final KeyboardFocusStack keyboardFocusStack;
+    /** The camera representing the viewer's position and orientation. */
     private final Camera camera = new Camera();
+    /** The root shape collection containing all 3D shapes in the scene. */
     private final ShapeCollection rootShapeCollection = new ShapeCollection();
+    /** The set of frame listeners notified before each frame. */
     private final Set<FrameListener> frameListeners = ConcurrentHashMap.newKeySet();
+    /** The executor service for parallel rendering. */
     private final ExecutorService renderExecutor = Executors.newFixedThreadPool(NUM_RENDER_SEGMENTS);
+    /** The background color of the view. */
     public Color backgroundColor = Color.BLACK;
 
+    /** Developer tools for this view panel. */
+    private final DeveloperTools developerTools = new DeveloperTools();
+    /** Debug log buffer for capturing diagnostic output. */
+    private final DebugLogBuffer debugLogBuffer = new DebugLogBuffer(10000);
+    /** The developer tools panel popup, or null if not currently shown. */
+    private DeveloperToolsPanel developerToolsPanel = null;
+
     /**
      * Stores milliseconds when the last frame was updated. This is needed to calculate the time delta between frames.
      * Time delta is used to calculate smooth animation.
      */
     private long lastUpdateMillis = 0;
 
+    /** The current rendering context for the active frame. */
     private RenderingContext renderingContext = null;
 
     /**
@@ -117,12 +132,18 @@ public class ViewPanel extends Canvas {
      */
     private volatile boolean renderThreadRunning = false;
 
+    /** Timestamp for the next scheduled frame. */
     private long nextFrameTime;
 
+    /** The buffer strategy for page-flipping rendering. */
     private BufferStrategy bufferStrategy;
 
+    /** Whether the buffer strategy has been initialized. */
     private boolean bufferStrategyInitialized = false;
 
+    /**
+     * Creates a new view panel with default settings.
+     */
     public ViewPanel() {
         frameListeners.add(camera);
         frameListeners.add(inputManager);
@@ -135,13 +156,11 @@ public class ViewPanel extends Canvas {
             @Override
             public void componentResized(final ComponentEvent e) {
                 viewRepaintNeeded = true;
-                maybeStartRenderThread();
             }
 
             @Override
             public void componentShown(final ComponentEvent e) {
                 viewRepaintNeeded = true;
-                maybeStartRenderThread();
             }
         });
     }
@@ -158,6 +177,11 @@ public class ViewPanel extends Canvas {
         maybeStartRenderThread();
     }
 
+    /**
+     * Returns the camera representing the viewer's position and orientation.
+     *
+     * @return the camera
+     */
     public Camera getCamera() {
         return camera;
     }
@@ -227,10 +251,58 @@ public class ViewPanel extends Canvas {
         return getPreferredSize();
     }
 
+    /**
+     * Returns the current rendering context for the active frame.
+     *
+     * @return the rendering context, or null if no frame is being rendered
+     */
     public RenderingContext getRenderingContext() {
         return renderingContext;
     }
 
+    /**
+     * Returns the developer tools for this view panel.
+     *
+     * @return the developer tools
+     */
+    public DeveloperTools getDeveloperTools() {
+        return developerTools;
+    }
+
+    /**
+     * Returns the debug log buffer for this view panel.
+     *
+     * @return the debug log buffer
+     */
+    public DebugLogBuffer getDebugLogBuffer() {
+        return debugLogBuffer;
+    }
+
+    /**
+     * Shows the developer tools panel, toggling it if already open.
+     * Called when F12 is pressed.
+     */
+    public void showDeveloperToolsPanel() {
+        if (developerToolsPanel != null && developerToolsPanel.isVisible()) {
+            developerToolsPanel.dispose();
+            developerToolsPanel = null;
+            return;
+        }
+
+        Frame parentFrame = null;
+        Container parent = getParent();
+        while (parent != null) {
+            if (parent instanceof Frame) {
+                parentFrame = (Frame) parent;
+                break;
+            }
+            parent = parent.getParent();
+        }
+
+        developerToolsPanel = new DeveloperToolsPanel(parentFrame, developerTools, debugLogBuffer);
+        developerToolsPanel.setVisible(true);
+    }
+
     @Override
     public void paint(final Graphics g) {
     }
@@ -257,87 +329,138 @@ public class ViewPanel extends Canvas {
         try {
             createBufferStrategy(NUM_BUFFERS);
             bufferStrategy = getBufferStrategy();
-            if (bufferStrategy != null)
+            if (bufferStrategy != null) {
                 bufferStrategyInitialized = true;
+                // Prime the buffer strategy with an initial show() to ensure it's ready
+                Graphics2D g = null;
+                try {
+                    g = (Graphics2D) bufferStrategy.getDrawGraphics();
+                    if (g != null) {
+                        g.setColor(java.awt.Color.BLACK);
+                        g.fillRect(0, 0, getWidth(), getHeight());
+                    }
+                } finally {
+                    if (g != null) g.dispose();
+                }
+                bufferStrategy.show();
+                java.awt.Toolkit.getDefaultToolkit().sync();
+            }
         } catch (final Exception e) {
             bufferStrategy = null;
             bufferStrategyInitialized = false;
         }
     }
 
+    private static int renderFrameCount = 0;
+
     private void renderFrame() {
         ensureBufferStrategy();
 
-        if (bufferStrategy == null || renderingContext == null)
+        if (bufferStrategy == null || renderingContext == null) {
+            debugLogBuffer.log("[VIEWPANEL] renderFrame ABORT: bufferStrategy=" + bufferStrategy + ", renderingContext=" + renderingContext);
             return;
+        }
+
+        renderFrameCount++;
+        debugLogBuffer.log("[VIEWPANEL] renderFrame #" + renderFrameCount 
+            + " START, shapes=" + rootShapeCollection.getShapes().size()
+            + ", frameNumber=" + renderingContext.frameNumber
+            + ", bufferSize=" + renderingContext.width + "x" + renderingContext.height);
 
         try {
-            do {
-                // Phase 1: Clear all segments
-                clearCanvasAllSegments();
-
-                // Phase 2: Transform shapes (single-threaded)
-                rootShapeCollection.transformShapes(this, renderingContext);
-
-                // Phase 3: Sort shapes (single-threaded)
-                rootShapeCollection.sortShapes();
-
-                // Phase 4: Paint segments in parallel
-                final int height = renderingContext.height;
-                final int segmentHeight = height / NUM_RENDER_SEGMENTS;
-                final SegmentRenderingContext[] segmentContexts = new SegmentRenderingContext[NUM_RENDER_SEGMENTS];
-                final CountDownLatch latch = new CountDownLatch(NUM_RENDER_SEGMENTS);
-
-                for (int i = 0; i < NUM_RENDER_SEGMENTS; i++) {
-                    final int segmentIndex = i;
-                    final int minY = i * segmentHeight;
-                    final int maxY = (i == NUM_RENDER_SEGMENTS - 1) ? height : (i + 1) * segmentHeight;
-
-                    segmentContexts[i] = new SegmentRenderingContext(renderingContext, minY, maxY);
-
-                    renderExecutor.submit(() -> {
-                        try {
-                            rootShapeCollection.paintShapes(segmentContexts[segmentIndex]);
-                        } finally {
-                            latch.countDown();
-                        }
-                    });
+            // === Render ONCE to offscreen buffer ===
+            // The offscreen bufferedImage is unaffected by BufferStrategy contentsRestored(),
+            // so we only need to render once, then retry the blit if needed.
+            clearCanvasAllSegments();
+            debugLogBuffer.log("[VIEWPANEL] Phase 1 done: canvas cleared");
+            
+            rootShapeCollection.transformShapes(this, renderingContext);
+            debugLogBuffer.log("[VIEWPANEL] Phase 2 done: shapes transformed, queued=" + rootShapeCollection.getQueuedShapeCount());
+            
+            rootShapeCollection.sortShapes();
+            debugLogBuffer.log("[VIEWPANEL] Phase 3 done: shapes sorted");
+
+            // Phase 4: Paint segments in parallel
+            final int height = renderingContext.height;
+            final int segmentHeight = height / NUM_RENDER_SEGMENTS;
+            final SegmentRenderingContext[] segmentContexts = new SegmentRenderingContext[NUM_RENDER_SEGMENTS];
+            final CountDownLatch latch = new CountDownLatch(NUM_RENDER_SEGMENTS);
+
+            for (int i = 0; i < NUM_RENDER_SEGMENTS; i++) {
+                final int segmentIndex = i;
+                final int minY = i * segmentHeight;
+                final int maxY = (i == NUM_RENDER_SEGMENTS - 1) ? height : (i + 1) * segmentHeight;
+
+                segmentContexts[i] = new SegmentRenderingContext(renderingContext, minY, maxY);
+
+                // Skip odd segments when renderAlternateSegments is enabled for overdraw debugging
+                if (developerTools.renderAlternateSegments && (i % 2 == 1)) {
+                    latch.countDown();
+                    continue;
                 }
 
-                // Wait for all segments to complete
-                try {
-                    latch.await();
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    return;
-                }
+                renderExecutor.submit(() -> {
+                    try {
+                        rootShapeCollection.paintShapes(segmentContexts[segmentIndex]);
+                    } finally {
+                        latch.countDown();
+                    }
+                });
+            }
+
+            // Wait for all segments to complete
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return;
+            }
+            debugLogBuffer.log("[VIEWPANEL] Phase 4 done: segments painted");
 
-                // Phase 5: Combine mouse results
-                combineMouseResults(segmentContexts);
+            // Phase 5: Combine mouse results
+            combineMouseResults(segmentContexts);
+            debugLogBuffer.log("[VIEWPANEL] Phase 5 done: mouse results combined");
 
-                // Phase 6: Blit to screen
+            // === Blit loop — only re-blit, never re-render ===
+            // contentsRestored() can trigger when the OS recreates the back buffer
+            // (common during window creation). Since our offscreen bufferedImage still
+            // contains the correct frame data, we only need to re-blit, not re-render.
+            int blitCount = 0;
+            do {
                 Graphics2D g = null;
                 try {
                     g = (Graphics2D) bufferStrategy.getDrawGraphics();
                     if (g != null) {
-                        g.drawImage(renderingContext.bufferedImage, 0, 0, null);
-                        g.dispose();
+                        debugLogBuffer.log("[VIEWPANEL] Blit attempt #" + (blitCount + 1) + 
+                            ", image=" + renderingContext.bufferedImage.getWidth() + "x" + renderingContext.bufferedImage.getHeight() + 
+                            ", type=" + renderingContext.bufferedImage.getType() +
+                            ", g=" + g.getClass().getSimpleName());
+                        // Use image observer to ensure proper image loading
+                        g.drawImage(renderingContext.bufferedImage, 0, 0, this);
+                        blitCount++;
                     }
                 } catch (final Exception e) {
-                    if (g != null)
-                        g.dispose();
+                    debugLogBuffer.log("[VIEWPANEL] Blit exception: " + e.getMessage());
                     break;
+                } finally {
+                    if (g != null) g.dispose();
                 }
             } while (bufferStrategy.contentsRestored());
+            debugLogBuffer.log("[VIEWPANEL] Phase 6 done: blit complete (x" + blitCount + "), contentsRestored=" + bufferStrategy.contentsRestored() + ", contentsLost=" + bufferStrategy.contentsLost());
 
             if (bufferStrategy.contentsLost()) {
+                debugLogBuffer.log("[VIEWPANEL] Buffer contents LOST, reinitializing");
                 bufferStrategyInitialized = false;
                 bufferStrategy = null;
             } else {
+                debugLogBuffer.log("[VIEWPANEL] Calling bufferStrategy.show()");
                 bufferStrategy.show();
                 java.awt.Toolkit.getDefaultToolkit().sync();
+                debugLogBuffer.log("[VIEWPANEL] show() completed");
             }
         } catch (final Exception e) {
+            debugLogBuffer.log("[VIEWPANEL] renderFrame exception: " + e.getMessage());
+            e.printStackTrace();
             bufferStrategyInitialized = false;
             bufferStrategy = null;
         }
@@ -345,9 +468,22 @@ public class ViewPanel extends Canvas {
 
     private void clearCanvasAllSegments() {
         final int rgb = (backgroundColor.r << 16) | (backgroundColor.g << 8) | backgroundColor.b;
+        debugLogBuffer.log("[VIEWPANEL] Clearing canvas with color: 0x" + Integer.toHexString(rgb) + " (black=0x0)");
         final int width = renderingContext.width;
+        final int height = renderingContext.height;
         final int[] pixels = renderingContext.pixels;
-        Arrays.fill(pixels, 0, width * renderingContext.height, rgb);
+
+        if (developerTools.renderAlternateSegments) {
+            // Clear only even segments (0, 2, 4, 6), leave odd segments black
+            final int segmentHeight = height / NUM_RENDER_SEGMENTS;
+            for (int seg = 0; seg < NUM_RENDER_SEGMENTS; seg += 2) {
+                final int minY = seg * segmentHeight;
+                final int maxY = (seg == NUM_RENDER_SEGMENTS - 1) ? height : (seg + 1) * segmentHeight;
+                Arrays.fill(pixels, minY * width, maxY * width, rgb);
+            }
+        } else {
+            Arrays.fill(pixels, 0, width * height, rgb);
+        }
     }
 
     private void combineMouseResults(final SegmentRenderingContext[] segmentContexts) {
@@ -418,7 +554,11 @@ public class ViewPanel extends Canvas {
         nextFrameTime = System.currentTimeMillis();
 
         while (renderThreadRunning) {
-            ensureThatViewIsUpToDate();
+            try {
+                ensureThatViewIsUpToDate();
+            } catch (final Exception e) {
+                e.printStackTrace();
+            }
 
             if (maintainTargetFps()) break;
         }
@@ -493,6 +633,7 @@ public class ViewPanel extends Canvas {
                 || (renderingContext.width != panelWidth)
                 || (renderingContext.height != panelHeight)) {
             renderingContext = new RenderingContext(panelWidth, panelHeight);
+            renderingContext.developerTools = developerTools;
         }
 
         renderingContext.prepareForNewFrameRendering();
index 36cb220..df26d03 100644 (file)
@@ -38,6 +38,9 @@ public class ViewSpaceTracker {
      */
     public Vertex down;
 
+    /**
+     * Creates a new view space tracker.
+     */
     public ViewSpaceTracker() {
     }
 
index 8594a2c..32810d9 100755 (executable)
@@ -11,8 +11,14 @@ package eu.svjatoslav.sixth.e3d.gui;
  */
 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;
     }
index 027dbf0..6580906 100644 (file)
@@ -18,6 +18,18 @@ import java.io.IOException;
 
 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(
index 0a3f4af..5b6db3d 100644 (file)
@@ -42,11 +42,23 @@ public class InputManager implements
     private boolean mouseMoved;
     private boolean mouseWithinWindow = false;
 
+    /**
+     * Creates an input manager attached to the given view panel.
+     *
+     * @param viewPanel the view panel to receive input from
+     */
     public InputManager(final ViewPanel viewPanel) {
         this.viewPanel = viewPanel;
         bind(viewPanel);
     }
 
+    /**
+     * Processes accumulated input events and updates camera based on mouse drag/wheel.
+     *
+     * @param viewPanel                  the view panel
+     * @param millisecondsSinceLastFrame time since last frame (unused)
+     * @return {@code true} if a view repaint is needed
+     */
     @Override
     public boolean onFrame(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
         boolean viewUpdateNeeded = handleKeyboardEvents();
@@ -56,6 +68,11 @@ public class InputManager implements
         return viewUpdateNeeded;
     }
 
+    /**
+     * Binds this input manager to listen for events on the given component.
+     *
+     * @param component the component to attach listeners to
+     */
     private void bind(final Component component) {
         component.addMouseMotionListener(this);
         component.addKeyListener(this);
@@ -63,6 +80,11 @@ public class InputManager implements
         component.addMouseWheelListener(this);
     }
 
+    /**
+     * Processes all accumulated keyboard events and forwards them to the current focus owner.
+     *
+     * @return {@code true} if any event handler requested a repaint
+     */
     private boolean handleKeyboardEvents() {
         final KeyboardInputHandler currentFocusOwner = viewPanel.getKeyboardFocusStack().getCurrentFocusOwner();
 
@@ -78,6 +100,13 @@ public class InputManager implements
         return viewUpdateNeeded;
     }
 
+    /**
+     * Processes a single keyboard event by dispatching to the focus owner.
+     *
+     * @param currentFocusOwner the component that currently has keyboard focus
+     * @param keyEvent          the keyboard event to process
+     * @return {@code true} if the handler requested a repaint
+     */
     private boolean processKeyEvent(KeyboardInputHandler currentFocusOwner, KeyEvent keyEvent) {
         switch (keyEvent.getID()) {
             case KeyEvent.KEY_PRESSED:
@@ -89,6 +118,13 @@ public class InputManager implements
         return false;
     }
 
+    /**
+     * Handles mouse clicks and hover detection.
+     * Sets up the mouse event in the rendering context for shape hit testing.
+     *
+     * @param viewPanel the view panel
+     * @return {@code true} if a repaint is needed
+     */
     private synchronized boolean handleMouseClicksAndHover(final ViewPanel viewPanel) {
         boolean rerenderNeeded = false;
         MouseEvent event = findClickLocationToTrace();
@@ -134,6 +170,10 @@ public class InputManager implements
 
     @Override
     public void keyPressed(final KeyEvent evt) {
+        if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_F12) {
+            viewPanel.showDeveloperToolsPanel();
+            return;
+        }
         synchronized (detectedKeyEvents) {
             pressedKeysToPressedTimeMap.put(evt.getKeyCode(), System.currentTimeMillis());
             detectedKeyEvents.add(evt);
index fd4d5cf..9b9ce68 100644 (file)
@@ -32,6 +32,12 @@ import java.util.Set;
  */
 public class KeyboardHelper {
 
+    /**
+     * Private constructor to prevent instantiation of this utility class.
+     */
+    private KeyboardHelper() {
+    }
+
     /** Key code for the Tab key. */
     public static final int TAB = 9;
     /** Key code for the Down arrow key. */
index ccdb962..e80bcd9 100644 (file)
@@ -18,22 +18,36 @@ import java.awt.event.KeyEvent;
 public interface KeyboardInputHandler {
 
     /**
-     * @return <code>true</code> if view needs to be re-rendered.
+     * Called when the component loses keyboard focus.
+     *
+     * @param viewPanel the view panel that owns this handler
+     * @return {@code true} if view needs to be re-rendered
      */
     boolean focusLost(ViewPanel viewPanel);
 
     /**
-     * @return <code>true</code> if view needs to be re-rendered.
+     * Called when the component receives keyboard focus.
+     *
+     * @param viewPanel the view panel that owns this handler
+     * @return {@code true} if view needs to be re-rendered
      */
     boolean focusReceived(ViewPanel viewPanel);
 
     /**
-     * @return <code>true</code> if view needs to be re-rendered.
+     * Called when a key is pressed while the component has focus.
+     *
+     * @param event     the key event
+     * @param viewPanel the view panel that owns this handler
+     * @return {@code true} if view needs to be re-rendered
      */
     boolean keyPressed(KeyEvent event, ViewPanel viewPanel);
 
     /**
-     * @return <code>true</code> if view needs to be re-rendered.
+     * Called when a key is released while the component has focus.
+     *
+     * @param event     the key event
+     * @param viewPanel the view panel that owns this handler
+     * @return {@code true} if view needs to be re-rendered
      */
     boolean keyReleased(KeyEvent event, ViewPanel viewPanel);
 
index 5b6d470..3a9dc3a 100644 (file)
@@ -12,7 +12,8 @@ public interface MouseInteractionController {
     /**
      * Called when mouse is clicked on component.
      *
-     * @return <code>true</code> if view update is needed as a consequence of this mouse click.
+     * @param button the mouse button that was clicked (1 = left, 2 = middle, 3 = right)
+     * @return {@code true} if view update is needed as a consequence of this mouse click
      */
     boolean mouseClicked(int button);
 
index f479a88..c8feb38 100644 (file)
@@ -33,6 +33,12 @@ import java.awt.event.KeyEvent;
  */
 public class WorldNavigationUserInputTracker implements KeyboardInputHandler, FrameListener {
 
+    /**
+     * Creates a new world navigation input tracker.
+     */
+    public WorldNavigationUserInputTracker() {
+    }
+
     @Override
     public boolean onFrame(final ViewPanel viewPanel,
                                 final int millisecondsSinceLastFrame) {
index dfbbd22..62256b9 100644 (file)
@@ -1,6 +1,7 @@
-package eu.svjatoslav.sixth.e3d.gui.humaninput;
-
 /**
- * This package is responsible for tracking human input devices (keyboard, mouse, etc.) and
- * forwarding those inputs to subsequent virtual components.
- */
\ No newline at end of file
+ * Provides input device tracking (keyboard, mouse) and event forwarding to virtual components.
+ *
+ * @see eu.svjatoslav.sixth.e3d.gui.humaninput.InputManager
+ * @see eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardFocusStack
+ */
+package eu.svjatoslav.sixth.e3d.gui.humaninput;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/package-info.java
new file mode 100644 (file)
index 0000000..ecb4d5f
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Graphical user interface components for the Sixth 3D engine.
+ *
+ * <p>This package provides the primary integration points for embedding 3D rendering
+ * into Java applications using Swing/AWT.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.gui.ViewPanel} - The main rendering surface (JPanel)</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.gui.ViewFrame} - A JFrame with embedded ViewPanel</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.gui.Camera} - Represents the viewer's position and orientation</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.gui.DeveloperTools} - Debugging and profiling utilities</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.gui.ViewPanel
+ * @see eu.svjatoslav.sixth.e3d.gui.Camera
+ */
+
+package eu.svjatoslav.sixth.e3d.gui;
\ No newline at end of file
index 27113b0..c49ecf7 100644 (file)
@@ -14,6 +14,11 @@ public class Character {
      */
     char value;
 
+    /**
+     * Creates a character with the given value.
+     *
+     * @param value the character value
+     */
     public Character(final char value) {
         this.value = value;
     }
index b792779..5acabc6 100644 (file)
@@ -11,15 +11,31 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
  */
 public class LookAndFeel {
 
+    /** Default foreground (text) color. */
     public Color foreground = new Color(255, 255, 255);
+
+    /** Default background color. */
     public Color background = new Color(20, 20, 20, 255);
 
+    /** Background color for tab stop positions. */
     public Color tabStopBackground = new Color(25, 25, 25, 255);
 
+    /** Cursor foreground color. */
     public Color cursorForeground = new Color(255, 255, 255);
+
+    /** Cursor background color. */
     public Color cursorBackground = new Color(255, 0, 0);
 
+    /** Selection foreground color. */
     public Color selectionForeground = new Color(255, 255, 255);
+
+    /** Selection background color. */
     public Color selectionBackground = new Color(0, 80, 80);
 
+    /**
+     * Creates a look and feel with default colors.
+     */
+    public LookAndFeel() {
+    }
+
 }
index 47c898f..7be9770 100644 (file)
@@ -17,6 +17,17 @@ public class Page {
      */
     public List<TextLine> rows = new ArrayList<>();
 
+    /**
+     * Creates a new empty page.
+     */
+    public Page() {
+    }
+
+    /**
+     * Ensures that the page has at least the specified number of lines.
+     *
+     * @param row the minimum number of lines required
+     */
     public void ensureMaxTextLine(final int row) {
         while (rows.size() <= row)
             rows.add(new TextLine());
@@ -26,7 +37,9 @@ public class Page {
      * Returns the character at the specified location.
      * If the location is out of bounds, returns a space.
      *
-     * @return The character at the specified location.
+     * @param row    the row index
+     * @param column the column index
+     * @return the character at the specified location
      */
     public char getChar(final int row, final int column) {
         if (rows.size() <= row)
@@ -84,10 +97,23 @@ public class Page {
         return result.toString();
     }
 
+    /**
+     * Inserts a character at the specified position.
+     *
+     * @param row   the row index
+     * @param col   the column index
+     * @param value the character to insert
+     */
     public void insertCharacter(final int row, final int col, final char value) {
         getLine(row).insertCharacter(col, value);
     }
 
+    /**
+     * Inserts a line at the specified row.
+     *
+     * @param row      the row index where to insert
+     * @param textLine the text line to insert
+     */
     public void insertLine(final int row, final TextLine textLine) {
         rows.add(row, textLine);
     }
index 5ed7a76..d4bfbf4 100755 (executable)
@@ -98,6 +98,10 @@ public class TextEditComponent extends GuiComponent implements ClipboardOwner {
      * Selection start and end pointers.
      */
     public TextPointer selectionStart = new TextPointer(0, 0);
+
+    /**
+     * The end position of the text selection.
+     */
     public TextPointer selectionEnd = new TextPointer(0, 0);
 
     /**
index ff8dff5..cf1eb11 100644 (file)
@@ -1,5 +1,6 @@
-package eu.svjatoslav.sixth.e3d.gui.textEditorComponent;
-
 /**
- * This package contains a simple text editor component.
- */
\ No newline at end of file
+ * Provides a simple text editor component rendered in 3D space.
+ *
+ * @see eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextEditComponent
+ */
+package eu.svjatoslav.sixth.e3d.gui.textEditorComponent;
\ No newline at end of file
index d921357..b76ce06 100644 (file)
@@ -34,6 +34,9 @@ public class Rotation implements Cloneable {
      */
     private double angleYZ = 0;
 
+    /**
+     * Creates a rotation with no rotation (zero angles).
+     */
     public Rotation() {
         computeMultipliers();
     }
@@ -50,6 +53,11 @@ public class Rotation implements Cloneable {
         computeMultipliers();
     }
 
+    /**
+     * Creates a copy of this rotation with the same angles.
+     *
+     * @return a new rotation with the same angle values
+     */
     @Override
     public Rotation clone() {
         return new Rotation(angleXZ, angleYZ);
@@ -105,6 +113,11 @@ public class Rotation implements Cloneable {
         computeMultipliers();
     }
 
+    /**
+     * Copies the angles from another rotation into this rotation.
+     *
+     * @param rotation the rotation to copy angles from
+     */
     public void setAngles(Rotation rotation) {
         this.angleXZ = rotation.angleXZ;
         this.angleYZ = rotation.angleYZ;
index 36a6202..8131ef6 100755 (executable)
@@ -25,6 +25,9 @@ public class Transform implements Cloneable {
      */
     private final Rotation rotation;
 
+    /**
+     * Creates a transform with no translation or rotation (identity transform).
+     */
     public Transform() {
         translation = new Point3D();
         rotation = new Rotation();
@@ -65,15 +68,30 @@ public class Transform implements Cloneable {
         this.rotation = rotation;
     }
 
+    /**
+     * Creates a copy of this transform with cloned translation and rotation.
+     *
+     * @return a new transform with the same translation and rotation values
+     */
     @Override
     public Transform clone() {
         return new Transform(translation, rotation);
     }
 
+    /**
+     * Returns the rotation component of this transform.
+     *
+     * @return the rotation (mutable reference)
+     */
     public Rotation getRotation() {
         return rotation;
     }
 
+    /**
+     * Returns the translation component of this transform.
+     *
+     * @return the translation point (mutable reference)
+     */
     public Point3D getTranslation() {
         return translation;
     }
index 1088dc4..5f29a5d 100644 (file)
@@ -29,6 +29,12 @@ import eu.svjatoslav.sixth.e3d.geometry.Point3D;
  */
 public class TransformStack {
 
+    /**
+     * Creates a new empty transform stack.
+     */
+    public TransformStack() {
+    }
+
     /**
      * Array of transforms in the stack.
      * Fixed size for efficiency to avoid memory allocation during rendering.
index 4fedbb9..1da9fdd 100644 (file)
@@ -9,51 +9,85 @@ import eu.svjatoslav.sixth.e3d.geometry.Point3D;
 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
 
 /**
- * Vertex is a point where two or more lines, line segments, or rays come together.
- * In other words, it's a corner of a polygon, polyhedron, or other geometric shape.
- * For example, a triangle has three vertices, a square has four, and a cube has eight.
+ * A vertex in 3D space with transformation and screen projection support.
+ *
+ * <p>A vertex represents a corner point of a polygon or polyhedron. In addition to
+ * the 3D coordinate, it stores the transformed position (relative to viewer) and
+ * the projected screen coordinates for rendering.</p>
+ *
+ * <p><b>Coordinate spaces:</b></p>
+ * <ul>
+ *   <li>{@link #coordinate} - Original position in local/model space</li>
+ *   <li>{@link #transformedCoordinate} - Position relative to viewer (camera space)</li>
+ *   <li>{@link #onScreenCoordinate} - 2D screen position after perspective projection</li>
+ * </ul>
+ *
+ * <p><b>Example:</b></p>
+ * <pre>{@code
+ * Vertex v = new Vertex(new Point3D(10, 20, 30));
+ * v.calculateLocationRelativeToViewer(transformStack, renderContext);
+ * if (v.transformedCoordinate.z > 0) {
+ *     // Vertex is in front of the camera
+ * }
+ * }</pre>
+ *
+ * @see Point3D
+ * @see TransformStack
  */
 public class Vertex {
 
     /**
-     * Vertex coordinate in 3D space.
+     * Vertex coordinate in local/model 3D space.
      */
     public Point3D coordinate;
 
     /**
-     * Vertex coordinate relative to the viewer after transformation.
-     * Visible vertices have positive z coordinate.
-     * Viewer is located at (0, 0, 0).
+     * Vertex coordinate relative to the viewer after transformation (camera space).
+     * Visible vertices have positive z coordinate (in front of the viewer).
      * No perspective correction is applied.
      */
     public Point3D transformedCoordinate;
 
     /**
-     * Vertex coordinate in pixels relative to the top left corner of the screen after transformation
-     * and perspective correction.
+     * Vertex position on screen in pixels, relative to top-left corner.
+     * Calculated after transformation and perspective projection.
      */
     public Point2D onScreenCoordinate;
 
 
     /**
-     * Coordinate within texture.
+     * Texture coordinate for UV mapping (optional).
      */
     public Point2D textureCoordinate;
 
 
     /**
-     * The frame number when this vertex was last transformed.
+     * The frame number when this vertex was last transformed (for caching).
      */
-    private int lastTransformedFrame;
+    private int lastTransformedFrame = -1;  // Start at -1 so first frame (frameNumber=1) will transform
 
+    /**
+     * Creates a vertex at the origin (0, 0, 0) with no texture coordinate.
+     */
     public Vertex() {
         this(new Point3D());
     }
 
+    /**
+     * Creates a vertex at the specified position with no texture coordinate.
+     *
+     * @param location the 3D position of this vertex
+     */
     public Vertex(final Point3D location) {
         this(location, null);
     }
 
+    /**
+     * Creates a vertex at the specified position with an optional texture coordinate.
+     *
+     * @param location          the 3D position of this vertex
+     * @param textureCoordinate the UV texture coordinate, or {@code null} for none
+     */
     public Vertex(final Point3D location, Point2D textureCoordinate) {
         coordinate = location;
         transformedCoordinate = new Point3D();
@@ -63,11 +97,14 @@ public class Vertex {
 
 
     /**
-     * Transforms vertex coordinate to calculate its location relative to the viewer.
-     * It also calculates its location on the screen.
+     * Transforms this vertex from model space to screen space.
+     *
+     * <p>This method applies the transform stack to compute the vertex position
+     * relative to the viewer, then projects it to 2D screen coordinates.
+     * Results are cached per-frame to avoid redundant calculations.</p>
      *
-     * @param transforms    Transforms pipeline.
-     * @param renderContext Rendering context.
+     * @param transforms    the transform stack to apply (world-to-camera transforms)
+     * @param renderContext the rendering context providing projection parameters
      */
     public void calculateLocationRelativeToViewer(final TransformStack transforms,
                                                   final RenderingContext renderContext) {
index 7dce375..9ea02e9 100644 (file)
@@ -1,16 +1,35 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
 package eu.svjatoslav.sixth.e3d.renderer.octree;
 
 /**
- * Point in 3D space. Used for octree. All coordinates are integers.
+ * Point in 3D space with integer coordinates. Used for octree voxel positions.
  */
 public class IntegerPoint
 {
-    public int x, y, z = 0;
+    /** X coordinate. */
+    public int x;
+    /** Y coordinate. */
+    public int y;
+    /** Z coordinate. */
+    public int z = 0;
 
+    /**
+     * Creates a point at the origin (0, 0, 0).
+     */
     public IntegerPoint()
     {
     }
 
+    /**
+     * Creates a point with the specified coordinates.
+     *
+     * @param x the X coordinate
+     * @param y the Y coordinate
+     * @param z the Z coordinate
+     */
     public IntegerPoint(final int x, final int y, final int z)
     {
         this.x = x;
index a4f4382..dee7a76 100755 (executable)
@@ -12,52 +12,73 @@ import static java.lang.Integer.max;
 import static java.lang.Integer.min;
 
 /**
- * <pre>
- * There are 3 cell types:
+ * Sparse voxel octree for 3D volume storage and ray tracing.
  *
- *      UNUSED
+ * <p>The octree represents a 3D volume with three cell types:</p>
+ * <ul>
+ *   <li><b>UNUSED</b> - Empty cell, not yet allocated</li>
+ *   <li><b>SOLID</b> - Contains color and illumination data</li>
+ *   <li><b>CLUSTER</b> - Contains pointers to 8 child cells (for subdivision)</li>
+ * </ul>
  *
- *      SOLID
- *          contains:
- *              original color
- *              visible color, after being illuminated by nearby light sources
+ * <p>Cell data is stored in parallel arrays ({@code cell1} through {@code cell8})
+ * for memory efficiency. Each array stores different aspects of cell data.</p>
  *
- *      CLUSTER
- *          contains pointers to 8 sub cells
- * </pre>
+ * @see eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RayTracer
+ * @see eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.Ray
  */
-
 public class OctreeVolume {
 
-    // cell is not hit by the ray
+    /** Return value indicating no intersection during ray tracing. */
     public static final int TRACE_NO_HIT = -1;
 
-    // solid cell (contains color and illumination)
+    /** Cell state marker for solid cells. */
     private static final int CELL_STATE_SOLID = -2;
 
-    // unused cell
+    /** Cell state marker for unused/empty cells. */
     private static final int CELL_STATE_UNUSED = -1;
 
+    /** Cell data array 1: stores cell state and first child pointer. */
     public int[] cell1;
+    /** Cell data array 2: stores color values. */
     public int[] cell2;
+    /** Cell data array 3: stores illumination values. */
     public int[] cell3;
+    /** Cell data array 4: stores child pointer 4. */
     public int[] cell4;
+    /** Cell data array 5: stores child pointer 5. */
     public int[] cell5;
+    /** Cell data array 6: stores child pointer 6. */
     public int[] cell6;
+    /** Cell data array 7: stores child pointer 7. */
     public int[] cell7;
+    /** Cell data array 8: stores child pointer 8. */
     public int[] cell8;
 
     /**
-     * Pointer to the first unused cell.
+     * Pointer to the next unused cell in the allocation buffer.
      */
     public int cellAllocationPointer = 0;
+
+    /** Number of currently allocated cells. */
     public int usedCellsCount = 0;
+
+    /** Size of the root (master) cell in world units. */
     public int masterCellSize;
 
+    /**
+     * Creates a new octree volume with default buffer size (1.5M cells)
+     * and master cell size of 256*64 units.
+     */
     public OctreeVolume() {
         initWorld(1500000, 256 * 64);
     }
 
+    /**
+     * Subdivides a solid cell into 8 child cells, each with the same color and illumination.
+     *
+     * @param pointer the cell to break up
+     */
     public void breakSolidCell(final int pointer) {
         final int color = getCellColor(pointer);
         final int illumination = getCellIllumination(pointer);
@@ -88,12 +109,27 @@ public class OctreeVolume {
         cell8[pointer] = 0;
     }
 
+    /**
+     * Marks a cell as deleted and returns it to the unused pool.
+     *
+     * @param cellPointer the cell to delete
+     */
     public void deleteCell(final int cellPointer) {
         clearCell(cellPointer);
         cell1[cellPointer] = CELL_STATE_UNUSED;
         usedCellsCount--;
     }
 
+    /**
+     * Tests whether a ray intersects with a cubic region.
+     *
+     * @param cubeX    the X center of the cube
+     * @param cubeY    the Y center of the cube
+     * @param cubeZ    the Z center of the cube
+     * @param cubeSize the half-size of the cube
+     * @param r        the ray to test
+     * @return intersection type code, or 0 if no intersection
+     */
     public int doesIntersect(final int cubeX, final int cubeY, final int cubeZ,
                              final int cubeSize, final Ray r) {
 
@@ -212,7 +248,11 @@ public class OctreeVolume {
     }
 
     /**
-     * Fill 3D rectangle.
+     * Fills a 3D rectangular region with solid cells of the given color.
+     *
+     * @param p1    one corner of the rectangle
+     * @param p2    the opposite corner of the rectangle
+     * @param color the color to fill with
      */
     public void fillRectangle(IntegerPoint p1, IntegerPoint p2, Color color) {
 
@@ -229,14 +269,32 @@ public class OctreeVolume {
                     putCell(x, y, z, 0, 0, 0, masterCellSize, 0, color);
     }
 
+    /**
+     * Returns the color value stored in a solid cell.
+     *
+     * @param pointer the cell pointer
+     * @return the packed RGB color value
+     */
     public int getCellColor(final int pointer) {
         return cell2[pointer];
     }
 
+    /**
+     * Returns the illumination value stored in a solid cell.
+     *
+     * @param pointer the cell pointer
+     * @return the packed RGB illumination value
+     */
     public int getCellIllumination(final int pointer) {
         return cell3[pointer];
     }
 
+    /**
+     * Initializes the octree storage arrays with the specified buffer size and root cell size.
+     *
+     * @param bufferLength   the number of cells to allocate space for
+     * @param masterCellSize the size of the root cell in world units
+     */
     public void initWorld(final int bufferLength, final int masterCellSize) {
         // System.out.println("Initializing new world");
 
@@ -260,6 +318,12 @@ public class OctreeVolume {
         clearCell(0);
     }
 
+    /**
+     * Checks if the cell at the given pointer is a solid (leaf) cell.
+     *
+     * @param pointer the cell pointer to check
+     * @return {@code true} if the cell is solid
+     */
     public boolean isCellSolid(final int pointer) {
         return cell1[pointer] == CELL_STATE_SOLID;
     }
@@ -285,6 +349,13 @@ public class OctreeVolume {
         }
     }
 
+    /**
+     * Allocates a new solid cell with the given color and illumination.
+     *
+     * @param color        the color value for the new cell
+     * @param illumination the illumination value for the new cell
+     * @return the pointer to the newly allocated cell
+     */
     public int makeNewCell(final int color, final int illumination) {
         final int pointer = getNewCellPointer();
         markCellAsSolid(pointer);
@@ -302,6 +373,14 @@ public class OctreeVolume {
         cell1[pointer] = CELL_STATE_SOLID;
     }
 
+    /**
+     * Stores a voxel at the given world coordinates with the specified color.
+     *
+     * @param x     the X coordinate
+     * @param y     the Y coordinate
+     * @param z     the Z coordinate
+     * @param color the color of the voxel
+     */
     public void putCell(final int x, final int y, final int z, final Color color) {
         putCell(x, y, z, 0, 0, 0, masterCellSize, 0, color);
     }
@@ -399,18 +478,36 @@ public class OctreeVolume {
         }
     }
 
+    /**
+     * Sets the color value for the cell at the given pointer.
+     *
+     * @param pointer the cell pointer
+     * @param color   the color value to set
+     */
     public void setCellColor(final int pointer, final int color) {
         cell2[pointer] = color;
     }
 
+    /**
+     * Sets the illumination value for the cell at the given pointer.
+     *
+     * @param pointer      the cell pointer
+     * @param illumination the illumination value to set
+     */
     public void setCellIllumination(final int pointer, final int illumination) {
         cell3[pointer] = illumination;
     }
 
     /**
-     * Trace ray through the world and return pointer to intersecting cell.
+     * Traces a ray through the octree to find an intersecting solid cell.
      *
-     * @return pointer to intersecting cell or TRACE_NO_HIT if no intersection.
+     * @param cellX    the X coordinate of the current cell center
+     * @param cellY    the Y coordinate of the current cell center
+     * @param cellZ    the Z coordinate of the current cell center
+     * @param cellSize the size of the current cell
+     * @param pointer  the pointer to the current cell
+     * @param ray      the ray to trace
+     * @return pointer to intersecting cell or TRACE_NO_HIT if no intersection
      */
     public int traceCell(final int cellX, final int cellY, final int cellZ,
                          final int cellSize, final int pointer, final Ray ray) {
index f0b214b..12d149e 100644 (file)
@@ -20,6 +20,12 @@ public class CameraView {
      */
     Point3D cameraCenter, topLeft, topRight, bottomLeft, bottomRight;
 
+    /**
+     * Creates a camera view for ray tracing from the given camera and zoom level.
+     *
+     * @param camera the camera to create a view for
+     * @param zoom   the zoom level (scales the view frustum)
+     */
     public CameraView(final Camera camera, final double zoom) {
         // compute camera view coordinates as if camera is at (0,0,0) and look at (0,0,1)
         final float viewAngle = (float) .6;
index c0939d7..174b130 100755 (executable)
@@ -25,6 +25,13 @@ public class LightSource {
      */
     Point3D location;
 
+    /**
+     * Creates a light source at the given location with the specified color and brightness.
+     *
+     * @param location   the position of the light source in world space
+     * @param color      the color of the light
+     * @param Brightness the brightness multiplier (0.0 = off, 1.0 = full)
+     */
     public LightSource(final Point3D location, final Color color,
                        final float Brightness) {
         this.location = location;
index e8237a9..f0f5184 100755 (executable)
@@ -25,10 +25,18 @@ import java.net.URL;
  */
 public class RaytracingCamera extends TexturedRectangle {
 
+    /** Size of the camera view in world units. */
     public static final int SIZE = 100;
+    /** Size of the rendered image in pixels. */
     public static final int IMAGE_SIZE = 500;
     private final CameraView cameraView;
 
+    /**
+     * Creates a raytracing camera at the specified camera position.
+     *
+     * @param camera the camera to use for the view
+     * @param zoom   the zoom level
+     */
     public RaytracingCamera(final Camera camera, final double zoom) {
         super(new Transform(camera.getTransform().getTranslation().clone()));
         cameraView = new CameraView(camera, zoom);
@@ -87,10 +95,22 @@ public class RaytracingCamera extends TexturedRectangle {
 
     }
 
+    /**
+     * Returns the camera view used for ray tracing.
+     *
+     * @return the camera view
+     */
     public CameraView getCameraView() {
         return cameraView;
     }
 
+    /**
+     * Loads a sprite image from the classpath.
+     *
+     * @param ref the resource path
+     * @return the loaded image
+     * @throws IOException if the image cannot be loaded
+     */
     public BufferedImage getSprite(final String ref) throws IOException {
         final URL url = this.getClass().getClassLoader().getResource(ref);
         return ImageIO.read(url);
index dfa42eb..c594571 100644 (file)
@@ -117,6 +117,8 @@ public final class Color {
     }
 
     /**
+     * Creates a color from a hexadecimal string.
+     *
      * @param colorHexCode color code in hex format.
      *                     Supported formats are:
      *                     <pre>
index 290d961..4fbd91e 100644 (file)
@@ -24,11 +24,17 @@ import java.util.Comparator;
  * <p>This class is used internally by {@link ShapeCollection} during the render pipeline.
  * You typically do not need to interact with it directly.</p>
  *
- * @see ShapeCollection#paint(eu.svjatoslav.sixth.e3d.gui.ViewPanel, RenderingContext)
+ * @see ShapeCollection#paintShapes(RenderingContext)
  * @see AbstractCoordinateShape#onScreenZ
  */
 public class RenderAggregator {
 
+    /**
+     * Creates a new render aggregator.
+     */
+    public RenderAggregator() {
+    }
+
     private final ArrayList<AbstractCoordinateShape> shapes = new ArrayList<>();
     private final ShapesZIndexComparator comparator = new ShapesZIndexComparator();
     private boolean sorted = false;
@@ -80,9 +86,9 @@ public class RenderAggregator {
     }
 
     /**
-     * Adds a transformed shape to the queue for rendering in this frame.
+     * Queues a shape for rendering. Called during the transform phase.
      *
-     * @param shape the shape to render, with its screen-space coordinates already computed
+     * @param shape the shape to queue
      */
     public void queueShapeForRendering(final AbstractCoordinateShape shape) {
         shapes.add(shape);
index 05717fa..07be4a3 100755 (executable)
@@ -50,6 +50,12 @@ import java.util.List;
  */
 public class ShapeCollection {
 
+    /**
+     * Creates a new empty shape collection.
+     */
+    public ShapeCollection() {
+    }
+
     private final RenderAggregator aggregator = new RenderAggregator();
     private final TransformStack transformStack = new TransformStack();
     private final List<AbstractShape> shapes = new ArrayList<>();
@@ -79,9 +85,9 @@ public class ShapeCollection {
     }
 
     /**
-     * Removes all shapes from this collection.
+     * Removes all shapes from this collection. This method is thread-safe.
      */
-    public void clear() {
+    public synchronized void clear() {
         shapes.clear();
     }
 
@@ -95,8 +101,6 @@ public class ShapeCollection {
     public synchronized void transformShapes(final ViewPanel viewPanel,
                                               final RenderingContext renderingContext) {
 
-        renderingContext.frameNumber++;
-
         aggregator.reset();
         transformStack.clear();
 
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/lighting/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/lighting/package-info.java
new file mode 100644 (file)
index 0000000..9ea82bd
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Lighting system for flat-shaded polygon rendering.
+ *
+ * <p>This package implements a simple Lambertian lighting model for shading
+ * solid polygons based on their surface normals relative to light sources.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager} - Manages lights and calculates shading</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource} - Represents a point light source</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.lighting;
\ No newline at end of file
index f0d37b1..d5eaf42 100644 (file)
@@ -32,6 +32,12 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.RenderAggregator;
  */
 public abstract class AbstractShape {
 
+    /**
+     * Default constructor for abstract shape.
+     */
+    public AbstractShape() {
+    }
+
     /**
      * Optional controller that receives mouse interaction events (click, enter, exit)
      * when the user interacts with this shape in the 3D view.
index a956e22..821bb32 100644 (file)
@@ -13,32 +13,47 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
 import eu.svjatoslav.sixth.e3d.renderer.raster.texture.TextureBitmap;
 
 /**
- * Base class for textures always facing the viewer.
- * <p>
- * This class implements the "billboard" rendering technique where the texture
+ * A billboard: a texture that always faces the viewer.
+ *
+ * <p>This class implements the "billboard" rendering technique where the texture
  * remains oriented towards the camera regardless of 3D position. The visible size
- * is calculated based on distance from viewer (z-coordinate) and scale factor.
- * <p>
- * The texture mapping algorithm:
- * 1. Calculates screen coverage based on perspective
- * 2. Clips to viewport boundaries
- * 3. Maps texture pixels to screen pixels using proportional scaling
+ * is calculated based on distance from viewer (z-coordinate) and scale factor.</p>
+ *
+ * <p><b>Texture mapping algorithm:</b></p>
+ * <ol>
+ *   <li>Calculates screen coverage based on perspective</li>
+ *   <li>Clips to viewport boundaries</li>
+ *   <li>Maps texture pixels to screen pixels using proportional scaling</li>
+ * </ol>
+ *
+ * @see GlowingPoint a billboard with a circular gradient texture
+ * @see Texture
  */
 public class Billboard extends AbstractCoordinateShape {
 
     private static final double SCALE_MULTIPLIER = 0.005;
+
+    /**
+     * The texture to display on this billboard.
+     */
     public final Texture texture;
 
     /**
-     * Scale of the texture object.
-     * <p>
-     * Object rendered visible size on the screen depends on underlying texture size and scale.
-     * <p>
-     * 0 means that object will be infinitely small.
-     * 1 in recommended value to maintain sharpness of the texture as seen by the viewer.
+     * Scale factor for the billboard's visible size.
+     * <ul>
+     *   <li>0 means infinitely small</li>
+     *   <li>1 is recommended to maintain texture sharpness</li>
+     * </ul>
      */
     private double scale;
 
+    /**
+     * Creates a billboard at the specified position with the given scale and texture.
+     *
+     * @param point   the 3D position of the billboard center
+     * @param scale   the scale factor (1.0 is recommended for sharpness)
+     * @param texture the texture to display
+     */
     public Billboard(final Point3D point, final double scale,
                      final Texture texture) {
         super(new Vertex(point));
@@ -47,9 +62,12 @@ public class Billboard extends AbstractCoordinateShape {
     }
 
     /**
-     * Paint the texture on the screen (targetRenderingArea)
+     * Renders this billboard to the screen.
+     *
+     * <p>The billboard is rendered as a screen-aligned quad centered on the projected
+     * position. The size is computed based on distance and scale factor.</p>
      *
-     * @param targetRenderingArea the screen to paint on
+     * @param targetRenderingArea the rendering context containing the pixel buffer
      */
     @Override
     public void paint(final RenderingContext targetRenderingArea) {
@@ -132,14 +150,19 @@ public class Billboard extends AbstractCoordinateShape {
     }
 
     /**
-     * Set the scale of the texture
+     * Sets the scale factor for this billboard.
      *
-     * @param scale the scale of the texture
+     * @param scale the scale factor (1.0 is recommended for sharpness)
      */
     public void setScale(final double scale) {
         this.scale = scale * SCALE_MULTIPLIER;
     }
 
+    /**
+     * Returns the 3D position of this billboard.
+     *
+     * @return the center position in world coordinates
+     */
     public Point3D getLocation() {
         return coordinates[0].coordinate;
     }
index 59cc454..9822112 100644 (file)
@@ -17,24 +17,35 @@ import static java.lang.Math.sqrt;
 
 /**
  * A glowing 3D point rendered with a circular gradient texture.
- * <p>
- * This class creates and reuses textures for glowing points of the same color.
+ *
+ * <p>This class creates and reuses textures for glowing points of the same color.
  * The texture is a circle with an alpha gradient from center to edge, ensuring
- *  a consistent visual appearance regardless of viewing angle.
- * <p>
- * The static set of glowing points enables texture sharing and garbage
- * collection of unused textures via WeakHashMap.
+ * a consistent visual appearance regardless of viewing angle.</p>
+ *
+ * <p><b>Texture sharing:</b> Glowing points of the same color share textures
+ * to reduce memory usage. Textures are garbage collected via WeakHashMap when
+ * no longer referenced.</p>
+ *
+ * @see Billboard the parent class
+ * @see Color
  */
 public class GlowingPoint extends Billboard {
 
     private static final int TEXTURE_RESOLUTION_PIXELS = 100;
+
     /**
-     * A set of all existing glowing points.
-     * Used to reuse textures of glowing points of the same color.
+     * Set of all existing glowing points, used for texture sharing.
      */
     private static final Set<GlowingPoint> glowingPoints = Collections.newSetFromMap(new WeakHashMap<>());
     private final Color color;
 
+    /**
+     * Creates a glowing point at the specified position with the given size and color.
+     *
+     * @param point     the 3D position of the point
+     * @param pointSize the visible size of the point
+     * @param color     the color of the glow
+     */
     public GlowingPoint(final Point3D point, final double pointSize,
                         final Color color) {
         super(point, computeScale(pointSize), getTexture(color));
@@ -46,13 +57,24 @@ public class GlowingPoint extends Billboard {
     }
 
 
+    /**
+     * Computes the scale factor from point size.
+     *
+     * @param pointSize the desired visible size
+     * @return the scale factor for the billboard
+     */
     private static double computeScale(double pointSize) {
         return pointSize / ((double) (TEXTURE_RESOLUTION_PIXELS / 50f));
     }
 
     /**
      * Returns a texture for a glowing point of the given color.
-     * The texture is a circle with a gradient from transparent to the given color.
+     *
+     * <p>Attempts to reuse an existing texture from another glowing point of the
+     * same color. If none exists, creates a new texture.</p>
+     *
+     * @param color the color of the glow
+     * @return a texture with a circular alpha gradient
      */
     private static Texture getTexture(final Color color) {
         // attempt to reuse texture from existing glowing point of the same color
index 91a89d5..83acbb0 100644 (file)
@@ -46,12 +46,25 @@ public class Line extends AbstractCoordinateShape {
      */
     public Color color;
 
+    /**
+     * Creates a copy of an existing line with cloned coordinates and color.
+     *
+     * @param parentLine the line to copy
+     */
     public Line(final Line parentLine) {
         this(parentLine.coordinates[0].coordinate.clone(),
                 parentLine.coordinates[1].coordinate.clone(),
                 new Color(parentLine.color), parentLine.width);
     }
 
+    /**
+     * Creates a line between two points with the specified color and width.
+     *
+     * @param point1 the starting point of the line
+     * @param point2 the ending point of the line
+     * @param color  the color of the line
+     * @param width  the width of the line in world units
+     */
     public Line(final Point3D point1, final Point3D point2, final Color color,
                 final double width) {
 
@@ -68,6 +81,14 @@ public class Line extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Draws a horizontal scanline between two interpolators with alpha blending.
+     *
+     * @param line1        the left edge interpolator
+     * @param line2        the right edge interpolator
+     * @param y            the Y coordinate of the scanline
+     * @param renderBuffer the rendering context to draw into
+     */
     private void drawHorizontalLine(final LineInterpolator line1,
                                     final LineInterpolator line2, final int y,
                                     final RenderingContext renderBuffer) {
@@ -133,6 +154,13 @@ public class Line extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Draws a thin line as single pixels with alpha-adjusted color.
+     * Used for lines that appear thin on screen (below minimum width threshold).
+     *
+     * @param buffer the rendering context to draw into
+     * @param alpha  the alpha value for the entire line
+     */
     private void drawSinglePixelHorizontalLine(final RenderingContext buffer,
                                                final int alpha) {
 
@@ -194,6 +222,13 @@ public class Line extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Draws a thin vertical line as single pixels with alpha-adjusted color.
+     * Used for lines that appear thin on screen and are more vertical than horizontal.
+     *
+     * @param buffer the rendering context to draw into
+     * @param alpha  the alpha value for the entire line
+     */
     private void drawSinglePixelVerticalLine(final RenderingContext buffer,
                                              final int alpha) {
 
@@ -254,6 +289,13 @@ public class Line extends AbstractCoordinateShape {
         }
     }
 
+    /**
+     * Finds the index of the first interpolator (starting from startPointer) that contains the given Y coordinate.
+     *
+     * @param startPointer the index to start searching from
+     * @param y            the Y coordinate to search for
+     * @return the index of the interpolator, or -1 if not found
+     */
     private int getLineInterpolator(final int startPointer, final int y) {
 
         for (int i = startPointer; i < lineInterpolators.length; i++)
@@ -262,6 +304,19 @@ public class Line extends AbstractCoordinateShape {
         return -1;
     }
 
+    /**
+     * Renders this line to the screen using perspective-correct width and alpha blending.
+     *
+     * <p>This method handles two rendering modes:</p>
+     * <ul>
+     *   <li>Thin lines: When the projected width is below threshold, draws single-pixel
+     *       lines with alpha adjusted for sub-pixel appearance.</li>
+     *   <li>Thick lines: Creates four edge interpolators and fills the rectangular area
+     *       scanline by scanline with perspective-correct alpha fading at edges.</li>
+     * </ul>
+     *
+     * @param buffer the rendering context containing the pixel buffer
+     */
     @Override
     public void paint(final RenderingContext buffer) {
 
index c27aad6..ab17ec2 100644 (file)
@@ -14,6 +14,19 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
  * This class encapsulates common line styling parameters (width and color) to
  * avoid redundant configuration. It provides multiple constructors for
  * flexibility and ensures default values are used when not specified.
+ *
+ * <p><b>Example usage:</b></p>
+ * <pre>{@code
+ * // Create a line appearance with default color and width 2.0
+ * LineAppearance appearance = new LineAppearance(2.0, Color.RED);
+ *
+ * // Create multiple lines with the same appearance
+ * Line line1 = appearance.getLine(new Point3D(0, 0, 100), new Point3D(10, 0, 100));
+ * Line line2 = appearance.getLine(new Point3D(0, 10, 100), new Point3D(10, 10, 100));
+ *
+ * // Override color for a specific line
+ * Line blueLine = appearance.getLine(p1, p2, Color.BLUE);
+ * }</pre>
  */
 public class LineAppearance {
 
@@ -21,28 +34,62 @@ public class LineAppearance {
 
     private Color color = new Color(100, 100, 255, 255);
 
+    /**
+     * Creates a line appearance with default width (1.0) and default color (light blue).
+     */
     public LineAppearance() {
         lineWidth = 1;
     }
 
+    /**
+     * Creates a line appearance with the specified width and default color (light blue).
+     *
+     * @param lineWidth the line width in world units
+     */
     public LineAppearance(final double lineWidth) {
         this.lineWidth = lineWidth;
     }
 
+    /**
+     * Creates a line appearance with the specified width and color.
+     *
+     * @param lineWidth the line width in world units
+     * @param color     the line color
+     */
     public LineAppearance(final double lineWidth, final Color color) {
         this.lineWidth = lineWidth;
         this.color = color;
     }
 
+    /**
+     * Creates a line between two points using this appearance's width and color.
+     *
+     * @param point1 the starting point of the line
+     * @param point2 the ending point of the line
+     * @return a new Line instance
+     */
     public Line getLine(final Point3D point1, final Point3D point2) {
         return new Line(point1, point2, color, lineWidth);
     }
 
+    /**
+     * Creates a line between two points using this appearance's width and a custom color.
+     *
+     * @param point1 the starting point of the line
+     * @param point2 the ending point of the line
+     * @param color  the color for this specific line (overrides the default)
+     * @return a new Line instance
+     */
     public Line getLine(final Point3D point1, final Point3D point2,
                         final Color color) {
         return new Line(point1, point2, color, lineWidth);
     }
 
+    /**
+     * Returns the line width configured for this appearance.
+     *
+     * @return the line width in world units
+     */
     public double getLineWidth() {
         return lineWidth;
     }
index 57f8dee..8b3a642 100644 (file)
@@ -23,6 +23,18 @@ public class LineInterpolator {
     private int width;
     private double dinc;
 
+    /**
+     * Creates a new line interpolator with uninitialized endpoints.
+     */
+    public LineInterpolator() {
+    }
+
+    /**
+     * Checks if the given Y coordinate falls within the vertical span of this line.
+     *
+     * @param y the Y coordinate to test
+     * @return {@code true} if y is between y1 and y2 (inclusive)
+     */
     public boolean containsY(final int y) {
 
         if (y1 < y2) {
@@ -34,10 +46,21 @@ public class LineInterpolator {
         return false;
     }
 
+    /**
+     * Returns the depth value (d) at the current Y position.
+     *
+     * @return the interpolated depth value
+     */
     public double getD() {
         return d;
     }
 
+    /**
+     * Computes the X coordinate for the given Y position.
+     *
+     * @param y the Y coordinate
+     * @return the interpolated X coordinate
+     */
     public int getX(final int y) {
         if (height == 0)
             return (int) (x2 + x1) / 2;
@@ -49,6 +72,16 @@ public class LineInterpolator {
         return (int) x1 + ((width * distanceFromY1) / height);
     }
 
+    /**
+     * Sets the endpoints and depth values for this line interpolator.
+     *
+     * @param x1 the X coordinate of the first point
+     * @param y1 the Y coordinate of the first point
+     * @param d1 the depth value at the first point
+     * @param x2 the X coordinate of the second point
+     * @param y2 the Y coordinate of the second point
+     * @param d2 the depth value at the second point
+     */
     public void setPoints(final double x1, final double y1, final double d1,
                           final double x2, final double y2, final double d2) {
 
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/package-info.java
new file mode 100644 (file)
index 0000000..970539a
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * 3D line segment rendering with perspective-correct width and alpha blending.
+ *
+ * <p>Lines are rendered with width that adjusts based on distance from the viewer.
+ * The rendering uses interpolators for smooth edges and proper alpha blending.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line} - The line shape</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance} - Color and width configuration</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineInterpolator} - Scanline edge interpolation</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/package-info.java
new file mode 100644 (file)
index 0000000..bdfc35e
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Primitive shape implementations for the rasterization pipeline.
+ *
+ * <p>Basic shapes are the building blocks of 3D scenes. Each can be rendered
+ * independently and combined to create more complex objects.</p>
+ *
+ * <p>Subpackages:</p>
+ * <ul>
+ *   <li>{@code line} - 3D line segments with perspective-correct width</li>
+ *   <li>{@code solidpolygon} - Solid-color triangles with flat shading</li>
+ *   <li>{@code texturedpolygon} - Triangles with UV-mapped textures</li>
+ * </ul>
+ *
+ * <p>Additional basic shapes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.Billboard} - Textures that always face the camera</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint} - Circular gradient billboards</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.Billboard
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic;
\ No newline at end of file
index 20fb6f4..fa14d14 100644 (file)
@@ -49,6 +49,12 @@ public class LineInterpolator implements Comparable<LineInterpolator> {
      */
     private int absoluteHeight;
 
+    /**
+     * Creates a new line interpolator with uninitialized endpoints.
+     */
+    public LineInterpolator() {
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (o == null) return false;
index 5bcfc1e..124b679 100644 (file)
@@ -42,6 +42,14 @@ public class SolidPolygon extends AbstractCoordinateShape {
     private LightingManager lightingManager;
     private boolean backfaceCulling = false;
 
+    /**
+     * Creates a solid triangle with the specified vertices and color.
+     *
+     * @param point1 the first vertex position
+     * @param point2 the second vertex position
+     * @param point3 the third vertex position
+     * @param color  the fill color of the triangle
+     */
     public SolidPolygon(final Point3D point1, final Point3D point2,
                         final Point3D point3, final Color color) {
         super(
@@ -52,6 +60,15 @@ public class SolidPolygon extends AbstractCoordinateShape {
         this.color = color;
     }
 
+    /**
+     * Draws a horizontal scanline between two edge interpolators with alpha blending.
+     *
+     * @param line1        the left edge interpolator
+     * @param line2        the right edge interpolator
+     * @param y            the Y coordinate of the scanline
+     * @param renderBuffer the rendering context to draw into
+     * @param color        the color to draw with
+     */
     public static void drawHorizontalLine(final LineInterpolator line1,
                                           final LineInterpolator line2, final int y,
                                           final RenderingContext renderBuffer, final Color color) {
@@ -108,6 +125,24 @@ public class SolidPolygon extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Renders a triangle with mouse interaction support and optional backface culling.
+     *
+     * <p>This static method handles:</p>
+     * <ul>
+     *   <li>Rounding vertices to integer screen coordinates</li>
+     *   <li>Mouse hover detection via point-in-polygon test</li>
+     *   <li>Viewport clipping</li>
+     *   <li>Scanline rasterization with alpha blending</li>
+     * </ul>
+     *
+     * @param context                     the rendering context
+     * @param onScreenPoint1              the first vertex in screen coordinates
+     * @param onScreenPoint2              the second vertex in screen coordinates
+     * @param onScreenPoint3              the third vertex in screen coordinates
+     * @param mouseInteractionController  optional controller for mouse events, or null
+     * @param color                       the fill color
+     */
     public static void drawPolygon(final RenderingContext context,
                                    final Point2D onScreenPoint1, final Point2D onScreenPoint2,
                                    final Point2D onScreenPoint3,
@@ -187,10 +222,20 @@ public class SolidPolygon extends AbstractCoordinateShape {
                     drawHorizontalLine(b, c, y, context, color);
     }
 
+    /**
+     * Returns the fill color of this polygon.
+     *
+     * @return the polygon color
+     */
     public Color getColor() {
         return color;
     }
 
+    /**
+     * Sets the fill color of this polygon.
+     *
+     * @param color the new color
+     */
     public void setColor(final Color color) {
         this.color = color;
     }
@@ -224,14 +269,32 @@ public class SolidPolygon extends AbstractCoordinateShape {
         this.lightingManager = lightingManager;
     }
 
+    /**
+     * Checks if backface culling is enabled for this polygon.
+     *
+     * @return {@code true} if backface culling is enabled
+     */
     public boolean isBackfaceCullingEnabled() {
         return backfaceCulling;
     }
 
+    /**
+     * Enables or disables backface culling for this polygon.
+     *
+     * <p>When enabled, polygons facing away from the camera (determined by
+     * screen-space winding order) are not rendered.</p>
+     *
+     * @param backfaceCulling {@code true} to enable backface culling
+     */
     public void setBackfaceCulling(final boolean backfaceCulling) {
         this.backfaceCulling = backfaceCulling;
     }
 
+    /**
+     * Calculates the unit normal vector of this triangle.
+     *
+     * @param result the point to store the normal vector in
+     */
     private void calculateNormal(final Point3D result) {
         final Point3D v1 = coordinates[0].coordinate;
         final Point3D v2 = coordinates[1].coordinate;
@@ -261,6 +324,11 @@ public class SolidPolygon extends AbstractCoordinateShape {
         result.z = nz;
     }
 
+    /**
+     * Calculates the centroid (geometric center) of this triangle.
+     *
+     * @param result the point to store the center in
+     */
     private void calculateCenter(final Point3D result) {
         final Point3D v1 = coordinates[0].coordinate;
         final Point3D v2 = coordinates[1].coordinate;
@@ -271,6 +339,18 @@ public class SolidPolygon extends AbstractCoordinateShape {
         result.z = (v1.z + v2.z + v3.z) / 3.0;
     }
 
+    /**
+     * Renders this triangle to the screen.
+     *
+     * <p>This method performs:</p>
+     * <ul>
+     *   <li>Backface culling check (if enabled)</li>
+     *   <li>Flat shading calculation (if lighting is enabled)</li>
+     *   <li>Triangle rasterization using the static drawPolygon method</li>
+     * </ul>
+     *
+     * @param renderBuffer the rendering context containing the pixel buffer
+     */
     @Override
     public void paint(final RenderingContext renderBuffer) {
 
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/package-info.java
new file mode 100644 (file)
index 0000000..79b79d5
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Solid-color triangle rendering with scanline rasterization.
+ *
+ * <p>Solid polygons are the primary building blocks for opaque 3D surfaces.
+ * The rasterizer handles perspective-correct interpolation, alpha blending,
+ * viewport clipping, and optional flat shading.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon} - The solid triangle shape</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.LineInterpolator} - Edge interpolation for scanlines</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon;
\ No newline at end of file
index 5b6c198..ec4caeb 100644 (file)
@@ -36,6 +36,11 @@ public class PolygonBorderInterpolator implements
     private Point2D texturePoint1;
     private Point2D texturePoint2;
 
+    /**
+     * Creates a new polygon border interpolator.
+     */
+    public PolygonBorderInterpolator() {
+    }
 
     @Override
     public boolean equals(final Object o) {
@@ -66,6 +71,12 @@ public class PolygonBorderInterpolator implements
         return 0;
     }
 
+    /**
+     * Checks if the given Y coordinate falls within the vertical span of this edge.
+     *
+     * @param y the Y coordinate to test
+     * @return {@code true} if y is within the edge's vertical range
+     */
     public boolean containsY(final int y) {
 
         if (onScreenPoint1.y < onScreenPoint2.y) {
@@ -77,6 +88,11 @@ public class PolygonBorderInterpolator implements
         return false;
     }
 
+    /**
+     * Returns the interpolated texture X coordinate at the current Y position.
+     *
+     * @return the texture X coordinate
+     */
     public double getTX() {
 
         if (onScreenHeight == 0)
@@ -85,6 +101,11 @@ public class PolygonBorderInterpolator implements
         return texturePoint1.x + ((textureWidth * distanceFromY1) / onScreenHeight);
     }
 
+    /**
+     * Returns the interpolated texture Y coordinate at the current Y position.
+     *
+     * @return the texture Y coordinate
+     */
     public double getTY() {
 
         if (onScreenHeight == 0)
@@ -93,6 +114,11 @@ public class PolygonBorderInterpolator implements
         return texturePoint1.y + ((textureHeight * distanceFromY1) / onScreenHeight);
     }
 
+    /**
+     * Returns the interpolated screen X coordinate at the current Y position.
+     *
+     * @return the screen X coordinate
+     */
     public int getX() {
 
         if (onScreenHeight == 0)
@@ -101,10 +127,23 @@ public class PolygonBorderInterpolator implements
         return (int) (onScreenPoint1.x + ((onScreenWidth * distanceFromY1) / onScreenHeight));
     }
 
+    /**
+     * Sets the current Y coordinate for interpolation.
+     *
+     * @param y the current Y coordinate
+     */
     public void setCurrentY(final int y) {
         distanceFromY1 = y - onScreenPoint1.y;
     }
 
+    /**
+     * Sets the screen and texture coordinates for this edge.
+     *
+     * @param onScreenPoint1 the first screen-space endpoint
+     * @param onScreenPoint2 the second screen-space endpoint
+     * @param texturePoint1  the texture coordinate for the first endpoint
+     * @param texturePoint2  the texture coordinate for the second endpoint
+     */
     public void setPoints(final Point2D onScreenPoint1, final Point2D onScreenPoint2,
                           final Point2D texturePoint1, final Point2D texturePoint2) {
 
index 9237fb0..8042b77 100644 (file)
@@ -16,16 +16,20 @@ import java.awt.Color;
 import static eu.svjatoslav.sixth.e3d.geometry.Polygon.pointWithinPolygon;
 
 /**
- * Textured polygon.
- * <p>
+ * A textured triangle renderer with perspective-correct texture mapping.
  *
- * <pre>
- * This is how perspective-correct texture rendering is implemented:
- * If polygon is sufficiently small, it is rendered without perspective correction.
- * Otherwise, it is sliced into smaller polygons.
- * </pre>
+ * <p>This class renders triangles with UV-mapped textures. For large triangles,
+ * the rendering may be sliced into smaller pieces for better perspective correction.</p>
+ *
+ * <p><b>Perspective-correct texture rendering:</b></p>
+ * <ul>
+ *   <li>Small polygons are rendered without perspective correction</li>
+ *   <li>Larger polygons are sliced into smaller pieces for accurate perspective</li>
+ * </ul>
+ *
+ * @see Texture
+ * @see Vertex#textureCoordinate
  */
-
 public class TexturedPolygon extends AbstractCoordinateShape {
 
     private static final ThreadLocal<PolygonBorderInterpolator[]> INTERPOLATORS =
@@ -33,23 +37,33 @@ public class TexturedPolygon extends AbstractCoordinateShape {
                     new PolygonBorderInterpolator(), new PolygonBorderInterpolator(), new PolygonBorderInterpolator()
             });
 
-    public final Texture texture;
-
     /**
-     * If <code>true</code> then polygon borders will be drawn.
-     * It is used for debugging purposes.
+     * The texture to apply to this polygon.
      */
-    public boolean showBorders = false;
+    public final Texture texture;
+
     private boolean backfaceCulling = false;
 
     private double totalTextureDistance = -1;
 
+    /**
+     * Creates a textured triangle with the specified vertices and texture.
+     *
+     * @param p1      the first vertex (must have textureCoordinate set)
+     * @param p2      the second vertex (must have textureCoordinate set)
+     * @param p3      the third vertex (must have textureCoordinate set)
+     * @param texture the texture to apply
+     */
     public TexturedPolygon(Vertex p1, Vertex p2, Vertex p3, final Texture texture) {
 
         super(p1, p2, p3);
         this.texture = texture;
     }
 
+    /**
+     * Computes the total UV distance between all texture coordinate pairs.
+     * Used to determine appropriate mipmap level.
+     */
     private void computeTotalTextureDistance() {
         // compute total texture distance
         totalTextureDistance = coordinates[0].textureCoordinate.getDistanceTo(coordinates[1].textureCoordinate);
@@ -57,6 +71,15 @@ public class TexturedPolygon extends AbstractCoordinateShape {
         totalTextureDistance += coordinates[1].textureCoordinate.getDistanceTo(coordinates[2].textureCoordinate);
     }
 
+    /**
+     * Draws a horizontal scanline between two edge interpolators with texture sampling.
+     *
+     * @param line1         the left edge interpolator
+     * @param line2         the right edge interpolator
+     * @param y             the Y coordinate of the scanline
+     * @param renderBuffer  the rendering context to draw into
+     * @param textureBitmap the texture bitmap to sample from
+     */
     private void drawHorizontalLine(final PolygonBorderInterpolator line1,
                                     final PolygonBorderInterpolator line2, final int y,
                                     final RenderingContext renderBuffer,
@@ -124,6 +147,19 @@ public class TexturedPolygon extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Renders this textured triangle to the screen.
+     *
+     * <p>This method performs:</p>
+     * <ul>
+     *   <li>Backface culling check (if enabled)</li>
+     *   <li>Mouse interaction detection</li>
+     *   <li>Mipmap level selection based on screen coverage</li>
+     *   <li>Scanline rasterization with texture sampling</li>
+     * </ul>
+     *
+     * @param renderBuffer the rendering context containing the pixel buffer
+     */
     @Override
     public void paint(final RenderingContext renderBuffer) {
 
@@ -152,7 +188,7 @@ public class TexturedPolygon extends AbstractCoordinateShape {
                     renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
 
         // Show polygon boundaries (for debugging)
-        if (showBorders)
+        if (renderBuffer.developerTools != null && renderBuffer.developerTools.showPolygonBorders)
             showBorders(renderBuffer);
 
         // find top-most point
@@ -232,14 +268,29 @@ public class TexturedPolygon extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Checks if backface culling is enabled for this polygon.
+     *
+     * @return {@code true} if backface culling is enabled
+     */
     public boolean isBackfaceCullingEnabled() {
         return backfaceCulling;
     }
 
+    /**
+     * Enables or disables backface culling for this polygon.
+     *
+     * @param backfaceCulling {@code true} to enable backface culling
+     */
     public void setBackfaceCulling(final boolean backfaceCulling) {
         this.backfaceCulling = backfaceCulling;
     }
 
+    /**
+     * Draws the polygon border edges in yellow (for debugging).
+     *
+     * @param renderBuffer the rendering context
+     */
     private void showBorders(final RenderingContext renderBuffer) {
 
         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/package-info.java
new file mode 100644 (file)
index 0000000..f893beb
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Textured triangle rendering with perspective-correct UV mapping.
+ *
+ * <p>Textured polygons apply 2D textures to 3D triangles using UV coordinates.
+ * Large polygons may be sliced into smaller pieces for accurate perspective correction.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon} - The textured triangle shape</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.PolygonBorderInterpolator} - Edge interpolation with UVs</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon;
\ No newline at end of file
index e35c451..d3455a0 100644 (file)
@@ -129,6 +129,9 @@ public class AbstractCompositeShape extends AbstractShape {
     /**
      * This method should be overridden by anyone wanting to customize shape
      * before it is rendered.
+     *
+     * @param transformPipe the current transform stack
+     * @param context       the rendering context for the current frame
      */
     public void beforeTransformHook(final TransformStack transformPipe,
                                     final RenderingContext context) {
@@ -242,6 +245,8 @@ public class AbstractCompositeShape extends AbstractShape {
 
     /**
      * Paint solid elements of this composite shape into given color.
+     *
+     * @param color the color to apply to all solid sub-shapes
      */
     public void setColor(final Color color) {
         for (final SubShape subShape : getOriginalSubShapes()) {
@@ -326,6 +331,11 @@ public class AbstractCompositeShape extends AbstractShape {
         }
     }
 
+    /**
+     * Enables or disables backface culling for all SolidPolygon and TexturedPolygon sub-shapes.
+     *
+     * @param backfaceCulling {@code true} to enable backface culling, {@code false} to disable
+     */
     public void setBackfaceCulling(final boolean backfaceCulling) {
         for (final SubShape subShape : getOriginalSubShapes()) {
             final AbstractShape shape = subShape.getShape();
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/package-info.java
new file mode 100644 (file)
index 0000000..877c939
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Base class and utilities for composite shapes.
+ *
+ * <p>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape}
+ * is the foundation for building complex 3D objects by grouping primitives.</p>
+ *
+ * <p>Features:</p>
+ * <ul>
+ *   <li>Position and rotation in 3D space</li>
+ *   <li>Named groups for selective visibility</li>
+ *   <li>Automatic sub-shape management</li>
+ *   <li>Integration with lighting and slicing</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/package-info.java
new file mode 100644 (file)
index 0000000..1cb46b5
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Composite shapes that group multiple primitives into compound 3D objects.
+ *
+ * <p>Composite shapes allow building complex objects from simpler primitives.
+ * They support grouping, visibility toggling, and hierarchical transformations.</p>
+ *
+ * <p>Subpackages:</p>
+ * <ul>
+ *   <li>{@code base} - Base class for all composite shapes</li>
+ *   <li>{@code solid} - Solid objects (cubes, spheres, cylinders)</li>
+ *   <li>{@code wireframe} - Wireframe objects (boxes, grids, spheres)</li>
+ *   <li>{@code textcanvas} - 3D text rendering canvas</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/package-info.java
new file mode 100644 (file)
index 0000000..3a3c501
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Solid composite shapes built from SolidPolygon primitives.
+ *
+ * <p>These shapes render as filled surfaces with optional flat shading.
+ * Useful for creating opaque 3D objects like boxes, spheres, and cylinders.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube} - A solid cube</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox} - A solid box</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere} - A solid sphere</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder} - A solid cylinder</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonPyramid} - A solid pyramid</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid;
\ No newline at end of file
index d310032..a103d7c 100644 (file)
@@ -44,6 +44,14 @@ public class CanvasCharacter extends AbstractCoordinateShape {
      */
     private eu.svjatoslav.sixth.e3d.renderer.raster.Color backgroundColor;
 
+    /**
+     * Creates a canvas character at the specified location with given colors.
+     *
+     * @param centerLocation   the center position in 3D space
+     * @param character        the character to render
+     * @param foregroundColor  the foreground (text) color
+     * @param backgroundColor  the background color
+     */
     public CanvasCharacter(final Point3D centerLocation, final char character,
                            final eu.svjatoslav.sixth.e3d.renderer.raster.Color foregroundColor,
                            final eu.svjatoslav.sixth.e3d.renderer.raster.Color backgroundColor) {
@@ -97,14 +105,18 @@ public class CanvasCharacter extends AbstractCoordinateShape {
     }
 
     /**
-     * Returns color of the background.
+     * Returns the background color of the character.
+     *
+     * @return the background color
      */
     public eu.svjatoslav.sixth.e3d.renderer.raster.Color getBackgroundColor() {
         return backgroundColor;
     }
 
     /**
-     * Sets color of the background.
+     * Sets the background color of the character.
+     *
+     * @param backgroundColor the new background color
      */
     public void setBackgroundColor(
             final eu.svjatoslav.sixth.e3d.renderer.raster.Color backgroundColor) {
@@ -190,6 +202,11 @@ public class CanvasCharacter extends AbstractCoordinateShape {
 
     }
 
+    /**
+     * Sets the character value to render.
+     *
+     * @param value the new character value
+     */
     public void setValue(final char value) {
         this.value = value;
     }
index 14e563b..6a9d08a 100644 (file)
@@ -84,6 +84,9 @@ public class TextCanvas extends TexturedRectangle {
     public static final int FONT_CHAR_HEIGHT_TEXTURE_PIXELS = 32;
 
 
+    /**
+     * The default font used for rendering text on the canvas.
+     */
     public static final Font FONT = CanvasCharacter.getFont((int) (FONT_CHAR_HEIGHT_TEXTURE_PIXELS / 1.066));
     private static final String GROUP_TEXTURE = "texture";
     private static final String GROUP_CHARACTERS = "characters";
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/wireframe/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/wireframe/package-info.java
new file mode 100644 (file)
index 0000000..b96b561
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Wireframe composite shapes built from Line primitives.
+ *
+ * <p>These shapes render as edge-only outlines, useful for visualization,
+ * debugging, and architectural-style rendering.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox} - A wireframe box</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeCube} - A wireframe cube</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeSphere} - A wireframe sphere</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D} - A 2D grid plane</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D} - A 3D grid volume</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/package-info.java
new file mode 100644 (file)
index 0000000..1d88c7e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Renderable shape classes for the rasterization pipeline.
+ *
+ * <p>This package contains the shape hierarchy used for 3D rendering:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape} - Base class for all shapes</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape} - Base for shapes with vertices</li>
+ * </ul>
+ *
+ * <p>Subpackages organize shapes by type:</p>
+ * <ul>
+ *   <li>{@code basic} - Primitive shapes (lines, polygons, billboards)</li>
+ *   <li>{@code composite} - Compound shapes built from primitives (boxes, grids, text)</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.shapes;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/slicer/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/slicer/package-info.java
new file mode 100644 (file)
index 0000000..37c3784
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Geometry slicing for perspective-correct texture rendering.
+ *
+ * <p>Large textured polygons are subdivided into smaller triangles to ensure
+ * accurate perspective correction. This package provides the recursive subdivision
+ * algorithm used by composite shapes.</p>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.slicer.Slicer
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.slicer.BorderLine
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.slicer;
\ No newline at end of file
index 824c415..b650230 100644 (file)
@@ -309,13 +309,27 @@ public class Texture {
     }
 
     /**
-     * A helper class that accumulates color values for a given area of a bitmap
+     * A helper class that accumulates color values for a given area of a bitmap.
      */
     public static class ColorAccumulator {
-        public int r, g, b, a;
-
+        /** Accumulated red component. */
+        public int r;
+        /** Accumulated green component. */
+        public int g;
+        /** Accumulated blue component. */
+        public int b;
+        /** Accumulated alpha component. */
+        public int a;
+
+        /** Number of pixels accumulated. */
         public int pixelCount = 0;
 
+        /**
+         * Creates a new color accumulator with zero values.
+         */
+        public ColorAccumulator() {
+        }
+
         /**
          * Accumulates the color values of the given pixel
          *
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/package-info.java
new file mode 100644 (file)
index 0000000..848a83b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Texture support with mipmap chains for level-of-detail rendering.
+ *
+ * <p>Textures provide 2D image data that can be mapped onto polygons. The mipmap
+ * system automatically generates scaled versions for efficient rendering at
+ * various distances.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture} - Main texture class with mipmap support</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.renderer.raster.texture.TextureBitmap} - Raw pixel data for a single mipmap level</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture
+ */
+
+package eu.svjatoslav.sixth.e3d.renderer.raster.texture;
\ No newline at end of file
diff --git a/src/test/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java b/src/test/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java
new file mode 100644 (file)
index 0000000..d95fa10
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Unit tests for the text editor component.
+ *
+ * <p>Tests for {@link eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextLine}
+ * and related text processing functionality.</p>
+ */
+
+package eu.svjatoslav.sixth.e3d.gui.textEditorComponent;
\ No newline at end of file