lottie: ensure proper shape closure
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run

- Ensured proper closure of star and polygon shapes.
The start and end points now match - in cases with
degenerate bezier curves, the second-to-last point
is also aligned.
Proper shape closure is necessary for modifiers like
offset (future pucker bloat). If the start and end
points aren’t equal (within the comparison function’s
precision), the shape will be closed with a straight
line - and during offsetting, that line will become
visible, even though it’s not intended.

- Use of the internal _zero() function for point equality check
in modifiers algs led to incorrect results when p2.x or p2.y
was zero (division by zero).
The intent was to treat nearly identical points as equal, but
this approach was flawed - at the modifier stage, it’s no longer
possible to tell if small gaps are intentional or just due to
limited numerical precision (as seen for example in the difference
between the start and end points of star/polygon shapes).
This commit is contained in:
Mira Grudzinska 2025-06-18 23:55:28 +02:00 committed by Hermet Park
parent e20562c8c2
commit 2679880bc3
2 changed files with 14 additions and 10 deletions

View file

@ -488,6 +488,13 @@ void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float
}
static void _close(Array<Point>& pts, const Point& p, bool round)
{
if (round && tvg::zero(pts.last() - pts[pts.count - 2])) pts[pts.count - 2] = p;
pts.last() = p;
}
void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx, Tween& tween, LottieExpressions* exps)
{
static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
@ -595,6 +602,8 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran
angle += dTheta * direction;
longSegment = !longSegment;
}
//ensure proper shape closure - important for modifiers that behave differently for degenerate (linear) vs curved cubics
_close(SHAPE(shape)->rs.path.pts, in, hasRoundness);
shape->close();
if (ctx->modifier) ctx->modifier->modifyPolystar(SHAPE(shape)->rs.path, SHAPE(merging)->rs.path, outerRoundness, hasRoundness);
@ -663,6 +672,8 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
}
angle += anglePerPoint * direction;
}
//ensure proper shape closure - important for modifiers that behave differently for degenerate (linear) vs curved cubics
_close(SHAPE(shape)->rs.path.pts, in, hasRoundness);
shape->close();
if (ctx->modifier) ctx->modifier->modifyPolystar(SHAPE(shape)->rs.path, SHAPE(merging)->rs.path, 0.0f, false);

View file

@ -52,16 +52,9 @@ static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, Point& pre
}
static bool _zero(Point& p1, Point& p2)
{
constexpr float epsilon = 1e-3f;
return fabsf(p1.x / p2.x - 1.0f) < epsilon && fabsf(p1.y / p2.y - 1.0f) < epsilon;
}
static bool _intersect(Line& line1, Line& line2, Point& intersection, bool& inside)
{
if (_zero(line1.pt2, line2.pt1)) {
if (tvg::zero(line1.pt2 - line2.pt1)) {
intersection = line1.pt2;
inside = true;
return true;
@ -168,7 +161,7 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i
Line nextLine = state.firstLine;
if (inCmds[curCmd + 1] == PathCommand::LineTo) nextLine = _offset(inPts[curPt + degenerated], inPts[curPt + 1 + degenerated], offset);
else if (inCmds[curCmd + 1] == PathCommand::CubicTo) nextLine = _offset(inPts[curPt + 1 + degenerated], inPts[curPt + 2 + degenerated], offset);
else if (inCmds[curCmd + 1] == PathCommand::Close && !_zero(inPts[curPt + degenerated], inPts[state.movetoInIndex + degenerated]))
else if (inCmds[curCmd + 1] == PathCommand::Close && !tvg::zero(inPts[curPt + degenerated] - inPts[state.movetoInIndex + degenerated]))
nextLine = _offset(inPts[curPt + degenerated], inPts[state.movetoInIndex + degenerated], offset);
corner(out, state.line, nextLine, state.movetoOutIndex, inCmds[curCmd + 1] == PathCommand::Close);
@ -386,7 +379,7 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
iPt += 3;
}
else {
if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) {
if (!tvg::zero(inPts[iPt - 1] - inPts[state.movetoInIndex])) {
out.cmds.push(PathCommand::LineTo);
corner(out, state.line, state.firstLine, state.movetoOutIndex, true);
}