/* OneLoneCoder.com - Splines Part 2 "Bendier Wavier Curlier" - @Javidx9 Disclaimer ~~~~~~~~~~ I don't care what you use this for. It's intended to be educational, and perhaps to the oddly minded - a little bit of fun. Please hack this, change it and use it in any way you see fit. BUT, you acknowledge that I am not responsible for anything bad that happens as a result of your actions. However, if good stuff happens, I would appreciate a shout out, or at least give the blog some publicity for me. Cheers! Background ~~~~~~~~~~ Curvy things are always better. Splines are a nice way to approximate curves and loops for games. This video is the first of two parts demonstrating how Catmull-Rom splines can be implemented. Use Z + X to select a point and move it with the arrow keys Use A + S to move the agent around the spline loop Author ~~~~~~ Twitter: @javidx9 Blog: www.onelonecoder.com Video: ~~~~~~ https://youtu.be/9_aJGUTePYo https://youtu.be/DzjtU4WLYNs Last Updated: 25/09/2017 */ #include #include using namespace std; #include "olcConsoleGameEngine.h" struct sPoint2D { float x; float y; float length; }; struct sSpline { vector points; float fTotalSplineLength = 0.0f; sPoint2D GetSplinePoint(float t, bool bLooped = false) { int p0, p1, p2, p3; if (!bLooped) { p1 = (int)t + 1; p2 = p1 + 1; p3 = p2 + 1; p0 = p1 - 1; } else { p1 = (int)t; p2 = (p1 + 1) % points.size(); p3 = (p2 + 1) % points.size(); p0 = p1 >= 1 ? p1 - 1 : points.size() - 1; } t = t - (int)t; float tt = t * t; float ttt = tt * t; float q1 = -ttt + 2.0f*tt - t; float q2 = 3.0f*ttt - 5.0f*tt + 2.0f; float q3 = -3.0f*ttt + 4.0f*tt + t; float q4 = ttt - tt; float tx = 0.5f * (points[p0].x * q1 + points[p1].x * q2 + points[p2].x * q3 + points[p3].x * q4); float ty = 0.5f * (points[p0].y * q1 + points[p1].y * q2 + points[p2].y * q3 + points[p3].y * q4); return{ tx, ty }; } sPoint2D GetSplineGradient(float t, bool bLooped = false) { int p0, p1, p2, p3; if (!bLooped) { p1 = (int)t + 1; p2 = p1 + 1; p3 = p2 + 1; p0 = p1 - 1; } else { p1 = (int)t; p2 = (p1 + 1) % points.size(); p3 = (p2 + 1) % points.size(); p0 = p1 >= 1 ? p1 - 1 : points.size() - 1; } t = t - (int)t; float tt = t * t; float ttt = tt * t; float q1 = -3.0f * tt + 4.0f*t - 1; float q2 = 9.0f*tt - 10.0f*t; float q3 = -9.0f*tt + 8.0f*t + 1.0f; float q4 = 3.0f*tt - 2.0f*t; float tx = 0.5f * (points[p0].x * q1 + points[p1].x * q2 + points[p2].x * q3 + points[p3].x * q4); float ty = 0.5f * (points[p0].y * q1 + points[p1].y * q2 + points[p2].y * q3 + points[p3].y * q4); return{ tx, ty }; } float CalculateSegmentLength(int node, bool bLooped = false) { float fLength = 0.0f; float fStepSize = 0.005; sPoint2D old_point, new_point; old_point = GetSplinePoint((float)node, bLooped); for (float t = 0; t < 1.0f; t += fStepSize) { new_point = GetSplinePoint((float)node + t, bLooped); fLength += sqrtf((new_point.x - old_point.x)*(new_point.x - old_point.x) + (new_point.y - old_point.y)*(new_point.y - old_point.y)); old_point = new_point; } return fLength; } float GetNormalisedOffset(float p) { // Which node is the base? int i = 0; while (p > points[i].length) { p -= points[i].length; i++; } // The fractional is the offset return (float)i + (p / points[i].length); } }; class OneLoneCoder_Splines : public olcConsoleGameEngine { public: OneLoneCoder_Splines() { m_sAppName = L"Splines 2"; } private: sSpline path; int nSelectedPoint = 0; float fMarker = 0.0f; vector> vecModelCar; protected: // Called by olcConsoleGameEngine virtual bool OnUserCreate() { //path.points = { { 10, 41 },{ 40, 41 },{ 70, 41 },{ 100, 41 } }; //path.points = { { 10, 41 },{ 20, 41 },{ 30, 41 },{ 40, 41 },{ 50, 41 },{ 60, 41 },{ 70, 41 },{ 80, 41 },{ 90, 41 },{ 100, 41 } }; for (int i = 0; i < 10; i++) path.points.push_back({ 30.0f * sinf((float)i / 10.0f * 3.14159f * 2.0f) + ScreenWidth() / 2, 30.0f * cosf((float)i / 10.0f * 3.14159f * 2.0f) + ScreenHeight() / 2 }); vecModelCar = { { 1,1 },{ 1,3 },{ 3,0 },{ 0,-3 },{ -3,0 },{ -1, 3 },{ -1,1 } }; return true; } // Called by olcConsoleGameEngine virtual bool OnUserUpdate(float fElapsedTime) { // Clear Screen Fill(0, 0, ScreenWidth(), ScreenHeight(), L' '); // Handle input if (m_keys[L'X'].bReleased) { nSelectedPoint++; if (nSelectedPoint >= path.points.size()) nSelectedPoint = 0; } if (m_keys[L'Z'].bReleased) { nSelectedPoint--; if (nSelectedPoint < 0) nSelectedPoint = path.points.size() - 1; } if (m_keys[VK_LEFT].bHeld) path.points[nSelectedPoint].x -= 30.0f * fElapsedTime; if (m_keys[VK_RIGHT].bHeld) path.points[nSelectedPoint].x += 30.0f * fElapsedTime; if (m_keys[VK_UP].bHeld) path.points[nSelectedPoint].y -= 30.0f * fElapsedTime; if (m_keys[VK_DOWN].bHeld) path.points[nSelectedPoint].y += 30.0f * fElapsedTime; if (m_keys[L'A'].bHeld) fMarker -= 20.0f * fElapsedTime; if (m_keys[L'S'].bHeld) fMarker += 20.0f * fElapsedTime; if (fMarker >= (float)path.fTotalSplineLength) fMarker -= (float)path.fTotalSplineLength; if (fMarker < 0.0f) fMarker += (float)path.fTotalSplineLength; // Draw Spline for (float t = 0; t < (float)path.points.size(); t += 0.005f) { sPoint2D pos = path.GetSplinePoint(t, true); Draw(pos.x, pos.y); } path.fTotalSplineLength = 0.0f; // Draw Control Points for (int i = 0; i < path.points.size(); i++) { path.fTotalSplineLength += (path.points[i].length = path.CalculateSegmentLength(i, true)); Fill(path.points[i].x - 1, path.points[i].y - 1, path.points[i].x + 2, path.points[i].y + 2, PIXEL_SOLID, FG_RED); DrawString(path.points[i].x, path.points[i].y, to_wstring(i)); DrawString(path.points[i].x + 3, path.points[i].y, to_wstring(path.points[i].length)); } // Highlight control point Fill(path.points[nSelectedPoint].x - 1, path.points[nSelectedPoint].y - 1, path.points[nSelectedPoint].x + 2, path.points[nSelectedPoint].y + 2, PIXEL_SOLID, FG_YELLOW); DrawString(path.points[nSelectedPoint].x, path.points[nSelectedPoint].y, to_wstring(nSelectedPoint)); // Draw agent to demonstrate gradient float fOffset = path.GetNormalisedOffset(fMarker); sPoint2D p1 = path.GetSplinePoint(fOffset, true); sPoint2D g1 = path.GetSplineGradient(fOffset, true); float r = atan2(-g1.y, g1.x); DrawLine(5.0f * sin(r) + p1.x, 5.0f * cos(r) + p1.y, -5.0f * sin(r) + p1.x, -5.0f * cos(r) + p1.y, PIXEL_SOLID, FG_BLUE); DrawWireFrameModel(vecModelCar, p1.x, p1.y, -r + (3.14159f / 2.0f), 5.0f, FG_CYAN); DrawString(2, 2, to_wstring(fOffset)); DrawString(2, 4, to_wstring(fMarker)); return true; } }; int main() { OneLoneCoder_Splines demo; demo.ConstructConsole(160, 80, 10, 10); demo.Start(); return 0; }