From a413f56fd2b21a7bb288faf9dea1c0948fed6fd2 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Fri, 20 Mar 2026 22:51:20 +0200 Subject: [PATCH] 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 --- AGENTS.md | 6 +- doc/Developer tools.png | Bin 0 -> 111329 bytes doc/Example.png | Bin 0 -> 67796 bytes doc/Minimal example.png | Bin 0 -> 3601 bytes doc/Winding order demo.png | Bin 0 -> 2331 bytes doc/index.org | 267 ++++++++++++++++-- doc/rendering-loop.org | 223 +++++++++++++++ .../eu/svjatoslav/sixth/e3d/geometry/Box.java | 46 ++- .../svjatoslav/sixth/e3d/geometry/Circle.java | 12 +- .../sixth/e3d/geometry/Point2D.java | 97 +++++-- .../sixth/e3d/geometry/Point3D.java | 52 ++-- .../sixth/e3d/geometry/Polygon.java | 36 ++- .../sixth/e3d/geometry/Rectangle.java | 40 ++- .../sixth/e3d/geometry/package-info.java | 10 +- .../eu/svjatoslav/sixth/e3d/gui/Camera.java | 8 + .../sixth/e3d/gui/DebugLogBuffer.java | 127 +++++++++ .../sixth/e3d/gui/DeveloperTools.java | 39 +++ .../sixth/e3d/gui/DeveloperToolsPanel.java | 168 +++++++++++ .../sixth/e3d/gui/GuiComponent.java | 23 ++ .../sixth/e3d/gui/RenderingContext.java | 15 +- .../svjatoslav/sixth/e3d/gui/TextPointer.java | 14 + .../svjatoslav/sixth/e3d/gui/ViewFrame.java | 5 +- .../svjatoslav/sixth/e3d/gui/ViewPanel.java | 241 ++++++++++++---- .../sixth/e3d/gui/ViewSpaceTracker.java | 3 + .../sixth/e3d/gui/ViewUpdateTimerTask.java | 6 + .../sixth/e3d/gui/humaninput/Connexion3D.java | 12 + .../e3d/gui/humaninput/InputManager.java | 40 +++ .../e3d/gui/humaninput/KeyboardHelper.java | 6 + .../gui/humaninput/KeyboardInputHandler.java | 22 +- .../MouseInteractionController.java | 3 +- .../WorldNavigationUserInputTracker.java | 6 + .../e3d/gui/humaninput/package-info.java | 11 +- .../sixth/e3d/gui/package-info.java | 24 ++ .../gui/textEditorComponent/Character.java | 5 + .../gui/textEditorComponent/LookAndFeel.java | 16 ++ .../e3d/gui/textEditorComponent/Page.java | 28 +- .../TextEditComponent.java | 4 + .../gui/textEditorComponent/package-info.java | 9 +- .../svjatoslav/sixth/e3d/math/Rotation.java | 13 + .../svjatoslav/sixth/e3d/math/Transform.java | 18 ++ .../sixth/e3d/math/TransformStack.java | 6 + .../eu/svjatoslav/sixth/e3d/math/Vertex.java | 69 +++-- .../e3d/renderer/octree/IntegerPoint.java | 23 +- .../e3d/renderer/octree/OctreeVolume.java | 133 +++++++-- .../renderer/octree/raytracer/CameraView.java | 6 + .../octree/raytracer/LightSource.java | 7 + .../octree/raytracer/RaytracingCamera.java | 20 ++ .../sixth/e3d/renderer/raster/Color.java | 2 + .../e3d/renderer/raster/RenderAggregator.java | 12 +- .../e3d/renderer/raster/ShapeCollection.java | 12 +- .../raster/lighting/package-info.java | 21 ++ .../renderer/raster/shapes/AbstractShape.java | 6 + .../raster/shapes/basic/Billboard.java | 61 ++-- .../raster/shapes/basic/GlowingPoint.java | 40 ++- .../raster/shapes/basic/line/Line.java | 55 ++++ .../shapes/basic/line/LineAppearance.java | 47 +++ .../shapes/basic/line/LineInterpolator.java | 33 +++ .../shapes/basic/line/package-info.java | 22 ++ .../raster/shapes/basic/package-info.java | 28 ++ .../basic/solidpolygon/LineInterpolator.java | 6 + .../basic/solidpolygon/SolidPolygon.java | 80 ++++++ .../basic/solidpolygon/package-info.java | 22 ++ .../PolygonBorderInterpolator.java | 39 +++ .../texturedpolygon/TexturedPolygon.java | 79 +++++- .../basic/texturedpolygon/package-info.java | 22 ++ .../base/AbstractCompositeShape.java | 10 + .../shapes/composite/base/package-info.java | 24 ++ .../raster/shapes/composite/package-info.java | 23 ++ .../shapes/composite/solid/package-info.java | 24 ++ .../composite/textcanvas/CanvasCharacter.java | 21 +- .../composite/textcanvas/TextCanvas.java | 3 + .../composite/wireframe/package-info.java | 24 ++ .../renderer/raster/shapes/package-info.java | 25 ++ .../renderer/raster/slicer/package-info.java | 17 ++ .../e3d/renderer/raster/texture/Texture.java | 20 +- .../renderer/raster/texture/package-info.java | 22 ++ .../gui/textEditorComponent/package-info.java | 13 + 77 files changed, 2467 insertions(+), 265 deletions(-) create mode 100644 doc/Developer tools.png create mode 100644 doc/Example.png create mode 100644 doc/Minimal example.png create mode 100644 doc/Winding order demo.png create mode 100644 doc/rendering-loop.org create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/gui/DebugLogBuffer.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperTools.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/gui/DeveloperToolsPanel.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/gui/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/lighting/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/line/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/wireframe/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/slicer/package-info.java create mode 100644 src/main/java/eu/svjatoslav/sixth/e3d/renderer/raster/texture/package-info.java create mode 100644 src/test/java/eu/svjatoslav/sixth/e3d/gui/textEditorComponent/package-info.java 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 0000000000000000000000000000000000000000..e6c3208e89c8aa5e4ae4b11a2ebbb8e383aee48d GIT binary patch literal 111329 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 OtAD)b;_TjJ&$V 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 XdFRH$bPi1^=v)#G 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

4QW>=NnP|;>oGr^ov?# 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

!D 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 KMNUMnLSTZv%wjVD literal 0 HcmV?d00001 diff --git a/doc/Minimal example.png b/doc/Minimal example.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ceac7f660fa855ac897fc516c77aab6bfc71ca GIT binary patch literal 3601 zcmeAS@N?(olHy`uVBq!ia0y~yV0y~H!1$1b87N|%d)JMDfr~9OB%&n3*T*V3KUXg? zB|j-uuOhbqD9^xPV_#8_n4FzjqL7rDo|$K>^nUk#C56lsTcvPQUjyF)=hTc$kE){7 z;3~h6MhJ1(0FE1&_nsU?XD6}dTi#a0!zN?>!XfNYSkzLEl1NlCV?QiN}S zf^&XRs)C80iJpP3Yei<6k&+#kf=y9MnpKdC8`KCO&sHg;q@=(~U%$M(T(8_%FTW^V z-_X+1Qs2Nx-^fT8s6w~6GOr}DLN~8i8Ds>+442g6}$%~k=K6{$H9E}6NhdBs2{ z*eaQu7=jH$$VMW`npi+&K_*#w<|d}6hG(XfWFWL5Bz#iy(m^sPTKtPLQ-NUs@~y3s zp^`o*B=w;|35*$ic+`NxhxTC95|YArG`L2Ci=+@BN%3gv8VxRzLVzU2qp53X1s4~h zahIA0Y|)hin{G-jnx(*IpIm@Xi0glF3($dqVLtZ6z`z!Om z%=mfa+0Ki}&xB_`cgvr7J||9kZ>rtq%G@tgeqMRD@nZ0^;MvFB^k<&Wy(hId#cq3L z_Lm7ipFCT6(fQfr*~{JD&p4kKC$%@(Zg*wom!6+ro-Mp+{Ot1VcDMC2&gb8g+?!;# zzcT$x$IrgA#*5w0l;_No+?%*B{qw08yq|4nhr9XDcz?^rsOnJ7^=A_=3O|!SzRhl? zar!x6m;<9=ltINn_`m;jWwiOT`n&$OcNx{6L`tcoq)6lp1J6P@bIwohnf45yIIQ&*dM?}GT)E@;&q`^Q@se*`&vOoeRfdH$bpooYB5Yrx=b5kTHP%9F= zk}ePg1LDn=B;Bi2%ITeLG-Yh9P|$E}_9E2+SH&N1>fr#JLt>+s-QGXW-OSC+-5)#8 zexG^w`+oL$-`RIhAn9U1-#}jog8ZbCq-_xN66wA6%$ehT|EzdjVPzjGs$m~^{F*Vd-I zwqpNJE0%vAQdKwl=IFrFN9X_k{msCycl@Bxl*HutNc0;>qi%f(xuxYoJwNRhTT6qO zdVL9xsrm01nVK2IOJfFk>;L$Wx9R`hCLNmwXX7RB=+R9kMoG2lG1K zEKp{F3Vb}w6J__C8q9DPGIo1DaXaPWeB~yPWf*V9-XlAs(ku}ik8n84l0#gQcH7WP zYE)m*RzmKWIIz@tjl3ZombrDZvVL(ih-C*43x{`EvWO$n?vtQs=HW`uPPX$dA}95t zuW}VAiyB_V{meM}-FoopVcJ(&3exfMP|sd-&b@X$eZ;`eU5xF-e;qj-PU(#Cbs&A_ zK&a=aOn>gT;!lvW=J5b0OmeTI?P|LI**>=QR^oQ*#d*pgfXOWx@GVJYsNHz>&R`hj zFnem$D)GIU~hxDh0MErEz&fi zDA8`>o&t~5NvZK`nfdC2o#%~uQQK~aC#C1OAl<#iVCxs3rgiKlEyv+3!5XTBjO|DFz@)LXiP;-9^IccS zeB59ERleCixrnd%hDF{@WHi})l}&V7bZ`^48nhO%DGN0h&`hG;Sb7?CsFS3l8(HMV z&hsX{uq{+GB@^G{N!|BZWCGzv2EXcD0p>@wHDZh~Yc0j$TtOK17TGKpgmW2T8h;?v zTt;O?f$_~gG#%dW6=p@os@!VOh{@H+w0?60IHBMN!}SyqYm{7+t$ zGE6_R1bYYPgj?UB6no5N_X>3V^je^s7+UN+K^_rW?-#1J!;Z@`tYIV&OT#&DSXWYt zedg)00>A6@3UFg$D8OkTHwmr$B2_BvxGKZyM>yCToD*RUp%ij+*;s+@I_*vL@FO2r zCU7I22E#}l787mf8F_cQLZ|{$zsOo$WP!Pg1LnCB?bUXeI%GO4vYLx5o4JaEW~}+6 z6XJ4WZt7C4N_PWQ!j2&sv!yU6lF@X|&(+cj?x;JyG{CzB5u9&v&YIjnXUsI*cVxcN|) literal 0 HcmV?d00001 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 -- 2.20.1