The official distribution of olcConsoleGameEngine, a tool used in javidx9's YouTube videos and projects
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
videos/OneLoneCoder_Splines2.cpp

279 lines
7.1 KiB

/*
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 <iostream>
#include <string>
using namespace std;
#include "olcConsoleGameEngine.h"
struct sPoint2D
{
float x;
float y;
float length;
};
struct sSpline
{
vector<sPoint2D> 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<pair<float, float>> 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;
}