9237fb01cb34fc73f6a36d5aa377cc6935980382
[sixth-3d.git] /
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko.
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon;
6
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;
13
14 import java.awt.Color;
15
16 import static eu.svjatoslav.sixth.e3d.geometry.Polygon.pointWithinPolygon;
17
18 /**
19  * Textured polygon.
20  * <p>
21  *
22  * <pre>
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.
26  * </pre>
27  */
28
29 public class TexturedPolygon extends AbstractCoordinateShape {
30
31     private static final ThreadLocal<PolygonBorderInterpolator[]> INTERPOLATORS =
32             ThreadLocal.withInitial(() -> new PolygonBorderInterpolator[]{
33                     new PolygonBorderInterpolator(), new PolygonBorderInterpolator(), new PolygonBorderInterpolator()
34             });
35
36     public final Texture texture;
37
38     /**
39      * If <code>true</code> then polygon borders will be drawn.
40      * It is used for debugging purposes.
41      */
42     public boolean showBorders = false;
43     private boolean backfaceCulling = false;
44
45     private double totalTextureDistance = -1;
46
47     public TexturedPolygon(Vertex p1, Vertex p2, Vertex p3, final Texture texture) {
48
49         super(p1, p2, p3);
50         this.texture = texture;
51     }
52
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);
58     }
59
60     private void drawHorizontalLine(final PolygonBorderInterpolator line1,
61                                     final PolygonBorderInterpolator line2, final int y,
62                                     final RenderingContext renderBuffer,
63                                     final TextureBitmap textureBitmap) {
64
65         line1.setCurrentY(y);
66         line2.setCurrentY(y);
67
68         int x1 = line1.getX();
69         int x2 = line2.getX();
70
71         final double tx2, ty2;
72         final double tx1, ty1;
73
74         if (x1 <= x2) {
75
76             tx1 = line1.getTX() * textureBitmap.multiplicationFactor;
77             ty1 = line1.getTY() * textureBitmap.multiplicationFactor;
78
79             tx2 = line2.getTX() * textureBitmap.multiplicationFactor;
80             ty2 = line2.getTY() * textureBitmap.multiplicationFactor;
81
82         } else {
83             final int tmp = x1;
84             x1 = x2;
85             x2 = tmp;
86
87             tx1 = line2.getTX() * textureBitmap.multiplicationFactor;
88             ty1 = line2.getTY() * textureBitmap.multiplicationFactor;
89
90             tx2 = line1.getTX() * textureBitmap.multiplicationFactor;
91             ty2 = line1.getTY() * textureBitmap.multiplicationFactor;
92         }
93
94         final double realWidth = x2 - x1;
95         final double realX1 = x1;
96
97         if (x1 < 0)
98             x1 = 0;
99
100         if (x2 >= renderBuffer.width)
101             x2 = renderBuffer.width - 1;
102
103         int renderBufferOffset = (y * renderBuffer.width) + x1;
104         final int[] renderBufferPixels = renderBuffer.pixels;
105
106         final double twidth = tx2 - tx1;
107         final double theight = ty2 - ty1;
108
109         for (int x = x1; x < x2; x++) {
110
111             final double distance = x - realX1;
112
113             final double tx = tx1 + ((twidth * distance) / realWidth);
114             final double ty = ty1 + ((theight * distance) / realWidth);
115
116             final int textureOffset = textureBitmap.getAddress((int) tx,
117                     (int) ty);
118
119             textureBitmap.drawPixel(textureOffset, renderBufferPixels,
120                     renderBufferOffset);
121
122             renderBufferOffset++;
123         }
124
125     }
126
127     @Override
128     public void paint(final RenderingContext renderBuffer) {
129
130         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
131         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
132         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
133
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);
139             if (signedArea >= 0)
140                 return;
141         }
142
143         projectedPoint1.roundToInteger();
144         projectedPoint2.roundToInteger();
145         projectedPoint3.roundToInteger();
146
147         if (mouseInteractionController != null)
148             if (renderBuffer.getMouseEvent() != null)
149                 if (pointWithinPolygon(
150                         renderBuffer.getMouseEvent().coordinate, projectedPoint1,
151                         projectedPoint2, projectedPoint3))
152                     renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
153
154         // Show polygon boundaries (for debugging)
155         if (showBorders)
156             showBorders(renderBuffer);
157
158         // find top-most point
159         int yTop = (int) projectedPoint1.y;
160
161         if (projectedPoint2.y < yTop)
162             yTop = (int) projectedPoint2.y;
163
164         if (projectedPoint3.y < yTop)
165             yTop = (int) projectedPoint3.y;
166
167         if (yTop < 0)
168             yTop = 0;
169
170         // find bottom-most point
171         int yBottom = (int) projectedPoint1.y;
172
173         if (projectedPoint2.y > yBottom)
174             yBottom = (int) projectedPoint2.y;
175
176         if (projectedPoint3.y > yBottom)
177             yBottom = (int) projectedPoint3.y;
178
179         if (yBottom >= renderBuffer.height)
180             yBottom = renderBuffer.height - 1;
181
182         // clamp to render Y bounds
183         yTop = Math.max(yTop, renderBuffer.renderMinY);
184         yBottom = Math.min(yBottom, renderBuffer.renderMaxY);
185         if (yTop >= yBottom)
186             return;
187
188         // paint
189         double totalVisibleDistance = projectedPoint1.getDistanceTo(projectedPoint2);
190         totalVisibleDistance += projectedPoint1.getDistanceTo(projectedPoint3);
191         totalVisibleDistance += projectedPoint2.getDistanceTo(projectedPoint3);
192
193         if (totalTextureDistance == -1)
194             computeTotalTextureDistance();
195         final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
196
197         final TextureBitmap zoomedBitmap = texture.getZoomedBitmap(scaleFactor);
198
199         final PolygonBorderInterpolator[] interp = INTERPOLATORS.get();
200         final PolygonBorderInterpolator polygonBorder1 = interp[0];
201         final PolygonBorderInterpolator polygonBorder2 = interp[1];
202         final PolygonBorderInterpolator polygonBorder3 = interp[2];
203
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);
213
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; }
222
223         for (int y = yTop; y < yBottom; y++)
224             if (a.containsY(y)) {
225                 if (b.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))
230                 if (c.containsY(y))
231                     drawHorizontalLine(b, c, y, renderBuffer, zoomedBitmap);
232
233     }
234
235     public boolean isBackfaceCullingEnabled() {
236         return backfaceCulling;
237     }
238
239     public void setBackfaceCulling(final boolean backfaceCulling) {
240         this.backfaceCulling = backfaceCulling;
241     }
242
243     private void showBorders(final RenderingContext renderBuffer) {
244
245         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
246         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
247         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
248
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;
255
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);
261         });
262     }
263
264 }