1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.layoutlib.bridge.shadowutil;
18
19import android.annotation.NonNull;
20
21public class SpotShadow {
22
23    private static float rayIntersectPoly(@NonNull float[] poly, int polyLength, float px, float py,
24            float dx, float dy) {
25        int p1 = polyLength - 1;
26        for (int p2 = 0; p2 < polyLength; p2++) {
27            float p1x = poly[p1 * 2 + 0];
28            float p1y = poly[p1 * 2 + 1];
29            float p2x = poly[p2 * 2 + 0];
30            float p2y = poly[p2 * 2 + 1];
31            float div = (dx * (p1y - p2y) + dy * p2x - dy * p1x);
32            if (div != 0) {
33                float t = (dx * (p1y - py) + dy * px - dy * p1x) / (div);
34                if (t >= 0 && t <= 1) {
35                    float t2 = (p1x * (py - p2y) + p2x * (p1y - py) + px * (p2y - p1y)) / div;
36                    if (t2 > 0) {
37                        return t2;
38                    }
39                }
40            }
41            p1 = p2;
42        }
43        return Float.NaN;
44    }
45
46    private static void centroid2d(@NonNull float[] poly, int len, @NonNull float[] ret) {
47        float sumX = 0;
48        float sumY = 0;
49        int p1 = len - 1;
50        float area = 0;
51        for (int p2 = 0; p2 < len; p2++) {
52            float x1 = poly[p1 * 2 + 0];
53            float y1 = poly[p1 * 2 + 1];
54            float x2 = poly[p2 * 2 + 0];
55            float y2 = poly[p2 * 2 + 1];
56            float a = (x1 * y2 - x2 * y1);
57            sumX += (x1 + x2) * a;
58            sumY += (y1 + y2) * a;
59            area += a;
60            p1 = p2;
61        }
62
63        float centroidX = sumX / (3 * area);
64        float centroidY = sumY / (3 * area);
65        ret[0] = centroidX;
66        ret[1] = centroidY;
67    }
68
69    /**
70     * calculates the Centroid of a 3d polygon
71     * @param poly The flatten 3d vertices coordinates of polygon, the format is like
72     * [x0, y0, z0, x1, y1, z1, x2, ...]
73     * @param len The number of polygon vertices. So the length of poly should be len * 3.
74     * @param ret The array used to sotre the result. The length should be 3.
75     */
76    private static void centroid3d(@NonNull float[] poly, int len, @NonNull float[] ret) {
77        int n = len - 1;
78        double area = 0;
79        double cx = 0, cy = 0, cz = 0;
80        for (int i = 1; i < n; i++) {
81            int k = i + 1;
82            float a0 = poly[i * 3 + 0] - poly[0 * 3 + 0];
83            float a1 = poly[i * 3 + 1] - poly[0 * 3 + 1];
84            float a2 = poly[i * 3 + 2] - poly[0 * 3 + 2];
85            float b0 = poly[k * 3 + 0] - poly[0 * 3 + 0];
86            float b1 = poly[k * 3 + 1] - poly[0 * 3 + 1];
87            float b2 = poly[k * 3 + 2] - poly[0 * 3 + 2];
88            float c0 = a1 * b2 - b1 * a2;
89            float c1 = a2 * b0 - b2 * a0;
90            float c2 = a0 * b1 - b0 * a1;
91            double areaOfTriangle = Math.sqrt(c0 * c0 + c1 * c1 + c2 * c2);
92            area += areaOfTriangle;
93            cx += areaOfTriangle * (poly[i * 3 + 0] + poly[k * 3 + 0] + poly[0 * 3 + 0]);
94            cy += areaOfTriangle * (poly[i * 3 + 1] + poly[k * 3 + 1] + poly[0 * 3 + 1]);
95            cz += areaOfTriangle * (poly[i * 3 + 2] + poly[k * 3 + 2] + poly[0 * 3 + 2]);
96        }
97        ret[0] = (float) (cx / (3 * area));
98        ret[1] = (float) (cy / (3 * area));
99        ret[2] = (float) (cz / (3 * area));
100    }
101
102    /**
103     * Extracts the convex hull of a polygon.
104     * @param points The vertices coordinates of polygon
105     * @param pointsLength The number of polygon vertices. So the length of poly should be len * 3.
106     * @param retPoly retPoly is at most the size of the input polygon
107     * @return The number of points in the retPolygon
108     */
109    private static int hull(@NonNull float[] points, int pointsLength, @NonNull float[] retPoly) {
110        quicksortX(points, 0, pointsLength - 1);
111        int n = pointsLength;
112        float[] lUpper = new float[n * 2];
113        lUpper[0] = points[0];
114        lUpper[1] = points[1];
115        lUpper[2] = points[2];
116        lUpper[3] = points[3];
117
118        int lUpperSize = 2;
119
120        for (int i = 2; i < n; i++) {
121            lUpper[lUpperSize * 2 + 0] = points[i * 2 + 0];
122            lUpper[lUpperSize * 2 + 1] = points[i * 2 + 1];
123            lUpperSize++;
124
125            while (lUpperSize > 2 &&
126                    !rightTurn(lUpper[(lUpperSize - 3) * 2], lUpper[(lUpperSize - 3) * 2 + 1],
127                            lUpper[(lUpperSize - 2) * 2], lUpper[(lUpperSize - 2) * 2 + 1],
128                            lUpper[(lUpperSize - 1) * 2], lUpper[(lUpperSize - 1) * 2 + 1])) {
129                // Remove the middle point of the three last
130                lUpper[(lUpperSize - 2) * 2 + 0] = lUpper[(lUpperSize - 1) * 2 + 0];
131                lUpper[(lUpperSize - 2) * 2 + 1] = lUpper[(lUpperSize - 1) * 2 + 1];
132                lUpperSize--;
133            }
134        }
135
136        float[] lLower = new float[n * 2];
137        lLower[0] = points[(n - 1) * 2 + 0];
138        lLower[1] = points[(n - 1) * 2 + 1];
139        lLower[2] = points[(n - 2) * 2 + 0];
140        lLower[3] = points[(n - 2) * 2 + 1];
141
142        int lLowerSize = 2;
143
144        for (int i = n - 3; i >= 0; i--) {
145            lLower[lLowerSize * 2 + 0] = points[i * 2 + 0];
146            lLower[lLowerSize * 2 + 1] = points[i * 2 + 1];
147            lLowerSize++;
148
149            while (lLowerSize > 2 &&
150                    !rightTurn(lLower[(lLowerSize - 3) * 2], lLower[(lLowerSize - 3) * 2 + 1],
151                            lLower[(lLowerSize - 2) * 2], lLower[(lLowerSize - 2) * 2 + 1],
152                            lLower[(lLowerSize - 1) * 2], lLower[(lLowerSize - 1) * 2 + 1])) {
153                // Remove the middle point of the three last
154                lLower[(lLowerSize - 2) * 2 + 0] = lLower[(lLowerSize - 1) * 2 + 0];
155                lLower[(lLowerSize - 2) * 2 + 1] = lLower[(lLowerSize - 1) * 2 + 1];
156                lLowerSize--;
157            }
158        }
159
160        int count = 0;
161        for (int i = 0; i < lUpperSize; i++) {
162            retPoly[count * 2 + 0] = lUpper[i * 2 + 0];
163            retPoly[count * 2 + 1] = lUpper[i * 2 + 1];
164            count++;
165        }
166        for (int i = 1; i < lLowerSize - 1; i++) {
167            retPoly[count * 2 + 0] = lLower[i * 2 + 0];
168            retPoly[count * 2 + 1] = lLower[i * 2 + 1];
169            count++;
170        }
171        return count;
172    }
173
174    private static boolean rightTurn(float ax, float ay, float bx, float by, float cx, float cy) {
175        return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > 0.00001;
176    }
177
178    /**
179     * calculates the intersection of poly1 with poly2 and put in poly2
180     * @param poly1 The flatten 2d coordinates of polygon
181     * @param poly1length The vertices number of poly1
182     * @param poly2 The flatten 2d coordinates of polygon
183     * @param poly2length The vertices number of poly2
184     * @return number of vertices in poly2
185     */
186    private static int intersection(@NonNull float[] poly1, int poly1length, @NonNull float[] poly2,
187            int poly2length) {
188        makeClockwise(poly1, poly1length);
189        makeClockwise(poly2, poly2length);
190        float[] poly = new float[(poly1length * poly2length + 2) * 2];
191        int count = 0;
192        int pCount = 0;
193        for (int i = 0; i < poly1length; i++) {
194            if (pointInsidePolygon(poly1[i * 2 + 0], poly1[i * 2 + 1], poly2, poly2length)) {
195                poly[count * 2 + 0] = poly1[i * 2 + 0];
196                poly[count * 2 + 1] = poly1[i * 2 + 1];
197                count++;
198                pCount++;
199            }
200        }
201        int fromP1 = pCount;
202        for (int i = 0; i < poly2length; i++) {
203            if (pointInsidePolygon(poly2[i * 2 + 0], poly2[i * 2 + 1], poly1, poly1length)) {
204                poly[count * 2 + 0] = poly2[i * 2 + 0];
205                poly[count * 2 + 1] = poly2[i * 2 + 1];
206                count++;
207            }
208        }
209        int fromP2 = count - fromP1;
210        if (fromP1 == poly1length) { // use p1
211            for (int i = 0; i < poly1length; i++) {
212                poly2[i * 2 + 0] = poly1[i * 2 + 0];
213                poly2[i * 2 + 1] = poly1[i * 2 + 1];
214            }
215            return poly1length;
216        }
217        if (fromP2 == poly2length) { // use p2
218            return poly2length;
219        }
220        float[] intersection = new float[2];
221        for (int i = 0; i < poly2length; i++) {
222            for (int j = 0; j < poly1length; j++) {
223                int i1_by_2 = i * 2;
224                int i2_by_2 = ((i + 1) % poly2length) * 2;
225                int j1_by_2 = j * 2;
226                int j2_by_2 = ((j + 1) % poly1length) * 2;
227                boolean found =
228                        lineIntersection(poly2[i1_by_2 + 0], poly2[i1_by_2 + 1], poly2[i2_by_2 + 0],
229                                poly2[i2_by_2 + 1], poly1[j1_by_2 + 0], poly1[j1_by_2 + 1],
230                                poly1[j2_by_2 + 0], poly1[j2_by_2 + 1], intersection);
231                if (found) {
232                    poly[count * 2 + 0] = intersection[0];
233                    poly[count * 2 + 1] = intersection[1];
234                    count++;
235                } else {
236                    float dx = poly2[i * 2 + 0] - poly1[j * 2 + 0];
237                    float dy = poly2[i * 2 + 1] - poly1[j * 2 + 1];
238
239                    if (dx * dx + dy * dy < 0.01) {
240                        poly[count * 2 + 0] = poly2[i * 2 + 0];
241                        poly[count * 2 + 1] = poly2[i * 2 + 1];
242                        count++;
243                    }
244                }
245            }
246        }
247        if (count == 0) {
248            return 0;
249        }
250        float avgX = 0;
251        float avgY = 0;
252        for (int i = 0; i < count; i++) {
253            avgX += poly[i * 2 + 0];
254            avgY += poly[i * 2 + 1];
255        }
256        avgX /= count;
257        avgY /= count;
258
259        float[] ctr = new float[]{avgX, avgY};
260        sort(poly, count, ctr);
261        int size = count;
262
263        poly2[0] = poly[0];
264        poly2[1] = poly[1];
265
266        count = 1;
267        for (int i = 1; i < size; i++) {
268            float dx = poly[i * 2 + 0] - poly[(i - 1) * 2 + 0];
269            float dy = poly[i * 2 + 1] - poly[(i - 1) * 2 + 1];
270            if (dx * dx + dy * dy >= 0.01) {
271                poly2[count * 2 + 0] = poly[i * 2 + 0];
272                poly2[count * 2 + 1] = poly[i * 2 + 1];
273                count++;
274            }
275        }
276        return count;
277    }
278
279    public static void sort(@NonNull float[] poly, int polyLength, @NonNull float[] ctr) {
280        quicksortCircle(poly, 0, polyLength - 1, ctr);
281    }
282
283    public static float angle(float x1, float y1, @NonNull float[] ctr) {
284        return -(float) Math.atan2(x1 - ctr[0], y1 - ctr[1]);
285    }
286
287    private static void swapPair(@NonNull float[] points, int i, int j) {
288        float x = points[i * 2 + 0];
289        float y = points[i * 2 + 1];
290        points[i * 2 + 0] = points[j * 2 + 0];
291        points[i * 2 + 1] = points[j * 2 + 1];
292        points[j * 2 + 0] = x;
293        points[j * 2 + 1] = y;
294    }
295
296    private static void quicksortCircle(@NonNull float[] points, int low, int high,
297            @NonNull float[] ctr) {
298        int i = low, j = high;
299        int p = low + (high - low) / 2;
300        float pivot = angle(points[p * 2], points[p * 2 + 1], ctr);
301        while (i <= j) {
302            while (angle(points[i * 2 + 0], points[i * 2 + 1], ctr) < pivot) {
303                i++;
304            }
305            while (angle(points[j * 2 + 0], points[j * 2 + 1], ctr) > pivot) {
306                j--;
307            }
308            if (i <= j) {
309                swapPair(points, i, j);
310                i++;
311                j--;
312            }
313        }
314        if (low < j) {
315            quicksortCircle(points, low, j, ctr);
316        }
317        if (i < high) {
318            quicksortCircle(points, i, high, ctr);
319        }
320    }
321
322    /**
323     * This function do Quick Sort by comparing X axis only.<br>
324     * Note that the input values of points are paired coordinates, e.g. {@code [x0, y0, x1, y1, x2,
325     * y2, ...]).}
326     * @param points The input point pairs. Every {@code (2 * i, 2 * i + 1)} points are pairs.
327     * @param low lowest index used to do quick sort sort
328     * @param high highest index used to do quick sort
329     */
330    private static void quicksortX(@NonNull float[] points, int low, int high) {
331        int i = low, j = high;
332        int p = low + (high - low) / 2;
333        float pivot = points[p * 2];
334        while (i <= j) {
335            while (points[i * 2 + 0] < pivot) {
336                i++;
337            }
338            while (points[j * 2 + 0] > pivot) {
339                j--;
340            }
341
342            if (i <= j) {
343                swapPair(points, i, j);
344                i++;
345                j--;
346            }
347        }
348        if (low < j) {
349            quicksortX(points, low, j);
350        }
351        if (i < high) {
352            quicksortX(points, i, high);
353        }
354    }
355
356    private static boolean pointInsidePolygon(float x, float y, @NonNull float[] poly, int len) {
357        boolean c = false;
358        float testX = x;
359        float testY = y;
360        for (int i = 0, j = len - 1; i < len; j = i++) {
361            if (((poly[i * 2 + 1] > testY) != (poly[j * 2 + 1] > testY)) && (testX <
362                    (poly[j * 2 + 0] - poly[i * 2 + 0]) * (testY - poly[i * 2 + 1]) /
363                            (poly[j * 2 + 1] - poly[i * 2 + 1]) + poly[i * 2 + 0])) {
364                c = !c;
365            }
366        }
367        return c;
368    }
369
370    private static void makeClockwise(@NonNull float[] polygon, int len) {
371        if (polygon == null || len == 0) {
372            return;
373        }
374        if (!isClockwise(polygon, len)) {
375            reverse(polygon, len);
376        }
377    }
378
379    private static boolean isClockwise(@NonNull float[] polygon, int len) {
380        float sum = 0;
381        float p1x = polygon[(len - 1) * 2 + 0];
382        float p1y = polygon[(len - 1) * 2 + 1];
383        for (int i = 0; i < len; i++) {
384            float p2x = polygon[i * 2 + 0];
385            float p2y = polygon[i * 2 + 1];
386            sum += p1x * p2y - p2x * p1y;
387            p1x = p2x;
388            p1y = p2y;
389        }
390        return sum < 0;
391    }
392
393    private static void reverse(@NonNull float[] polygon, int len) {
394        int n = len / 2;
395        for (int i = 0; i < n; i++) {
396            float tmp0 = polygon[i * 2 + 0];
397            float tmp1 = polygon[i * 2 + 1];
398            int k = len - 1 - i;
399            polygon[i * 2 + 0] = polygon[k * 2 + 0];
400            polygon[i * 2 + 1] = polygon[k * 2 + 1];
401            polygon[k * 2 + 0] = tmp0;
402            polygon[k * 2 + 1] = tmp1;
403        }
404    }
405
406    /**
407     * Intersects two lines in parametric form.
408     */
409    private static final boolean lineIntersection(float x1, float y1, float x2, float y2, float x3,
410            float y3, float x4, float y4, @NonNull float[] ret) {
411        float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
412        if (d == 0.000f) {
413            return false;
414        }
415
416        float dx = (x1 * y2 - y1 * x2);
417        float dy = (x3 * y4 - y3 * x4);
418        float x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
419        float y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
420
421        if (((x - x1) * (x - x2) > 0.0000001) || ((x - x3) * (x - x4) > 0.0000001) ||
422                ((y - y1) * (y - y2) > 0.0000001) || ((y - y3) * (y - y4) > 0.0000001)) {
423            return false;
424        }
425        ret[0] = x;
426        ret[1] = y;
427        return true;
428    }
429
430    @NonNull
431    public static float[] calculateLight(float size, int points, float x, float y, float height) {
432        float[] ret = new float[points * 3];
433        for (int i = 0; i < points; i++) {
434            double angle = 2 * i * Math.PI / points;
435            ret[i * 3 + 0] = (float) Math.cos(angle) * size + x;
436            ret[i * 3 + 1] = (float) Math.sin(angle) * size + y;
437            ret[i * 3 + 2] = (height);
438        }
439        return ret;
440    }
441
442    /**
443     layers indicates the number of circular strips to generate.
444     Strength is how dark a shadow to generate.
445
446    /**
447     * This calculates a collection of triangles that represent the shadow cast by a polygonal
448     * light source (lightPoly) hitting a convex polygon (poly).
449     * @param lightPoly The flatten 3d coordinates of light
450     * @param lightPolyLength The vertices number of light polygon.
451     * @param poly The flatten 3d coordinates of item
452     * @param polyLength The vertices number of light polygon.
453     * @param rays defines the number of points around the perimeter of the shadow to generate
454     * @param layers The number of shadow's fading.
455     * @param strength The factor of the black color of shadow.
456     * @param retStrips Used to store the calculated shadow strength
457     * @return true if the params is able to calculate a shadow, else false.
458     */
459    public static boolean calcShadow(@NonNull float[] lightPoly, int lightPolyLength,
460            @NonNull float[] poly, int polyLength, int rays, int layers, float strength,
461            @NonNull float[] retStrips) {
462        float[] shadowRegion = new float[lightPolyLength * polyLength * 2];
463        float[] outline = new float[polyLength * 2];
464        float[] umbra = new float[polyLength * lightPolyLength * 2];
465        int umbraLength = 0;
466
467        int k = 0;
468        for (int j = 0; j < lightPolyLength; j++) {
469            int m = 0;
470            for (int i = 0; i < polyLength; i++) {
471                float t = lightPoly[j * 3 + 2] - poly[i * 3 + 2];
472                if (t == 0) {
473                    return false;
474                }
475                t = lightPoly[j * 3 + 2] / t;
476                float x = lightPoly[j * 3 + 0] - t * (lightPoly[j * 3 + 0] - poly[i * 3 + 0]);
477                float y = lightPoly[j * 3 + 1] - t * (lightPoly[j * 3 + 1] - poly[i * 3 + 1]);
478
479                shadowRegion[k * 2 + 0] = x;
480                shadowRegion[k * 2 + 1] = y;
481                outline[m * 2 + 0] = x;
482                outline[m * 2 + 1] = y;
483
484                k++;
485                m++;
486            }
487            if (umbraLength == 0) {
488                System.arraycopy(outline, 0, umbra, 0, polyLength);
489                umbraLength = polyLength;
490            } else {
491                umbraLength = intersection(outline, polyLength, umbra, umbraLength);
492                if (umbraLength == 0) {
493                    break;
494                }
495            }
496        }
497        int shadowRegionLength = k;
498
499        float[] penumbra = new float[k * 2];
500        int penumbraLength = hull(shadowRegion, shadowRegionLength, penumbra);
501        if (umbraLength < 3) {// no real umbra make a fake one
502            float[] p = new float[3];
503            centroid3d(lightPoly, lightPolyLength, p);
504            float[] centShadow = new float[polyLength * 2];
505            for (int i = 0; i < polyLength; i++) {
506                float t = p[2] - poly[i * 3 + 2];
507                if (t == 0) {
508                    return false;
509                }
510                t = p[2] / t;
511                float x = p[0] - t * (p[0] - poly[i * 3 + 0]);
512                float y = p[1] - t * (p[1] - poly[i * 3 + 1]);
513
514                centShadow[i * 2 + 0] = x;
515                centShadow[i * 2 + 1] = y;
516            }
517            float[] c = new float[2];
518            centroid2d(centShadow, polyLength, c);
519            for (int i = 0; i < polyLength; i++) {
520                centShadow[i * 2 + 0] = (c[0] * 9 + centShadow[i * 2 + 0]) / 10;
521                centShadow[i * 2 + 1] = (c[1] * 9 + centShadow[i * 2 + 1]) / 10;
522            }
523            umbra = centShadow; // fake umbra
524            umbraLength = polyLength; // same size as the original polygon
525        }
526
527        triangulateConcentricPolygon(penumbra, penumbraLength, umbra, umbraLength, rays, layers,
528                strength, retStrips);
529        return true;
530    }
531
532    /**
533     * triangulate concentric circles.
534     * This takes the inner and outer polygons of the umbra and penumbra and triangulates it.
535     * @param penumbra The 2d flatten vertices of penumbra polygons.
536     * @param penumbraLength The number of vertices in penumbra.
537     * @param umbra The 2d flatten vertices of umbra polygons.
538     * @param umbraLength The number of vertices in umbra.
539     * @param rays defines the number of points around the perimeter of the shadow to generate
540     * @param layers The number of shadow's fading.
541     * @param strength The factor of the black color of shadow.
542     * @param retStrips Used to store the calculated shadow strength.
543     */
544    private static void triangulateConcentricPolygon(@NonNull float[] penumbra, int penumbraLength,
545            @NonNull float[] umbra, int umbraLength, int rays, int layers, float strength,
546            @NonNull float[] retStrips) {
547        int rings = layers + 1;
548        double step = Math.PI * 2 / rays;
549        float[] retXY = new float[2];
550        centroid2d(umbra, umbraLength, retXY);
551        float cx = retXY[0];
552        float cy = retXY[1];
553
554        float[] t1 = new float[rays];
555        float[] t2 = new float[rays];
556
557        for (int i = 0; i < rays; i++) {
558            float dx = (float) Math.cos(Math.PI / 4 + step * i);
559            float dy = (float) Math.sin(Math.PI / 4 + step * i);
560            t2[i] = rayIntersectPoly(umbra, umbraLength, cx, cy, dx, dy);
561            t1[i] = rayIntersectPoly(penumbra, penumbraLength, cx, cy, dx, dy);
562        }
563
564        int p = 0;
565        // Calculate the vertex
566        for (int r = 0; r < layers; r++) {
567            int startP = p;
568            for (int i = 0; i < rays; i++) {
569                float dx = (float) Math.cos(Math.PI / 4 + step * i);
570                float dy = (float) Math.sin(Math.PI / 4 + step * i);
571
572                for (int j = r; j < (r + 2); j++) {
573                    float jf = j / (float) (rings - 1);
574                    float t = t1[i] + jf * (t2[i] - t1[i]);
575                    float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
576                    retStrips[p * 3 + 0] = dx * t + cx;
577                    retStrips[p * 3 + 1] = dy * t + cy;
578                    retStrips[p * 3 + 2] = jf * op * strength;
579                    p++;
580
581                }
582            }
583            retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
584            retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
585            retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
586            p++;
587            startP++;
588            retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
589            retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
590            retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
591            p++;
592        }
593        int oldP = p - 1;
594        retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
595        retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
596        retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
597        p++;
598
599        // Skip the first point here, then make it same as last point later.
600        oldP = p;
601        p++;
602        for (int k = 0; k < rays; k++) {
603            int i = k / 2;
604            if ((k & 1) == 1) { // traverse the inside in a zig zag pattern
605                // for strips
606                i = rays - i - 1;
607            }
608            float dx = (float) Math.cos(Math.PI / 4 + step * i);
609            float dy = (float) Math.sin(Math.PI / 4 + step * i);
610
611            float jf = 1;
612
613            float t = t1[i] + jf * (t2[i] - t1[i]);
614            float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
615
616            retStrips[p * 3 + 0] = dx * t + cx;
617            retStrips[p * 3 + 1] = dy * t + cy;
618            retStrips[p * 3 + 2] = jf * op * strength;
619            p++;
620        }
621        p = oldP;
622        retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
623        retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
624        retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
625    }
626
627    public static int getStripSize(int rays, int layers) {
628        return (2 + rays + ((layers) * 2 * (rays + 1)));
629    }
630}
631