blob: 7d41be92cccc55222fbce4356c11de530ee712c2 [file] [log] [blame]
#include "rive/shapes/paint/trim_path.hpp"
#include "rive/shapes/paint/stroke.hpp"
#include "rive/factory.hpp"
using namespace rive;
StatusCode TrimPath::onAddedClean(CoreContext* context)
{
if (!parent()->is<Stroke>())
{
return StatusCode::InvalidObject;
}
parent()->as<Stroke>()->addStrokeEffect(this);
return StatusCode::Ok;
}
void TrimPath::trimRawPath(const RawPath& source)
{
m_rawPath.rewind();
auto renderOffset = std::fmod(std::fmod(offset(), 1.0f) + 1.0f, 1.0f);
// Build up contours if they're empty.
if (m_contours.empty())
{
ContourMeasureIter iter(&source);
while (auto meas = iter.next())
{
m_contours.push_back(meas);
}
}
switch (mode())
{
case TrimPathMode::sequential:
{
float totalLength = 0.0f;
for (auto contour : m_contours)
{
totalLength += contour->length();
}
auto startLength = totalLength * (start() + renderOffset);
auto endLength = totalLength * (end() + renderOffset);
if (endLength < startLength)
{
float swap = startLength;
startLength = endLength;
endLength = swap;
}
if (startLength > totalLength)
{
startLength -= totalLength;
endLength -= totalLength;
}
int i = 0, subPathCount = (int)m_contours.size();
while (endLength > 0)
{
auto contour = m_contours[i % subPathCount];
auto contourLength = contour->length();
if (startLength < contourLength)
{
contour->getSegment(startLength,
endLength,
&m_rawPath,
true);
endLength -= contourLength;
startLength = 0;
}
else
{
startLength -= contourLength;
endLength -= contourLength;
}
i++;
}
}
break;
case TrimPathMode::synchronized:
{
for (auto contour : m_contours)
{
auto contourLength = contour->length();
auto startLength = contourLength * (start() + renderOffset);
auto endLength = contourLength * (end() + renderOffset);
if (endLength < startLength)
{
auto length = startLength;
startLength = endLength;
endLength = length;
}
if (startLength > contourLength)
{
startLength -= contourLength;
endLength -= contourLength;
}
contour->getSegment(startLength, endLength, &m_rawPath, true);
while (endLength > contourLength)
{
startLength = 0;
endLength -= contourLength;
contour->getSegment(startLength,
endLength,
&m_rawPath,
true);
}
}
}
break;
default:
RIVE_UNREACHABLE();
}
}
RenderPath* TrimPath::effectPath(const RawPath& source, Factory* factory)
{
if (m_renderPath != nullptr)
{
// Previous result hasn't been invalidated, it's still good.
return m_renderPath;
}
trimRawPath(source);
if (!m_trimmedPath)
{
m_trimmedPath = factory->makeEmptyRenderPath();
}
else
{
m_trimmedPath->rewind();
}
m_renderPath = m_trimmedPath.get();
m_rawPath.addTo(m_renderPath);
return m_renderPath;
}
void TrimPath::invalidateEffect()
{
invalidateTrim();
// This is usually sent when the path is changed so we need to also
// invalidate the contours, not just the trim effect.
m_contours.clear();
}
void TrimPath::invalidateTrim()
{
m_renderPath = nullptr;
if (parent() != nullptr)
{
auto stroke = parent()->as<Stroke>();
stroke->parent()->addDirt(ComponentDirt::Paint);
stroke->invalidateRendering();
}
}
void TrimPath::startChanged() { invalidateTrim(); }
void TrimPath::endChanged() { invalidateTrim(); }
void TrimPath::offsetChanged() { invalidateTrim(); }
void TrimPath::modeValueChanged() { invalidateTrim(); }
StatusCode TrimPath::onAddedDirty(CoreContext* context)
{
auto code = Super::onAddedDirty(context);
if (code != StatusCode::Ok)
{
return code;
}
switch (mode())
{
case TrimPathMode::sequential:
case TrimPathMode::synchronized:
return StatusCode::Ok;
}
return StatusCode::InvalidObject;
}