2026-07-19 10:30:56 -07:00
|
|
|
/* Copyright (C) 2026 Wildfire Games.
|
2023-12-02 16:30:12 -08:00
|
|
|
* This file is part of 0 A.D.
|
2009-04-18 10:00:33 -07:00
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
2009-04-18 10:00:33 -07:00
|
|
|
* 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.
|
|
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
2009-04-18 10:00:33 -07:00
|
|
|
* 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
|
2023-12-02 16:30:12 -08:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
2009-04-18 10:00:33 -07:00
|
|
|
*/
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
#include "precompiled.h"
|
2017-07-01 08:49:59 -07:00
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
#include "NUSpline.h"
|
2025-06-19 15:06:41 -07:00
|
|
|
|
|
|
|
|
#include "lib/debug.h"
|
|
|
|
|
#include "maths/FixedVector3D.h"
|
|
|
|
|
#include "maths/Matrix3D.h"
|
|
|
|
|
#include "maths/Vector3D.h"
|
|
|
|
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
|
2016-11-23 05:02:58 -08:00
|
|
|
//Note: column major order! Each set of 4 constitutes a column.
|
|
|
|
|
CMatrix3D HermiteSpline(2.f, -3.f, 0.f, 1.f,
|
|
|
|
|
-2.f, 3.f, 0.f, 0.f,
|
|
|
|
|
1.f, -2.f, 1.f, 0.f,
|
2015-12-31 05:40:56 -08:00
|
|
|
1.f, -1.f, 0.f, 0.f); // Matrix H in article
|
2006-04-23 16:14:18 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// cubic curve defined by 2 positions and 2 velocities
|
2015-12-31 05:40:56 -08:00
|
|
|
CVector3D GetPositionOnCubic(const CVector3D& startPos, const CVector3D& startVel, const CVector3D& endPos, const CVector3D& endVel, float time)
|
2006-04-23 16:14:18 -07:00
|
|
|
{
|
2016-11-23 05:02:58 -08:00
|
|
|
CMatrix3D m(startPos.X, endPos.X, startVel.X, endVel.X,
|
2015-12-31 05:40:56 -08:00
|
|
|
startPos.Y, endPos.Y, startVel.Y, endVel.Y,
|
2016-11-23 05:02:58 -08:00
|
|
|
startPos.Z, endPos.Z, startVel.Z, endVel.Z,
|
2015-12-31 05:40:56 -08:00
|
|
|
0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
|
|
|
|
|
|
m = m * HermiteSpline; // multiply by the mixer
|
|
|
|
|
|
|
|
|
|
CVector3D TimeVector(time*time*time, time*time, time);
|
|
|
|
|
CVector3D Result;
|
|
|
|
|
m.Transform(TimeVector, Result);
|
|
|
|
|
return Result;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************** R N S **************************************************/
|
|
|
|
|
|
2026-07-21 00:41:28 -07:00
|
|
|
RNSpline::RNSpline() = default;
|
2016-04-07 10:47:30 -07:00
|
|
|
|
2016-04-14 09:36:12 -07:00
|
|
|
RNSpline::~RNSpline() = default;
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
// adds node and updates segment length
|
2016-01-03 04:41:04 -08:00
|
|
|
void RNSpline::AddNode(const CFixedVector3D& pos)
|
2006-04-23 16:14:18 -07:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount >= MAX_SPLINE_NODES)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0)
|
|
|
|
|
m_MaxDistance = fixed::Zero();
|
2015-12-31 05:40:56 -08:00
|
|
|
else
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[m_NodeCount-1].Distance = (m_Nodes[m_NodeCount-1].Position - pos).Length();
|
|
|
|
|
m_MaxDistance += m_Nodes[m_NodeCount-1].Distance;
|
2015-12-31 05:40:56 -08:00
|
|
|
}
|
|
|
|
|
SplineData temp;
|
|
|
|
|
temp.Position = pos;
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes.push_back(temp);
|
|
|
|
|
++m_NodeCount;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// called after all nodes added. This function calculates the node velocities
|
|
|
|
|
void RNSpline::BuildSpline()
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 2)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[0].Velocity = GetStartVelocity(0);
|
|
|
|
|
m_Nodes[m_NodeCount-1].Velocity = GetEndVelocity(m_NodeCount-1);
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-07-19 10:30:56 -07:00
|
|
|
else if (m_NodeCount < 2)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
for (int i = 1; i < m_NodeCount-1; ++i)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
CVector3D Next = m_Nodes[i+1].Position - m_Nodes[i].Position;
|
|
|
|
|
CVector3D Previous = m_Nodes[i-1].Position - m_Nodes[i].Position;
|
2015-12-31 05:40:56 -08:00
|
|
|
Next.Normalize();
|
|
|
|
|
Previous.Normalize();
|
|
|
|
|
|
|
|
|
|
// split the angle (figure 4)
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[i].Velocity = Next - Previous;
|
|
|
|
|
m_Nodes[i].Velocity.Normalize();
|
2015-12-31 05:40:56 -08:00
|
|
|
}
|
|
|
|
|
// calculate start and end velocities
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[0].Velocity = GetStartVelocity(0);
|
|
|
|
|
m_Nodes[m_NodeCount-1].Velocity = GetEndVelocity(m_NodeCount-1);
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// spline access function. time is 0 -> 1
|
2006-08-21 19:24:44 -07:00
|
|
|
CVector3D RNSpline::GetPosition(float time) const
|
2006-04-23 16:14:18 -07:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount < 2)
|
2015-12-31 05:40:56 -08:00
|
|
|
return CVector3D(0.0f, 0.0f, 0.0f);
|
2016-01-03 04:41:04 -08:00
|
|
|
if (time < 0.0f)
|
|
|
|
|
time = 0.0f;
|
2015-12-31 05:40:56 -08:00
|
|
|
if (time > 1.0f)
|
2006-04-23 16:14:18 -07:00
|
|
|
time = 1.0f;
|
2026-07-19 10:30:56 -07:00
|
|
|
float Distance = time * m_MaxDistance.ToFloat();
|
2015-12-31 05:40:56 -08:00
|
|
|
float CurrentDistance = 0.f;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
2016-01-03 04:41:04 -08:00
|
|
|
// Find which node we're on
|
2026-07-19 10:30:56 -07:00
|
|
|
while (CurrentDistance + m_Nodes[i].Distance.ToFloat() < Distance && i < m_NodeCount - 2)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
CurrentDistance += m_Nodes[i].Distance.ToFloat();
|
2015-12-31 05:40:56 -08:00
|
|
|
++i;
|
|
|
|
|
}
|
2026-07-19 10:30:56 -07:00
|
|
|
ENSURE(i < m_NodeCount - 1);
|
2015-12-31 05:40:56 -08:00
|
|
|
float t = Distance - CurrentDistance;
|
2016-01-03 04:41:04 -08:00
|
|
|
// TODO: reimplement CVector3D comparator (float comparing is bad without EPS)
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_Nodes[i].Position == m_Nodes[i+1].Position || m_Nodes[i].Distance.ToFloat() < 1e-7) // distance too small or zero
|
2016-01-03 04:41:04 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
return m_Nodes[i+1].Position;
|
2016-01-03 04:41:04 -08:00
|
|
|
}
|
2026-07-19 10:30:56 -07:00
|
|
|
t /= m_Nodes[i].Distance.ToFloat(); // scale t in range 0 - 1
|
|
|
|
|
CVector3D startVel = m_Nodes[i].Velocity * m_Nodes[i].Distance.ToFloat();
|
|
|
|
|
CVector3D endVel = m_Nodes[i+1].Velocity * m_Nodes[i].Distance.ToFloat();
|
|
|
|
|
return GetPositionOnCubic(m_Nodes[i].Position, startVel,
|
|
|
|
|
m_Nodes[i+1].Position, endVel, t);
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
2016-04-07 10:47:30 -07:00
|
|
|
const std::vector<SplineData>& RNSpline::GetAllNodes() const
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
return m_Nodes;
|
2016-04-07 10:47:30 -07:00
|
|
|
}
|
|
|
|
|
|
2016-11-23 05:02:58 -08:00
|
|
|
// internal. Based on Equation 14
|
2006-04-23 16:14:18 -07:00
|
|
|
CVector3D RNSpline::GetStartVelocity(int index)
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (index >= m_NodeCount - 1 || index < 0)
|
2015-12-31 05:40:56 -08:00
|
|
|
return CVector3D(0.0f, 0.0f, 0.0f);
|
2026-07-19 10:30:56 -07:00
|
|
|
CVector3D temp = CVector3D(m_Nodes[index+1].Position - m_Nodes[index].Position) * 3.0f * (1.0f / m_Nodes[index].Distance.ToFloat());
|
|
|
|
|
return (temp - m_Nodes[index+1].Velocity)*0.5f;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
2016-11-23 05:02:58 -08:00
|
|
|
// internal. Based on Equation 15
|
2006-04-23 16:14:18 -07:00
|
|
|
CVector3D RNSpline::GetEndVelocity(int index)
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (index >= m_NodeCount || index < 1)
|
2015-12-31 05:40:56 -08:00
|
|
|
return CVector3D(0.0f, 0.0f, 0.0f);
|
2026-07-19 10:30:56 -07:00
|
|
|
CVector3D temp = CVector3D(m_Nodes[index].Position - m_Nodes[index-1].Position) * 3.0f * (1.0f / m_Nodes[index-1].Distance.ToFloat());
|
|
|
|
|
return (temp - m_Nodes[index-1].Velocity) * 0.5f;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************** S N S **************************************************/
|
|
|
|
|
|
2016-04-14 09:36:12 -07:00
|
|
|
SNSpline::~SNSpline() = default;
|
|
|
|
|
|
2016-04-07 10:47:30 -07:00
|
|
|
void SNSpline::BuildSpline()
|
|
|
|
|
{
|
|
|
|
|
RNSpline::BuildSpline();
|
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
|
Smooth();
|
|
|
|
|
}
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
// smoothing filter.
|
|
|
|
|
void SNSpline::Smooth()
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount < 3)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
CVector3D newVel;
|
|
|
|
|
CVector3D oldVel = GetStartVelocity(0);
|
2026-07-19 10:30:56 -07:00
|
|
|
for (int i = 1; i < m_NodeCount-1; ++i)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
|
|
|
|
// Equation 12
|
2026-07-19 10:30:56 -07:00
|
|
|
newVel = GetEndVelocity(i) * m_Nodes[i].Distance.ToFloat() + GetStartVelocity(i) * m_Nodes[i-1].Distance.ToFloat();
|
|
|
|
|
newVel = newVel * (1 / (m_Nodes[i-1].Distance + m_Nodes[i].Distance).ToFloat());
|
|
|
|
|
m_Nodes[i-1].Velocity = oldVel;
|
2015-12-31 05:40:56 -08:00
|
|
|
oldVel = newVel;
|
|
|
|
|
}
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[m_NodeCount-1].Velocity = GetEndVelocity(m_NodeCount-1);
|
|
|
|
|
m_Nodes[m_NodeCount-2].Velocity = oldVel;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*********************************** T N S **************************************************/
|
|
|
|
|
|
2016-04-14 09:36:12 -07:00
|
|
|
TNSpline::~TNSpline() = default;
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
// as with RNSpline but use timePeriod in place of actual node spacing
|
|
|
|
|
// ie time period is time from last node to this node
|
2016-01-03 04:41:04 -08:00
|
|
|
void TNSpline::AddNode(const CFixedVector3D& pos, const CFixedVector3D& rotation, fixed timePeriod)
|
2006-04-23 16:14:18 -07:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount >= MAX_SPLINE_NODES)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0)
|
|
|
|
|
m_MaxDistance = fixed::Zero();
|
2015-12-31 05:40:56 -08:00
|
|
|
else
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[m_NodeCount-1].Distance = timePeriod;
|
|
|
|
|
m_MaxDistance += m_Nodes[m_NodeCount-1].Distance;
|
2015-12-31 05:40:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SplineData temp;
|
|
|
|
|
temp.Position = pos;
|
|
|
|
|
|
|
|
|
|
//make sure we don't end up using undefined numbers...
|
2016-01-03 04:41:04 -08:00
|
|
|
temp.Distance = fixed::Zero();
|
2015-12-31 05:40:56 -08:00
|
|
|
temp.Velocity = CVector3D(0.0f, 0.0f, 0.0f);
|
|
|
|
|
temp.Rotation = rotation;
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes.push_back(temp);
|
|
|
|
|
++m_NodeCount;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Inserts node before position
|
2025-06-03 05:13:41 -07:00
|
|
|
void TNSpline::InsertNode(const int index, const CFixedVector3D& pos, const CFixedVector3D& /*rotation*/,
|
|
|
|
|
fixed timePeriod)
|
2006-04-23 16:14:18 -07:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount >= MAX_SPLINE_NODES || index < 0 || index > m_NodeCount)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
2017-04-30 16:47:16 -07:00
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0)
|
|
|
|
|
m_MaxDistance = fixed::Zero();
|
2015-12-31 05:40:56 -08:00
|
|
|
else
|
2026-07-19 10:30:56 -07:00
|
|
|
m_MaxDistance += timePeriod;
|
2017-04-30 16:47:16 -07:00
|
|
|
|
2015-12-31 05:40:56 -08:00
|
|
|
SplineData temp;
|
|
|
|
|
temp.Position = pos;
|
2017-04-30 16:47:16 -07:00
|
|
|
temp.Distance = timePeriod;
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes.insert(m_Nodes.begin() + index, temp);
|
2017-07-01 08:49:59 -07:00
|
|
|
if (index > 0)
|
2026-07-19 10:30:56 -07:00
|
|
|
std::swap(m_Nodes[index].Distance, m_Nodes[index - 1].Distance);
|
|
|
|
|
++m_NodeCount;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
2015-12-31 05:40:56 -08:00
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
//Removes node at index
|
|
|
|
|
void TNSpline::RemoveNode(const int index)
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0 || index > m_NodeCount - 1)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
m_MaxDistance -= m_Nodes[index].Distance;
|
|
|
|
|
m_Nodes.erase(m_Nodes.begin() + index);
|
|
|
|
|
--m_NodeCount;
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
2016-04-07 10:47:30 -07:00
|
|
|
|
2016-01-03 04:41:04 -08:00
|
|
|
void TNSpline::UpdateNodeTime(const int index, fixed time)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0 || index > m_NodeCount - 1)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[index].Distance = time;
|
2006-07-05 20:17:44 -07:00
|
|
|
}
|
2016-04-07 10:47:30 -07:00
|
|
|
|
2016-01-03 04:41:04 -08:00
|
|
|
void TNSpline::UpdateNodePos(const int index, const CFixedVector3D& pos)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount == 0 || index > m_NodeCount - 1)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
m_Nodes[index].Position = pos;
|
2006-07-05 20:17:44 -07:00
|
|
|
}
|
2015-12-31 05:40:56 -08:00
|
|
|
|
2016-04-07 10:47:30 -07:00
|
|
|
void TNSpline::BuildSpline()
|
|
|
|
|
{
|
|
|
|
|
RNSpline::BuildSpline();
|
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
|
Smooth();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TNSpline::Smooth()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
|
|
|
{
|
|
|
|
|
SNSpline::Smooth();
|
|
|
|
|
Constrain();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2006-04-23 16:14:18 -07:00
|
|
|
void TNSpline::Constrain()
|
|
|
|
|
{
|
2026-07-19 10:30:56 -07:00
|
|
|
if (m_NodeCount < 3)
|
2015-12-31 05:40:56 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-07-19 10:30:56 -07:00
|
|
|
for (int i = 1; i < m_NodeCount-1; ++i)
|
2015-12-31 05:40:56 -08:00
|
|
|
{
|
|
|
|
|
// Equation 13
|
2026-07-19 10:30:56 -07:00
|
|
|
float r0 = (m_Nodes[i].Position - m_Nodes[i - 1].Position).Length().ToFloat() / m_Nodes[i-1].Distance.ToFloat();
|
|
|
|
|
float r1 = (m_Nodes[i+1].Position - m_Nodes[i].Position).Length().ToFloat() / m_Nodes[i].Distance.ToFloat();
|
|
|
|
|
m_Nodes[i].Velocity *= 4.0f*r0*r1 / ((r0 + r1)*(r0 + r1));
|
2015-12-31 05:40:56 -08:00
|
|
|
}
|
2006-04-23 16:14:18 -07:00
|
|
|
}
|
|
|
|
|
|