From 2e131924793dd479e823530d883a1641001adec7 Mon Sep 17 00:00:00 2001 From: Ralph Sennhauser Date: Wed, 29 Jul 2026 19:07:22 +0200 Subject: [PATCH] Fix deleting last node in spline The last node needs to have distance "0" to next node. Do the inverse of when adding nodes. Also add some basic tests covering this bug. Fixes: #4659 Signed-off-by: Ralph Sennhauser --- source/maths/NUSpline.cpp | 2 + source/maths/tests/test_NUSpline.h | 120 +++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 source/maths/tests/test_NUSpline.h diff --git a/source/maths/NUSpline.cpp b/source/maths/NUSpline.cpp index a92aff050a..47fbdae55d 100644 --- a/source/maths/NUSpline.cpp +++ b/source/maths/NUSpline.cpp @@ -247,6 +247,8 @@ void TNSpline::RemoveNode(const int index) if (m_NodeCount == 0 || index > m_NodeCount - 1) return; + if (index > 0) + std::swap(m_Nodes[index].Distance, m_Nodes[index - 1].Distance); m_MaxDistance -= m_Nodes[index].Distance; m_Nodes.erase(m_Nodes.begin() + index); --m_NodeCount; diff --git a/source/maths/tests/test_NUSpline.h b/source/maths/tests/test_NUSpline.h new file mode 100644 index 0000000000..c99baaaa00 --- /dev/null +++ b/source/maths/tests/test_NUSpline.h @@ -0,0 +1,120 @@ +/* Copyright (C) 2026 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "maths/Fixed.h" +#include "maths/NUSpline.h" + +#define TS_ASSERT_VEC_EQUALS(v, x, y, z) \ + TS_ASSERT_EQUALS(v.X, x); \ + TS_ASSERT_EQUALS(v.Y, y); \ + TS_ASSERT_EQUALS(v.Z, z); + +class TestNUSpline : public CxxTest::TestSuite +{ +public: + void test_TNSAddNode() + { + TNSpline spline{}; + CFixedVector3D p0{fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(0)}; + CFixedVector3D r0{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + fixed d0 = fixed::FromInt(33); + + CFixedVector3D p1{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + CFixedVector3D r1{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + fixed d1 = fixed::FromInt(4); + + CFixedVector3D p2{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + CFixedVector3D r2{fixed::FromInt(3), fixed::FromInt(3), fixed::FromInt(3)}; + fixed d2 = fixed::FromInt(12); + + spline.AddNode(p0, r0, d0); + spline.AddNode(p1, r1, d1); + spline.AddNode(p2, r2, d2); + + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.f), 0, 0, 0); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.25f), 1, 1, 1); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(1.f), 2, 2, 2); + + TS_ASSERT_EQUALS(spline.GetMaxDistance(), fixed::FromInt(16)); + } + + void test_TNSInsertNode() + { + TNSpline spline{}; + CFixedVector3D p0{fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(0)}; + CFixedVector3D r0{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + fixed d0 = fixed::FromInt(4); + + CFixedVector3D p1{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + CFixedVector3D r1{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + fixed d1 = fixed::FromInt(33); + + CFixedVector3D p2{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + CFixedVector3D r2{fixed::FromInt(3), fixed::FromInt(3), fixed::FromInt(3)}; + fixed d2 = fixed::FromInt(12); + + spline.InsertNode(0, p1, r1, d1); + spline.InsertNode(0, p0, r0, d0); + spline.InsertNode(2, p2, r2, d2); + + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.f), 0, 0, 0); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.25f), 1, 1, 1); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(1.f), 2, 2, 2); + + TS_ASSERT_EQUALS(spline.GetMaxDistance(), fixed::FromInt(16)); + } + + void test_TNSRemoveNode() + { + TNSpline spline{}; + CFixedVector3D p0{fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(0)}; + CFixedVector3D r0{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + fixed d0 = fixed::FromInt(33); + + CFixedVector3D p1{fixed::FromInt(1), fixed::FromInt(1), fixed::FromInt(1)}; + CFixedVector3D r1{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + fixed d1 = fixed::FromInt(4); + + CFixedVector3D p2{fixed::FromInt(2), fixed::FromInt(2), fixed::FromInt(2)}; + CFixedVector3D r2{fixed::FromInt(3), fixed::FromInt(3), fixed::FromInt(3)}; + fixed d2 = fixed::FromInt(12); + + spline.AddNode(p0, r0, d0); + spline.AddNode(p1, r1, d1); + spline.AddNode(p2, r2, d2); + + spline.RemoveNode(2); + + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.f), 0, 0, 0); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(1.f), 1, 1, 1); + + TS_ASSERT_EQUALS(spline.GetMaxDistance(), fixed::FromInt(4)); + + spline.RemoveNode(0); + + TS_ASSERT_EQUALS(spline.GetMaxDistance(), fixed::FromInt(0)); + + spline.AddNode(p2, r2, d2); + + TS_ASSERT_VEC_EQUALS(spline.GetPosition(0.f), 1, 1, 1); + TS_ASSERT_VEC_EQUALS(spline.GetPosition(1.f), 2, 2, 2); + + TS_ASSERT_EQUALS(spline.GetMaxDistance(), fixed::FromInt(12)); + } +};