🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Linear interpolation of two vector arrays with different lenghts

Started by
54 comments, last by ManitouVR 4 years, 8 months ago

Okay, i integrated and tested the demo...

It works but the replay is just linear. So you are as usual right, haha.

Youtube Demo

3D Artist

Advertisement

Can't access the video (it's private) - it'd be interesting to see what you're talking about though.

In any case, if you're still looking for a solution, I'd recommend at least giving my previous suggestion a try. I can't guarantee it's what you're looking for, but it shouldn't take too long to implement it and try it out.

@Zakwayda Oh sorry. I made the video now to "not listed". Should work now.

Yes, iam working already on the segment splitting part.

3D Artist

Thanks for posting the video.

Needless to say, that's a bit more complicated case than your original example :)

Maybe you already said this, but are you doing the path smoothing yourself? Or are you using third-party code for that and trying to compute the new deltas as a post-processing step? I ask because if you're doing the smoothing yourself, then I'd think computing the new deltas could be rolled into that process, but without knowing how the smoothing algorithm works I couldn't say exactly how to do that.

There are a couple potential issues with my suggestion with respect to your new example:

- With that many points you may run into performance problems with a brute-force approach (which my suggestion is, essentially).

- You could run into problems where the path intersects itself due to points from the smoothed curve associating themselves with the wrong segments from the original curve.

In any case, I think it'd be useful to know at this point whether you're doing the smoothing yourself or not.

@Zakwayda Haha, yes, its a bit more complicated in the video. The background story is that i need a drawing software that can replay what was drawn.

 
The smoothing is done in 3 steps:
1. Curve reduction with RamerDouglasPeucker algorithm
2. Smoothing with Chaikin (corner cutting)
3. Evenly distribute along curve - Uniform Linear Interpolation
 
I hacked those functions together with what i found on the internet.
Iam very sure that they are far from optimal. (Have to move vertex data sometimes several times so they match the functions input)
But i have the code. Yes. Although i dont fully understand it.
 

3D Artist

Oh, one more thing. The long pause that u saw in the video was me. Not grasping that the window was out of focus. That was not calculation time, lol.

3D Artist

It seems likely that the optimal solution would involve computing the new time values as part of the smoothing operation. It may not necessarily be the case, but I wouldn't be surprised if all the information you needed was available during the course of those operations.

For example, according to Wikipedia at least, the points returned by Ramer–Douglas–Peucker are a subset of the input points. So for that algorithm at least, the problem is trivial - just retain the time values for the points that survive the process.

Chaikin appears to involve interpolating between points of the input curve, in which case time values can be interpolated as well. Similarly for your third step.

If those three steps are easily separable, you could tackle the problem one step at a time. First get your Ramer–Douglas–Peucker implementation to preserve the time values, then try to get Chaikin to interpolate those values along with the positions, and then similarly with the last step. Test each step as you go to see how it's working.

Note that throughout this discussion I'm assuming absolute time values, not deltas. If you start with deltas or need deltas at the end, you can convert back and forth to and from absolute values as needed.

Although I only took a quick look at the algorithms you're using, I do wonder a little how robust they themselves are in the face of arbitrary paths (e.g. self-intersecting). For example, based on Wikipedia's description at least, it seems like Ramer–Douglas–Peucker could fail if the first and last points in the path are coincident. I looked around online a little and didn't find any discussion of this, so I may be missing something. In any case, you might be unlikely to encounter failure cases in practice, but it's something to keep in mind.

Similarly, my proposed post-processing solution wouldn't be reliable in the face of arbitrary paths - it could fail if the path intersects or even passes very close to itself. I'll post back if I have any other ideas though.

I started to rewrite the Ramer–Douglas–Peucker function so that i can feed it with an sf::VectorArray to get rid of some converting overhead AND to feed in the actual struct i have that also holds the time values.

struct Pencil 
{
	sf::VertexArray vertices;
	std::vector<int> pressure;
	std::vector<sf::Int32> timeAbslt;
	std::vector<sf::Int32> timeDelta;
};

 

The current RDP-function expects an std::pair which holds the xy coordinates.
I would also like that the function returns the processed sf::VectorArray instead of referencing the result back as an output.
I was able to reduce the RDP-function already down to one function only. Before it was two.
Now i ran into a "problem". The RDP-function uses two recursive calls and i cant figure out how to unwind them into a standard loop.
Recursive calls are totally new to me to be honest...
 

#include <cmath>	
#include <utility>	
#include <stdexcept>
  

typedef std::pair<double, double> RDPpoint;

void RamerDouglasPeucker(const std::vector<RDPpoint> &PointListIN, double epsilon, std::vector<RDPpoint> &PointListOUT)
{
	if (PointListIN.size() < 2) // if not enough points
	{
		PointListOUT.clear();
		PointListOUT.shrink_to_fit();
		PointListOUT = PointListIN;
		return;
	}

	// Find the point with the maximum distance from line between start and end
	double dmax = 0.0;
	size_t index = 0;
	size_t end = PointListIN.size() - 1;

	for (size_t i = 1; i < end; i++)
	{
		// ********************************************************
		// Calculate PerpendicularDistance
		// ********************************************************

		double dx = PointListIN[end].first  - PointListIN[0].first;
		double dy = PointListIN[end].second - PointListIN[0].second;

		//Normalise
		double mag = pow(pow(dx, 2.0) + pow(dy, 2.0), 0.5);
		if (mag > 0.0) { dx /= mag; dy /= mag; }

		double pvx = PointListIN[i].first  - PointListIN[0].first;
		double pvy = PointListIN[i].second - PointListIN[0].second;

		//Get dot product (project pv onto normalized direction)
		double pvdot = dx * pvx + dy * pvy;

		//Scale line direction vector
		double dsx = pvdot * dx;
		double dsy = pvdot * dy;

		//Subtract this from pv
		double ax = pvx - dsx;
		double ay = pvy - dsy;
		double PerpDist = pow(pow(ax, 2.0) + pow(ay, 2.0), 0.5);

		// ********************************************************
		
		if (PerpDist > dmax)
		{
			index = i;
			dmax = PerpDist;
		}
	}

	// If the maximum distance is greater than epsilon, recursively simplify
	if (dmax > epsilon)
	{
		// Recursive call
		std::vector<RDPpoint> recResults1;
		std::vector<RDPpoint> recResults2;
		std::vector<RDPpoint> firstLine(PointListIN.begin(), PointListIN.begin() + index + 1);
		std::vector<RDPpoint> lastLine(PointListIN.begin() + index, PointListIN.end());

		RamerDouglasPeucker(firstLine, epsilon, recResults1);
		RamerDouglasPeucker(lastLine, epsilon, recResults2);

		// Build the result list
		PointListOUT.assign(recResults1.begin(), recResults1.end() - 1);
		PointListOUT.insert(PointListOUT.end(), recResults2.begin(), recResults2.end());
		if (PointListOUT.size() < 2)
			throw std::runtime_error("Problem assembling output");
	}
	else
	{
		//Just return start and end points
		PointListOUT.clear();
		PointListOUT.push_back(PointListIN[0]);
		PointListOUT.push_back(PointListIN[end]);
	}
}

 

3D Artist

In case performance ever becomes an issue for you, I'll mention that I see some places where the RamerDouglasPeucker() function may be doing considerably more work than is needed. So, there may be some opportunities for optimization there.

As for revising the code to meet your needs, here are some suggestions.

Recursive algorithms can generally be expressed iteratively (in a loop or loops, as you suggest), but you really don't need to dig into that if you don't want to. There are other ways to address the issue.

Similarly, you could fairly easily revise the code to return an array rather than operating on an input array, as you mention, but you could also avoid that if you're not that comfortable with the code. You could just give up on that one (as it's ancillary to your problem), or you could write a wrapper function that creates an array locally, passes it to the function that does the actual work, and then returns the local array. That way, from the outside a new array would be returned, but internally it would be using the code you have currently with no modification.

Anyway, on to the real issue. If you're not too comfortable with the code, you might want to modify it as little as possible in order to avoid introducing bugs or errors. With that in mind, if you've already made significant changes, you might want to go back to the original code and start over.

The reason I suggest this is that getting the original time values back from the code should require minimal changes - probably fewer than you've made already. The code expects a type, RDPpoint, that includes fields 'first' and 'second' (the vector x and y value). There's no reason I can see that RDPpoint can't instead be a struct/class of your own creation that has 'first' and 'second' fields of type double, and also an 'absolute time' field.

Given that change, your original code would work exactly as before, but every time it copied one of these values, it would copy the time value as well. (I only see copying/moving of these values in the code, but if there are other things involved, like creating new std::pair instances, you should easily be able to adapt those parts.)

In summary, my suggestion is to use the original code, and replace RDPpoint with a struct/class of your own that stores the time values along with the coordinates so that the time values are included in the output.

@Zakwayda  The RDP code i posted earlier is the working almost original one. Thank you for your very nice tips always!! Much appreciated.

I didnt post my modified code earlier. The problem is that the recursive RDP doesent return the result but has the output in the function variables because it needs that to call itself. Was also looking online half the day but couldnt really find an iterative RDB version anywhere. Just some pseudo code. Good thing is that i start to understand the basic idea behind the algorithm.

However.  This is how far iam at the moment. The code throws an Error. Still investigating...


void RDPreduce(sf::VertexArray &PointListIN, double epsilon, sf::VertexArray &PointListOUT)
{
	if (PointListIN.getVertexCount() < 2) { PointListOUT = PointListIN; return; } // if not enough points

	// Find the point with the maximum distance from line between start and end
	double dmax = 0.0;
	size_t index = 0;
	size_t end = PointListIN.getVertexCount() ;

	for (size_t i = 1; i < end; i++)
	{
		// ********************************************************
		// Calculate PerpendicularDistance
		// ********************************************************

		double dx = PointListIN[end].position.x - PointListIN[0].position.x;
		double dy = PointListIN[end].position.y - PointListIN[0].position.y;

		//Normalise
		double mag = pow(pow(dx, 2.0) + pow(dy, 2.0), 0.5);
		if (mag > 0.0) { dx /= mag; dy /= mag; }

		double pvx = PointListIN[i].position.x - PointListIN[0].position.x;
		double pvy = PointListIN[i].position.y - PointListIN[0].position.y;

		//Get dot product (project pv onto normalized direction)
		double pvdot = dx * pvx + dy * pvy;

		//Scale line direction vector
		double dsx = pvdot * dx;
		double dsy = pvdot * dy;

		//Subtract this from pv
		double ax = pvx - dsx;
		double ay = pvy - dsy;
		double PerpDist = pow(pow(ax, 2.0) + pow(ay, 2.0), 0.5);

		// ********************************************************

		if (PerpDist > dmax) { index = i; dmax = PerpDist; }
	}

	// If the maximum distance is greater than epsilon, recursively simplify
	if (dmax > epsilon)
	{
		// Recursive call
		sf::VertexArray recResults1, recResults2;
		sf::VertexArray firstLine, lastLine;

		firstLine[0].position = (sf::Vector2f(PointListIN[0].position.x, PointListIN[index +1].position.y));  // <----- Error here exeption
		lastLine[0].position  = (sf::Vector2f(PointListIN[index].position.x, PointListIN[end].position.y));

		//std::vector<RDPpoint> recResults1;
		//std::vector<RDPpoint> recResults2;
		//std::vector<RDPpoint> firstLine(PointListIN.begin(), PointListIN.begin() + index + 1);
		//std::vector<RDPpoint> lastLine(PointListIN.begin() + index, PointListIN.end());

		RDPreduce(firstLine, epsilon, recResults1);
		RDPreduce(lastLine, epsilon, recResults2);

		// Build the result list

		PointListOUT.append(sf::Vertex(sf::Vector2f(recResults1[0].position.x, recResults1[end - 1].position.y), sf::Color::Black));

		//PointListOUT.assign(recResults1.begin(), recResults1.end() - 1);
		//PointListOUT.insert(PointListOUT.end(), recResults2.begin(), recResults2.end());  // <---- Dont know how to INSERT. sf::VertexArray has no insert()
		//if (PointListOUT.size() < 2)
		//	throw std::runtime_error("Problem assembling output");
	}

 

3D Artist

This topic is closed to new replies.

Advertisement