Updated readability of the code.
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / renderer / raster / shapes / basic / line / Line.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.line;
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.Color;
11 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractCoordinateShape;
12
13 /**
14  * Line in 3D space.
15  *
16  * Line is represented by two points, width and color and width.
17  */
18 public class Line extends AbstractCoordinateShape {
19
20     private static final double MINIMUM_WIDTH_THRESHOLD = 1;
21
22     private static final double LINE_WIDTH_MULTIPLIER = 0.2d;
23
24     /**
25      * width of the line.
26      */
27     public final double width;
28     final LineInterpolator[] lineInterpolators = new LineInterpolator[4];
29
30     /**
31      * Color of the line.
32      */
33     public Color color;
34
35     public Line(final Line parentLine) {
36         this(parentLine.coordinates[0].coordinate.clone(),
37                 parentLine.coordinates[1].coordinate.clone(),
38                 new Color(parentLine.color), parentLine.width);
39     }
40
41     public Line(final Point3D point1, final Point3D point2, final Color color,
42                 final double width) {
43
44         super(point1, point2);
45
46         this.color = color;
47         this.width = width;
48
49         for (int i = 0; i < lineInterpolators.length; i++)
50             lineInterpolators[i] = new LineInterpolator();
51
52     }
53
54     private void drawHorizontalLine(final LineInterpolator line1,
55                                     final LineInterpolator line2, final int y,
56                                     final RenderingContext renderBuffer) {
57
58         int x1 = line1.getX(y);
59         int x2 = line2.getX(y);
60
61         double d1 = line1.getD();
62         double d2 = line2.getD();
63
64         if (x1 > x2) {
65             final int tmp = x1;
66             x1 = x2;
67             x2 = tmp;
68
69             final double tmp2 = d1;
70             d1 = d2;
71             d2 = tmp2;
72         }
73
74         final int unclippedWidth = x2 - x1;
75         final double dinc = (d2 - d1) / unclippedWidth;
76
77         if (x1 < 0) {
78             d1 += (dinc * (-x1));
79             x1 = 0;
80         }
81
82         if (x2 >= renderBuffer.width)
83             x2 = renderBuffer.width - 1;
84
85         final int drawnWidth = x2 - x1;
86
87         int offset = ((y * renderBuffer.width) + x1) * 4;
88         final byte[] offSreenBufferBytes = renderBuffer.pixels;
89
90         final int lineAlpha = color.a;
91
92         final int colorB = color.b;
93         final int colorG = color.g;
94         final int colorR = color.r;
95
96         for (int i = 0; i < drawnWidth; i++) {
97
98             final double alphaMultiplier = 1d - Math.abs(d1);
99
100             final int realLineAlpha = (int) (lineAlpha * alphaMultiplier);
101             final int backgroundAlpha = 255 - realLineAlpha;
102
103             offSreenBufferBytes[offset] = (byte) 255;
104             offset++;
105             offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorB * realLineAlpha)) / 256);
106             offset++;
107             offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorG * realLineAlpha)) / 256);
108             offset++;
109             offSreenBufferBytes[offset] = (byte) ((((offSreenBufferBytes[offset] & 0xff) * backgroundAlpha) + (colorR * realLineAlpha)) / 256);
110             offset++;
111
112             d1 += dinc;
113         }
114
115     }
116
117     private void drawSinglePixelHorizontalLine(final RenderingContext buffer,
118                                                final int alpha) {
119
120         final Point2D onScreenPoint1 = coordinates[0].onScreenCoordinate;
121         final Point2D onScreenPoint2 = coordinates[1].onScreenCoordinate;
122
123         int xStart = (int) onScreenPoint1.x;
124         int xEnd = (int) onScreenPoint2.x;
125
126         int lineHeight;
127         int yBase;
128
129         if (xStart > xEnd) {
130             final int tmp = xStart;
131             xStart = xEnd;
132             xEnd = tmp;
133             lineHeight = (int) (onScreenPoint1.y - onScreenPoint2.y);
134             yBase = (int) onScreenPoint2.y;
135         } else {
136             yBase = (int) onScreenPoint1.y;
137             lineHeight = (int) (onScreenPoint2.y - onScreenPoint1.y);
138         }
139
140         final int lineWidth = xEnd - xStart;
141         if (lineWidth == 0)
142             return;
143
144         final byte[] offSreenBufferBytes = buffer.pixels;
145         final int backgroundAlpha = 255 - alpha;
146
147         final int blueWithAlpha = color.b * alpha;
148         final int greenWithAplha = color.g * alpha;
149         final int redWithAlpha = color.r * alpha;
150
151         for (int relativeX = 0; relativeX <= lineWidth; relativeX++) {
152             final int x = xStart + relativeX;
153
154             if ((x >= 0) && (x < buffer.width)) {
155
156                 final int y = yBase + ((relativeX * lineHeight) / lineWidth);
157                 if ((y >= 0) && (y < buffer.height)) {
158                     int ramOffset = ((y * buffer.width) + x) * 4;
159
160                     offSreenBufferBytes[ramOffset] = (byte) 255;
161                     ramOffset++;
162                     offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256);
163                     ramOffset++;
164                     offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAplha) / 256);
165                     ramOffset++;
166                     offSreenBufferBytes[ramOffset] = (byte) ((((offSreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256);
167                 }
168             }
169         }
170
171     }
172
173     private void drawSinglePixelVerticalLine(final RenderingContext buffer,
174                                              final int alpha) {
175
176         final Point2D onScreenPoint1 = coordinates[0].onScreenCoordinate;
177         final Point2D onScreenPoint2 = coordinates[1].onScreenCoordinate;
178
179         int yStart = (int) onScreenPoint1.y;
180         int yEnd = (int) onScreenPoint2.y;
181
182         int lineWidth;
183         int xBase;
184
185         if (yStart > yEnd) {
186             final int tmp = yStart;
187             yStart = yEnd;
188             yEnd = tmp;
189             lineWidth = (int) (onScreenPoint1.x - onScreenPoint2.x);
190             xBase = (int) onScreenPoint2.x;
191         } else {
192             xBase = (int) onScreenPoint1.x;
193             lineWidth = (int) (onScreenPoint2.x - onScreenPoint1.x);
194         }
195
196         final int lineHeight = yEnd - yStart;
197         if (lineHeight == 0)
198             return;
199
200         final byte[] offScreenBufferBytes = buffer.pixels;
201         final int backgroundAlpha = 255 - alpha;
202
203         final int blueWithAlpha = color.b * alpha;
204         final int greenWithAlpha = color.g * alpha;
205         final int redWithAlpha = color.r * alpha;
206
207         for (int relativeY = 0; relativeY <= lineHeight; relativeY++) {
208             final int y = yStart + relativeY;
209
210             if ((y >= 0) && (y < buffer.height)) {
211
212                 final int x = xBase + ((relativeY * lineWidth) / lineHeight);
213                 if ((x >= 0) && (x < buffer.width)) {
214                     int ramOffset = ((y * buffer.width) + x) * 4;
215
216                     offScreenBufferBytes[ramOffset] = (byte) 255;
217                     ramOffset++;
218                     offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + blueWithAlpha) / 256);
219                     ramOffset++;
220                     offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + greenWithAlpha) / 256);
221                     ramOffset++;
222                     offScreenBufferBytes[ramOffset] = (byte) ((((offScreenBufferBytes[ramOffset] & 0xff) * backgroundAlpha) + redWithAlpha) / 256);
223                 }
224             }
225         }
226     }
227
228     private int getLineInterpolator(final int startPointer, final int y) {
229
230         for (int i = startPointer; i < lineInterpolators.length; i++)
231             if (lineInterpolators[i].containsY(y))
232                 return i;
233         return -1;
234     }
235
236     @Override
237     public void paint(final RenderingContext buffer) {
238
239         final Point2D onScreenPoint1 = coordinates[0].onScreenCoordinate;
240         final Point2D onScreenPoint2 = coordinates[1].onScreenCoordinate;
241
242         final double xp = onScreenPoint2.x - onScreenPoint1.x;
243         final double yp = onScreenPoint2.y - onScreenPoint1.y;
244
245         final double point1radius = (buffer.width * LINE_WIDTH_MULTIPLIER * width)
246                 / coordinates[0].transformedCoordinate.z;
247         final double point2radius = (buffer.width * LINE_WIDTH_MULTIPLIER * width)
248                 / coordinates[1].transformedCoordinate.z;
249
250         if ((point1radius < MINIMUM_WIDTH_THRESHOLD)
251                 || (point2radius < MINIMUM_WIDTH_THRESHOLD)) {
252
253             double averageRadius = (point1radius + point2radius) / 2;
254
255             if (averageRadius > 1)
256                 averageRadius = 1;
257
258             final int alpha = (int) (color.a * averageRadius);
259             if (alpha < 2)
260                 return;
261
262             if (Math.abs(xp) > Math.abs(yp))
263                 drawSinglePixelHorizontalLine(buffer, alpha);
264             else
265                 drawSinglePixelVerticalLine(buffer, alpha);
266             return;
267         }
268
269         final double lineLength = Math.sqrt((xp * xp) + (yp * yp));
270
271         final double yinc1 = (point1radius * xp) / lineLength;
272         final double yinc2 = (point2radius * xp) / lineLength;
273
274         final double xdec1 = (point1radius * yp) / lineLength;
275         final double xdec2 = (point2radius * yp) / lineLength;
276
277         final double p1x1 = onScreenPoint1.x - xdec1;
278         final double p1y1 = onScreenPoint1.y + yinc1;
279
280         final double p1x2 = onScreenPoint1.x + xdec1;
281         final double p1y2 = onScreenPoint1.y - yinc1;
282
283         final double p2x1 = onScreenPoint2.x - xdec2;
284         final double p2y1 = onScreenPoint2.y + yinc2;
285
286         final double p2x2 = onScreenPoint2.x + xdec2;
287         final double p2y2 = onScreenPoint2.y - yinc2;
288
289         lineInterpolators[0].setPoints(p1x1, p1y1, 1d, p2x1, p2y1, 1d);
290         lineInterpolators[1].setPoints(p1x2, p1y2, -1d, p2x2, p2y2, -1d);
291
292         lineInterpolators[2].setPoints(p1x1, p1y1, 1d, p1x2, p1y2, -1d);
293         lineInterpolators[3].setPoints(p2x1, p2y1, 1d, p2x2, p2y2, -1d);
294
295         double ymin = p1y1;
296         if (p1y2 < ymin)
297             ymin = p1y2;
298         if (p2y1 < ymin)
299             ymin = p2y1;
300         if (p2y2 < ymin)
301             ymin = p2y2;
302         if (ymin < 0)
303             ymin = 0;
304
305         double ymax = p1y1;
306         if (p1y2 > ymax)
307             ymax = p1y2;
308         if (p2y1 > ymax)
309             ymax = p2y1;
310         if (p2y2 > ymax)
311             ymax = p2y2;
312         if (ymax >= buffer.height)
313             ymax = buffer.height - 1;
314
315         for (int y = (int) ymin; y <= ymax; y++) {
316             final int li1 = getLineInterpolator(0, y);
317             if (li1 != -1) {
318                 final int li2 = getLineInterpolator(li1 + 1, y);
319                 if (li2 != -1)
320                     drawHorizontalLine(lineInterpolators[li1], lineInterpolators[li2], y, buffer);
321             }
322         }
323     }
324
325 }