From c692e625579164646f17a541aa3508410d62e052 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sat, 4 Apr 2026 15:43:43 +0300 Subject: [PATCH] docs: add CSG and frustum culling documentation with diagrams Add comprehensive documentation pages for Constructive Solid Geometry and view frustum culling, both with inline SVG diagrams explaining the concepts visually: - CSG page covers boolean operations (subtract, union, intersect) with diagrams showing input shapes and results, plus BSP tree algorithm explanation and polygon clipping visualization - Frustum culling page explains the six frustum planes, AABB intersection testing with P-vertex optimization, and scene design recommendations Reorganize main index with dedicated sections for each topic and add inline preview diagrams. Move rendering-loop into its own directory for consistent structure. Apply italic formatting for project name throughout. --- doc/csg/CSG demo.png | Bin 0 -> 35668 bytes doc/csg/index.org | 513 ++++++++++++++++++ doc/frustum-culling/index.org | 303 +++++++++++ doc/index.org | 162 +++++- doc/perspective-correct-textures/index.org | 2 +- .../index.org} | 2 +- 6 files changed, 962 insertions(+), 20 deletions(-) create mode 100644 doc/csg/CSG demo.png create mode 100644 doc/csg/index.org create mode 100644 doc/frustum-culling/index.org rename doc/{rendering-loop.org => rendering-loop/index.org} (99%) diff --git a/doc/csg/CSG demo.png b/doc/csg/CSG demo.png new file mode 100644 index 0000000000000000000000000000000000000000..2275350ce6d1e62e0b28fae99bf68e24ce20b358 GIT binary patch literal 35668 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 IEPRsx3-D+e;Q#;t literal 0 HcmV?d00001 diff --git a/doc/csg/index.org b/doc/csg/index.org new file mode 100644 index 0000000..f526950 --- /dev/null +++ b/doc/csg/index.org @@ -0,0 +1,513 @@ +#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme +#+TITLE: Constructive Solid Geometry - Sixth 3D +#+LANGUAGE: en +#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry} +#+LATEX_HEADER: \usepackage{parskip} +#+LATEX_HEADER: \usepackage[none]{hyphenat} + +#+OPTIONS: H:20 num:20 +#+OPTIONS: author:nil + +#+begin_export html + +#+end_export + +[[file:../index.org][Back to main documentation]] + +* What is CSG? +:PROPERTIES: +:CUSTOM_ID: what-is-csg +:ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 +:END: + +*Constructive Solid Geometry* (CSG) is a modeling technique that builds +complex 3D shapes by combining simpler primitives using boolean +operations. Instead of manually creating every vertex and face, you +define shapes as the result of operations like "merge these two cubes" +or "carve a hole using this sphere." + +CSG is particularly powerful for: +- *Procedural modeling* — generate complex geometry algorithmically +- *CAD/CAM applications* — define parts as combinations of primitives +- *Game development* — create architectural elements, holes, cavities +- *Rapid prototyping* — iterate on designs by adjusting operations + +The three fundamental CSG operations are: + +| Operation | Symbol | Result | +|-------------+--------+-------------------------------------------| +| Subtract | A - B | A with B carved out (holes, cavities) | +| Union | A + B | Combined volume (both shapes merged) | +| Intersect | A ∩ B | Volume where both overlap | + +See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#csg-demo][CSG demo]] for an interactive visualization. + +* The Three Operations +:PROPERTIES: +:CUSTOM_ID: the-three-operations +:END: + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#csg-demo][file:CSG%20demo.png]] + +The screenshot above shows all three operations displayed left to right: +subtract (green cube with spherical cavity), union (merged green and +orange shapes), and intersect (only the overlapping region in blue). + +The diagrams below use the same green cube (A) and orange sphere (B) as +the screenshot above. Each operation transforms these inputs differently, +producing the results shown from left to right in the image. + +** Subtract (A - B) +:PROPERTIES: +:CUSTOM_ID: subtract-operation +:END: + +#+BEGIN_EXPORT html + + + + + + + + + Input + + A + + B + + + − + subtract + + + + + + + Result: A − B + + + + + + cavity + + + B is the "cutter" + carves out of A + Cube with cavity + interior faces visible + +#+END_EXPORT + +*Subtract* removes the orange sphere (B) from the green cube (A), carving +out a cavity. The diagram shows B acting as a "cutter" — where it overlaps +A, a hole is created. Interior faces *are preserved* and become visible, +allowing you to see inside the carved-out space (shown as the orange dashed +curve in the result). + +This matches the leftmost shape in the screenshot: a green cube with a +visible spherical hollow inside, showing the interior surfaces created by +the subtraction. + +This operation is ideal for creating: +- Holes and tunnels +- Carved-out spaces +- Hollow objects + +#+BEGIN_SRC java +SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, Color.GREEN); +SolidPolygonSphere sphere = new SolidPolygonSphere(origin(), 60, 8, Color.ORANGE); + +cube.subtract(sphere); // cube now has a spherical cavity +#+END_SRC + +** Union (A + B) +:PROPERTIES: +:CUSTOM_ID: union-operation +:END: + +#+BEGIN_EXPORT html + + + + + + + + Input + + A + + B + + + + + union + + + + + + + Result: A + B + + + + + + removed + + + Keeps all geometry + from both shapes + Single combined volume + interior faces removed + +#+END_EXPORT + +*Union* merges the green cube (A) and orange sphere (B) into one continuous +volume. The diagram shows both shapes combining — the interior seam (where +they overlap) is removed, creating a single solid surface with no internal +boundaries (indicated by the dashed blue line labeled "removed"). + +This corresponds to the center shape in the screenshot: both green and +orange colors present but seamlessly joined, forming one unified object. + +#+BEGIN_SRC java +SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, Color.GREEN); +SolidPolygonSphere sphere = new SolidPolygonSphere(origin(), 60, 8, Color.ORANGE); + +cube.union(sphere); // cube now contains the merged result +#+END_SRC + +** Intersect (A ∩ B) +:PROPERTIES: +:CUSTOM_ID: intersect-operation +:END: + +#+BEGIN_EXPORT html + + + + + + + + + + Input + + A + + B + + + ∩ + intersect + + + + + + + Result: A ∩ B + + + + + + + + + + + + Find overlap + between both + Only shared volume + remains + +#+END_EXPORT + +*Intersect* keeps only the volume where the green cube (A) and orange +sphere (B) overlap — the region that is inside *both* shapes +simultaneously. The diagram shows this as the blue-shaded area: the +portion of the sphere that fits within the cube boundaries. Everything +else is discarded. + +This is the rightmost shape in the screenshot: only the overlapping +portion remains, showing which parts of space were occupied by both the +cube and sphere at the same time. + +This operation is useful for: +- Creating shapes constrained by multiple boundaries +- Finding collision regions +- Trimming geometry to fit within bounds + +#+BEGIN_SRC java +SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, Color.GREEN); +SolidPolygonSphere sphere = new SolidPolygonSphere(origin(), 60, 8, Color.ORANGE); + +cube.intersect(sphere); // only the overlapping region remains +#+END_SRC + +* BSP Tree Algorithm +:PROPERTIES: +:CUSTOM_ID: bsp-tree-algorithm +:END: + +CSG boolean operations are implemented using *Binary Space Partitioning* +(BSP) trees. A BSP tree recursively divides 3D space using planes, +creating a hierarchical structure that enables efficient polygon clipping +and spatial queries. + +** BSP Tree Structure +:PROPERTIES: +:CUSTOM_ID: bsp-tree-structure +:END: + +#+BEGIN_EXPORT html + + + + + + + + + Plane P₁ + + + + + + + + + front + back + + + + Front (P₂) + + + + Back (P₃) + + + + + + + + + + + leaf + + leaf + + + Each plane divides space into front (normal side) and back (opposite) + Polygons are classified and split at each partitioning plane + +#+END_EXPORT + +Each BSP node contains: +- A *partitioning plane* that divides space into two half-spaces +- *Polygons* that lie exactly on this plane (coplanar) +- *Front* subtree — polygons on the same side as the plane's normal +- *Back* subtree — polygons on the opposite side + +** Key BSP Operations +:PROPERTIES: +:CUSTOM_ID: key-bsp-operations +:END: + +The BSP tree provides three core operations that enable CSG: + +| Operation | Description | +|----------------+--------------------------------------------------| +| =invert()= | Flip all normals, swap front/back children | +| =clipTo(tree)= | Remove polygons inside the other tree's solid | +| =addPolygons()= | Insert new polygons, splitting at planes | + +*Invert* is fundamental to CSG. By flipping inside/outside, we can +transform subtraction and intersection into variations of clipping: + +- **Subtract** = invert A, clip against B, add B's clipped parts, invert back +- **Intersect** = invert A, clip B against A, invert both, combine, invert back + +** Polygon Clipping +:PROPERTIES: +:CUSTOM_ID: polygon-clipping +:END: + +When a polygon crosses a partitioning plane, it's *split* into two +fragments: + +#+BEGIN_EXPORT html + + + + + + + + Polygon crosses plane + + + plane + + + + + → + split + + + Split into fragments + + + + + front + + + + back + + + + + + new edge + + + Spanning polygons are split; each fragment goes to its respective subtree + +#+END_EXPORT + +This recursive splitting ensures that all polygons are cleanly classified +as entirely in front, entirely behind, or exactly on a plane — never +"spanning" across. + +* Using CSG in Sixth 3D +:PROPERTIES: +:CUSTOM_ID: using-csg-in-sixth-3d +:END: + +CSG operations are methods on [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.html][AbstractCompositeShape]]. They modify the +shape *in-place* — the result replaces the original geometry. + +** Basic Usage +:PROPERTIES: +:CUSTOM_ID: basic-usage +:END: + +#+BEGIN_SRC java +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.*; + +// Create two shapes +SolidPolygonCube cube = new SolidPolygonCube(origin(), 100, Color.GREEN); +SolidPolygonSphere sphere = new SolidPolygonSphere(origin(), 70, 12, Color.ORANGE); + +// Perform CSG operations (in-place modification) +cube.subtract(sphere); // Cube with spherical cavity +// or +cube.union(sphere); // Merged shape +// or +cube.intersect(sphere); // Only overlapping region + +// Add to scene +shapes.addShape(cube.setBackfaceCulling(true)); +#+END_SRC + +** Child Handling Behavior +:PROPERTIES: +:CUSTOM_ID: child-handling +:END: + +CSG operations only affect *SolidPolygon* children. Other children are +preserved: + +| Child Type | Union | Subtract | Intersect | +|-----------------------+------------------+------------------+------------------| +| SolidPolygon (this) | Replaced with result | Replaced with result | Replaced with result | +| SolidPolygon (other) | Merged into result | Discarded (cutter) | Discarded | +| Line, TextCanvas | Preserved | Preserved | Preserved | +| Nested composite | Preserved unchanged | Preserved unchanged | Preserved unchanged | + +This allows you to attach labels, decorations, or wireframe overlays to +shapes without them being affected by CSG operations. + +** Important Notes +:PROPERTIES: +:CUSTOM_ID: important-notes +:END: + +1. *Shapes are modified in-place*. The original geometry is replaced. + Clone shapes beforehand if you need to preserve the originals. + +2. *CSG works on SolidPolygon children only*. TexturedTriangle and other + shape types are not processed. + +3. *Result quality depends on mesh density*. Low-polygon inputs may + produce visible artifacts at intersection boundaries. Use higher + subdivision counts for smoother results. + +4. *Backface culling is recommended*. CSG results often have internal + faces from the cutting operation. Enable culling to hide backfaces: + =shape.setBackfaceCulling(true)= + +* Related Classes +:PROPERTIES: +:CUSTOM_ID: related-classes +:END: + +| Class | Purpose | +|--------------------------+------------------------------------------------------| +| [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/BspTree.html][BspTree]] | BSP tree for spatial partitioning and CSG operations | +| [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Plane.html][Plane]] | Partitioning plane used by BSP nodes | +| [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.html][SolidPolygon]] | Polygon shape processed by CSG | +| [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.html][AbstractCompositeShape]] | Base class with union/subtract/intersect methods | +| [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/solid/SolidPolygonMesh.html][SolidPolygonMesh]] | Custom polygon mesh for arbitrary geometry | +| SolidPolygon* primitives | See [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#shape-gallery][Shape Gallery]] for all available shapes | diff --git a/doc/frustum-culling/index.org b/doc/frustum-culling/index.org new file mode 100644 index 0000000..5d6fa3c --- /dev/null +++ b/doc/frustum-culling/index.org @@ -0,0 +1,303 @@ +#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme +#+TITLE: Frustum & View Frustum Culling - Sixth 3D +#+LANGUAGE: en +#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry} +#+LATEX_HEADER: \usepackage{parskip} +#+LATEX_HEADER: \usepackage[none]{hyphenat} + +#+OPTIONS: H:20 num:20 +#+OPTIONS: author:nil + +#+begin_export html + +#+end_export + +[[file:../index.org][Back to main documentation]] + +* Frustum & View Frustum Culling +:PROPERTIES: +:CUSTOM_ID: frustum-view-frustum-culling +:END: + +#+BEGIN_EXPORT html + + + + + + + + + +Z + (view direction) + + + + Camera + + + + + + + + + + + + + + Near + + + + Far + + + + + + visible region + + + Top plane + Bottom plane + + + + ✓ + rendered + + + + ✗ + culled + + + + ✗ + culled + +#+END_EXPORT + +The *view frustum* is a truncated pyramid-shaped volume that represents +everything the camera can see. Objects completely outside this volume are +skipped during rendering — a powerful optimization called *frustum culling*. + +** The Six Frustum Planes +:PROPERTIES: +:CUSTOM_ID: frustum-planes +:END: + +The frustum is defined by six clipping planes: + +| Plane | Purpose | +|---------+--------------------------------------------| +| Left | Left edge of viewport | +| Right | Right edge of viewport | +| Top | Top edge of viewport (smaller Y in Y-down) | +| Bottom | Bottom edge of viewport (larger Y) | +| Near | Closest visible distance from camera | +| Far | Farthest visible distance from camera | + +Each plane divides 3D space into "inside" (visible) and "outside" +(culled). An object must pass all six plane tests to be considered +potentially visible. + +** Frustum Culling vs Backface Culling +:PROPERTIES: +:CUSTOM_ID: frustum-vs-backface-culling +:END: + +These are complementary optimizations at different levels: + +| Optimization | Level | What it skips | +|-----------------+--------------+----------------------------------| +| Frustum culling | Object level | Entire composite shapes + children | +| Backface culling | Polygon level | Individual triangles facing away | + +*Frustum culling* happens first during the transform phase — entire +object trees are skipped with a single bounding box test. *Backface +culling* happens later during rasterization — individual triangles +are checked before being drawn. + +For best performance, use both: organize your scene with composite +shapes for effective frustum culling, and enable backface culling on +closed meshes. + +* How Frustum Culling Works in Sixth 3D +:PROPERTIES: +:CUSTOM_ID: frustum-culling-implementation +:END: + +Frustum culling is applied automatically to all [[../index.org#mesh][composite shapes]] +during Phase 2 of the [[../rendering-loop/][rendering loop]]: + +1. *Update frustum*: Compute 6 planes from camera FOV and viewport size +2. *For each composite shape*: + - Get its [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Box.html][Axis-Aligned Bounding Box (AABB)]] + - Transform all 8 corners to view space + - Test against frustum using [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Frustum.html#intersectsAABB][intersectsAABB()]] + - If outside: skip the entire composite and all children + - If inside: continue transforming children + +** The AABB Intersection Algorithm +:PROPERTIES: +:CUSTOM_ID: aabb-intersection-algorithm +:END: + +The intersection test uses an optimized "P-vertex" approach: + +#+BEGIN_EXPORT html + + + + + + + + P-vertex: corner most aligned with plane normal + If P is behind the plane → entire AABB is outside + + + + Plane + + + inside frustum + + outside frustum + + + + + N + + + + inside + + + P + + + + outside + + + P + +#+END_EXPORT + +For each plane, instead of testing all 8 corners of the bounding box, +we test only the *P-vertex* — the corner most aligned with the plane +normal. If this "best" corner is behind the plane, the entire box must +be outside the frustum. + +- Plane normal points *into* the frustum (toward visible region) +- P-vertex: select corner based on normal direction + - If normal.x > 0 → use maxX (rightmost corner) + - If normal.x < 0 → use minX (leftmost corner) + - Same logic for Y and Z +- Test: =dot(normal, P-vertex) < distance= → outside + +This reduces from 48 tests (8 corners × 6 planes) to just 6 tests per +object. + +* Performance Benefits +:PROPERTIES: +:CUSTOM_ID: frustum-performance +:END: + +Frustum culling can dramatically improve performance for large scenes: + +- *High cull % (60-90%)*: Excellent — most objects skipped entirely +- *Medium cull % (20-60%)*: Moderate benefit +- *Low cull % (0-20%)*: Limited benefit — most objects visible + +A composite shape that is culled skips: +- Transforming all its children +- Computing bounding boxes for children +- All polygon-level operations (backface culling, rasterization) + +Open Developer Tools (F12) to see real-time [[../index.org#frustum-culling-statistics][frustum culling statistics]]. + +* Scene Design for Effective Culling +:PROPERTIES: +:CUSTOM_ID: frustum-scene-design +:END: + +Frustum culling works best when you organize your scene into +well-defined composite shapes: + +#+BEGIN_SRC java +// Good: Each building is a separate composite +AbstractCompositeShape cityBlock = new AbstractCompositeShape(); +for (Building building : buildings) { + AbstractCompositeShape buildingComposite = new AbstractCompositeShape(); + buildingComposite.add(buildingWalls); + buildingComposite.add(buildingRoof); + buildingComposite.add(buildingInterior); + cityBlock.add(buildingComposite); +} + +// Less effective: Everything in one giant composite +AbstractCompositeShape allObjects = new AbstractCompositeShape(); +allObjects.add(building1Walls); +allObjects.add(building1Roof); +allObjects.add(building2Walls); +// ... hundreds of shapes directly in root +#+END_SRC + +*Best practices:* + +- Use composites to group objects that occupy a bounded region of space +- Keep bounding boxes tight (don't add distant objects to the same composite) +- Nest composites hierarchically for multi-level culling (city → block → building) +- Call [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractShape.html#updateBoundingBox()][updateBoundingBox()]] after moving shapes + +* Technical Details +:PROPERTIES: +:CUSTOM_ID: frustum-technical-details +:END: + +The [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Frustum.html][Frustum]] class: + +- Computes planes in *view space* (camera at origin, looking along +Z) +- FOV derived from =projectionScale = width / 3= (≈112° horizontal FOV) +- Default clip distances: Near = 1.0, Far = 10000.0 +- Planes stored in [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Plane.html][Hesse normal form]]: (normal vector, distance) + +The frustum is updated once per frame in +[[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/ShapeCollection.html][ShapeCollection.transformAllShapes()]] before processing composite +shapes. diff --git a/doc/index.org b/doc/index.org index b739154..6407c9e 100644 --- a/doc/index.org +++ b/doc/index.org @@ -80,7 +80,7 @@ quirks, and dependency hell. Every GPU API comes with its own ecosystem of pain — version mismatches, incomplete implementations, vendor-specific workarounds. I want a library that "just works". -Sixth 3D takes a different path. By rendering everything in software +*Sixth 3D* takes a different path. By rendering everything in software on the CPU, the entire GPU problem space simply disappears. You add a Maven dependency, write some Java, and you have a 3D scene. It runs wherever Java runs. @@ -99,7 +99,7 @@ constrained by what a GPU API exposes. Instead of brute-forcing everything through a fixed GPU pipeline, you can implement clever, application-specific optimizations. -Sixth 3D is part of the larger [[https://www3.svjatoslav.eu/projects/sixth/][Sixth project]], with the long-term goal +*Sixth 3D* is part of the larger [[https://www3.svjatoslav.eu/projects/sixth/][Sixth project]], with the long-term goal of providing a platform for 3D user interfaces and interactive data visualization. It can also be used as a standalone 3D engine in any Java project. See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][demos]] for examples of what it can do today. @@ -110,14 +110,13 @@ Java project. See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][dem :ID: 4b6c1355-0afe-40c6-86c3-14bf8a11a8d0 :END: -- To understand main render loop, see dedicated page: [[file:rendering-loop.org][Rendering loop]] - -- To understand perspective-correct texture mapping, see dedicated - page: [[file:perspective-correct-textures/][Perspective-correct textures]] - - Study [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][demo applications]] for practical examples. Start with [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#minimal-example][minimal example]]. +** Main render loop + +- To understand main render loop, see dedicated page: [[file:rendering-loop/][Rendering loop]] + ** Coordinate System (X, Y, Z) :PROPERTIES: :CUSTOM_ID: coordinate-system @@ -144,7 +143,7 @@ Java project. See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][dem #+END_EXPORT -Sixth 3D uses a **left-handed coordinate system with X pointing right +*Sixth 3D* uses a **left-handed coordinate system with X pointing right and Y pointing down**, matching standard 2D screen coordinates. This coordinate system should feel intuitive for people with preexisting 2D graphics background. @@ -197,7 +196,7 @@ position. - Position: =(x, y, z)= - Can also store: color, texture UV, normal vector - A triangle = 3 vertices, a cube = 8 vertices -- Vertex maps to [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Point3D.html][Point3D]] class in Sixth 3D engine. +- Vertex maps to [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/geometry/Point3D.html][Point3D]] class in *Sixth 3D* engine. ** Edge :PROPERTIES: @@ -259,7 +258,7 @@ A *face* is a flat surface enclosed by edges. In most 3D engines, the fundamenta - Always guaranteed to be coplanar - Quads (4 vertices) = 2 triangles - Complex shapes = many triangles (a "mesh") -- Face maps to [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidTriangle.html][SolidTriangle]], [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.html][SolidPolygon]], or [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedTriangle.html][TexturedTriangle]] in Sixth 3D. +- Face maps to [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidTriangle.html][SolidTriangle]], [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.html][SolidPolygon]], or [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedTriangle.html][TexturedTriangle]] in *Sixth 3D*. ** Normal Vector :PROPERTIES: @@ -335,14 +334,139 @@ A *mesh* is a collection of vertices, edges, and faces that together define the - Cube: 8 vertices, 12 triangles - Smooth sphere: hundreds–thousands of triangles - =vertices[] + indices[]= → efficient storage -- In Sixth 3D engine: +- In *Sixth 3D* engine: - [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/AbstractCoordinateShape.html][AbstractCoordinateShape]]: base class for single shapes with vertices (triangles, lines). Use when creating one primitive. - [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.html][AbstractCompositeShape]]: groups multiple shapes into one object. Use for complex models that move/rotate together. See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#shape-gallery][Shape Gallery demo]] for a visual showcase of -all primitive shapes available in Sixth 3D, rendered in both +all primitive shapes available in *Sixth 3D*, rendered in both wireframe and solid polygon styles with dynamic lighting. +** Perspective correct textures + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:perspective-correct-textures/Affine distortion.png]] + +*Sixth 3D* tries to do perspective-correct texture rendering. Read more +about [[file:perspective-correct-textures/][perspective-correct texture implementation]]. + +** Frustum & View Frustum Culling + +*Sixth 3D* implements view frustum culling. + +#+BEGIN_EXPORT html + + + + + + + + + +Z + (view direction) + + + + Camera + + + + + + + + + + + + + + Near + + + + Far + + + + + + visible region + + + Top plane + Bottom plane + + + + ✓ + rendered + + + + ✗ + culled + + + + ✗ + culled + +#+END_EXPORT + +To understand frustum culling and object-level visibility +optimization, read more about [[file:frustum-culling/][frustum & view frustum culling.]] + +** Constructive Solid Geometry + +*Sixth 3D* allows performing boolean operations against geometry shapes. +So one can subtract, unionize or intersect shapes. + +#+BEGIN_EXPORT html + + + + + + + + + Input + + A + + B + + + − + subtract + + + + + + + Result: A − B + + + + + + cavity + + + B is the "cutter" + carves out of A + Cube with cavity + interior faces visible + +#+END_EXPORT + +To understand CSG boolean operations, read more about [[file:csg/][Constructive +Solid Geometry]]. + ** Winding Order & Backface Culling :PROPERTIES: :CUSTOM_ID: winding-order-backface-culling @@ -386,7 +510,7 @@ wireframe and solid polygon styles with dynamic lighting. #+END_EXPORT The order in which a triangle's vertices are listed determines its -*winding order*. In Sixth 3D, screen coordinates have Y-axis pointing +*winding order*. In *Sixth 3D*, screen coordinates have Y-axis pointing *down*, which inverts the apparent winding direction compared to standard mathematical convention (Y-up). *Counter-clockwise (CCW)* in screen space means front-facing. *Backface culling* skips rendering @@ -401,19 +525,21 @@ optimization. (in Y-down screen coordinates, negative signed area corresponds to visually CCW winding) -In Sixth 3D, backface culling is *optional* and disabled by default. Enable it per-shape: -- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidTriangle.html#setBackfaceCulling(boolean)][SolidTriangle.setBackfaceCulling(true)]] +In *Sixth 3D*, backface culling is *optional* and disabled by default. Enable it per-shape: +- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/solidpolygon/SolidPolygon.html#setBackfaceCulling(boolean)][SolidPolygon.setBackfaceCulling(true)]] - [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/basic/texturedpolygon/TexturedTriangle.html#setBackfaceCulling(boolean)][TexturedTriangle.setBackfaceCulling(true)]] - [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/renderer/raster/shapes/composite/base/AbstractCompositeShape.html#setBackfaceCulling(boolean)][AbstractCompositeShape.setBackfaceCulling(true)]] (applies to all sub-shapes) +See the [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/#winding-order][Winding Order demo]] for an interactive visualization. + ** Working with Colors :PROPERTIES: :CUSTOM_ID: working-with-colors :ID: f2c9642a-a093-444f-8992-76c97ff28c16 :END: -Sixth 3D uses its own Color class (not java.awt.Color): +*Sixth 3D* uses its own Color class (not java.awt.Color): #+BEGIN_SRC java import eu.svjatoslav.sixth.e3d.renderer.raster.Color; @@ -673,8 +799,8 @@ so multiple views can have different debug configurations simultaneously. :END: - Study how [[id:4b6c1355-0afe-40c6-86c3-14bf8a11a8d0][scene definition]] works. -- Understand [[file:rendering-loop.org][main rendering loop]]. +- Understand [[file:rendering-loop/][main rendering loop]]. - Read online [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/][JavaDoc]]. -- See [[https://www3.svjatoslav.eu/projects/sixth-3d/graphs/][Sixth 3D class diagrams]]. (Diagrams were generated by using +- 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) - Study [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/][demo applications]]. diff --git a/doc/perspective-correct-textures/index.org b/doc/perspective-correct-textures/index.org index 99f43b8..3be7399 100644 --- a/doc/perspective-correct-textures/index.org +++ b/doc/perspective-correct-textures/index.org @@ -63,7 +63,7 @@ screen space, not accounting for depth. #+attr_latex: :width 1000px [[file:Affine distortion.png]] -The Sixth 3D engine solves this through *adaptive polygon tessellation*. +The *Sixth 3D* engine solves this through *adaptive polygon tessellation*. Instead of computing true perspective-correct interpolation per pixel (which is expensive), the engine subdivides large triangles into smaller pieces. Each sub-triangle is rendered with simple affine diff --git a/doc/rendering-loop.org b/doc/rendering-loop/index.org similarity index 99% rename from doc/rendering-loop.org rename to doc/rendering-loop/index.org index f0d0d18..1f09cac 100644 --- a/doc/rendering-loop.org +++ b/doc/rendering-loop/index.org @@ -25,7 +25,7 @@ #+end_export -[[file:index.org][Back to main documentation]] +[[file:../index.org][Back to main documentation]] * Rendering loop :PROPERTIES: -- 2.20.1