/* OneLoneCoder.com - Splines Part 1 "Bendy Wavy Curly" - @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 Last Updated: 06/08/2017 */ #include #include using namespace std; #include "olcConsoleGameEngine.h" struct sPoint2D { float x; float y; }; struct sSpline { vector points; 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 }; } }; class OneLoneCoder_Splines : public olcConsoleGameEngine { public: OneLoneCoder_Splines() { m_sAppName = L"Splines"; } private: sSpline path; int nSelectedPoint = 0; float fMarker = 0.0f; 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 } }; 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 -= 5.0f * fElapsedTime; if (m_keys[L'S'].bHeld) fMarker += 5.0f * fElapsedTime; if (fMarker >= (float)path.points.size()) fMarker -= (float)path.points.size(); if (fMarker < 0.0f) fMarker += (float)path.points.size(); // 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); } // Draw Control Points for (int i = 0; i < path.points.size(); i++) { 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)); } // 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 sPoint2D p1 = path.GetSplinePoint(fMarker, true); sPoint2D g1 = path.GetSplineGradient(fMarker, 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); return true; } }; int main() { OneLoneCoder_Splines demo; demo.ConstructConsole(160, 80, 10, 10); demo.Start(); return 0; }