msteinbeck/tinyspline

The results of the tangent function are different than expected for NURBS

abrzozowski opened this issue · 2 comments

I need to obtain the angle of the tangent line at a point of the function (NURBS), but I am getting results that I don't understand.

Below, I am providing the code that I used for testing. In it, I created a NURBS curve defined as a quarter circle with a radius of 3 meters.

image

#include <iostream>
#include <cmath>

#include "tinysplinecxx.h"

int main(int argc, char **argv)
{
	tinyspline::BSpline nurbs(3, 3, 2, tinyspline::BSpline::Type::Clamped);
	std::vector<tinyspline::real> ctrlp(3 * 3, 1.0);
	std::vector<tinyspline::real> weights{1.0, 0.707, 1.0};
	nurbs.setControlPoints({
		0.0 * weights[0],
		0.0 * weights[0],
		weights[0],
		3.0 * weights[1],
		0.0 * weights[1],
		weights[1],
		3.0 * weights[2],
		3.0 * weights[2],
		weights[2],
	});
	nurbs.setKnots({0.0, 0.0, 0.0, 1.0, 1.0, 1.0});

	std::vector<double> parameters{0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0};

	// [1] the derive method
	const auto samples = nurbs.evalAll(parameters);
	const auto derives = nurbs.derive().evalAll(parameters);
	for (std::size_t i = 0; i < parameters.size(); ++i)
	{
		const auto theta = std::atan2(derives[i * 3 + 1] / derives[i * 3 + 2], derives[i * 3 + 0] / derives[i * 3 + 2]);

		std::cout << "p(" << samples[i * 3 + 0] / samples[i * 3 + 2] << ", " << samples[i * 3 + 1] / samples[i * 3 + 2]
				  << ", " << theta << " (" << theta / M_PI * 180.0 << " degs)"
				  << ")" << std::endl;
	}

	// [2] use frames
	const auto frames = nurbs.computeRMF(parameters);
	for (std::size_t i = 0; i < frames.size(); ++i)
	{
		const auto dx = frames.at(i).tangent().x() / frames.at(i).tangent().z();
		const auto dy = frames.at(i).tangent().y() / frames.at(i).tangent().z();
		const auto position = frames.at(i).position();

		const auto theta = std::atan2(dy, dx);

		std::cout << "p(" << position.x() / position.z() << ", " << position.y() / position.z()
				  << ", " << theta << " (" << theta / M_PI * 180.0 << " degs)"
				  << ")" << std::endl;
	}

	// [3] custom implemention of the derive
	for (std::size_t i = 0; i < parameters.size(); ++i)
	{
		const auto p1 = nurbs.eval(std::max(parameters[i] - TS_KNOT_EPSILON, nurbs.domain().min())).resultVec3();
		const auto p2 = nurbs.eval(std::min(parameters[i] + TS_KNOT_EPSILON, nurbs.domain().max())).resultVec3();
		const auto dx = p2.x() / p2.z() - p1.x() / p1.z();
		const auto dy = p2.y() / p2.z() - p1.y() / p1.z();
		const auto theta = std::atan2(dy, dx);

		std::cout << "p(" << samples[i * 3 + 0] / samples[i * 3 + 2] << ", " << samples[i * 3 + 1] / samples[i * 3 + 2]
				  << ", " << theta << " (" << theta / M_PI * 180.0 << " degs)"
				  << ")" << std::endl;
	}

	return 0;
}

The results are as follows

// [1] the derive method
p(0, 0, -3.14159 (-180 degs))
p(0.545828, 0.0500851, -2.95309 (-169.2 degs))
p(1.1042, 0.210645, -2.74886 (-157.498 degs))
p(1.64155, 0.489042, -2.54465 (-145.798 degs))
p(2.12127, 0.878735, 0.785398 (45 degs))
p(2.51096, 1.35845, 0.94862 (54.3519 degs))
p(2.78936, 1.8958, 1.08448 (62.1361 degs))
p(2.94991, 2.45417, 1.19547 (68.4956 degs))
p(3, 3, 1.28577 (73.6694 degs))

// [2] use frames
p(0, 0, -3.14159 (-180 degs))
p(0.545828, 0.0500851, -2.95309 (-169.2 degs))
p(1.1042, 0.210645, -2.74886 (-157.498 degs))
p(1.64155, 0.489042, -2.54465 (-145.798 degs))
p(2.12127, 0.878735, 0.785398 (45 degs))
p(2.51096, 1.35845, 0.94862 (54.3519 degs))
p(2.78936, 1.8958, 1.08448 (62.1361 degs))
p(2.94991, 2.45417, 1.19547 (68.4956 degs))
p(3, 3, 1.28577 (73.6694 degs))

// [3] custom implemention of the derive
p(0, 0, 7.07234e-05 (0.00405215 degs))
p(0.545828, 0.0500851, 0.183002 (10.4853 degs))
p(1.1042, 0.210645, 0.376984 (21.5996 degs))
p(1.64155, 0.489042, 0.579044 (33.1768 degs))
p(2.12127, 0.878735, 0.785398 (45 degs))
p(2.51096, 1.35845, 0.991753 (56.8232 degs))
p(2.78936, 1.8958, 1.19381 (68.4004 degs))
p(2.94991, 2.45417, 1.38779 (79.5147 degs))
p(3, 3, 1.57073 (89.9959 degs))

The greatest difference is seen at the last point p(3, 3) of the curve: 89.9959 degs vs 73.6694 degs. The result should indicate a value of 90 degs, because at this point, the NURBS "indicates due north".

Is there an obvious mistake I am making using tinyspline?

hi @abrzozowski,

that's an interesting observation. I'll have a look.

@msteinbeck The solution is a bit more complicated, but it is quite well described here: https://public.vrac.iastate.edu/~oliver/courses/me625/week8.pdf
image
image