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 }