xref: /third_party/skia/src/utils/SkCamera.cpp (revision cb93a386)
1/*
2 * Copyright 2006 The Android Open Source Project
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/utils/SkCamera.h"
9
10static SkScalar SkScalarDotDiv(int count, const SkScalar a[], int step_a,
11                               const SkScalar b[], int step_b,
12                               SkScalar denom) {
13    SkScalar prod = 0;
14    for (int i = 0; i < count; i++) {
15        prod += a[0] * b[0];
16        a += step_a;
17        b += step_b;
18    }
19    return prod / denom;
20}
21
22///////////////////////////////////////////////////////////////////////////////
23
24SkPatch3D::SkPatch3D() {
25    this->reset();
26}
27
28void SkPatch3D::reset() {
29    fOrigin = {0, 0, 0};
30    fU = {SK_Scalar1, 0, 0};
31    fV = {0, -SK_Scalar1, 0};
32}
33
34void SkPatch3D::transform(const SkM44& m, SkPatch3D* dst) const {
35    if (dst == nullptr) {
36        dst = (SkPatch3D*)this;
37    }
38    dst->fU = m * fU;
39    dst->fV = m * fV;
40    auto [x,y,z,_] = m.map(fOrigin.x, fOrigin.y, fOrigin.z, 1);
41    dst->fOrigin = {x, y, z};
42}
43
44SkScalar SkPatch3D::dotWith(SkScalar dx, SkScalar dy, SkScalar dz) const {
45    SkScalar cx = fU.y * fV.z - fU.z * fV.y;
46    SkScalar cy = fU.z * fV.x - fU.x * fV.y;
47    SkScalar cz = fU.x * fV.y - fU.y * fV.x;
48
49    return cx * dx + cy * dy + cz * dz;
50}
51
52///////////////////////////////////////////////////////////////////////////////
53
54SkCamera3D::SkCamera3D() {
55    this->reset();
56}
57
58void SkCamera3D::reset() {
59    fLocation = {0, 0, -SkIntToScalar(576)};   // 8 inches backward
60    fAxis = {0, 0, SK_Scalar1};                // forward
61    fZenith = {0, -SK_Scalar1, 0};             // up
62
63    fObserver = {0, 0, fLocation.z};
64
65    fNeedToUpdate = true;
66}
67
68void SkCamera3D::update() {
69    fNeedToUpdate = true;
70}
71
72void SkCamera3D::doUpdate() const {
73    SkV3    axis, zenith, cross;
74
75    // construct a orthonormal basis of cross (x), zenith (y), and axis (z)
76    axis = fAxis.normalize();
77
78    zenith = fZenith - (axis * fZenith) * axis;
79    zenith = zenith.normalize();
80
81    cross = axis.cross(zenith);
82
83    {
84        SkMatrix* orien = &fOrientation;
85        auto [x, y, z] = fObserver;
86
87        // Looking along the view axis we have:
88        //
89        //   /|\ zenith
90        //    |
91        //    |
92        //    |  * observer (projected on XY plane)
93        //    |
94        //    |____________\ cross
95        //                 /
96        //
97        // So this does a z-shear along the view axis based on the observer's x and y values,
98        // and scales in x and y relative to the negative of the observer's z value
99        // (the observer is in the negative z direction).
100
101        orien->set(SkMatrix::kMScaleX, x * axis.x - z * cross.x);
102        orien->set(SkMatrix::kMSkewX,  x * axis.y - z * cross.y);
103        orien->set(SkMatrix::kMTransX, x * axis.z - z * cross.z);
104        orien->set(SkMatrix::kMSkewY,  y * axis.x - z * zenith.x);
105        orien->set(SkMatrix::kMScaleY, y * axis.y - z * zenith.y);
106        orien->set(SkMatrix::kMTransY, y * axis.z - z * zenith.z);
107        orien->set(SkMatrix::kMPersp0, axis.x);
108        orien->set(SkMatrix::kMPersp1, axis.y);
109        orien->set(SkMatrix::kMPersp2, axis.z);
110    }
111}
112
113void SkCamera3D::patchToMatrix(const SkPatch3D& quilt, SkMatrix* matrix) const {
114    if (fNeedToUpdate) {
115        this->doUpdate();
116        fNeedToUpdate = false;
117    }
118
119    const SkScalar* mapPtr = (const SkScalar*)(const void*)&fOrientation;
120    const SkScalar* patchPtr;
121
122    SkV3 diff = quilt.fOrigin - fLocation;
123    SkScalar dot = diff.dot({mapPtr[6], mapPtr[7], mapPtr[8]});
124
125    // This multiplies fOrientation by the matrix [quilt.fU quilt.fV diff] -- U, V, and diff are
126    // column vectors in the matrix -- then divides by the length of the projection of diff onto
127    // the view axis (which is 'dot'). This transforms the patch (which transforms from local path
128    // space to world space) into view space (since fOrientation transforms from world space to
129    // view space).
130    //
131    // The divide by 'dot' isn't strictly necessary as the homogeneous divide would do much the
132    // same thing (it's just scaling the entire matrix by 1/dot). It looks like it's normalizing
133    // the matrix into some canonical space.
134    patchPtr = (const SkScalar*)&quilt;
135    matrix->set(SkMatrix::kMScaleX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
136    matrix->set(SkMatrix::kMSkewY,  SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
137    matrix->set(SkMatrix::kMPersp0, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
138
139    patchPtr += 3;
140    matrix->set(SkMatrix::kMSkewX,  SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
141    matrix->set(SkMatrix::kMScaleY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
142    matrix->set(SkMatrix::kMPersp1, SkScalarDotDiv(3, patchPtr, 1, mapPtr+6, 1, dot));
143
144    patchPtr = (const SkScalar*)(const void*)&diff;
145    matrix->set(SkMatrix::kMTransX, SkScalarDotDiv(3, patchPtr, 1, mapPtr, 1, dot));
146    matrix->set(SkMatrix::kMTransY, SkScalarDotDiv(3, patchPtr, 1, mapPtr+3, 1, dot));
147    matrix->set(SkMatrix::kMPersp2, SK_Scalar1);
148}
149
150///////////////////////////////////////////////////////////////////////////////
151
152Sk3DView::Sk3DView() {
153    fRec = &fInitialRec;
154}
155
156Sk3DView::~Sk3DView() {
157    Rec* rec = fRec;
158    while (rec != &fInitialRec) {
159        Rec* next = rec->fNext;
160        delete rec;
161        rec = next;
162    }
163}
164
165void Sk3DView::save() {
166    Rec* rec = new Rec;
167    rec->fNext = fRec;
168    rec->fMatrix = fRec->fMatrix;
169    fRec = rec;
170}
171
172void Sk3DView::restore() {
173    SkASSERT(fRec != &fInitialRec);
174    Rec* next = fRec->fNext;
175    delete fRec;
176    fRec = next;
177}
178
179void Sk3DView::setCameraLocation(SkScalar x, SkScalar y, SkScalar z) {
180    // the camera location is passed in inches, set in pt
181    SkScalar lz = z * 72.0f;
182    fCamera.fLocation = {x * 72.0f, y * 72.0f, lz};
183    fCamera.fObserver = {0, 0, lz};
184    fCamera.update();
185
186}
187
188SkScalar Sk3DView::getCameraLocationX() const {
189    return fCamera.fLocation.x / 72.0f;
190}
191
192SkScalar Sk3DView::getCameraLocationY() const {
193    return fCamera.fLocation.y / 72.0f;
194}
195
196SkScalar Sk3DView::getCameraLocationZ() const {
197    return fCamera.fLocation.z / 72.0f;
198}
199
200void Sk3DView::translate(SkScalar x, SkScalar y, SkScalar z) {
201    fRec->fMatrix.preTranslate(x, y, z);
202}
203
204void Sk3DView::rotateX(SkScalar deg) {
205    fRec->fMatrix.preConcat(SkM44::Rotate({1, 0, 0}, deg * SK_ScalarPI / 180));
206}
207
208void Sk3DView::rotateY(SkScalar deg) {
209    fRec->fMatrix.preConcat(SkM44::Rotate({0,-1, 0}, deg * SK_ScalarPI / 180));
210}
211
212void Sk3DView::rotateZ(SkScalar deg) {
213    fRec->fMatrix.preConcat(SkM44::Rotate({0, 0, 1}, deg * SK_ScalarPI / 180));
214}
215
216SkScalar Sk3DView::dotWithNormal(SkScalar x, SkScalar y, SkScalar z) const {
217    SkPatch3D   patch;
218    patch.transform(fRec->fMatrix);
219    return patch.dotWith(x, y, z);
220}
221
222void Sk3DView::getMatrix(SkMatrix* matrix) const {
223    if (matrix != nullptr) {
224        SkPatch3D   patch;
225        patch.transform(fRec->fMatrix);
226        fCamera.patchToMatrix(patch, matrix);
227    }
228}
229
230#include "include/core/SkCanvas.h"
231
232void Sk3DView::applyToCanvas(SkCanvas* canvas) const {
233    SkMatrix    matrix;
234
235    this->getMatrix(&matrix);
236    canvas->concat(matrix);
237}
238