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