1 package eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.texturedpolygon;
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;
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.
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>
20 final class MeshTriangle extends TexturedTriangle {
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.
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()});
36 private final TriangleMeshBlock block;
37 private final int index;
39 MeshTriangle(final TriangleMeshBlock block, final int index,
40 final eu.svjatoslav.aukio.e3d.renderer.raster.texture.Texture texture) {
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}.
54 public void transform(final TransformStack transforms,
55 final RenderAggregator aggregator,
56 final RenderingContext renderingContext) {
57 // intentionally empty — see TriangleMeshBlock.transform
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
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);
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();
78 final int clip = block.clipOffset(slot, index);
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],
105 store[clip + i * 7 + 2],
106 store[clip + (i + 1) * 7 + 2],
108 block.clipTtd(slot, clip));
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));
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.
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];