2 * Sixth 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon;
7 import eu.svjatoslav.sixth.e3d.geometry.Point2D;
8 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
9 import eu.svjatoslav.sixth.e3d.math.Vertex;
10 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape;
11 import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
12 import eu.svjatoslav.sixth.e3d.renderer.raster.texture.TextureBitmap;
14 import java.awt.Color;
16 import static eu.svjatoslav.sixth.e3d.geometry.Polygon.pointWithinPolygon;
23 * This is how perspective-correct texture rendering is implemented:
24 * If polygon is sufficiently small, it is rendered without perspective correction.
25 * Otherwise, it is sliced into smaller polygons.
29 public class TexturedPolygon extends AbstractCoordinateShape {
31 private static final ThreadLocal<PolygonBorderInterpolator[]> INTERPOLATORS =
32 ThreadLocal.withInitial(() -> new PolygonBorderInterpolator[]{
33 new PolygonBorderInterpolator(), new PolygonBorderInterpolator(), new PolygonBorderInterpolator()
36 public final Texture texture;
39 * If <code>true</code> then polygon borders will be drawn.
40 * It is used for debugging purposes.
42 public boolean showBorders = false;
43 private boolean backfaceCulling = false;
45 private double totalTextureDistance = -1;
47 public TexturedPolygon(Vertex p1, Vertex p2, Vertex p3, final Texture texture) {
50 this.texture = texture;
53 private void computeTotalTextureDistance() {
54 // compute total texture distance
55 totalTextureDistance = coordinates[0].textureCoordinate.getDistanceTo(coordinates[1].textureCoordinate);
56 totalTextureDistance += coordinates[0].textureCoordinate.getDistanceTo(coordinates[2].textureCoordinate);
57 totalTextureDistance += coordinates[1].textureCoordinate.getDistanceTo(coordinates[2].textureCoordinate);
60 private void drawHorizontalLine(final PolygonBorderInterpolator line1,
61 final PolygonBorderInterpolator line2, final int y,
62 final RenderingContext renderBuffer,
63 final TextureBitmap textureBitmap) {
68 int x1 = line1.getX();
69 int x2 = line2.getX();
71 final double tx2, ty2;
72 final double tx1, ty1;
76 tx1 = line1.getTX() * textureBitmap.multiplicationFactor;
77 ty1 = line1.getTY() * textureBitmap.multiplicationFactor;
79 tx2 = line2.getTX() * textureBitmap.multiplicationFactor;
80 ty2 = line2.getTY() * textureBitmap.multiplicationFactor;
87 tx1 = line2.getTX() * textureBitmap.multiplicationFactor;
88 ty1 = line2.getTY() * textureBitmap.multiplicationFactor;
90 tx2 = line1.getTX() * textureBitmap.multiplicationFactor;
91 ty2 = line1.getTY() * textureBitmap.multiplicationFactor;
94 final double realWidth = x2 - x1;
95 final double realX1 = x1;
100 if (x2 >= renderBuffer.width)
101 x2 = renderBuffer.width - 1;
103 int renderBufferOffset = (y * renderBuffer.width) + x1;
104 final int[] renderBufferPixels = renderBuffer.pixels;
106 final double twidth = tx2 - tx1;
107 final double theight = ty2 - ty1;
109 for (int x = x1; x < x2; x++) {
111 final double distance = x - realX1;
113 final double tx = tx1 + ((twidth * distance) / realWidth);
114 final double ty = ty1 + ((theight * distance) / realWidth);
116 final int textureOffset = textureBitmap.getAddress((int) tx,
119 textureBitmap.drawPixel(textureOffset, renderBufferPixels,
122 renderBufferOffset++;
128 public void paint(final RenderingContext renderBuffer) {
130 final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
131 final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
132 final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
134 if (backfaceCulling) {
135 final double signedArea = (projectedPoint2.x - projectedPoint1.x)
136 * (projectedPoint3.y - projectedPoint1.y)
137 - (projectedPoint3.x - projectedPoint1.x)
138 * (projectedPoint2.y - projectedPoint1.y);
143 projectedPoint1.roundToInteger();
144 projectedPoint2.roundToInteger();
145 projectedPoint3.roundToInteger();
147 if (mouseInteractionController != null)
148 if (renderBuffer.getMouseEvent() != null)
149 if (pointWithinPolygon(
150 renderBuffer.getMouseEvent().coordinate, projectedPoint1,
151 projectedPoint2, projectedPoint3))
152 renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
154 // Show polygon boundaries (for debugging)
156 showBorders(renderBuffer);
158 // find top-most point
159 int yTop = (int) projectedPoint1.y;
161 if (projectedPoint2.y < yTop)
162 yTop = (int) projectedPoint2.y;
164 if (projectedPoint3.y < yTop)
165 yTop = (int) projectedPoint3.y;
170 // find bottom-most point
171 int yBottom = (int) projectedPoint1.y;
173 if (projectedPoint2.y > yBottom)
174 yBottom = (int) projectedPoint2.y;
176 if (projectedPoint3.y > yBottom)
177 yBottom = (int) projectedPoint3.y;
179 if (yBottom >= renderBuffer.height)
180 yBottom = renderBuffer.height - 1;
182 // clamp to render Y bounds
183 yTop = Math.max(yTop, renderBuffer.renderMinY);
184 yBottom = Math.min(yBottom, renderBuffer.renderMaxY);
189 double totalVisibleDistance = projectedPoint1.getDistanceTo(projectedPoint2);
190 totalVisibleDistance += projectedPoint1.getDistanceTo(projectedPoint3);
191 totalVisibleDistance += projectedPoint2.getDistanceTo(projectedPoint3);
193 if (totalTextureDistance == -1)
194 computeTotalTextureDistance();
195 final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
197 final TextureBitmap zoomedBitmap = texture.getZoomedBitmap(scaleFactor);
199 final PolygonBorderInterpolator[] interp = INTERPOLATORS.get();
200 final PolygonBorderInterpolator polygonBorder1 = interp[0];
201 final PolygonBorderInterpolator polygonBorder2 = interp[1];
202 final PolygonBorderInterpolator polygonBorder3 = interp[2];
204 polygonBorder1.setPoints(projectedPoint1, projectedPoint2,
205 coordinates[0].textureCoordinate,
206 coordinates[1].textureCoordinate);
207 polygonBorder2.setPoints(projectedPoint1, projectedPoint3,
208 coordinates[0].textureCoordinate,
209 coordinates[2].textureCoordinate);
210 polygonBorder3.setPoints(projectedPoint2, projectedPoint3,
211 coordinates[1].textureCoordinate,
212 coordinates[2].textureCoordinate);
214 // Inline sort for 3 elements to avoid array allocation
215 PolygonBorderInterpolator a = polygonBorder1;
216 PolygonBorderInterpolator b = polygonBorder2;
217 PolygonBorderInterpolator c = polygonBorder3;
218 PolygonBorderInterpolator t;
219 if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
220 if (b.compareTo(c) > 0) { t = b; b = c; c = t; }
221 if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
223 for (int y = yTop; y < yBottom; y++)
224 if (a.containsY(y)) {
226 drawHorizontalLine(a, b, y, renderBuffer, zoomedBitmap);
227 else if (c.containsY(y))
228 drawHorizontalLine(a, c, y, renderBuffer, zoomedBitmap);
229 } else if (b.containsY(y))
231 drawHorizontalLine(b, c, y, renderBuffer, zoomedBitmap);
235 public boolean isBackfaceCullingEnabled() {
236 return backfaceCulling;
239 public void setBackfaceCulling(final boolean backfaceCulling) {
240 this.backfaceCulling = backfaceCulling;
243 private void showBorders(final RenderingContext renderBuffer) {
245 final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
246 final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
247 final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
249 final int x1 = (int) projectedPoint1.x;
250 final int y1 = (int) projectedPoint1.y;
251 final int x2 = (int) projectedPoint2.x;
252 final int y2 = (int) projectedPoint2.y;
253 final int x3 = (int) projectedPoint3.x;
254 final int y3 = (int) projectedPoint3.y;
256 renderBuffer.executeWithGraphics(g -> {
257 g.setColor(Color.YELLOW);
258 g.drawLine(x1, y1, x2, y2);
259 g.drawLine(x3, y3, x2, y2);
260 g.drawLine(x1, y1, x3, y3);