Initial commit
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 4 Apr 2026 09:53:56 +0000 (12:53 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 4 Apr 2026 09:53:56 +0000 (12:53 +0300)
60 files changed:
.gitignore [new file with mode: 0644]
AGENTS.md [new file with mode: 0644]
COPYING [new file with mode: 0644]
Tools/Open with IntelliJ IDEA [new file with mode: 0755]
Tools/Update web site [new file with mode: 0755]
doc/.gitignore [new file with mode: 0644]
doc/Screenshots/Benchmark.png [new file with mode: 0644]
doc/Screenshots/Essentials/CSG demo.png [new file with mode: 0644]
doc/Screenshots/Essentials/Coordinate system.png [new file with mode: 0644]
doc/Screenshots/Essentials/Minimal example.png [new file with mode: 0644]
doc/Screenshots/Essentials/Shape gallery.png [new file with mode: 0644]
doc/Screenshots/Essentials/Winding order.png [new file with mode: 0644]
doc/Screenshots/Life.png [new file with mode: 0644]
doc/Screenshots/Mathematical formulas.png [new file with mode: 0644]
doc/Screenshots/Raytracing fractal in voxel polygon hybrid scene.png [new file with mode: 0644]
doc/Screenshots/Sine heightmap and sphere.png [new file with mode: 0644]
doc/Screenshots/Text editors 2.png [new file with mode: 0644]
doc/Screenshots/Text editors.png [new file with mode: 0644]
doc/index.org [new file with mode: 0644]
doc/overview.png [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/StarGridTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/Galaxy.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/package-info.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/package-info.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java [new file with mode: 0644]
src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java [new file with mode: 0644]
src/main/resources/demo.txt [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..31378ad
--- /dev/null
@@ -0,0 +1,9 @@
+/.idea/
+/target/
+/.classpath
+/.project
+/.settings/
+/doc/graphs/
+/doc/apidocs/
+/*.iml
+*.html
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644 (file)
index 0000000..d5e1345
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,130 @@
+# Project Overview
+
+Sixth 3D Demos is a collection of demo applications showcasing the Sixth 3D rendering engine. It features interactive 3D
+visualizations including Conway's Game of Life, galaxy simulations, and various geometric demos.
+
+## Build and Test Commands
+
+### Build the project
+
+```bash
+mvn clean package
+```
+
+### Compile only (without packaging)
+
+```bash
+mvn clean compile
+```
+
+### Run the application
+
+```bash
+java -jar target/sixth-3d-demos.jar
+```
+
+### Generate API documentation
+
+```bash
+mvn javadoc:javadoc
+```
+
+### Run a specific demo directly
+
+```bash
+# Run the launcher
+java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.launcher.Main
+```
+
+### Testing
+
+This project currently has no automated tests. Testing is done manually by running the demo applications.
+
+## Code Organization
+
+```
+src/main/java/eu/svjatoslav/sixth/e3d/examples/
+├── launcher/           - Application launcher and GUI
+│   ├── Main.java       - Main entry point
+│   └── ApplicationListPanel.java
+├── life_demo/          - Conway's Game of Life 3D demo
+│   ├── Main.java
+│   ├── Cell.java
+│   ├── Matrix.java
+│   └── Star.java
+├── terrain_demo/         - Procedural terrain generation demo
+│   ├── DiamondSquareTerrain.java
+│   ├── FractalTree.java
+│   └── TerrainDemo.java
+├── galaxy_demo/        - Galaxy simulation demo
+│   ├── Galaxy.java
+│   └── PointCloudDemo.java
+├── RandomPolygonsDemo.java
+├── OctreeDemo.java
+├── graph_demo/          - 3D graph visualization demos
+│   ├── MathGraphsDemo.java
+│   └── SurfaceGraph3D.java
+├── TextEditorDemo.java
+├── TextEditorDemo2.java
+├── RainingNumbersDemo.java
+├── WindingOrderDemo.java    - Tests winding order & backface culling
+└── package-info.java
+```
+
+## Code Style Guidelines
+
+### Java Version
+
+- Java 21 (source and target compatibility)
+
+### Indentation and Formatting
+
+- Use 4 spaces for indentation (no tabs)
+
+## External Dependencies
+
+| Dependency        | Version      | Description         |
+|-------------------|--------------|---------------------|
+| sixth-3d          | 1.3-SNAPSHOT | 3D rendering engine |
+| svjatoslavcommons | 1.8          | Utility library     |
+
+Both are available from the svjatoslav.eu Maven repository.
+
+## Common Patterns in This Codebase
+
+### Creating a Demo Application
+
+1. Create a class with a `main` method
+2. Create a `ViewFrame` to display the 3D scene
+3. Get the `ShapeCollection` from the view panel
+4. Add shapes to the collection
+5. Optionally set up user input handling
+
+### Extending AbstractCompositeShape
+
+Use this pattern for creating composite 3D objects:
+
+```java
+public class MyShape extends AbstractCompositeShape {
+    public MyShape(Transform transform) {
+        super(transform);
+        // Add child shapes using addShape()
+    }
+}
+```
+
+### Handling User Input
+
+Implement `MouseInteractionController` for mouse events, or extend input tracker classes for keyboard input.
+
+### Polygon Winding Order
+
+When creating triangles with backface culling enabled, use CCW winding in screen space:
+
+- Vertex order: top → lower-left → lower-right (as seen from camera)
+- `signedArea < 0` = front-facing = visible
+- See `WindingOrderDemo.java` for a minimal example
+
+## Documentation
+
+Always make sure that documentation in`doc/index.org` stays up to date.
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..0e259d4
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/Tools/Open with IntelliJ IDEA b/Tools/Open with IntelliJ IDEA
new file mode 100755 (executable)
index 0000000..304bf94
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+# This script launches IntelliJ IDEA with the current project
+# directory. The script is designed to be run by double-clicking it in
+# the GNOME Nautilus file manager.
+
+# First, we change the current working directory to the directory of
+# the script.
+
+# "${0%/*}" gives us the path of the script itself, without the
+# script's filename.
+
+# This command basically tells the system "change the current
+# directory to the directory containing this script".
+
+cd "${0%/*}"
+
+# Then, we move up one directory level.
+# The ".." tells the system to go to the parent directory of the current directory.
+# This is done because we assume that the project directory is one level up from the script.
+cd ..
+
+# Now, we use the 'setsid' command to start a new session and run
+# IntelliJ IDEA in the background. 'setsid' is a UNIX command that
+# runs a program in a new session.
+
+# The command 'idea .' opens IntelliJ IDEA with the current directory
+# as the project directory.  The '&' at the end is a UNIX command that
+# runs the process in the background.  The '> /dev/null' part tells
+# the system to redirect all output (both stdout and stderr, denoted
+# by '&') that would normally go to the terminal to go to /dev/null
+# instead, which is a special file that discards all data written to
+# it.
+
+setsid idea . &>/dev/null &
+
+# The 'disown' command is a shell built-in that removes a shell job
+# from the shell's active list. Therefore, the shell will not send a
+# SIGHUP to this particular job when the shell session is terminated.
+
+# '-h' option specifies that if the shell receives a SIGHUP, it also
+# doesn't send a SIGHUP to the job.
+
+# '$!' is a shell special parameter that expands to the process ID of
+# the most recent background job.
+disown -h $!
+
+
+sleep 2
+
+# Finally, we use the 'exit' command to terminate the shell script.
+# This command tells the system to close the terminal window after
+# IntelliJ IDEA has been opened.
+exit
diff --git a/Tools/Update web site b/Tools/Update web site
new file mode 100755 (executable)
index 0000000..26537ed
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/bash
+
+cd ..
+
+# Function to export org to html using emacs in batch mode
+export_org_to_html() {
+    local org_file=$1
+    local dir=$(dirname "$org_file")
+    local base=$(basename "$org_file" .org)
+    (
+        cd "$dir" || return 1
+        local html_file="${base}.html"
+        if [ -f "$html_file" ]; then
+            rm -f "$html_file"
+        fi
+        echo "Exporting: $org_file → $dir/$html_file"
+        emacs --batch -l ~/.emacs --visit="${base}.org" --funcall=org-html-export-to-html --kill
+        if [ $? -eq 0 ]; then
+            echo "✓ Successfully exported $org_file"
+        else
+            echo "✗ Failed to export $org_file"
+            return 1
+        fi
+    )
+}
+
+export_org_files_to_html() {
+    echo "🔍 Searching for .org files in doc/ ..."
+    echo "======================================="
+
+    mapfile -t ORG_FILES < <(find doc -type f -name "*.org" | sort)
+
+    if [ ${#ORG_FILES[@]} -eq 0 ]; then
+        echo "❌ No .org files found!"
+        return 1
+    fi
+
+    echo "Found ${#ORG_FILES[@]} .org file(s):"
+    printf '%s\n' "${ORG_FILES[@]}"
+    echo "======================================="
+
+    SUCCESS_COUNT=0
+    FAILED_COUNT=0
+
+    for org_file in "${ORG_FILES[@]}"; do
+        export_org_to_html "$org_file"
+        if [ $? -eq 0 ]; then
+            ((SUCCESS_COUNT++))
+        else
+            ((FAILED_COUNT++))
+        fi
+    done
+
+    echo "======================================="
+    echo "📊 SUMMARY:"
+    echo "   ✓ Successful: $SUCCESS_COUNT"
+    echo "   ✗ Failed:     $FAILED_COUNT"
+    echo "   Total:        $((SUCCESS_COUNT + FAILED_COUNT))"
+    echo ""
+}
+
+build_visualization_graphs() {
+    rm -rf doc/graphs/
+    mkdir -p doc/graphs/
+
+    # javainspect -j target/sixth-3d-demos.jar -d doc/graphs/ -n "All classes" -t png -w "eu.svjatoslav.sixth.e3d.examples.*"
+    javainspect -j target/sixth-3d-demos.jar -d doc/graphs/ -n "Game of Life" -t png -w "eu.svjatoslav.sixth.e3d.examples.life_demo.*"
+
+    meviz index -w doc/graphs/ -t "Sixth 3D Demos classes"
+}
+
+# Build project jar file and JavaDocs
+mvn clean package
+
+# Put generated JavaDoc HTML files to documentation directory
+rm -rf doc/apidocs/
+cp -r target/apidocs/ doc/
+
+# Copy runnable jar file for publishing on website.
+cp target/sixth-3d-demos.jar doc/
+
+
+# Publish Emacs org-mode files into HTML format
+export_org_files_to_html
+
+# Generate nice looking code visualization diagrams
+build_visualization_graphs
+
+
+## Upload assembled documentation to server
+echo "📤 Uploading to server..."
+rsync -avz --delete -e 'ssh -p 10006' doc/ \
+      n0@www3.svjatoslav.eu:/mnt/big/projects/sixth-3d-demos/
+
+if [ $? -eq 0 ]; then
+    echo "✓ Upload completed successfully!"
+else
+    echo "✗ Upload failed!"
+fi
+
+echo ""
+echo "Press ENTER to close this window."
+read
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..0cc8fd2
--- /dev/null
@@ -0,0 +1,2 @@
+apidocs/
+sixth-3d-demos.jar
diff --git a/doc/Screenshots/Benchmark.png b/doc/Screenshots/Benchmark.png
new file mode 100644 (file)
index 0000000..5029be0
Binary files /dev/null and b/doc/Screenshots/Benchmark.png differ
diff --git a/doc/Screenshots/Essentials/CSG demo.png b/doc/Screenshots/Essentials/CSG demo.png
new file mode 100644 (file)
index 0000000..5602255
Binary files /dev/null and b/doc/Screenshots/Essentials/CSG demo.png differ
diff --git a/doc/Screenshots/Essentials/Coordinate system.png b/doc/Screenshots/Essentials/Coordinate system.png
new file mode 100644 (file)
index 0000000..7896688
Binary files /dev/null and b/doc/Screenshots/Essentials/Coordinate system.png differ
diff --git a/doc/Screenshots/Essentials/Minimal example.png b/doc/Screenshots/Essentials/Minimal example.png
new file mode 100644 (file)
index 0000000..6bee1cb
Binary files /dev/null and b/doc/Screenshots/Essentials/Minimal example.png differ
diff --git a/doc/Screenshots/Essentials/Shape gallery.png b/doc/Screenshots/Essentials/Shape gallery.png
new file mode 100644 (file)
index 0000000..76a4fe5
Binary files /dev/null and b/doc/Screenshots/Essentials/Shape gallery.png differ
diff --git a/doc/Screenshots/Essentials/Winding order.png b/doc/Screenshots/Essentials/Winding order.png
new file mode 100644 (file)
index 0000000..f939231
Binary files /dev/null and b/doc/Screenshots/Essentials/Winding order.png differ
diff --git a/doc/Screenshots/Life.png b/doc/Screenshots/Life.png
new file mode 100644 (file)
index 0000000..565f5ab
Binary files /dev/null and b/doc/Screenshots/Life.png differ
diff --git a/doc/Screenshots/Mathematical formulas.png b/doc/Screenshots/Mathematical formulas.png
new file mode 100644 (file)
index 0000000..fab8469
Binary files /dev/null and b/doc/Screenshots/Mathematical formulas.png differ
diff --git a/doc/Screenshots/Raytracing fractal in voxel polygon hybrid scene.png b/doc/Screenshots/Raytracing fractal in voxel polygon hybrid scene.png
new file mode 100644 (file)
index 0000000..7094240
Binary files /dev/null and b/doc/Screenshots/Raytracing fractal in voxel polygon hybrid scene.png differ
diff --git a/doc/Screenshots/Sine heightmap and sphere.png b/doc/Screenshots/Sine heightmap and sphere.png
new file mode 100644 (file)
index 0000000..0d3e92b
Binary files /dev/null and b/doc/Screenshots/Sine heightmap and sphere.png differ
diff --git a/doc/Screenshots/Text editors 2.png b/doc/Screenshots/Text editors 2.png
new file mode 100644 (file)
index 0000000..cb1733f
Binary files /dev/null and b/doc/Screenshots/Text editors 2.png differ
diff --git a/doc/Screenshots/Text editors.png b/doc/Screenshots/Text editors.png
new file mode 100644 (file)
index 0000000..8567b8b
Binary files /dev/null and b/doc/Screenshots/Text editors.png differ
diff --git a/doc/index.org b/doc/index.org
new file mode 100644 (file)
index 0000000..734d9cb
--- /dev/null
@@ -0,0 +1,539 @@
+#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme
+#+TITLE: Sixth 3D engine demos
+#+LANGUAGE: en
+#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}
+#+LATEX_HEADER: \usepackage{parskip}
+#+LATEX_HEADER: \usepackage[none]{hyphenat}
+
+#+OPTIONS: H:20 num:20
+#+OPTIONS: author:nil
+
+#+begin_export html
+<style>
+  .flex-center {
+    display: flex;            /* activate flexbox */
+    justify-content: center;  /* horizontally center anything inside   */
+  }
+
+  .flex-center video {
+    width: min(90%, 1000px); /* whichever is smaller wins */
+    height: auto;            /* preserve aspect ratio */
+  }
+
+  .responsive-img {
+    width: min(100%, 1000px);
+    height: auto;
+  }
+</style>
+#+end_export
+
+
+* Overview
+:PROPERTIES:
+:CUSTOM_ID: overview
+:ID:       4430626f-21f4-42d7-89b1-7097a0b99916
+:END:
+
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:overview.png]]
+
+The goal of this project is to show off capabilities and API usage of
+[[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D]] engine.
+
+All [[id:5f88b493-6ab3-4659-8280-803f75dbd5e0][example scenes in this repository]] render at interactive
+framerates.
+
+You can download pre-compiled runnable JAR file with embedded demos
+from here: [[file:sixth-3d-demos.jar]]
+
+It requires Java 21 or newer to run.
+
+To start the demo application, use command:
+: java -jar sixth-3d-demos.jar
+
+* Essentials
+
+*Resources to help you understand the Sixth 3D library:*
+- 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)
+
+** Minimal example
+:PROPERTIES:
+:CUSTOM_ID: minimal-example
+:ID:       1a2b3c4d-5e6f-7890-abcd-ef1234567890
+:END:
+
+This is the "Hello World" of *Sixth 3D* - the minimal boilerplate
+needed to render any 3D geometry.
+
+: eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample
+
+*Brief tutorial:*
+
+Here we guide you through creating your first 3D scene with Sixth 3D
+engine.
+
+Prerequisites:
+- Java 21 or later installed
+- Maven 3.x
+- Basic Java knowledge
+
+*** Add Dependency to Your Project
+:PROPERTIES:
+:CUSTOM_ID: add-dependency-to-your-project
+:ID:       3fffc32e-ae66-40b7-ad7d-fab6093c778b
+:END:
+
+Add *Sixth 3D* to your pom.xml:
+
+#+BEGIN_SRC xml
+<dependencies>
+    <dependency>
+        <groupId>eu.svjatoslav</groupId>
+        <artifactId>sixth-3d</artifactId>
+        <version>1.4-SNAPSHOT</version>
+    </dependency>
+</dependencies>
+
+<repositories>
+    <repository>
+        <id>svjatoslav.eu</id>
+        <name>Svjatoslav repository</name>
+        <url>https://www3.svjatoslav.eu/maven/</url>
+    </repository>
+</repositories>
+#+END_SRC
+
+
+*** Create Your First 3D Scene
+:PROPERTIES:
+:CUSTOM_ID: create-your-first-3d-scene
+:ID:       564fa596-9b2b-418a-9df9-baa46f0d0a66
+:END:
+
+
+#+BEGIN_SRC java
+  import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+  import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+  import eu.svjatoslav.sixth.e3d.math.Transform;
+  import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+  import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+  import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox;
+
+  public class MyFirstScene {
+      public static void main(String[] args) {
+          // Create the application window
+          ViewFrame viewFrame = new ViewFrame("My First Scene");
+
+          // Get the collection where you add 3D shapes
+          ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
+
+          // Create a red box centered at origin
+          final Transform boxTransform = new Transform();
+          SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                  point(-50, -50, -50),
+                  point(50, 50, 50),
+                  Color.RED
+          );
+          box.setTransform(boxTransform);
+          shapes.addShape(box);
+
+          // Position your camera
+          viewFrame.getViewPanel().getCamera().getTransform()
+                  .setTranslation(point(0, -100, -300));
+
+          // Trigger initial render
+          viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+      }
+  }
+#+END_SRC
+
+Compile and run *MyFirstScene* class. A new window should open that will
+display 3D scene with red box.
+
+
+You should see this:
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Screenshots/Essentials/Minimal example.png]]
+
+
+*Navigating the scene:*
+
+| Input              | Action                      |
+|--------------------+-----------------------------|
+| Arrow Up           | Move forward                |
+| Arrow Down         | Move backward               |
+| Arrow Left         | Move left (strafe)          |
+| Arrow Right        | Move right (strafe)         |
+| Mouse drag         | Look around (rotate camera) |
+| Mouse scroll wheel | Move up / down              |
+
+Movement uses physics-based acceleration for smooth, natural
+motion. The faster you're moving, the more acceleration builds up,
+creating an intuitive flying experience.
+
+Press *F12* in any demo to open the [[https://www3.svjatoslav.eu/projects/sixth-3d/#outline-container-developer-tools][Developer Tools panel]]. This
+debugging interface provides real-time insight into the rendering
+pipeline with diagnostic toggles.
+
+** Coordinate system
+:PROPERTIES:
+:CUSTOM_ID: coordinate-system
+:ID:       2b3c4d5e-6f7a-8901-bcde-f12345678901
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.essentials.CoordinateSystemDemo
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Screenshots/Essentials/Coordinate system.png]]
+
+Visual reference for the Sixth 3D coordinate system using three colored
+arrows originating from (0,0,0):
+
+| Axis | Color | Direction                          |
+|------+-------+------------------------------------|
+| X    | Red   | Points RIGHT (positive X)          |
+| Y    | Green | Points DOWN (positive Y)           |
+| Z    | Blue  | Points AWAY from viewer (positive Z) |
+
+A wireframe grid on the Y=0 plane provides spatial context and scale. Text
+labels (X, Y, Z) appear at each arrow tip.
+
+This demo is essential for understanding Sixth 3D's coordinate system where:
+- Positive Y goes down (screen coordinates convention)
+- Positive Z goes into the screen (away from camera)
+- Positive X goes to the right
+
+See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#coordinate-system][Coordinate System]] section in the Sixth 3D documentation
+for a detailed explanation of the axis conventions and coordinate math.
+
+** Winding order
+:PROPERTIES:
+:CUSTOM_ID: winding-order
+:ID:       3c4d5e6f-7a8b-9012-cdef-123456789012
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.essentials.WindingOrderDemo
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Screenshots/Essentials/Winding order.png]]
+
+This demo demonstrates polygon winding order and backface culling - fundamental
+concepts for efficient 3D rendering.
+
+The green triangle uses counter-clockwise (CCW) vertex ordering:
+1. Upper-center vertex
+2. Lower-left vertex
+3. Lower-right vertex
+
+With backface culling enabled:
+- *CCW winding* (negative signed area) = front-facing = visible
+- *CW winding* (positive signed area) = back-facing = culled (not rendered)
+
+This optimization prevents rendering polygons facing away from the camera,
+improving performance by roughly 50% for closed meshes. Navigate around the
+scene and observe that the triangle becomes invisible when viewed from behind,
+demonstrating the culling effect.
+
+See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#winding-order-backface-culling][Winding Order & Backface Culling]] section
+in the Sixth 3D documentation for a detailed explanation with diagrams.
+
+** Shape gallery
+:PROPERTIES:
+:CUSTOM_ID: shape-gallery
+:ID:       4d5e6f7a-8b9c-0123-def0-234567890123
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Screenshots/Essentials/Shape gallery.png]]
+
+Comprehensive showcase of all primitive 3D shapes available in Sixth 3D,
+organized in a 7×2 grid:
+
+| Column | Shape Type | Description                    |
+|--------+------------+--------------------------------|
+| 1      | Arrow      | 3D arrow with conical tip      |
+| 2      | Cone       | Circular base cone             |
+| 3      | Cube       | Regular hexahedron             |
+| 4      | Cylinder   | Tube with circular caps        |
+| 5      | Pyramid    | Square base pyramid            |
+| 6      | Box        | Rectangular parallelepiped     |
+| 7      | Sphere     | Geodesic sphere approximation  |
+
+*Top row:* Wireframe versions - edges only, no surfaces
+*Bottom row:* Solid polygon versions - filled surfaces with lighting
+
+Additional features demonstrated:
+- Dynamic lighting from 10 orbiting colored light sources (visible as small
+  glowing dots)
+- Per-shape color assignment for visual distinction
+- Grid floor showing spatial layout and alignment
+- Text labels identifying each shape type
+- Shading enabled on solid shapes to show lighting effects
+
+See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#mesh][Mesh]] section in the Sixth 3D documentation
+for an explanation of 3D mesh fundamentals and how shapes are constructed.
+
+** CSG demo
+:PROPERTIES:
+:CUSTOM_ID: csg-demo
+:ID:       5e6f7a8b-9c0d-1234-ef01-345678901234
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo
+
+#+attr_html: :class responsive-img
+#+attr_latex: :width 1000px
+[[file:Screenshots/Essentials/CSG demo.png]]
+
+Constructive Solid Geometry (CSG) boolean operations on 3D meshes. Three
+operations displayed left to right:
+
+1. *Subtract (Cube - Sphere)* - A red cube with a spherical cavity carved out
+   where a blue sphere intersects it. Shows the cube's interior surfaces.
+
+2. *Union (Cube + Sphere)* - A merged shape combining both volumes. Red
+   surfaces from the cube, blue surfaces from the sphere, seamlessly joined.
+
+3. *Intersect (Cube ∩ Sphere)* - Only the volume shared by both shapes
+   remains. Shows which parts of space were occupied by both the cube and
+   sphere simultaneously.
+
+Color scheme:
+- *Red* = geometry from the first operand (cube)
+- *Blue* = geometry from the second operand (sphere)
+
+Each operation includes a text description panel below the shape. The demo
+uses:
+- SolidPolygonCube (red)
+- SolidPolygonSphere (blue)
+- CSGSolid.subtract(), union(), intersect() methods
+- toMesh() conversion for rendering
+- Backface culling and shading enabled
+
+CSG is powerful for procedural modeling, allowing complex shapes to be built
+from simple primitives through boolean combinations.
+
+* Advanced examples
+:PROPERTIES:
+:CUSTOM_ID: example-scenes
+:ID:       5f88b493-6ab3-4659-8280-803f75dbd5e0
+:END:
+
+
+** Conway's Game of Life
+:PROPERTIES:
+:CUSTOM_ID: conways-game-of-life
+:ID:       08914390-742b-4c78-88bf-602ab9640082
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.life_demo.Main
+
+The Game of Life, also known simply as Life, is a cellular automaton
+devised by the British mathematician John Horton Conway in 1970.
+
++ https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
+  + Game rules:
+    + 2 cell states: alive / dead
+    + Each cell sees 8 neighboring cells.
+    + If alive cell neighbors count is 2 or 3, then cell survives,
+      otherwise it dies.
+    + Dead cell becomes alive if neighbors count is exactly 3.
+
+[[file:Screenshots/Life.png]]
+
+This demo projects the 2D game grid onto three-dimensional space. The extra
+dimension (height) visualizes history (previous iterations) using glowing dots
+suspended in space.
+
+Usage:
+| key                            | result                               |
+|--------------------------------+--------------------------------------|
+| mouse click on the cell (cell) | toggles cell state                   |
+| <space>                        | next iteration                       |
+| ENTER                          | next iteration with the history     |
+| "c"                            | clear the matrix                     |
+
+** Text editors
+:PROPERTIES:
+:CUSTOM_ID: text-editors
+:ID:       39250157-db8e-4861-a21b-8568912bd160
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.TextEditorDemo
+
+[[file:Screenshots/Text editors.png]]
+
+Initial test for creating user interfaces in 3D, demonstrating:
++ window focus handling
++ picking objects using the mouse
++ redirecting keyboard input to the focused window
+
+Window focus acts like a stack.
+
+When a window is clicked with the mouse, the previously focused window (if
+any) is pushed to the focus stack and the new window receives focus. A red
+frame appears around the window to indicate this.
+
+When the ESC key is pressed, focus is returned to the previous window (if any).
+
+When any window is focused, all keyboard input is redirected to that window,
+including cursor keys. To navigate around the world again, the window must be
+unfocused first by pressing the ESC key.
+
+
++ TODO:
+  + Improve focus handling:
+    + Perhaps add shortcut to navigate world without exiting entire
+      stack of focus.
+    + Possibility to retain and reuse recently focused elements.
+    + Store user location in the world and view direction with the
+      focused window. So that when returning focus to far away object,
+      user is redirected also to proper location in the world.
+  + Possibility to store recently visited locations in the world and
+    return to them.
+
+** Text editors demo gallery
+:PROPERTIES:
+:CUSTOM_ID: text-editors-demo-gallery
+:ID:       a7b8c9d0-e1f2-3456-0123-567890123456
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2
+
+*Quite a lot of text editors can be rendered:*
+
+[[file:Screenshots/Text editors 2.png]]
+
+See also this [[https://hackers-1995.vercel.app/][similar-looking web-based demo]]!
+
+** Math graphs demo
+:PROPERTIES:
+:CUSTOM_ID: mathematical-formulas
+:ID:       b1c2d3e4-f5a6-7890-bcde-f12345678901
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo
+
+[[file:Screenshots/Mathematical formulas.png]]
+
++ TODO: instead of projecting 2D visualizations onto 3D space,
+  visualize some formula using all 3 dimensions available.
+
+** Sine heightmap and sphere
+:PROPERTIES:
+:CUSTOM_ID: sine-heightmaps-and-sphere
+:ID:       b2c3d4e5-f6a7-8901-bcde-f12345678901
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.SineHeightmap
+
+[[file:Screenshots/Sine heightmap and sphere.png]]
+
+Simple test scene. Easy to implement and looks nice.
+
+** Raytracing through voxels
+:PROPERTIES:
+:CUSTOM_ID: raytracing-through-voxels
+:ID:       c3d4e5f6-a7b8-9012-cdef-123456789012
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.OctreeDemo
+
+[[file:Screenshots/Raytracing fractal in voxel polygon hybrid scene.png]]
+
+Test scene that is generated simultaneously using:
++ conventional polygons
+  + for realtime navigation, and
++ voxels
+  + for on-demand raytracing
+
+Instead of storing voxels in a naive [X × Y × Z] array, a dynamically
+partitioned [[https://en.wikipedia.org/wiki/Octree][octree]] compresses the data. Press the "r" key anywhere in
+the scene to raytrace the current view through the compressed voxel
+data structure.
+
+* Graphics Benchmark
+:PROPERTIES:
+:CUSTOM_ID: graphics-benchmark
+:ID:       e5f6a7b8-c9d0-1234-ef01-345678901234
+:END:
+
+: eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark
+
+An automated graphics benchmark that measures the engine's rendering
+performance across different rendering modes.
+
+[[file:Screenshots/Benchmark.png]]
+
+The benchmark cycles through scenes that utilize different rendering
+primitives (textured polygons, billboards, solid polygons, etc.) to measure
+their relative performance.
+
+The camera follows a deterministic orbital path around the scene,
+ensuring reproducible results across runs.
+
+Upon completion, the benchmark outputs a report suitable for preservation
+and later comparisons.
+
+Example benchmark report:
+#+begin_example
+================================================================================
+                         GRAPHICS BENCHMARK RESULTS
+================================================================================
+Date:        2026-03-16 19:17:51
+Resolution:  1920x1080
+Cubes:       4096 (16x16x16 grid)
+Duration:    30 seconds per test
+
+--------------------------------------------------------------------------------
+SYSTEM INFORMATION
+--------------------------------------------------------------------------------
+CPU Name:    AMD Ryzen AI 9 HX 370 w/ Radeon 890M
+Arch:        amd64
+CPU cores:   24
+
+--------------------------------------------------------------------------------
+Test                         Avg FPS
+--------------------------------------------------------------------------------
+Solid Cubes                  49.65
+Lit Solid Cubes              41.40
+Textured Cubes               32.80
+Wireframe Cubes              42.84
+Star Grid                    318.97
+================================================================================
+#+end_example
+
+* Source code
+:PROPERTIES:
+:CUSTOM_ID: source-code
+:ID:       d4e5f6a7-b8c9-0123-def0-234567890123
+:END:
+
+*This program is free software, released under the Creative Commons Zero
+(CC0) license.*
+
+*Program author:*
+- Svjatoslav Agejenko
+- Homepage: https://svjatoslav.eu
+- Email: mailto://svjatoslav@svjatoslav.eu
+- See also: [[https://www.svjatoslav.eu/projects/][Other software projects hosted at svjatoslav.eu]]
+
+*Getting the source code:*
+- [[https://www2.svjatoslav.eu/gitweb/?p=sixth-3d-demos.git;a=snapshot;h=HEAD;sf=tgz][Download latest snapshot in TAR GZ format]]
+- [[https://www2.svjatoslav.eu/gitweb/?p=sixth-3d-demos.git;a=summary][Browse Git repository online]]
+- Clone Git repository using command:
+  : git clone https://www2.svjatoslav.eu/git/sixth-3d-demos.git
+
+*Understanding the Sixth 3D demos source code:*
+- Read the online [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/apidocs/][JavaDoc]].
+- Study the underlying [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D]] engine.
diff --git a/doc/overview.png b/doc/overview.png
new file mode 100644 (file)
index 0000000..ba977ab
Binary files /dev/null and b/doc/overview.png differ
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..4baf761
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,173 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>eu.svjatoslav</groupId>
+    <artifactId>sixth-3d-demos</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>Sixth 3D demos</name>
+    <description>3D engine demos</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.source>21</maven.compiler.source>
+        <maven.compiler.target>21</maven.compiler.target>
+    </properties>
+
+    <organization>
+        <name>svjatoslav.eu</name>
+        <url>https://svjatoslav.eu</url>
+    </organization>
+
+    <dependencies>
+        <dependency>
+            <groupId>eu.svjatoslav</groupId>
+            <artifactId>sixth-3d</artifactId>
+            <version>1.4-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>eu.svjatoslav</groupId>
+            <artifactId>svjatoslavcommons</artifactId>
+            <version>1.8</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <source>21</source>
+                    <target>21</target>
+                    <optimize>true</optimize>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.2.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>3.6.0</version>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <!-- workaround for https://bugs.openjdk.java.net/browse/JDK-8212233 -->
+                    <javaApiLinks>
+                        <property>
+                            <name>foo</name>
+                            <value>bar</value>
+                        </property>
+                    </javaApiLinks>
+                    <!-- Workaround for https://stackoverflow.com/questions/49472783/maven-is-unable-to-find-javadoc-command -->
+                    <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>2.4.3</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>eu.svjatoslav.sixth.e3d.examples.launcher.Main</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <finalName>sixth-3d-demos</finalName>
+                    <appendAssemblyId>false</appendAssemblyId>
+                </configuration>
+
+                <executions>
+                    <execution>
+                        <id>package-jar-with-dependencies</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptorRefs>
+                                <descriptorRef>jar-with-dependencies</descriptorRef>
+                            </descriptorRefs>
+                            <archive>
+                                <manifest>
+                                    <mainClass>eu.svjatoslav.sixth.e3d.examples.launcher.Main</mainClass>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+
+        <extensions>
+            <extension>
+                <groupId>org.apache.maven.wagon</groupId>
+                <artifactId>wagon-ssh-external</artifactId>
+                <version>2.6</version>
+            </extension>
+        </extensions>
+    </build>
+
+
+    <distributionManagement>
+        <snapshotRepository>
+            <id>svjatoslav.eu</id>
+            <name>svjatoslav.eu</name>
+            <url>scpexe://svjatoslav.eu:10006/srv/maven</url>
+        </snapshotRepository>
+        <repository>
+            <id>svjatoslav.eu</id>
+            <name>svjatoslav.eu</name>
+            <url>scpexe://svjatoslav.eu:10006/srv/maven</url>
+        </repository>
+    </distributionManagement>
+
+    <repositories>
+        <repository>
+            <id>svjatoslav.eu</id>
+            <name>Svjatoslav repository</name>
+            <url>https://www3.svjatoslav.eu/maven/</url>
+        </repository>
+    </repositories>
+
+    <scm>
+        <connection>scm:git:ssh://n0@svjatoslav.eu/home/git/repositories/sixth-3d-demos.git</connection>
+        <developerConnection>scm:git:ssh://n0@svjatoslav.eu/home/git/repositories/sixth-3d-demos.git
+        </developerConnection>
+    </scm>
+
+
+</project>
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java
new file mode 100644 (file)
index 0000000..3b45854
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCone;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Demo showcasing the SolidPolygonArrow shape with various colors, sizes,
+ * orientations, and transparency levels.
+ *
+ * <p>This demo displays arrows pointing in different directions to demonstrate
+ * the flexibility of the arrow shape. A 3D grid provides spatial reference.</p>
+ */
+public class ArrowDemo {
+
+    /**
+     * Entry point for the arrow demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("Arrow Demo");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view the scene
+        viewPanel.getCamera().getTransform().setTranslation(point(0, -200, -600));
+
+        // Add a 3D grid for spatial reference
+        final LineAppearance gridAppearance = new LineAppearance(1, hex("64646450"));
+        final Grid3D grid = new Grid3D(
+                point(-300, -200, -300),
+                point(300, 200, 300),
+                100,
+                gridAppearance
+        );
+        shapes.addShape(grid);
+
+        // Create arrows pointing in different directions with different properties
+
+        // Red arrow pointing up (negative Y direction)
+        final SolidPolygonArrow redArrow = new SolidPolygonArrow(
+                point(0, 150, 0),
+                point(0, -150, 0),
+                8, Color.RED
+        );
+        shapes.addShape(redArrow);
+
+        // Green arrow pointing along positive X
+        final SolidPolygonArrow greenArrow = new SolidPolygonArrow(
+                point(-200, 0, 0),
+                point(0, 0, 0),
+                6, Color.GREEN
+        );
+        shapes.addShape(greenArrow);
+
+        // Blue arrow pointing along positive Z
+        final SolidPolygonArrow blueArrow = new SolidPolygonArrow(
+                point(0, 0, -200),
+                point(0, 0, 0),
+                6, Color.BLUE
+        );
+        shapes.addShape(blueArrow);
+
+        // Yellow arrow pointing diagonally
+        final SolidPolygonArrow yellowArrow = new SolidPolygonArrow(
+                point(100, 100, 100),
+                point(300, -100, 300),
+                10, Color.YELLOW
+        );
+        shapes.addShape(yellowArrow);
+
+        // Semi-transparent cyan arrow (50% opacity)
+        final SolidPolygonArrow transparentCyanArrow = new SolidPolygonArrow(
+                point(-150, 50, -100),
+                point(-50, -100, 100),
+                8, hex("00FFFF80")
+        );
+        shapes.addShape(transparentCyanArrow);
+
+        // Semi-transparent magenta arrow (25% opacity)
+        final SolidPolygonArrow transparentMagentaArrow = new SolidPolygonArrow(
+                point(50, 200, 50),
+                point(50, 0, 50),
+                12, hex("FF00FF40")
+        );
+        shapes.addShape(transparentMagentaArrow);
+
+        // Small white arrows forming a circle pattern
+        for (int i = 0; i < 8; i++) {
+            final double angle = i * Math.PI / 4;
+            final double radius = 250;
+            final double startX = radius * Math.cos(angle);
+            final double startZ = radius * Math.sin(angle);
+            final double endX = (radius + 80) * Math.cos(angle);
+            final double endZ = (radius + 80) * Math.sin(angle);
+
+            final SolidPolygonArrow circleArrow = new SolidPolygonArrow(
+                    point(startX, -80, startZ),
+                    point(endX, -80, endZ),
+                    4, Color.WHITE
+            );
+            shapes.addShape(circleArrow);
+        }
+
+        // A standalone cone to demonstrate SolidPolygonCone
+        final SolidPolygonCone standaloneCone = new SolidPolygonCone(
+                point(-300, 0, 0),
+                40,
+                80,
+                16,
+                hex("FF8000FF")
+        );
+        shapes.addShape(standaloneCone);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java
new file mode 100755 (executable)
index 0000000..d4e1c9c
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.octree.IntegerPoint;
+import eu.svjatoslav.sixth.e3d.renderer.octree.OctreeVolume;
+import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RayTracer;
+import eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.RaytracingCamera;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
+
+import java.awt.event.KeyEvent;
+import java.util.Vector;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Demo showing volumetric octree rendering with raytracing capability.
+ * Creates a 3D scene with various geometric shapes stored in an octree data structure.
+ * Press 'r' to render the current view using software raytracing.
+ */
+public class OctreeDemo extends WorldNavigationUserInputTracker {
+
+    /**
+     * Scale factor for rendering octree voxels in the scene.
+     */
+    private static final double magnification = 5;
+    private final LineAppearance gridAppearance = new LineAppearance(40, hex("FF00003C"));
+    private final Vector<eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.LightSource> lights = new Vector<>();
+    private OctreeVolume octreeVolume;
+    private ShapeCollection shapeCollection;
+    private ViewPanel viewPanel;
+
+    /**
+     * Creates a new OctreeDemo instance.
+     */
+    public OctreeDemo() {
+    }
+
+    /**
+     * Entry point for the octree demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        new OctreeDemo().init();
+    }
+
+    /**
+     * Adds a light source to both the raytracer and rasterizer lighting systems.
+     *
+     * @param location   position in octree space (will be scaled by magnification for display)
+     * @param color      color of the light
+     * @param brightness intensity of the light
+     */
+    private void addLightToBothSystems(final Point3D location, final Color color,
+                                       final float brightness) {
+        shapeCollection.addShape(new LightSourceMarker(new Point3D(location)
+                .multiply(magnification), color));
+
+        lights.add(new eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.LightSource(
+                location, color, brightness));
+
+        viewPanel.getLightingManager().addLight(new LightSource(
+                new Point3D(location).multiply(magnification), color, brightness * 50));
+    }
+
+    /**
+     * Creates a colorful spiral pattern of voxels in the octree.
+     */
+    private void dotSpiral() {
+        for (double i = 0; i < 20; i = i + .1) {
+
+            double w = 1;
+            double h = 1;
+
+            final double x = Math.sin(i) * 20f * h;
+            final double y = Math.cos(i) * 20f * w;
+            final double c1 = (Math.cos(i * 3f) * 100) + 127;
+            final double c2 = (Math.cos(i * 5.3332f) * 100f) + 127;
+            final double c3 = (Math.cos(i * 1.342f) * 100f) + 127;
+
+            putPixel((int) x, (int) y, (int) (i * 4f), new Color((int) c1,
+                    (int) c2, (int) c3, 255));
+        }
+    }
+
+    /**
+     * Recursively creates a fractal pattern of rectangles.
+     *
+     * @param x    center X coordinate
+     * @param y    center Y coordinate
+     * @param z    center Z coordinate
+     * @param size size of the current rectangle
+     * @param step recursion depth counter
+     */
+    private void fractal(final int x, final int y, final int z, final int size,
+                         final int step) {
+        final double c1 = (Math.cos(y / 7f) * 100f) + 127;
+        final double c2 = (Math.cos(x / 10f) * 100f) + 127;
+        final double c3 = (Math.cos(z / 12f) * 100f) + 127;
+
+        putRect(
+                new IntegerPoint(x - size, y - size, z - size),
+                new IntegerPoint(x + size, y + size, z + size),
+                new Color((int) c1, (int) c2, (int) c3, 200));
+
+        if (size > 1) {
+            fractal(x, y - (size * 3), z, size / 2, step + 1);
+            fractal(x + (size * 3), y, z, size / 2, step + 1);
+            fractal(x, y, z + (size * 3), size / 2, step + 1);
+        }
+    }
+
+    /**
+     * Initializes the demo scene with grid, lights, shapes, and fractal pattern.
+     */
+    private void init() {
+
+        final ViewFrame viewFrame = new ViewFrame("Volumetric octree");
+        viewPanel = viewFrame.getViewPanel();
+
+        viewPanel.getCamera().getTransform().set(104.13, -65.04, -370.53, 0.12, 0.14, 0);
+
+        viewPanel.getLightingManager().setAmbientLight(new Color(50, 50, 50));
+
+        octreeVolume = new OctreeVolume();
+
+        shapeCollection = viewPanel.getRootShapeCollection();
+
+        shapeCollection.addShape(new Grid3D(
+                point(-10000, -10000, -10000), point(10000, 10000,
+                10000), 4000, gridAppearance));
+
+        addLightToBothSystems(point(20, -450, 240), new Color(255, 255, 255), 100);
+        addLightToBothSystems(point(-150, -116, 141), new Color(255, 0, 0), 10);
+
+        dotSpiral();
+
+        // arbitrary rectangles
+        putRect(new IntegerPoint(-10, -10, -10),
+                new IntegerPoint(10, 10, -20),
+                hex("C8FFC864"));
+
+        putRect(new IntegerPoint(-3, 0, -30),
+                new IntegerPoint(12, 3, 300),
+                hex("FFC8C864"));
+
+        putRect(new IntegerPoint(-20, 20, -20),
+                new IntegerPoint(20, 80, 20),
+                hex("FFC8FF64"));
+
+        tiledFloor();
+
+        fractal(-50, 20, 100, 32, 1);
+
+        final TextCanvas message = new TextCanvas(new Transform(point(
+                -10, 20, -180)), "Press \"r\" to raytrace current view",
+                Color.WHITE, Color.PURPLE);
+        shapeCollection.addShape(message);
+
+        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Handles keyboard input for triggering raytracing.
+     *
+     * @param event     the key event
+     * @param viewPanel the view panel
+     * @return true if the event was consumed
+     */
+    @Override
+    public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
+
+        if ('r' == event.getKeyChar()) {
+            raytrace();
+            return true;
+        }
+        return super.keyPressed(event, viewPanel);
+    }
+
+    /**
+     * Places a single voxel at the specified position with the given color.
+     *
+     * @param x     X coordinate in octree space
+     * @param y     Y coordinate in octree space
+     * @param z     Z coordinate in octree space
+     * @param color the color of the voxel
+     */
+    private void putPixel(final int x, final int y, final int z,
+                          final Color color) {
+        shapeCollection.addShape(new GlowingPoint(point(x, y, z)
+                .multiply(magnification), 3 * magnification, color));
+        octreeVolume.putCell(x, y, z, color);
+
+    }
+
+    /**
+     * Fills a rectangular region in the octree with the specified color.
+     *
+     * @param p1    first corner of the rectangle
+     * @param p2    opposite corner of the rectangle
+     * @param color the color to fill
+     */
+    private void putRect(IntegerPoint p1, IntegerPoint p2, final Color color) {
+        final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                new Point3D(p1).multiply(magnification),
+                new Point3D(p2).multiply(magnification), color);
+        box.setShadingEnabled(true);
+        shapeCollection.addShape(box);
+        octreeVolume.fillRectangle(p1, p2, color);
+    }
+
+    /**
+     * Starts a raytracing render of the current view in a background thread.
+     */
+    private void raytrace() {
+        // create and add camera object to scene
+        final RaytracingCamera raytracingCamera = new RaytracingCamera(viewPanel.getCamera(), magnification);
+        shapeCollection.addShape(raytracingCamera);
+
+        // initialize and start Raytracer in a separate thread
+        final RayTracer rayTracer = new RayTracer(raytracingCamera.getTexture(),
+                octreeVolume, lights, raytracingCamera, viewPanel);
+        final Thread thread = new Thread(rayTracer);
+        thread.start();
+    }
+
+    /**
+     * Creates a tiled floor pattern using small rectangular tiles.
+     */
+    private void tiledFloor() {
+        final int step = 40;
+        final int size = step - 15;
+        Color color = hex("FFFFFF64");
+        for (int x = -200; x < 200; x += step)
+            for (int z = -200; z < 200; z += step)
+                putRect(
+                        new IntegerPoint(x, 100, z),
+                        new IntegerPoint(x + size, 110, z + size),
+                        color);
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java
new file mode 100644 (file)
index 0000000..bc6f6b8
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+*/
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
+
+import java.util.Collection;
+import java.util.Random;
+
+/**
+ * Demo showing numbers falling through 3D space like rain.
+ * Creates 1000 randomly positioned text canvases displaying digits 0-9,
+ * which continuously fall downward and wrap around to create a "Matrix" style effect.
+ */
+public class RainingNumbersDemo implements FrameListener {
+
+    /**
+     * Creates a new RainingNumbersDemo instance.
+     */
+    public RainingNumbersDemo() {
+    }
+
+    /** Number of falling numbers in the scene. */
+    private static final int NUMBERS_COUNT = 1000;
+    /** Size of the cubic area containing the numbers. */
+    private final static int AREA = 600;
+    /** Half of the area for positioning calculations. */
+    private final static int AREA_HALF = AREA / 2;
+
+    /**
+     * Entry point for the raining numbers demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        new RainingNumbersDemo().run();
+    }
+
+    /**
+     * Animates all text canvases to fall downward each frame.
+     * Numbers that fall below the area wrap around to the top.
+     * @param viewPanel the view panel
+     * @param millisecondsSinceLastFrame time elapsed since last frame
+     * @return true to continue animation
+     */
+    @Override
+    public boolean onFrame(final ViewPanel viewPanel,
+                                final int millisecondsSinceLastFrame) {
+
+        final Collection<AbstractShape> shapes = viewPanel
+                .getRootShapeCollection().getShapes();
+
+        final double translateAmount = millisecondsSinceLastFrame / 50d;
+
+        shapes.stream().filter(shape -> shape instanceof TextCanvas).forEach(shape -> {
+            final TextCanvas block = (TextCanvas) shape;
+            final Point3D location = block.getLocation();
+            location.translateY(translateAmount);
+
+            if (location.y > AREA_HALF)
+                location.y -= AREA;
+        });
+
+        return true;
+    }
+
+    /** Initializes the demo by creating the view frame and adding falling numbers. */
+    private void run() {
+        final ViewFrame viewFrame = new ViewFrame("Raining Numbers");
+
+        viewFrame.getViewPanel().getCamera().getTransform().set(-129.75, 228.27, -220.69, -0.55, 0.54, 0);
+
+        final ShapeCollection geometryCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        Random random = new Random();
+
+        for (int i = 0; i < NUMBERS_COUNT; i++) {
+            final Point3D location = point((Math.random() * AREA)
+                    - AREA_HALF, (Math.random() * AREA) - AREA_HALF,
+                    (Math.random() * AREA) - AREA_HALF);
+
+            final Color color = new Color(Math.random(), Math.random(),
+                    Math.random(), Math.random());
+
+            final TextCanvas textCanvas = new TextCanvas(
+                    new Transform(location), String.valueOf(random.nextInt(10)), color,
+                    Color.TRANSPARENT);
+
+            geometryCollection.addShape(textCanvas);
+        }
+
+        viewFrame.getViewPanel().addFrameListener(this);
+        
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java
new file mode 100755 (executable)
index 0000000..64433da
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeSphere;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Demo showing a sine heightmap surface with a central wireframe sphere.
+ * Two wobbly surfaces are positioned above and below the sphere.
+ */
+public class SineHeightmap {
+
+    /**
+     * Frequency of the wave pattern in the wobbly surfaces.
+     */
+    private static final double WAVE_FREQUENCY = 50d;
+    /**
+     * Amplitude of the wave pattern in the wobbly surfaces.
+     */
+    private static final double WAVE_AMPLITUDE = 50d;
+    /**
+     * Color for the square plates in the wobbly surfaces.
+     */
+    private static final Color SQUARE_PLATE_COLOR = hex("88F7");
+    /**
+     * Creates a new GraphDemo instance.
+     */
+    public SineHeightmap() {
+    }
+
+    /**
+     * Creates a single square plate at the specified position.
+     *
+     * @param shapeCollection the collection to add the plate to
+     * @param y               the Y coordinate (elevation)
+     * @param x               the X coordinate
+     * @param z               the Z coordinate
+     */
+    private static void makeSquarePlate(final ShapeCollection shapeCollection,
+                                        final double y, final double x, final double z) {
+        final Point3D p1 = point(x, y, z);
+        final Point3D p2 = point(x + 20, y, z);
+        final Point3D p3 = point(x, y, z + 20);
+        final Point3D p4 = point(x + 20, y, z + 20);
+        final SolidPolygon quad = SolidPolygon.quad(p1, p2, p4, p3, SQUARE_PLATE_COLOR);
+        shapeCollection.addShape(quad);
+    }
+
+    /**
+     * Creates a wobbly surface composed of square plates arranged in a wave pattern.
+     *
+     * @param shapeCollection  the collection to add plates to
+     * @param surfaceElevation the base Y elevation of the surface
+     */
+    private static void addWobblySurface(final ShapeCollection shapeCollection,
+                                         final double surfaceElevation) {
+        for (double x = -500; x < 500; x += 20)
+            for (double z = -500; z < 500; z += 20) {
+
+                final double distanceFromCenter = Math.sqrt((x * x) + (z * z));
+
+                double plateElevation = Math.sin(distanceFromCenter / WAVE_FREQUENCY) * WAVE_AMPLITUDE;
+
+                makeSquarePlate(shapeCollection, plateElevation + surfaceElevation, x,
+                        z);
+            }
+    }
+
+    /**
+     * Entry point for the graph demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+
+        final ViewFrame viewFrame = new ViewFrame("Sine Heightmap");
+        final ShapeCollection geometryCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        viewFrame.getViewPanel().getCamera().getTransform().setTranslation(point(0, 0, -500));
+
+        addSphere(geometryCollection);
+        addWobblySurface(geometryCollection, 200);
+        addWobblySurface(geometryCollection, -200);
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Adds a wireframe sphere at the center of the scene.
+     *
+     * @param geometryCollection the collection to add the sphere to
+     */
+    private static void addSphere(ShapeCollection geometryCollection) {
+        geometryCollection.addShape(new WireframeSphere(origin(),
+                100,
+                new LineAppearance(
+                        4,
+                        hex("FF00001E"))
+        ));
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java
new file mode 100644 (file)
index 0000000..79fa5ad
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Rectangle;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.LookAndFeel;
+import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextEditComponent;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Demo showing a grid of 3D text editor components.
+ * Creates a 5x5 grid of text editors floating in 3D space with a decorative grid
+ * below them. This demonstrates the basic text editing capabilities of the Sixth 3D engine.
+ */
+public class TextEditorDemo {
+
+    /**
+     * Creates a new TextEditorDemo instance.
+     */
+    public TextEditorDemo() {
+    }
+
+    /**
+     * Entry point for the text editor demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+
+        final ViewFrame viewFrame = new ViewFrame("Text Editors");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+
+        viewPanel.getCamera().getTransform().set(500, -300, -800, 0.6, -0.5, 0);
+
+        final ShapeCollection shapeCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        addGrid(shapeCollection);
+
+        addTextEditors(viewPanel, shapeCollection);
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Adds a decorative grid below the text editors.
+     *
+     * @param shapeCollection the collection to add the grid to
+     */
+    private static void addGrid(ShapeCollection shapeCollection) {
+        final Transform transform = Transform.fromAngles(0, 100, 0, 0, Math.PI / 2, 0);
+
+        final Rectangle rectangle = new Rectangle(2000);
+        final LineAppearance appearance = new LineAppearance(10, hex("00b3ad"));
+
+        shapeCollection.addShape(new Grid2D(transform, rectangle, 10, 10, appearance));
+    }
+
+    /**
+     * Creates a grid of text editor components arranged in 3D space.
+     *
+     * @param viewPanel       the view panel for the text editors
+     * @param shapeCollection the collection to add editors to
+     */
+    private static void addTextEditors(ViewPanel viewPanel, ShapeCollection shapeCollection) {
+        final double m = 1.5;
+        for (double z = -500 * m; z <= (500 * m); z += 250 * m)
+            for (double x = -500 * m; x <= (500 * m); x += 250 * m) {
+
+                final TextEditComponent textEditor = new TextEditComponent(
+                        new Transform(point(x, 0, z)), viewPanel,
+                        new Point2D(200, 120), new LookAndFeel());
+
+                shapeCollection.addShape(textEditor);
+            }
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java
new file mode 100644 (file)
index 0000000..ed38515
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Rectangle;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.LookAndFeel;
+import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextEditComponent;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.util.stream.Collectors;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+import static java.lang.Math.PI;
+
+/**
+ * "Text Editor City" demo showing a 3D city of text editor components.
+ * Creates a grid of buildings where each building has four text editor panels
+ * facing different directions. This demonstrates the 3D text editing capabilities
+ * of the Sixth 3D engine in a large-scale scene.
+ */
+public class TextEditorDemo2 {
+
+    /**
+     * Creates a new TextEditorDemo2 instance.
+     */
+    public TextEditorDemo2() {
+    }
+
+    /**
+     * Entry point for the text editor city demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        try {
+            new TextEditorDemo2().build();
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Adds a grid to the scene for visual reference.
+     *
+     * @param shapeCollection the collection to add the grid to
+     */
+    private static void addGrid(ShapeCollection shapeCollection) {
+        final Transform transform = fromAngles(0, 100, 0, 0, PI / 2, 0);
+
+        final Rectangle rectangle = new Rectangle(10000);
+        final LineAppearance appearance = new LineAppearance(10, hex("00b3ad"));
+
+        shapeCollection.addShape(new Grid2D(transform, rectangle, 50, 50, appearance));
+    }
+
+    /**
+     * Reads a resource file as a string.
+     *
+     * @param fileName the name of the resource file
+     * @return the file contents as a string, or null if not found
+     * @throws IOException if an I/O error occurs
+     */
+    static String getResourceFileAsString(String fileName) throws IOException {
+        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
+        try (InputStream is = classLoader.getResourceAsStream(fileName)) {
+            if (is == null) return null;
+            try (InputStreamReader isr = new InputStreamReader(is);
+                 BufferedReader reader = new BufferedReader(isr)) {
+                return reader.lines().collect(Collectors.joining(System.lineSeparator()));
+            }
+        }
+    }
+
+    /**
+     * Builds and displays the 3D city scene with text editors.
+     *
+     * @throws URISyntaxException if resource URI is malformed
+     * @throws IOException        if demo text file cannot be read
+     */
+    public void build() throws URISyntaxException, IOException {
+        final ViewFrame viewFrame = new ViewFrame("Text Editors City");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+
+        viewPanel.getCamera().getTransform().set(500, -300, -800, 0.6, -0.5, 0);
+
+        final ShapeCollection shapeCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        addGrid(shapeCollection);
+
+        addCity(viewPanel, shapeCollection);
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates a grid of buildings in the city.
+     *
+     * @param viewPanel       the view panel for the text editors
+     * @param shapeCollection the collection to add buildings to
+     * @throws URISyntaxException if resource URI is malformed
+     * @throws IOException        if demo text file cannot be read
+     */
+    private void addCity(ViewPanel viewPanel, ShapeCollection shapeCollection) throws URISyntaxException, IOException {
+        int citySize = 4000;
+        for (int z = -citySize; z < citySize; z += 1000) {
+            for (int x = -citySize; x < citySize; x += 1000) {
+                addBuilding(viewPanel, shapeCollection, x, z);
+            }
+        }
+    }
+
+    /**
+     * Adds a single building with four text editor panels facing different directions.
+     *
+     * @param viewPanel       the view panel for the text editors
+     * @param shapeCollection the collection to add the building to
+     * @param x               the X coordinate of the building center
+     * @param z               the Z coordinate of the building center
+     * @throws URISyntaxException if resource URI is malformed
+     * @throws IOException        if demo text file cannot be read
+     */
+    private void addBuilding(ViewPanel viewPanel, ShapeCollection shapeCollection, double x, double z) throws URISyntaxException, IOException {
+        addTextEditor(viewPanel, shapeCollection, new Transform(point(x, -390, z - 200)));
+
+        addTextEditor(viewPanel, shapeCollection, fromAngles(x, -390, z + 200, PI, 0, 0));
+
+        addTextEditor(viewPanel, shapeCollection, fromAngles(x - 200, -390, z, PI / 2, 0, 0));
+
+        addTextEditor(viewPanel, shapeCollection, fromAngles(x + 200, -390, z, PI / 2 * 3f, 0, 0));
+    }
+
+    /**
+     * Adds a single text editor component at the specified transform.
+     *
+     * @param viewPanel       the view panel for the text editor
+     * @param shapeCollection the collection to add the editor to
+     * @param transform       the position and orientation of the editor
+     * @throws IOException if demo text file cannot be read
+     */
+    private void addTextEditor(ViewPanel viewPanel, ShapeCollection shapeCollection, Transform transform) throws IOException {
+        LookAndFeel lookAndFeel = getLookAndFeel();
+
+        final TextEditComponent textEditor = new TextEditComponent(
+                transform,
+                viewPanel,
+                new Point2D(400, 1000),
+                lookAndFeel
+        );
+
+        String text = getResourceFileAsString("demo.txt");
+
+        textEditor.setText(text);
+        textEditor.goToLine((int) (Math.random() * 200f));
+        shapeCollection.addShape(textEditor);
+    }
+
+    /**
+     * Creates the look and feel for text editors with a dark theme.
+     *
+     * @return a LookAndFeel configured with dark blue background and light text
+     */
+    private LookAndFeel getLookAndFeel() {
+        LookAndFeel lookAndFeel = new LookAndFeel();
+        lookAndFeel.background = hex("141E3296");
+        lookAndFeel.tabStopBackground = lookAndFeel.background;
+        lookAndFeel.foreground = hex("9696FFFA");
+        return lookAndFeel;
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/BenchmarkTest.java
new file mode 100644 (file)
index 0000000..14ab974
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+
+/**
+ * Interface for a single benchmark test.
+ * Implementations define how to set up and tear down the test scene.
+ */
+public interface BenchmarkTest {
+
+    /**
+     * Returns the display name for this benchmark test.
+     * @return the test name
+     */
+    String getName();
+
+    /**
+     * Sets up the test scene by adding shapes to the collection.
+     * @param shapes the shape collection to populate
+     */
+    void setup(ShapeCollection shapes);
+
+    /**
+     * Tears down the test scene by removing all shapes added during setup.
+     * @param shapes the shape collection to clean up
+     */
+    void teardown(ShapeCollection shapes);
+
+    /**
+     * Called after setup to provide the view panel for tests that need animation.
+     * Default implementation does nothing.
+     * @param viewPanel the view panel
+     */
+    default void setViewPanel(ViewPanel viewPanel) {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/GraphicsBenchmark.java
new file mode 100644 (file)
index 0000000..f44bd6e
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.Camera;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.KeyboardInputHandler;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.KeyEvent;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Automated graphics benchmark that tests the engine's rendering performance.
+ * Runs multiple tests sequentially, each for a fixed duration, and displays
+ * results in a dialog with copy-to-clipboard functionality.
+ * 
+ * <p>The benchmark creates a 16x16x16 grid of cubes (4096 total) with the camera
+ * following a deterministic orbital path. Each test runs for 30 seconds by default.</p>
+ * 
+ * <p>Press <b>Space</b> to skip to the next test immediately.</p>
+ * 
+ * <p>Available tests:</p>
+ * <ul>
+ *   <li>{@link SolidCubesTest} - Semi-transparent solid polygon rendering</li>
+ *   <li>{@link LitSolidCubesTest} - Opaque solid polygons with dynamic lighting</li>
+ *   <li>{@link TexturedCubesTest} - Textured polygon rendering</li>
+ *   <li>{@link WireframeCubesTest} - Line rendering</li>
+ *   <li>{@link StarGridTest} - Billboard (glowing point) rendering</li>
+ * </ul>
+ */
+public class GraphicsBenchmark implements FrameListener, KeyboardInputHandler {
+
+    private static final int WINDOW_WIDTH = 1920;
+    private static final int WINDOW_HEIGHT = 1080;
+    private static final int GRID_SIZE = 16;
+    private static final double ORBIT_DISTANCE = 1200;
+    private static final double ORBIT_SPEED = 0.0003;
+    private static final double WOBBLE_AMPLITUDE = 800;
+    private static final int TEST_DURATION_MS = 30000;
+
+    private ViewFrame viewFrame;
+    private ViewPanel viewPanel;
+    private ShapeCollection shapes;
+    private Camera camera;
+
+    private double orbitAngle = 0;
+    private long testStartTime;
+    private long frameCount;
+    private BenchmarkTest currentTest;
+    private boolean testFinished = false;
+    private boolean benchmarkFinished = false;
+    private boolean pendingTestTransition = false;
+    private final List<TestResult> results = new ArrayList<>();
+    private final List<BenchmarkTest> tests = new ArrayList<>();
+
+    /**
+     * Entry point for the graphics benchmark.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(() -> {
+            if (showIntroDialog()) {
+                new GraphicsBenchmark();
+            }
+        });
+    }
+
+    private static boolean showIntroDialog() {
+        String message =
+                "<html><div style='width:400px; font-family: sans-serif;'>" +
+                "<h2>Graphics Benchmark</h2>" +
+                "<p>This will run a series of performance tests.</p>" +
+                "<ul>" +
+                "<li>Each test runs for 30 seconds</li>" +
+                "<li>Press <b>SPACE</b> to skip any test<br>" +
+                "<span style='color: gray;'>(skipping reduces measurement precision)</span></li>" +
+                "<li>A summary will be shown at the end</li>" +
+                "</ul>" +
+                "</div></html>";
+
+        int choice = JOptionPane.showConfirmDialog(
+                null,
+                message,
+                "Graphics Benchmark",
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.INFORMATION_MESSAGE
+        );
+
+        return choice == JOptionPane.OK_OPTION;
+    }
+
+    /**
+     * Constructs and runs the graphics benchmark.
+     */
+    public GraphicsBenchmark() {
+        registerTests();
+        initializeWindow();
+    }
+
+    private void initializeWindow() {
+        viewFrame = new ViewFrame("Graphics Benchmark", WINDOW_WIDTH, WINDOW_HEIGHT);
+        viewPanel = viewFrame.getViewPanel();
+        viewPanel.setFrameRate(0);
+        shapes = viewPanel.getRootShapeCollection();
+        
+        camera = viewPanel.getCamera();
+        camera.getTransform().set(0, -500, -800, 0, -0.5, 0);
+        
+        viewPanel.addFrameListener(this);
+        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    private void registerTests() {
+        tests.add(new SolidCubesTest());
+        tests.add(new LitSolidCubesTest());
+        tests.add(new TexturedCubesTest());
+        tests.add(new WireframeCubesTest());
+        tests.add(new StarGridTest());
+    }
+
+    private void startNextTest() {
+        int nextIndex = results.size();
+        if (nextIndex >= tests.size()) {
+            scheduleBenchmarkFinish();
+            return;
+        }
+
+        currentTest = tests.get(nextIndex);
+        testFinished = false;
+        orbitAngle = 0;
+        frameCount = 0;
+        testStartTime = System.currentTimeMillis();
+
+        currentTest.setup(shapes);
+        currentTest.setViewPanel(viewPanel);
+    }
+
+    private void finishCurrentTest() {
+        if (currentTest == null || testFinished) {
+            return;
+        }
+
+        testFinished = true;
+        long elapsed = System.currentTimeMillis() - testStartTime;
+        double durationSeconds = elapsed / 1000.0;
+        results.add(new TestResult(currentTest.getName(), frameCount, durationSeconds));
+
+        pendingTestTransition = true;
+    }
+
+    private void performTestTransition() {
+        if (currentTest != null) {
+            currentTest.teardown(shapes);
+        }
+        startNextTest();
+    }
+
+    private void scheduleBenchmarkFinish() {
+        benchmarkFinished = true;
+        SwingUtilities.invokeLater(this::finishBenchmark);
+    }
+
+    private void finishBenchmark() {
+        currentTest = null;
+        viewPanel.getKeyboardFocusStack().popFocusOwner();
+        viewPanel.removeFrameListener(this);
+        viewPanel.stop();
+        viewFrame.dispose();
+
+        showResultsDialog();
+    }
+
+    private String formatResults() {
+        StringBuilder sb = new StringBuilder();
+        String separator = "================================================================================";
+        String thinSeparator = "--------------------------------------------------------------------------------";
+
+        Runtime runtime = Runtime.getRuntime();
+
+        sb.append(separator).append("\n");
+        sb.append("                         GRAPHICS BENCHMARK RESULTS\n");
+        sb.append(separator).append("\n");
+        sb.append("Date:        ").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("\n");
+        sb.append("Resolution:  ").append(WINDOW_WIDTH).append("x").append(WINDOW_HEIGHT).append("\n");
+        sb.append("Cubes:       ").append(GRID_SIZE * GRID_SIZE * GRID_SIZE)
+                .append(" (").append(GRID_SIZE).append("x").append(GRID_SIZE).append("x").append(GRID_SIZE).append(" grid)\n");
+        sb.append("Duration:    ").append(TEST_DURATION_MS / 1000).append(" seconds per test\n");
+        sb.append("\n");
+        sb.append(thinSeparator).append("\n");
+        sb.append("SYSTEM INFORMATION\n");
+        sb.append(thinSeparator).append("\n");
+        sb.append("CPU Name:    ").append(getCpuName()).append("\n");
+        sb.append("Arch:        ").append(System.getProperty("os.arch")).append("\n");
+        sb.append("CPU cores:   ").append(runtime.availableProcessors()).append("\n");
+        sb.append("\n");
+        sb.append(thinSeparator).append("\n");
+        sb.append(String.format("%-28s %s%n", "Test", "Avg FPS"));
+        sb.append(thinSeparator).append("\n");
+
+        for (TestResult result : results) {
+            sb.append(String.format("%-28s %.2f%n", result.testName, result.averageFps));
+        }
+
+        sb.append(separator).append("\n");
+
+        return sb.toString();
+    }
+
+    private void showResultsDialog() {
+        String resultsText = formatResults();
+
+        JTextArea textArea = new JTextArea(resultsText);
+        textArea.setEditable(false);
+        textArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
+        textArea.setCaretPosition(0);
+
+        JScrollPane scrollPane = new JScrollPane(textArea);
+        scrollPane.setPreferredSize(new Dimension(600, 400));
+
+        JButton copyButton = new JButton("Copy to Clipboard");
+        JButton closeButton = new JButton("Close");
+
+        copyButton.addActionListener(e -> {
+            StringSelection selection = new StringSelection(resultsText);
+            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);
+            copyButton.setText("Copied!");
+            copyButton.setEnabled(false);
+        });
+
+        closeButton.addActionListener(e -> {
+            Window window = SwingUtilities.getWindowAncestor(closeButton);
+            if (window != null) {
+                window.dispose();
+            }
+        });
+
+        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+        buttonPanel.add(copyButton);
+        buttonPanel.add(closeButton);
+
+        JPanel panel = new JPanel(new BorderLayout(0, 10));
+        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+        panel.add(scrollPane, BorderLayout.CENTER);
+        panel.add(buttonPanel, BorderLayout.SOUTH);
+
+        JOptionPane.showMessageDialog(
+                null,
+                panel,
+                "Benchmark Results",
+                JOptionPane.PLAIN_MESSAGE
+        );
+    }
+
+    private String getCpuName() {
+        try {
+            java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader("/proc/cpuinfo"));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.startsWith("model name")) {
+                    reader.close();
+                    return line.substring(line.indexOf(':') + 1).trim();
+                }
+            }
+            reader.close();
+        } catch (Exception ignored) {
+        }
+        return System.getProperty("java.vm.name", "Unknown");
+    }
+
+    @Override
+    public boolean onFrame(ViewPanel viewPanel, int millisecondsSinceLastFrame) {
+        if (pendingTestTransition) {
+            pendingTestTransition = false;
+            performTestTransition();
+        }
+
+        if (currentTest == null && !benchmarkFinished && results.isEmpty()) {
+            startNextTest();
+        }
+
+        if (benchmarkFinished) {
+            return false;
+        }
+
+        if (currentTest == null) {
+            return false;
+        }
+
+        orbitAngle += ORBIT_SPEED * millisecondsSinceLastFrame;
+
+        double x = Math.sin(orbitAngle) * ORBIT_DISTANCE;
+        double z = Math.cos(orbitAngle) * ORBIT_DISTANCE;
+        double y = Math.sin(orbitAngle * 1.8934) * WOBBLE_AMPLITUDE;
+
+        camera.getTransform().setTranslation(new Point3D(x, y, z));
+        camera.lookAt(new Point3D(0, 0, 0));
+
+        frameCount++;
+
+        long elapsed = System.currentTimeMillis() - testStartTime;
+        if (elapsed >= TEST_DURATION_MS && !testFinished) {
+            finishCurrentTest();
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean keyPressed(KeyEvent event, ViewPanel viewPanel) {
+        if (event.getKeyChar() == ' ') {
+            finishCurrentTest();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean keyReleased(KeyEvent event, ViewPanel viewPanel) {
+        return false;
+    }
+
+    @Override
+    public boolean focusLost(ViewPanel viewPanel) {
+        return false;
+    }
+
+    @Override
+    public boolean focusReceived(ViewPanel viewPanel) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/LitSolidCubesTest.java
new file mode 100644 (file)
index 0000000..4932cdf
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static eu.svjatoslav.sixth.e3d.math.Transform.*;
+
+/**
+ * Benchmark test for solid opaque cubes with dynamic lighting.
+ * Renders a grid of fully opaque cubes lit by three orbiting light sources
+ * to test shaded polygon rasterization performance.
+ */
+public class LitSolidCubesTest implements BenchmarkTest {
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<SolidPolygonCube> cubes = new ArrayList<>();
+    private final List<OrbitingLight> orbitingLights = new ArrayList<>();
+    private FrameListener animator;
+    private ViewPanel viewPanel;
+
+    @Override
+    public String getName() {
+        return "Lit Solid Cubes";
+    }
+
+    @Override
+    public void setup(final ShapeCollection shapes) {
+        random.setSeed(RANDOM_SEED);
+
+        // Note: lights will be added when setViewPanel is called
+        final Color[] lightColors = {
+                new Color(255, 100, 100),
+                new Color(100, 255, 100),
+                new Color(100, 100, 255)
+        };
+
+        for (int i = 0; i < 3; i++) {
+            final double angleOffset = i * (Math.PI * 2 / 3);
+            final LightSource light = new LightSource(
+                    new Point3D(0, 0, 0),
+                    lightColors[i],
+                    2.5
+            );
+
+            final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), lightColors[i]);
+            shapes.addShape(marker);
+
+            orbitingLights.add(new OrbitingLight(light, marker, 600, 0.002, angleOffset, i));
+        }
+
+        final double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    final double px = offset + x * SPACING;
+                    final double py = offset + y * SPACING;
+                    final double pz = offset + z * SPACING;
+
+                    final Color color = new Color(
+                            150 + random.nextInt(105),
+                            150 + random.nextInt(105),
+                            150 + random.nextInt(105)
+                    );
+
+                    final SolidPolygonCube cube = new SolidPolygonCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            color
+                    );
+                    cube.setShadingEnabled(true);
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(final ShapeCollection shapes) {
+        shapes.clear();
+        cubes.clear();
+
+        for (final OrbitingLight ol : orbitingLights) {
+            if (viewPanel != null) {
+                viewPanel.getLightingManager().removeLight(ol.light);
+            }
+        }
+        orbitingLights.clear();
+
+        if (viewPanel != null && animator != null) {
+            viewPanel.removeFrameListener(animator);
+        }
+        viewPanel = null;
+        animator = null;
+    }
+
+    /**
+     * Sets the view panel for animation callbacks.
+     * @param viewPanel the view panel
+     */
+    public void setViewPanel(final ViewPanel viewPanel) {
+        this.viewPanel = viewPanel;
+        if (viewPanel != null) {
+            // Configure global lighting
+            LightingManager lightingManager = viewPanel.getLightingManager();
+            lightingManager.setAmbientLight(new Color(15, 15, 20));
+            for (final OrbitingLight ol : orbitingLights) {
+                lightingManager.addLight(ol.light);
+            }
+
+            animator = new LightAnimator();
+            viewPanel.addFrameListener(animator);
+        }
+    }
+
+    private static class OrbitingLight {
+        final LightSource light;
+        final LightSourceMarker marker;
+        final double orbitRadius;
+        final double speed;
+        final int axisIndex;
+        double angle;
+
+        OrbitingLight(final LightSource light, final LightSourceMarker marker,
+                      final double orbitRadius, final double speed, final double angleOffset, final int axisIndex) {
+            this.light = light;
+            this.marker = marker;
+            this.orbitRadius = orbitRadius;
+            this.speed = speed;
+            this.angle = angleOffset;
+            this.axisIndex = axisIndex;
+        }
+    }
+
+    private class LightAnimator implements FrameListener {
+        @Override
+        public boolean onFrame(final ViewPanel vp, final int millisecondsSinceLastFrame) {
+            for (final OrbitingLight ol : orbitingLights) {
+                ol.angle += ol.speed * millisecondsSinceLastFrame;
+
+                double x, y, z;
+                switch (ol.axisIndex) {
+                    case 0:
+                        x = ol.orbitRadius * Math.cos(ol.angle);
+                        y = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        z = ol.orbitRadius * Math.sin(ol.angle);
+                        break;
+                    case 1:
+                        x = ol.orbitRadius * Math.sin(ol.angle);
+                        y = ol.orbitRadius * Math.cos(ol.angle);
+                        z = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        break;
+                    default:
+                        x = ol.orbitRadius * 0.3 * Math.sin(ol.angle * 2);
+                        y = ol.orbitRadius * Math.sin(ol.angle);
+                        z = ol.orbitRadius * Math.cos(ol.angle);
+                        break;
+                }
+
+                final Point3D newPos = new Point3D(x, y, z);
+                ol.light.setPosition(newPos);
+                ol.marker.setTransform(fromAngles(newPos, 0, 0));
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/SolidCubesTest.java
new file mode 100644 (file)
index 0000000..01ec844
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for solid-color cubes.
+ * Renders a grid of cubes with random semi-transparent colors to test
+ * solid polygon rasterization performance.
+ */
+public class SolidCubesTest implements BenchmarkTest {
+
+    /**
+     * Creates a new SolidCubesTest instance.
+     */
+    public SolidCubesTest() {
+    }
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> cubes = new ArrayList<>();
+
+    @Override
+    public String getName() {
+        return "Solid Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes) {
+        random.setSeed(RANDOM_SEED);
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Color color = new Color(
+                            50 + random.nextInt(150),
+                            100 + random.nextInt(155),
+                            50 + random.nextInt(150),
+                            40
+                    );
+                    SolidPolygonCube cube = new SolidPolygonCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            color
+                    );
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        shapes.clear();
+        cubes.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/StarGridTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/StarGridTest.java
new file mode 100644 (file)
index 0000000..2e80dc3
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for glowing point (star) billboards.
+ * Renders a grid of glowing points arranged in a cube formation to test
+ * billboard rendering and texture blending performance.
+ */
+public class StarGridTest implements BenchmarkTest {
+
+    /**
+     * Creates a new StarGridTest instance.
+     */
+    public StarGridTest() {
+    }
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double STAR_SIZE = 20;
+    private static final int UNIQUE_COLORS_COUNT = 30;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> stars = new ArrayList<>();
+    private List<Color> colors;
+
+    @Override
+    public String getName() {
+        return "Star Grid";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes) {
+        initializeColors();
+        random.setSeed(RANDOM_SEED);
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Color color = colors.get(random.nextInt(colors.size()));
+                    GlowingPoint star = new GlowingPoint(
+                            new Point3D(px, py, pz),
+                            STAR_SIZE,
+                            color
+                    );
+                    shapes.addShape(star);
+                    stars.add(star);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        shapes.clear();
+        stars.clear();
+    }
+
+    private void initializeColors() {
+        colors = new ArrayList<>();
+        random.setSeed(RANDOM_SEED);
+        for (int i = 0; i < UNIQUE_COLORS_COUNT; i++) {
+            colors.add(new Color(
+                    random.nextDouble() + 0.5,
+                    random.nextDouble() + 0.5,
+                    random.nextDouble() + 0.5,
+                    255
+            ));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TestResult.java
new file mode 100644 (file)
index 0000000..9c7eef2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+/**
+ * Holds the results of a single benchmark test.
+ */
+public class TestResult {
+
+    /** The name of the benchmark test. */
+    public final String testName;
+
+    /** Total number of frames rendered during the test. */
+    public final long frameCount;
+
+    /** Duration of the test in seconds. */
+    public final double durationSeconds;
+
+    /** Average frames per second achieved during the test. */
+    public final double averageFps;
+
+    /**
+     * Creates a test result record.
+     * @param testName the name of the test
+     * @param frameCount total frames rendered
+     * @param durationSeconds test duration in seconds
+     */
+    public TestResult(String testName, long frameCount, double durationSeconds) {
+        this.testName = testName;
+        this.frameCount = frameCount;
+        this.durationSeconds = durationSeconds;
+        this.averageFps = frameCount / durationSeconds;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java
new file mode 100644 (file)
index 0000000..f4ddb33
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.Vertex;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedTriangle;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+/**
+ * A cube composed of textured polygons.
+ * Used by the {@link TexturedCubesTest} benchmark to measure texture rendering performance.
+ */
+public class TexturedCube extends AbstractCompositeShape {
+
+    /**
+     * Creates a textured cube centered at the given position.
+     * @param center the center position of the cube
+     * @param size half the edge length of the cube
+     * @param texture the texture to apply to all faces
+     */
+    public TexturedCube(Point3D center, double size, Texture texture) {
+        double s = size;
+        Point3D p1 = new Point3D(center.x - s, center.y - s, center.z - s);
+        Point3D p7 = new Point3D(center.x + s, center.y + s, center.z + s);
+
+        Point3D p2 = new Point3D(p7.x, p1.y, p1.z);
+        Point3D p3 = new Point3D(p7.x, p1.y, p7.z);
+        Point3D p4 = new Point3D(p1.x, p1.y, p7.z);
+        Point3D p5 = new Point3D(p1.x, p7.y, p1.z);
+        Point3D p6 = new Point3D(p7.x, p7.y, p1.z);
+        Point3D p8 = new Point3D(p1.x, p7.y, p7.z);
+
+        Point2D t00 = new Point2D(0, 0);
+        Point2D t10 = new Point2D(64, 0);
+        Point2D t01 = new Point2D(0, 64);
+        Point2D t11 = new Point2D(64, 64);
+
+        addTexturedFace(p1, p2, p3, p4, t00, t10, t11, t01, texture);
+        addTexturedFace(p5, p8, p7, p6, t00, t01, t11, t10, texture);
+        addTexturedFace(p1, p5, p6, p2, t00, t01, t11, t10, texture);
+        addTexturedFace(p3, p7, p8, p4, t00, t10, t11, t01, texture);
+        addTexturedFace(p1, p4, p8, p5, t00, t10, t11, t01, texture);
+        addTexturedFace(p2, p6, p7, p3, t00, t01, t11, t10, texture);
+
+        setBackfaceCulling(true);
+    }
+
+    private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
+                                 Point2D t1, Point2D t2, Point2D t3, Point2D t4,
+                                 Texture texture) {
+        TexturedTriangle tri1 = new TexturedTriangle(
+                new Vertex(p1, t1),
+                new Vertex(p2, t2),
+                new Vertex(p3, t3),
+                texture
+        );
+        tri1.setBackfaceCulling(true);
+
+        TexturedTriangle tri2 = new TexturedTriangle(
+                new Vertex(p1, t1),
+                new Vertex(p3, t3),
+                new Vertex(p4, t4),
+                texture
+        );
+        tri2.setBackfaceCulling(true);
+
+        addShape(tri1);
+        addShape(tri2);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCubesTest.java
new file mode 100644 (file)
index 0000000..887b29d
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for textured cubes.
+ * Renders a grid of cubes with textures to test textured polygon rendering
+ * and texture sampling performance.
+ */
+public class TexturedCubesTest implements BenchmarkTest {
+
+    /**
+     * Creates a new TexturedCubesTest instance.
+     */
+    public TexturedCubesTest() {
+    }
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final int TEXTURE_COUNT = 20;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> cubes = new ArrayList<>();
+    private Texture[] textures;
+    private int[] cubeTextureIndices;
+
+    @Override
+    public String getName() {
+        return "Textured Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes) {
+        initializeTextures();
+        initializeCubeTextureIndices();
+
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+        int idx = 0;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Texture tex = textures[cubeTextureIndices[idx]];
+                    TexturedCube cube = new TexturedCube(
+                            new Point3D(px, py, pz),
+                            CUBE_SIZE,
+                            tex
+                    );
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                    idx++;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        shapes.clear();
+        cubes.clear();
+    }
+
+    private void initializeTextures() {
+        textures = new Texture[TEXTURE_COUNT];
+        for (int i = 0; i < TEXTURE_COUNT; i++) {
+            textures[i] = createGlowTexture(
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200),
+                    50 + random.nextInt(200)
+            );
+        }
+    }
+
+    private void initializeCubeTextureIndices() {
+        random.setSeed(RANDOM_SEED);
+        cubeTextureIndices = new int[GRID_SIZE * GRID_SIZE * GRID_SIZE];
+        for (int i = 0; i < cubeTextureIndices.length; i++) {
+            cubeTextureIndices[i] = random.nextInt(TEXTURE_COUNT);
+        }
+    }
+
+    private Texture createGlowTexture(int r, int g, int b) {
+        int texSize = 64;
+        Texture texture = new Texture(texSize, texSize, 2);
+
+        java.awt.Graphics2D gr = texture.graphics;
+        gr.setBackground(new java.awt.Color(r, g, b, 30));
+        gr.clearRect(0, 0, texSize, texSize);
+
+        int glowWidth = 6;
+        for (int i = 0; i < glowWidth; i++) {
+            int intensity = (int) (120.0 * (glowWidth - i) / glowWidth);
+            java.awt.Color glowColor = new java.awt.Color(
+                    Math.min(255, r + intensity),
+                    Math.min(255, g + intensity),
+                    Math.min(255, b + intensity),
+                    50 - i * 8
+            );
+            gr.setColor(glowColor);
+            gr.drawRect(i, i, texSize - 1 - 2 * i, texSize - 1 - 2 * i);
+        }
+
+        gr.dispose();
+        texture.resetResampledBitmapCache();
+        return texture;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/WireframeCubesTest.java
new file mode 100644 (file)
index 0000000..23dc72f
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeBox;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Benchmark test for wireframe cubes.
+ * Renders a grid of wireframe cube outlines to test line rendering performance.
+ */
+public class WireframeCubesTest implements BenchmarkTest {
+
+    /**
+     * Creates a new WireframeCubesTest instance.
+     */
+    public WireframeCubesTest() {
+    }
+
+    private static final int GRID_SIZE = 16;
+    private static final double SPACING = 80;
+    private static final double CUBE_SIZE = 25;
+    private static final long RANDOM_SEED = 42;
+
+    private final Random random = new Random(RANDOM_SEED);
+    private final List<Object> cubes = new ArrayList<>();
+
+    @Override
+    public String getName() {
+        return "Wireframe Cubes";
+    }
+
+    @Override
+    public void setup(ShapeCollection shapes) {
+        random.setSeed(RANDOM_SEED);
+        double offset = -(GRID_SIZE - 1) * SPACING / 2;
+
+        for (int x = 0; x < GRID_SIZE; x++) {
+            for (int y = 0; y < GRID_SIZE; y++) {
+                for (int z = 0; z < GRID_SIZE; z++) {
+                    double px = offset + x * SPACING;
+                    double py = offset + y * SPACING;
+                    double pz = offset + z * SPACING;
+
+                    Color color = new Color(
+                            100 + random.nextInt(155),
+                            100 + random.nextInt(155),
+                            100 + random.nextInt(155),
+                            50
+                    );
+                    LineAppearance appearance = new LineAppearance(5.5, color);
+
+                    Point3D center = new Point3D(px, py, pz);
+                    Point3D p1 = new Point3D(center.x - CUBE_SIZE, center.y - CUBE_SIZE, center.z - CUBE_SIZE);
+                    Point3D p2 = new Point3D(center.x + CUBE_SIZE, center.y + CUBE_SIZE, center.z + CUBE_SIZE);
+
+                    WireframeBox cube = new WireframeBox(p1, p2, appearance);
+                    shapes.addShape(cube);
+                    cubes.add(cube);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void teardown(ShapeCollection shapes) {
+        shapes.clear();
+        cubes.clear();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/package-info.java
new file mode 100644 (file)
index 0000000..f95f2b4
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Graphics benchmark components for measuring the Sixth 3D engine's rendering performance.
+ * 
+ * <p>The benchmark suite includes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark} - Main benchmark runner</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.BenchmarkTest} - Interface for test implementations</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.SolidCubesTest} - Solid-color cube rendering test</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.TexturedCubesTest} - Textured cube rendering test</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.WireframeCubesTest} - Wireframe cube rendering test</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.benchmark.StarGridTest} - Billboard (glowing point) rendering test</li>
+ * </ul>
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.benchmark;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java
new file mode 100644 (file)
index 0000000..cf33722
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.TextPointer;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.*;
+
+/**
+ * Demo showcasing Constructive Solid Geometry (CSG) boolean operations.
+ *
+ * <p>This demo displays three CSG operations side by side:</p>
+ * <ul>
+ *   <li><b>Subtract:</b> A cube with a spherical cavity carved out</li>
+ *   <li><b>Union:</b> A cube merged with a sphere</li>
+ *   <li><b>Intersect:</b> The volume shared by a cube and sphere</li>
+ * </ul>
+ *
+ * <p>All operations use a consistent color scheme: red for the first argument
+ * (cube) and blue for the second argument (sphere). This makes it easy to see
+ * which parts of the result came from which input shape.</p>
+ *
+ * <p><b>Run this demo:</b></p>
+ * <pre>{@code
+ * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo
+ * }</pre>
+ *
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#union
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#subtract
+ * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#intersect
+ */
+public class CSGDemo {
+
+    /**
+     * Distance between shapes on the X axis.
+     */
+    private static final double SPACING = 400;
+
+    // ==================== SHAPE FACTORY METHODS ====================
+
+    /**
+     * Creates a cube for CSG operations with current demo parameters.
+     *
+     * <p>The cube is created with shading enabled for realistic lighting.</p>
+     *
+     * @return a new SolidPolygonCube centered at origin with shading enabled
+     */
+    private static SolidPolygonCube createCube() {
+        final SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, hex("39FF14D9"));
+        cube.setShadingEnabled(true);
+        return cube;
+    }
+
+    /**
+     * Creates a sphere for CSG operations with current demo parameters.
+     *
+     * <p>The sphere is created with shading disabled, causing it to glow
+     * at full intensity regardless of lighting conditions.</p>
+     *
+     * @return a new SolidPolygonSphere centered at origin with shading disabled
+     */
+    private static SolidPolygonSphere createSphere() {
+        final SolidPolygonSphere sphere = new SolidPolygonSphere(origin(), 96, 12, hex("FF6600C0"));
+        sphere.setShadingEnabled(false);
+        return sphere;
+    }
+
+    // ==================== MAIN ENTRY POINT ====================
+
+    /**
+     * Entry point for the CSG demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("CSG demo - Boolean operations");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view all three shapes
+        viewPanel.getCamera().getTransform().set(-244.24, 254.40, -458.83, -0.26, 0.24, -0.00);
+
+        // Set up lighting
+        viewPanel.getLightingManager().setAmbientLight(hex("0D0D0D"));
+
+        // Create lights
+        createLights(viewPanel, shapes);
+
+        // Create the three CSG demonstrations
+        createSubtractDemo(shapes, point(-SPACING, 0, 0));
+        createUnionDemo(shapes, origin());
+        createIntersectDemo(shapes, point(SPACING, 0, 0));
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates the subtract operation demo: cube - sphere.
+     * Results in a cube with a spherical cavity.
+     *
+     * @param shapes   the shape collection
+     * @param location the position for the result
+     */
+    private static void createSubtractDemo(final ShapeCollection shapes, final Point3D location) {
+
+        final SolidPolygonCube cube = createCube();
+        cube.subtract(createSphere());
+
+        shapes.addShape(cube
+                .setTransform(new Transform(location))
+                .setBackfaceCulling(true));
+
+        final String description =
+                "Subtract: Cube - Sphere\n" +
+                        "\n" +
+                        "Green = Cube (kept)\n" +
+                        "Orange = Sphere (carved out)";
+
+        shapes.addShape(createDescriptionPanel(location, description));
+    }
+
+    /**
+     * Creates the union operation demo: cube + sphere.
+     * Results in a combined shape.
+     *
+     * @param shapes   the shape collection
+     * @param location the position for the result
+     */
+    private static void createUnionDemo(final ShapeCollection shapes, final Point3D location) {
+
+        final SolidPolygonCube cube = createCube();
+        cube.union(createSphere());
+
+        shapes.addShape(cube
+                .setTransform(new Transform(location))
+                .setBackfaceCulling(true));
+
+        final String description =
+                "Union: Cube + Sphere\n" +
+                        "\n" +
+                        "Green = Cube\n" +
+                        "Orange = Sphere";
+
+        shapes.addShape(createDescriptionPanel(location, description));
+    }
+
+    /**
+     * Creates the intersect operation demo: cube ∩ sphere.
+     * Results in the volume shared by both shapes.
+     *
+     * @param shapes   the shape collection
+     * @param location the position for the result
+     */
+    private static void createIntersectDemo(final ShapeCollection shapes, final Point3D location) {
+
+        final SolidPolygonCube cube = createCube();
+        cube.intersect(createSphere());
+
+        shapes.addShape(cube
+                .setTransform(new Transform(location))
+                .setBackfaceCulling(true));
+
+        final String description =
+                "Intersect: Cube ∩ Sphere\n" +
+                        "\n" +
+                        "Green = from Cube\n" +
+                        "Orange = from Sphere";
+
+        shapes.addShape(createDescriptionPanel(location, description));
+    }
+
+    /**
+     * Creates a description panel positioned below a shape.
+     *
+     * @param location the position of the associated shape
+     * @param text     the description text to display
+     * @return a TextCanvas positioned below the shape
+     */
+    private static TextCanvas createDescriptionPanel(final Point3D location, final String text) {
+        final Transform transform = Transform.fromAngles(
+                point(location.x, location.y + 220, location.z),
+                0, 0);
+
+        final TextCanvas panel = new TextCanvas(
+                transform,
+                new TextPointer(5, 35),
+                WHITE,
+                TRANSPARENT);
+
+        panel.setText(text);
+        return panel;
+    }
+
+    /**
+     * Creates lighting for the scene.
+     *
+     * @param viewPanel the view panel
+     * @param shapes    the shape collection
+     */
+    private static void createLights(final ViewPanel viewPanel, final ShapeCollection shapes) {
+        // Main light from above-front - bright white
+        final LightSource mainLight = new LightSource(
+                point(0, -300, -400),
+                hex("FFFFFF"),
+                5.5
+        );
+        viewPanel.getLightingManager().addLight(mainLight);
+
+        // Fill light from the side - warm orange tint
+        final LightSource fillLight = new LightSource(
+                point(500, 100, -200),
+                hex("FF6600"),
+                5.5
+        );
+        viewPanel.getLightingManager().addLight(fillLight);
+
+        viewPanel.getLightingManager().setAmbientLight(hex("3D3D5D"));
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java
new file mode 100644 (file)
index 0000000..dafd300
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Demo displaying coordinate system axes using 3D arrows.
+ *
+ * <p>This demo illustrates the Sixth 3D coordinate system with three colored
+ * arrows originating from the origin (0,0,0), each pointing along a positive
+ * axis direction:</p>
+ *
+ * <ul>
+ *   <li><b>Red arrow</b> — +X axis (points RIGHT)</li>
+ *   <li><b>Green arrow</b> — +Y axis (points DOWN visually)</li>
+ *   <li><b>Blue arrow</b> — +Z axis (points AWAY from viewer)</li>
+ * </ul>
+ *
+ * <p>A reference grid at Y=0 provides spatial context. Text labels identify
+ * each axis.</p>
+ *
+ * @see SolidPolygonArrow
+ * @see ForwardOrientedTextBlock
+ */
+public class CoordinateSystemDemo {
+
+    /**
+     * Length of each axis arrow.
+     */
+    private static final double ARROW_LENGTH = 200;
+
+    /**
+     * Radius of the arrow body (cylindrical shaft).
+     */
+    private static final double BODY_RADIUS = 6;
+
+    /**
+     * Distance from arrow tip to text label position.
+     */
+    private static final double LABEL_OFFSET = 20;
+
+    /**
+     * Scale factor for text labels.
+     */
+    private static final double LABEL_SCALE = 20.0;
+
+    /**
+     * Entry point for the coordinate system demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("Coordinate System Demo");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view all three axes
+        viewPanel.getCamera().getTransform().set(130.66, -65.49, -248.18, -0.06, -0.36, -0.00);
+
+        // Create reference grid at Y=0 plane
+        createReferenceGrid(shapes);
+
+        // Create axis arrows
+        createAxisArrows(shapes);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates a reference grid on the Y=0 plane.
+     *
+     * @param shapes the shape collection to add the grid to
+     */
+    private static void createReferenceGrid(final ShapeCollection shapes) {
+        final LineAppearance gridAppearance = new LineAppearance(
+                1, hex("505064FF"));
+        final Grid3D grid = new Grid3D(
+                point(-300, 0, -300),
+                point(300, 0, 300),
+                50,
+                gridAppearance
+        );
+        shapes.addShape(grid);
+    }
+
+    /**
+     * Creates the three axis arrows and their labels.
+     *
+     * @param shapes the shape collection to add the arrows to
+     */
+    private static void createAxisArrows(final ShapeCollection shapes) {
+        // X-axis arrow (RED) - points in positive X direction (RIGHT)
+        final SolidPolygonArrow xArrow = new SolidPolygonArrow(
+                origin(),
+                point(ARROW_LENGTH, 0, 0),
+                BODY_RADIUS, hex("FF0000B4")
+        );
+        shapes.addShape(xArrow);
+        createLabel(shapes, "X", point(ARROW_LENGTH + LABEL_OFFSET, 0, 0), Color.RED);
+
+        // Y-axis arrow (GREEN) - points in positive Y direction (DOWN)
+        final SolidPolygonArrow yArrow = new SolidPolygonArrow(
+                origin(),
+                point(0, ARROW_LENGTH, 0),
+                BODY_RADIUS, hex("00FF00B4")
+        );
+        shapes.addShape(yArrow);
+        createLabel(shapes, "Y", point(0, ARROW_LENGTH + LABEL_OFFSET, 0), Color.GREEN);
+
+        // Z-axis arrow (BLUE) - points in positive Z direction (AWAY)
+        final SolidPolygonArrow zArrow = new SolidPolygonArrow(
+                origin(),
+                point(0, 0, ARROW_LENGTH),
+                BODY_RADIUS, hex("0000FFB4")
+        );
+        shapes.addShape(zArrow);
+        createLabel(shapes, "Z", point(0, 0, ARROW_LENGTH + LABEL_OFFSET), Color.BLUE);
+    }
+
+    /**
+     * Creates a text label at the specified position.
+     *
+     * @param shapes   the shape collection to add the label to
+     * @param text     the label text
+     * @param position the 3D position of the label
+     * @param color    the text color
+     */
+    private static void createLabel(final ShapeCollection shapes,
+                                    final String text,
+                                    final Point3D position,
+                                    final Color color) {
+        final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock(
+                position, LABEL_SCALE, 2, text, color
+        );
+        shapes.addShape(label);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java
new file mode 100644 (file)
index 0000000..aa4347c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+
+/**
+ * Minimal example demonstrating how to create a basic 3D scene.
+ * <p>
+ * Creates a window with a single red box. This is the "Create Your First 3D Scene"
+ * example from the Sixth 3D documentation.
+ */
+public class MinimalExample {
+
+    /**
+     * Entry point for the minimal scene demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        ViewFrame viewFrame = new ViewFrame("Minimal example");
+        ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection();
+
+        viewFrame.getViewPanel().getCamera().getTransform().setTranslation(point(0, -100, -300));
+
+        final Transform boxTransform = new Transform();
+        SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                point(-50, -50, -50),
+                point(50, 50, 50),
+                Color.RED
+        );
+        box.setTransform(boxTransform);
+        shapes.addShape(box);
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java
new file mode 100644 (file)
index 0000000..9efa14b
--- /dev/null
@@ -0,0 +1,441 @@
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.FrameListener;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.*;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+
+/**
+ * Gallery demo showcasing all available 3D shapes in the Sixth 3D engine.
+ *
+ * <p>This demo displays a 7x2 grid of shapes organized by type (columns) and
+ * rendering style (rows). The top row shows solid polygon shapes, and the bottom
+ * row shows wireframe shapes. Each column represents a different shape type.</p>
+ *
+ * <p>Features:</p>
+ * <ul>
+ *   <li>Orbiting colored lights for dynamic lighting demonstration</li>
+ *   <li>Floor grid showing the layout structure</li>
+ *   <li>Text labels identifying each shape type</li>
+ * </ul>
+ */
+public class ShapeGalleryDemo {
+
+    /**
+     * Number of columns (shape types).
+     */
+    private static final int COLUMN_COUNT = 7;
+
+    /**
+     * Size of each cell in world units.
+     */
+    private static final double CELL_SIZE = 250;
+
+    /**
+     * Radius/size for most shapes.
+     */
+    private static final double SHAPE_RADIUS = 50;
+
+    /**
+     * Number of segments for smooth curved shapes.
+     */
+    private static final int SEGMENTS = 16;
+
+    /**
+     * Y offset for labels above shapes.
+     */
+    private static final double LABEL_Y_OFFSET = -100;
+
+    /**
+     * Number of orbiting light sources in the scene.
+     */
+    private static final int LIGHT_COUNT = 10;
+
+    /**
+     * Color palette - one color per column (shape type).
+     */
+    private static final Color[] SHAPE_COLORS = {
+            hex("FF6464FF"),  // Arrow - Red
+            hex("FFB464FF"),  // Cone - Orange
+            hex("FFFF64FF"),  // Cube - Yellow
+            hex("64FF64FF"),  // Cylinder - Green
+            hex("64FFFFFF"),  // Pyramid - Cyan
+            hex("6496FFFF"),  // RectangularBox - Blue
+            hex("C896FFFF"),  // Sphere - Purple
+    };
+
+    /**
+     * Shape type names for labels.
+     */
+    private static final String[] SHAPE_NAMES = {
+            "Arrow",
+            "Cone",
+            "Cube",
+            "Cylinder",
+            "Pyramid",
+            "Box",
+            "Sphere",
+    };
+
+    /**
+     * Entry point for the shaded shapes demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("3D Shape Gallery");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        // Position camera to view the entire gallery
+        viewPanel.getCamera().getTransform().set(-615.31, -299.50, -378.64, -0.62, -0.66, 0.00);
+
+        // Set up lighting
+        viewPanel.getLightingManager().setAmbientLight(hex("282832FF"));
+
+        // Create floor grid
+        createFloorGrid(shapes);
+
+        // Create all shapes
+        createAllShapes(shapes);
+
+        // Create orbiting lights
+        createOrbitingLights(viewPanel, shapes);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates the floor grid that outlines the gallery cells.
+     *
+     * @param shapes the shape collection to add the grid to
+     */
+    private static void createFloorGrid(final ShapeCollection shapes) {
+        final LineAppearance gridAppearance = new LineAppearance(1, hex("505064FF"));
+
+        // Calculate grid bounds: 7 columns, 2 rows, centered around origin
+        final double halfWidth = (COLUMN_COUNT * CELL_SIZE) / 2.0;
+        final double gridDepth = 2 * CELL_SIZE;
+
+        final Point3D cornerA = point(-halfWidth, 0, -CELL_SIZE / 2);
+        final Point3D cornerB = point(halfWidth, 0, gridDepth - CELL_SIZE / 2);
+
+        final Grid3D grid = new Grid3D(cornerA, cornerB, CELL_SIZE, gridAppearance);
+        shapes.addShape(grid);
+    }
+
+    /**
+     * Creates all solid and wireframe shapes in the gallery grid.
+     *
+     * @param shapes the shape collection to add shapes to
+     */
+    private static void createAllShapes(final ShapeCollection shapes) {
+        for (int col = 0; col < COLUMN_COUNT; col++) {
+            final double x = getColumnX(col);
+            final Color color = SHAPE_COLORS[col];
+            final String name = SHAPE_NAMES[col];
+
+            // Row 0: Solid polygon shapes
+            final Point3D solidPos = point(x, 0, 0);
+            createSolidShape(shapes, col, solidPos, color);
+            createLabel(shapes, solidPos, name, color);
+
+            // Row 1: Wireframe shapes
+            final Point3D wireframePos = point(x, 0, CELL_SIZE);
+            createWireframeShape(shapes, col, wireframePos, color);
+            createLabel(shapes, wireframePos, "Wireframe " + name, color);
+        }
+    }
+
+    /**
+     * Calculates the X coordinate for a given column index.
+     *
+     * @param col the column index (0-6)
+     * @return the X coordinate in world units
+     */
+    private static double getColumnX(final int col) {
+        return (col - (COLUMN_COUNT - 1) / 2.0) * CELL_SIZE;
+    }
+
+    /**
+     * Creates a solid polygon shape for the given column.
+     *
+     * @param shapes the shape collection
+     * @param col    the column index (determines shape type)
+     * @param pos    the center position
+     * @param color  the shape color
+     */
+    private static void createSolidShape(final ShapeCollection shapes, final int col,
+                                         final Point3D pos, final Color color) {
+        switch (col) {
+            case 0: // Arrow
+                final SolidPolygonArrow arrow = new SolidPolygonArrow(
+                        point(pos.x, SHAPE_RADIUS, pos.z),
+                        point(pos.x, -SHAPE_RADIUS, pos.z),
+                        8, color);
+                arrow.setShadingEnabled(true);
+                shapes.addShape(arrow);
+                break;
+
+            case 1: // Cone (pointing upward)
+                final SolidPolygonCone cone = new SolidPolygonCone(
+                        point(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, SEGMENTS, color);
+                cone.setShadingEnabled(true);
+                shapes.addShape(cone);
+                break;
+
+            case 2: // Cube
+                final SolidPolygonCube cube = new SolidPolygonCube(pos, SHAPE_RADIUS * 0.8, color);
+                cube.setShadingEnabled(true);
+                shapes.addShape(cube);
+                break;
+
+            case 3: // Cylinder
+                final SolidPolygonCylinder cylinder = new SolidPolygonCylinder(
+                        point(pos.x, SHAPE_RADIUS, pos.z),
+                        point(pos.x, -SHAPE_RADIUS, pos.z),
+                        SHAPE_RADIUS * 0.7, SEGMENTS, color);
+                cylinder.setShadingEnabled(true);
+                shapes.addShape(cylinder);
+                break;
+
+            case 4: // Pyramid
+                final SolidPolygonPyramid pyramid = new SolidPolygonPyramid(
+                        point(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, color);
+                pyramid.setShadingEnabled(true);
+                shapes.addShape(pyramid);
+                break;
+
+            case 5: // RectangularBox
+                final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox(
+                        point(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35),
+                        point(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35),
+                        color);
+                box.setShadingEnabled(true);
+                shapes.addShape(box);
+                break;
+
+            case 6: // Sphere
+                final SolidPolygonSphere sphere = new SolidPolygonSphere(pos, SHAPE_RADIUS, SEGMENTS, color);
+                sphere.setShadingEnabled(true);
+                shapes.addShape(sphere);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Creates a wireframe shape for the given column.
+     *
+     * @param shapes the shape collection
+     * @param col    the column index (determines shape type)
+     * @param pos    the center position
+     * @param color  the line color
+     */
+    private static void createWireframeShape(final ShapeCollection shapes, final int col,
+                                             final Point3D pos, final Color color) {
+        final LineAppearance appearance = new LineAppearance(2, color);
+
+        switch (col) {
+            case 0: // Arrow
+                final WireframeArrow arrow = new WireframeArrow(
+                        point(pos.x, SHAPE_RADIUS, pos.z),
+                        point(pos.x, -SHAPE_RADIUS, pos.z),
+                        8, appearance);
+                shapes.addShape(arrow);
+                break;
+
+            case 1: // Cone
+                final WireframeCone cone = new WireframeCone(
+                        point(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, SEGMENTS, appearance);
+                shapes.addShape(cone);
+                break;
+
+            case 2: // Cube
+                final WireframeCube cube = new WireframeCube(pos, SHAPE_RADIUS * 0.8, appearance);
+                shapes.addShape(cube);
+                break;
+
+            case 3: // Cylinder
+                final WireframeCylinder cylinder = new WireframeCylinder(
+                        point(pos.x, SHAPE_RADIUS, pos.z),
+                        point(pos.x, -SHAPE_RADIUS, pos.z),
+                        SHAPE_RADIUS * 0.7, SEGMENTS, appearance);
+                shapes.addShape(cylinder);
+                break;
+
+            case 4: // Pyramid
+                final WireframePyramid pyramid = new WireframePyramid(
+                        point(pos.x, -SHAPE_RADIUS, pos.z),  // apex above
+                        point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below
+                        SHAPE_RADIUS * 0.8, appearance);
+                shapes.addShape(pyramid);
+                break;
+
+            case 5: // Box (WireframeBox)
+                final WireframeBox box = new WireframeBox(
+                        point(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35),
+                        point(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35),
+                        appearance);
+                shapes.addShape(box);
+                break;
+
+            case 6: // Sphere
+                final WireframeSphere sphere = new WireframeSphere(pos, (float) SHAPE_RADIUS, appearance);
+                shapes.addShape(sphere);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Creates a text label above a shape.
+     *
+     * @param shapes the shape collection
+     * @param pos    the shape position
+     * @param text   the label text
+     * @param color  the text color
+     */
+    private static void createLabel(final ShapeCollection shapes, final Point3D pos,
+                                    final String text, final Color color) {
+        final Point3D labelPos = point(pos.x, pos.y + LABEL_Y_OFFSET, pos.z);
+        final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock(
+                labelPos, 8.0, 2, text, color);
+        shapes.addShape(label);
+    }
+
+    /**
+     * Creates orbiting light sources around the scene.
+     *
+     * @param viewPanel the view panel
+     * @param shapes    the shape collection
+     */
+    private static void createOrbitingLights(final ViewPanel viewPanel, final ShapeCollection shapes) {
+        final Random random = new Random(42);
+        final List<OrbitingLight> orbitingLights = new ArrayList<>();
+
+        for (int i = 0; i < LIGHT_COUNT; i++) {
+            final Color color = new Color(
+                    random.nextInt(256),
+                    random.nextInt(256),
+                    random.nextInt(256)
+            );
+
+            final double orbitRadius = 600 + random.nextInt(200);
+            final double speed = 0.01 + random.nextDouble() * 0.02;
+            final double angleOffset = random.nextDouble() * Math.PI * 2;
+            final double intensity = 2.0 + random.nextDouble() * 2.0;
+
+            final int axis = random.nextInt(3);
+            final double ellipseFactor = 0.7 + random.nextDouble() * 0.3;
+
+            final LightSource light = new LightSource(point(0, 0, CELL_SIZE / 2), color, intensity);
+            final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color);
+
+            viewPanel.getLightingManager().addLight(light);
+            shapes.addShape(marker);
+
+            orbitingLights.add(new OrbitingLight(
+                    light, marker,
+                    orbitRadius, speed, angleOffset, axis, ellipseFactor
+            ));
+        }
+
+        final MultiLightAnimator animator = new MultiLightAnimator(orbitingLights);
+        viewPanel.addFrameListener(animator);
+    }
+
+    /**
+     * Represents a light source that orbits around the scene center.
+     */
+    private static class OrbitingLight {
+        final LightSource light;
+        final LightSourceMarker marker;
+        final double orbitRadius;
+        final double speed;
+        final int axis;
+        final double ellipseFactor;
+        double angle;
+
+        OrbitingLight(final LightSource light, final LightSourceMarker marker,
+                      final double orbitRadius, final double speed, final double angleOffset,
+                      final int axis, final double ellipseFactor) {
+            this.light = light;
+            this.marker = marker;
+            this.orbitRadius = orbitRadius;
+            this.speed = speed;
+            this.angle = angleOffset;
+            this.axis = axis;
+            this.ellipseFactor = ellipseFactor;
+        }
+    }
+
+    /**
+     * Frame listener that animates all orbiting lights.
+     */
+    private record MultiLightAnimator(List<OrbitingLight> lights) implements FrameListener {
+
+        @Override
+        public boolean onFrame(final ViewPanel viewPanel, final int millisecondsSinceLastFrame) {
+            final double centerY = CELL_SIZE / 2;
+
+            for (final OrbitingLight orbitingLight : lights) {
+                orbitingLight.angle += orbitingLight.speed * millisecondsSinceLastFrame / 100;
+
+                final double r = orbitingLight.orbitRadius;
+                final double e = orbitingLight.ellipseFactor;
+
+                double x, y, z;
+                switch (orbitingLight.axis) {
+                    case 0:
+                        x = r * Math.cos(orbitingLight.angle);
+                        y = r * e * Math.sin(orbitingLight.angle);
+                        z = r * 0.5 * Math.sin(orbitingLight.angle * 2);
+                        break;
+                    case 1:
+                        x = r * e * Math.sin(orbitingLight.angle * 2);
+                        y = r * Math.cos(orbitingLight.angle);
+                        z = r * 0.5 * Math.sin(orbitingLight.angle);
+                        break;
+                    default:
+                        x = r * 0.5 * Math.sin(orbitingLight.angle);
+                        y = r * Math.sin(orbitingLight.angle * 2);
+                        z = r * e * Math.cos(orbitingLight.angle);
+                        break;
+                }
+
+                final Point3D newPosition = point(x, y + centerY, z);
+                orbitingLight.light.setPosition(newPosition);
+                orbitingLight.marker.setTransform(fromAngles(
+                        newPosition.x, newPosition.y, newPosition.z, 0, 0, 0));
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java
new file mode 100644 (file)
index 0000000..fceba7b
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+package eu.svjatoslav.sixth.e3d.examples.essentials;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+
+/**
+ * Demo to test winding order and backface culling documentation.
+ * <p>
+ * Creates one triangle with CCW winding (front face) following the docs:
+ * <ul>
+ *   <li>upper-center → lower-left → lower-right</li>
+ *   <li>Backface culling enabled</li>
+ * </ul>
+ * <p>
+ * Expected: green triangle visible (CCW = front face).
+ */
+public class WindingOrderDemo {
+
+    /**
+     * Creates a new WindingOrderDemo instance.
+     */
+    public WindingOrderDemo() {
+    }
+
+    /**
+     * Entry point for the winding order demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(String[] args) {
+        ViewFrame viewFrame = new ViewFrame("Winding order demo");
+        ViewPanel viewPanel = viewFrame.getViewPanel();
+        ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        viewPanel.getCamera().getTransform().setTranslation(point(0, 0, -500));
+
+        double size = 150;
+
+        Point3D upperCenter = point(0, -size, 0);
+        Point3D lowerLeft = point(-size, +size, 0);
+        Point3D lowerRight = point(+size, +size, 0);
+
+        SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN);
+        triangle.setBackfaceCulling(true);
+
+        shapes.addShape(triangle);
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/Galaxy.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/Galaxy.java
new file mode 100755 (executable)
index 0000000..a632268
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.examples.galaxy_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static java.lang.Math.*;
+
+/**
+ * Represents a spiral galaxy composed of glowing points.
+ * Uses a mathematical spiral distribution to create a realistic galaxy shape
+ * with multiple spiral arms. Points are colored from a limited palette to
+ * enable texture reuse optimization.
+ */
+public class Galaxy extends AbstractCompositeShape {
+
+    /**
+     * The number of unique colors used in the galaxy.
+     * Used to reuse textures of glowing points of the same color.
+     */
+    public static final int UNIQUE_COLORS_COUNT = 30;
+
+    /**
+     * A list of all colors used in the galaxy.
+     * Used to reuse textures of glowing points of the same color.
+     */
+    private static List<Color> colors;
+
+    /**
+     * Constructs a galaxy with the specified parameters.
+     * @param galaxySize the overall size scale of the galaxy
+     * @param tailCount the number of spiral arms
+     * @param starsCount the total number of stars to generate
+     * @param transform the position and orientation of the galaxy
+     */
+    public Galaxy(final int galaxySize, final int tailCount, final int starsCount,
+                  Transform transform) {
+
+        super(transform);
+
+        ensureColorsAreInitialized();
+
+        final double angle1 = random() * 10;
+        final double angle2 = random() * 10;
+
+        final double angleSin1 = sin(angle1);
+        final double angleCos1 = cos(angle1);
+        final double angleSin2 = sin(angle2);
+        final double angleCos2 = cos(angle2);
+
+        Random random = new Random();
+
+        double starSize = galaxySize / 70d;
+
+        for (int i = 1; i < starsCount; i++) {
+            final double b = random() * 10;
+
+            final double s = (b * b) / 30;
+
+            final double v1 = (random() * (11.5 - b)) / 3;
+            final double v1p = v1 / 2;
+
+            final double ane = ((random() * (s / 2)) / tailCount) * 2;
+            final double sba = ((2 * PI) / tailCount)
+                    * random.nextInt(tailCount);
+
+            final double x = (((sin((b - sba) + ane) * s) + (random() * v1)) - v1p) * galaxySize;
+            final double z = (((cos((b - sba) + ane) * s) + (random() * v1)) - v1p) * galaxySize;
+            final double y = ((random() * v1) - v1p) * galaxySize;
+
+            final double x1 = (x * angleCos1) + (z * angleSin1);
+            final double z1 = (z * angleCos1) - (x * angleSin1);
+
+            final double y1 = (y * angleCos2) + (z1 * angleSin2);
+            final double z2 = (z1 * angleCos2) - (y * angleSin2);
+
+            addStar(new Point3D(x1, y1, z2), starSize);
+        }
+    }
+
+    /**
+     * Adds a single star at the specified location.
+     * @param starLocation the position of the star
+     * @param size the visual size of the star
+     */
+    private void addStar(final Point3D starLocation, double size) {
+        addShape(new GlowingPoint(starLocation, size, colors.get((int) (random() * colors.size()))));
+    }
+
+    /**
+     * Initializes the shared color palette for all galaxies.
+     * Creates a limited set of random colors to enable texture reuse.
+     */
+    private synchronized void ensureColorsAreInitialized() {
+        if (colors != null) return;
+
+        colors = new ArrayList<>();
+
+        for (int i = 0; i < UNIQUE_COLORS_COUNT; i++)
+            colors.add(
+                    new Color(
+                            random() + 0.5,
+                            random() + 0.5,
+                            random() + 0.5,
+                            255));
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/PointCloudDemo.java
new file mode 100644 (file)
index 0000000..b382ad1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+*/
+
+package eu.svjatoslav.sixth.e3d.examples.galaxy_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+
+/**
+ * Demo showing a point cloud galaxy simulation.
+ * Creates a spiral galaxy with 10,000 glowing points using the Galaxy class.
+ */
+public class PointCloudDemo {
+
+    /**
+     * Creates a new PointCloudDemo instance.
+     */
+    public PointCloudDemo() {
+    }
+
+    /**
+     * Entry point for the point cloud demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+
+        final ViewFrame viewFrame = new ViewFrame("Point Cloud Galaxy");
+
+        viewFrame.getViewPanel().getCamera().getTransform().set(-1099.85, -2862.44, 144.32, -1.09, -0.60, 0);
+
+        final ShapeCollection geometryCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        Transform transform = Transform.fromAngles(0, -1000, 1000, 0, 0, 0);
+
+        // add galaxy
+        geometryCollection.addShape(new Galaxy(500, 3, 10000, transform));
+
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+
+    }
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/galaxy_demo/package-info.java
new file mode 100644 (file)
index 0000000..a80d64b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Galaxy simulation demo using glowing points.
+ *
+ * <p>Creates a spiral galaxy visualization using
+ * {@link eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint}
+ * shapes arranged in a mathematical spiral pattern.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.galaxy_demo.Galaxy} - The galaxy shape</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo} - Demo entry point</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.examples.galaxy_demo.Galaxy
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.galaxy_demo;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java
new file mode 100644 (file)
index 0000000..508fb7c
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.graph_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point2D;
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.Graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Demo showing mathematical function graphs rendered in 3D.
+ * Displays sine, cosine, tangent, and composite function graphs.
+ * Also includes a 3D surface graph showing z = (x² - y²) / 20 with
+ * intersecting planes and highlighted intersection curves.
+ */
+public class MathGraphsDemo {
+
+    private static final double GRAPH_SCALE = 50d;
+
+    /**
+     * Creates a new MathGraphsDemo instance.
+     */
+    public MathGraphsDemo() {
+    }
+
+    /**
+     * Creates a graph of the cosine function.
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing y = cos(x)
+     */
+    private static Graph getCosineGraph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            final double y = Math.cos(x);
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "Cosine", location);
+    }
+
+    /**
+     * Creates a graph of y = sin(tan(x)).
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing the composite function
+     */
+    private static Graph getFormula1Graph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            final double y = Math.sin(Math.tan(x));
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "y = sin(tan(x))", location);
+    }
+
+    /**
+     * Creates a graph of y = (10-x)^2 / 30.
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing the parabola
+     */
+    private static Graph getFormula2Graph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            final double y = (Math.pow((10 - x), 2) / 30) - 2;
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "y = ( (10-x)^2 ) / 30", location);
+    }
+
+    /**
+     * Creates a graph of y = sin(x/2) + sin(x/1.26).
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing the composite sine wave
+     */
+    private static Graph getFormula3Graph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            final double y = Math.sin(x / 2) + Math.sin(x / 1.26);
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "y = sin(x/2) + sin(x/1.26)", location);
+    }
+
+    /**
+     * Creates a graph of the sine function.
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing y = sin(x)
+     */
+    private static Graph getSineGraph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            final double y = Math.sin(x);
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "Sine", location);
+    }
+
+    /**
+     * Creates a graph of the tangent function with clamped values.
+     * @param location the position of the graph in 3D space
+     * @return a Graph component showing y = tan(x) with clamped range
+     */
+    private static Graph getTangentGraph(final Point3D location) {
+        final List<Point2D> data = new ArrayList<>();
+        for (double x = 0; x < 20; x += 0.25) {
+            double y = Math.tan(x);
+
+            if (y > 2)
+                y = 2;
+            if (y < -2)
+                y = -2;
+
+            final Point2D p = new Point2D(x, y);
+            data.add(p);
+        }
+
+        return new Graph(GRAPH_SCALE, data, "Tangent", location);
+    }
+
+    /**
+     * Entry point for the math graphs demo.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+
+        final ViewFrame viewFrame = new ViewFrame("Math Graphs");
+        final ShapeCollection geometryCollection = viewFrame.getViewPanel()
+                .getRootShapeCollection();
+
+        viewFrame.getViewPanel().getCamera().getTransform().set(-613.58, -408.70, -351.41, -0.60, -0.54, -0.00);
+
+        addMathFormulas(geometryCollection);
+        addSurfaceGraph(geometryCollection);
+        
+        viewFrame.getViewPanel().repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Adds all mathematical formula graphs to the scene.
+     * @param geometryCollection the collection to add graphs to
+     */
+    private static void addMathFormulas(ShapeCollection geometryCollection) {
+        int z = 1000;
+        Point3D location = point(-600, -300, z);
+        geometryCollection.addShape(getSineGraph(location));
+
+        location = point(600, -300, z);
+        geometryCollection.addShape(getFormula1Graph(location));
+
+        location = point(-600, 0, z);
+        geometryCollection.addShape(getCosineGraph(location));
+
+        location = point(600, 0, z);
+        geometryCollection.addShape(getFormula2Graph(location));
+
+        location = point(-600, 300, z);
+        geometryCollection.addShape(getTangentGraph(location));
+
+        location = point(600, 300, z);
+        geometryCollection.addShape(getFormula3Graph(location));
+    }
+
+    private static void addSurfaceGraph(ShapeCollection geometryCollection) {
+        final double range = 10;
+        final double step = 0.5;
+        final double scale = 30;
+
+        final SurfaceGraph3D.MathFunction3D function = (x, y) -> (x * x - y * y) / 20;
+
+        SurfaceGraph3D surface = new SurfaceGraph3D(
+                -range, range, step,
+                -range, range, step,
+                function,
+                new Color(150, 150, 150, 180),
+                Color.WHITE,
+                scale,
+                origin()
+        );
+
+        surface.addYIntersectionCurve(4, function, new Color(255, 255, 0, 220), 1.2);
+        surface.addXIntersectionCurve(-4, function, new Color(255, 120, 0, 220), 1.2);
+
+        geometryCollection.addShape(surface);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java
new file mode 100644 (file)
index 0000000..c4507d3
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.examples.graph_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+
+/**
+ * A 3D surface graph that visualizes mathematical functions of the form z = f(x, y).
+ *
+ * <p>The surface is rendered as a grid of quadrilaterals (split into triangles) with
+ * optional wireframe overlay. The surface color can be customized, and the wireframe
+ * lines are drawn in a contrasting color.</p>
+ *
+ * <p><b>Usage example:</b></p>
+ * <pre>{@code
+ * // Create a saddle surface: z = x^2 - y^2
+ * SurfaceGraph3D saddle = new SurfaceGraph3D(
+ *     -5, 5, 0.5,      // x range and step
+ *     -5, 5, 0.5,      // y range and step
+ *     (x, y) -> x * x - y * y,  // function z = f(x, y)
+ *     new Color(180, 180, 180, 200),  // gray semi-transparent surface
+ *     new Color(255, 255, 255),       // white grid lines
+ *     new Point3D(0, 0, 0)            // position in 3D space
+ * );
+ * shapeCollection.addShape(saddle);
+ * }</pre>
+ *
+ * @see AbstractCompositeShape
+ * @see SolidPolygon
+ * @see Line
+ */
+public class SurfaceGraph3D extends AbstractCompositeShape {
+
+    private final double xMin;
+    private final double xMax;
+    private final double xStep;
+    private final double yMin;
+    private final double yMax;
+    private final double yStep;
+    private final double scale;
+    private final Color surfaceColor;
+    private final Color gridColor;
+    private final double lineWidth;
+
+    /**
+     * Creates a 3D surface graph at the specified location.
+     *
+     * @param xMin         minimum X value of the domain
+     * @param xMax         maximum X value of the domain
+     * @param xStep        step size between grid points along X axis
+     * @param yMin         minimum Y value of the domain
+     * @param yMax         maximum Y value of the domain
+     * @param yStep        step size between grid points along Y axis
+     * @param function     the mathematical function z = f(x, y) to visualize
+     * @param surfaceColor color of the surface polygons (use semi-transparent for see-through effect)
+     * @param gridColor    color of the wireframe grid lines
+     * @param lineWidth    width of grid lines in world units
+     * @param scale        scale factor applied to all generated vertices
+     * @param location     the 3D position of the graph's origin
+     */
+    public SurfaceGraph3D(final double xMin, final double xMax, final double xStep,
+                          final double yMin, final double yMax, final double yStep,
+                          final MathFunction3D function,
+                          final Color surfaceColor, final Color gridColor,
+                          final double lineWidth, final double scale, final Point3D location) {
+        super(location);
+
+        this.xMin = xMin;
+        this.xMax = xMax;
+        this.yMin = yMin;
+        this.yMax = yMax;
+        this.xStep = xStep;
+        this.yStep = yStep;
+        this.scale = scale;
+        this.surfaceColor = surfaceColor;
+        this.gridColor = gridColor;
+        this.lineWidth = lineWidth;
+
+        generateSurface(function);
+    }
+
+    public SurfaceGraph3D(final double xMin, final double xMax, final double xStep,
+                          final double yMin, final double yMax, final double yStep,
+                          final MathFunction3D function,
+                          final Color surfaceColor, final Color gridColor,
+                          final double scale, final Point3D location) {
+        this(xMin, xMax, xStep, yMin, yMax, yStep, function, surfaceColor, gridColor, 0.1, scale, location);
+    }
+
+    private void generateSurface(final MathFunction3D function) {
+        final int xCount = (int) ((xMax - xMin) / xStep) + 1;
+        final int yCount = (int) ((yMax - yMin) / yStep) + 1;
+
+        final Point3D[][] vertices = new Point3D[xCount][yCount];
+
+        for (int i = 0; i < xCount; i++) {
+            for (int j = 0; j < yCount; j++) {
+                final double x = xMin + i * xStep;
+                final double y = yMin + j * yStep;
+                final double z = function.apply(x, y);
+                vertices[i][j] = new Point3D(x * scale, -z * scale, y * scale);
+            }
+        }
+
+        for (int i = 0; i < xCount - 1; i++) {
+            for (int j = 0; j < yCount - 1; j++) {
+                final Point3D p1 = vertices[i][j];
+                final Point3D p2 = vertices[i + 1][j];
+                final Point3D p3 = vertices[i][j + 1];
+                final Point3D p4 = vertices[i + 1][j + 1];
+
+                addQuad(p1, p2, p3, p4);
+                addGridLines(p1, p2, p3, p4);
+            }
+        }
+    }
+
+    private void addQuad(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) {
+        final SolidPolygon quad = SolidPolygon.quad(p1, p2, p4, p3, surfaceColor);
+        quad.setBackfaceCulling(false);
+        addShape(quad);
+    }
+
+    private void addGridLines(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) {
+        addShape(new Line(p1, p2, gridColor, lineWidth));
+        addShape(new Line(p2, p4, gridColor, lineWidth));
+        addShape(new Line(p3, p4, gridColor, lineWidth));
+        addShape(new Line(p1, p3, gridColor, lineWidth));
+    }
+
+    /**
+     * Adds a curve highlighting the intersection of the surface with a vertical plane at constant X.
+     *
+     * @param xValue   the X coordinate of the intersecting plane
+     * @param function the mathematical function z = f(x, y)
+     * @param color    the color of the intersection curve
+     * @param width    the line width
+     */
+    public void addXIntersectionCurve(final double xValue, final MathFunction3D function,
+                                      final Color color, final double width) {
+        Point3D prevPoint = null;
+        for (double y = yMin; y <= yMax; y += 0.15) {
+            final double z = function.apply(xValue, y);
+            final Point3D currentPoint = new Point3D(xValue * scale, -z * scale, y * scale);
+            if (prevPoint != null) {
+                addShape(new Line(prevPoint, currentPoint, color, width));
+            }
+            prevPoint = currentPoint;
+        }
+    }
+
+    /**
+     * Adds a curve highlighting the intersection of the surface with a vertical plane at constant Y.
+     *
+     * @param yValue   the Y coordinate of the intersecting plane
+     * @param function the mathematical function z = f(x, y)
+     * @param color    the color of the intersection curve
+     * @param width    the line width
+     */
+    public void addYIntersectionCurve(final double yValue, final MathFunction3D function,
+                                      final Color color, final double width) {
+        Point3D prevPoint = null;
+        for (double x = xMin; x <= xMax; x += 0.15) {
+            final double z = function.apply(x, yValue);
+            final Point3D currentPoint = new Point3D(x * scale, -z * scale, yValue * scale);
+            if (prevPoint != null) {
+                addShape(new Line(prevPoint, currentPoint, color, width));
+            }
+            prevPoint = currentPoint;
+        }
+    }
+
+    /**
+     * Functional interface for 3D mathematical functions of the form z = f(x, y).
+     */
+    @FunctionalInterface
+    public interface MathFunction3D {
+        /**
+         * Computes the Z value for given X and Y coordinates.
+         *
+         * @param x the X coordinate
+         * @param y the Y coordinate
+         * @return the Z value (height) at the given (x, y) position
+         */
+        double apply(double x, double y);
+    }
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java
new file mode 100644 (file)
index 0000000..550e60d
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.launcher;
+
+import eu.svjatoslav.sixth.e3d.examples.OctreeDemo;
+import eu.svjatoslav.sixth.e3d.examples.RainingNumbersDemo;
+import eu.svjatoslav.sixth.e3d.examples.SineHeightmap;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo;
+import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2;
+import eu.svjatoslav.sixth.e3d.examples.essentials.CoordinateSystemDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample;
+import eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo;
+import eu.svjatoslav.sixth.e3d.examples.essentials.WindingOrderDemo;
+import eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark;
+import eu.svjatoslav.sixth.e3d.examples.galaxy_demo.PointCloudDemo;
+import eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo;
+import eu.svjatoslav.sixth.e3d.examples.terrain_demo.TerrainDemo;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+
+/**
+ * Panel containing buttons to launch each demo application.
+ * Displays demos organized into sections with header labels.
+ */
+class ApplicationListPanel extends JPanel {
+    private static final long serialVersionUID = 2012721856427052560L;
+
+    private static final int BUTTON_WIDTH = 160;
+    private static final int GAP = 12;
+    private static final int SECTION_TOP_MARGIN = 16;
+
+    private static final DemoEntry[] ESSENTIALS = {
+            new DemoEntry("Minimal example",
+                    "Minimal example showing a single red box",
+                    new ShowMinimalExample()),
+            new DemoEntry("Winding order",
+                    "Triangle demo for winding order & backface culling",
+                    new ShowWindingOrder()),
+            new DemoEntry("Coordinate system",
+                    "Coordinate system axes: X (red), Y (green), Z (blue)",
+                    new ShowCoordinateSystem()),
+            new DemoEntry("Shape gallery",
+                    "All 3D shapes with orbiting colored light sources",
+                    new ShowShadedShapes()),
+            new DemoEntry("CSG demo",
+                    "Boolean operations: union, subtract, intersect on 3D shapes",
+                    new ShowCSG()),
+    };
+
+    private static final DemoEntry[] OTHER_DEMOS = {
+            new DemoEntry("Volumetric Octree",
+                    "Octree-based rendering with on-demand raytracing",
+                    new ShowOctree()),
+            new DemoEntry("Sine heightmap",
+                    "Two wobbly sine wave surfaces with central sphere",
+                    new ShowSineHeightmap()),
+            new DemoEntry("Math graphs demo",
+                    "Function graphs (sin, cos, tan) rendered in 3D",
+                    new ShowMathGraphs()),
+            new DemoEntry("Point cloud galaxy",
+                    "Spiral galaxy with 10,000 glowing points",
+                    new ShowPointCloud()),
+            new DemoEntry("Raining numbers",
+                    "Numbers falling through 3D space like rain",
+                    new ShowRain()),
+            new DemoEntry("Text editors",
+                    "5x5 grid of 3D text editor components",
+                    new ShowTextEditors()),
+            new DemoEntry("Text editors city",
+                    "3D city of text editor panels as buildings",
+                    new ShowTextEditors2()),
+            new DemoEntry("Game of Life",
+                    "Conway's Game of Life with 3D visualization",
+                    new ShowGameOfLife()),
+            new DemoEntry("Procedural Terrain",
+                    "Procedural mountains with 3 colored light sources",
+                    new ShowTerrainDemo()),
+            new DemoEntry("Graphics benchmark",
+                    "Automated performance measuring FPS across modes",
+                    new ShowGraphicsBenchmark()),
+    };
+
+    ApplicationListPanel() {
+        final GroupLayout layout = new GroupLayout(this);
+        setLayout(layout);
+
+        layout.setAutoCreateGaps(true);
+        layout.setAutoCreateContainerGaps(true);
+
+        final GroupLayout.SequentialGroup verticalGroup = layout.createSequentialGroup();
+        final GroupLayout.ParallelGroup horizontalButtonGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING);
+        final GroupLayout.ParallelGroup horizontalDescGroup = layout.createParallelGroup(GroupLayout.Alignment.LEADING);
+
+        addSectionHeader("Essentials", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, false);
+        addDemoEntries(ESSENTIALS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout);
+
+        addSectionHeader("Demos", verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout, true);
+        addDemoEntries(OTHER_DEMOS, verticalGroup, horizontalButtonGroup, horizontalDescGroup, layout);
+
+        layout.setVerticalGroup(verticalGroup);
+        layout.setHorizontalGroup(layout.createSequentialGroup()
+                .addGroup(horizontalButtonGroup)
+                .addGap(GAP)
+                .addGroup(horizontalDescGroup));
+    }
+
+    /**
+     * Adds a section header label.
+     *
+     * @param title                the section title
+     * @param verticalGroup        the vertical layout group
+     * @param horizontalButtonGroup the horizontal group for buttons
+     * @param horizontalDescGroup   the horizontal group for descriptions
+     * @param layout                the GroupLayout
+     * @param addTopMargin          whether to add extra top margin for visual separation
+     */
+    private void addSectionHeader(final String title,
+                                  final GroupLayout.SequentialGroup verticalGroup,
+                                  final GroupLayout.ParallelGroup horizontalButtonGroup,
+                                  final GroupLayout.ParallelGroup horizontalDescGroup,
+                                  final GroupLayout layout,
+                                  final boolean addTopMargin) {
+        final JLabel headerLabel = new JLabel(title);
+        headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD, 14f));
+        if (addTopMargin) {
+            headerLabel.setBorder(new EmptyBorder(SECTION_TOP_MARGIN, 0, 0, 0));
+        }
+
+        verticalGroup.addComponent(headerLabel);
+        horizontalButtonGroup.addComponent(headerLabel);
+        horizontalDescGroup.addComponent(headerLabel);
+    }
+
+    /**
+     * Adds demo entry buttons to the layout.
+     *
+     * @param entries              the demo entries to add
+     * @param verticalGroup        the vertical layout group
+     * @param horizontalButtonGroup the horizontal group for buttons
+     * @param horizontalDescGroup   the horizontal group for descriptions
+     * @param layout                the GroupLayout
+     */
+    private void addDemoEntries(final DemoEntry[] entries,
+                                final GroupLayout.SequentialGroup verticalGroup,
+                                final GroupLayout.ParallelGroup horizontalButtonGroup,
+                                final GroupLayout.ParallelGroup horizontalDescGroup,
+                                final GroupLayout layout) {
+        for (final DemoEntry entry : entries) {
+            final JButton button = new JButton(entry.action());
+            button.setPreferredSize(new Dimension(BUTTON_WIDTH, button.getPreferredSize().height));
+
+            final JLabel descLabel = new JLabel(entry.description());
+            descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN));
+
+            verticalGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+                    .addComponent(button)
+                    .addComponent(descLabel));
+
+            horizontalButtonGroup.addComponent(button);
+            horizontalDescGroup.addComponent(descLabel);
+        }
+    }
+
+    private record DemoEntry(String name, String description, AbstractAction action) {
+    }
+
+    private static class ShowMinimalExample extends AbstractAction {
+        ShowMinimalExample() {
+            putValue(NAME, "Minimal example");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            MinimalExample.main(null);
+        }
+    }
+
+    private static class ShowWindingOrder extends AbstractAction {
+        ShowWindingOrder() {
+            putValue(NAME, "Winding order");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            WindingOrderDemo.main(null);
+        }
+    }
+
+    private static class ShowTextEditors extends AbstractAction {
+        ShowTextEditors() {
+            putValue(NAME, "Text editors");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            TextEditorDemo.main(null);
+        }
+    }
+
+    private static class ShowTextEditors2 extends AbstractAction {
+        ShowTextEditors2() {
+            putValue(NAME, "Text editors city");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            TextEditorDemo2.main(null);
+        }
+    }
+
+    private static class ShowRain extends AbstractAction {
+        ShowRain() {
+            putValue(NAME, "Raining numbers");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            RainingNumbersDemo.main(null);
+        }
+    }
+
+    private static class ShowPointCloud extends AbstractAction {
+        ShowPointCloud() {
+            putValue(NAME, "Point cloud galaxy");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            PointCloudDemo.main(null);
+        }
+    }
+
+    private static class ShowMathGraphs extends AbstractAction {
+        ShowMathGraphs() {
+            putValue(NAME, "Mathematical graphs");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            MathGraphsDemo.main(null);
+        }
+    }
+
+    private static class ShowSineHeightmap extends AbstractAction {
+        ShowSineHeightmap() {
+            putValue(NAME, "Sine heightmap");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            SineHeightmap.main(null);
+        }
+    }
+
+    private static class ShowOctree extends AbstractAction {
+        ShowOctree() {
+            putValue(NAME, "Volumetric octree");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            OctreeDemo.main(null);
+        }
+    }
+
+    private static class ShowGameOfLife extends AbstractAction {
+        ShowGameOfLife() {
+            putValue(NAME, "Game of Life");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            eu.svjatoslav.sixth.e3d.examples.life_demo.Main.main(null);
+        }
+    }
+
+    private static class ShowShadedShapes extends AbstractAction {
+        ShowShadedShapes() {
+            putValue(NAME, "Shape gallery");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            ShapeGalleryDemo.main(null);
+        }
+    }
+
+    private static class ShowCoordinateSystem extends AbstractAction {
+        ShowCoordinateSystem() {
+            putValue(NAME, "Coordinate system");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            CoordinateSystemDemo.main(null);
+        }
+    }
+
+    private static class ShowTerrainDemo extends AbstractAction {
+        ShowTerrainDemo() {
+            putValue(NAME, "Procedural terrain");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            TerrainDemo.main(null);
+        }
+    }
+
+    private static class ShowGraphicsBenchmark extends AbstractAction {
+        ShowGraphicsBenchmark() {
+            putValue(NAME, "Graphics benchmark");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            GraphicsBenchmark.main(null);
+        }
+    }
+
+    private static class ShowCSG extends AbstractAction {
+        ShowCSG() {
+            putValue(NAME, "CSG demo");
+        }
+
+        @Override
+        public void actionPerformed(final ActionEvent e) {
+            CSGDemo.main(null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/Main.java
new file mode 100755 (executable)
index 0000000..c04d42e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+*/
+
+package eu.svjatoslav.sixth.e3d.examples.launcher;
+
+import javax.swing.*;
+import java.awt.*;
+
+import static java.awt.BorderLayout.CENTER;
+import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
+
+
+/**
+ * Launcher application for all Sixth 3D demos.
+ * Displays a window with buttons to launch each demo application.
+ */
+class Main {
+
+    /**
+     * Entry point for the demo launcher.
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        buildAndShowGuiWindow();
+    }
+
+    /**
+     * Builds and shows the main window of the application.
+     */
+    private static void buildAndShowGuiWindow() {
+        JFrame frame = new JFrame("Sixth 3D engine demos");
+
+        // Keep application running until last frame is closed.
+        frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+        frame.getContentPane().setLayout(new BorderLayout());
+        frame.getContentPane().add(new ApplicationListPanel(), CENTER);
+
+        frame.pack();
+        frame.setMinimumSize(frame.getSize());
+
+        frame.setLocationRelativeTo(null); // center frame on screen
+        frame.setVisible(true);
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/package-info.java
new file mode 100644 (file)
index 0000000..560327b
--- /dev/null
@@ -0,0 +1,15 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Demo launcher application.
+ *
+ * <p>Provides a GUI window with buttons to launch each demo application.
+ * The main entry point is {@link eu.svjatoslav.sixth.e3d.examples.launcher.Main}.</p>
+ *
+ * @see eu.svjatoslav.sixth.e3d.examples.launcher.Main
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.launcher;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java
new file mode 100755 (executable)
index 0000000..c9be293
--- /dev/null
@@ -0,0 +1,173 @@
+package eu.svjatoslav.sixth.e3d.examples.life_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+
+/**
+ * Represents a single cell in Conway's Game of Life matrix.
+ * Each cell can be active (alive) or inactive (dead), and responds to mouse
+ * interactions for toggling its state. The cell changes color based on its
+ * state and whether the mouse is hovering over it.
+ */
+class Cell extends AbstractCompositeShape implements
+        MouseInteractionController {
+
+    /**
+     * Visual size of each cell in world units.
+     */
+    static final int SIZE = 20;
+    /**
+     * Color of the active cell (R, G, B, A)
+     */
+    private static final Color ACTIVE_COLOR = new Color("A8FF");
+    /**
+     * Color of the active cell (R, G, B, A) while the mouse is over it.
+     */
+    private static final Color ACTIVE_COLOR_MOUSE_OVER = new Color("F9FF");
+    /**
+     * Color of the inactive cell (R, G, B, A)
+     */
+    private static final Color INACTIVE_COLOR = new Color("55F8");
+    /**
+     * Color of the inactive cell (R, G, B, A) while mouse is over it.
+     */
+    private static final Color INACTIVE_COLOR_MOUSE_OVER = new Color("77F8");
+    /**
+     * A placeholder variable to help in next generation computation. Indicates
+     * whether cell is going to survive within next generation.
+     */
+    public boolean survives;
+    /**
+     * Indicates whether cell is currently active
+     */
+    private boolean active;
+    /**
+     * Indicates whether mouse pointer is currently over this cell.
+     */
+    private boolean isMouseOver = false;
+
+    /**
+     * Constructs a cell at the specified center position.
+     *
+     * @param center the 3D position of the cell center
+     */
+    public Cell(final Point3D center) {
+        super(center);
+
+        createCellShape();
+
+        // enable receiving of mouse events
+        setMouseInteractionController(this);
+    }
+
+    /**
+     * Creates the visual representation of the cell as two triangles.
+     */
+    private void createCellShape() {
+        final double halfSize = SIZE / 2f;
+
+        // define 4 points corresponding to cell borders
+        final Point3D p1 = new Point3D(-halfSize, 0, -halfSize);
+
+        final Point3D p2 = new Point3D(+halfSize, 0, -halfSize);
+
+        final Point3D p3 = new Point3D(+halfSize, 0, +halfSize);
+
+        final Point3D p4 = new Point3D(-halfSize, 0, +halfSize);
+
+        // connect 4 points with 2 polygons
+        addShape(new SolidPolygon(p1, p2, p3, computeCellColor()));
+        addShape(new SolidPolygon(p1, p4, p3, computeCellColor()));
+    }
+
+    /**
+     * Computes the cell color based on active state and mouse hover.
+     *
+     * @return the appropriate color for the current state
+     */
+    private Color computeCellColor() {
+        if (active)
+            if (isMouseOver)
+                return ACTIVE_COLOR_MOUSE_OVER;
+            else
+                return ACTIVE_COLOR;
+        else if (isMouseOver)
+            return INACTIVE_COLOR_MOUSE_OVER;
+        else
+            return INACTIVE_COLOR;
+    }
+
+    /**
+     * Returns whether this cell is currently active (alive).
+     *
+     * @return true if the cell is active
+     */
+    public boolean isActive() {
+        return active;
+    }
+
+    /**
+     * Sets the active state of this cell and updates its color.
+     *
+     * @param active true to make the cell active, false for inactive
+     */
+    public void setActive(final boolean active) {
+        this.active = active;
+        updateColor();
+    }
+
+    /**
+     * Handles mouse click by toggling the cell state.
+     *
+     * @param button the mouse button that was clicked
+     * @return true to indicate the event was consumed
+     */
+    @Override
+    public boolean mouseClicked(int button) {
+        setActive(!isActive());
+        return true;
+    }
+
+    /**
+     * Handles mouse entering the cell area by highlighting it.
+     *
+     * @return true to indicate the event was consumed
+     */
+    @Override
+    public boolean mouseEntered() {
+        setMouseOver(true);
+        return true;
+    }
+
+    /**
+     * Handles mouse exiting the cell area by removing highlight.
+     *
+     * @return true to indicate the event was consumed
+     */
+    @Override
+    public boolean mouseExited() {
+        setMouseOver(false);
+        return true;
+    }
+
+    /**
+     * Sets the mouse-over state and updates the cell color.
+     *
+     * @param isMouseOver true if mouse is over the cell
+     */
+    private void setMouseOver(final boolean isMouseOver) {
+        this.isMouseOver = isMouseOver;
+        updateColor();
+    }
+
+    /**
+     * Updates the cell's visual color to match its current state.
+     */
+    private void updateColor() {
+        setColor(computeCellColor());
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java
new file mode 100644 (file)
index 0000000..d60dad7
--- /dev/null
@@ -0,0 +1,173 @@
+package eu.svjatoslav.sixth.e3d.examples.life_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.geometry.Rectangle;
+import eu.svjatoslav.sixth.e3d.gui.TextPointer;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker;
+import eu.svjatoslav.sixth.e3d.math.Transform;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D;
+
+import java.awt.event.KeyEvent;
+
+
+/**
+ * Main entry point for Conway's Game of Life 3D demo.
+ * Creates a 30x30 cell matrix where cells evolve based on Conway's rules.
+ * The user can interact with cells by clicking to toggle their state.
+ *
+ * <p>Key controls:
+ * <ul>
+ *   <li>Space - Evolve one generation</li>
+ *   <li>Enter - Evolve with history (leaves stars marking previous positions)</li>
+ *   <li>C - Clear the matrix</li>
+ * </ul>
+ */
+public class Main extends WorldNavigationUserInputTracker {
+
+    /**
+     * The game of life matrix, centered at the origin.
+     */
+    private static final Matrix MATRIX = new Matrix(
+            origin() // position matrix in the center of the scene
+    );
+
+    /**
+     * Creates a new Main instance for the Game of Life demo.
+     */
+    public Main() {
+    }
+
+    /**
+     * Entry point for the Game of Life demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        new Main().run();
+    }
+
+    /**
+     * Handle keyboard input.
+     */
+    @Override
+    public boolean keyPressed(final KeyEvent event, final ViewPanel viewPanel) {
+        switch (event.getKeyChar()) {
+            case ' ': // space key
+                MATRIX.evolve(false);
+                break;
+            case 10: // ENTER
+                MATRIX.evolve(true);
+                break;
+            case 'c': // reset matrix
+                MATRIX.clear();
+                break;
+            default:
+                return super.keyPressed(event, viewPanel);
+        }
+        return true;
+    }
+
+    /**
+     * Initializes and displays the Game of Life demo.
+     */
+    private void run() {
+
+        // create application frame visible to the user
+        final ViewFrame viewFrame = new ViewFrame("Game of Life");
+        final ViewPanel viewPanel = viewFrame.getViewPanel();
+
+        viewPanel.getCamera().getTransform().set(855.83, -174.40, -31.46, 1.58, -0.02, -0.00);
+
+        final ShapeCollection shapeCollection = viewPanel.getRootShapeCollection();
+
+        // add matrix
+        shapeCollection.addShape(MATRIX);
+
+        // add help panel explaining rules and controls
+        shapeCollection.addShape(createHelpPanel());
+
+        // add wire-frame grid (optional)
+        shapeCollection.addShape(createGrid());
+
+        // enable receiving of keyboard events
+        viewPanel.getKeyboardFocusStack().pushFocusOwner(this);
+
+        // Done! World is built. So ensure screen is updated too.
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Creates a pink wire-frame grid below the matrix for decorative purposes.
+     *
+     * @return a Grid2D positioned below the game matrix
+     */
+    private Grid2D createGrid() {
+        return new Grid2D(
+                Transform.fromAngles(0, 100, 0, 0, Math.PI / 2, 0),
+
+                new Rectangle(800),
+
+                5, 5,
+
+                new LineAppearance(3,
+                        hex("FF000050")
+                )
+        );
+    }
+
+    /**
+     * Creates a help panel displaying game rules and controls.
+     * The panel is positioned to the right of the matrix and faces toward the viewer.
+     *
+     * @return a TextCanvas with help information
+     */
+    private TextCanvas createHelpPanel() {
+        final String helpText =
+                "=== CONWAY'S GAME OF LIFE ===\n" +
+                        "\n" +
+                        "RULES:\n" +
+                        "- Live cell with 2-3 neighbors survives\n" +
+                        "- Dead cell with 3 neighbors becomes alive\n" +
+                        "- All other cells die or stay dead\n" +
+                        "\n" +
+                        "KEYBOARD:\n" +
+                        "Space  - Evolve one generation\n" +
+                        "Enter  - Evolve with history trail\n" +
+                        "C      - Clear the matrix\n" +
+                        "Arrows - Move camera\n" +
+                        "\n" +
+                        "MOUSE:\n" +
+                        "Click  - Toggle cell state\n" +
+                        "Hover  - Highlight cell";
+
+        final Transform location = Transform.fromAngles(
+                point(500, -80, 0),
+                -Math.PI / 2,
+                0
+        );
+
+        final TextPointer dimensions = new TextPointer(16, 45);
+
+        final TextCanvas panel = new TextCanvas(
+                location,
+                dimensions,
+                Color.WHITE,
+                hex("000050C8")
+        );
+
+        panel.setText(helpText);
+
+        return panel;
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Matrix.java
new file mode 100644 (file)
index 0000000..adf0a8a
--- /dev/null
@@ -0,0 +1,201 @@
+package eu.svjatoslav.sixth.e3d.examples.life_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.SubShape;
+
+/**
+ * Represents the 2D game board for Conway's Game of Life.
+ * Contains a 30x30 grid of cells that can evolve according to Conway's rules.
+ * Supports optional history tracking where stars mark previously active cells.
+ *
+ * @see Cell
+ * @see Star
+ */
+class Matrix extends AbstractCompositeShape {
+
+    /**
+     * Empty space between adjacent cells.
+     */
+    private static final int BORDER = 5;
+
+    /**
+     * Number of cells along each dimension (30x30 grid).
+     */
+    private static final int SIZE = 30;
+
+    /**
+     * Group ID for history-tracking stars.
+     */
+    private static final String GROUP_STARS = "stars";
+
+    /**
+     * Group ID for the cell surface.
+     */
+    private static final String GROUP_SURFACE = "surface";
+
+    /**
+     * 2D array of cells forming the game board.
+     */
+    private final Cell[][] cells = new Cell[SIZE][];
+
+    /**
+     * Constructs a game matrix at the specified location.
+     *
+     * @param location the center position of the matrix in 3D space
+     */
+    public Matrix(final Point3D location) {
+        super(location);
+
+
+        for (int x = 0; x < SIZE; x++) {
+            cells[x] = new Cell[SIZE];
+
+            // init Y row
+            for (int z = 0; z < SIZE; z++) {
+                // create cell and register it
+                final Cell cell = new Cell(getCellLocation(x, z));
+                cells[x][z] = cell;
+                addShape(cell);
+            }
+        }
+
+        setGroupForUngrouped(GROUP_SURFACE);
+    }
+
+    /**
+     * Clears all cells to inactive state and removes history stars.
+     */
+    public void clear() {
+
+        // mark every cell as inactive
+        for (int x = 0; x < SIZE; x++)
+            for (int y = 0; y < SIZE; y++)
+                cells[x][y].setActive(false);
+
+        // remove history stars
+        removeGroup(GROUP_STARS);
+    }
+
+    /**
+     * Computes the next generation using Conway's Game of Life rules.
+     * Each cell's survival is determined by its number of alive neighbors.
+     */
+    private void computeSurvivedCells() {
+
+        for (int y = 0; y < SIZE; y++)
+            for (int x = 0; x < SIZE; x++)
+                processCell(x, y);
+
+        for (int y = 0; y < SIZE; y++)
+            for (int x = 0; x < SIZE; x++)
+                cells[x][y].setActive(cells[x][y].survives);
+
+    }
+
+    /**
+     * Processes a single cell to determine if it survives to the next generation.
+     *
+     * @param x the X coordinate of the cell
+     * @param y the Y coordinate of the cell
+     */
+    private void processCell(int x, int y) {
+        int aliveNeighbours = countNeighbours(x, y);
+
+        if (cells[x][y].isActive()) {
+            cells[x][y].survives = ((aliveNeighbours == 2) || (aliveNeighbours == 3));
+        } else {
+            cells[x][y].survives = aliveNeighbours == 3;
+        }
+    }
+
+    /**
+     * Counts the number of alive neighbors around the specified cell.
+     *
+     * @param x the X coordinate of the cell
+     * @param y the Y coordinate of the cell
+     * @return the count of alive neighboring cells
+     */
+    private int countNeighbours(int x, int y) {
+        int result = 0;
+        for (int ny = y - 1; ny <= y + 1; ny++)
+            for (int nx = x - 1; nx <= x + 1; nx++)
+                if (isCellAlive(nx, ny)) result++;
+
+        if (isCellAlive(x, y)) result--;
+
+        return result;
+    }
+
+    /**
+     * Checks if the cell at the specified coordinates is alive.
+     *
+     * @param x the X coordinate of the cell
+     * @param y the Y coordinate of the cell
+     * @return true if the cell is within bounds and active
+     */
+    private boolean isCellAlive(int x, int y) {
+        if (x < 0) return false;
+        if (x >= SIZE) return false;
+        if (y < 0) return false;
+        if (y >= SIZE) return false;
+        return cells[x][y].isActive();
+    }
+
+    /**
+     * Evolves the matrix by one generation.
+     *
+     * @param preserveHistory if true, places stars at positions of previously active cells
+     */
+    public void evolve(final boolean preserveHistory) {
+        if (preserveHistory)
+            markActiveCells();
+
+        shiftStarsUp();
+
+        computeSurvivedCells();
+    }
+
+    /**
+     * Computes the world position of a cell given its grid coordinates.
+     *
+     * @param x the X grid coordinate
+     * @param z the Z grid coordinate
+     * @return the 3D world position of the cell center
+     */
+    private Point3D getCellLocation(final int x, final int z) {
+        final int shift = -((SIZE / 2) * (Cell.SIZE + BORDER));
+
+        return new Point3D(
+                (x * (Cell.SIZE + BORDER)) + shift,
+                0,
+                (z * (Cell.SIZE + BORDER)) + shift);
+    }
+
+    /**
+     * Marks all currently active cells with stars for history tracking.
+     * Stars are added to the GROUP_STARS group.
+     */
+    private void markActiveCells() {
+        // mark survived cells
+        for (int x = 0; x < SIZE; x++)
+            for (int y = 0; y < SIZE; y++)
+                if (cells[x][y].isActive())
+                    addShape(new Star(getCellLocation(x, y)));
+
+        setGroupForUngrouped(GROUP_STARS);
+    }
+
+    /**
+     * Shifts all history tracking stars upward to create a trailing effect.
+     * Also invalidates the Matrix's cached bounding box so frustum culling
+     * uses the updated star positions.
+     */
+    private void shiftStarsUp() {
+        for (final SubShape subShape : getGroup(GROUP_STARS)) {
+            final Star star = (Star) subShape.getShape();
+            star.translate(0, -10, 0);
+        }
+        invalidateBounds();
+    }
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Star.java
new file mode 100644 (file)
index 0000000..f3303db
--- /dev/null
@@ -0,0 +1,52 @@
+package eu.svjatoslav.sixth.e3d.examples.life_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.GlowingPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a glowing star marker in the Game of Life history trail.
+ * When cells evolve with history enabled, stars are placed at previous
+ * active cell positions to create a visual trail of the game's evolution.
+ * Uses a limited color palette to enable texture reuse optimization.
+ */
+class Star extends GlowingPoint {
+    /** Visual size of each star. */
+    public static final int STAR_SIZE = 10;
+
+    /** Number of unique star colors for texture reuse optimization. */
+    public static final int UNIQUE_STARS_COUNT = 30;
+
+    /** Shared pool of star colors for texture reuse. */
+    private static final List<Color> uniqueStarColors = new ArrayList<>();
+
+    /*
+     * Initializes a limited set of precomputed star colors.
+     * This optimization allows the Sixth 3D engine to reuse textures
+     * for stars with identical colors, saving memory when many stars are present.
+     */
+    static {
+        for (int i = 0; i < UNIQUE_STARS_COUNT; i++)
+            uniqueStarColors.add(
+                    new Color(
+                            Math.random() + 0.5,
+                            Math.random() + 0.5,
+                            Math.random() + 0.5,
+                            255));
+    }
+
+    /**
+     * Constructs a star at the specified location with a random color from the palette.
+     * @param location the 3D position of the star
+     */
+    public Star(Point3D location) {
+        super(location,
+                STAR_SIZE,
+                uniqueStarColors.get((int) (Math.random() * uniqueStarColors.size())) // pick random pre-generated color
+        );
+    }
+
+}
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/package-info.java
new file mode 100644 (file)
index 0000000..423b24b
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+/**
+ * Conway's Game of Life 3D demo.
+ *
+ * <p>Implements a 30x30 cell matrix where cells evolve based on Conway's rules.
+ * Users can interact by clicking to toggle cell states. The simulation runs
+ * in a 3D view with camera navigation.</p>
+ *
+ * <p>Key classes:</p>
+ * <ul>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.life_demo.Main} - Demo entry point</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.life_demo.Cell} - Individual cell representation</li>
+ *   <li>{@link eu.svjatoslav.sixth.e3d.examples.life_demo.Matrix} - The cell grid</li>
+ * </ul>
+ *
+ * @see eu.svjatoslav.sixth.e3d.examples.life_demo.Main
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.life_demo;
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/package-info.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/package-info.java
new file mode 100755 (executable)
index 0000000..0c166bd
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko. 
+ * This project is released under Creative Commons Zero (CC0) license.
+ *
+*/
+
+/**
+ * Example applications that make use of the API.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples;
+
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/DiamondSquareTerrain.java
new file mode 100644 (file)
index 0000000..47a96aa
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.math.DiamondSquare;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+
+/**
+ * A procedurally generated terrain mesh using the diamond-square algorithm.
+ * Creates a grid of triangular polygons with height variation for mountains.
+ */
+public class DiamondSquareTerrain extends AbstractCompositeShape {
+
+    private static final double TERRAIN_SIZE = 800.0;
+
+    private final double[][] heightmap;
+    private final int gridSize;
+
+    /**
+     * Creates a diamond-square terrain with the specified parameters.
+     *
+     * @param gridSize  the size of the heightmap grid (must be 2^n + 1)
+     * @param minHeight the minimum terrain height
+     * @param maxHeight the maximum terrain height
+     * @param seed      random seed for reproducible terrain
+     */
+    public DiamondSquareTerrain(final int gridSize, final double minHeight, final double maxHeight, final long seed) {
+        super();
+
+        this.gridSize = gridSize;
+        this.heightmap = DiamondSquare.generateMap(gridSize, minHeight, maxHeight, seed);
+        createPolygons(gridSize, heightmap);
+        setBackfaceCulling(true);
+    }
+
+    /**
+     * Returns the terrain height at the specified world XZ coordinates.
+     *
+     * @param worldX the world X coordinate
+     * @param worldZ the world Z coordinate
+     * @return the terrain height at that position, or 0 if outside terrain bounds
+     */
+    public double getHeightAt(final double worldX, final double worldZ) {
+        final double cellSize = TERRAIN_SIZE / (gridSize - 1);
+        final double offsetX = -TERRAIN_SIZE / 2;
+        final double offsetZ = -TERRAIN_SIZE / 2;
+
+        final int gridX = (int) Math.round((worldX - offsetX) / cellSize);
+        final int gridZ = (int) Math.round((worldZ - offsetZ) / cellSize);
+
+        if (gridX < 0 || gridX >= gridSize || gridZ < 0 || gridZ >= gridSize) {
+            return 0;
+        }
+
+        return heightmap[gridZ][gridX];
+    }
+
+    /**
+     * Creates triangular polygons from the heightmap.
+     */
+    private void createPolygons(final int gridSize, final double[][] heightmap) {
+        final double cellSize = TERRAIN_SIZE / (gridSize - 1);
+        final double offsetX = -TERRAIN_SIZE / 2;
+        final double offsetZ = -TERRAIN_SIZE / 2;
+
+        for (int z = 0; z < gridSize - 1; z++) {
+            for (int x = 0; x < gridSize - 1; x++) {
+                final double x0 = offsetX + x * cellSize;
+                final double x1 = offsetX + (x + 1) * cellSize;
+                final double z0 = offsetZ + z * cellSize;
+                final double z1 = offsetZ + (z + 1) * cellSize;
+
+                final double y00 = heightmap[z][x];
+                final double y10 = heightmap[z][x + 1];
+                final double y01 = heightmap[z + 1][x];
+                final double y11 = heightmap[z + 1][x + 1];
+
+                final Color color = new Color(10, 10, 10);
+
+                final Point3D p00 = new Point3D(x0, y00, z0);
+                final Point3D p10 = new Point3D(x1, y10, z0);
+                final Point3D p01 = new Point3D(x0, y01, z1);
+                final Point3D p11 = new Point3D(x1, y11, z1);
+
+                final SolidPolygon tri1 = new SolidPolygon(p00, p10, p01, color);
+                final SolidPolygon tri2 = new SolidPolygon(p10, p11, p01, color);
+
+                tri1.setShadingEnabled(true);
+                tri2.setShadingEnabled(true);
+
+                addShape(tri1);
+                addShape(tri2);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/FractalTree.java
new file mode 100644 (file)
index 0000000..8b78e72
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCylinder;
+
+/**
+ * A simple tree trunk made of a single cylinder.
+ * Positioned to extend through the full terrain height range.
+ *
+ * @see TerrainDemo
+ * @see SolidPolygonCylinder
+ */
+public class FractalTree extends AbstractCompositeShape {
+
+    private static final Color BROWN = new Color(139, 90, 43);
+
+    /**
+     * Creates a tree trunk at the specified base position.
+     *
+     * @param basePosition the position of the trunk base (on the terrain surface)
+     * @param trunkHeight  the height of the trunk
+     * @param trunkRadius  the radius of the trunk base
+     */
+    public FractalTree(final Point3D basePosition, final double trunkHeight,
+                       final double trunkRadius) {
+        super();
+
+        // In Sixth 3D, Y increases downward (screen-space coordinates).
+        // The trunk extends upward from basePosition, so the top has a smaller Y value.
+        final Point3D topPosition = new Point3D(
+                basePosition.x,
+                basePosition.y - trunkHeight,
+                basePosition.z
+        );
+
+        final SolidPolygonCylinder trunk = new SolidPolygonCylinder(
+                basePosition,    // start (bottom of trunk)
+                topPosition,     // end (top of trunk)
+                trunkRadius,
+                8,
+                BROWN);
+        trunk.setShadingEnabled(true);
+
+        addShape(trunk);
+        setBackfaceCulling(true);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java
new file mode 100644 (file)
index 0000000..1bf03ec
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Sixth 3D engine demos. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+
+package eu.svjatoslav.sixth.e3d.examples.terrain_demo;
+
+import eu.svjatoslav.sixth.e3d.geometry.Point3D;
+import eu.svjatoslav.sixth.e3d.gui.ViewFrame;
+import eu.svjatoslav.sixth.e3d.gui.ViewPanel;
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin;
+import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point;
+import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex;
+import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource;
+import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager;
+import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.LightSourceMarker;
+
+/**
+ * Demo showing a procedurally generated mountain landscape using the diamond-square algorithm.
+ * Three colored light sources (warm orange, cool cyan, neutral white) illuminate the terrain
+ * from above, demonstrating dynamic flat shading. A fractal tree stands at the center of the terrain.
+ */
+public class TerrainDemo {
+
+    /**
+     * Entry point for the terrain demo.
+     *
+     * @param args command line arguments (ignored)
+     */
+    public static void main(final String[] args) {
+        final ViewFrame viewFrame = new ViewFrame("Procedural terrain");
+        ViewPanel viewPanel = viewFrame.getViewPanel();
+
+        viewPanel.getCamera().getTransform().set(-76.43, -72.05, -159.08, -0.64, -0.46, -0.00);
+
+        final ShapeCollection shapes = viewPanel.getRootShapeCollection();
+
+        setLights(viewFrame, shapes);
+
+        final DiamondSquareTerrain terrain = new DiamondSquareTerrain(129, 0.0, 100.0, 42);
+        shapes.addShape(terrain);
+
+        // Get terrain height at center and place tree trunk on it
+        final double terrainHeight = terrain.getHeightAt(0, 0);
+        shapes.addShape(new FractalTree(point(0, terrainHeight, 0), 30, 6));
+
+        viewPanel.repaintDuringNextViewUpdate();
+    }
+
+    /**
+     * Sets up the lighting for the terrain scene.
+     *
+     * @param viewFrame the view frame containing the lighting manager
+     * @param shapes    the shape collection to add light source markers to
+     */
+    private static void setLights(ViewFrame viewFrame, ShapeCollection shapes) {
+        LightingManager lightingManager = viewFrame.getViewPanel().getLightingManager();
+        lightingManager.setAmbientLight(hex("FA9664FF"));
+
+        final LightSource warmLight = new LightSource(
+                point(-400, -500, 0),
+                hex("FFB464FF"),
+                190.0
+        );
+        final LightSource coolLight = new LightSource(
+                point(400, -500, 0),
+                hex("64C8FFFF"),
+                220.0
+        );
+        final LightSource neutralLight = new LightSource(
+                point(0, -600, 300),
+                hex("FFFFFFFF"),
+                250.0
+        );
+
+        lightingManager.addLight(warmLight);
+        lightingManager.addLight(coolLight);
+        lightingManager.addLight(neutralLight);
+
+        shapes.addShape(new LightSourceMarker(warmLight.getPosition(), hex("FFB464FF")));
+        shapes.addShape(new LightSourceMarker(coolLight.getPosition(), hex("64C8FFFF")));
+        shapes.addShape(new LightSourceMarker(neutralLight.getPosition(), hex("FFFFFFFF")));
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/demo.txt b/src/main/resources/demo.txt
new file mode 100644 (file)
index 0000000..fffb8fe
--- /dev/null
@@ -0,0 +1,505 @@
+microcode: microcode updated early to revision 0xe2, date = 2020-07-14
+Linux version 5.10.0-6-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #1 SMP Debian 5.10.28-1 (2021-04-09)
+Command line: BOOT_IMAGE=/vmlinuz-5.10.0-6-amd64 root=/dev/mapper/main-root ro quiet
+x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
+x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
+x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
+x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'
+x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'
+x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256
+x86/fpu: xstate_offset[3]:  832, xstate_sizes[3]:   64
+x86/fpu: xstate_offset[4]:  896, xstate_sizes[4]:   64
+x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
+BIOS-provided physical RAM map:
+BIOS-e820: [mem 0x0000000000000000-0x000000000009e7ff] usable
+BIOS-e820: [mem 0x000000000009e800-0x000000000009ffff] reserved
+BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
+BIOS-e820: [mem 0x0000000000100000-0x000000005f692fff] usable
+BIOS-e820: [mem 0x000000005f693000-0x000000005f693fff] ACPI NVS
+BIOS-e820: [mem 0x000000005f694000-0x000000005f6ddfff] reserved
+BIOS-e820: [mem 0x000000005f6de000-0x000000005f789fff] usable
+BIOS-e820: [mem 0x000000005f78a000-0x0000000060089fff] reserved
+BIOS-e820: [mem 0x000000006008a000-0x000000007558efff] usable
+BIOS-e820: [mem 0x000000007558f000-0x0000000075f7efff] reserved
+BIOS-e820: [mem 0x0000000075f7f000-0x0000000077f7efff] ACPI NVS
+BIOS-e820: [mem 0x0000000077f7f000-0x0000000077ffefff] ACPI data
+BIOS-e820: [mem 0x0000000077fff000-0x0000000077ffffff] usable
+BIOS-e820: [mem 0x0000000078000000-0x00000000780fffff] reserved
+BIOS-e820: [mem 0x0000000079000000-0x000000007e7fffff] reserved
+BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
+BIOS-e820: [mem 0x00000000fd000000-0x00000000fe7fffff] reserved
+BIOS-e820: [mem 0x00000000feb00000-0x00000000feb03fff] reserved
+BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
+BIOS-e820: [mem 0x00000000fed00000-0x00000000fed00fff] reserved
+BIOS-e820: [mem 0x00000000fed10000-0x00000000fed19fff] reserved
+BIOS-e820: [mem 0x00000000fed84000-0x00000000fed84fff] reserved
+BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
+BIOS-e820: [mem 0x00000000ffa00000-0x00000000ffffffff] reserved
+BIOS-e820: [mem 0x0000000100000000-0x00000004807fffff] usable
+NX (Execute Disable) protection: active
+SMBIOS 2.8 present.
+DMI: LENOVO 80NV/Allsparks 5A, BIOS CDCN53WW 09/19/2016
+tsc: Detected 2600.000 MHz processor
+tsc: Detected 2599.992 MHz TSC
+e820: update [mem 0x00000000-0x00000fff] usable ==> reserved
+e820: remove [mem 0x000a0000-0x000fffff] usable
+last_pfn = 0x480800 max_arch_pfn = 0x400000000
+MTRR default type: uncachable
+MTRR fixed ranges enabled:
+  00000-9FFFF write-back
+  A0000-BFFFF uncachable
+  C0000-FFFFF write-protect
+MTRR variable ranges enabled:
+  0 base 0000000000 mask 7800000000 write-back
+  1 base 0078000000 mask 7FF8000000 uncachable
+  2 base 0080000000 mask 7F80000000 uncachable
+  3 disabled
+  4 disabled
+  5 disabled
+  6 disabled
+  7 disabled
+  8 disabled
+  9 disabled
+x86/PAT: Configuration [0-7]: WB  WC  UC- UC  WB  WP  UC- WT
+e820: update [mem 0x78000000-0xffffffff] usable ==> reserved
+last_pfn = 0x78000 max_arch_pfn = 0x400000000
+found SMP MP-table at [mem 0x000fe1b0-0x000fe1bf]
+ACPI: Early table checksum verification disabled
+ACPI: RSDP 0x00000000000FE020 000024 (v02 LENOVO)
+ACPI: XSDT 0x0000000077FC0188 0000D4 (v01 LENOVO CB-01    00000001      01000013)
+ACPI: FACP 0x0000000077FEE000 0000F4 (v05 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: DSDT 0x0000000077FCB000 01E97D (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: FACS 0x0000000077F60000 000040
+ACPI: UEFI 0x0000000077FFD000 000236 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: UEFI 0x0000000077FFC000 000042 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: MSDM 0x0000000077FFB000 000055 (v03 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FF5000 005104 (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: DBGP 0x0000000077FF4000 000034 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: DBG2 0x0000000077FF3000 000061 (v00 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FF2000 00077C (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: ASF! 0x0000000077FF1000 0000A5 (v32 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: ASPT 0x0000000077FF0000 000034 (v07 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: BOOT 0x0000000077FEF000 000028 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: HPET 0x0000000077FED000 000038 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: LPIT 0x0000000077FEC000 000094 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: APIC 0x0000000077FEB000 0000BC (v03 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: MCFG 0x0000000077FEA000 00003C (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FCA000 0002D4 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FC9000 00019A (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FC8000 0003F4 (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FC7000 000E58 (v02 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: SSDT 0x0000000077FC2000 004354 (v01 Insyde NVOP     00001000 INTL 20130927)
+ACPI: DMAR 0x0000000077FC1000 0000A8 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: FPDT 0x0000000077FBF000 000044 (v01 LENOVO CB-01    00000001 ACPI 00040000)
+ACPI: Reserving FACP table memory at [mem 0x77fee000-0x77fee0f3]
+ACPI: Reserving DSDT table memory at [mem 0x77fcb000-0x77fe997c]
+ACPI: Reserving FACS table memory at [mem 0x77f60000-0x77f6003f]
+ACPI: Reserving UEFI table memory at [mem 0x77ffd000-0x77ffd235]
+ACPI: Reserving UEFI table memory at [mem 0x77ffc000-0x77ffc041]
+ACPI: Reserving MSDM table memory at [mem 0x77ffb000-0x77ffb054]
+ACPI: Reserving SSDT table memory at [mem 0x77ff5000-0x77ffa103]
+ACPI: Reserving DBGP table memory at [mem 0x77ff4000-0x77ff4033]
+ACPI: Reserving DBG2 table memory at [mem 0x77ff3000-0x77ff3060]
+ACPI: Reserving SSDT table memory at [mem 0x77ff2000-0x77ff277b]
+ACPI: Reserving ASF! table memory at [mem 0x77ff1000-0x77ff10a4]
+ACPI: Reserving ASPT table memory at [mem 0x77ff0000-0x77ff0033]
+ACPI: Reserving BOOT table memory at [mem 0x77fef000-0x77fef027]
+ACPI: Reserving HPET table memory at [mem 0x77fed000-0x77fed037]
+ACPI: Reserving LPIT table memory at [mem 0x77fec000-0x77fec093]
+ACPI: Reserving APIC table memory at [mem 0x77feb000-0x77feb0bb]
+ACPI: Reserving MCFG table memory at [mem 0x77fea000-0x77fea03b]
+ACPI: Reserving SSDT table memory at [mem 0x77fca000-0x77fca2d3]
+ACPI: Reserving SSDT table memory at [mem 0x77fc9000-0x77fc9199]
+ACPI: Reserving SSDT table memory at [mem 0x77fc8000-0x77fc83f3]
+ACPI: Reserving SSDT table memory at [mem 0x77fc7000-0x77fc7e57]
+ACPI: Reserving SSDT table memory at [mem 0x77fc2000-0x77fc6353]
+ACPI: Reserving DMAR table memory at [mem 0x77fc1000-0x77fc10a7]
+ACPI: Reserving FPDT table memory at [mem 0x77fbf000-0x77fbf043]
+Using GB pages for direct mapping
+RAMDISK: [mem 0x2efdd000-0x337e5fff]
+ACPI: Local APIC address 0xfee00000
+No NUMA configuration found
+Faking a node at [mem 0x0000000000000000-0x00000004807fffff]
+NODE_DATA(0) allocated [mem 0x4807d6000-0x4807fffff]
+Zone ranges:
+  DMA      [mem 0x0000000000001000-0x0000000000ffffff]
+  DMA32    [mem 0x0000000001000000-0x00000000ffffffff]
+  Normal   [mem 0x0000000100000000-0x00000004807fffff]
+  Device   empty
+Movable zone start for each node
+Early memory node ranges
+  node   0: [mem 0x0000000000001000-0x000000000009dfff]
+  node   0: [mem 0x0000000000100000-0x000000005f692fff]
+  node   0: [mem 0x000000005f6de000-0x000000005f789fff]
+  node   0: [mem 0x000000006008a000-0x000000007558efff]
+  node   0: [mem 0x0000000077fff000-0x0000000077ffffff]
+  node   0: [mem 0x0000000100000000-0x00000004807fffff]
+Initmem setup node 0 [mem 0x0000000000001000-0x00000004807fffff]
+On node 0 totalpages: 4150242
+  DMA zone: 64 pages used for memmap
+  DMA zone: 21 pages reserved
+  DMA zone: 3997 pages, LIFO batch:0
+  DMA zone: 28771 pages in unavailable ranges
+  DMA32 zone: 7410 pages used for memmap
+  DMA32 zone: 474181 pages, LIFO batch:63
+  DMA32 zone: 13243 pages in unavailable ranges
+  Normal zone: 57376 pages used for memmap
+  Normal zone: 3672064 pages, LIFO batch:63
+  Normal zone: 30720 pages in unavailable ranges
+Reserving Intel graphics memory at [mem 0x7a800000-0x7e7fffff]
+ACPI: PM-Timer IO Port: 0x1808
+ACPI: Local APIC address 0xfee00000
+ACPI: LAPIC_NMI (acpi_id[0x01] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x02] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x03] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x04] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x05] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x06] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x07] high edge lint[0x1])
+ACPI: LAPIC_NMI (acpi_id[0x08] high edge lint[0x1])
+IOAPIC[0]: apic_id 2, version 32, address 0xfec00000, GSI 0-119
+ACPI: INT_SRC_OVR (bus 0 bus_irq 0 global_irq 2 dfl dfl)
+ACPI: INT_SRC_OVR (bus 0 bus_irq 9 global_irq 9 high level)
+ACPI: IRQ0 used by override.
+ACPI: IRQ9 used by override.
+Using ACPI (MADT) for SMP configuration information
+ACPI: HPET id: 0x8086a201 base: 0xfed00000
+TSC deadline timer available
+smpboot: Allowing 8 CPUs, 0 hotplug CPUs
+PM: hibernation: Registered nosave memory: [mem 0x00000000-0x00000fff]
+PM: hibernation: Registered nosave memory: [mem 0x0009e000-0x0009efff]
+PM: hibernation: Registered nosave memory: [mem 0x0009f000-0x0009ffff]
+PM: hibernation: Registered nosave memory: [mem 0x000a0000-0x000dffff]
+PM: hibernation: Registered nosave memory: [mem 0x000e0000-0x000fffff]
+PM: hibernation: Registered nosave memory: [mem 0x5f693000-0x5f693fff]
+PM: hibernation: Registered nosave memory: [mem 0x5f694000-0x5f6ddfff]
+PM: hibernation: Registered nosave memory: [mem 0x5f78a000-0x60089fff]
+PM: hibernation: Registered nosave memory: [mem 0x7558f000-0x75f7efff]
+PM: hibernation: Registered nosave memory: [mem 0x75f7f000-0x77f7efff]
+PM: hibernation: Registered nosave memory: [mem 0x77f7f000-0x77ffefff]
+PM: hibernation: Registered nosave memory: [mem 0x78000000-0x780fffff]
+PM: hibernation: Registered nosave memory: [mem 0x78100000-0x78ffffff]
+PM: hibernation: Registered nosave memory: [mem 0x79000000-0x7e7fffff]
+PM: hibernation: Registered nosave memory: [mem 0x7e800000-0xdfffffff]
+PM: hibernation: Registered nosave memory: [mem 0xe0000000-0xefffffff]
+PM: hibernation: Registered nosave memory: [mem 0xf0000000-0xfcffffff]
+PM: hibernation: Registered nosave memory: [mem 0xfd000000-0xfe7fffff]
+PM: hibernation: Registered nosave memory: [mem 0xfe800000-0xfeafffff]
+PM: hibernation: Registered nosave memory: [mem 0xfeb00000-0xfeb03fff]
+PM: hibernation: Registered nosave memory: [mem 0xfeb04000-0xfebfffff]
+PM: hibernation: Registered nosave memory: [mem 0xfec00000-0xfec00fff]
+PM: hibernation: Registered nosave memory: [mem 0xfec01000-0xfecfffff]
+PM: hibernation: Registered nosave memory: [mem 0xfed00000-0xfed00fff]
+PM: hibernation: Registered nosave memory: [mem 0xfed01000-0xfed0ffff]
+PM: hibernation: Registered nosave memory: [mem 0xfed10000-0xfed19fff]
+PM: hibernation: Registered nosave memory: [mem 0xfed1a000-0xfed83fff]
+PM: hibernation: Registered nosave memory: [mem 0xfed84000-0xfed84fff]
+PM: hibernation: Registered nosave memory: [mem 0xfed85000-0xfedfffff]
+PM: hibernation: Registered nosave memory: [mem 0xfee00000-0xfee00fff]
+PM: hibernation: Registered nosave memory: [mem 0xfee01000-0xff9fffff]
+PM: hibernation: Registered nosave memory: [mem 0xffa00000-0xffffffff]
+[mem 0x7e800000-0xdfffffff] available for PCI devices
+Booting paravirtualized kernel on bare hardware
+clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645519600211568 ns
+setup_percpu: NR_CPUS:8192 nr_cpumask_bits:8 nr_cpu_ids:8 nr_node_ids:1
+percpu: Embedded 54 pages/cpu s183960 r8192 d29032 u262144
+pcpu-alloc: s183960 r8192 d29032 u262144 alloc=1*2097152
+pcpu-alloc: [0] 0 1 2 3 4 5 6 7
+Built 1 zonelists, mobility grouping on.  Total pages: 4085371
+Policy zone: Normal
+Kernel command line: BOOT_IMAGE=/vmlinuz-5.10.0-6-amd64 root=/dev/mapper/main-root ro quiet
+Dentry cache hash table entries: 2097152 (order: 12, 16777216 bytes, linear)
+Inode-cache hash table entries: 1048576 (order: 11, 8388608 bytes, linear)
+mem auto-init: stack:off, heap alloc:on, heap free:off
+Memory: 1904140K/16600968K available (12295K kernel code, 2544K rwdata, 7548K rodata, 2388K init, 3712K bss, 463332K reserved, 0K cma-reserved)
+random: get_random_u64 called from __kmem_cache_create+0x2e/0x550 with crng_init=0
+SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
+Kernel/User page tables isolation: enabled
+ftrace: allocating 36387 entries in 143 pages
+ftrace: allocated 143 pages with 5 groups
+rcu: Hierarchical RCU implementation.
+rcu:    RCU restricting CPUs from NR_CPUS=8192 to nr_cpu_ids=8.
+        Rude variant of Tasks RCU enabled.
+        Tracing variant of Tasks RCU enabled.
+rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
+rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=8
+NR_IRQS: 524544, nr_irqs: 2048, preallocated irqs: 16
+random: crng done (trusting CPU's manufacturer)
+Console: colour VGA+ 80x25
+printk: console [tty0] enabled
+ACPI: Core revision 20200925
+clocksource: hpet: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 79635855245 ns
+APIC: Switch to symmetric I/O mode setup
+DMAR: Host address width 39
+DMAR: DRHD base: 0x000000fed90000 flags: 0x0
+DMAR: dmar0: reg_base_addr fed90000 ver 1:0 cap 1c0000c40660462 ecap 7e3ff0501e
+DMAR: DRHD base: 0x000000fed91000 flags: 0x1
+DMAR: dmar1: reg_base_addr fed91000 ver 1:0 cap d2008c40660462 ecap f050da
+DMAR: RMRR base: 0x00000075e5a000 end: 0x00000075e79fff
+DMAR: RMRR base: 0x0000007a000000 end: 0x0000007e7fffff
+DMAR-IR: IOAPIC id 2 under DRHD base  0xfed91000 IOMMU 1
+DMAR-IR: HPET id 0 under DRHD base 0xfed91000
+DMAR-IR: x2apic is disabled because BIOS sets x2apic opt out bit.
+DMAR-IR: Use 'intremap=no_x2apic_optout' to override the BIOS setting.
+DMAR-IR: Enabled IRQ remapping in xapic mode
+x2apic: IRQ remapping doesn't support X2APIC mode
+..TIMER: vector=0x30 apic1=0 pin1=2 apic2=-1 pin2=-1
+clocksource: tsc-early: mask: 0xffffffffffffffff max_cycles: 0x257a34a6eea, max_idle_ns: 440795264358 ns
+Calibrating delay loop (skipped), value calculated using timer frequency.. 5199.98 BogoMIPS (lpj=10399968)
+pid_max: default: 32768 minimum: 301
+LSM: Security Framework initializing
+Yama: disabled by default; enable with sysctl kernel.yama.*
+AppArmor: AppArmor initialized
+TOMOYO Linux initialized
+Mount-cache hash table entries: 32768 (order: 6, 262144 bytes, linear)
+Mountpoint-cache hash table entries: 32768 (order: 6, 262144 bytes, linear)
+mce: CPU0: Thermal monitoring enabled (TM1)
+process: using mwait in idle threads
+Last level iTLB entries: 4KB 64, 2MB 8, 4MB 8
+Last level dTLB entries: 4KB 64, 2MB 0, 4MB 0, 1GB 4
+Spectre V1 : Mitigation: usercopy/swapgs barriers and __user pointer sanitization
+Spectre V2 : Mitigation: Full generic retpoline
+Spectre V2 : Spectre v2 / SpectreRSB mitigation: Filling RSB on context switch
+Spectre V2 : Enabling Restricted Speculation for firmware calls
+Spectre V2 : mitigation: Enabling conditional Indirect Branch Prediction Barrier
+Spectre V2 : User space: Mitigation: STIBP via seccomp and prctl
+Speculative Store Bypass: Mitigation: Speculative Store Bypass disabled via prctl and seccomp
+TAA: Mitigation: Clear CPU buffers
+SRBDS: Mitigation: Microcode
+MDS: Mitigation: Clear CPU buffers
+Freeing SMP alternatives memory: 32K
+smpboot: CPU0: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz (family: 0x6, model: 0x5e, stepping: 0x3)
+Performance Events: PEBS fmt3+, Skylake events, 32-deep LBR, full-width counters, Intel PMU driver.
+... version:                4
+... bit width:              48
+... generic registers:      4
+... value mask:             0000ffffffffffff
+... max period:             00007fffffffffff
+... fixed-purpose events:   3
+... event mask:             000000070000000f
+rcu: Hierarchical SRCU implementation.
+NMI watchdog: Enabled. Permanently consumes one hw-PMU counter.
+smp: Bringing up secondary CPUs ...
+x86: Booting SMP configuration:
+.... node  #0, CPUs:      #1 #2 #3 #4
+MDS CPU bug present and SMT on, data leak possible. See https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html for more details.
+TAA CPU bug present and SMT on, data leak possible. See https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/tsx_async_abort.html for more details.
+ #5 #6 #7
+smp: Brought up 1 node, 8 CPUs
+smpboot: Max logical packages: 1
+smpboot: Total of 8 processors activated (41599.87 BogoMIPS)
+node 0 deferred pages initialised in 16ms
+devtmpfs: initialized
+x86/mm: Memory block size: 128MB
+PM: Registering ACPI NVS region [mem 0x5f693000-0x5f693fff] (4096 bytes)
+PM: Registering ACPI NVS region [mem 0x75f7f000-0x77f7efff] (33554432 bytes)
+clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
+futex hash table entries: 2048 (order: 5, 131072 bytes, linear)
+pinctrl core: initialized pinctrl subsystem
+NET: Registered protocol family 16
+audit: initializing netlink subsys (disabled)
+audit: type=2000 audit(1622213389.056:1): state=initialized audit_enabled=0 res=1
+thermal_sys: Registered thermal governor 'fair_share'
+thermal_sys: Registered thermal governor 'bang_bang'
+thermal_sys: Registered thermal governor 'step_wise'
+thermal_sys: Registered thermal governor 'user_space'
+thermal_sys: Registered thermal governor 'power_allocator'
+cpuidle: using governor ladder
+cpuidle: using governor menu
+Simple Boot Flag at 0x44 set to 0x1
+ACPI: bus type PCI registered
+acpiphp: ACPI Hot Plug PCI Controller Driver version: 0.5
+PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xe0000000-0xefffffff] (base 0xe0000000)
+PCI: MMCONFIG at [mem 0xe0000000-0xefffffff] reserved in E820
+pmd_set_huge: Cannot satisfy [mem 0xe0000000-0xe0200000] with a huge-page mapping due to MTRR override.
+PCI: Using configuration type 1 for base access
+ENERGY_PERF_BIAS: Set to 'normal', was 'performance'
+Kprobes globally optimized
+HugeTLB registered 1.00 GiB page size, pre-allocated 0 pages
+HugeTLB registered 2.00 MiB page size, pre-allocated 0 pages
+ACPI: Added _OSI(Module Device)
+ACPI: Added _OSI(Processor Device)
+ACPI: Added _OSI(3.0 _SCP Extensions)
+ACPI: Added _OSI(Processor Aggregator Device)
+ACPI: Added _OSI(Linux-Dell-Video)
+ACPI: Added _OSI(Linux-Lenovo-NV-HDMI-Audio)
+ACPI: Added _OSI(Linux-HPI-Hybrid-Graphics)
+ACPI: 8 ACPI AML tables successfully acquired and loaded
+ACPI: [Firmware Bug]: BIOS _OSI(Linux) query ignored
+ACPI: Dynamic OEM Table Load:
+ACPI: SSDT 0xFFFF9FC7C0C14800 0005DC (v02 PmRef  Cpu0Ist  00003000 INTL 20130927)
+ACPI: \_PR_.CPU0: _OSC native thermal LVT Acked
+ACPI: Dynamic OEM Table Load:
+ACPI: SSDT 0xFFFF9FC7C10D0800 00037F (v02 PmRef  Cpu0Cst  00003001 INTL 20130927)
+ACPI: Dynamic OEM Table Load:
+ACPI: SSDT 0xFFFF9FC7C0C12000 0005AA (v02 PmRef  ApIst    00003000 INTL 20130927)
+ACPI: Dynamic OEM Table Load:
+ACPI: SSDT 0xFFFF9FC7C1023200 000119 (v02 PmRef  ApCst    00003000 INTL 20130927)
+ACPI: EC: EC started
+ACPI: EC: interrupt blocked
+ACPI: EC: EC_CMD/EC_SC=0x66, EC_DATA=0x62
+ACPI: \_SB_.PCI0.LPCB.EC0_: Boot DSDT EC used to handle transactions
+ACPI: Interpreter enabled
+ACPI: (supports S0 S3 S4 S5)
+ACPI: Using IOAPIC for interrupt routing
+PCI: Using host bridge windows from ACPI; if necessary, use "pci=nocrs" and report a bug
+ACPI: Enabled 9 GPEs in block 00 to 7F
+ACPI: Power Resource [PG00] (on)
+ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-fe])
+acpi PNP0A08:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI HPX-Type3]
+acpi PNP0A08:00: _OSC: OS now controls [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR]
+PCI host bridge to bus 0000:00
+pci_bus 0000:00: root bus resource [io  0x0000-0x0cf7 window]
+pci_bus 0000:00: root bus resource [io  0x0d00-0xffff window]
+pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
+pci_bus 0000:00: root bus resource [mem 0x7e800000-0xdfffffff window]
+pci_bus 0000:00: root bus resource [mem 0xfd000000-0xfe7fffff window]
+pci_bus 0000:00: root bus resource [bus 00-fe]
+pci 0000:00:00.0: [8086:1910] type 00 class 0x060000
+pci 0000:00:01.0: [8086:1901] type 01 class 0x060400
+pci 0000:00:01.0: PME# supported from D0 D3hot D3cold
+pci 0000:00:02.0: [8086:191b] type 00 class 0x030000
+pci 0000:00:02.0: reg 0x10: [mem 0x92000000-0x92ffffff 64bit]
+pci 0000:00:02.0: reg 0x18: [mem 0xa0000000-0xafffffff 64bit pref]
+pci 0000:00:02.0: reg 0x20: [io  0x5000-0x503f]
+pci 0000:00:14.0: [8086:a12f] type 00 class 0x0c0330
+pci 0000:00:14.0: reg 0x10: [mem 0x94300000-0x9430ffff 64bit]
+pci 0000:00:14.0: PME# supported from D3hot D3cold
+pci 0000:00:16.0: [8086:a13a] type 00 class 0x078000
+pci 0000:00:16.0: reg 0x10: [mem 0x9432a000-0x9432afff 64bit]
+pci 0000:00:16.0: PME# supported from D3hot
+pci 0000:00:17.0: [8086:a103] type 00 class 0x010601
+pci 0000:00:17.0: reg 0x10: [mem 0x94328000-0x94329fff]
+pci 0000:00:17.0: reg 0x14: [mem 0x9432d000-0x9432d0ff]
+pci 0000:00:17.0: reg 0x18: [io  0x5080-0x5087]
+pci 0000:00:17.0: reg 0x1c: [io  0x5088-0x508b]
+pci 0000:00:17.0: reg 0x20: [io  0x5060-0x507f]
+pci 0000:00:17.0: reg 0x24: [mem 0x9432b000-0x9432b7ff]
+pci 0000:00:17.0: PME# supported from D3hot
+pci 0000:00:1c.0: [8086:a111] type 01 class 0x060400
+pci 0000:00:1c.0: PME# supported from D0 D3hot D3cold
+pci 0000:00:1c.0: Intel SPT PCH root port ACS workaround enabled
+pci 0000:00:1c.2: [8086:a112] type 01 class 0x060400
+pci 0000:00:1c.2: PME# supported from D0 D3hot D3cold
+pci 0000:00:1c.2: Intel SPT PCH root port ACS workaround enabled
+pci 0000:00:1c.3: [8086:a113] type 01 class 0x060400
+pci 0000:00:1c.3: PME# supported from D0 D3hot D3cold
+pci 0000:00:1c.3: Intel SPT PCH root port ACS workaround enabled
+pci 0000:00:1f.0: [8086:a14e] type 00 class 0x060100
+pci 0000:00:1f.2: [8086:a121] type 00 class 0x058000
+pci 0000:00:1f.2: reg 0x10: [mem 0x94324000-0x94327fff]
+pci 0000:00:1f.3: [8086:a170] type 00 class 0x040300
+pci 0000:00:1f.3: reg 0x10: [mem 0x94320000-0x94323fff 64bit]
+pci 0000:00:1f.3: reg 0x20: [mem 0x94310000-0x9431ffff 64bit]
+pci 0000:00:1f.3: PME# supported from D3hot D3cold
+pci 0000:00:1f.4: [8086:a123] type 00 class 0x0c0500
+pci 0000:00:1f.4: reg 0x10: [mem 0x9432c000-0x9432c0ff 64bit]
+pci 0000:00:1f.4: reg 0x20: [io  0x5040-0x505f]
+pci 0000:01:00.0: [10de:139b] type 00 class 0x030200
+pci 0000:01:00.0: reg 0x10: [mem 0x93000000-0x93ffffff]
+pci 0000:01:00.0: reg 0x14: [mem 0x80000000-0x8fffffff 64bit pref]
+pci 0000:01:00.0: reg 0x1c: [mem 0x90000000-0x91ffffff 64bit pref]
+pci 0000:01:00.0: reg 0x24: [io  0x4000-0x407f]
+pci 0000:01:00.0: reg 0x30: [mem 0xfff80000-0xffffffff pref]
+pci 0000:01:00.0: Enabling HDA controller
+pci 0000:00:01.0: PCI bridge to [bus 01-06]
+pci 0000:00:01.0:   bridge window [io  0x4000-0x4fff]
+pci 0000:00:01.0:   bridge window [mem 0x93000000-0x93ffffff]
+pci 0000:00:01.0:   bridge window [mem 0x80000000-0x91ffffff 64bit pref]
+pci 0000:07:00.0: [1217:8520] type 00 class 0x080501
+pci 0000:07:00.0: reg 0x10: [mem 0x94201000-0x94201fff]
+pci 0000:07:00.0: reg 0x14: [mem 0x94200000-0x942007ff]
+pci 0000:07:00.0: PME# supported from D3hot D3cold
+pci 0000:00:1c.0: PCI bridge to [bus 07]
+pci 0000:00:1c.0:   bridge window [mem 0x94200000-0x942fffff]
+pci 0000:08:00.0: [8086:3166] type 00 class 0x028000
+pci 0000:08:00.0: reg 0x10: [mem 0x94100000-0x94101fff 64bit]
+pci 0000:08:00.0: PME# supported from D0 D3hot D3cold
+pci 0000:00:1c.2: PCI bridge to [bus 08]
+pci 0000:00:1c.2:   bridge window [mem 0x94100000-0x941fffff]
+pci 0000:09:00.0: [10ec:8168] type 00 class 0x020000
+pci 0000:09:00.0: reg 0x10: [io  0x3000-0x30ff]
+pci 0000:09:00.0: reg 0x18: [mem 0x94004000-0x94004fff 64bit]
+pci 0000:09:00.0: reg 0x20: [mem 0x94000000-0x94003fff 64bit]
+pci 0000:09:00.0: supports D1 D2
+pci 0000:09:00.0: PME# supported from D0 D1 D2 D3hot D3cold
+pci 0000:00:1c.3: PCI bridge to [bus 09]
+pci 0000:00:1c.3:   bridge window [io  0x3000-0x3fff]
+pci 0000:00:1c.3:   bridge window [mem 0x94000000-0x940fffff]
+ACPI: PCI Interrupt Link [LNKA] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKB] (IRQs 3 4 5 6 *10 11 12 14 15)
+ACPI: PCI Interrupt Link [LNKC] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKD] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKE] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKF] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKG] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: PCI Interrupt Link [LNKH] (IRQs 3 4 5 6 10 *11 12 14 15)
+ACPI: EC: interrupt unblocked
+ACPI: EC: event unblocked
+ACPI: EC: EC_CMD/EC_SC=0x66, EC_DATA=0x62
+ACPI: EC: GPE=0x2
+ACPI: \_SB_.PCI0.LPCB.EC0_: Boot DSDT EC initialization complete
+ACPI: \_SB_.PCI0.LPCB.EC0_: EC: Used to handle transactions and events
+iommu: Default domain type: Translated
+pci 0000:00:02.0: vgaarb: setting as boot VGA device
+pci 0000:00:02.0: vgaarb: VGA device added: decodes=io+mem,owns=io+mem,locks=none
+pci 0000:00:02.0: vgaarb: bridge control possible
+vgaarb: loaded
+EDAC MC: Ver: 3.0.0
+NetLabel: Initializing
+NetLabel:  domain hash size = 128
+NetLabel:  protocols = UNLABELED CIPSOv4 CALIPSO
+NetLabel:  unlabeled traffic allowed by default
+PCI: Using ACPI for IRQ routing
+PCI: pci_cache_line_size set to 64 bytes
+e820: reserve RAM buffer [mem 0x0009e800-0x0009ffff]
+e820: reserve RAM buffer [mem 0x5f693000-0x5fffffff]
+e820: reserve RAM buffer [mem 0x5f78a000-0x5fffffff]
+e820: reserve RAM buffer [mem 0x7558f000-0x77ffffff]
+e820: reserve RAM buffer [mem 0x480800000-0x483ffffff]
+hpet0: at MMIO 0xfed00000, IRQs 2, 8, 0, 0, 0, 0, 0, 0
+hpet0: 8 comparators, 64-bit 24.000000 MHz counter
+clocksource: Switched to clocksource tsc-early
+VFS: Disk quotas dquot_6.6.0
+VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
+AppArmor: AppArmor Filesystem Enabled
+pnp: PnP ACPI init
+system 00:00: [mem 0xfd000000-0xfdabffff] has been reserved
+system 00:00: [mem 0xfdad0000-0xfdadffff] has been reserved
+system 00:00: [mem 0xfdb00000-0xfdffffff] has been reserved
+system 00:00: [mem 0xfe000000-0xfe01ffff] has been reserved
+system 00:00: [mem 0xfe036000-0xfe03bfff] has been reserved
+system 00:00: [mem 0xfe03d000-0xfe3fffff] has been reserved
+system 00:00: [mem 0xfe410000-0xfe7fffff] has been reserved
+system 00:00: Plug and Play ACPI device, IDs PNP0c02 (active)
+system 00:01: [io  0x2000-0x20fe] has been reserved
+system 00:01: Plug and Play ACPI device, IDs PNP0c02 (active)
+system 00:02: [io  0x0680-0x069f] has been reserved
+system 00:02: [io  0xffff] has been reserved
+system 00:02: [io  0xffff] has been reserved
+system 00:02: [io  0xffff] has been reserved
+system 00:02: [io  0x1800-0x18fe] has been reserved
+system 00:02: [io  0x164e-0x164f] has been reserved
+system 00:02: Plug and Play ACPI device, IDs PNP0c02 (active)
+system 00:03: [io  0x0800-0x087f] has been reserved
+system 00:03: Plug and Play ACPI device, IDs PNP0c02 (active)
+pnp 00:04: Plug and Play ACPI device, IDs PNP0b00 (active)
+system 00:05: [io  0x1854-0x1857] has been reserved
+system 00:05: Plug and Play ACPI device, IDs INT3f0d PNP0c02 (active)
+pnp 00:06: Plug and Play ACPI device, IDs PNP0303 (active)
+pnp 00:07: Plug and Play ACPI device, IDs SYN2b5b PNP0f03 (active)
+system 00:08: [mem 0xfe035000-0xfe035fff] has been reserved
+system 00:08: [mem 0xfe034008-0xfe034fff] has been reserved
+system 00:08: [mem 0xfdaf0000-0xfdafffff] has been reserved
+system 00:08: [mem 0xfdae0000-0xfdaeffff] has been reserved
+system 00:08: [mem 0xfdac0000-0xfdacffff] has been reserved
+system 00:08: Plug and Play ACPI device, IDs PNP0c02 (active)
+system 00:09: [mem 0xfe034000-0xfe034007] has been reserved
+system 00:09: Plug and Play ACPI device, IDs PNP0c02 (active)
+system 00:0a: [mem 0xfed10000-0xfed17fff] has been reserved
+system 00:0a: [mem 0xfed18000-0xfed18fff] has been reserved
+system 00:0a: [mem 0xfed19000-0xfed19fff] has been reserved
+system 00:0a: [mem 0xe0000000-0xefffffff] has been reserved
+system 00:0a: [mem 0xfed20000-0xfed3ffff] has been reserved
+system 00:0a: [mem 0xfed90000-0xfed93fff] could not be reserved
+system 00:0a: [mem 0xfed45000-0xfed8ffff] could not be reserved