diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 4d7aa21d..c5cb5305 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -191,13 +191,12 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut } auto skewAngle = transform->skewAngle(frameNo, exps); - if (fabsf(skewAngle) > 0.01f) { + if (skewAngle != 0.0f) { // For angles where tangent explodes, the shape degenerates into an infinitely thin line. // This is handled by zeroing out the matrix due to finite numerical precision. skewAngle = fmod(skewAngle, 180.0f); if (fabsf(skewAngle - 90.0f) < 0.01f || fabsf(skewAngle + 90.0f) < 0.01f) return false; - auto skewAxis = transform->skewAxis(frameNo, exps); - _skew(&matrix, skewAngle, skewAxis); + _skew(&matrix, skewAngle, transform->skewAxis(frameNo, exps)); } auto scale = transform->scale(frameNo, exps); @@ -498,7 +497,7 @@ static void _updateRect(LottieGroup* parent, LottieObject** child, float frameNo auto roundness = rect->radius(frameNo, exps); if (ctx->roundness > roundness) roundness = ctx->roundness; - if (roundness > 0.0f) { + if (roundness > ROUNDNESS_EPSILON) { if (roundness > size.x * 0.5f) roundness = size.x * 0.5f; if (roundness > size.y * 0.5f) roundness = size.y * 0.5f; } @@ -656,10 +655,72 @@ static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo } +static void _applyRoundedCorner(Shape* star, Shape* merging, float outerRoundness, float roundness, bool hasRoundness) +{ + static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; + + auto cmdCnt = star->pathCommands(nullptr); + const Point *pts = nullptr; + auto ptsCnt = star->pathCoords(&pts); + + auto len = mathLength(pts[1] - pts[2]); + auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * mathMin(len * 0.5f, roundness) / len : 0.0f; + + if (hasRoundness) { + P(merging)->rs.path.cmds.grow((uint32_t)(1.5 * cmdCnt)); + P(merging)->rs.path.pts.grow((uint32_t)(4.5 * cmdCnt)); + + int start = 3 * mathZero(outerRoundness); + merging->moveTo(pts[start].x, pts[start].y); + + for (uint32_t i = 1 + start; i < ptsCnt; i += 6) { + auto& prev = pts[i]; + auto& curr = pts[i + 2]; + auto& next = (i < ptsCnt - start) ? pts[i + 4] : pts[2]; + auto& nextCtrl = (i < ptsCnt - start) ? pts[i + 5] : pts[3]; + auto dNext = r * (curr - next); + auto dPrev = r * (curr - prev); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + merging->cubicTo(prev.x, prev.y, p0.x, p0.y, p0.x, p0.y); + merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + merging->cubicTo(p3.x, p3.y, next.x, next.y, nextCtrl.x, nextCtrl.y); + } + } else { + P(merging)->rs.path.cmds.grow(2 * cmdCnt); + P(merging)->rs.path.pts.grow(4 * cmdCnt); + + auto dPrev = r * (pts[1] - pts[0]); + auto p = pts[0] + 2.0f * dPrev; + merging->moveTo(p.x, p.y); + + for (uint32_t i = 1; i < ptsCnt; ++i) { + auto& curr = pts[i]; + auto& next = (i == ptsCnt - 1) ? pts[1] : pts[i + 1]; + auto dNext = r * (curr - next); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + merging->lineTo(p0.x, p0.y); + merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + + dPrev = -1.0f * dNext; + } + } + merging->close(); +} + + static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, float roundness, float frameNo, Shape* merging, LottieExpressions* exps) { static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; - static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; auto ptsCnt = star->ptsCnt(frameNo, exps); auto innerRadius = star->innerRadius(frameNo, exps); @@ -676,8 +737,9 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans auto numPoints = size_t(ceilf(ptsCnt) * 2); auto direction = (star->direction == 0) ? 1.0f : -1.0f; auto hasRoundness = false; - bool applyExtRoundness = roundness > ROUNDNESS_EPSILON && (mathZero(innerRoundness) || mathZero(outerRoundness)); - auto shape = applyExtRoundness ? Shape::gen().release() : merging; + bool roundedCorner = (roundness > ROUNDNESS_EPSILON) && (mathZero(innerRoundness) || mathZero(outerRoundness)); + //TODO: we can use PathCommand / PathCoord directly. + auto shape = roundedCorner ? Shape::gen().release() : merging; float x, y; @@ -766,63 +828,8 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans } shape->close(); - if (applyExtRoundness) { - auto cmdCnt = shape->pathCommands(nullptr); - const Point *pts = nullptr; - auto ptsCnt = shape->pathCoords(&pts); - - auto len = mathLength(pts[1] - pts[2]); - auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * mathMin(len * 0.5f, roundness) / len : 0.0f; - - if (hasRoundness) { - P(merging)->rs.path.cmds.grow((uint32_t)(1.5 * cmdCnt)); - P(merging)->rs.path.pts.grow((uint32_t)(4.5 * cmdCnt)); - - int start = 3 * mathZero(outerRoundness); - merging->moveTo(pts[start].x, pts[start].y); - - for (uint32_t i = 1 + start; i < ptsCnt; i += 6) { - auto& prev = pts[i]; - auto& curr = pts[i + 2]; - auto& next = (i < ptsCnt - start) ? pts[i + 4] : pts[2]; - auto& nextCtrl = (i < ptsCnt - start) ? pts[i + 5] : pts[3]; - auto dNext = r * (curr - next); - auto dPrev = r * (curr - prev); - - auto p0 = curr - 2.0f * dPrev; - auto p1 = curr - dPrev; - auto p2 = curr - dNext; - auto p3 = curr - 2.0f * dNext; - - merging->cubicTo(prev.x, prev.y, p0.x, p0.y, p0.x, p0.y); - merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - merging->cubicTo(p3.x, p3.y, next.x, next.y, nextCtrl.x, nextCtrl.y); - } - } else { - P(merging)->rs.path.cmds.grow(2 * cmdCnt); - P(merging)->rs.path.pts.grow(4 * cmdCnt); - - auto dPrev = r * (pts[1] - pts[0]); - auto p = pts[0] + 2.0f * dPrev; - merging->moveTo(p.x, p.y); - - for (uint32_t i = 1; i < ptsCnt; ++i) { - auto& curr = pts[i]; - auto& next = (i == ptsCnt - 1) ? pts[1] : pts[i + 1]; - auto dNext = r * (curr - next); - - auto p0 = curr - 2.0f * dPrev; - auto p1 = curr - dPrev; - auto p2 = curr - dNext; - auto p3 = curr - 2.0f * dNext; - - merging->lineTo(p0.x, p0.y); - merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - - dPrev = -1.0f * dNext; - } - } - merging->close(); + if (roundedCorner) { + _applyRoundedCorner(shape, merging, outerRoundness, roundness, hasRoundness); delete(shape); } } diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index cb521642..f94678e3 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -210,30 +210,30 @@ struct LottieExpression }; -static void _copy(PathSet& pathset, Array& outPts, Matrix* transform) +static void _copy(PathSet* pathset, Array& outPts, Matrix* transform) { Array inPts; if (transform) { - for (int i = 0; i < pathset.ptsCnt; ++i) { - Point pt = pathset.pts[i]; + for (int i = 0; i < pathset->ptsCnt; ++i) { + Point pt = pathset->pts[i]; mathMultiply(&pt, transform); outPts.push(pt); } } else { - inPts.data = pathset.pts; - inPts.count = pathset.ptsCnt; + inPts.data = pathset->pts; + inPts.count = pathset->ptsCnt; outPts.push(inPts); inPts.data = nullptr; } } -static void _copy(PathSet& pathset, Array& outCmds) +static void _copy(PathSet* pathset, Array& outCmds) { Array inCmds; - inCmds.data = pathset.cmds; - inCmds.count = pathset.cmdsCnt; + inCmds.data = pathset->cmds; + inCmds.count = pathset->cmdsCnt; outCmds.push(inCmds); inCmds.data = nullptr; } @@ -258,44 +258,44 @@ static void _roundCorner(Array& cmds, Array& pts, const Poin } -static void _handleCorners(const PathSet& path, Array& cmds, Array& pts, Matrix* transform, float roundness) +static bool _modifier(Point* inputPts, uint32_t inputPtsCnt, PathCommand* inputCmds, uint32_t inputCmdsCnt, Array& cmds, Array& pts, Matrix* transform, float roundness) { - cmds.reserve(path.cmdsCnt * 2); - pts.reserve((uint16_t)(path.ptsCnt * 1.5)); + cmds.reserve(inputCmdsCnt * 2); + pts.reserve((uint16_t)(inputPtsCnt * 1.5)); auto ptsCnt = pts.count; auto startIndex = 0; - for (auto iCmds = 0, iPts = 0; iCmds < path.cmdsCnt; ++iCmds) { - switch (path.cmds[iCmds]) { + for (uint32_t iCmds = 0, iPts = 0; iCmds < inputCmdsCnt; ++iCmds) { + switch (inputCmds[iCmds]) { case PathCommand::MoveTo: { startIndex = pts.count; cmds.push(PathCommand::MoveTo); - pts.push(path.pts[iPts++]); + pts.push(inputPts[iPts++]); break; } case PathCommand::CubicTo: { - auto& prev = path.pts[iPts - 1]; - auto& curr = path.pts[iPts + 2]; - if (iCmds < path.cmdsCnt - 1 && - mathZero(path.pts[iPts - 1] - path.pts[iPts]) && - mathZero(path.pts[iPts + 1] - path.pts[iPts + 2])) { - if (path.cmds[iCmds + 1] == PathCommand::CubicTo && - mathZero(path.pts[iPts + 2] - path.pts[iPts + 3]) && - mathZero(path.pts[iPts + 4] - path.pts[iPts + 5])) { - _roundCorner(cmds, pts, prev, curr, path.pts[iPts + 5], roundness); + auto& prev = inputPts[iPts - 1]; + auto& curr = inputPts[iPts + 2]; + if (iCmds < inputCmdsCnt - 1 && + mathZero(inputPts[iPts - 1] - inputPts[iPts]) && + mathZero(inputPts[iPts + 1] - inputPts[iPts + 2])) { + if (inputCmds[iCmds + 1] == PathCommand::CubicTo && + mathZero(inputPts[iPts + 2] - inputPts[iPts + 3]) && + mathZero(inputPts[iPts + 4] - inputPts[iPts + 5])) { + _roundCorner(cmds, pts, prev, curr, inputPts[iPts + 5], roundness); iPts += 3; break; - } else if (path.cmds[iCmds + 1] == PathCommand::Close) { - _roundCorner(cmds, pts, prev, curr, path.pts[2], roundness); + } else if (inputCmds[iCmds + 1] == PathCommand::Close) { + _roundCorner(cmds, pts, prev, curr, inputPts[2], roundness); pts[startIndex] = pts.last(); iPts += 3; break; } } cmds.push(PathCommand::CubicTo); - pts.push(path.pts[iPts++]); - pts.push(path.pts[iPts++]); - pts.push(path.pts[iPts++]); + pts.push(inputPts[iPts++]); + pts.push(inputPts[iPts++]); + pts.push(inputPts[iPts++]); break; } case PathCommand::Close: { @@ -309,6 +309,7 @@ static void _handleCorners(const PathSet& path, Array& cmds, Array< for (auto i = ptsCnt; i < pts.count; ++i) mathTransform(transform, &pts[i]); } + return true; } @@ -442,8 +443,8 @@ struct LottieGenericProperty : LottieProperty T operator()(float frameNo, LottieExpressions* exps) { - T out{}; if (exps && (exp && exp->enabled)) { + T out{}; if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); if (exps->result>(frameNo, out, exp)) return out; } @@ -531,59 +532,29 @@ struct LottiePathSet : LottieProperty bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness) { - if (!frames) { - if (roundness > ROUNDNESS_EPSILON) { - _handleCorners(value, cmds, pts, transform, roundness); - return true; + PathSet* path = nullptr; + LottieScalarFrame* frame = nullptr; + float t; + bool interpolate = false; + + if (!frames) path = &value; + else if (frames->count == 1 || frameNo <= frames->first().no) path = &frames->first().value; + else if (frameNo >= frames->last().no) path = &frames->last().value; + else { + frame = frames->data + _bsearch(frames, frameNo); + if (mathEqual(frame->no, frameNo)) path = &frame->value; + else { + t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); + if (frame->interpolator) t = frame->interpolator->progress(t); + if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value; + else interpolate = true; } - _copy(value, cmds); - _copy(value, pts, transform); - return true; } - if (frames->count == 1 || frameNo <= frames->first().no) { - if (roundness > ROUNDNESS_EPSILON) { - _handleCorners(frames->first().value, cmds, pts, transform, roundness); - return true; - } - _copy(frames->first().value, cmds); - _copy(frames->first().value, pts, transform); - return true; - } - - if (frameNo >= frames->last().no) { - if (roundness > ROUNDNESS_EPSILON) { - _handleCorners(frames->last().value, cmds, pts, transform, roundness); - return true; - } - _copy(frames->last().value, cmds); - _copy(frames->last().value, pts, transform); - return true; - } - - auto frame = frames->data + _bsearch(frames, frameNo); - - if (mathEqual(frame->no, frameNo)) { - if (roundness > ROUNDNESS_EPSILON) { - _handleCorners(frame->value, cmds, pts, transform, roundness); - return true; - } - _copy(frame->value, cmds); - _copy(frame->value, pts, transform); - return true; - } - - //interpolate - auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); - if (frame->interpolator) t = frame->interpolator->progress(t); - - if (frame->hold) { - if (roundness > ROUNDNESS_EPSILON) { - _handleCorners((frame + ((t < 1.0f) ? 0 : 1))->value, cmds, pts, transform, roundness); - return true; - } - _copy((frame + ((t < 1.0f) ? 0 : 1))->value, cmds); - _copy((frame)->value, pts, transform); + if (!interpolate) { + if (roundness > ROUNDNESS_EPSILON) return _modifier(path->pts, path->ptsCnt, path->cmds, path->cmdsCnt, cmds, pts, transform, roundness); + _copy(path, cmds); + _copy(path, pts, transform); return true; } @@ -591,27 +562,23 @@ struct LottiePathSet : LottieProperty auto e = (frame + 1)->value.pts; if (roundness > ROUNDNESS_EPSILON) { - PathSet interpPathSet = frame->value; - interpPathSet.pts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point)); - - auto p = interpPathSet.pts; + auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point)); + auto p = interpPts; for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) { + *p = mathLerp(*s, *e, t); + if (transform) mathMultiply(p, transform); + } + _modifier(interpPts, frame->value.ptsCnt, frame->value.cmds, frame->value.cmdsCnt, cmds, pts, nullptr, roundness); + free(interpPts); + return true; + } else { + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { auto pt = mathLerp(*s, *e, t); if (transform) mathMultiply(&pt, transform); - *p = pt; + pts.push(pt); } - _handleCorners(interpPathSet, cmds, pts, nullptr, roundness); - free(interpPathSet.pts); - return true; + _copy(&frame->value, cmds); } - - for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { - auto pt = mathLerp(*s, *e, t); - if (transform) mathMultiply(&pt, transform); - pts.push(pt); - } - _copy(frame->value, cmds); - return true; }