45e9b98526f99010ece944787b4dea83bbb7abd9
[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 PolygonBorderInterpolator polygonBorder1 = new PolygonBorderInterpolator();
32     private static final PolygonBorderInterpolator polygonBorder2 = new PolygonBorderInterpolator();
33     private static final PolygonBorderInterpolator polygonBorder3 = new PolygonBorderInterpolator();
34
35     public final Texture texture;
36
37     /**
38      * If <code>true</code> then polygon borders will be drawn.
39      * It is used for debugging purposes.
40      */
41     public boolean showBorders = false;
42     private boolean backfaceCulling = false;
43
44     private double totalTextureDistance = -1;
45
46     public TexturedPolygon(Vertex p1, Vertex p2, Vertex p3, final Texture texture) {
47
48         super(p1, p2, p3);
49         this.texture = texture;
50     }
51
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);
57     }
58
59     private void drawHorizontalLine(final PolygonBorderInterpolator line1,
60                                     final PolygonBorderInterpolator line2, final int y,
61                                     final RenderingContext renderBuffer,
62                                     final TextureBitmap textureBitmap) {
63
64         line1.setCurrentY(y);
65         line2.setCurrentY(y);
66
67         int x1 = line1.getX();
68         int x2 = line2.getX();
69
70         final double tx2, ty2;
71         final double tx1, ty1;
72
73         if (x1 <= x2) {
74
75             tx1 = line1.getTX() * textureBitmap.multiplicationFactor;
76             ty1 = line1.getTY() * textureBitmap.multiplicationFactor;
77
78             tx2 = line2.getTX() * textureBitmap.multiplicationFactor;
79             ty2 = line2.getTY() * textureBitmap.multiplicationFactor;
80
81         } else {
82             final int tmp = x1;
83             x1 = x2;
84             x2 = tmp;
85
86             tx1 = line2.getTX() * textureBitmap.multiplicationFactor;
87             ty1 = line2.getTY() * textureBitmap.multiplicationFactor;
88
89             tx2 = line1.getTX() * textureBitmap.multiplicationFactor;
90             ty2 = line1.getTY() * textureBitmap.multiplicationFactor;
91         }
92
93         final double realWidth = x2 - x1;
94         final double realX1 = x1;
95
96         if (x1 < 0)
97             x1 = 0;
98
99         if (x2 >= renderBuffer.width)
100             x2 = renderBuffer.width - 1;
101
102         int renderBufferOffset = ((y * renderBuffer.width) + x1) * 4;
103         final byte[] renderBufferBytes = renderBuffer.pixels;
104
105         final double twidth = tx2 - tx1;
106         final double theight = ty2 - ty1;
107
108         for (int x = x1; x < x2; x++) {
109
110             final double distance = x - realX1;
111
112             final double tx = tx1 + ((twidth * distance) / realWidth);
113             final double ty = ty1 + ((theight * distance) / realWidth);
114
115             final int textureOffset = textureBitmap.getAddress((int) tx,
116                     (int) ty);
117
118             textureBitmap.drawPixel(textureOffset, renderBufferBytes,
119                     renderBufferOffset);
120
121             renderBufferOffset += 4;
122         }
123
124     }
125
126     @Override
127     public void paint(final RenderingContext renderBuffer) {
128
129         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
130         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
131         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
132
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);
138             if (signedArea >= 0)
139                 return;
140         }
141
142         projectedPoint1.roundToInteger();
143         projectedPoint2.roundToInteger();
144         projectedPoint3.roundToInteger();
145
146         if (mouseInteractionController != null)
147             if (renderBuffer.getMouseEvent() != null)
148                 if (pointWithinPolygon(
149                         renderBuffer.getMouseEvent().coordinate, projectedPoint1,
150                         projectedPoint2, projectedPoint3))
151                     renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
152
153         // Show polygon boundaries (for debugging)
154         if (showBorders)
155             showBorders(renderBuffer);
156
157         // find top-most point
158         int yTop = (int) projectedPoint1.y;
159
160         if (projectedPoint2.y < yTop)
161             yTop = (int) projectedPoint2.y;
162
163         if (projectedPoint3.y < yTop)
164             yTop = (int) projectedPoint3.y;
165
166         if (yTop < 0)
167             yTop = 0;
168
169         // find bottom-most point
170         int yBottom = (int) projectedPoint1.y;
171
172         if (projectedPoint2.y > yBottom)
173             yBottom = (int) projectedPoint2.y;
174
175         if (projectedPoint3.y > yBottom)
176             yBottom = (int) projectedPoint3.y;
177
178         if (yBottom >= renderBuffer.height)
179             yBottom = renderBuffer.height - 1;
180
181         // paint
182         double totalVisibleDistance = projectedPoint1.getDistanceTo(projectedPoint2);
183         totalVisibleDistance += projectedPoint1.getDistanceTo(projectedPoint3);
184         totalVisibleDistance += projectedPoint2.getDistanceTo(projectedPoint3);
185
186         if (totalTextureDistance == -1)
187             computeTotalTextureDistance();
188         final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
189
190         final TextureBitmap zoomedBitmap = texture.getZoomedBitmap(scaleFactor);
191
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);
201
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; }
210
211         for (int y = yTop; y < yBottom; y++)
212             if (a.containsY(y)) {
213                 if (b.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))
218                 if (c.containsY(y))
219                     drawHorizontalLine(b, c, y, renderBuffer, zoomedBitmap);
220
221     }
222
223     public boolean isBackfaceCullingEnabled() {
224         return backfaceCulling;
225     }
226
227     public void setBackfaceCulling(final boolean backfaceCulling) {
228         this.backfaceCulling = backfaceCulling;
229     }
230
231     private void showBorders(final RenderingContext renderBuffer) {
232
233         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
234         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
235         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
236
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);
247     }
248
249 }