1 /*
2  * Copyright 2022 The Android Open Source Project
3  * Copyright (C) 2006 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include "Conic.h"
19 
20 #include "scalar.h"
21 
22 #include "math/vec2.h"
23 
24 #include <cmath>
25 #include <cstring>
26 
27 using namespace filament::math;
28 
isFinite(const Point points[],int count)29 constexpr bool isFinite(const Point points[], int count) noexcept {
30     return isFinite(&points[0].x, count << 1);
31 }
32 
isFinite(const Point & point)33 constexpr bool isFinite(const Point& point) noexcept {
34     float a = 0.0f;
35     a *= point.x;
36     a *= point.y;
37     return a == 0.0f;
38 }
39 
toPoint(const float2 & v)40 constexpr Point toPoint(const float2& v) noexcept {
41     return { .x = v.x, .y = v.y };
42 }
43 
fromPoint(const Point & v)44 constexpr float2 fromPoint(const Point& v) noexcept {
45     return float2{v.x, v.y};
46 }
47 
conicToQuadratics(const Point conicPoints[3],Point * quadraticPoints,int bufferSize,float weight,float tolerance)48 int conicToQuadratics(
49     const Point conicPoints[3], Point *quadraticPoints, int bufferSize,
50     float weight, float tolerance
51 ) noexcept {
52     Conic conic(conicPoints[0], conicPoints[1], conicPoints[2], weight);
53 
54     int count = conic.computeQuadraticCount(tolerance);
55     int quadraticCount = 1 << count;
56     if (quadraticCount > bufferSize) {
57         // Buffer not large enough; return necessary size to resize and try again
58         return quadraticCount;
59     }
60     quadraticCount = conic.splitIntoQuadratics(quadraticPoints, count);
61 
62     return quadraticCount;
63 }
64 
toQuadratics(const Point points[3],float weight,float tolerance)65 const Point* ConicConverter::toQuadratics(
66         const Point points[3], float weight, float tolerance
67 ) noexcept {
68     Conic conic(points[0], points[1], points[2], weight);
69 
70     int count = conic.computeQuadraticCount(tolerance);
71     mQuadraticCount = conic.splitIntoQuadratics(mStorage, count);
72 
73     return mStorage;
74 }
75 
computeQuadraticCount(float tolerance) const76 int Conic::computeQuadraticCount(float tolerance) const noexcept {
77     if (tolerance <= 0.0f || !isFinite(tolerance) || !isFinite(points, 3)) return 0;
78 
79     float a = weight - 1.0f;
80     float k = a / (4.0f * (2.0f + a));
81     float x = k * (points[0].x - 2.0f * points[1].x + points[2].x);
82     float y = k * (points[0].y - 2.0f * points[1].y + points[2].y);
83 
84     float error = std::sqrtf(x * x + y * y);
85     int count = 0;
86     for ( ; count < kMaxConicToQuadCount; count++) {
87         if (error <= tolerance) break;
88         error *= 0.25f;
89     }
90 
91     return count;
92 }
93 
subdivide(const Conic & src,Point pts[],int level)94 static Point* subdivide(const Conic& src, Point pts[], int level) {
95     if (level == 0) {
96         memcpy(pts, &src.points[1], 2 * sizeof(Point));
97         return pts + 2;
98     } else {
99         Conic dst[2];
100         src.split(dst);
101         const float startY = src.points[0].y;
102         const float endY = src.points[2].y;
103         if (between(startY, src.points[1].y, endY)) {
104             float midY = dst[0].points[2].y;
105             if (!between(startY, midY, endY)) {
106                 float closerY = tabs(midY - startY) < tabs(midY - endY) ? startY : endY;
107                 dst[0].points[2].y = dst[1].points[0].y = closerY;
108             }
109             if (!between(startY, dst[0].points[1].y, dst[0].points[2].y)) {
110                 dst[0].points[1].y = startY;
111             }
112             if (!between(dst[1].points[0].y, dst[1].points[1].y, endY)) {
113                 dst[1].points[1].y = endY;
114             }
115         }
116         --level;
117         pts = subdivide(dst[0], pts, level);
118         return subdivide(dst[1], pts, level);
119     }
120 }
121 
split(Conic * __restrict__ dst) const122 void Conic::split(Conic* __restrict__ dst) const noexcept {
123     float2 scale{1.0f / (1.0f + weight)};
124     float newW = std::sqrtf(0.5f + weight * 0.5f);
125 
126     float2 p0 = fromPoint(points[0]);
127     float2 p1 = fromPoint(points[1]);
128     float2 p2 = fromPoint(points[2]);
129     float2 ww(weight);
130 
131     float2 wp1 = ww * p1;
132     float2 m = (p0 + (wp1 + wp1) + p2) * scale * float2(0.5f);
133     Point pt = toPoint(m);
134     if (!isFinite(pt)) {
135         double w_d = weight;
136         double w_2 = w_d * 2.0;
137         double scale_half = 1.0 / (1.0 + w_d) * 0.5;
138         pt.x = float((points[0].x + w_2 * points[1].x + points[2].x) * scale_half);
139         pt.y = float((points[0].y + w_2 * points[1].y + points[2].y) * scale_half);
140     }
141     dst[0].points[0] = points[0];
142     dst[0].points[1] = toPoint((p0 + wp1) * scale);
143     dst[0].points[2] = dst[1].points[0] = pt;
144     dst[1].points[1] = toPoint((wp1 + p2) * scale);
145     dst[1].points[2] = points[2];
146 
147     dst[0].weight = dst[1].weight = newW;
148 }
149 
splitIntoQuadratics(Point dstPoints[],int count) const150 int Conic::splitIntoQuadratics(Point dstPoints[], int count) const noexcept {
151     *dstPoints = points[0];
152 
153     if (count >= kMaxConicToQuadCount) {
154         Conic dst[2];
155         split(dst);
156 
157         if (equals(dst[0].points[1], dst[0].points[2]) &&
158             equals(dst[1].points[0], dst[1].points[1])) {
159             dstPoints[1] = dstPoints[2] = dstPoints[3] = dst[0].points[1];
160             dstPoints[4] = dst[1].points[2];
161             count = 1;
162             goto commonFinitePointCheck;
163         }
164     }
165 
166     subdivide(*this, dstPoints + 1, count);
167 
168 commonFinitePointCheck:
169     const int quadCount = 1 << count;
170     const int pointCount = 2 * quadCount + 1;
171 
172     if (!isFinite(dstPoints, pointCount)) {
173         for (int i = 1; i < pointCount - 1; ++i) {
174             dstPoints[i] = points[1];
175         }
176     }
177 
178     return quadCount;
179 }
180