d62fa3b27425fe1c7c93d10b7b70681eb387df1b
[aukio-3d.git] /
1 package eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.texturedpolygon;
2
3 import eu.svjatoslav.aukio.e3d.geometry.Point2D;
4 import eu.svjatoslav.aukio.e3d.gui.RenderingContext;
5 import eu.svjatoslav.aukio.e3d.renderer.raster.RenderAggregator;
6 import eu.svjatoslav.aukio.e3d.math.TransformStack;
7
8 /**
9  * Paint/sort handle for one triangle of a {@link TriangleMeshBlock}. The
10  * triangle's geometry lives in the block's flat arrays (SoA layout); this
11  * object exists only so the existing sort/bin/paint pipeline — typed on
12  * individual shapes — can address one triangle. Handles are allocated once
13  * at block build time and reused every frame; {@link #transform} is a
14  * no-op because the block transforms all its triangles in one tight loop.
15  *
16  * <p>Per-slot screen data is read from the block's arrays through the
17  * overridden accessors, so the Z-comparator and tile binning see exactly
18  * the values an object-backed {@link TexturedTriangle} would expose.</p>
19  */
20 final class MeshTriangle extends TexturedTriangle {
21
22     /**
23      * Scratch Point2D carriers for the flat paint call. Interpolators
24      * hold references to the screen/UV points they are given, so the
25      * objects must stay stable for the duration of one paint — but a
26      * paint never nests, so three screen + three UV points per thread
27      * suffice and nothing is allocated per triangle.
28      */
29     private static final ThreadLocal<Point2D[]> SCREEN_SCRATCH =
30             ThreadLocal.withInitial(() -> new Point2D[]{
31                     new Point2D(), new Point2D(), new Point2D()});
32     private static final ThreadLocal<Point2D[]> UV_SCRATCH =
33             ThreadLocal.withInitial(() -> new Point2D[]{
34                     new Point2D(), new Point2D(), new Point2D()});
35
36     private final TriangleMeshBlock block;
37     private final int index;
38
39     MeshTriangle(final TriangleMeshBlock block, final int index,
40                  final eu.svjatoslav.aukio.e3d.renderer.raster.texture.Texture texture) {
41         super(texture);
42         this.block = block;
43         this.index = index;
44     }
45
46     /**
47      * No-op: the owning block transforms all its triangles (including this
48      * one) in a single flat-array loop. Queued handles are never
49      * transformed through the shape path. Sort Z and screen bounds live in
50      * the inherited per-slot fields, written by the block via
51      * {@code setSlotScreenState}.
52      */
53     @Override
54     public void transform(final TransformStack transforms,
55                           final RenderAggregator aggregator,
56                           final RenderingContext renderingContext) {
57         // intentionally empty — see TriangleMeshBlock.transform
58     }
59
60     /**
61      * Publishes this triangle's per-slot screen state (called by the
62      * owning block during its flat transform loop). Trampoline: the
63      * inherited setter is protected, so the call must go through the
64      * subclass.
65      */
66     void publishSlotState(final int slot, final double z,
67                           final double minY, final double maxY,
68                           final double minX, final double maxX) {
69         setSlotScreenState(slot, z, minY, maxY, minX, maxX);
70     }
71
72     @Override
73     public void paint(final RenderingContext renderBuffer) {
74         final int slot = renderBuffer.vertexSlot;
75         final Point2D[] screen = SCREEN_SCRATCH.get();
76         final Point2D[] uvs = UV_SCRATCH.get();
77
78         final int clip = block.clipOffset(slot, index);
79         if (clip >= 0) {
80             // Near-plane straddler: the block clipped to a loop of 3-4
81             // vertices, stored as (x, y, z, u, v, sx, sy) tuples. Paint
82             // as a fan, mirroring TexturedTriangle.paint's clipped path.
83             final int count = block.clipCount(slot, index);
84             final double[] store = block.clipStore(slot);
85             loadClipVertex(screen[0], uvs[0], store, clip);
86             for (int i = 1; i + 1 < count; i++) {
87                 loadClipVertex(screen[1], uvs[1], store, clip + i * 7);
88                 loadClipVertex(screen[2], uvs[2], store, clip + (i + 1) * 7);
89                 paintFlat(renderBuffer, block.texture(index), block.backfaceCull(),
90                         screen[0], screen[1], screen[2],
91                         uvs[0], uvs[1], uvs[2],
92                         store[clip + 2],
93                         store[clip + i * 7 + 2],
94                         store[clip + (i + 1) * 7 + 2],
95                         block.clipTtd(slot, clip));
96             }
97             return;
98         }
99
100         block.loadScreenVertex(screen[0], uvs[0], slot, index, 0, renderBuffer);
101         block.loadScreenVertex(screen[1], uvs[1], slot, index, 1, renderBuffer);
102         block.loadScreenVertex(screen[2], uvs[2], slot, index, 2, renderBuffer);
103         paintFlat(renderBuffer, block.texture(index), block.backfaceCull(),
104                 screen[0], screen[1], screen[2],
105                 uvs[0], uvs[1], uvs[2],
106                 block.camZ(slot, index, 0),
107                 block.camZ(slot, index, 1),
108                 block.camZ(slot, index, 2),
109                 block.origTtd(index));
110     }
111
112     /**
113      * Fills the scratch screen/UV points from one clip-loop entry; screen
114      * coordinates were stored at clip time with the exact
115      * {@code Vertex.setCameraSpaceCoordinate} expression.
116      */
117     private void loadClipVertex(final Point2D screen, final Point2D uv,
118                                 final double[] store, final int entry) {
119         screen.x = store[entry + 5];
120         screen.y = store[entry + 6];
121         uv.x = store[entry + 3];
122         uv.y = store[entry + 4];
123     }
124 }