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 PolygonBorderInterpolator polygonBorder1 = new PolygonBorderInterpolator();
32 private static final PolygonBorderInterpolator polygonBorder2 = new PolygonBorderInterpolator();
33 private static final PolygonBorderInterpolator polygonBorder3 = new PolygonBorderInterpolator();
35 public final Texture texture;
38 * If <code>true</code> then polygon borders will be drawn.
39 * It is used for debugging purposes.
41 public boolean showBorders = false;
42 private boolean backfaceCulling = false;
44 private double totalTextureDistance = -1;
46 public TexturedPolygon(Vertex p1, Vertex p2, Vertex p3, final Texture texture) {
49 this.texture = texture;
52 private void computeTotalTextureDistance() {
53 // compute total texture distance
54 totalTextureDistance = coordinates[0].textureCoordinate.getDistanceTo(coordinates[1].textureCoordinate);
55 totalTextureDistance += coordinates[0].textureCoordinate.getDistanceTo(coordinates[2].textureCoordinate);
56 totalTextureDistance += coordinates[1].textureCoordinate.getDistanceTo(coordinates[2].textureCoordinate);
59 private void drawHorizontalLine(final PolygonBorderInterpolator line1,
60 final PolygonBorderInterpolator line2, final int y,
61 final RenderingContext renderBuffer,
62 final TextureBitmap textureBitmap) {
67 int x1 = line1.getX();
68 int x2 = line2.getX();
70 final double tx2, ty2;
71 final double tx1, ty1;
75 tx1 = line1.getTX() * textureBitmap.multiplicationFactor;
76 ty1 = line1.getTY() * textureBitmap.multiplicationFactor;
78 tx2 = line2.getTX() * textureBitmap.multiplicationFactor;
79 ty2 = line2.getTY() * textureBitmap.multiplicationFactor;
86 tx1 = line2.getTX() * textureBitmap.multiplicationFactor;
87 ty1 = line2.getTY() * textureBitmap.multiplicationFactor;
89 tx2 = line1.getTX() * textureBitmap.multiplicationFactor;
90 ty2 = line1.getTY() * textureBitmap.multiplicationFactor;
93 final double realWidth = x2 - x1;
94 final double realX1 = x1;
99 if (x2 >= renderBuffer.width)
100 x2 = renderBuffer.width - 1;
102 int renderBufferOffset = ((y * renderBuffer.width) + x1) * 4;
103 final byte[] renderBufferBytes = renderBuffer.pixels;
105 final double twidth = tx2 - tx1;
106 final double theight = ty2 - ty1;
108 for (int x = x1; x < x2; x++) {
110 final double distance = x - realX1;
112 final double tx = tx1 + ((twidth * distance) / realWidth);
113 final double ty = ty1 + ((theight * distance) / realWidth);
115 final int textureOffset = textureBitmap.getAddress((int) tx,
118 textureBitmap.drawPixel(textureOffset, renderBufferBytes,
121 renderBufferOffset += 4;
127 public void paint(final RenderingContext renderBuffer) {
129 final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
130 final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
131 final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
133 if (backfaceCulling) {
134 final double signedArea = (projectedPoint2.x - projectedPoint1.x)
135 * (projectedPoint3.y - projectedPoint1.y)
136 - (projectedPoint3.x - projectedPoint1.x)
137 * (projectedPoint2.y - projectedPoint1.y);
142 projectedPoint1.roundToInteger();
143 projectedPoint2.roundToInteger();
144 projectedPoint3.roundToInteger();
146 if (mouseInteractionController != null)
147 if (renderBuffer.getMouseEvent() != null)
148 if (pointWithinPolygon(
149 renderBuffer.getMouseEvent().coordinate, projectedPoint1,
150 projectedPoint2, projectedPoint3))
151 renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
153 // Show polygon boundaries (for debugging)
155 showBorders(renderBuffer);
157 // find top-most point
158 int yTop = (int) projectedPoint1.y;
160 if (projectedPoint2.y < yTop)
161 yTop = (int) projectedPoint2.y;
163 if (projectedPoint3.y < yTop)
164 yTop = (int) projectedPoint3.y;
169 // find bottom-most point
170 int yBottom = (int) projectedPoint1.y;
172 if (projectedPoint2.y > yBottom)
173 yBottom = (int) projectedPoint2.y;
175 if (projectedPoint3.y > yBottom)
176 yBottom = (int) projectedPoint3.y;
178 if (yBottom >= renderBuffer.height)
179 yBottom = renderBuffer.height - 1;
182 double totalVisibleDistance = projectedPoint1.getDistanceTo(projectedPoint2);
183 totalVisibleDistance += projectedPoint1.getDistanceTo(projectedPoint3);
184 totalVisibleDistance += projectedPoint2.getDistanceTo(projectedPoint3);
186 if (totalTextureDistance == -1)
187 computeTotalTextureDistance();
188 final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
190 final TextureBitmap zoomedBitmap = texture.getZoomedBitmap(scaleFactor);
192 polygonBorder1.setPoints(projectedPoint1, projectedPoint2,
193 coordinates[0].textureCoordinate,
194 coordinates[1].textureCoordinate);
195 polygonBorder2.setPoints(projectedPoint1, projectedPoint3,
196 coordinates[0].textureCoordinate,
197 coordinates[2].textureCoordinate);
198 polygonBorder3.setPoints(projectedPoint2, projectedPoint3,
199 coordinates[1].textureCoordinate,
200 coordinates[2].textureCoordinate);
202 // Inline sort for 3 elements to avoid array allocation
203 PolygonBorderInterpolator a = polygonBorder1;
204 PolygonBorderInterpolator b = polygonBorder2;
205 PolygonBorderInterpolator c = polygonBorder3;
206 PolygonBorderInterpolator t;
207 if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
208 if (b.compareTo(c) > 0) { t = b; b = c; c = t; }
209 if (a.compareTo(b) > 0) { t = a; a = b; b = t; }
211 for (int y = yTop; y < yBottom; y++)
212 if (a.containsY(y)) {
214 drawHorizontalLine(a, b, y, renderBuffer, zoomedBitmap);
215 else if (c.containsY(y))
216 drawHorizontalLine(a, c, y, renderBuffer, zoomedBitmap);
217 } else if (b.containsY(y))
219 drawHorizontalLine(b, c, y, renderBuffer, zoomedBitmap);
223 public boolean isBackfaceCullingEnabled() {
224 return backfaceCulling;
227 public void setBackfaceCulling(final boolean backfaceCulling) {
228 this.backfaceCulling = backfaceCulling;
231 private void showBorders(final RenderingContext renderBuffer) {
233 final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
234 final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
235 final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
237 renderBuffer.graphics.setColor(Color.YELLOW);
238 renderBuffer.graphics.drawLine((int) projectedPoint1.x,
239 (int) projectedPoint1.y, (int) projectedPoint2.x,
240 (int) projectedPoint2.y);
241 renderBuffer.graphics.drawLine((int) projectedPoint3.x,
242 (int) projectedPoint3.y, (int) projectedPoint2.x,
243 (int) projectedPoint2.y);
244 renderBuffer.graphics.drawLine((int) projectedPoint1.x,
245 (int) projectedPoint1.y, (int) projectedPoint3.x,
246 (int) projectedPoint3.y);