From: Svjatoslav Agejenko Date: Fri, 20 Mar 2026 20:51:20 +0000 (+0200) Subject: feat: add debug tools and documentation X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=a413f56fd2b21a7bb288faf9dea1c0948fed6fd2;p=sixth-3d.git feat: add debug tools and documentation - 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 --- diff --git a/AGENTS.md b/AGENTS.md index 3ad2276..4299ec5 100644 --- 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 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 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 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 index 0000000..e9f9e25 Binary files /dev/null and b/doc/Winding order demo.png differ diff --git a/doc/index.org b/doc/index.org index c882fd4..4b7eafc 100644 --- a/doc/index.org +++ b/doc/index.org @@ -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 - - - - - CCW + + + + + + + + + + + + + + CCW - V₁ - V₂ - V₃ + V₁ + V₂ + V₃ FRONT FACE ✓ + - - - CW + + + CW BACK FACE ✗ - (culled — not drawn) + (culled — not drawn) #+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 index 0000000..274851c --- /dev/null +++ b/doc/rendering-loop.org @@ -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 + +#+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 + + + + + + + + + + + Segment 0 (Thread 0) + Segment 1 (Thread 1) + Segment 2 (Thread 2) + Segment 3 (Thread 3) + Segment 4 (Thread 4) + Segment 5 (Thread 5) + Segment 6 (Thread 6) + Segment 7 (Thread 7) + +#+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. diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Box.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Box.java index e461531..abb48b0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Box.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Box.java @@ -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. + * + *

Also known as: 3D rectangle, rectangular box, rectangular parallelepiped, + * cuboid, rhomboid, hexahedron, or rectangular prism.

+ * + *

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.

+ * + *

Example usage:

+ *
{@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
+ * }
+ * + * @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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Circle.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Circle.java index 2ac177d..18dcbee 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Circle.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Circle.java @@ -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() { + } + } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point2D.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point2D.java index a6bb6a1..6e6ac97 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point2D.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point2D.java @@ -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 + *

{@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.

+ * + *

All mutation methods return {@code this} for fluent chaining:

+ *
{@code
+ * Point2D p = new Point2D(10, 20)
+ *     .add(new Point2D(5, 5))
+ *     .invert();
+ * // p is now (-15, -25)
+ * }
+ * + * @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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point3D.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point3D.java index 6f93616..7cfd8e0 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point3D.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Point3D.java @@ -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. *

* See also: Let's remove Quaternions from every 3D Engine * - * @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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Polygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Polygon.java index d5f558e..50f9dc6 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Polygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Polygon.java @@ -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. + * + *

Provides static methods for geometric computations on triangles and other polygons.

+ * + * @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. + * + *

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.

+ * + * @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) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Rectangle.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Rectangle.java index 966d366..23c2079 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Rectangle.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/Rectangle.java @@ -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. + * + *

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.

+ * + * @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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/package-info.java index 9423419..e00e5fa 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/geometry/package-info.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/geometry/package-info.java @@ -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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/Camera.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/Camera.java index 0baf2fa..2f0b842 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/Camera.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/Camera.java @@ -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 index 0000000..59e5f2e --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DebugLogBuffer.java @@ -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. + * + *

Always captures log messages to a fixed-size circular buffer. + * When {@link #passthrough} is enabled, messages are also printed to stdout.

+ * + *

This allows capturing early initialization logs before the user opens + * the {@link DeveloperToolsPanel}. When the panel is opened, the buffered history + * becomes immediately visible.

+ * + * @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. + * + *

If passthrough is enabled, also prints to stdout.

+ * + * @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 getEntries() { + final List 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. + * + *

When enabled, all subsequent log messages will be printed + * to stdout in addition to being captured in the buffer.

+ * + * @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 index 0000000..4be604c --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperTools.java @@ -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. + * + *

Each {@link ViewPanel} has its own DeveloperTools instance, allowing + * different views to have independent debug configurations.

+ * + *

Settings can be toggled at runtime via the {@link DeveloperToolsPanel} + * (opened with F12 key).

+ * + * @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 index 0000000..c209fc1 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperToolsPanel.java @@ -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. + * + *

Opens as a popup dialog when F12 is pressed. Provides:

+ *
    + *
  • Checkboxes to toggle debug settings
  • + *
  • A scrollable log viewer showing captured debug output
  • + *
  • A button to clear the log buffer
  • + *
+ * + * @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 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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/GuiComponent.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/GuiComponent.java index cd3a357..82b979d 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/GuiComponent.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/GuiComponent.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java index 625a897..28097b0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/RenderingContext.java @@ -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 true 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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/TextPointer.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/TextPointer.java index 91cd6e6..4ad8b4d 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/TextPointer.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/TextPointer.java @@ -24,15 +24,29 @@ public class TextPointer implements Comparable { */ 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); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewFrame.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewFrame.java index 6704e21..4a78009 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewFrame.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewFrame.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java index dcb354e..0e35b3e 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewPanel.java @@ -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 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(); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewSpaceTracker.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewSpaceTracker.java index 36cb220..df26d03 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewSpaceTracker.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewSpaceTracker.java @@ -38,6 +38,9 @@ public class ViewSpaceTracker { */ public Vertex down; + /** + * Creates a new view space tracker. + */ public ViewSpaceTracker() { } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewUpdateTimerTask.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewUpdateTimerTask.java index 8594a2c..32810d9 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewUpdateTimerTask.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/ViewUpdateTimerTask.java @@ -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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/Connexion3D.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/Connexion3D.java index 027dbf0..6580906 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/Connexion3D.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/Connexion3D.java @@ -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( diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java index 0a3f4af..5b6db3d 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardHelper.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardHelper.java index fd4d5cf..9b9ce68 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardHelper.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardHelper.java @@ -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. */ diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardInputHandler.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardInputHandler.java index ccdb962..e80bcd9 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardInputHandler.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/KeyboardInputHandler.java @@ -18,22 +18,36 @@ import java.awt.event.KeyEvent; public interface KeyboardInputHandler { /** - * @return true 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 true 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 true 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 true 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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/MouseInteractionController.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/MouseInteractionController.java index 5b6d470..3a9dc3a 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/MouseInteractionController.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/MouseInteractionController.java @@ -12,7 +12,8 @@ public interface MouseInteractionController { /** * Called when mouse is clicked on component. * - * @return true 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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/WorldNavigationUserInputTracker.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/WorldNavigationUserInputTracker.java index f479a88..c8feb38 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/WorldNavigationUserInputTracker.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/WorldNavigationUserInputTracker.java @@ -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) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/package-info.java index dfbbd22..62256b9 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/package-info.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/humaninput/package-info.java @@ -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 index 0000000..ecb4d5f --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/package-info.java @@ -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. + * + *

This package provides the primary integration points for embedding 3D rendering + * into Java applications using Swing/AWT.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.gui.ViewPanel} - The main rendering surface (JPanel)
  • + *
  • {@link eu.svjatoslav.sixth.e3d.gui.ViewFrame} - A JFrame with embedded ViewPanel
  • + *
  • {@link eu.svjatoslav.sixth.e3d.gui.Camera} - Represents the viewer's position and orientation
  • + *
  • {@link eu.svjatoslav.sixth.e3d.gui.DeveloperTools} - Debugging and profiling utilities
  • + *
+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Character.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Character.java index 27113b0..c49ecf7 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Character.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Character.java @@ -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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/LookAndFeel.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/LookAndFeel.java index b792779..5acabc6 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/LookAndFeel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/LookAndFeel.java @@ -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() { + } + } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Page.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Page.java index 47c898f..7be9770 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Page.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/Page.java @@ -17,6 +17,17 @@ public class Page { */ public List 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); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/TextEditComponent.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/TextEditComponent.java index 5ed7a76..d4bfbf4 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/TextEditComponent.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/TextEditComponent.java @@ -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); /** diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java index ff8dff5..cf1eb11 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java @@ -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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/math/Rotation.java b/src/main/java/eu/svjatoslav/sixth/e3d/math/Rotation.java index d921357..b76ce06 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/math/Rotation.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/math/Rotation.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java b/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java index 36a6202..8131ef6 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/math/Transform.java @@ -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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/math/TransformStack.java b/src/main/java/eu/svjatoslav/sixth/e3d/math/TransformStack.java index 1088dc4..5f29a5d 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/math/TransformStack.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/math/TransformStack.java @@ -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. diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/math/Vertex.java b/src/main/java/eu/svjatoslav/sixth/e3d/math/Vertex.java index 4fedbb9..1da9fdd 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/math/Vertex.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/math/Vertex.java @@ -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. + * + *

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.

+ * + *

Coordinate spaces:

+ *
    + *
  • {@link #coordinate} - Original position in local/model space
  • + *
  • {@link #transformedCoordinate} - Position relative to viewer (camera space)
  • + *
  • {@link #onScreenCoordinate} - 2D screen position after perspective projection
  • + *
+ * + *

Example:

+ *
{@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
+ * }
+ * }
+ * + * @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. + * + *

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.

* - * @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) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/IntegerPoint.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/IntegerPoint.java index 7dce375..9ea02e9 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/IntegerPoint.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/IntegerPoint.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/OctreeVolume.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/OctreeVolume.java index a4f4382..dee7a76 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/OctreeVolume.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/OctreeVolume.java @@ -12,52 +12,73 @@ import static java.lang.Integer.max; import static java.lang.Integer.min; /** - *
- * There are 3 cell types:
+ * Sparse voxel octree for 3D volume storage and ray tracing.
  *
- *      UNUSED
+ * 

The octree represents a 3D volume with three cell types:

+ *
    + *
  • UNUSED - Empty cell, not yet allocated
  • + *
  • SOLID - Contains color and illumination data
  • + *
  • CLUSTER - Contains pointers to 8 child cells (for subdivision)
  • + *
* - * SOLID - * contains: - * original color - * visible color, after being illuminated by nearby light sources + *

Cell data is stored in parallel arrays ({@code cell1} through {@code cell8}) + * for memory efficiency. Each array stores different aspects of cell data.

* - * CLUSTER - * contains pointers to 8 sub cells - *
+ * @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) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/CameraView.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/CameraView.java index f0b214b..12d149e 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/CameraView.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/CameraView.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/LightSource.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/LightSource.java index c0939d7..174b130 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/LightSource.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/LightSource.java @@ -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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/RaytracingCamera.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/RaytracingCamera.java index e8237a9..f0f5184 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/RaytracingCamera.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/octree/raytracer/RaytracingCamera.java @@ -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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java index dfa42eb..c594571 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/Color.java @@ -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: *
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/RenderAggregator.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/RenderAggregator.java
index 290d961..4fbd91e 100644
--- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/RenderAggregator.java
+++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/RenderAggregator.java
@@ -24,11 +24,17 @@ import java.util.Comparator;
  * 

This class is used internally by {@link ShapeCollection} during the render pipeline. * You typically do not need to interact with it directly.

* - * @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 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); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.java index 05717fa..07be4a3 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.java @@ -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 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 index 0000000..9ea82bd --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/lighting/package-info.java @@ -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. + * + *

This package implements a simple Lambertian lighting model for shading + * solid polygons based on their surface normals relative to light sources.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager} - Manages lights and calculates shading
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource} - Represents a point light source
  • + *
+ * + * @see eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager + */ + +package eu.svjatoslav.sixth.e3d.renderer.raster.lighting; \ No newline at end of file diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.java index f0d37b1..d5eaf42 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.java @@ -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. diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java index a956e22..821bb32 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/Billboard.java @@ -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. - *

- * This class implements the "billboard" rendering technique where the texture + * A billboard: a texture that always faces the viewer. + * + *

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. - *

- * 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.

+ * + *

Texture mapping algorithm:

+ *
    + *
  1. Calculates screen coverage based on perspective
  2. + *
  3. Clips to viewport boundaries
  4. + *
  5. Maps texture pixels to screen pixels using proportional scaling
  6. + *
+ * + * @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. - *

- * Object rendered visible size on the screen depends on underlying texture size and scale. - *

- * 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. + *

    + *
  • 0 means infinitely small
  • + *
  • 1 is recommended to maintain texture sharpness
  • + *
*/ 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. + * + *

The billboard is rendered as a screen-aligned quad centered on the projected + * position. The size is computed based on distance and scale factor.

* - * @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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java index 59cc454..9822112 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/GlowingPoint.java @@ -17,24 +17,35 @@ import static java.lang.Math.sqrt; /** * A glowing 3D point rendered with a circular gradient texture. - *

- * This class creates and reuses textures for glowing points of the same color. + * + *

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. - *

- * 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.

+ * + *

Texture sharing: Glowing points of the same color share textures + * to reduce memory usage. Textures are garbage collected via WeakHashMap when + * no longer referenced.

+ * + * @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 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. + * + *

Attempts to reuse an existing texture from another glowing point of the + * same color. If none exists, creates a new texture.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java index 91a89d5..83acbb0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/Line.java @@ -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. + * + *

This method handles two rendering modes:

+ *
    + *
  • Thin lines: When the projected width is below threshold, draws single-pixel + * lines with alpha adjusted for sub-pixel appearance.
  • + *
  • Thick lines: Creates four edge interpolators and fills the rectangular area + * scanline by scanline with perspective-correct alpha fading at edges.
  • + *
+ * + * @param buffer the rendering context containing the pixel buffer + */ @Override public void paint(final RenderingContext buffer) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineAppearance.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineAppearance.java index c27aad6..ab17ec2 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineAppearance.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineAppearance.java @@ -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. + * + *

Example usage:

+ *
{@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);
+ * }
*/ 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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineInterpolator.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineInterpolator.java index 57f8dee..8b3a642 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineInterpolator.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/LineInterpolator.java @@ -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 index 0000000..970539a --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/package-info.java @@ -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. + * + *

Lines are rendered with width that adjusts based on distance from the viewer. + * The rendering uses interpolators for smooth edges and proper alpha blending.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line} - The line shape
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance} - Color and width configuration
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineInterpolator} - Scanline edge interpolation
  • + *
+ * + * @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 index 0000000..bdfc35e --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/package-info.java @@ -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. + * + *

Basic shapes are the building blocks of 3D scenes. Each can be rendered + * independently and combined to create more complex objects.

+ * + *

Subpackages:

+ *
    + *
  • {@code line} - 3D line segments with perspective-correct width
  • + *
  • {@code solidpolygon} - Solid-color triangles with flat shading
  • + *
  • {@code texturedpolygon} - Triangles with UV-mapped textures
  • + *
+ * + *

Additional basic shapes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.Billboard} - Textures that always face the camera
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint} - Circular gradient billboards
  • + *
+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/LineInterpolator.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/LineInterpolator.java index 20fb6f4..fa14d14 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/LineInterpolator.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/LineInterpolator.java @@ -49,6 +49,12 @@ public class LineInterpolator implements Comparable { */ 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; diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java index 5bcfc1e..124b679 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.java @@ -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. + * + *

This static method handles:

+ *
    + *
  • Rounding vertices to integer screen coordinates
  • + *
  • Mouse hover detection via point-in-polygon test
  • + *
  • Viewport clipping
  • + *
  • Scanline rasterization with alpha blending
  • + *
+ * + * @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. + * + *

When enabled, polygons facing away from the camera (determined by + * screen-space winding order) are not rendered.

+ * + * @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. + * + *

This method performs:

+ *
    + *
  • Backface culling check (if enabled)
  • + *
  • Flat shading calculation (if lighting is enabled)
  • + *
  • Triangle rasterization using the static drawPolygon method
  • + *
+ * + * @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 index 0000000..79b79d5 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/package-info.java @@ -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. + * + *

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.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon} - The solid triangle shape
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.LineInterpolator} - Edge interpolation for scanlines
  • + *
+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/PolygonBorderInterpolator.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/PolygonBorderInterpolator.java index 5b6c198..ec4caeb 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/PolygonBorderInterpolator.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/PolygonBorderInterpolator.java @@ -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) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java index 9237fb0..8042b77 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedPolygon.java @@ -16,16 +16,20 @@ import java.awt.Color; import static eu.svjatoslav.sixth.e3d.geometry.Polygon.pointWithinPolygon; /** - * Textured polygon. - *

+ * A textured triangle renderer with perspective-correct texture mapping. * - *

- * 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.
- * 
+ *

This class renders triangles with UV-mapped textures. For large triangles, + * the rendering may be sliced into smaller pieces for better perspective correction.

+ * + *

Perspective-correct texture rendering:

+ *
    + *
  • Small polygons are rendered without perspective correction
  • + *
  • Larger polygons are sliced into smaller pieces for accurate perspective
  • + *
+ * + * @see Texture + * @see Vertex#textureCoordinate */ - public class TexturedPolygon extends AbstractCoordinateShape { private static final ThreadLocal INTERPOLATORS = @@ -33,23 +37,33 @@ public class TexturedPolygon extends AbstractCoordinateShape { new PolygonBorderInterpolator(), new PolygonBorderInterpolator(), new PolygonBorderInterpolator() }); - public final Texture texture; - /** - * If true 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. + * + *

This method performs:

+ *
    + *
  • Backface culling check (if enabled)
  • + *
  • Mouse interaction detection
  • + *
  • Mipmap level selection based on screen coverage
  • + *
  • Scanline rasterization with texture sampling
  • + *
+ * + * @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 index 0000000..f893beb --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/package-info.java @@ -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. + * + *

Textured polygons apply 2D textures to 3D triangles using UV coordinates. + * Large polygons may be sliced into smaller pieces for accurate perspective correction.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon} - The textured triangle shape
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.PolygonBorderInterpolator} - Edge interpolation with UVs
  • + *
+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.java index e35c451..d3455a0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.java @@ -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 index 0000000..877c939 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/package-info.java @@ -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. + * + *

{@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape} + * is the foundation for building complex 3D objects by grouping primitives.

+ * + *

Features:

+ *
    + *
  • Position and rotation in 3D space
  • + *
  • Named groups for selective visibility
  • + *
  • Automatic sub-shape management
  • + *
  • Integration with lighting and slicing
  • + *
+ * + * @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 index 0000000..1cb46b5 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/package-info.java @@ -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. + * + *

Composite shapes allow building complex objects from simpler primitives. + * They support grouping, visibility toggling, and hierarchical transformations.

+ * + *

Subpackages:

+ *
    + *
  • {@code base} - Base class for all composite shapes
  • + *
  • {@code solid} - Solid objects (cubes, spheres, cylinders)
  • + *
  • {@code wireframe} - Wireframe objects (boxes, grids, spheres)
  • + *
  • {@code textcanvas} - 3D text rendering canvas
  • + *
+ * + * @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 index 0000000..3a3c501 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/package-info.java @@ -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. + * + *

These shapes render as filled surfaces with optional flat shading. + * Useful for creating opaque 3D objects like boxes, spheres, and cylinders.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube} - A solid cube
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox} - A solid box
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere} - A solid sphere
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder} - A solid cylinder
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonPyramid} - A solid pyramid
  • + *
+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/CanvasCharacter.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/CanvasCharacter.java index d310032..a103d7c 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/CanvasCharacter.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/CanvasCharacter.java @@ -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; } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/TextCanvas.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/TextCanvas.java index 14e563b..6a9d08a 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/TextCanvas.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/textcanvas/TextCanvas.java @@ -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 index 0000000..b96b561 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/wireframe/package-info.java @@ -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. + * + *

These shapes render as edge-only outlines, useful for visualization, + * debugging, and architectural-style rendering.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox} - A wireframe box
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeCube} - A wireframe cube
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeSphere} - A wireframe sphere
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D} - A 2D grid plane
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D} - A 3D grid volume
  • + *
+ * + * @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 index 0000000..1d88c7e --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/package-info.java @@ -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. + * + *

This package contains the shape hierarchy used for 3D rendering:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape} - Base class for all shapes
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape} - Base for shapes with vertices
  • + *
+ * + *

Subpackages organize shapes by type:

+ *
    + *
  • {@code basic} - Primitive shapes (lines, polygons, billboards)
  • + *
  • {@code composite} - Compound shapes built from primitives (boxes, grids, text)
  • + *
+ * + * @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 index 0000000..37c3784 --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/slicer/package-info.java @@ -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. + * + *

Large textured polygons are subdivided into smaller triangles to ensure + * accurate perspective correction. This package provides the recursive subdivision + * algorithm used by composite shapes.

+ * + * @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 diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java index 824c415..b650230 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/Texture.java @@ -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 index 0000000..848a83b --- /dev/null +++ b/src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/package-info.java @@ -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. + * + *

Textures provide 2D image data that can be mapped onto polygons. The mipmap + * system automatically generates scaled versions for efficient rendering at + * various distances.

+ * + *

Key classes:

+ *
    + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture} - Main texture class with mipmap support
  • + *
  • {@link eu.svjatoslav.sixth.e3d.renderer.raster.texture.TextureBitmap} - Raw pixel data for a single mipmap level
  • + *
+ * + * @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 index 0000000..d95fa10 --- /dev/null +++ b/src/test/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java @@ -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. + * + *

Tests for {@link eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextLine} + * and related text processing functionality.

+ */ + +package eu.svjatoslav.sixth.e3d.gui.textEditorComponent; \ No newline at end of file