Jump to content

Moving along a spline


photo

Recommended Posts

Hi,

I'm trying to move a node along a spline segment, with a specific velocity. What is the correct way to do this? Any code sample?

Thanks,

 

Link to comment

Hi Stephane,

I would highly recommend using bezier curves for your solution, as they have various benefits over other other ones, like solely definition via matrices, calculation of tangent/normals e.g.. So that said, I can highly recommend A primer on Bèzier Curves, that site teaches you everything from setting up those splines, move objects along this one, calculating tangents (for proper directions) and normals (easier offset, because "parallel" splines are nearly impossible to generate).

 

What you basically need to understand is, that movement will be done between lerping from 0 (start) to 1.0 (end) while using the t-parameter in the curve functions. This t-value is basically your iFPS and can be multiplied with any velocity as well. The only "tricky" part is, that for different spline lengths, your objects will move faster/slower because of the 0.0-1.0-lerping. But this can be done with an combination of the spline-length and an LUT for the t-value.

 

For some kind of "pseudo-code" I can recommend this topic. I also had implemented a more or less complex traffic system a couple of years ago for one of the UNIGINE customers. So in case you need help regarding that, just ping me and I am more than happy to help you with it as well.

 

Best regards

Christian

 

Link to comment

Thanks Christian, indeed I could rewrite a Bezier (or another Spline) implementation, but here I'm trying to specifically use the WorldSplineGraph and the SplineSegment.

To be more precise, I have used this code to simulate a constant velocity and measure the distance between each step:

float linearPosition = 0; // linear position (normalized to [0..1]) on the spline segment
float dp = 1; // velocity, aka distance travelled between each step
float dl = dp / segment->getLength(); // normalized velocity for this segment
Vec3 pos0, pos1;

while (linearPosition<1) {

	float t = segment->linearToParametric(linearPosition); // from linear position to spline 'time'
	Vec3 pos1 = segment->calcPoint(t); // point on spline
  	
	if (linearPosition>0) 
		Log::message("%f\n", (pos1 - pos0).length() );
 	
	pos0 = pos1;
 	
	linearPosition += dl;
}

Here, I should be moving consistently 1m (so-so) between each step, but depending on the spline segment, the log prints from 0.8 to 1.2, and sometimes an impressive 1.00; but the inconsistency is catastrophic in my case, yet the spline segment is quite usual and much - much - longer than the step.

So, am I doing something wrong?

Link to comment

Hi Stephane,

 

your code actually is looking good, I modified it a little bit with a moving object:
 

Spoiler


.h-file
Unigine::ObjectPtr splineObject;

float linearPosition;
float dp;
float dl;
Unigine::Math::Vec3 pos0, pos1;

Unigine::Vector<Unigine::SplineSegmentPtr> splineSegements;
Unigine::SplineSegmentPtr curSplineSegment;


/.cpp
  
int AppWorldLogic::init()
{
	// Write here code to be called on world initialization: initialize resources for your world scene during the world start.
	Unigine::NodePtr splineNode = Unigine::World::getNodeByType(Unigine::Node::TYPE::WORLD_SPLINE_GRAPH);

	if (splineNode != nullptr)
	{
		Unigine::WorldSplineGraphPtr splineGraph = Unigine::dynamic_ptr_cast<Unigine::WorldSplineGraph>(splineNode);
		splineGraph->getSplineSegments(splineSegements);

		curSplineSegment = splineSegements[0];
		dp = .030f;
		dl = dp / curSplineSegment->getLength();
	}


	//create a simple box object
	splineObject = Unigine::Primitives::createBox(Unigine::Math::vec3(0.5f));

	return 1;
}

////////////////////////////////////////////////////////////////////////////////
// start of the main loop
////////////////////////////////////////////////////////////////////////////////

int AppWorldLogic::update()
{
	if (curSplineSegment != nullptr)
	{
		float t = curSplineSegment->linearToParametric(linearPosition); // from linear position to spline 'time'

		if (t >= 1.0f)
		{
			t -= 1.0f;
			linearPosition = 0.f;
		}


		pos1 = curSplineSegment->calcPoint(t); // point on spline

		if (linearPosition > 0)
			Unigine::Log::message("%f\n", (pos1 - pos0).length());

		pos0 = pos1;
		splineObject->setWorldPosition(pos0);

		linearPosition += dl;

	}

	// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.
	return 1;
}


So the created box is moving continiously along the spline. I guess your issue is more in understanding the linearToParametric-function. This function tries to calculate (I guess it approximates) the nearest t-value for the given length between 0 and 1. So if you have an curved line, the function tries to travel along the curve in small steps as long as it reaches your l-value and returns t. But your log will draw a direct line between the two points, that is even an further approximation of the true spline. See the following picture:

 

image.png.b1c211cd1ad1e4e072c0bb5c1c82aa3a.png

 

When your linearPosition is 1.0, the function will also return 1, so your pos0 is the endPosition of your spline, pos1 is your startPoint. But your logged output will be the direct line which is, of course, shorter than the overall spline-length. The linearToParametric-function will be more accurate if your spline is not an curve but an straight line, however because of its approximation nature, you will never get the correct result (your dl-value). Unfortunately, this is an expected behavior in using splines. That is why I mentioned a look up table (LUT) that tries to minimize the problem. Increase the LUT-size will result in an more precise calculation, while using more memory as a trade-off.

You can further read in this presentation for more details, slide 75 tries to deal with your problem.

Hope that helps!

Christian

 

 

 

  • Thanks 1
Link to comment

Hello,

The linearToParametric function itself performs an iterative pass with some internal step, which introduces some variability.

For uniform movement between spline points, it's necessary to calculate intermediate points and then interpolate between them.

A simple example can be found in cpp_samples/basics/trajectory_movement, but it uses Catmull-Rom spline for movement.

For the WSG, the general approach will be the same - calculating as many intermediate points as possible. Approximately something like this:

	float step = 1.0f / ((segment->getLength() / velocity) * 1500); // 150 * 10 like max fps and 10 iteration for each frame
	float dist = Game::getIFps() * velocity.get() + diff;
	float cdist = 0;

	Vec3 pos = node->getWorldPosition();
	Vec3 new_pos = pos;
	Vec3 old_pos = pos;
	while (cdist < dist)
	{
		old_pos = new_pos;

		ctime += step;
		if (ctime >= 1)
		{
			cindex++;
			if (cindex >= segments.size())
				cindex = 0;

			ctime -= 1;
			segment = segments[cindex];
		}

		new_pos = segment->calcPoint(ctime);

		cdist += length(old_pos - pos);
	}

	vec3 tangent = segment->calcTangent(ctime);
	vec3 up = segment->calcUpVector(ctime);

	node->setWorldTransform(setTo(new_pos , new_pos  + Vec3(tangent), up, AXIS_Y));

 

  • Thanks 1
Link to comment
×
×
  • Create New...