Why is my Catmull-Rom Spline being drawn incorrectly?

Description:
I have a list of Vector3 points.
When I draw a Catmull-Rom spline through the points, it behaves very oddly and I can’t fix it.

For the code example, I’m doing 3 things:

  1. I’ve build a points array of 10 points which is data extracted from my project.
  2. I’m displaying the points with spheres for clarity purposes, so you can clearly see them from above
  3. Bug inducing step: I’m drawing a Catmull-rom spline through the points which behaves not as expected.

The problem:
Notice the odd behavior of the line making a sharp turn in the wrong direction before looping back to the correct direction.

Here is the full snippet playground that reproduces the same problem as it exists within my own project:

What am I doing wrong, how can this be fixed?
Note that the points array can not, and should not, ever be altered.

I’ve tried to reproduce the bug in another framework by placing the points similarly, but in these other frameworks, the line is being drawn normally, as expected.
( for example: Smooth Paths Using Catmull-Rom Splines | Mika’s Coding Bits )

Been stuck on this for a few days. Any help would be greatly appreciated :slight_smile:

UPDATE:

After a lot of research, I found the culprit, but no fix yet.

The Catmull-Rom Spline implementation in BabylonJS appears to be the “Uniform” type.
The other two possible types are “Centripetal” and “Chordal” which have seemingly not been implemented in BabylonJS (correct me if I’m wrong :grey_question::grey_question::grey_question:).
I figured this out through Mika’s Coding Bits ( Smooth Paths Using Catmull-Rom Splines | Mika’s Coding Bits ),
I could verify that this is the issue when changing Type: “Centripal” to “Uniform” on his webpage.
When set to “Uniform”, the bug is 100 % recreated and I get the same odd behavior as in my BabylonJS playground here above.

If you take a look at Three.JS framework for example (three.js docs ),
their implementation allows you to give a “curveType” as a parameter with possible values:
centripal, chordal, catmullrom ( = uniform).
This appears to be exactly what I need, allowing me to choose the “centripal” instead of “uniform”.

To make the difference between the 3 types easier to understand:

“Uniform” expects your points to be somewhat equally distributed.
“Chordal” and “Centripetal” do not require this - which is my use case.

It seems the only way to move forward with BabylonJS, would be:
implementing the Centripal-version of the Catmull-Rom myself? (which I can not, I don’t have the mathemetical background to even start wrapping my head around this :woozy_face:).

Any help or tips on how to proceed within BabylonJS would be appreciated,
maybe I might still be missing some important information? :sweat_smile:

1 Like

Nice finding :slight_smile: I guess the cleanest solution would be to re-implement a Catmull-Rom computation with the adequate type.

Depending on your use case, I have a quickfix that might work by cleaning the input before generating the spline, it consists in inserting points where the segments are the longest.

Here’s an example where all segments longer than 5x the smallest are subdivided https://playground.babylonjs.com/#82UWMI#8

(I’ve also reduced the nbPoints parameter because CleanInPlace already adds a lot of points)

It ‘works’ in your case, but because it flattens the longest segment, if you have for example a spiral where each segment is longer than the previous one, it will cause visible artifacts.
You may improve it by subdividing segments only if they are too long compared to the adjacent ones… But then it might just be more simple to implement the adequate Catmull-Clark computation ^^

2 Likes

Thanks for taking the time to think about the problem.

Your proposal is a bit of a hotfix indeed that I’d like to keep only as a last resort.
In that case, I’d adapt your solution by going through the entire list and ensure the points are more equally distributed overall (by adding extra, to ensure no unexpected new bugs, problems or artifacts).
But the main worry however, would be that this will malform the line more, as you are stating with spirals for example, which definetely will be an occurance.

That is far from ideal.

A proper implementation of the Catmull-Rom with the adequate type (Centripetal) seems to be necessary and the most wished for at this point, for my use case :thinking:

I actually converted the threejs CatmullRomCurve3.js to work with babylonjs a number of years ago for a project, which I’ve copied in below. I haven’t tested it with a current version of babylonjs, but it should still work, and would just need a slight change the import if using the es6 version.

import * as BABYLON from "babylonjs";

//For centripetal catmullrom
//http://zz85.github.io/ThreeLabs/spline_editor3.html
//Ported from threejs

/**
 * @author zz85 https://github.com/zz85
 *
 * Centripetal CatmullRom Curve - which is useful for avoiding
 * cusps and self-intersections in non-uniform catmull rom curves.
 * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
 *
 * curve.type accepts centripetal(default), chordal and catmullrom
 * curve.tension is used for catmullrom which defaults to 0.5
 */


/*
Based on an optimized c++ solution in
 - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
 - http://ideone.com/NoEbVM

This CubicPoly class could be used for reusing some variables and calculations,
but for three.js curve use, it could be possible inlined and flatten into a single function call
which can be placed in CurveUtils.
*/

class CubicPoly {
    c0: number;
    c1: number;
    c2: number;
    c3: number;

	/*
	 * Compute coefficients for a cubic polynomial
	 *   p(s) = c0 + c1*s + c2*s^2 + c3*s^3
	 * such that
	 *   p(0) = x0, p(1) = x1
	 *  and
	 *   p'(0) = t0, p'(1) = t1.
	 */
	private init( x0: number, x1: number, t0: number, t1: number ) {
		this.c0 = x0;
		this.c1 = t0;
		this.c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
		this.c3 = 2 * x0 - 2 * x1 + t0 + t1;
	}


    initCatmullRom(x0: number, x1: number, x2: number, x3: number, tension: number) {
        this.init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
    }

    initNonuniformCatmullRom(x0: number, x1: number, x2: number, x3: number, dt0: number, dt1: number, dt2: number) {
        // compute tangents when parameterized in [t1,t2]
        let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
        let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;

        // rescale tangents for parametrization in [0,1]
        t1 *= dt1;
        t2 *= dt1;

        this.init( x1, x2, t1, t2 );
    }

    calc(t: number) {
        const t2 = t * t;
        const t3 = t2 * t;
        return this.c0 + this.c1 * t + this.c2 * t2 + this.c3 * t3;
    }
}

class CatmullRomCurve3 {

    public tmp = BABYLON.Vector3.Zero();
    public px = new CubicPoly();
    public py = new CubicPoly();
    public pz = new CubicPoly();
    private _points: BABYLON.Vector3[] | [number, number, number][]  = [];

    constructor(public points: BABYLON.Vector3[], public returnArray = false, public maxLineDistance = 0, public closed = false, public type?: string, public tension?: number) {

    }

    getPoints(divisions: number): BABYLON.Vector3[] | [number, number, number][] {
        if ( divisions === undefined ) divisions = 5;

        this._points.length = divisions + 1;

        for (let d = 0; d <= divisions; d++) {
            if (this._points[d] === undefined) {
                if (this.returnArray && !this._points[d]) {
                    this._points[d] = [0, 0, 0];
                } else if (!this._points[d]){
                    this._points[d] = BABYLON.Vector3.Zero();
                }
            }
            this.getPoint( d / divisions, this._points[d] );
        }

        return this._points;
    }

    getPoint(t: number, ref: BABYLON.Vector3 | [number, number, number]): void {
        const points = this.points;
        const l = points.length;

        if ( l < 2 ) console.log( 'duh, you need at least 2 points' );

        const point = ( l - ( this.closed ? 0 : 1 ) ) * t;
        let intPoint = Math.floor( point );
        let weight = point - intPoint;

        if ( this.closed ) {
            intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;

        } else if ( weight === 0 && intPoint === l - 1 ) {
            intPoint = l - 2;
            weight = 1;
        }

        let p0, p1, p2, p3; // 4 points

        if ( this.closed || intPoint > 0 ) {
            p0 = points[ ( intPoint - 1 ) % l ];

        } else {
            p0 = points[0];
            // extrapolate first point
            // (points[ 0 ]).subtractToRef(points[ 1 ], tmp).addToRef(points[ 0 ], tmp);
            // // tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
            // p0 = tmp.clone();
        }

        p1 = points[ intPoint % l ];
        p2 = points[ ( intPoint + 1 ) % l ];

        if ( this.closed || intPoint + 2 < l ) {
            p3 = points[ ( intPoint + 2 ) % l ];

        } else {
            p3 = points[ l - 1 ];
            // extrapolate last point
            // (points[ l - 1 ]).subtractToRef(points[ l - 2 ], tmp).addToRef(points[  l - 1 ], tmp);
            // tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
            // p3 = tmp.clone();
        }

        if ( this.type === undefined || this.type === 'centripetal' || this.type === 'chordal' ) {
            // for use when using on 2d globe, and don't want to smooth line segment that should be culled
            if (this.maxLineDistance > 0 && Math.abs(p1.z - p2.z) > this.maxLineDistance) {
                if (this.returnArray) {
                    ref[0] = p1.x; ref[1] = p1.y; ref[2] = p1.z;
                } else {
                    (<BABYLON.Vector3>ref).copyFrom(p1);
                }
                return;
            }
            // init Centripetal / Chordal Catmull-Rom
            const pow = this.type === 'chordal' ? 0.5 : 0.25;
            let dt0 = Math.pow( BABYLON.Vector3.DistanceSquared(p0, p1), pow );
            let dt1 = Math.pow( BABYLON.Vector3.DistanceSquared(p1, p2), pow );
            let dt2 = Math.pow( BABYLON.Vector3.DistanceSquared(p2, p3), pow );

            // safety check for repeated points
            if ( dt1 < 1e-4 ) dt1 = 0.1;
            if ( dt0 < 1e-4 ) dt0 = dt1;
            if ( dt2 < 1e-4 ) dt2 = dt1;

            if (dt0 + dt1 + dt2 === 0.3) { //if all the same, then return that point
                if (this.returnArray) {
                    ref[0] = p0.x; ref[1] = p0.y; ref[2] = p0.z;
                } else {
                    (<BABYLON.Vector3>ref).copyFromFloats(p0.x, p0.y, p0.z);
                }
                return
            }

            this.px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
            this.py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
            this.pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );

        } else if ( this.type === 'catmullrom' ) {
            const tension = this.tension !== undefined ? this.tension : 0.5;
            this.px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, tension );
            this.py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, tension );
            this.pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, tension );
        }

        if (this.returnArray) {
            ref[0] = this.px.calc( weight ); ref[1] = this.py.calc( weight ); ref[2] = this.pz.calc( weight );
        } else {
            (<BABYLON.Vector3>ref).copyFromFloats( this.px.calc( weight ), this.py.calc( weight ), this.pz.calc( weight ) );
        }

    }
}

export { CatmullRomCurve3 };
3 Likes

wow this looks really useful,
I’m going to look into this tomorrow and will reply again with my findings!
Just want to hereby thank you already for the help :smiley:

1 Like

@bghgary is the one who knows for this part for the babylon implementation ???

I didn’t write the catmull-rom spline code in Babylon.js, but it only supports one type (looks like uniform… I haven’t checked the math). Uniform can cause loops and intersections. Add the other flavors shouldn’t be too hard to do. You might also consider using the Hermite representation which allows you to specify the tangents.

1 Like

Oh I would have bet a lot on you for this one lol

I have implemented my own version, also based on the threejs CatmullRomCurve3.js like @sable did.
But I didn’t base it off of @sable since I had already started on it and figured it was easier to complete what I already had, but again, thanks for your sharing, I appreciate it.

My version works with the current version of BabylonJS, and it’s written in TypeScript.
Here is the code:

import * as BABYLON from "@babylonjs/core";


/**
 * Centripetal CatmullRom Curve - which is useful for avoiding
 * cusps and self-intersections in non-uniform catmull rom curves.
 * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
 *
 * curve.type accepts centripetal(default), chordal and catmullrom
 * curve.tension is used for catmullrom which defaults to 0.5
 */


/*
Based on an optimized c++ solution in
 - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
 - http://ideone.com/NoEbVM
This CubicPoly class could be used for reusing some variables and calculations,
but for three.js curve use, it could be possible inlined and flatten into a single function call
which can be placed in CurveUtils.
*/

interface CubicPoly {
    initCatmullRom(x0: number, x1: number, x2: number, x3: number, tension: number): void;
    initNonuniformCatmullRom(x0: number, x1: number, x2: number, x3: number, dt0: number, dt1: number, dt2: number, tension : number): void;
    calc(t: number): number;
}

class CubicPoly implements CubicPoly {
    c0: number;
    c1: number;
    c2: number;
    c3: number;
    constructor() {
        this.c0 = 0;
        this.c1 = 0;
        this.c2 = 0;
        this.c3 = 0;
    }
    initCatmullRom(x0: number, x1: number, x2: number, x3: number, tension: number) {
        this.init(x1, x2, tension * (x2 - x0), tension * (x3 - x1));
    }
    initNonuniformCatmullRom(x0: number, x1: number, x2: number, x3: number, dt0: number, dt1: number, dt2: number, tension: number) {
        // compute tangents when parameterized in [t1,t2]
        let t1 = (x1 - x0) / dt0 - (x2 - x0) / (dt0 + dt1) + (x2 - x1) / dt1;
        let t2 = (x2 - x1) / dt1 - (x3 - x1) / (dt1 + dt2) + (x3 - x2) / dt2;
        // rescale tangents for parametrization in [0,1]
        t1 *= dt1 * (tension);
        t2 *= dt1 * (tension);
        this.init(x1, x2, t1, t2);
    }
    calc(t: number) {
        const t2 = t * t;
        const t3 = t2 * t;
        return this.c0 + this.c1 * t + this.c2 * t2 + this.c3 * t3;
    }
    init(x0: number, x1: number, t0: number, t1: number) {
        this.c0 = x0;
        this.c1 = t0;
        this.c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1;
        this.c3 = 2 * x0 - 2 * x1 + t0 + t1;
    }
}

//

const px = new CubicPoly();
const py = new CubicPoly();
const pz = new CubicPoly();


class CatmullRomCurve3 {

    isCatmullRomCurve3 : boolean;
    type : string;
    closed : boolean;
    curveType : string;
    tension : number;
    curve: BABYLON.Curve3;
    nbPoints : number;

	constructor( points : BABYLON.Vector3[], nbPoints : number, closed = false, curveType = 'centripetal', tension = 0.5 ) {

		// super(points);
        Object.assign(this, new BABYLON.Curve3(points));
        this.curve = new BABYLON.Curve3(points);

		this.isCatmullRomCurve3 = true;
		this.type = 'CatmullRomCurve3';
		this.closed = closed;
		this.curveType = curveType;
		this.tension = tension;
        this.nbPoints = nbPoints;

	}

    // My own override of the original getPoints behaviour,
    // This one uses nbPoints... goes through all steps
    getPoints(){
        const rawPoints = this.curve.getPoints();
        const result = [];
        for(let i = 0; i < rawPoints.length;  i += rawPoints.length / this.nbPoints){
            result.push( this.getPoint(i / rawPoints.length) )
        }

        return result;
    }

	getPoint( t: number, optionalTarget = new BABYLON.Vector3() ) {

		const point = optionalTarget;

		const points = this.curve.getPoints();
		const l = points.length;

		const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
		let intPoint = Math.floor( p );
		let weight = p - intPoint;

		if ( this.closed ) {

			intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;

		} else if ( weight === 0 && intPoint === l - 1 ) {

			intPoint = l - 2;
			weight = 1;

		}

		let p0, p3; // 4 points (p1 & p2 defined below)

		if ( this.closed || intPoint > 0 ) {

			p0 = points[ ( intPoint - 1 ) % l ];

		} else {

			// extrapolate first point
            p0 = points[ 0 ].subtract(points[1]).add( points[ 0 ] );

		}

		const p1 = points[ intPoint % l ];
		const p2 = points[ ( intPoint + 1 ) % l ];

		if ( this.closed || intPoint + 2 < l ) {

			p3 = points[ ( intPoint + 2 ) % l ];

		} else {

			// extrapolate last point
            p3 =  points[ l - 1 ].subtract(points[ l - 2 ]).add( points[ l - 1 ] );

		}

		if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {

			// init Centripetal / Chordal Catmull-Rom
			const pow = this.curveType === 'chordal' ? 0.5 : 0.25;
            let dt0 = Math.pow( BABYLON.Vector3.DistanceSquared(p0, p1), pow );
            let dt1 = Math.pow( BABYLON.Vector3.DistanceSquared(p1, p2), pow ); 
            let dt2 = Math.pow( BABYLON.Vector3.DistanceSquared(p2, p3), pow ); 
    

			// safety check for repeated points
			if ( dt1 < 1e-4 ) dt1 = 1.0;
			if ( dt0 < 1e-4 ) dt0 = dt1;
			if ( dt2 < 1e-4 ) dt2 = dt1;

			px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 , this.tension );
			py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 , this.tension );
			pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 , this.tension );

		} else if ( this.curveType === 'uniform' ) {

			px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );
			py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );
			pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );

		}

		point.set(
			px.calc( weight ),
			py.calc( weight ),
			pz.calc( weight )

);


		return point;

	}
}

export { CatmullRomCurve3 };

I didn’t properly test everything, I’m not an expert at this stuff and am probably going against some “good practices”.
But it works in my project and all my splines are correct now.

@bghgary

@bghgary , yes that are my findings:
Babylon only supports one type, the uniform indeed.
As far as I understand, the “uniform” type is the oldest one, but games tend to use the moderner versions like “chordal” and “centripetal”… but most often “centripetal”.
I have tested the “Hermite” solution but that was more trouble.

The thing is, there is a common use case for devs to want to just give a long list of points and then request the code to “draw me a long line through these 100s or 1000s of points”.

The Catmull-Rom curve offers just that (accepting long list of Vector3s).
While the Hermite spline on the other hand, doesn’t work like that (accepts only 2 points and 2 tangents).

I think it’s important for BabylonJS to have full support of a proper functioning Catmull-Rom implementation that simply accepts 1000s of points and just draws it, with no artifacts (which means, centripetal instead of “uniform”).
(and in my implementation, I also have to ability to provide a tension of 0 to 1 , which I’m not sure I have implemented correctly, but it is useable in its current form)

I’m not a github Babylon contributer yet ( considering I’ve only joined the 3D space just recently), so I’m not going to suddenly start editing the framework publicly just yet :smile:

But if anyone wants to use my above solution for helping with updating the github code, feel free to just do so (obviously no credits or anything required): someone who actually knows what they are doing instead of me who’s just trying until it works :sweat_smile:.

1 Like

There should be a way to compute the tangents, but I agree if the Catmull-Rom version can be modified to support other types, that would be easier.

I once hired someone many, many moons ago to solve this in an obscure scripting language for a game I worked on. It came out extremely well and handles quick acceleration changes and turns quite nicely with no tangents and non uniform keyframes (in time/space). It’s some very intense maths far beyond my meager capabilities. If that sounds useful to anyone willing to take a look at porting it, can post.

Its movement/path looks something like this:

3 Likes

@Gamedev, I know Babylon a bit and you know the maths so if you want I can help you on a PR if you fancy adding the Maths to Babylon

@sebavan I can’t say I know the maths. I just copy-pasted it and converted the code where necessary.
For example, I don’t know how to apply tension properly, I only made a best guess for that part.
I’m not qualified on the math department to assist on an official proper implementation.

I think it’s important that someone does it who know the ins and outs of the involved mathemetical formulas, otherwise it’s possibly going to be a halfly-correct implementation that seemingly works, but doesn’t. I also wouldn’t know how to even begin testing it.

I’m really new to 3D here including the math part of it.

2 Likes

Well, here we go. See if anyone is insane enough to try :joy: :joy: @sebavan

Outputs [interpolated transforms] from non-uniform keyframes in 3D space + 1D timeline for camera tracks and other uses. The output path can be sampled with deltaTime(ms) for a position or can be used to visualize the spline. Acceleration and rotation changes are very smooth with no tangents needed… IN TORQUESCRIPT LOL. Every game looking to make cinematic videos should have this or something like it.

You saw the results above but… I was not kidding about how much would be to port from Mathematics perspective and forgot the best part about Torquescript which is everything on the script level is a string! Seriously.

Im 100% confident everything producing the “path over time” from video in last post is wrapped up in this gist though. Except the part that was lerping the camera between the positions output by this thing.

Reading some of the comments, I remember it’s a “7D spline using cubic polynomials” is the way to describe it :exploding_head:

I made sure to fully plaster with MIT. All games should have a spline system like this for making movies :slight_smile: it will also handle displaying the interpolated points nicely too besides the whole 7D :joy: That part about visualizing is mentioned in here but commented out and would be up to the engine/framework.

3 Likes

So coool to read :slight_smile:

3 Likes

I am the culprit
So long ago that I can’t remember how I implemented that… probably from some maths I read on Wikipedia at this time.

2 Likes

There is no blame here. :slight_smile: It’s a totally valid Catmull-Rom spline that has its own characteristics.

2 Likes

@jerome
Thanks for joining the discussion :smile:

None of us seems to be knowing the maths,
but still we are close to an improved implementation so maybe it’s worth exploring it properly now.

Here is a short course I’ve made.

There exist 3 types of Catmull-Rom splines:
Uniform, Centripetal, Chordal

source: http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf

  • Green = Uniform (the only one currently implemented in BabylonJS): assumes your input points to be evenly spaced… doesn’t require this though but leads to unexpected behaviour if not evenly spaced.
    Can suffer from “the overshoot problem” as you can see in the figure above where the line goes beyond the input point, loops back (overshooting a second time), and then connects with the next input point.

  • Blue = Centripetal (not implemented, but most popular for 3D games for its smoothness and natural look): adapts itself when the points are not evenly spaced, but tries to not divert too much

  • Red = Chordal (not implemented): adapts itself when the points are not evenly spaced, but prioritizes smoothness above accuracy

A developers’ choice of which type of Catmull-Rom Spline to use will depend on the specific requirements of his application.
Supporting all 3 types would be the best possible outcome.

In terms of implementation in BabylonJS, I’d advice to:
For ensuring backward compatibility, the default could be set to the one that’s been currently implemented which is “Uniform” and that math can remain untouched since it is correctly implemented.

Current API states:
CreateCatmullRomSpline(points: DeepImmutableArray, nbPoints: number, closed?: boolean): Curve3

New implementation could :pray: accept 2 extra parameters after “closed” which are:

  • curveType : String (default “uniform”, possible values are: uniform, centripetal, chordal )
  • tension : Float (default: 0.5, possibe values from 0 to 1)

The required math can be found here:
https://github.com/mrdoob/three.js/blob/03349e98aa1f57a97433c0a3f17697594fdc0997/src/extras/curves/CatmullRomCurve3.js

Tension?
The effect and implementation of Tension can be described as follows:
The tension parameter affects all three types of Catmull-Rom Splines in a similar way, but the effect can be more pronounced in some types than in others.

uniform: this type of spline is least affected by the tension parameter than the other types.

  • Increasing the tension parameter will result in a tighter curve that is closer to the input points.
  • Decreasing the tension parameter will result in a looser curve that is farther away from the input points. This type of spline is less affected by the tension parameter than the other types.

chordal: effect will be more pronounced compared to “uniform”

  • Increasing the tension parameter will result in a tighter curve that is closer to the input points.
  • Decreasing the tension parameter will result in a looser curve that is farther away from the input points. Effect will be more pronounced compared to “uniform”

centripetal: mostly affected than the other types.

  • Increasing the tension parameter will result in a tighter curve that is closer to the input points.
  • Decreasing the tension parameter will result in a looser curve that is farther away from the input points.

If wanting to learn more, you can play around with tension in the following tool:
https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html

In general, the tension parameter can be used to fine-tune the curve to meet the specific requirements of your application. It can be used to adjust the smoothness of the curve, or to control the distance between the curve and the input points.

Also providing the choice for “tension” is an additional good way to give the developer more control over the curve shape.

I hope this is helpful!

1 Like

More generally, if you look for weird old code sections until early 2019, I sometimes might be “culprit” :blush: on these parts :

Although I don’t remember everything, feel free to ask if anything goes wrong. Maybe I still have memories about some tricky stuff…

@jerome
Sorry, english is not my first language.
When I typed “culprit”, I meant “what line of code is the culprit”, and not who wrote it

There is nothing wrong with what you coded, you implemented the basics.
It’s not a bug in your code. I was looking for the incorrection in my own code.

Just wanted to clarify this so you know your work is appreciated :smile: