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