391b17d2fa4cce97895fc9531975c0840f4757ec
[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.renderer.raster.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                 // Fan sub-triangle screen perimeter — same expression as
90                 // the object path (edge12 + edge13 + edge23); straddlers
91                 // are rare, so it is computed here rather than stored.
92                 final double dx01 = screen[0].x - screen[1].x;
93                 final double dy01 = screen[0].y - screen[1].y;
94                 final double dx02 = screen[0].x - screen[2].x;
95                 final double dy02 = screen[0].y - screen[2].y;
96                 final double dx12 = screen[1].x - screen[2].x;
97                 final double dy12 = screen[1].y - screen[2].y;
98                 final double visPerimeter = Math.sqrt(dx01 * dx01 + dy01 * dy01)
99                         + Math.sqrt(dx02 * dx02 + dy02 * dy02)
100                         + Math.sqrt(dx12 * dx12 + dy12 * dy12);
101                 paintFlat(renderBuffer, block.texture(index), block.backfaceCull(),
102                         screen[0], screen[1], screen[2],
103                         uvs[0], uvs[1], uvs[2],
104                         store[clip + 2],
105                         store[clip + i * 7 + 2],
106                         store[clip + (i + 1) * 7 + 2],
107                         visPerimeter,
108                         block.clipTtd(slot, clip));
109             }
110             return;
111         }
112
113         block.loadScreenVertex(screen[0], uvs[0], slot, index, 0, renderBuffer);
114         block.loadScreenVertex(screen[1], uvs[1], slot, index, 1, renderBuffer);
115         block.loadScreenVertex(screen[2], uvs[2], slot, index, 2, renderBuffer);
116         paintFlat(renderBuffer, block.texture(index), block.backfaceCull(),
117                 screen[0], screen[1], screen[2],
118                 uvs[0], uvs[1], uvs[2],
119                 block.camZ(slot, index, 0),
120                 block.camZ(slot, index, 1),
121                 block.camZ(slot, index, 2),
122                 block.screenPerim(slot, index),
123                 block.uvPerimeter(index));
124     }
125
126     /**
127      * Fills the scratch screen/UV points from one clip-loop entry; screen
128      * coordinates were stored at clip time with the exact
129      * {@code Vertex.setCameraSpaceCoordinate} expression.
130      */
131     private void loadClipVertex(final Point2D screen, final Point2D uv,
132                                 final double[] store, final int entry) {
133         screen.x = store[entry + 5];
134         screen.y = store[entry + 6];
135         uv.x = store[entry + 3];
136         uv.y = store[entry + 4];
137     }
138 }