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