lottie: keep the code clean and neat.

- Separate the rounded corner logic from the updateStar
  since its logic has become too lengthy and complicated.
- Revise the path generation to eliminate duplicates.
This commit is contained in:
Hermet Park 2024-05-16 14:40:03 +09:00
parent 47e1d48b28
commit bcc8dd4f09
2 changed files with 133 additions and 159 deletions

View file

@ -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);
}
}

View file

@ -210,30 +210,30 @@ struct LottieExpression
};
static void _copy(PathSet& pathset, Array<Point>& outPts, Matrix* transform)
static void _copy(PathSet* pathset, Array<Point>& outPts, Matrix* transform)
{
Array<Point> 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<PathCommand>& outCmds)
static void _copy(PathSet* pathset, Array<PathCommand>& outCmds)
{
Array<PathCommand> 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<PathCommand>& cmds, Array<Point>& pts, const Poin
}
static void _handleCorners(const PathSet& path, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
static bool _modifier(Point* inputPts, uint32_t inputPtsCnt, PathCommand* inputCmds, uint32_t inputCmdsCnt, Array<PathCommand>& cmds, Array<Point>& 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<PathCommand>& 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<LottieGenericProperty<T>>(frameNo, out, exp)) return out;
}
@ -531,59 +532,29 @@ struct LottiePathSet : LottieProperty
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
{
if (!frames) {
if (roundness > ROUNDNESS_EPSILON) {
_handleCorners(value, cmds, pts, transform, roundness);
return true;
PathSet* path = nullptr;
LottieScalarFrame<PathSet>* 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;
}