diff --git a/engine/includes/components/spline.h b/engine/includes/components/spline.h index 375eab947..200b49dc5 100644 --- a/engine/includes/components/spline.h +++ b/engine/includes/components/spline.h @@ -38,11 +38,15 @@ class ENGINE_EXPORT Spline : public Component { void insertPoint(int index, const Point &point); void removePoint(int index); - Vector3 value(float position) const; + Vector3 value(float position); private: void normalizePath(); + float arcLengthFactor(int start, int end, float factor) const; + float computeArcLength(int start, int end, float t) const; + Vector3 bezierDerivative(int start, int end, float t) const; + void composeComponent() override; void loadUserData(const VariantMap& data) override; @@ -58,7 +62,11 @@ class ENGINE_EXPORT Spline : public Component { IndexVector m_indices; - bool m_closed; + float m_fullLength = 0.0f; + + bool m_closed = false; + + bool m_cacheDirty = true; }; diff --git a/engine/src/components/actor.cpp b/engine/src/components/actor.cpp index 46a0083bc..eb743abca 100644 --- a/engine/src/components/actor.cpp +++ b/engine/src/components/actor.cpp @@ -5,10 +5,6 @@ #include "resources/prefab.h" -#include "systems/resourcesystem.h" - -#include "commandbuffer.h" - #include namespace { diff --git a/engine/src/components/spline.cpp b/engine/src/components/spline.cpp index b74970965..c48c2adb5 100644 --- a/engine/src/components/spline.cpp +++ b/engine/src/components/spline.cpp @@ -14,8 +14,7 @@ namespace { \inmodule Components */ -Spline::Spline() : - m_closed(false) { +Spline::Spline() { } /*! @@ -29,8 +28,7 @@ bool Spline::closed() const { */ void Spline::setClosed(bool closed) { m_closed = closed; - - normalizePath(); + m_cacheDirty = true; } /*! Returns the number of points in the spline. @@ -57,7 +55,7 @@ void Spline::setPoint(int index, const Point &point) { m_points.push_back(point); } - normalizePath(); + m_cacheDirty = true; } /*! Inserts a \a point at the given \a index. @@ -65,7 +63,7 @@ void Spline::setPoint(int index, const Point &point) { void Spline::insertPoint(int index, const Point &point) { m_points.insert(std::next(m_points.begin(), index), point); - normalizePath(); + m_cacheDirty = true; } /*! Removes the point at the given \a index. @@ -73,47 +71,58 @@ void Spline::insertPoint(int index, const Point &point) { void Spline::removePoint(int index) { m_points.erase(std::next(m_points.begin(), index)); - normalizePath(); + m_cacheDirty = true; } /*! Returns the value of the spline at the given normalized \a position. */ -Vector3 Spline::value(float position) const { +Vector3 Spline::value(float position) { + normalizePath(); + position = CLAMP(position, 0.0f, 1.0f); - Vector3 result = (m_points.empty()) ? 0.0f : m_points.front().position; - if(m_points.size() >= 2) { - int begin = 0; - int end = 0; + if(m_points.size() < 2) { + return (m_points.empty()) ? Vector3(0.0f) : m_points.front().position; + } - for(; end < m_points.size() - 1; end++) { - if(position == m_points[end].normDistance) { - return m_points[end].position; - } - if(position > m_points[end].normDistance) { - begin = end; - } else if(position < m_points[end].normDistance) { - break; - } - } + int begin = 0; + int end = 0; - float dist = 1.0f; - if(m_closed && end == m_points.size()) { - end = 0; - } else { - dist = m_points[end].normDistance; + for(; end < m_points.size() - 1; end++) { + if(position == m_points[end].normDistance) { + return m_points[end].position; } + if(position > m_points[end].normDistance) { + begin = end; + } else if(position < m_points[end].normDistance) { + break; + } + } - float factor = (position - m_points[begin].normDistance) / (dist - m_points[begin].normDistance); - return CMIX(m_points[begin].position, m_points[begin].tangentOut, m_points[end].tangentIn, m_points[end].position, factor); + if(m_closed && end == m_points.size()) { + end = 0; } - return result; + float segmentStart = m_points[begin].normDistance; + float segmentEnd = (end == 0 && m_closed) ? 1.0f : m_points[end].normDistance; + float segmentLength = segmentEnd - segmentStart; + + if(segmentLength < 0.0001f) { + return m_points[begin].position; + } + + float factor = (position - segmentStart) / segmentLength; + factor = arcLengthFactor(begin, end, factor); + + return CMIX(m_points[begin].position, m_points[begin].tangentOut, + m_points[end].tangentIn, m_points[end].position, factor); } /*! \internal */ void Spline::normalizePath() { + if(!m_cacheDirty) return; + if(m_points.empty()) { return; } @@ -129,7 +138,7 @@ void Spline::normalizePath() { std::vector paths(m_points.size(), 0.0f); - float fullLength = 0.0f; + m_fullLength = 0.0f; int vertexIndex = 0; for(int i = 0; i < segments; i++) { @@ -146,7 +155,7 @@ void Spline::normalizePath() { if(step != 0 || i != 0) { float length = (m_vertices[index] - m_vertices[index - 1]).length(); paths[i] += length; - fullLength += length; + m_fullLength += length; } if(step != steps) { @@ -157,13 +166,87 @@ void Spline::normalizePath() { } } - float lastLength = 0.0f; - float dist = 0.0f; + float accumulated = 0.0f; for(int i = 0; i < m_points.size(); i++) { - dist += lastLength / fullLength; - m_points[i].normDistance = dist; - lastLength = paths[i]; + m_points[i].normDistance = accumulated / m_fullLength; + accumulated += paths[i]; } + + m_cacheDirty = false; +} +/*! + \internal +*/ +float Spline::arcLengthFactor(int start, int end, float factor) const { + float totalSegmentLength = computeArcLength(start, end, 1.0f); + float targetLength = factor * totalSegmentLength; + + float low = 0.0f; + float high = 1.0f; + float epsilon = 0.001f * totalSegmentLength; + + for(int i = 0; i < 12; i++) { + float t = (low + high) * 0.5f; + float currentLength = computeArcLength(start, end, t); + + if(fabs(currentLength - targetLength) < epsilon) { + return t; + } + + if(currentLength < targetLength) { + low = t; + } else { + high = t; + } + } + + return (low + high) * 0.5f; +} +/*! + \internal +*/ +float Spline::computeArcLength(int start, int end, float t) const { + const int gaussPoints = 5; + static const float gaussWeights[] = { + 0.236926885056189, 0.478628670499366, 0.568888888888889, + 0.478628670499366, 0.236926885056189 + }; + static const float gaussAbscissas[] = { + -0.906179845938664, -0.538469310105683, 0.0, + 0.538469310105683, 0.906179845938664 + }; + + float length = 0.0f; + float halfT = t * 0.5f; + + for(int i = 0; i < gaussPoints; i++) { + float u = halfT * (1.0f + gaussAbscissas[i]); + Vector3 derivative = bezierDerivative(start, end, u); + length += gaussWeights[i] * derivative.length(); + } + + return length * halfT; +} +/*! + \internal +*/ +Vector3 Spline::bezierDerivative(int start, int end, float t) const { + // P'(t) = 3(1-t)²(P1-P0) + 6(1-t)t(P2-P1) + 3t²(P3-P2) + + const Vector3 &p0 = m_points[start].position; + const Vector3 &p1 = m_points[start].tangentOut; + const Vector3 &p3 = m_points[end].position; + const Vector3 &p2 = m_points[end].tangentIn; + + float t2 = t * t; + float mt = 1.0f - t; + float mt2 = mt * mt; + + Vector3 dp0 = (p1 - p0) * 3.0f * mt2; + Vector3 dp1 = (p2 - p1) * 6.0f * mt * t; + Vector3 dp2 = (p3 - p2) * 3.0f * t2; + + return dp0 + dp1 + dp2; } /*! \internal @@ -175,7 +258,7 @@ void Spline::loadUserData(const VariantMap& data) { if(it != data.end()) { m_points.clear(); VariantList knots = it->second.value(); - for(auto k : knots) { + for(auto &k : knots) { Point point; VariantList fields = k.value(); @@ -191,7 +274,7 @@ void Spline::loadUserData(const VariantMap& data) { m_points.push_back(point); } - normalizePath(); + m_cacheDirty = true; } } /*! @@ -201,7 +284,7 @@ VariantMap Spline::saveUserData() const { VariantMap result = Component::saveUserData(); VariantList points; - for(auto knot : m_points) { + for(auto &knot : m_points) { VariantList fields; fields.push_back(knot.position); fields.push_back(knot.tangentIn); @@ -223,12 +306,14 @@ void Spline::composeComponent() { {Vector3( 1.0f, 0.0f, 0.0f), Vector3( 0.0f, 0.0f, 0.0f), Vector3( 2.0f, 0.0f, 0.0f)} }; - normalizePath(); + m_cacheDirty = true; } /*! \internal */ void Spline::drawGizmos() { + normalizePath(); + if(!m_vertices.empty()) { Transform *t = transform(); Gizmos::drawLines(m_vertices, m_indices, Vector4(0.0f, 0.0f, 0.0f, 1.0f), t->worldTransform()); @@ -238,6 +323,8 @@ void Spline::drawGizmos() { \internal */ void Spline::drawGizmosSelected() { + normalizePath(); + if(!m_vertices.empty()) { Transform *t = transform(); Gizmos::drawLines(m_vertices, m_indices, Vector4(1.0f, 1.0f, 0.0f, 1.0f), t->worldTransform()); diff --git a/thirdparty/next/inc/math/amath.h b/thirdparty/next/inc/math/amath.h index 5c83613d5..73846c7a6 100644 --- a/thirdparty/next/inc/math/amath.h +++ b/thirdparty/next/inc/math/amath.h @@ -34,16 +34,16 @@ static std::uniform_int_distribution dist(0, UINT32_MAX); #define DEG2RAD (PI / 180.0f) #define RAD2DEG (180.0f / PI) -#define SQR(a) (a * a) -#define QUAD(a) (a * a * a) +#define SQR(a) ((a) * (a)) +#define CUBE(a) ((a) * (a) * (a)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define CLAMP(x, min, max) ((x < min) ? min : (x > max) ? max : x) -#define MIX(a, b, f) (a * (1 - f) + b * f) -#define QMIX(a, b, c, f) (a * SQR((1 - f)) + b * 2 * f * (1 - f) + c * SQR(f)) -#define CMIX(a, b, c, d, f) (a * QUAD((1 - f)) + b * 3 * f * SQR((1 - f)) + c * 3 * SQR(f) * (1 - f) + d * QUAD(f)) +#define MIX(a, b, f) ((a) * (1 - (f)) + (b) * (f)) +#define QMIX(a, b, c, f) ((a) * SQR(1 - (f)) + (b) * 2 * (f) * (1 - (f)) + (c) * SQR(f)) +#define CMIX(a, b, c, d, f) ((a) * CUBE(1 - (f)) + (b) * 3 * (f) * SQR(1 - (f)) + (c) * 3 * SQR(f) * (1 - (f)) + (d) * CUBE(f)) #define RANGE(min, max) (min + ((max - min) * (static_cast(dist(mt)) / static_cast(UINT32_MAX)))) diff --git a/worldeditor/src/screens/scenecomposer/tools/spline/splinepanel.cpp b/worldeditor/src/screens/scenecomposer/tools/spline/splinepanel.cpp index 4dbed0bdf..f9159ff5d 100644 --- a/worldeditor/src/screens/scenecomposer/tools/spline/splinepanel.cpp +++ b/worldeditor/src/screens/scenecomposer/tools/spline/splinepanel.cpp @@ -38,12 +38,12 @@ void SplinePanel::setTool(SplineTool *tool) { } void SplinePanel::update() { - int index = m_tool->point(); + int index = m_tool->pointIndex(); Spline *spline = m_tool->spline(); if(spline && index > -1) { Spline::Point p = spline->point(index); - QRegularExpression reg("\\.?0+$"); + static const QRegularExpression reg("\\.?0+$"); ui->xEdit->setText(QString::number(p.position.x, 'f', 4).remove(reg)); ui->yEdit->setText(QString::number(p.position.y, 'f', 4).remove(reg)); ui->zEdit->setText(QString::number(p.position.z, 'f', 4).remove(reg)); @@ -53,7 +53,7 @@ void SplinePanel::update() { } void SplinePanel::onEditFinished() { - int index = m_tool->point(); + int index = m_tool->pointIndex(); Spline *spline = m_tool->spline(); if(spline && index > -1) { Spline::Point p = spline->point(index); diff --git a/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.cpp b/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.cpp index a9fe14df0..56d34abd4 100644 --- a/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.cpp +++ b/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.cpp @@ -23,14 +23,14 @@ SplineTool::SplineTool(ObjectController *controller) : m_spline(nullptr), m_splinePanel(nullptr), m_dotSize(0.003f), - m_point(-1), + m_index(-1), m_tangent(0), m_canceled(false) { } -void SplineTool::setPoint(int point) { - m_point = point; +void SplineTool::setPointIndex(int index) { + m_index = index; if(m_splinePanel) { m_splinePanel->update(); } @@ -55,14 +55,14 @@ void SplineTool::update(bool center, bool local, bool snap) { const float sense = Handles::s_Sense * 0.2f; - if(Input::isKeyDown(Input::KEY_DELETE) && m_point > -1) { + if(Input::isKeyDown(Input::KEY_DELETE) && m_index > -1) { m_controller->undoRedo()->push(new DeleteSplinePoint(this)); } for(int i = 0; i < spline->pointsCount(); i++) { Spline::Point point(spline->point(i)); - bool selected = (i == m_point); + bool selected = (i == m_index); Gizmos::drawBox(point.position, (camera - point.position).length() * m_dotSize, selected && (m_tangent == 0) ? m_dotColorSelected : m_dotColor, m); float distance = HandleTools::distanceToPoint(m, point.position, Handles::s_Mouse); @@ -114,7 +114,7 @@ void SplineTool::update(bool center, bool local, bool snap) { } } m_savedWorld = m_world; - spline->setPoint(m_point, point); + spline->setPoint(m_index, point); if(m_splinePanel) { m_splinePanel->update(); } @@ -124,7 +124,7 @@ void SplineTool::update(bool center, bool local, bool snap) { if(Input::isMouseButtonUp(Input::MOUSE_LEFT) && !isDrag) { if(!m_canceled) { - if(m_point > -1 || m_tangent || hoverPoint > -1 || hoverTangent) { + if(m_index > -1 || m_tangent || hoverPoint > -1 || hoverTangent) { m_controller->undoRedo()->push(new SelectSplinePoint(hoverPoint, hoverTangent, this)); } } else { @@ -138,16 +138,12 @@ void SplineTool::update(bool center, bool local, bool snap) { void SplineTool::beginControl() { EditorTool::beginControl(); - if(m_point > -1) { + if(m_index > -1) { Actor *actor = m_controller->selectList().begin()->object; if(actor) { Spline *spline = actor->getComponent(); if(spline) { - Spline::Point point(spline->point(m_point)); - - m_position = point.position; - m_positionIn = point.tangentIn; - m_positionOut = point.tangentOut; + m_point = spline->point(m_index); } } @@ -158,14 +154,14 @@ void SplineTool::beginControl() { void SplineTool::endControl() { EditorTool::beginControl(); - if(m_point > -1) { + if(m_index > -1) { Actor *actor = m_controller->selectList().begin()->object; if(actor) { Spline *spline = actor->getComponent(); if(spline) { - Spline::Point point(spline->point(m_point)); + Spline::Point point(spline->point(m_index)); - spline->setPoint(m_point, {m_position, m_positionIn, m_positionOut}); + spline->setPoint(m_index, m_point); m_controller->undoRedo()->push(new ChangeSplinePoint(point, this)); } @@ -176,12 +172,12 @@ void SplineTool::endControl() { void SplineTool::cancelControl() { EditorTool::beginControl(); - if(m_point > -1) { + if(m_index > -1) { Actor *actor = m_controller->selectList().begin()->object; if(actor) { Spline *spline = actor->getComponent(); if(spline) { - spline->setPoint(m_point, {m_position, m_positionIn, m_positionOut}); + spline->setPoint(m_index, m_point); } } @@ -227,25 +223,25 @@ Spline *SplineTool::spline() { return nullptr; } -SelectSplinePoint::SelectSplinePoint(int point, int tangent, SplineTool *tool, const TString &name, UndoCommand *group) : +SelectSplinePoint::SelectSplinePoint(int index, int tangent, SplineTool *tool, const TString &name, UndoCommand *group) : UndoCommand(name, group), m_tool(tool), - m_point(point), + m_index(index), m_tangent(tangent) { } void SelectSplinePoint::redo() { - int point = m_tool->point(); + int index = m_tool->pointIndex(); int tangent = m_tool->tangent(); m_tool->setTangent(m_tangent); if(m_tangent > 0) { - m_tool->setPoint(point); + m_tool->setPointIndex(index); } else { - m_tool->setPoint(m_point); + m_tool->setPointIndex(m_index); } - m_point = point; + m_index = index; m_tangent = tangent; } @@ -258,7 +254,7 @@ ChangeSplinePoint::ChangeSplinePoint(const Spline::Point &point, SplineTool *too void ChangeSplinePoint::redo() { Spline *spline = m_tool->spline(); if(spline) { - int index = m_tool->point(); + int index = m_tool->pointIndex(); Spline::Point point(spline->point(index)); spline->setPoint(index, m_point); m_tool->update(); @@ -279,18 +275,18 @@ void DeleteSplinePoint::undo() { if(spline) { spline->insertPoint(m_index, m_point); m_tool->setTangent(m_tangent); - m_tool->setPoint(m_index); + m_tool->setPointIndex(m_index); } } void DeleteSplinePoint::redo() { Spline *spline = m_tool->spline(); if(spline) { - m_index = m_tool->point(); + m_index = m_tool->pointIndex(); m_tangent = m_tool->tangent(); m_point = spline->point(m_index); spline->removePoint(m_index); m_tool->setTangent(0); - m_tool->setPoint(-1); + m_tool->setPointIndex(-1); } } @@ -305,13 +301,13 @@ void InsertSplinePoint::undo() { Spline *spline = m_tool->spline(); if(spline) { spline->removePoint(m_index); - m_tool->setPoint(m_index-1); + m_tool->setPointIndex(m_index - 1); } } void InsertSplinePoint::redo() { Spline *spline = m_tool->spline(); if(spline) { - m_index = m_tool->point(); + m_index = m_tool->pointIndex(); Spline::Point a = spline->point(m_index); if(m_index < (spline->pointsCount() - 1)) { // interpolation @@ -335,7 +331,7 @@ void InsertSplinePoint::redo() { } spline->insertPoint(m_index + 1, a); - m_tool->setPoint(m_index + 1); + m_tool->setPointIndex(m_index + 1); m_index += 1; } diff --git a/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.h b/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.h index a332105d5..f460e1d3b 100644 --- a/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.h +++ b/worldeditor/src/screens/scenecomposer/tools/spline/splinetool.h @@ -16,8 +16,8 @@ class SplineTool : public EditorTool { ObjectController *controller() { return m_controller; } - int point() const { return m_point; } - void setPoint(int point); + int pointIndex() const { return m_index; } + void setPointIndex(int index); int tangent() const { return m_tangent; } void setTangent(int tangent) { m_tangent = tangent; } @@ -43,15 +43,14 @@ class SplineTool : public EditorTool { QWidget *panel() override; private: + Spline::Point m_point; + Vector4 m_dotColor; Vector4 m_dotColorSelected; Vector4 m_lineColor; Vector3 m_world; Vector3 m_savedWorld; - Vector3 m_position; - Vector3 m_positionIn; - Vector3 m_positionOut; ObjectController *m_controller; @@ -61,7 +60,7 @@ class SplineTool : public EditorTool { float m_dotSize; - int m_point; + int m_index; int m_tangent; bool m_canceled; @@ -70,14 +69,14 @@ class SplineTool : public EditorTool { class SelectSplinePoint : public UndoCommand { public: - SelectSplinePoint(int point, int tangent, SplineTool *tool, const TString &name = QObject::tr("Select Spline Point").toStdString(), UndoCommand *group = nullptr); + SelectSplinePoint(int index, int tangent, SplineTool *tool, const TString &name = QObject::tr("Select Spline Point").toStdString(), UndoCommand *group = nullptr); void undo() override { redo(); } void redo() override; protected: SplineTool *m_tool; - int m_point; + int m_index; int m_tangent; };