Closed Jorundur closed 1 year ago
Optimizing Flubber to work with the Skia API a bit deeper might be easier than expected. the interpolate function has a string
boolean option that returns the points instead of the SVG string. So instead of using a Path (with SVG serialization/parsing/creation = SLOW), you can use <Vertices />
directly which what Flubber used behind the scene: https://shopify.github.io/react-native-skia/docs/shapes/vertices
fun no?
I'll keep this issue open for now as we might provide an example in the near future on how to achieve this.
If you are interested of what flubber does behing the scene, this article is a create intro: https://css-tricks.com/rendering-svg-paths-in-webgl/ And this is the function they use to make sure that the meshes can be interpolated: https://github.com/veltman/flubber/blob/master/src/interpolate.js#L40
Did you try to generate the path at t=0
and the path at t=1
as SkPath
and then use path.interpolate
on it. That may work?
Did you try to generate the path at t=0 and the path at t=1 as SkPath and then use path.interpolate on it. That may work?
Yeah, I have something like (I think this is what you're referring to)
const start = graphData[graphState.current.current].lineGraph;
const end = graphData[graphState.current.next].lineGraph;
return end.interpolate(start, graphTransition.current) ?? Skia.Path.Make();
and it's not working. I can also see when I check with isInterpolatable
that it returns false.
This is probably because the two lineGraphs (both SkPath
) are of varying length.
Thanks for the other suggestions as well. I'm not sure how I'll change my code from the <Path>
s to <Vertices>
but I can try to work it out.
That's quite interesting. This is because:
return t => {
if (t < 1e-4 && typeof fromShape === "string") {
return fromShape;
}
if (1 - t < 1e-4 && typeof toShape === "string") {
return toShape;
}
return interpolator(t);
};
So
const leftInterpolator0 = Skia.Path.MakeFromSVGString(leftInterpolator(0.01))!;
const leftInterpolator1 = Skia.Path.MakeFromSVGString(leftInterpolator(0.99))!;
console.log(leftInterpolator0.isInterpolatable(leftInterpolator1)); // Returns true
And
const leftInterpolator0 = Skia.Path.MakeFromSVGString(leftInterpolator(0))!;
const leftInterpolator1 = Skia.Path.MakeFromSVGString(leftInterpolator(1))!;
console.log(leftInterpolator0.isInterpolatable(leftInterpolator1)); // Returns false
Which makes a lot of sense. At 0 and 1 you want to display the original path, not the triangulated one. Can you infer the proper function you need to build based on this information?
this function seems to work like a charm:
const Flubber2SkiaInterpolator = (interpolator: (t: number) => string) => {
const d = 1e-3;
const i0 = Skia.Path.MakeFromSVGString(interpolator(0))!;
const i01 = Skia.Path.MakeFromSVGString(interpolator(d))!;
const i1 = Skia.Path.MakeFromSVGString(interpolator(1))!;
const i11 = Skia.Path.MakeFromSVGString(interpolator(1 - d))!;
console.log(i01.isInterpolatable(i11));
return (t: number) => {
if (t < d) {
return i0;
}
if (1 - t < d) {
return i1;
}
return i11.interpolate(i01, t)!;
};
};
const leftInterpolator = Flubber2SkiaInterpolator(
interpolate(
"M 8 125 C 3.5 123 0.4 118.6 0 113.6 V 12.7 C 0.2 10.3 1 7.9 2.4 5.9 C 3.9 3.9 5.8 2.3 8 1.3 C 9.8 0.4 11.8 0 13.7 0 C 14.2 0 14.7 0 15.1 0.1 C 17.6 0.3 19.9 1.2 21.9 2.7 L 50 22 V 104.3 L 21.9 123.6 C 20.3 124.8 18.5 125.6 16.6 126 H 10.9 C 9.9 125.8 8.9 125.5 8 125 Z",
"M 16.7 0 C 12.2 0 8 1.8 4.9 4.9 C 1.8 8 0 12.2 0 16.7 V 105.6 C 0 110 1.8 114.2 4.9 117.3 C 8 120.5 12.2 122.2 16.7 122.2 C 21.1 122.2 25.3 120.5 28.5 117.3 C 31.6 114.2 33.3 110 33.3 105.6 V 16.7 C 33.3 12.2 31.6 8 28.5 4.9 C 25.3 1.8 21.1 0 16.7 0 Z"
)
);
const left = useComputedValue(() => {
const pathLeft = leftInterpolator(progress.current);
return pathLeft;
}, [progress]);
This is like super fun no?
I'm closing the issue for now but feel free to reopen if needed.
@wcandillon
This is very interesting and fun indeed! Thanks for taking the time to write this.
So the way I understand this is that with Flubber, even though the start and end paths are of different sizes, all the intermediate paths (during the transition) are of the same size and that's the reason why we can use Skia to interpolate the intermediate paths.
I did manage to run this in my project but unfortunately the Flubber algorithm doesn't work nicely with my use case (animating line graphs) - see https://github.com/veltman/flubber/issues/99. In my case Flubber added a connection between the start and end point of the line graph, which looked odd during the transition.
The algorithm provided by Polymorph looks a lot nicer - i.e. it handles polylines better. But I don't think I can use that library in a similar way as you described since it differs from Flubber in that the intermediate paths can also have different sizes. I.e. in the case of Polymorph, i01.isInterpolatable(i11)
would return false
while it's true
with Flubber. At least that's what I think is going on.
In any case, all I wanted was a nice animation when transitioning from one line graph to another and I ended up Easing
the end
value of the line path when switching between graphs which looks nice.
I feel like you're not trying to interpolate heterogeneous shapes but rather charts which have different number of data points. Maybe it would be easier to add the missing datapoints manually? I'm sure there must be some simple algorithm/lib that can help you to do it.
Yeah, that's a good idea. So if line graph A has 10 points and line graph B has 100 points, I could "fake" A during the transition so that it now has 100 points but looks the same way as before and then use Skia to interpolate as normal.
@Jorundur Have you end up doing some tests for adding data points before the interpolation? I'm planning to give it a try and solve the issue this way.
@marcocaldera Unfortunately I haven't done that, I figured out a different way of animating the path change which I was happy with and which didn't require interpolation (by animating the end
prop of the Path
so that it appears smoothly from left to right).
But I'm curious to see your implementation if you were successful in trying this out!
Hi @wcandillon , I have been trying to realize real time data chart with animation but I got stack on this point, I caught an error when I tried interpolate 2 variants of path of chart: prev data state (without last tick) and actual data. I have made paths from svg string. Issue from React Native stack trace is "Could not parse path from string". I would like to ask you for help to resolve this issue or maybe I am on the wrong way and you know better approach, so please share it with me =)
Thanks in advance.
I've been trying to get this to work with the interpolating example in the repo and I'm not sure how to make it work with the recipe. @Jorundur You mentioned you were able to get polymorph-js
to work. Could you let me know if you took an approach like the one below?
@wcandillon You mentioned polymorph-js
in your YT video about drawing bezier curves. Did you ever end up using it for this scenario?
if (!path.isInterpolatable(currentPathRef.current)) {
const pointsToAdd = path.countPoints() - currentPathRef.current.countPoints()
console.warn('Paths must have the same length. Skipping interpolation.', pointsToAdd)
const path1 = new P.Path(animatedPath.current.toSVGString()) // polymorph-js
console.log('path1', path1)
const path2 = new P.Path(path.toSVGString())
console.log('path2', path2)
const newPath = P.interpolate([path1, path2], { // crashes
addPoints: pointsToAdd,
origin: { x: 0, y: 0 },
optimize: 'fill',
precision: 0,
})(0.5)
return
}
currentPathRef.current = animatedPath.current
nextPathRef.current = path
runSpring(
progress,
{ from: 0, to: 1 },
{
mass: 1,
stiffness: 500,
damping: 400,
velocity: 0,
},
)
Description
Current functionality
endPath.interpolate(startPath, transition.current);
if the two paths have the same number of pointsisInterpolatable
to check whether or not we can interpolateDesired functionality
Current workaround
interpolate
function.Can the algorithm that is used in e.g.
Polymorph
or another similar path morphing library be added to the path interpolate function we have in Skia so as to do it natively?