2cd60e74693af6ac3266714184ce64bc4ad2654e
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / renderer / raster / shapes / basic / texturedpolygon / TexturedPolygon.java
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.geometry.Point3D;
9 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
10 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape;
11 import eu.svjatoslav.sixth.e3d.renderer.raster.slicer.PolygonCoordinate;
12 import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture;
13 import eu.svjatoslav.sixth.e3d.renderer.raster.texture.TextureBitmap;
14
15 import java.awt.*;
16
17 import static eu.svjatoslav.sixth.e3d.geometry.Polygon.pointWithinPolygon;
18 import static java.util.Arrays.sort;
19
20 /**
21  * Textured polygon.
22  * <p>
23  *
24  * <pre>
25  * This is how perspective-correct texture rendering is implemented:
26  * If polygon is sufficiently small, it is rendered without perspective correction.
27  * Otherwise, it is sliced into smaller polygons.
28  * </pre>
29  */
30
31 public class TexturedPolygon extends AbstractCoordinateShape {
32
33     public final Texture texture;
34     /**
35      * Polygon texture coordinates.
36      */
37     public Point2D texturePoint1, texturePoint2, texturePoint3;
38
39     /**
40      * If <code>true</code> then polygon borders will be drawn.
41      * It is used for debugging purposes.
42      */
43     public boolean showBorders = false;
44     private double totalTextureDistance = -1;
45
46     public TexturedPolygon(final Point3D p1, final Point3D p2,
47                            final Point3D p3, final Point2D tp1, final Point2D tp2,
48                            final Point2D tp3, final Texture texture) {
49
50         super(p1, p2, p3);
51
52         texturePoint1 = tp1;
53         texturePoint2 = tp2;
54         texturePoint3 = tp3;
55
56         this.texture = texture;
57     }
58
59     public TexturedPolygon(final PolygonCoordinate pc1,
60                            final PolygonCoordinate pc2, final PolygonCoordinate pc3,
61                            final Texture texture) {
62
63         this(pc1.spaceCoordinate, pc2.spaceCoordinate, pc3.spaceCoordinate, pc1.textureCoordinate, pc2.textureCoordinate,
64                 pc3.textureCoordinate, texture);
65     }
66
67     private void computeTotalTextureDistance() {
68         // compute total texture distance
69         totalTextureDistance = texturePoint1.getDistanceTo(texturePoint2);
70         totalTextureDistance += texturePoint1.getDistanceTo(texturePoint3);
71         totalTextureDistance += texturePoint2.getDistanceTo(texturePoint3);
72     }
73
74     private void drawHorizontalLine(final PolygonBorderInterpolator line1,
75                                     final PolygonBorderInterpolator line2, final int y,
76                                     final RenderingContext renderBuffer,
77                                     final TextureBitmap textureBitmap) {
78
79         line1.setCurrentY(y);
80         line2.setCurrentY(y);
81
82         int x1 = line1.getX();
83         int x2 = line2.getX();
84
85         final double tx2, ty2;
86         final double tx1, ty1;
87
88         if (x1 <= x2) {
89
90             tx1 = line1.getTX() * textureBitmap.multiplicationFactor;
91             ty1 = line1.getTY() * textureBitmap.multiplicationFactor;
92
93             tx2 = line2.getTX() * textureBitmap.multiplicationFactor;
94             ty2 = line2.getTY() * textureBitmap.multiplicationFactor;
95
96         } else {
97             final int tmp = x1;
98             x1 = x2;
99             x2 = tmp;
100
101             tx1 = line2.getTX() * textureBitmap.multiplicationFactor;
102             ty1 = line2.getTY() * textureBitmap.multiplicationFactor;
103
104             tx2 = line1.getTX() * textureBitmap.multiplicationFactor;
105             ty2 = line1.getTY() * textureBitmap.multiplicationFactor;
106         }
107
108         final double realWidth = x2 - x1;
109         final double realX1 = x1;
110
111         if (x1 < 0)
112             x1 = 0;
113
114         if (x2 >= renderBuffer.width)
115             x2 = renderBuffer.width - 1;
116
117         int renderBufferOffset = ((y * renderBuffer.width) + x1) * 4;
118         final byte[] renderBufferBytes = renderBuffer.pixels;
119
120         final double twidth = tx2 - tx1;
121         final double theight = ty2 - ty1;
122
123         for (int x = x1; x < x2; x++) {
124
125             final double distance = x - realX1;
126
127             final double tx = tx1 + ((twidth * distance) / realWidth);
128             final double ty = ty1 + ((theight * distance) / realWidth);
129
130             final int textureOffset = textureBitmap.getAddress((int) tx,
131                     (int) ty);
132
133             textureBitmap.drawPixel(textureOffset, renderBufferBytes,
134                     renderBufferOffset);
135
136             renderBufferOffset += 4;
137         }
138
139     }
140
141     @Override
142     public void paint(final RenderingContext renderBuffer) {
143
144         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
145         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
146         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
147
148         projectedPoint1.roundToInteger();
149         projectedPoint2.roundToInteger();
150         projectedPoint3.roundToInteger();
151
152         if (mouseInteractionController != null)
153             if (renderBuffer.getMouseEvent() != null)
154                 if (pointWithinPolygon(
155                         renderBuffer.getMouseEvent().coordinate, projectedPoint1,
156                         projectedPoint2, projectedPoint3))
157                     renderBuffer.setCurrentObjectUnderMouseCursor(mouseInteractionController);
158
159         // Show polygon boundaries (for debugging)
160         if (showBorders)
161             showBorders(renderBuffer);
162
163         // find top-most point
164         int yTop = (int) projectedPoint1.y;
165
166         if (projectedPoint2.y < yTop)
167             yTop = (int) projectedPoint2.y;
168
169         if (projectedPoint3.y < yTop)
170             yTop = (int) projectedPoint3.y;
171
172         if (yTop < 0)
173             yTop = 0;
174
175         // find bottom-most point
176         int yBottom = (int) projectedPoint1.y;
177
178         if (projectedPoint2.y > yBottom)
179             yBottom = (int) projectedPoint2.y;
180
181         if (projectedPoint3.y > yBottom)
182             yBottom = (int) projectedPoint3.y;
183
184         if (yBottom >= renderBuffer.height)
185             yBottom = renderBuffer.height - 1;
186
187         // paint
188         double totalVisibleDistance = projectedPoint1.getDistanceTo(projectedPoint2);
189         totalVisibleDistance += projectedPoint1.getDistanceTo(projectedPoint3);
190         totalVisibleDistance += projectedPoint2.getDistanceTo(projectedPoint3);
191
192         if (totalTextureDistance == -1)
193             computeTotalTextureDistance();
194         final double scaleFactor = (totalVisibleDistance / totalTextureDistance) * 1.2d;
195
196         final TextureBitmap zoomedBitmap = texture.getZoomedBitmap(scaleFactor);
197
198         final PolygonBorderInterpolator[] is = new PolygonBorderInterpolator[]{
199                 new PolygonBorderInterpolator(), new PolygonBorderInterpolator(),
200                 new PolygonBorderInterpolator()};
201
202         is[0].setPoints(projectedPoint1, projectedPoint2, texturePoint1,
203                 texturePoint2);
204         is[1].setPoints(projectedPoint1, projectedPoint3, texturePoint1,
205                 texturePoint3);
206         is[2].setPoints(projectedPoint2, projectedPoint3, texturePoint2,
207                 texturePoint3);
208
209         sort(is);
210
211         for (int y = yTop; y < yBottom; y++)
212             if (is[0].containsY(y)) {
213
214                 if (is[1].containsY(y))
215                     drawHorizontalLine(is[0], is[1], y, renderBuffer,
216                             zoomedBitmap);
217                 else if (is[2].containsY(y))
218                     drawHorizontalLine(is[0], is[2], y, renderBuffer,
219                             zoomedBitmap);
220             } else if (is[1].containsY(y))
221                 if (is[2].containsY(y))
222                     drawHorizontalLine(is[1], is[2], y, renderBuffer,
223                             zoomedBitmap);
224
225     }
226
227     private void showBorders(final RenderingContext renderBuffer) {
228
229         final Point2D projectedPoint1 = coordinates[0].onScreenCoordinate;
230         final Point2D projectedPoint2 = coordinates[1].onScreenCoordinate;
231         final Point2D projectedPoint3 = coordinates[2].onScreenCoordinate;
232
233         renderBuffer.graphics.setColor(Color.YELLOW);
234         renderBuffer.graphics.drawLine((int) projectedPoint1.x,
235                 (int) projectedPoint1.y, (int) projectedPoint2.x,
236                 (int) projectedPoint2.y);
237         renderBuffer.graphics.drawLine((int) projectedPoint3.x,
238                 (int) projectedPoint3.y, (int) projectedPoint2.x,
239                 (int) projectedPoint2.y);
240         renderBuffer.graphics.drawLine((int) projectedPoint1.x,
241                 (int) projectedPoint1.y, (int) projectedPoint3.x,
242                 (int) projectedPoint3.y);
243     }
244
245 }