| 1 | /* |
| 2 | * Copyright 2017 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "include/core/SkMatrix.h" |
| 9 | #include "include/core/SkPath.h" |
| 10 | #include "include/core/SkRect.h" |
| 11 | #include "src/core/SkDrawShadowInfo.h" |
| 12 | #include "src/utils/SkPolyUtils.h" |
| 13 | |
| 14 | namespace SkDrawShadowMetrics { |
| 15 | |
| 16 | static SkScalar compute_z(SkScalar x, SkScalar y, const SkPoint3& params) { |
| 17 | return x*params.fX + y*params.fY + params.fZ; |
| 18 | } |
| 19 | |
| 20 | bool GetSpotShadowTransform(const SkPoint3& lightPos, SkScalar lightRadius, |
| 21 | const SkMatrix& ctm, const SkPoint3& zPlaneParams, |
| 22 | const SkRect& pathBounds, SkMatrix* shadowTransform, SkScalar* radius) { |
| 23 | auto heightFunc = [zPlaneParams] (SkScalar x, SkScalar y) { |
| 24 | return zPlaneParams.fX*x + zPlaneParams.fY*y + zPlaneParams.fZ; |
| 25 | }; |
| 26 | SkScalar occluderHeight = heightFunc(pathBounds.centerX(), pathBounds.centerY()); |
| 27 | |
| 28 | if (!ctm.hasPerspective()) { |
| 29 | SkScalar scale; |
| 30 | SkVector translate; |
| 31 | SkDrawShadowMetrics::GetSpotParams(occluderHeight, lightPos.fX, lightPos.fY, lightPos.fZ, |
| 32 | lightRadius, radius, &scale, &translate); |
| 33 | shadowTransform->setScaleTranslate(scale, scale, translate.fX, translate.fY); |
| 34 | shadowTransform->preConcat(ctm); |
| 35 | } else { |
| 36 | if (SkScalarNearlyZero(pathBounds.width()) || SkScalarNearlyZero(pathBounds.height())) { |
| 37 | return false; |
| 38 | } |
| 39 | |
| 40 | // get rotated quad in 3D |
| 41 | SkPoint pts[4]; |
| 42 | ctm.mapRectToQuad(pts, pathBounds); |
| 43 | // No shadows for bowties or other degenerate cases |
| 44 | if (!SkIsConvexPolygon(pts, 4)) { |
| 45 | return false; |
| 46 | } |
| 47 | SkPoint3 pts3D[4]; |
| 48 | SkScalar z = heightFunc(pathBounds.fLeft, pathBounds.fTop); |
| 49 | pts3D[0].set(pts[0].fX, pts[0].fY, z); |
| 50 | z = heightFunc(pathBounds.fRight, pathBounds.fTop); |
| 51 | pts3D[1].set(pts[1].fX, pts[1].fY, z); |
| 52 | z = heightFunc(pathBounds.fRight, pathBounds.fBottom); |
| 53 | pts3D[2].set(pts[2].fX, pts[2].fY, z); |
| 54 | z = heightFunc(pathBounds.fLeft, pathBounds.fBottom); |
| 55 | pts3D[3].set(pts[3].fX, pts[3].fY, z); |
| 56 | |
| 57 | // project from light through corners to z=0 plane |
| 58 | for (int i = 0; i < 4; ++i) { |
| 59 | SkScalar dz = lightPos.fZ - pts3D[i].fZ; |
| 60 | // light shouldn't be below or at a corner's z-location |
| 61 | if (dz <= SK_ScalarNearlyZero) { |
| 62 | return false; |
| 63 | } |
| 64 | SkScalar zRatio = pts3D[i].fZ / dz; |
| 65 | pts3D[i].fX -= (lightPos.fX - pts3D[i].fX)*zRatio; |
| 66 | pts3D[i].fY -= (lightPos.fY - pts3D[i].fY)*zRatio; |
| 67 | pts3D[i].fZ = SK_Scalar1; |
| 68 | } |
| 69 | |
| 70 | // Generate matrix that projects from [-1,1]x[-1,1] square to projected quad |
| 71 | SkPoint3 h0, h1, h2; |
| 72 | // Compute homogenous crossing point between top and bottom edges (gives new x-axis). |
| 73 | h0 = (pts3D[1].cross(pts3D[0])).cross(pts3D[2].cross(pts3D[3])); |
| 74 | // Compute homogenous crossing point between left and right edges (gives new y-axis). |
| 75 | h1 = (pts3D[0].cross(pts3D[3])).cross(pts3D[1].cross(pts3D[2])); |
| 76 | // Compute homogenous crossing point between diagonals (gives new origin). |
| 77 | h2 = (pts3D[0].cross(pts3D[2])).cross(pts3D[1].cross(pts3D[3])); |
| 78 | // If h2 is a vector (z=0 in 2D homogeneous space), that means that at least |
| 79 | // two of the quad corners are coincident and we don't have a realistic projection |
| 80 | if (SkScalarNearlyZero(h2.fZ)) { |
| 81 | return false; |
| 82 | } |
| 83 | // In some cases the crossing points are in the wrong direction |
| 84 | // to map (-1,-1) to pts3D[0], so we need to correct for that. |
| 85 | // Want h0 to be to the right of the left edge. |
| 86 | SkVector3 v = pts3D[3] - pts3D[0]; |
| 87 | SkVector3 w = h0 - pts3D[0]; |
| 88 | SkScalar perpDot = v.fX*w.fY - v.fY*w.fX; |
| 89 | if (perpDot > 0) { |
| 90 | h0 = -h0; |
| 91 | } |
| 92 | // Want h1 to be above the bottom edge. |
| 93 | v = pts3D[1] - pts3D[0]; |
| 94 | perpDot = v.fX*w.fY - v.fY*w.fX; |
| 95 | if (perpDot < 0) { |
| 96 | h1 = -h1; |
| 97 | } |
| 98 | shadowTransform->setAll(h0.fX / h2.fZ, h1.fX / h2.fZ, h2.fX / h2.fZ, |
| 99 | h0.fY / h2.fZ, h1.fY / h2.fZ, h2.fY / h2.fZ, |
| 100 | h0.fZ / h2.fZ, h1.fZ / h2.fZ, 1); |
| 101 | // generate matrix that transforms from bounds to [-1,1]x[-1,1] square |
| 102 | SkMatrix toHomogeneous; |
| 103 | SkScalar xScale = 2/(pathBounds.fRight - pathBounds.fLeft); |
| 104 | SkScalar yScale = 2/(pathBounds.fBottom - pathBounds.fTop); |
| 105 | toHomogeneous.setAll(xScale, 0, -xScale*pathBounds.fLeft - 1, |
| 106 | 0, yScale, -yScale*pathBounds.fTop - 1, |
| 107 | 0, 0, 1); |
| 108 | shadowTransform->preConcat(toHomogeneous); |
| 109 | |
| 110 | *radius = SkDrawShadowMetrics::SpotBlurRadius(occluderHeight, lightPos.fZ, lightRadius); |
| 111 | } |
| 112 | |
| 113 | return true; |
| 114 | } |
| 115 | |
| 116 | void GetLocalBounds(const SkPath& path, const SkDrawShadowRec& rec, const SkMatrix& ctm, |
| 117 | SkRect* bounds) { |
| 118 | SkRect ambientBounds = path.getBounds(); |
| 119 | SkScalar occluderZ; |
| 120 | if (SkScalarNearlyZero(rec.fZPlaneParams.fX) && SkScalarNearlyZero(rec.fZPlaneParams.fY)) { |
| 121 | occluderZ = rec.fZPlaneParams.fZ; |
| 122 | } else { |
| 123 | occluderZ = compute_z(ambientBounds.fLeft, ambientBounds.fTop, rec.fZPlaneParams); |
| 124 | occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fTop, |
| 125 | rec.fZPlaneParams)); |
| 126 | occluderZ = std::max(occluderZ, compute_z(ambientBounds.fLeft, ambientBounds.fBottom, |
| 127 | rec.fZPlaneParams)); |
| 128 | occluderZ = std::max(occluderZ, compute_z(ambientBounds.fRight, ambientBounds.fBottom, |
| 129 | rec.fZPlaneParams)); |
| 130 | } |
| 131 | SkScalar ambientBlur; |
| 132 | SkScalar spotBlur; |
| 133 | SkScalar spotScale; |
| 134 | SkPoint spotOffset; |
| 135 | if (ctm.hasPerspective()) { |
| 136 | // transform ambient and spot bounds into device space |
| 137 | ctm.mapRect(&ambientBounds); |
| 138 | |
| 139 | // get ambient blur (in device space) |
| 140 | ambientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); |
| 141 | |
| 142 | // get spot params (in device space) |
| 143 | SkPoint devLightPos = SkPoint::Make(rec.fLightPos.fX, rec.fLightPos.fY); |
| 144 | ctm.mapPoints(&devLightPos, 1); |
| 145 | SkDrawShadowMetrics::GetSpotParams(occluderZ, devLightPos.fX, devLightPos.fY, |
| 146 | rec.fLightPos.fZ, rec.fLightRadius, |
| 147 | &spotBlur, &spotScale, &spotOffset); |
| 148 | } else { |
| 149 | SkScalar devToSrcScale = SkScalarInvert(ctm.getMinScale()); |
| 150 | |
| 151 | // get ambient blur (in local space) |
| 152 | SkScalar devSpaceAmbientBlur = SkDrawShadowMetrics::AmbientBlurRadius(occluderZ); |
| 153 | ambientBlur = devSpaceAmbientBlur*devToSrcScale; |
| 154 | |
| 155 | // get spot params (in local space) |
| 156 | SkDrawShadowMetrics::GetSpotParams(occluderZ, rec.fLightPos.fX, rec.fLightPos.fY, |
| 157 | rec.fLightPos.fZ, rec.fLightRadius, |
| 158 | &spotBlur, &spotScale, &spotOffset); |
| 159 | |
| 160 | // convert spot blur to local space |
| 161 | spotBlur *= devToSrcScale; |
| 162 | } |
| 163 | |
| 164 | // in both cases, adjust ambient and spot bounds |
| 165 | SkRect spotBounds = ambientBounds; |
| 166 | ambientBounds.outset(ambientBlur, ambientBlur); |
| 167 | spotBounds.fLeft *= spotScale; |
| 168 | spotBounds.fTop *= spotScale; |
| 169 | spotBounds.fRight *= spotScale; |
| 170 | spotBounds.fBottom *= spotScale; |
| 171 | spotBounds.offset(spotOffset.fX, spotOffset.fY); |
| 172 | spotBounds.outset(spotBlur, spotBlur); |
| 173 | |
| 174 | // merge bounds |
| 175 | *bounds = ambientBounds; |
| 176 | bounds->join(spotBounds); |
| 177 | // outset a bit to account for floating point error |
| 178 | bounds->outset(1, 1); |
| 179 | |
| 180 | // if perspective, transform back to src space |
| 181 | if (ctm.hasPerspective()) { |
| 182 | // TODO: create tighter mapping from dev rect back to src rect |
| 183 | SkMatrix inverse; |
| 184 | if (ctm.invert(&inverse)) { |
| 185 | inverse.mapRect(bounds); |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | |
| 191 | } // namespace SkDrawShadowMetrics |
| 192 | |
| 193 | |