diff --git a/src/common/tvgMath.h b/src/common/tvgMath.h index a5ef8fdd..ae702813 100644 --- a/src/common/tvgMath.h +++ b/src/common/tvgMath.h @@ -62,6 +62,12 @@ static inline bool mathZero(float a) } +static inline bool mathZero(const Point& p) +{ + return mathZero(p.x) && mathZero(p.y); +} + + static inline bool mathEqual(float a, float b) { return (fabsf(a - b) < FLT_EPSILON); diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 6694ecc8..433209f5 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -520,17 +520,13 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo if (ctx->repeater) { auto p = Shape::gen(); - path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, exps); + path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, ctx->roundness, exps); _repeat(parent, std::move(p), ctx); } else { auto merging = _draw(parent, ctx); - if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, exps)) { + if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, ctx->roundness, exps)) { P(merging)->update(RenderUpdateFlag::Path); } - if (ctx->roundness > 1.0f && P(merging)->rs.stroke) { - TVGERR("LOTTIE", "FIXME: Path roundesss should be applied properly!"); - P(merging)->rs.stroke->join = StrokeJoin::Round; - } } } @@ -587,7 +583,7 @@ static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) { auto group = static_cast(*g); for (auto p = group->children.begin(); p < group->children.end(); ++p) { - if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) { + if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps)) { P(shape)->update(RenderUpdateFlag::Path); } } @@ -1095,7 +1091,7 @@ static void _updateMaskings(LottieLayer* layer, float frameNo, LottieExpressions auto shape = Shape::gen().release(); shape->fill(255, 255, 255, mask->opacity(frameNo)); shape->transform(layer->cache.matrix); - if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) { + if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps)) { P(shape)->update(RenderUpdateFlag::Path); } auto method = mask->method; diff --git a/src/loaders/lottie/tvgLottieExpressions.h b/src/loaders/lottie/tvgLottieExpressions.h index 8dd6ca27..a88d7716 100644 --- a/src/loaders/lottie/tvgLottieExpressions.h +++ b/src/loaders/lottie/tvgLottieExpressions.h @@ -109,12 +109,12 @@ public: } template - bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpression* exp) + bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness, LottieExpression* exp) { auto bm_rt = evaluate(frameNo, exp); if (auto pathset = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { - (*pathset)(frameNo, cmds, pts, transform); + (*pathset)(frameNo, cmds, pts, transform, roundness); } else { TVGERR("LOTTIE", "Failed dispatching PathSet!"); return false; diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 9904966e..18af30ff 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -239,6 +239,79 @@ static void _copy(PathSet& pathset, Array& outCmds) } +static void _roundCorner(Array& cmds, Array& pts, const Point& prev, const Point& curr, const Point& next, float roundness) +{ + auto lenPrev = mathLength(prev - curr); + auto rPrev = lenPrev > 0.0f ? 0.5f * mathMin(lenPrev * 0.5f, roundness) / lenPrev : 0.0f; + auto lenNext = mathLength(next - curr); + auto rNext = lenNext > 0.0f ? 0.5f * mathMin(lenNext * 0.5f, roundness) / lenNext : 0.0f; + + auto dPrev = rPrev * (curr - prev); + auto dNext = rNext * (curr - next); + + pts.push(curr - 2.0f * dPrev); + pts.push(curr - dPrev); + pts.push(curr - dNext); + pts.push(curr - 2.0f * dNext); + cmds.push(PathCommand::LineTo); + cmds.push(PathCommand::CubicTo); +} + + +static void _handleCorners(const PathSet& path, Array& cmds, Array& pts, Matrix* transform, float roundness) +{ + cmds.reserve(path.cmdsCnt * 2); + pts.reserve((uint16_t)(path.ptsCnt * 1.5)); + auto ptsCnt = pts.count; + + auto startIndex = 0; + for (auto iCmds = 0, iPts = 0; iCmds < path.cmdsCnt; ++iCmds) { + switch (path.cmds[iCmds]) { + case PathCommand::MoveTo: { + startIndex = pts.count; + cmds.push(PathCommand::MoveTo); + pts.push(path.pts[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); + iPts += 3; + break; + } else if (path.cmds[iCmds + 1] == PathCommand::Close) { + _roundCorner(cmds, pts, prev, curr, path.pts[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++]); + break; + } + case PathCommand::Close: { + cmds.push(PathCommand::Close); + break; + } + default: break; + } + } + if (transform) { + for (auto i = ptsCnt; i < pts.count; ++i) + mathTransform(transform, &pts[i]); + } +} + + template uint32_t _bsearch(T* frames, float frameNo) { @@ -456,21 +529,33 @@ struct LottiePathSet : LottieProperty return (*frames)[frames->count]; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform) + 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; + } _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; @@ -479,42 +564,65 @@ struct LottiePathSet : LottieProperty 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 - _copy(frame->value, cmds); - auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); if (frame->interpolator) t = frame->interpolator->progress(t); if (frame->hold) { - if (t < 1.0f) _copy(frame->value, pts, transform); - else _copy((frame + 1)->value, pts, transform); + 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); return true; } auto s = frame->value.pts; 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; + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) { + auto pt = mathLerp(*s, *e, t); + if (transform) mathMultiply(&pt, transform); + *p = pt; + } + _handleCorners(interpPathSet, cmds, pts, nullptr, roundness); + free(interpPathSet.pts); + return true; + } + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { auto pt = mathLerp(*s, *e, t); - if (transform) mathMultiply(&pt,transform); + if (transform) mathMultiply(&pt, transform); pts.push(pt); } + _copy(frame->value, cmds); + return true; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpressions* exps) + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness, LottieExpressions* exps) { if (exps && (exp && exp->enabled)) { if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); - if (exps->result(frameNo, cmds, pts, transform, exp)) return true; + if (exps->result(frameNo, cmds, pts, transform, roundness, exp)) return true; } - return operator()(frameNo, cmds, pts, transform); + return operator()(frameNo, cmds, pts, transform, roundness); } void prepare() {}