How to follow object on CatmullRomSplines at constant speed (e.g. train and train carriage)?
- by Simon
I have a CatmullRomSpline, and using the very good example at https://github.com/libgdx/libgdx/wiki/Path-interface-%26-Splines I have my object moving at an even pace over the spline.
Using a simple train and carriage example, I now want to have the carriage follow the train at the same speed as the train (not jolting along as it does with my code below). This leads into my main questions:
How can I make the carriage have the same constant speed as the train and make it non jerky (it has something to do with the derivative I think, I don't understand how that part works)?
Why do I need to divide by the line length to convert to metres per second, and is that correct? It wasn't done in the linked examples?
I have used the example I linked to above, and modified for my specific example:
private void process(CatmullRomSpline catmullRomSpline) {
// Render path with precision of 1000 points
renderPath(catmullRomSpline, 1000);
float length = catmullRomSpline.approxLength(catmullRomSpline.spanCount * 1000);
// Render the "train"
Vector2 trainDerivative = new Vector2();
Vector2 trainLocation = new Vector2();
catmullRomSpline.derivativeAt(trainDerivative, current);
// For some reason need to divide by length to convert from pixel speed to metres per second but I do not
// really understand why I need it, it wasn't done in the examples???????
current += (Gdx.graphics.getDeltaTime() * speed / length) / trainDerivative.len();
catmullRomSpline.valueAt(trainLocation, current);
renderCircleAtLocation(trainLocation);
if (current >= 1) {
current -= 1;
}
// Render the "carriage"
Vector2 carriageLocation = new Vector2();
float carriagePercentageCovered = (((current * length) - 1f) / length); // I would like it to follow at 1 metre behind
carriagePercentageCovered = Math.max(carriagePercentageCovered, 0);
catmullRomSpline.valueAt(carriageLocation, carriagePercentageCovered);
renderCircleAtLocation(carriageLocation);
}
private void renderPath(CatmullRomSpline catmullRomSpline, int k) {
// catMulPoints would normally be cached when initialising, but for sake of example...
Vector2[] catMulPoints = new Vector2[k];
for (int i = 0; i < k; ++i) {
catMulPoints[i] = new Vector2();
catmullRomSpline.valueAt(catMulPoints[i], ((float) i) / ((float) k - 1));
}
SHAPE_RENDERER.begin(ShapeRenderer.ShapeType.Line);
SHAPE_RENDERER.setColor(Color.NAVY);
for (int i = 0; i < k - 1; ++i) {
SHAPE_RENDERER.line((Vector2) catMulPoints[i], (Vector2) catMulPoints[i + 1]);
}
SHAPE_RENDERER.end();
}
private void renderCircleAtLocation(Vector2 location) {
SHAPE_RENDERER.begin(ShapeRenderer.ShapeType.Filled);
SHAPE_RENDERER.setColor(Color.YELLOW);
SHAPE_RENDERER.circle(location.x, location.y, .5f);
SHAPE_RENDERER.end();
}
To create a decent sized CatmullRomSpline for testing this out:
Vector2[] controlPoints = makeControlPointsArray();
CatmullRomSpline myCatmull = new CatmullRomSpline(controlPoints, false);
....
private Vector2[] makeControlPointsArray() {
Vector2[] pointsArray = new Vector2[78];
pointsArray[0] = new Vector2(1.681817f, 10.379999f);
pointsArray[1] = new Vector2(2.045455f, 10.379999f);
pointsArray[2] = new Vector2(2.663636f, 10.479999f);
pointsArray[3] = new Vector2(3.027272f, 10.700000f);
pointsArray[4] = new Vector2(3.663636f, 10.939999f);
pointsArray[5] = new Vector2(4.245455f, 10.899999f);
pointsArray[6] = new Vector2(4.736363f, 10.720000f);
pointsArray[7] = new Vector2(4.754545f, 10.339999f);
pointsArray[8] = new Vector2(4.518181f, 9.860000f);
pointsArray[9] = new Vector2(3.790908f, 9.340000f);
pointsArray[10] = new Vector2(3.172727f, 8.739999f);
pointsArray[11] = new Vector2(3.300000f, 8.340000f);
pointsArray[12] = new Vector2(3.700000f, 8.159999f);
pointsArray[13] = new Vector2(4.227272f, 8.520000f);
pointsArray[14] = new Vector2(4.681818f, 8.819999f);
pointsArray[15] = new Vector2(5.081817f, 9.200000f);
pointsArray[16] = new Vector2(5.463636f, 9.460000f);
pointsArray[17] = new Vector2(5.972727f, 9.300000f);
pointsArray[18] = new Vector2(6.063636f, 8.780000f);
pointsArray[19] = new Vector2(6.027272f, 8.259999f);
pointsArray[20] = new Vector2(5.700000f, 7.739999f);
pointsArray[21] = new Vector2(5.300000f, 7.440000f);
pointsArray[22] = new Vector2(4.645454f, 7.179999f);
pointsArray[23] = new Vector2(4.136363f, 6.940000f);
pointsArray[24] = new Vector2(3.427272f, 6.720000f);
pointsArray[25] = new Vector2(2.572727f, 6.559999f);
pointsArray[26] = new Vector2(1.900000f, 7.100000f);
pointsArray[27] = new Vector2(2.336362f, 7.440000f);
pointsArray[28] = new Vector2(2.590908f, 7.940000f);
pointsArray[29] = new Vector2(2.318181f, 8.500000f);
pointsArray[30] = new Vector2(1.663636f, 8.599999f);
pointsArray[31] = new Vector2(1.209090f, 8.299999f);
pointsArray[32] = new Vector2(1.118181f, 7.700000f);
pointsArray[33] = new Vector2(1.045455f, 6.880000f);
pointsArray[34] = new Vector2(1.154545f, 6.100000f);
pointsArray[35] = new Vector2(1.281817f, 5.580000f);
pointsArray[36] = new Vector2(1.700000f, 5.320000f);
pointsArray[37] = new Vector2(2.190908f, 5.199999f);
pointsArray[38] = new Vector2(2.900000f, 5.100000f);
pointsArray[39] = new Vector2(3.700000f, 5.100000f);
pointsArray[40] = new Vector2(4.372727f, 5.220000f);
pointsArray[41] = new Vector2(4.827272f, 5.220000f);
pointsArray[42] = new Vector2(5.463636f, 5.160000f);
pointsArray[43] = new Vector2(5.554545f, 4.700000f);
pointsArray[44] = new Vector2(5.245453f, 4.340000f);
pointsArray[45] = new Vector2(4.445455f, 4.280000f);
pointsArray[46] = new Vector2(3.609091f, 4.260000f);
pointsArray[47] = new Vector2(2.718181f, 4.160000f);
pointsArray[48] = new Vector2(1.990908f, 4.140000f);
pointsArray[49] = new Vector2(1.427272f, 3.980000f);
pointsArray[50] = new Vector2(1.609090f, 3.580000f);
pointsArray[51] = new Vector2(2.136363f, 3.440000f);
pointsArray[52] = new Vector2(3.227272f, 3.280000f);
pointsArray[53] = new Vector2(3.972727f, 3.340000f);
pointsArray[54] = new Vector2(5.027272f, 3.360000f);
pointsArray[55] = new Vector2(5.718181f, 3.460000f);
pointsArray[56] = new Vector2(6.100000f, 4.240000f);
pointsArray[57] = new Vector2(6.209091f, 4.500000f);
pointsArray[58] = new Vector2(6.118181f, 5.320000f);
pointsArray[59] = new Vector2(5.772727f, 5.920000f);
pointsArray[60] = new Vector2(4.881817f, 6.140000f);
pointsArray[61] = new Vector2(5.318181f, 6.580000f);
pointsArray[62] = new Vector2(6.263636f, 7.020000f);
pointsArray[63] = new Vector2(6.645453f, 7.420000f);
pointsArray[64] = new Vector2(6.681817f, 8.179999f);
pointsArray[65] = new Vector2(6.627272f, 9.080000f);
pointsArray[66] = new Vector2(6.572727f, 9.699999f);
pointsArray[67] = new Vector2(6.263636f, 10.820000f);
pointsArray[68] = new Vector2(5.754546f, 11.479999f);
pointsArray[69] = new Vector2(4.536363f, 11.599998f);
pointsArray[70] = new Vector2(3.572727f, 11.700000f);
pointsArray[71] = new Vector2(2.809090f, 11.660000f);
pointsArray[72] = new Vector2(1.445455f, 11.559999f);
pointsArray[73] = new Vector2(0.936363f, 11.280000f);
pointsArray[74] = new Vector2(0.754545f, 10.879999f);
pointsArray[75] = new Vector2(0.700000f, 9.939999f);
pointsArray[76] = new Vector2(0.918181f, 9.620000f);
pointsArray[77] = new Vector2(1.463636f, 9.600000f);
return pointsArray;
}
Disclaimer: My math is very rusty, so please explain in lay mans terms....