Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
geometry.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-FileCopyrightText: 2024-2025 hypengw <hypengwip@gmail.com>
3// SPDX-License-Identifier: MPL-2.0
4
5#include "geometry.h"
6#include "skia_shadow.h"
7
8namespace Fluid {
9using scalar = float;
10
11namespace SceneGraph {
12
13namespace {
14/*
15 0 ------- 1
16 | /
17 | /
18 | /
19 | /
20 | /
21 | /
22 2
23 5
24 / |
25 / |
26 / |
27 / |
28 / |
29 / |
30 3 ------- 4
31*/
32
33template <typename T>
34inline void set_rect(T *v, T *lt, T *rt, T *lb, T *rb)
35{
36 v[0] = *lt;
37 v[1] = *rt;
38 v[2] = *lb;
39 v[3] = *lb;
40 v[4] = *rb;
41 v[5] = *rt;
42}
43
44template <typename T>
45inline void set_rect_flip(T *v, T *lt, T *rt, T *lb, T *rb)
46{
47 v[0] = *lt;
48 v[1] = *rt;
49 v[2] = *rb;
50 v[3] = *rb;
51 v[4] = *lt;
52 v[5] = *lb;
53}
54
55// four points
56
57inline void secorners(RectangleVertex *v, QVector2D o, QVector2D s, float outter, float inner)
58{
59 auto helper = [](RectangleVertex *v, QVector2D lt, QVector2D rt, QVector2D lb, QVector2D rb,
60 float outter, float inner) {
61 v[0].setPoint(lt);
62 v[0].ce_x = 0;
63 v[0].ce_y = 0;
64 v[0].ce_distance_to_outter = outter;
65 v[0].ce_distance_to_inner = inner;
66
67 v[1].setPoint(rt);
68 v[1].ce_x = 0;
69 v[1].ce_y = 0;
70 v[1].ce_distance_to_outter = outter;
71 v[1].ce_distance_to_inner = inner;
72
73 v[2].setPoint(lb);
74 v[2].ce_x = 0;
75 v[2].ce_y = 0;
76 v[2].ce_distance_to_outter = outter;
77 v[2].ce_distance_to_inner = inner;
78
79 v[3].setPoint(lb);
80 v[3].ce_x = 0;
81 v[3].ce_y = 0;
82 v[3].ce_distance_to_outter = outter;
83 v[3].ce_distance_to_inner = inner;
84
85 v[4].setPoint(rb);
86 v[4].ce_x = 0;
87 v[4].ce_y = 0;
88 v[4].ce_distance_to_outter = outter;
89 v[4].ce_distance_to_inner = inner;
90
91 v[5].setPoint(rt);
92 v[5].ce_x = 0;
93 v[5].ce_y = 0;
94 v[5].ce_distance_to_outter = outter;
95 v[5].ce_distance_to_inner = inner;
96 };
97 QVector2D lt = o;
98 QVector2D rt = o + QVector2D{ s.x(), 0 };
99 QVector2D lb = o + QVector2D{ 0, s.y() };
100 QVector2D rb = o + s;
101 helper(v, lt, rt, lb, rb, outter, inner);
102};
103
104template <typename T>
105inline void set_right_bottom_corner(T *v, QVector2D size, float r, float outter, float inner)
106{
107 secorners(v, { size.x() - r, size.y() - r }, { r, r }, outter, inner);
108
109 float offset = 1.0;
110 v[1].ce_x = offset;
111 v[5].ce_x = offset;
112
113 v[2].ce_y = offset;
114 v[3].ce_y = offset;
115
116 v[4].ce_x = 1;
117 v[4].ce_y = 1;
118}
119
120template <typename T>
121inline void set_right_top_corner(T *v, QVector2D size, float r, float outter, float inner)
122{
123 secorners(v, { size.x() - r, 0 }, { r, r }, outter, inner);
124 float offset = 1.0;
125 v[4].ce_x = offset;
126 v[0].ce_y = offset;
127
128 v[1].ce_x = 1;
129 v[1].ce_y = 1;
130 v[5].ce_x = 1;
131 v[5].ce_y = 1;
132}
133
134template <typename T>
135inline void set_left_bottom_corner(T *v, QVector2D size, float r, float outter, float inner)
136{
137 secorners(v, { 0, size.y() - r }, { r, r }, outter, inner);
138 float offset = 1.0;
139 v[0].ce_x = offset;
140 v[4].ce_y = offset;
141
142 v[2].ce_x = 1;
143 v[2].ce_y = 1;
144 v[3].ce_x = 1;
145 v[3].ce_y = 1;
146}
147
148template <typename T>
149inline void set_left_top_corner(T *v, QVector2D, float r, float outter, float inner)
150{
151 secorners(v, { }, { r, r }, outter, inner);
152 float offset = 1.0;
153 v[2].ce_x = offset;
154 v[3].ce_x = offset;
155
156 v[1].ce_y = offset;
157 v[5].ce_y = offset;
158
159 v[0].ce_x = 1;
160 v[0].ce_y = 1;
161}
162
163} // namespace
164
165} // namespace SceneGraph
166
167std::unique_ptr<QSGGeometry, std::default_delete<QSGGeometry>> SceneGraph::createRectangleGeometry()
168{
169 static QSGGeometry::Attribute attrs[] = {
170 QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType,
171 QSGGeometry::PositionAttribute),
172 QSGGeometry::Attribute::createWithAttributeType(1, 4, QSGGeometry::FloatType,
173 QSGGeometry::UnknownAttribute),
174 QSGGeometry::Attribute::createWithAttributeType(2, 4, QSGGeometry::FloatType,
175 QSGGeometry::UnknownAttribute),
176 };
177
178 static QSGGeometry::AttributeSet attrset = { 3, sizeof(RectangleVertex), attrs };
179
180 auto out = std::make_unique<QSGGeometry>(attrset, 6 * 9);
181 out->setDrawingMode(QSGGeometry::DrawTriangles);
182 return out;
183}
184
185void SceneGraph::updateRectangleGeometry(RectangleVertex *v, QVector2D size, QRgb color,
186 QVector4D radius)
187{
188 const auto u = 6;
189
190 auto lt = v;
191 auto rt = v + u;
192 auto lb = v + u * 2;
193 auto rb = v + u * 3;
194
195 auto outter = std::max({ radius[0], radius[1], radius[2], radius[3], 2.0f });
196 auto inner = -1.0f / outter;
197
198 set_left_top_corner(lt, size, radius[0], std::max(radius[0], 2.0f), inner);
199 set_right_top_corner(rt, size, radius[1], std::max(radius[1], 2.0f), inner);
200 set_left_bottom_corner(lb, size, radius[2], std::max(radius[2], 2.0f), inner);
201 set_right_bottom_corner(rb, size, radius[3], std::max(radius[3], 2.0f), inner);
202
203 auto top = v + u * 4;
204 auto left = v + u * 5;
205 auto bottom = v + u * 6;
206 auto right = v + u * 7;
207 auto middle = v + u * 8;
208
209 set_rect(top, lt + 5, rt + 0, lt + 4, rt + 2);
210 if (top[0].ce_distance_to_outter - top[1].ce_distance_to_outter > 0.1f) {
211 top[1].ce_distance_to_outter = top[0].ce_distance_to_outter * 10.0f;
212 }
213 set_rect_flip(bottom, lb + 1, rb + 0, lb + 4, rb + 2);
214 if (bottom[0].ce_distance_to_outter - bottom[1].ce_distance_to_outter > 0.1f) {
215 bottom[1].ce_distance_to_outter = bottom[0].ce_distance_to_outter * 10.0f;
216 }
217 set_rect(left, lt + 3, lt + 4, lb + 0, lb + 1);
218 if (left[0].ce_distance_to_outter - left[2].ce_distance_to_outter > 0.1f) {
219 left[2].ce_distance_to_outter = left[0].ce_distance_to_outter * 10.0f;
220 }
221 set_rect_flip(right, rt + 2, rt + 4, rb + 0, rb + 1);
222 if (right[0].ce_distance_to_outter - right[2].ce_distance_to_outter > 0.1f) {
223 right[2].ce_distance_to_outter = right[0].ce_distance_to_outter * 10.0f;
224 }
225 set_rect(middle, lt + 4, rt + 2, lb + 1, rb + 0);
226
227 for (int i = 0; i < u * 9; i++) {
228 v[i].setColor(color);
229 }
230}
231
232std::unique_ptr<QSGGeometry, std::default_delete<QSGGeometry>> SceneGraph::createShadowGeometry()
233{
234 static QSGGeometry::Attribute attrs[] = {
235 QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType,
236 QSGGeometry::PositionAttribute),
237 QSGGeometry::Attribute::createWithAttributeType(1, 4, QSGGeometry::FloatType,
238 QSGGeometry::UnknownAttribute),
239 QSGGeometry::Attribute::createWithAttributeType(2, 3, QSGGeometry::FloatType,
240 QSGGeometry::UnknownAttribute),
241 };
242
243 static QSGGeometry::AttributeSet attrset = { 3, sizeof(ShadowVertex), attrs };
244
245 auto out = std::make_unique<QSGGeometry>(attrset, 0);
246 out->setDrawingMode(QSGGeometry::DrawTriangles);
247 return out;
248}
249
250void SceneGraph::updateShadowGeometry(QSGGeometry *geo, const ShadowParams &params,
251 const QRectF &rect)
252{
253 float occluderHeight = params.z_plane_params.z();
254 bool transparent = (params.flags & ShadowFlags::TransparentOccluder_ShadowFlag);
255 bool directional = (params.flags & ShadowFlags::DirectionalLight_ShadowFlag);
256 auto devLightPos = params.light_pos;
257 auto max_radius = std::min(rect.width(), rect.height()) / 2.0;
258 bool is_oval = params.radius.x() >= max_radius && params.radius.y() >= max_radius
259 && params.radius.z() >= max_radius && params.radius.w() >= max_radius
260 && qFuzzyCompare(rect.width(), rect.height());
261
262 std::array<std::optional<Skia::ShadowCircularRRectOp>, 2> ops;
263
264 if (params.ambient_color > 0) {
265 scalar devSpaceInsetWidth = Skia::AmbientBlurRadius(occluderHeight);
266 const scalar umbraRecipAlpha = Skia::AmbientRecipAlpha(occluderHeight);
267 const scalar devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
268
269 // Outset the shadow rrect to the border of the penumbra
270 scalar ambientPathOutset = devSpaceInsetWidth; // * devToSrcScale;
271 QRectF ambientRRect = rect.adjusted(-ambientPathOutset, -ambientPathOutset,
272 ambientPathOutset, ambientPathOutset);
273 QVector4D ambientRadii;
274 for (int i = 0; i < 4; ++i)
275 ambientRadii[i] = std::min<float>(params.radius[i] + ambientPathOutset, max_radius);
276
277 if (transparent) {
278 // set a large inset to force a fill
279 devSpaceInsetWidth = ambientRRect.width();
280 }
281 ops[0] = Skia::ShadowCircularRRectOp(params.ambient_color, ambientRRect, ambientRadii,
282 is_oval, devSpaceAmbientBlur, devSpaceInsetWidth);
283 }
284
285 if (params.spot_color > 0) {
286 scalar devSpaceSpotBlur;
287 scalar spotScale;
288 QVector2D spotOffset;
289 if (directional) {
290 Skia::GetDirectionalParams(occluderHeight, devLightPos.x(), devLightPos.y(),
291 devLightPos.z(), params.light_radius, &devSpaceSpotBlur,
292 &spotScale, &spotOffset);
293 } else {
294 Skia::GetSpotParams(occluderHeight, devLightPos.x(), devLightPos.y(), devLightPos.z(),
295 params.light_radius, &devSpaceSpotBlur, &spotScale, &spotOffset);
296 }
297 // handle scale of radius due to CTM
298 const scalar srcSpaceSpotBlur = devSpaceSpotBlur; // * devToSrcScale;
299
300 // // This offset is in dev space, need to transform it into source space.
301 // SkMatrix ctmInverse;
302 // if (viewMatrix.invert(&ctmInverse)) {
303 // ctmInverse.mapPoints(&spotOffset, 1);
304 // } else {
305 // // Since the matrix is a similarity, this should never happen, but just in case...
306 // SkDebugf("Matrix is degenerate. Will not render spot shadow correctly!\n");
307 // SkASSERT(false);
308 // }
309
310 // Compute the transformed shadow rrect
311 QRectF spotShadowRRect;
312 {
313 auto dw = (spotScale - 1.0f) * rect.width() / 2.0f;
314 auto dh = (spotScale - 1.0f) * rect.height() / 2.0f;
315 spotShadowRRect =
316 rect.translated(spotOffset.x(), spotOffset.y()).adjusted(-dw, -dh, dw, dh);
317 }
318 // SkMatrix shadowTransform;
319 // shadowTransform.setScaleTranslate(spotScale, spotScale, spotOffset.fX, spotOffset.fY);
320 // rrect.transform(shadowTransform, &spotShadowRRect);
321 QVector4D spotRadii = params.radius * spotScale;
322
323 // Compute the insetWidth
324 scalar blurOutset = srcSpaceSpotBlur;
325 scalar insetWidth = blurOutset;
326 if (transparent) {
327 // If transparent, just do a fill
328 insetWidth += spotShadowRRect.width();
329 } else {
330 // For shadows, instead of using a stroke we specify an inset from the penumbra
331 // border. We want to extend this inset area so that it meets up with the caster
332 // geometry. The inset geometry will by default already be inset by the blur width.
333 //
334 // We compare the min and max corners inset by the radius between the original
335 // rrect and the shadow rrect. The distance between the two plus the difference
336 // between the scaled radius and the original radius gives the distance from the
337 // transformed shadow shape to the original shape in that corner. The max
338 // of these gives the maximum distance we need to cover.
339 //
340 // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
341 // that to get the full insetWidth.
342 scalar maxOffset;
343 // is rect
344 if (params.radius.length() == 0) {
345 // Manhattan distance works better for rects
346 maxOffset = std::max(std::max(std::abs(spotShadowRRect.left() - rect.left()),
347 std::abs(spotShadowRRect.top() - rect.top())),
348 std::max(std::abs(spotShadowRRect.right() - rect.right()),
349 std::abs(spotShadowRRect.bottom() - rect.bottom())));
350 } else {
351 scalar dr = 0;
352 for (int i = 0; i < 4; ++i)
353 dr = std::max(dr, spotRadii[i] - params.radius[i]);
354 QVector2D upperLeftOffset = QVector2D(spotShadowRRect.left() - rect.left() + dr,
355 spotShadowRRect.top() - rect.top() + dr);
356 QVector2D lowerRightOffset =
357 QVector2D(spotShadowRRect.right() - rect.right() - dr,
358 spotShadowRRect.bottom() - rect.bottom() - dr);
359 maxOffset = std::sqrt(std::max(upperLeftOffset.lengthSquared(),
360 lowerRightOffset.lengthSquared()))
361 + dr;
362 }
363 insetWidth += std::max(blurOutset, maxOffset);
364 }
365
366 // Outset the shadow rrect to the border of the penumbra
367 spotShadowRRect.adjust(-blurOutset, -blurOutset, blurOutset, blurOutset);
368 spotRadii += QVector4D(blurOutset, blurOutset, blurOutset, blurOutset);
369
370 ops[1] = Skia::ShadowCircularRRectOp(params.spot_color, spotShadowRRect, spotRadii,
371 is_oval, 2.0f * devSpaceSpotBlur, insetWidth);
372 }
373
374 int vertex_count = 0;
375 int index_count = 0;
376 for (auto &p : ops) {
377 if (p) {
378 vertex_count += p->fVertCount;
379 index_count += p->fIndexCount;
380 }
381 }
382 geo->allocate(vertex_count, index_count);
383
384 auto vertices = static_cast<ShadowVertex *>(geo->vertexData());
385 auto indexes = (std::uint16_t *)geo->indexData();
386 int vertice_offset = 0;
387 for (auto &p : ops) {
388 if (p) {
389 auto type = p->fGeoData.type;
390 if (p->fGeoData.is_circle) {
391 bool isStroked = (Skia::ShadowCircularRRectOp::kStroke_RRectType == type);
392 p->fillInCircleVerts(isStroked, &vertices);
393 } else {
394 p->fillInRRectVerts(&vertices);
395 }
396 for (int i = 0; i < p->fIndexCount; i++) {
397 indexes[i] = p->fIndexPtr[i] + vertice_offset;
398 }
399 indexes += p->fIndexCount;
400 vertice_offset += p->fVertCount;
401 }
402 }
403}
404
405} // namespace Fluid
std::unique_ptr< QSGGeometry, std::default_delete< QSGGeometry > > createShadowGeometry()
Definition geometry.cpp:232
std::unique_ptr< QSGGeometry, std::default_delete< QSGGeometry > > createRectangleGeometry()
Definition geometry.cpp:167
void updateShadowGeometry(QSGGeometry *geo, const ShadowParams &params, const QRectF &rect)
Definition geometry.cpp:250
void updateRectangleGeometry(RectangleVertex *vertexs, QVector2D size, QRgb color, QVector4D radius)
Definition geometry.cpp:185
void GetSpotParams(scalar occluderZ, scalar lightX, scalar lightY, scalar lightZ, scalar lightRadius, scalar *blurRadius, scalar *scale, QVector2D *translate)
Definition skia_shadow.h:88
scalar AmbientBlurRadius(scalar height)
Definition skia_shadow.h:74
void GetDirectionalParams(scalar occluderZ, scalar lightX, scalar lightY, scalar lightZ, scalar lightRadius, scalar *blurRadius, scalar *scale, QVector2D *translate)
Definition skia_shadow.h:98
scalar AmbientRecipAlpha(scalar height)
Definition skia_shadow.h:78
float scalar
Definition geometry.cpp:9
void setColor(QRgb c) noexcept
Definition geometry.h:35