From: Svjatoslav Agejenko Date: Sat, 4 Apr 2026 12:43:43 +0000 (+0300) Subject: docs: add CSG and frustum culling documentation with diagrams X-Git-Tag: sixth-3d-1.4~1 X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=c692e625579164646f17a541aa3508410d62e052;p=sixth-3d.git 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. --- diff --git a/doc/csg/CSG demo.png b/doc/csg/CSG demo.png new file mode 100644 index 0000000..2275350 Binary files /dev/null and b/doc/csg/CSG demo.png differ 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.org deleted file mode 100644 index f0d0d18..0000000 --- a/doc/rendering-loop.org +++ /dev/null @@ -1,259 +0,0 @@ -#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme -#+TITLE: Rendering Loop - Sixth 3D -#+LANGUAGE: en -#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry} -#+LATEX_HEADER: \usepackage{parskip} -#+LATEX_HEADER: \usepackage[none]{hyphenat} - -#+OPTIONS: H:20 num:20 -#+OPTIONS: author:nil - -#+begin_export html - -#+end_export - -[[file:index.org][Back to main documentation]] - -* Rendering loop -:PROPERTIES: -:CUSTOM_ID: rendering-loop -:ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 -:END: - -The rendering loop is the heart of the engine, continuously generating -frames on a dedicated background thread. It orchestrates the entire -rendering pipeline from 3D world space to pixels on screen. - -** Main loop structure -:PROPERTIES: -:CUSTOM_ID: main-loop-structure -:END: - -The render thread runs continuously in a dedicated daemon thread: - -#+BEGIN_SRC java -while (renderThreadRunning) { - ensureThatViewIsUpToDate(); // Render one frame - maintainTargetFps(); // Sleep if needed -} -#+END_SRC - -The thread is a daemon, so it automatically stops when the JVM exits. -You can stop it explicitly with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#stop()][ViewPanel.stop()]]. - -** Frame rate control -:PROPERTIES: -:CUSTOM_ID: frame-rate-control -:END: - -The engine supports two modes: - -- *Target FPS mode*: Set with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#setFrameRate(int)][setFrameRate(int)]]. The thread sleeps - between frames to maintain the target rate. If rendering takes - longer than the frame interval, the engine catches up naturally - without sleeping. - -- *Unlimited mode*: Set =setFrameRate(0)= or negative. No sleeping — - renders as fast as possible. Useful for benchmarking. - -** Frame listeners -:PROPERTIES: -:CUSTOM_ID: frame-listeners -:END: - -Before each frame, the engine notifies all registered [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/FrameListener.html][FrameListener]]s: - -#+BEGIN_SRC java -viewPanel.addFrameListener((panel, deltaMs) -> { - // Update animations, physics, game logic - shape.rotate(0.01); - return true; // true = force repaint -}); -#+END_SRC - -Frame listeners can trigger repaints by returning =true=. Built-in listeners include: -- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/Camera.html][Camera]] — handles keyboard/mouse navigation -- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.html][InputManager]] — processes input events - -* Rendering phases -:PROPERTIES: -:CUSTOM_ID: rendering-phases -:END: - -Each frame goes through 6 phases. Open the Developer Tools panel (F12) -to see these phases logged in real-time: - -** Phase 1: Clear canvas -:PROPERTIES: -:CUSTOM_ID: phase-1-clear-canvas -:END: - -The pixel buffer is filled with the background color (default: black). - -#+BEGIN_SRC java -Arrays.fill(pixels, 0, width * height, backgroundColorRgb); -#+END_SRC - -This is a simple =Arrays.fill= operation — very fast, single-threaded. - -** Phase 2: Transform shapes -:PROPERTIES: -:CUSTOM_ID: phase-2-transform-shapes -:END: - -All shapes are transformed from world space to screen space: - -1. Build camera-relative transform (inverse of camera position/rotation) -2. For each shape: - - Apply camera transform - - Project 3D → 2D (perspective projection) - - Calculate =onScreenZ= for depth sorting - - Queue for rendering - -This is single-threaded but very fast — just math, no pixel operations. - -** Phase 3: Sort shapes -:PROPERTIES: -:CUSTOM_ID: phase-3-sort-shapes -:END: - -Shapes are sorted by =onScreenZ= (depth) in descending order: - -#+BEGIN_SRC java -Collections.sort(queuedShapes, (a, b) -> Double.compare(b.onScreenZ, a.onScreenZ)); -#+END_SRC - -Back-to-front sorting is essential for correct transparency and -occlusion. Shapes further from the camera are painted first. - -** Phase 4: Paint shapes (multi-threaded) -:PROPERTIES: -:CUSTOM_ID: phase-4-paint-shapes -:END: - -The screen is divided into 8 horizontal segments, each rendered by a separate thread: - -#+BEGIN_EXPORT html - - - - - - - - - - - Segment 0 (Thread 0) - Segment 1 (Thread 1) - Segment 2 (Thread 2) - Segment 3 (Thread 3) - Segment 4 (Thread 4) - Segment 5 (Thread 5) - Segment 6 (Thread 6) - Segment 7 (Thread 7) - -#+END_EXPORT - -Each thread: -- Gets a =SegmentRenderingContext= with Y-bounds (minY, maxY) -- Iterates all shapes and paints pixels within its Y-range -- Clips triangles/lines at segment boundaries -- Detects mouse hits (before clipping) - -A =CountDownLatch= waits for all 8 threads to complete before proceeding. - -**Why 8 segments?** This matches the typical core count of modern CPUs. -The fixed thread pool (=Executors.newFixedThreadPool(8)=) avoids the -overhead of creating threads per frame. - -** Phase 5: Combine mouse results -:PROPERTIES: -:CUSTOM_ID: phase-5-combine-mouse-results -:END: - -During painting, each segment tracks which shape is under the mouse cursor. -Since all segments paint the same shapes (just different Y-ranges), they -should all report the same hit. Phase 5 takes the first non-null result: - -#+BEGIN_SRC java -for (SegmentRenderingContext ctx : segmentContexts) { - if (ctx.getSegmentMouseHit() != null) { - renderingContext.setCurrentObjectUnderMouseCursor(ctx.getSegmentMouseHit()); - break; - } -} -#+END_SRC - -** Phase 6: Blit to screen -:PROPERTIES: -:CUSTOM_ID: phase-6-blit-to-screen -:END: - -The rendered =BufferedImage= is copied to the screen using -[[https://docs.oracle.com/javase/21/docs/api/java/awt/image/BufferStrategy.html][BufferStrategy]] for tear-free page-flipping: - -#+BEGIN_SRC java -do { - Graphics2D g = bufferStrategy.getDrawGraphics(); - g.drawImage(renderingContext.bufferedImage, 0, 0, null); - g.dispose(); -} while (bufferStrategy.contentsRestored()); - -bufferStrategy.show(); -Toolkit.getDefaultToolkit().sync(); -#+END_SRC - -The =do-while= loop handles the case where the OS recreates the back -buffer (common during window resizing). Since our offscreen -=BufferedImage= still has the correct pixels, we only need to re-blit, -not re-render. - -* Smart repaint skipping -:PROPERTIES: -:CUSTOM_ID: smart-repaint-skipping -:END: - -The engine avoids unnecessary rendering: - -- =viewRepaintNeeded= flag: Set to =true= only when something changes -- Frame listeners can return =false= to skip repaint -- Resizing, component events, and explicit =repaintDuringNextViewUpdate()= - calls set the flag - -This means a static scene consumes almost zero CPU — the render thread -just spins checking the flag. - -* Rendering context -:PROPERTIES: -:CUSTOM_ID: rendering-context -:END: - -The [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/RenderingContext.html][RenderingContext]] holds all state for a single frame: - -| Field | Purpose | -|-------+---------| -| =pixels[]= | Raw pixel buffer (int[] in RGB format) | -| =bufferedImage= | Java2D wrapper around pixels | -| =graphics= | Graphics2D for text, lines, shapes | -| =width=, =height= | Screen dimensions | -| =centerCoordinate= | Screen center (for projection) | -| =projectionScale= | Perspective scale factor | -| =frameNumber= | Monotonically increasing frame counter | - -A new context is created when the window is resized. Otherwise, the -same context is reused — =prepareForNewFrameRendering()= just resets -per-frame state like mouse tracking. diff --git a/doc/rendering-loop/index.org b/doc/rendering-loop/index.org new file mode 100644 index 0000000..1f09cac --- /dev/null +++ b/doc/rendering-loop/index.org @@ -0,0 +1,259 @@ +#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme +#+TITLE: Rendering Loop - Sixth 3D +#+LANGUAGE: en +#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry} +#+LATEX_HEADER: \usepackage{parskip} +#+LATEX_HEADER: \usepackage[none]{hyphenat} + +#+OPTIONS: H:20 num:20 +#+OPTIONS: author:nil + +#+begin_export html + +#+end_export + +[[file:../index.org][Back to main documentation]] + +* Rendering loop +:PROPERTIES: +:CUSTOM_ID: rendering-loop +:ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 +:END: + +The rendering loop is the heart of the engine, continuously generating +frames on a dedicated background thread. It orchestrates the entire +rendering pipeline from 3D world space to pixels on screen. + +** Main loop structure +:PROPERTIES: +:CUSTOM_ID: main-loop-structure +:END: + +The render thread runs continuously in a dedicated daemon thread: + +#+BEGIN_SRC java +while (renderThreadRunning) { + ensureThatViewIsUpToDate(); // Render one frame + maintainTargetFps(); // Sleep if needed +} +#+END_SRC + +The thread is a daemon, so it automatically stops when the JVM exits. +You can stop it explicitly with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#stop()][ViewPanel.stop()]]. + +** Frame rate control +:PROPERTIES: +:CUSTOM_ID: frame-rate-control +:END: + +The engine supports two modes: + +- *Target FPS mode*: Set with [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/ViewPanel.html#setFrameRate(int)][setFrameRate(int)]]. The thread sleeps + between frames to maintain the target rate. If rendering takes + longer than the frame interval, the engine catches up naturally + without sleeping. + +- *Unlimited mode*: Set =setFrameRate(0)= or negative. No sleeping — + renders as fast as possible. Useful for benchmarking. + +** Frame listeners +:PROPERTIES: +:CUSTOM_ID: frame-listeners +:END: + +Before each frame, the engine notifies all registered [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/FrameListener.html][FrameListener]]s: + +#+BEGIN_SRC java +viewPanel.addFrameListener((panel, deltaMs) -> { + // Update animations, physics, game logic + shape.rotate(0.01); + return true; // true = force repaint +}); +#+END_SRC + +Frame listeners can trigger repaints by returning =true=. Built-in listeners include: +- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/Camera.html][Camera]] — handles keyboard/mouse navigation +- [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/humaninput/InputManager.html][InputManager]] — processes input events + +* Rendering phases +:PROPERTIES: +:CUSTOM_ID: rendering-phases +:END: + +Each frame goes through 6 phases. Open the Developer Tools panel (F12) +to see these phases logged in real-time: + +** Phase 1: Clear canvas +:PROPERTIES: +:CUSTOM_ID: phase-1-clear-canvas +:END: + +The pixel buffer is filled with the background color (default: black). + +#+BEGIN_SRC java +Arrays.fill(pixels, 0, width * height, backgroundColorRgb); +#+END_SRC + +This is a simple =Arrays.fill= operation — very fast, single-threaded. + +** Phase 2: Transform shapes +:PROPERTIES: +:CUSTOM_ID: phase-2-transform-shapes +:END: + +All shapes are transformed from world space to screen space: + +1. Build camera-relative transform (inverse of camera position/rotation) +2. For each shape: + - Apply camera transform + - Project 3D → 2D (perspective projection) + - Calculate =onScreenZ= for depth sorting + - Queue for rendering + +This is single-threaded but very fast — just math, no pixel operations. + +** Phase 3: Sort shapes +:PROPERTIES: +:CUSTOM_ID: phase-3-sort-shapes +:END: + +Shapes are sorted by =onScreenZ= (depth) in descending order: + +#+BEGIN_SRC java +Collections.sort(queuedShapes, (a, b) -> Double.compare(b.onScreenZ, a.onScreenZ)); +#+END_SRC + +Back-to-front sorting is essential for correct transparency and +occlusion. Shapes further from the camera are painted first. + +** Phase 4: Paint shapes (multi-threaded) +:PROPERTIES: +:CUSTOM_ID: phase-4-paint-shapes +:END: + +The screen is divided into 8 horizontal segments, each rendered by a separate thread: + +#+BEGIN_EXPORT html + + + + + + + + + + + Segment 0 (Thread 0) + Segment 1 (Thread 1) + Segment 2 (Thread 2) + Segment 3 (Thread 3) + Segment 4 (Thread 4) + Segment 5 (Thread 5) + Segment 6 (Thread 6) + Segment 7 (Thread 7) + +#+END_EXPORT + +Each thread: +- Gets a =SegmentRenderingContext= with Y-bounds (minY, maxY) +- Iterates all shapes and paints pixels within its Y-range +- Clips triangles/lines at segment boundaries +- Detects mouse hits (before clipping) + +A =CountDownLatch= waits for all 8 threads to complete before proceeding. + +**Why 8 segments?** This matches the typical core count of modern CPUs. +The fixed thread pool (=Executors.newFixedThreadPool(8)=) avoids the +overhead of creating threads per frame. + +** Phase 5: Combine mouse results +:PROPERTIES: +:CUSTOM_ID: phase-5-combine-mouse-results +:END: + +During painting, each segment tracks which shape is under the mouse cursor. +Since all segments paint the same shapes (just different Y-ranges), they +should all report the same hit. Phase 5 takes the first non-null result: + +#+BEGIN_SRC java +for (SegmentRenderingContext ctx : segmentContexts) { + if (ctx.getSegmentMouseHit() != null) { + renderingContext.setCurrentObjectUnderMouseCursor(ctx.getSegmentMouseHit()); + break; + } +} +#+END_SRC + +** Phase 6: Blit to screen +:PROPERTIES: +:CUSTOM_ID: phase-6-blit-to-screen +:END: + +The rendered =BufferedImage= is copied to the screen using +[[https://docs.oracle.com/javase/21/docs/api/java/awt/image/BufferStrategy.html][BufferStrategy]] for tear-free page-flipping: + +#+BEGIN_SRC java +do { + Graphics2D g = bufferStrategy.getDrawGraphics(); + g.drawImage(renderingContext.bufferedImage, 0, 0, null); + g.dispose(); +} while (bufferStrategy.contentsRestored()); + +bufferStrategy.show(); +Toolkit.getDefaultToolkit().sync(); +#+END_SRC + +The =do-while= loop handles the case where the OS recreates the back +buffer (common during window resizing). Since our offscreen +=BufferedImage= still has the correct pixels, we only need to re-blit, +not re-render. + +* Smart repaint skipping +:PROPERTIES: +:CUSTOM_ID: smart-repaint-skipping +:END: + +The engine avoids unnecessary rendering: + +- =viewRepaintNeeded= flag: Set to =true= only when something changes +- Frame listeners can return =false= to skip repaint +- Resizing, component events, and explicit =repaintDuringNextViewUpdate()= + calls set the flag + +This means a static scene consumes almost zero CPU — the render thread +just spins checking the flag. + +* Rendering context +:PROPERTIES: +:CUSTOM_ID: rendering-context +:END: + +The [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/eu/svjatoslav/sixth/e3d/gui/RenderingContext.html][RenderingContext]] holds all state for a single frame: + +| Field | Purpose | +|-------+---------| +| =pixels[]= | Raw pixel buffer (int[] in RGB format) | +| =bufferedImage= | Java2D wrapper around pixels | +| =graphics= | Graphics2D for text, lines, shapes | +| =width=, =height= | Screen dimensions | +| =centerCoordinate= | Screen center (for projection) | +| =projectionScale= | Perspective scale factor | +| =frameNumber= | Monotonically increasing frame counter | + +A new context is created when the window is resized. Otherwise, the +same context is reused — =prepareForNewFrameRendering()= just resets +per-frame state like mouse tracking.