1 /*
2  * Copyright (C) 2018 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 
17 package android.view.shadow;
18 
19 import android.graphics.Rect;
20 import android.view.math.Math3DHelper;
21 
22 /**
23  * Generates the vertices required for spot shadow and all other shadow-related rendering.
24  */
25 class SpotShadowVertexCalculator {
26 
SpotShadowVertexCalculator()27     private SpotShadowVertexCalculator() { }
28 
29     /**
30      * Create evenly distributed circular light source points from x and y (on flat z plane).
31      * This is useful for ray tracing the shadow points later. Format : (x1,y1,z1,x2,y2,z2 ...)
32      *
33      * @param radius - radius of the light source
34      * @param x - center X of the light source
35      * @param y - center Y of the light source
36      * @param height - how high (z depth) the light should be
37      * @return float points (x,y,z) of light source points.
38      */
calculateLight(float radius, float x, float y, float height)39     public static float[] calculateLight(float radius, float x, float y, float height) {
40         float[] ret = new float[4 * 3];
41         // bottom
42         ret[0] = x;
43         ret[1] = y + radius;
44         ret[2] = height;
45         // left
46         ret[3] = x - radius;
47         ret[4] = y;
48         ret[5] = height;
49         // top
50         ret[6] = x;
51         ret[7] = y - radius;
52         ret[8] = height;
53         // right
54         ret[9] = x + radius;
55         ret[10] = y;
56         ret[11] = height;
57 
58         return ret;
59     }
60 
61     /**
62      * @param polyLength - length of the outline polygon
63      * @return size required for shadow vertices mData array based on # of vertices in the
64      * outline polygon
65      */
getStripSizes(int polyLength)66     public static int[] getStripSizes(int polyLength){
67         return new int[] { ((polyLength + 4) / 8) * 16 + 2, 4};
68     }
69 
70     /**
71      * Generate shadow vertices based on params. Format : (x1,y1,z1,x2,y2,z2 ...)
72      * Precondition : Light poly must be evenly distributed on a flat surface
73      * Precondition : Poly vertices must be a convex
74      * Precondition : Light height must be higher than any poly vertices
75      *
76      * @param lightPoly - Vertices of a light source.
77      * @param poly - Vertices of opaque object casting shadow
78      * @param polyLength - Size of the vertices
79      * @param strength - Strength of the shadow overall [0-1]
80      * @param retstrips - Arrays of triplets, each triplet represents a point, thus every array to
81      * be filled in format : {x1, y1, z1, x2, y2, z2, ...},
82      * every 3 consecutive triplets constitute a triangle to fill, namely [t1, t2, t3], [t2, t3,
83      * t4], ... If at some point [t(n-1), tn, t(n+1)] is no longer a desired a triangle and
84      * there are more triangles to draw one can start a new array, hence retstrips is a 2D array.
85      */
calculateShadow( float[] lightPoly, float[] poly, int polyLength, float strength, float[][] retstrips)86     public static void calculateShadow(
87             float[] lightPoly,
88             float[] poly,
89             int polyLength,
90             float strength,
91             float[][] retstrips) {
92         float[] outerStrip = retstrips[0];
93 
94         // We would like to unify the cases where we have roundrects and rectangles
95         int roundedEdgeSegments = ((polyLength == 4) ? 0 : ShadowConstants.SPLICE_ROUNDED_EDGE);
96         int sideLength = (roundedEdgeSegments / 2 + 1) * 2;
97         float[] umbra = new float[4 * 2 * sideLength];
98         int idx = (roundedEdgeSegments + 1) / 2;
99         int uShift = 0;
100         // If we have even number of segments in rounded corner (0 included), the central point of
101         // rounded corner contributes to the hull twice, from 2 different light sources, thus
102         // rollBack in that case, otherwise every point contributes only once
103         int rollBack = (((polyLength % 8) == 0) ? 0 : 1);
104         // Calculate umbra - a hull of all projections
105         for (int s = 0; s < 4; ++s) { // 4 sides
106             float lx = lightPoly[s * 3 + 0];
107             float ly = lightPoly[s * 3 + 1];
108             float lz = lightPoly[s * 3 + 2];
109             for (int i = 0; i < sideLength; ++i, uShift += 2, ++idx) {
110                 int shift = (idx % polyLength) * 3;
111 
112                 float t = lz / (lz - poly[shift + 2]);
113 
114                 umbra[uShift + 0] = lx - t * (lx - poly[shift + 0]);
115                 umbra[uShift + 1] = ly - t * (ly - poly[shift + 1]);
116             }
117 
118             idx -= rollBack;
119         }
120 
121         idx = roundedEdgeSegments;
122         // An array that wil contain top, right, bottom, left coordinate of penumbra
123         float[] penumbraRect = new float[4];
124         // Calculate penumbra
125         for (int s = 0; s < 4; ++s, idx += (roundedEdgeSegments + 1)) { // 4 sides
126             int sp = (s + 2) % 4;
127 
128             float lz = lightPoly[sp * 3 + 2];
129 
130             int shift = (idx % polyLength) * 3;
131 
132             float t = lz / (lz - poly[shift + 2]);
133 
134             // We are interested in just one coordinate: x or y, depending on the light source
135             int c = (s + 1) % 2;
136             penumbraRect[s] =
137                     lightPoly[sp * 3 + c] - t * (lightPoly[sp * 3 + c] - poly[shift + c]);
138         }
139         if (penumbraRect[0] > penumbraRect[2]) {
140             float tmp = (penumbraRect[0] + penumbraRect[2]) / 2.0f;
141             penumbraRect[0] = penumbraRect[2] = tmp;
142         }
143         if (penumbraRect[3] > penumbraRect[1]) {
144             float tmp = (penumbraRect[1] + penumbraRect[3]) / 2.0f;
145             penumbraRect[1] = penumbraRect[3] = tmp;
146         }
147 
148         // Now just connect umbra points (at least 8 of them) with the closest points from
149         // penumbra (only 4 of them) to form triangles to fill the entire space between umbra and
150         // penumbra
151         idx = sideLength * 4 - sideLength / 2;
152         int rsShift = 0;
153         for (int s = 0; s < 4; ++s) {
154             int xidx = (((s + 3) % 4) / 2) * 2 + 1;
155             int yidx = (s / 2) * 2;
156             float penumbraX = penumbraRect[xidx];
157             float penumbraY = penumbraRect[yidx];
158             for (int i = 0; i < sideLength; ++i, rsShift += 6, ++idx) {
159                 int shift = (idx % (sideLength * 4)) * 2;
160 
161                 outerStrip[rsShift + 0] = umbra[shift + 0];
162                 outerStrip[rsShift + 1] = umbra[shift + 1];
163                 outerStrip[rsShift + 3] = penumbraX;
164                 outerStrip[rsShift + 4] = penumbraY;
165                 outerStrip[rsShift + 5] = strength;
166             }
167         }
168         // Connect with the beginning
169         outerStrip[rsShift + 0] = outerStrip[0];
170         outerStrip[rsShift + 1] = outerStrip[1];
171         // outerStrip[rsShift + 2] = 0;
172         outerStrip[rsShift + 3] = outerStrip[3];
173         outerStrip[rsShift + 4] = outerStrip[4];
174         outerStrip[rsShift + 5] = strength;
175 
176         float[] innerStrip = retstrips[1];
177         // Covering penumbra rectangle
178         // left, top
179         innerStrip[0] = penumbraRect[3];
180         innerStrip[1] = penumbraRect[0];
181         innerStrip[2] = strength;
182         // right, top
183         innerStrip[3] = penumbraRect[1];
184         innerStrip[4] = penumbraRect[0];
185         innerStrip[5] = strength;
186         // left, bottom
187         innerStrip[6] = penumbraRect[3];
188         innerStrip[7] = penumbraRect[2];
189         innerStrip[8] = strength;
190         // right, bottom
191         innerStrip[9] = penumbraRect[1];
192         innerStrip[10] = penumbraRect[2];
193         innerStrip[11] = strength;
194     }
195 }