Rollercoaster Track Orientation

Started by
10 comments, last by alvaro 10 years, 10 months ago

I'm making a rollercoaster and I'm having trouble figuring out the tracks up and side vectors from the track tangent. I have a series nodes with roll values and points that make up the track. Now, I want to keep the track upright as much as possible so it doesn't start to roll around corners but at the same time allow for the node roll values to make loops. No matter what I do, I get twists and bends when the loop goes up and starts to bend back around.

This is the code I have now that works okay apart from loops. I'm at a dead end with this, I just can't figure out a way to keep the track upright but still allow for loops. I know it's possible - I just don't know the maths to do it. My requirement is that there can't be any other data the user needs to give - just the node positions and roll values at each node. I'm using bezier curves if that helps.


	m_trackLength = 0.0f;
	const Vector3f up(0.0f, 1.0f, 0.0f);

	size_t segmentIndex = 0;
	size_t segmentPointIndex = 0;

	for (unsigned int i = 0; i < m_points.size(); ++i)
	{
		Point& point = m_points[i];

		Vector3f trackDelta;
		size_t rollIndex;
		size_t nextRollIndex;
		float rollPercentage;

		if (i < m_points.size() - 1)
		{
			trackDelta = m_points[i + 1].position - point.position;

			size_t segmentPointCount = m_segments[segmentIndex].pointCount;

			rollIndex = segmentIndex;
			nextRollIndex = rollIndex + 1;
			rollPercentage = (float)segmentPointIndex / ((float)segmentPointCount - 1);

			++segmentPointIndex;

			if (segmentPointIndex >= (segmentPointCount - 1))
			{
				++segmentIndex;
				segmentPointIndex = 0;
			}
		}
		else
		{
			trackDelta = m_points[1].position - point.position;
			
			rollIndex = m_nodes.size() - 1;
			nextRollIndex = 0;
			rollPercentage = 1.0f;
		}

		m_trackLength += trackDelta.Length();

		point.tangent = trackDelta.Normalise();

		float firstNodeRoll = m_nodes[rollIndex].GetRoll();
		float nextNodeRoll = m_nodes[((rollIndex < (m_nodes.size() - 1)) ? nextRollIndex : 0)].GetRoll();

		float pointRoll = Math::RadiansToDegrees(nextNodeRoll - firstNodeRoll);

		pointRoll -= floor(pointRoll / 360.0f) * 360.0f;

		if (pointRoll >= 270.0f)
		{
			pointRoll -= 360.0f;
		}
		else if (pointRoll <= -270.0f)
		{
			pointRoll += 360.0f;
		}

		pointRoll = Math::DegreesToRadians(pointRoll);

		pointRoll = firstNodeRoll + Util::Interpolate(0.0f, pointRoll, rollPercentage);

		point.normal = up;
		point.normal.Rotate(-pointRoll, point.tangent);
		point.binormal = point.tangent.Cross(point.normal).Normalise();
		point.normal = point.binormal.Cross(point.tangent).Normalise();
	}

And here's a picture of the problem:

CoasterStruct_150613_100151.png

I've heard about "Parallel Transport Frames" and have tried implementing them but all it seems to do is make the track roll around corners - something I don't want to happen. Maybe I'm just doing it wrong?

Advertisement

If 2 consecutive segments have their up vectors pointing in opposite directions (dot product is negative), negate the up vector of the second section? (EDIT: Just before you calculate the binormal).

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

By a segment do you mean between 2 points, not nodes?

Yes (looking at your code, it seems you have segments made up of multiple points). EDIT: You probably need to consider the last point of the previous segment if you are on the first point of a new segment in that case.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Yeah, there's multiple points between nodes, I need to calculate the up vector for each point.

In wireframe just to make it clearer.

CoasterStruct_150613_105406.png

I tried dotting the previous up with the current up and negated it if it's negative - doesn't seem to fix it, I'm probably just not understanding what you mean :(

Do you need to negate the roll angle in that case too?

What you need is to draw the normal/tangent/binormal as an axis set (like 3 differently coloured arrows) at each of the white points, you should be able to work out what is going on then...

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

CoasterStruct_150613_115556.png

This kinda makes it easier to see what's going on but I still have no idea how to fix it.

They are just the up vectors at each point.

Hmm, it looks like an angle wrapping problem to me, although I can't figure out exactly where it's ending up backward. Maybe during the interpolation. But I would recommend using a fun technique, where angles are represented as a 16-bit value, with 65536 being a full revolution so it automatically wraps when it overflows. You can simply read the value as signed 16-bit if you need a -180 to +180 degree value rather than 0-360.

At the very least, it'll save you doing those wrapping if checks, and it will keep things wrapped during interpolation. You can subtract, then read as signed, and it naturally takes the shortest path positive or negative to get from one value to the other, as opposed to radians where you might end up trying to go from -180 to +179 degrees by going all the way around, rather than just one degree backward.

There's really not much to it, but here's my code. Scalar is just a typedef of float or double depending on what system I'm coding for, and s16/u16 are signed short/unsigned short. The ScalarConstant macro either does nothing, or adds an f, like 65536.0f. Oh, and sometimes I make Scalar a fixed-point value, although the multiplies in this particular file wouldn't work right unless using C++ with operator overloading...

http://deku.rydia.net/program/Angle.h

http://deku.rydia.net/program/Angle.c

CoasterStruct_150613_115556.png

This kinda makes it easier to see what's going on but I still have no idea how to fix it.

They are just the up vectors at each point.

Yeah, so the up vector of the segment with the twist points the opposite way to how it should (i.e. the dot product between the 2 is negative). Negate that up vector... which will cause the next one to be negated as well (in the next iteration), and so on. EDIT: Unless it is 90 degrees out, hard to tell without all 3 axes drawn.

If you draw all 3 axes you will be able to tell which axes need negating and/or swapping round.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

Hmm, it looks like an angle wrapping problem to me, although I can't figure out exactly where it's ending up backward. Maybe during the interpolation. But I would recommend using a fun technique, where angles are represented as a 16-bit value, with 65536 being a full revolution so it automatically wraps when it overflows. You can simply read the value as signed 16-bit if you need a -180 to +180 degree value rather than 0-360.

At the very least, it'll save you doing those wrapping if checks, and it will keep things wrapped during interpolation. You can subtract, then read as signed, and it naturally takes the shortest path positive or negative to get from one value to the other, as opposed to radians where you might end up trying to go from -180 to +179 degrees by going all the way around, rather than just one degree backward.

There's really not much to it, but here's my code. Scalar is just a typedef of float or double depending on what system I'm coding for, and s16/u16 are signed short/unsigned short. The ScalarConstant macro either does nothing, or adds an f, like 65536.0f. Oh, and sometimes I make Scalar a fixed-point value, although the multiplies in this particular file wouldn't work right unless using C++ with operator overloading...

http://deku.rydia.net/program/Angle.h

http://deku.rydia.net/program/Angle.c

I don't think it's a wrapping problem because it doesn't matter what combination of roll values the nodes have - it will still twist in that situation no matter what. I'm starting to think there's no simple fix for this and the calculations needed to do this are much more complicated than the basic ones I'm doing.

This topic is closed to new replies.

Advertisement