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