This commit is contained in:
2024-02-27 10:10:02 +01:00
parent c0aea669c7
commit 547809c898
131 changed files with 65609 additions and 0 deletions

222
Engine/BaseGame.cpp Normal file
View File

@@ -0,0 +1,222 @@
#include "base.h"
#include <iostream>
#include <algorithm>
#include <chrono>
#include "BaseGame.h"
BaseGame::BaseGame(const Window& window)
: m_Window{ window }
, m_Viewport{ 0,0,window.width,window.height }
, m_pWindow{ nullptr }
, m_pContext{ nullptr }
, m_Initialized{ false }
, m_MaxElapsedSeconds{ 0.1f }
{
InitializeGameEngine();
}
BaseGame::~BaseGame()
{
CleanupGameEngine();
}
void BaseGame::InitializeGameEngine()
{
// disable console close window button
#ifdef _WIN32
HWND hwnd = GetConsoleWindow();
HMENU hmenu = GetSystemMenu(hwnd, FALSE);
EnableMenuItem(hmenu, SC_CLOSE, MF_GRAYED);
#endif
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO /*| SDL_INIT_AUDIO*/) < 0)
{
std::cerr << "BaseGame::Initialize( ), error when calling SDL_Init: " << SDL_GetError() << std::endl;
return;
}
// Use OpenGL 2.1
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
// Create window
m_pWindow = SDL_CreateWindow(
m_Window.title.c_str(),
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
int(m_Window.width),
int(m_Window.height),
SDL_WINDOW_OPENGL);
if (m_pWindow == nullptr)
{
std::cerr << "BaseGame::Initialize( ), error when calling SDL_CreateWindow: " << SDL_GetError() << std::endl;
return;
}
// Create OpenGL context
m_pContext = SDL_GL_CreateContext(m_pWindow);
if (m_pContext == nullptr)
{
std::cerr << "BaseGame::Initialize( ), error when calling SDL_GL_CreateContext: " << SDL_GetError() << std::endl;
return;
}
// Set the swap interval for the current OpenGL context,
// synchronize it with the vertical retrace
if (m_Window.isVSyncOn)
{
if (SDL_GL_SetSwapInterval(1) < 0)
{
std::cerr << "BaseGame::Initialize( ), error when calling SDL_GL_SetSwapInterval: " << SDL_GetError() << std::endl;
return;
}
}
else
{
SDL_GL_SetSwapInterval(0);
}
// Set the Projection matrix to the identity matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Set up a two-dimensional orthographic viewing region.
glOrtho(0, m_Window.width, 0, m_Window.height, -1, 1); // y from bottom to top
// Set the viewport to the client window area
// The viewport is the rectangular region of the window where the image is drawn.
glViewport(0, 0, int(m_Window.width), int(m_Window.height));
// Set the Modelview matrix to the identity matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Enable color blending and use alpha blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Initialize PNG loading
/*
int imgFlags = IMG_INIT_PNG;
if ( !( IMG_Init( imgFlags ) & imgFlags ) )
{
std::cerr << "BaseGame::Initialize( ), error when calling IMG_Init: " << IMG_GetError( ) << std::endl;
return;
}
*/
// Initialize SDL_ttf
if (TTF_Init() == -1)
{
std::cerr << "BaseGame::Initialize( ), error when calling TTF_Init: " << TTF_GetError() << std::endl;
return;
}
//Initialize SDL_mixer
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0)
{
std::cerr << "BaseGame::Initialize( ), error when calling Mix_OpenAudio: " << Mix_GetError() << std::endl;
return;
}
m_Initialized = true;
}
void BaseGame::Run()
{
if (!m_Initialized)
{
std::cerr << "BaseGame::Run( ), BaseGame not correctly initialized, unable to run the BaseGame\n";
std::cin.get();
return;
}
// Main loop flag
bool quit{ false };
// Set start time
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
//The event loop
SDL_Event e{};
while (!quit)
{
// Poll next event from queue
while (SDL_PollEvent(&e) != 0)
{
// Handle the polled event
switch (e.type)
{
case SDL_QUIT:
quit = true;
break;
case SDL_KEYDOWN:
this->ProcessKeyDownEvent(e.key);
break;
case SDL_KEYUP:
this->ProcessKeyUpEvent(e.key);
break;
case SDL_MOUSEMOTION:
e.motion.y = int(m_Window.height) - e.motion.y;
this->ProcessMouseMotionEvent(e.motion);
break;
case SDL_MOUSEBUTTONDOWN:
e.button.y = int(m_Window.height) - e.button.y;
this->ProcessMouseDownEvent(e.button);
break;
case SDL_MOUSEBUTTONUP:
e.button.y = int(m_Window.height) - e.button.y;
this->ProcessMouseUpEvent(e.button);
break;
}
}
if (!quit)
{
// Get current time
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
// Calculate elapsed time
float elapsedSeconds = std::chrono::duration<float>(t2 - t1).count();
// Update current time
t1 = t2;
// Prevent jumps in time caused by break points
elapsedSeconds = std::min(elapsedSeconds, m_MaxElapsedSeconds);
// Call the BaseGame object 's Update function, using time in seconds (!)
this->Update(elapsedSeconds);
// Draw in the back buffer
this->Draw();
// Update screen: swap back and front buffer
SDL_GL_SwapWindow(m_pWindow);
}
}
}
void BaseGame::CleanupGameEngine()
{
SDL_GL_DeleteContext(m_pContext);
SDL_DestroyWindow(m_pWindow);
m_pWindow = nullptr;
//Quit SDL subsystems
Mix_Quit();
TTF_Quit();
SDL_Quit();
// enable console close window button
#ifdef _WIN32
HWND hwnd = GetConsoleWindow();
HMENU hmenu = GetSystemMenu(hwnd, FALSE);
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED);
#endif
}

72
Engine/BaseGame.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include "structs.h"
#include "SDL.h"
// https://BaseGameprogrammingpatterns.com/subclass-sandbox.html
class BaseGame
{
public:
explicit BaseGame( const Window& window );
BaseGame( const BaseGame& other ) = delete;
BaseGame& operator=( const BaseGame& other ) = delete;
BaseGame(BaseGame&& other) = delete;
BaseGame& operator=(BaseGame&& other) = delete;
virtual ~BaseGame( );
void Run( );
virtual void Update(float elapsedSec)
{
}
virtual void Draw() const
{
}
// Event handling
virtual void ProcessKeyDownEvent(const SDL_KeyboardEvent& e)
{
}
virtual void ProcessKeyUpEvent(const SDL_KeyboardEvent& e)
{
}
virtual void ProcessMouseMotionEvent(const SDL_MouseMotionEvent& e)
{
}
virtual void ProcessMouseDownEvent(const SDL_MouseButtonEvent& e)
{
}
virtual void ProcessMouseUpEvent(const SDL_MouseButtonEvent& e)
{
}
const Rectf& GetViewPort() const
{
return m_Viewport;
}
private:
// DATA MEMBERS
// The window properties
const Window m_Window;
const Rectf m_Viewport;
// The window we render to
SDL_Window* m_pWindow;
// OpenGL context
SDL_GLContext m_pContext;
// Init info
bool m_Initialized;
// Prevent timing jumps when debugging
const float m_MaxElapsedSeconds;
// FUNCTIONS
void InitializeGameEngine( );
void CleanupGameEngine( );
};

173
Engine/Engine.vcxproj Normal file
View File

@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BaseGame.cpp" />
<ClCompile Include="Matrix2x3.cpp" />
<ClCompile Include="SoundEffect.cpp" />
<ClCompile Include="SoundStream.cpp" />
<ClCompile Include="structs.cpp" />
<ClCompile Include="SVGParser.cpp" />
<ClCompile Include="Texture.cpp" />
<ClCompile Include="utils.cpp" />
<ClCompile Include="Vector2f.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="BaseGame.h" />
<ClInclude Include="Matrix2x3.h" />
<ClInclude Include="base.h" />
<ClInclude Include="SoundEffect.h" />
<ClInclude Include="SoundStream.h" />
<ClInclude Include="structs.h" />
<ClInclude Include="SVGParser.h" />
<ClInclude Include="Texture.h" />
<ClInclude Include="Transform.h" />
<ClInclude Include="utils.h" />
<ClInclude Include="Vector2f.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{5adab721-cb6c-4ef5-89eb-20ec51a13cfc}</ProjectGuid>
<RootNamespace>Engine</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>..\Libraries\SDLTtf\SDL2_ttf-2.20.2\include;..\Libraries\SDLImage\SDL2_image-2.6.3\include;..\Libraries\SDLMixer\SDL2_mixer-2.6.3\include;..\Libraries\SDLMain\SDL2-2.26.3\include;$(IncludePath)</IncludePath>
<LibraryPath>..\Libraries\SDLTtf\SDL2_ttf-2.20.2\lib\x64;..\Libraries\SDL2_mixer-2.6.3\lib\x64;..\Libraries\SDLImage\SDL2_image-2.6.3\lib\x64;..\Libraries\SDLMain\SDL2-2.26.3\lib\x64;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>EnableAllWarnings</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<TreatWarningAsError>false</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BaseGame.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Matrix2x3.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SoundEffect.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SoundStream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="structs.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SVGParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Texture.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Vector2f.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="BaseGame.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Matrix2x3.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="base.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SoundEffect.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SoundStream.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="structs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SVGParser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Texture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="utils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Vector2f.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Transform.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

208
Engine/Matrix2x3.cpp Normal file
View File

@@ -0,0 +1,208 @@
#include "base.h"
#include <cmath>
#include <string>
#include <iostream>
#include "Matrix2x3.h"
Matrix2x3::Matrix2x3(Vector2f dirX, Vector2f dirY, Vector2f orig)
: dirX{ dirX }, dirY{ dirY }, orig{ orig }
{}
Matrix2x3::Matrix2x3(float e1X, float e1Y, float e2X, float e2Y, float oX, float oY)
: Matrix2x3{ Vector2f{e1X, e1Y}, Vector2f{e2X, e2Y}, Vector2f{oX, oY} }
{}
Vector2f Matrix2x3::Transform(const Vector2f& vector) const
{
return Vector2f{ vector.x * dirX + vector.y * dirY };
}
Point2f Matrix2x3::Transform(const Point2f& point) const
{
Vector2f v{ Transform( Vector2f{ point } ) + orig };
return v.ToPoint2f();
}
std::vector<Point2f> Matrix2x3::Transform(const Rectf & r) const
{
std::vector<Point2f> vertices{ 4 };
vertices[0] = Transform( Point2f{ r.left, r.bottom } );
vertices[1] = Transform( Point2f{ r.left, r.bottom + r.height } );
vertices[2] = Transform( Point2f{ r.left + r.width, r.bottom + r.height } );
vertices[3] = Transform( Point2f{ r.left + r.width, r.bottom } );
return vertices;
}
void Matrix2x3::Transform( const Rectf& r, Point2f* transVertices ) const
{
transVertices[0] = Transform( Point2f{ r.left, r.bottom } );
transVertices[1] = Transform( Point2f{ r.left, r.bottom + r.height } );
transVertices[2] = Transform( Point2f{ r.left + r.width, r.bottom + r.height } );
transVertices[3] = Transform( Point2f{ r.left + r.width, r.bottom } );
}
std::vector<Point2f> Matrix2x3::Transform( const std::vector<Point2f>& vertices ) const
{
size_t nrVectices{ vertices.size( ) };
std::vector<Point2f> transformedVertices{ nrVectices };
for ( size_t idx{ 0 }; idx < nrVectices; ++idx )
{
transformedVertices[idx] = Transform( vertices[idx] );
}
return transformedVertices;
}
void Matrix2x3::Transform( const std::vector<Point2f>& vertices, Point2f* transVertices ) const
{
Transform( vertices.data( ), transVertices, vertices.size( ) );
}
void Matrix2x3::Transform( const Point2f* vertices, Point2f* transVertices, size_t nrVertices ) const
{
for ( size_t idx{ 0 }; idx < nrVertices; ++idx )
{
transVertices[idx] = Transform( vertices[idx] );
}
}
float Matrix2x3::Determinant() const
{
return dirX.x * dirY.y - dirX.y * dirY.x;
}
Matrix2x3 Matrix2x3::Inverse() const
{
//Calculate Determinant
float det = Determinant();
//1)calculate matrix of minors
//2)Use the alternating law of signs to produce the matrix of cofactors
//3)Transpose
//4)the inverse matrix is 1/Determinant * the resulting matrix
return Matrix2x3{
Vector2f(+dirY.y, -dirX.y) / det,
Vector2f(-dirY.x, +dirX.x) / det,
Vector2f(dirY.x * orig.y - dirY.y * orig.x, -(dirX.x * orig.y - dirX.y * orig.x)) / det
};
}
bool Matrix2x3::Equals(const Matrix2x3& other, float epsilon ) const
{
return dirX.Equals(other.dirX, epsilon) &&
dirY.Equals(other.dirY, epsilon) &&
orig.Equals(other.orig, epsilon);
}
std::string Matrix2x3::ToString() const
{
return std::string( "Matrix2x3( x( ") +
std::to_string(dirX.x) + ", " + std::to_string( dirX.y )
+ " ), y( " + std::to_string( dirY.x ) + ", " + std::to_string( dirY.y )
+ " ), orig( " + std::to_string( orig.x ) + ", " + std::to_string( orig.y ) + " ) )";
}
void Matrix2x3::SetAsIdentity()
{
dirX = Vector2f{1, 0};
dirY = Vector2f{0, 1};
orig = Vector2f{0, 0};
}
void Matrix2x3::SetAsRotate(float degrees)
{
float radians = degrees * 3.1415926535f / 180;
dirX = Vector2f{ cos( radians ), sin( radians ) };
dirY = Vector2f{ -sin( radians ), cos( radians ) };
orig = Vector2f{ 0, 0 };
}
void Matrix2x3::SetAsTranslate(float tx, float ty)
{
dirX = Vector2f{ 1, 0 };
dirY = Vector2f{ 0, 1 };
orig = Vector2f{ tx, ty };
}
void Matrix2x3::SetAsTranslate(Vector2f pt)
{
dirX = Vector2f{ 1, 0 };
dirY = Vector2f{ 0, 1 };
orig = Vector2f{ pt.x, pt.y };
}
void Matrix2x3::SetAsScale(float scaleX, float scaleY)
{
dirX = Vector2f{ scaleX, 0 };
dirY = Vector2f{ 0, scaleY };
orig = Vector2f{ 0, 0 };
}
void Matrix2x3::SetAsScale(float scale)
{
SetAsScale(scale, scale);
}
Matrix2x3 Matrix2x3::CreateRotationMatrix(float degrees)
{
float radians = degrees * 3.1415926535f / 180;
return Matrix2x3( Vector2f{ cos( radians ), sin( radians ) }, Vector2f{ -sin(radians), cos( radians ) }, Vector2f{} );
}
Matrix2x3 Matrix2x3::CreateIdentityMatrix()
{
return Matrix2x3( Vector2f{ 1, 0 }, Vector2f{ 0, 1 }, Vector2f{} );
}
Matrix2x3 Matrix2x3::CreateScalingMatrix(float scale)
{
return CreateScalingMatrix(scale, scale);
}
Matrix2x3 Matrix2x3::CreateScalingMatrix(Vector2f scaleVector)
{
return CreateScalingMatrix(scaleVector.x, scaleVector.y);
}
Matrix2x3 Matrix2x3::CreateScalingMatrix(float scaleX, float scaleY)
{
return Matrix2x3( Vector2f{ scaleX, 0 }, Vector2f{ 0, scaleY }, Vector2f{} );
}
Matrix2x3 Matrix2x3::CreateTranslationMatrix(Vector2f origin)
{
return Matrix2x3( Vector2f{ 1, 0 }, Vector2f{ 0, 1 }, origin );
}
Matrix2x3 Matrix2x3::CreateTranslationMatrix(float tx, float ty)
{
return CreateTranslationMatrix( Vector2f{ tx, ty } );
}
// Operator overloading functionality
bool operator==(const Matrix2x3& lhs, const Matrix2x3& rhs)
{
return lhs.Equals(rhs);
}
bool operator!=(const Matrix2x3& lhs, const Matrix2x3& rhs)
{
return !(lhs == rhs);
}
Matrix2x3 operator*(const Matrix2x3& lhs, const Matrix2x3& rhs)
{
return Matrix2x3{
Vector2f{ rhs.dirX.x * lhs.dirX.x + rhs.dirX.y * lhs.dirY.x, rhs.dirX.x * lhs.dirX.y + rhs.dirX.y * lhs.dirY.y },
Vector2f{ rhs.dirY.x * lhs.dirX.x + rhs.dirY.y * lhs.dirY.x, rhs.dirY.x * lhs.dirX.y + rhs.dirY.y * lhs.dirY.y },
Vector2f{ rhs.orig.x * lhs.dirX.x + rhs.orig.y * lhs.dirY.x + lhs.orig.x, rhs.orig.x * lhs.dirX.y + rhs.orig.y * lhs.dirY.y + lhs.orig.y }
};
}
std::ostream& operator<<(std::ostream& os, const Matrix2x3& matrix )
{
os << matrix.ToString( );
return os;
}

125
Engine/Matrix2x3.h Normal file
View File

@@ -0,0 +1,125 @@
#pragma once
#include <vector>
#include "Vector2f.h"
struct Matrix2x3 final
{
// -------------------------
// Constructors
// -------------------------
// Default constructor results in a unity matrix
explicit Matrix2x3( Vector2f dirX = Vector2f{ 1, 0 }, Vector2f dirY = Vector2f{ 0, 1 }, Vector2f origTrans = Vector2f{ 0, 0 } );
// Constructor, using floats, all required
explicit Matrix2x3( float e1X, float e1Y, float e2X, float e2Y, float oX, float oY );
// -------------------------
// General Methods
// -------------------------
// Transform a vector by this matrix, no translation
// Vector2f vTransformed = mat.Transform(v);
Vector2f Transform( const Vector2f& v ) const;
// Transform a point by this matrix, including translation
// Point2f pTransformed = mat.Transform(p);
Point2f Transform( const Point2f& p ) const;
// Transform a Rectf by this matrix, including translation
// std::vector<Point2f> transformedVertices = mat.Transform(r);
std::vector<Point2f> Transform( const Rectf& r ) const;
// Transform a Rectf by this matrix, including translation
// Pass pointer to resulting array (size should be at least 4)
// Point2f transformedVertices[4];
// Transform(r, transformedVertices);
void Transform( const Rectf& r, Point2f* transVertices ) const;
// Transform a Polygon by this matrix, including translation
// std::vector<Point2f> transformedVertices = mat.Transform(vertices);
std::vector<Point2f> Transform( const std::vector<Point2f>& vertices ) const;
// Transform a Polygon by this matrix, including translation
// Pass pointer to resulting array (size should be at least size of polygon)
// Point2f transformedVertices[vertices.size()];
// Transform(vertices, transformedVertices);
void Transform( const std::vector<Point2f>& vertices, Point2f* transVertices ) const;
// Transform a Polygon by this matrix, including translation
// Point2f transformedVertices[nrVertices];
// Transform(vertices, nrVertices, transformedVertices);
void Transform( const Point2f* vertices, Point2f* transVertices, size_t nrVertices ) const;
// Calculate the determinant
float Determinant( ) const;
// Calculate the inverse matrix
Matrix2x3 Inverse( ) const;
// Are two matrices equal within a threshold?
// mat1.Equals(mat2)
bool Equals( const Matrix2x3& other, float epsilon = 0.001f ) const;
// Creates a string containing a text representation of the values of the matrix
std::string ToString( ) const;
// Converts this matrix into a Identity matrix
void SetAsIdentity( );
// Converts this matrix into a Rotate matrix
void SetAsRotate( float degrees );
// Converts this matrix into a Translation matrix
void SetAsTranslate( float tx, float ty );
// Converts this matrix into a Translation matrix
void SetAsTranslate( Vector2f pt );
// Converts this matrix into a Scale matrix
void SetAsScale( float sx, float sy );
// Converts this matrix into a Scale matrix
void SetAsScale( float s );
// -------------------------------------------
// Static Matrix2x3 object creation methods
// -------------------------------------------
// Instantiate a rotation matrix:
// Matrix matRot = Matrix::Rotation(45.0f);
static Matrix2x3 CreateRotationMatrix( float degrees );
// Instantiate an identity matrix:
// Matrix2x3 matId = Matrix2x3::Identity();
static Matrix2x3 CreateIdentityMatrix( );
// Instantiate a scale matrix:
// Matrix matScale = Matrix::Scaling(2.0f);
static Matrix2x3 CreateScalingMatrix( float scale );
// Instantiate a scale matrix:
// Matrix matScale = Matrix::Scaling(2.0f,-3.0f);
static Matrix2x3 CreateScalingMatrix( float scaleX, float scaleY );
// Instantiate a scale matrix:
// Matrix matScale = Matrix::Scaling( Vector2f(2.0f,-3.0f) );
static Matrix2x3 CreateScalingMatrix( Vector2f scaleVector );
// Instantiate a translation matrix:
// Matrix matTrans = Matrix::Translation( Vector2f(2.0f,3.0f) );
static Matrix2x3 CreateTranslationMatrix( Vector2f origin );
// Instantiate a translation matrix:
// Matrix matTrans = Matrix::Translation(2.0f, 3.0f);
static Matrix2x3 CreateTranslationMatrix( float tx, float ty );
// -------------------------
// Datamembers
// -------------------------
Vector2f dirX; // The first matrix vector (the "x-axis"), 1st column
Vector2f dirY; // The second matrix vector (the "y-axis"), second column
Vector2f orig; // The origin of the coordinate matrix (the "translation"), third column
};
// -------------------------
// Operators
// -------------------------
// Are two matrices exactly equal?
// mat1 == mat2
bool operator==( const Matrix2x3& lhs, const Matrix2x3& rhs );
// Are two matrices exactly unequal?
// mat1 != mat2
bool operator!=( const Matrix2x3& lhs, const Matrix2x3& rhs );
// Multiply matrices
// Matrix2x3 matProduct {mat1 * mat2};
Matrix2x3 operator*( const Matrix2x3& lhs, const Matrix2x3& rhs );
// Send matrix to output stream
// std::cout << mat;
std::ostream& operator<<( std::ostream& os, const Matrix2x3& matrix );

479
Engine/SVGParser.cpp Normal file
View File

@@ -0,0 +1,479 @@
#include "base.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include "SVGParser.h"
bool SVGParser::GetVerticesFromSvgFile( const std::string& filePath, std::vector<std::vector<Point2f>> &vertices )
{
// Open the file
std::ifstream svgStream( filePath.c_str( ) );
if ( !svgStream )
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), failed to load vertices from file " << filePath << std::endl;
return false;
}
// Read the file
std::string svgLine;
std::string svgString;
while ( !svgStream.eof() )
{
getline( svgStream, svgLine );
svgString += svgLine;
}
// close the file
svgStream.close( );
// Cleanup
RemoveSpaces( svgString );
if ( ! GetVerticesFromSvgString(svgString, vertices))
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), malformed or unsupported information in file " << filePath << std::endl;
return false;
}
// Get the viewbox rect to flip the y coordinates. SVG has org topleft, the framework bottom left.
std::string viewBoxValue;
if ( ! GetAttributeValue(svgString, "viewBox", viewBoxValue))
{
std::cerr << "SVGParser::GetVerticesFromSvgFile(..), no viewbox information found in " << filePath << std::endl;
vertices.clear();
return false;
}
Rectf viewBox{};
std::stringstream sstream{ viewBoxValue };
sstream >> viewBox.left >> viewBox.bottom >> viewBox.width >> viewBox.height;
//std::vector<std::vector<Point2f>> vertices{ vertices };
for (size_t i{}; i < vertices.size(); ++i)
{
// flip the y coordinate
for (Point2f& p : vertices[i])
{
p.y = viewBox.height - p.y;
}
}
return true;
}
void SVGParser::RemoveSpaces( std::string& svgString )
{
// Remove spaces before and = chars
size_t foundPos{};
while ( ( foundPos = svgString.find( " =" ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "=" );
}
// Remove spaces after and = chars
while ( ( foundPos = svgString.find( "= " ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "=" );
}
//std::cout << svgString.size( ) << "\n";
// Remove spaces before and > chars
while ( ( foundPos = svgString.find( " >" ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, ">" );
}
// Remove spaces after and < chars
while ( ( foundPos = svgString.find( "< " ) ) != std::string::npos )
{
svgString.replace( foundPos, 2, "<" );
}
//std::cout << svgString << "\n";
}
bool SVGParser::GetVerticesFromSvgString(std::string& svgString, std::vector<std::vector<Point2f>> &vertices)
{
size_t startPosContent{};
size_t endPosContent{};
std::string pathElementContent;
// Get path element until none has been found anymore
while (GetElementContent(svgString, "path", pathElementContent, startPosContent, endPosContent))
{
// Vector of Point2f to fill with a path's vertices
std::vector<Point2f> verticesVector;
// Get d attribute value
std::string pathDataValue{};
if (!GetAttributeValue(pathElementContent, " d", pathDataValue))
{
std::cerr << "SVGParser::GetVerticesFromSvgString(..), path element doesn't contain a d-attribute.\n ";
vertices.clear();
return false;
}
// Process the path data
if (!GetVerticesFromPathData(pathDataValue, verticesVector))
{
std::cerr << "SVGParser::GetVerticesFromSvgString(..), error while extracting vertices from the path. \n";
vertices.clear();
return false;
}
if (verticesVector.size() == 0)
{
std::cerr << "Empty verticesVector in GetVerticesFromSvgString(..), no vertices found in the path element" << std::endl;
vertices.clear();
return false;
}
// DEBUG: Read vertices of current vector
//for (Point2f& p : verticesVector)
//{
// std::cout << p.x << " " << p.y << std::endl;
//}
// Add the vector to the vector array
vertices.push_back(verticesVector);
}
if (vertices.size() == 0)
{
std::cerr << "Empty vertices in GetVerticesFromSvgString(..), no path element(s) found" << std::endl;
return false;
}
return true;
}
bool SVGParser::GetVerticesFromPathData( const std::string& pathData, std::vector<Point2f> &vertices )
{
std::string pathCmdChars( ( "mMZzLlHhVvCcSsQqTtAa" ) );
// Use streamstream for parsing
std::stringstream ss( pathData );
char cmd{ 0 };
Point2f cursor{};
Point2f startPoint{};//At the end of the z command, the new current point is set to the initial point of the current subpath.
bool isOpen = true;
// http://www.w3.org/TR/SVG/paths.html#Introduction
Point2f vertex{};
char pathCommand{};
ss >> pathCommand;
while ( !ss.eof( ) )
{
//if ( strchr( pathCmdChars.c_str( ), pathCommand ) != 0 )
// if the command is a valid command letter, proceed
if(pathCmdChars.find(pathCommand) != std::string::npos)
{
cmd = pathCommand;
}
else
{
// if not a command, then put it back
// Attempts to decrease the current location in the stream by one character,
// making the last character extracted from the stream once again available to be extracted by input operation
ss.putback( pathCommand );
}
switch ( cmd )
{
case ( 'Z' ):
case ( 'z' ):
isOpen = true;
break;
case ( 'M' ):
case ( 'm' ):
if ( isOpen )
{
cursor = FirstSvgPoint( ss, cursor, cmd, isOpen, true );
startPoint = cursor;
vertices.push_back( cursor );
isOpen = false;
break;
}
// Fallthrough when isOpen
case ( 'L' )://lineto
case ( 'l' ):
vertex = NextSvgPoint( ss, cursor, cmd, isOpen, true );
vertices.push_back( vertex );
break;
case ( 'h' ): // horizontal lineto
case ( 'H' ):
vertex = NextSvgCoordX( ss, cursor, cmd, isOpen );
vertices.push_back( vertex );
break;
case ( 'v' ): // vertical lineto
case ( 'V' ):
vertex = NextSvgCoordY( ss, cursor, cmd, isOpen );
vertices.push_back( vertex );
break;
case ( 'C' ):
case ( 'c' ):
std::cerr << "SVGParser::GetVerticesFromPathData, beziers are not supported.\nHave another look at the guide, or select all nodes in inkscape and press shift + L\n";
return false;
break;
default:
std::cerr << "SVGParser::GetVerticesFromPathData, " << cmd << " is not a supported SVG command";
return false;
break;
}
// Next command
ss >> pathCommand;
}
return true;
}
bool SVGParser::GetElementContent( const std::string& svgText, const std::string& elementName, std::string& elementContent, size_t& startContentPos, size_t& endContentPos )
{
// 2 possible formats
// <ElementName> content <ElementName/>
// Temporary start and end positions for checking
size_t tempStartPos{ startContentPos };
size_t tempEndPos{ endContentPos };
std::string startElement = "<" + elementName + ">";
std::string endElement = "<" + elementName + "/>";
if ( (tempStartPos = svgText.find( startElement )) != std::string::npos )
{
tempStartPos += startElement.length( );
if ( (tempEndPos = svgText.find( endElement ) ) != std::string::npos )
{
elementContent = svgText.substr(tempStartPos, tempEndPos - tempStartPos);
startContentPos = tempStartPos;
endContentPos = tempEndPos;
return true;
}
else
{
return false;
}
}
// or
// <ElementName content />
tempStartPos = startContentPos;
tempEndPos = endContentPos;
startElement = "<" + elementName;
endElement = "/>";
if ( (tempStartPos = svgText.find( startElement, tempStartPos) ) != std::string::npos )
{
tempStartPos += startElement.length( );
if ( (tempEndPos = svgText.find( endElement ) ) != std::string::npos )
{
elementContent = svgText.substr(tempStartPos, tempEndPos - tempStartPos);
startContentPos = tempStartPos;
endContentPos = tempEndPos;
return true;
}
}
return false;
}
bool SVGParser::GetAttributeValue( const std::string& svgText, const std::string& attributeName, std::string& attributeValue )
{
std::string searchAttributeName{ attributeName + "="};
size_t attributePos = svgText.find( searchAttributeName );
if( attributePos == std::string::npos )
{
return false;
}
size_t openingDoubleQuotePos{ svgText.find( "\"", attributePos ) };
if ( openingDoubleQuotePos == std::string::npos )
{
return false;
}
size_t closingDoubleQuotePos{ svgText.find( "\"", openingDoubleQuotePos + 1) };
if ( closingDoubleQuotePos == std::string::npos )
{
return false;
}
attributeValue = svgText.substr( openingDoubleQuotePos + 1, closingDoubleQuotePos - openingDoubleQuotePos - 1);
//std::cout << attributeName << ":" << attributeValue << "\n";
return true;
}
// Skips any optional commas in the stream
// SVG has a really funky format,
// not sure this code works for all cases.
// TODO: Test cases!
void SVGParser::SkipSvgComma( std::stringstream& svgStream, bool isRequired )
{
while ( true )
{
char c = char(svgStream.get( ));
if ( svgStream.eof( ) )
{
if ( isRequired )
{
std::cerr << "SVGParser::SkipSvgComma, expected comma or whitespace\n";
}
break;
}
if ( c == ( ',' ) )
return;
if ( !isspace( c ) )
{
svgStream.unget( );
return;
}
}
}
float SVGParser::ReadSvgValue( std::stringstream& svgStream, float defaultValue )
{
float s{};
svgStream >> s;
if ( svgStream.eof( ) )
{
s = defaultValue;
}
else
{
SkipSvgComma( svgStream, false );
}
return s;
}
float SVGParser::ReadSvgValue( std::stringstream& svgStream, bool separatorRequired )
{
float s{};
svgStream >> s;
SkipSvgComma( svgStream, separatorRequired );
return s;
}
// Reads a single point
Point2f SVGParser::ReadSvgPoint( std::stringstream& svgStream )
{
//std::cout << "ReadSvgPoint: " << svgStream.str() << "\n";
Point2f p{};
p.x = ReadSvgValue( svgStream, true );
p.y = ReadSvgValue( svgStream, false );
return p;
}
Point2f SVGParser::FirstSvgPoint( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen, bool advance )
{
if ( !isOpen )
{
std::cerr << "SVGParser::FirstSvgPoint, expected 'Z' or 'z' command";
}
Point2f p = ReadSvgPoint( svgStream );
if ( islower( cmd ) )
{
// Relative point
p.x = cursor.x + p.x;
p.y = cursor.y + p.y;
}
if ( advance )
{
cursor = p;
}
return p;
}
// Read the next point,
// taking into account relative and absolute positioning.
// Advances the cursor if requested.
// Throws an exception if the figure is not open
Point2f SVGParser::NextSvgPoint( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen, bool advance )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgPoint, expected 'M' or 'm' command\n";
}
Point2f p = ReadSvgPoint( svgStream );
if ( islower( cmd ) )
{
// Relative point
p.x = cursor.x + p.x;
p.y = cursor.y + p.y;
}
if ( advance )
{
cursor = p;
}
return p;
}
// Reads next point, given only the new x coordinate
Point2f SVGParser::NextSvgCoordX( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgCoordX, expected 'M' or 'm' command\n";
}
float c;
svgStream >> c;
if ( islower( cmd ) )
{
// Relative point
cursor.x += c;
}
else
{
cursor.x = c;
}
return cursor;
}
// Reads next point, given only the new y coordinate
Point2f SVGParser::NextSvgCoordY( std::stringstream& svgStream, Point2f& cursor, char cmd, bool isOpen )
{
if ( isOpen )
{
std::cerr << "SVGParser::NextSvgCoordY, expected 'M' or 'm' command\n";
}
float c{};
svgStream >> c;
if ( islower( cmd ) )
{
// Relative point
cursor.y += c;
}
else
{
cursor.y = c;
}
return cursor;
}

49
Engine/SVGParser.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <string>
#include <vector>
#include <sstream>
#include "structs.h"
class SVGParser final
{
public:
// The only function to be called
static bool GetVerticesFromSvgFile(const std::string& filePath, std::vector<std::vector<Point2f>> &vertices);
private:
//static bool LoadGeometryFromSvgStream(unsigned char* pBlob, int blobSize, std::vector<Point2f> &vertices);
static void RemoveSpaces( std::string& svgString );
static bool GetVerticesFromSvgString(std::string& svgText, std::vector<std::vector<Point2f>> &vertices);
static bool GetVerticesFromPathData( const std::string& pathData, std::vector<Point2f> &vertices );
static bool GetElementContent( const std::string& svgText, const std::string& elementName, std::string& elementValue, size_t& startContentPos, size_t& endContentPos);
static bool GetAttributeValue( const std::string& svgText, const std::string& attributeName, std::string& attributeValue );
// Skips any optional commas in the stream
// SVG has a really funky format,
// not sure this code works for all cases.
// TODO: Test cases!
static void SkipSvgComma(std::stringstream& stream, bool isRequired);
static float ReadSvgValue(std::stringstream& stream, float defaultValue);
static float ReadSvgValue(std::stringstream& stream, bool separatorRequired);
// Reads a single point
static Point2f ReadSvgPoint( std::stringstream& stream );
// Read the first point,
// taking into account relative and absolute positioning.
// Stores this point, needed when path is closed
// Advances the cursor if requested.
static Point2f FirstSvgPoint(std::stringstream& stream, Point2f& cursor, char cmd, bool isOpen, bool advance);
// Read the next point,
// taking into account relative and absolute positioning.
// Advances the cursor if requested.
// Throws an exception if the figure is not open
static Point2f NextSvgPoint(std::stringstream& stream, Point2f& cursor, char cmd, bool isOpen, bool advance);
// Reads next point, given only the new x coordinate
static Point2f NextSvgCoordX(std::stringstream& stream, Point2f& cursor, char cmd, bool isOpen);
// Reads next point, given only the new y coordinate
static Point2f NextSvgCoordY(std::stringstream& ssRef, Point2f& cursor, char cmd, bool isOpen);
};

77
Engine/SoundEffect.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "base.h"
#include <iostream>
#include "SoundEffect.h"
SoundEffect::SoundEffect( const std::string& path )
:m_pMixChunk{ Mix_LoadWAV( path.c_str( ) ) }
{
if ( m_pMixChunk == nullptr )
{
const std::string errorMsg = "SoundEffect: Failed to load " + path + ",\nSDL_mixer Error: " + Mix_GetError( );
std::cerr << errorMsg;
}
}
SoundEffect::~SoundEffect( )
{
Mix_FreeChunk( m_pMixChunk );
m_pMixChunk = nullptr;
}
bool SoundEffect::IsLoaded( ) const
{
return m_pMixChunk != nullptr;
}
bool SoundEffect::Play( const int loops ) const
{
// Don't save the channel as a data member,
// because when it stops playing the channel becomes free
// and available for usage by other effects
if ( m_pMixChunk != nullptr )
{
const int channel{ Mix_PlayChannel( -1, m_pMixChunk, loops ) };
return channel == -1 ? false : true;
}
else
{
return false;
}
}
void SoundEffect::SetVolume( const int value )
{
if ( m_pMixChunk != nullptr )
{
Mix_VolumeChunk( m_pMixChunk, value );
}
}
int SoundEffect::GetVolume( ) const
{
if ( m_pMixChunk != nullptr )
{
return Mix_VolumeChunk( m_pMixChunk, -1 );
}
else
{
return -1;
}
}
void SoundEffect::StopAll( )
{
Mix_HaltChannel(-1 );
}
void SoundEffect::PauseAll( )
{
Mix_Pause( -1 );
}
void SoundEffect::ResumeAll( )
{
Mix_Resume( -1 );
}

24
Engine/SoundEffect.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
class Mix_Chunk;
class SoundEffect final
{
public:
explicit SoundEffect( const std::string& path );
~SoundEffect( );
SoundEffect(const SoundEffect& other) = delete;
SoundEffect& operator=(const SoundEffect& rhs) = delete;
SoundEffect( SoundEffect&& other) = delete;
SoundEffect& operator=( SoundEffect&& rhs) = delete;
bool IsLoaded( ) const;
bool Play( const int loops ) const;
void SetVolume( const int value );
int GetVolume( ) const;
static void StopAll( );
static void PauseAll( );
static void ResumeAll( );
private:
Mix_Chunk* m_pMixChunk;
};

67
Engine/SoundStream.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "base.h"
#include <iostream>
#include "SoundStream.h"
SoundStream::SoundStream( const std::string& path )
:m_pMixMusic{ Mix_LoadMUS( path.c_str( )) }
{
if ( m_pMixMusic == nullptr )
{
std::string errorMsg = "SoundStream: Failed to load " + path + ",\nSDL_mixer Error: " + Mix_GetError( );
std::cerr << errorMsg;
}
}
SoundStream::~SoundStream( )
{
Mix_FreeMusic( m_pMixMusic );
m_pMixMusic = nullptr;
}
bool SoundStream::IsLoaded( ) const
{
return m_pMixMusic != nullptr;
}
bool SoundStream::Play(bool repeat ) const
{
if ( m_pMixMusic != nullptr )
{
int result{ Mix_PlayMusic( m_pMixMusic, repeat ? -1 : 1 ) };
return result == 0 ? true : false;
}
else
{
return false;
}
}
void SoundStream::Stop( )
{
Mix_HaltMusic( );
}
void SoundStream::Pause( )
{
Mix_PauseMusic( );
}
void SoundStream::Resume( )
{
Mix_ResumeMusic( );
}
int SoundStream::GetVolume( )
{
return Mix_VolumeMusic( -1 );
}
bool SoundStream::IsPlaying( )
{
return Mix_PlayingMusic( ) == 0 ? false : true;
}
void SoundStream::SetVolume( int value )
{
Mix_VolumeMusic( value );
}

24
Engine/SoundStream.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
class SoundStream final
{
public:
explicit SoundStream( const std::string& path );
~SoundStream( );
SoundStream( const SoundStream& other ) = delete;
SoundStream& operator=( const SoundStream& rhs ) = delete;
SoundStream(SoundStream&& other) = delete;
SoundStream& operator=(SoundStream&& other) = delete;
bool IsLoaded( ) const;
bool Play( bool repeat ) const;
static void Stop( );
static void Pause( );
static void Resume( );
static void SetVolume(int value );
static int GetVolume( );
static bool IsPlaying( );
private:
Mix_Music *m_pMixMusic;
};

353
Engine/Texture.cpp Normal file
View File

@@ -0,0 +1,353 @@
#include "base.h"
#include <iostream>
#include <string>
#include "Texture.h"
Texture::Texture( const std::string& imagePath )
:m_Id{ }
,m_Width{ 10.0f }
,m_Height{ 10.0f }
,m_CreationOk{ false }
{
CreateFromImage( imagePath );
}
Texture::Texture( const std::string& text, TTF_Font *pFont, const Color4f& textColor )
:m_Id{}
,m_Width{ 10.0f }
,m_Height{ 10.0f }
,m_CreationOk{ false }
{
CreateFromString( text, pFont, textColor );
}
Texture::Texture( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor )
:m_Id{}
,m_Width{ 10.0f }
,m_Height{ 10.0f }
,m_CreationOk{ false }
{
CreateFromString( text, fontPath, ptSize, textColor );
}
Texture::Texture( Texture&& other ) noexcept
:m_Id{ other.m_Id }
,m_Width{ other.m_Width }
,m_Height{ other.m_Height }
,m_CreationOk{ other.m_CreationOk }
{
other.m_Id = 0;
other.m_CreationOk = false;
}
Texture& Texture::operator=( Texture&& other ) noexcept
{
if (this != &other)// no self assignment
{
m_Id = other.m_Id;
m_Width = other.m_Width;
m_Height = other.m_Height;
m_CreationOk = other.m_CreationOk;
other.m_Id = 0;
other.m_CreationOk = false;
}
return *this;
}
Texture::~Texture()
{
glDeleteTextures( 1, &m_Id );
}
void Texture::CreateFromImage( const std::string& path )
{
m_CreationOk = true;
// Load image at specified path
SDL_Surface* pLoadedSurface = IMG_Load(path.c_str());
if (pLoadedSurface == nullptr)
{
std::cerr << "Texture::CreateFromImage, error when calling IMG_Load: " << SDL_GetError() << std::endl;
m_CreationOk = false;
return;
}
CreateFromSurface(pLoadedSurface);
// Free loaded surface
SDL_FreeSurface(pLoadedSurface);
}
void Texture::CreateFromString( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor )
{
m_CreationOk = true;
// Create font
TTF_Font *pFont{};
pFont = TTF_OpenFont( fontPath.c_str( ), ptSize );
if(pFont == nullptr )
{
std::cerr << "Texture::CreateFromString, error when calling TTF_OpenFont: " << TTF_GetError( ) << std::endl;
m_CreationOk = false;
return;
}
// Create texture using this font and close font afterwards
CreateFromString( text, pFont, textColor );
TTF_CloseFont( pFont );
}
void Texture::CreateFromString( const std::string& text, TTF_Font *pFont, const Color4f& color )
{
m_CreationOk = true;
if ( pFont == nullptr )
{
std::cerr << "Texture::CreateFromString, invalid TTF_Font pointer\n" ;
m_CreationOk = false;
return;
}
// Render text surface
SDL_Color textColor{};
textColor.r = Uint8( color.r * 255 );
textColor.g = Uint8( color.g * 255 );
textColor.b = Uint8( color.b * 255 );
textColor.a = Uint8( color.a * 255 );
SDL_Surface* pLoadedSurface = TTF_RenderText_Blended( pFont, text.c_str( ), textColor );
if ( pLoadedSurface == nullptr )
{
std::cerr << "Texture::CreateFromString, error when calling TTF_RenderText_Blended: " << TTF_GetError( ) << std::endl;
m_CreationOk = false;
return;
}
// Copy to video memory
CreateFromSurface( pLoadedSurface );
// Free loaded surface
SDL_FreeSurface( pLoadedSurface );
}
void Texture::CreateFromSurface( SDL_Surface* pSurface )
{
m_CreationOk = true;
//Get image dimensions
m_Width = float(pSurface->w);
m_Height =float( pSurface->h);
// Get pixel format information and translate to OpenGl format
GLenum pixelFormat{ GL_RGB };
switch ( pSurface->format->BytesPerPixel )
{
case 3:
if ( pSurface->format->Rmask == 0x000000ff )
{
pixelFormat = GL_RGB;
}
else
{
pixelFormat = GL_BGR;
}
break;
case 4:
if ( pSurface->format->Rmask == 0x000000ff )
{
pixelFormat = GL_RGBA;
}
else
{
pixelFormat = GL_BGRA;
}
break;
default:
std::cerr << "Texture::CreateFromSurface, unknow pixel format, BytesPerPixel: " << pSurface->format->BytesPerPixel << "\nUse 32 bit or 24 bit images.\n";
m_CreationOk = false;
return;
}
//Generate an array of textures. We only want one texture (one element array), so trick
//it by treating "texture" as array of length one.
glGenTextures(1, &m_Id);
//Select (bind) the texture we just generated as the current 2D texture OpenGL is using/modifying.
//All subsequent changes to OpenGL's texturing state for 2D textures will affect this texture.
glBindTexture(GL_TEXTURE_2D, m_Id);
glPixelStorei(GL_UNPACK_ROW_LENGTH, pSurface->pitch / pSurface->format->BytesPerPixel);
// check for errors. Can happen if a texture is created while a static pointer is being initialized, even before the call to the main function.
GLenum e = glGetError();
if (e != GL_NO_ERROR)
{
std::cerr << "Texture::CreateFromSurface, error binding textures, Error id = " << e << '\n';
std::cerr << "Can happen if a texture is created before performing the initialization code (e.g. a static Texture object).\n";
std::cerr << "There might be a white rectangle instead of the image.\n";
}
// Specify the texture's data.
// This function is a bit tricky, and it's hard to find helpful documentation.
// A summary:
// GL_TEXTURE_2D: The currently bound 2D texture (i.e. the one we just made)
// 0: The mipmap level. 0, since we want to update the base level mipmap image (i.e., the image itself,
// not cached smaller copies)
// GL_RGBA: Specifies the number of color components in the texture.
// This is how OpenGL will store the texture internally (kinda)--
// It's essentially the texture's type.
// surface->w: The width of the texture
// surface->h: The height of the texture
// 0: The border. Don't worry about this if you're just starting.
// pixelFormat: The format that the *data* is in--NOT the texture!
// GL_UNSIGNED_BYTE: The type the data is in. In SDL, the data is stored as an array of bytes, with each channel
// getting one byte. This is fairly typical--it means that the image can store, for each channel,
// any value that fits in one byte (so 0 through 255). These values are to be interpreted as
// *unsigned* values (since 0x00 should be dark and 0xFF should be bright).
// surface->pixels: The actual data. As above, SDL's array of bytes.
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, pSurface->w, pSurface->h, 0, pixelFormat, GL_UNSIGNED_BYTE, pSurface->pixels );
// Set the minification and magnification filters. In this case, when the texture is minified (i.e., the texture's pixels (texels) are
// *smaller* than the screen pixels you're seeing them on, linearly filter them (i.e. blend them together). This blends four texels for
// each sample--which is not very much. Mipmapping can give better results. Find a texturing tutorial that discusses these issues
// further. Conversely, when the texture is magnified (i.e., the texture's texels are *larger* than the screen pixels you're seeing
// them on), linearly filter them. Qualitatively, this causes "blown up" (overmagnified) textures to look blurry instead of blocky.
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}
void Texture::Draw( const Point2f& dstBottomLeft, const Rectf& srcRect ) const
{
const float epsilon{ 0.001f };
if ( !m_CreationOk )
{
if (!(srcRect.width > epsilon && srcRect.height > epsilon)) // No srcRect specified
{
DrawFilledRect(Rectf{ dstBottomLeft.x, dstBottomLeft.y, m_Width, m_Height });
}
else
{
DrawFilledRect(Rectf{ dstBottomLeft.x, dstBottomLeft.y, srcRect.width, srcRect.height });
}
}
else
{
Rectf dstRect{ dstBottomLeft.x, dstBottomLeft.y, srcRect.width, srcRect.height };
if (!(srcRect.width > epsilon && srcRect.height > epsilon)) // No srcRect specified
{
dstRect.width = m_Width;
dstRect.height = m_Height;
}
Draw( dstRect, srcRect );
}
}
void Texture::Draw( const Rectf& dstRect, const Rectf& srcRect ) const
{
const float epsilon{ 0.001f };
if ( !m_CreationOk )
{
DrawFilledRect( dstRect );
return;
}
// Determine texture coordinates using srcRect and default destination width and height
float textLeft{};
float textRight{};
float textTop{};
float textBottom{};
float defaultDestWidth{};
float defaultDestHeight{};
if ( !( srcRect.width > epsilon && srcRect.height > epsilon) ) // No srcRect specified
{
// Use complete texture
textLeft = 0.0f;
textRight = 1.0f;
textTop = 0.0f;
textBottom = 1.0f;
defaultDestHeight = m_Height;
defaultDestWidth = m_Width;
}
else // srcRect specified
{
// Convert to the range [0.0, 1.0]
textLeft = srcRect.left / m_Width;
textRight = ( srcRect.left + srcRect.width ) / m_Width;
textTop = ( srcRect.bottom - srcRect.height ) / m_Height;
textBottom = srcRect.bottom / m_Height;
defaultDestHeight = srcRect.height;
defaultDestWidth = srcRect.width;
}
// Determine vertex coordinates
float vertexLeft{ dstRect.left };
float vertexBottom{ dstRect.bottom };
float vertexRight{};
float vertexTop{};
if ( !( dstRect.width > 0.001f && dstRect.height > 0.001f ) ) // If no size specified use default size
{
vertexRight = vertexLeft + defaultDestWidth;
vertexTop = vertexBottom + defaultDestHeight;
}
else
{
vertexRight = vertexLeft + dstRect.width;
vertexTop = vertexBottom + dstRect.height;
}
// Tell opengl which texture we will use
glBindTexture( GL_TEXTURE_2D, m_Id );
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
// Draw
glEnable( GL_TEXTURE_2D );
{
glBegin( GL_QUADS );
{
glTexCoord2f( textLeft, textBottom );
glVertex2f( vertexLeft, vertexBottom );
glTexCoord2f( textLeft, textTop );
glVertex2f( vertexLeft, vertexTop );
glTexCoord2f( textRight, textTop );
glVertex2f( vertexRight, vertexTop );
glTexCoord2f( textRight, textBottom );
glVertex2f( vertexRight, vertexBottom );
}
glEnd( );
}
glDisable( GL_TEXTURE_2D );
}
float Texture::GetWidth() const
{
return m_Width;
}
float Texture::GetHeight() const
{
return m_Height;
}
bool Texture::IsCreationOk( ) const
{
return m_CreationOk;
}
void Texture::DrawFilledRect(const Rectf& rect) const
{
glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
glBegin(GL_POLYGON);
{
glVertex2f(rect.left, rect.bottom);
glVertex2f(rect.left + rect.width, rect.bottom);
glVertex2f(rect.left + rect.width, rect.bottom + rect.height);
glVertex2f(rect.left , rect.bottom + rect.height);
}
glEnd();
}

37
Engine/Texture.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include "base.h"
class Texture final
{
public:
explicit Texture( const std::string& imagePath );
explicit Texture( const std::string& text, TTF_Font *pFont, const Color4f& textColor );
explicit Texture( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor );
Texture( const Texture& other ) = delete;
Texture& operator=( const Texture& other ) = delete;
Texture( Texture&& other ) noexcept;
Texture& operator=( Texture&& other ) noexcept;
~Texture();
void Draw(const Point2f& dstBottomLeft = {}, const Rectf& srcRect = {}) const;
void Draw( const Rectf& dstRect, const Rectf& srcRect = {} ) const;
float GetWidth() const;
float GetHeight() const;
bool IsCreationOk( ) const;
private:
//DATA MEMBERS
GLuint m_Id;
float m_Width;
float m_Height;
bool m_CreationOk;
// FUNCTIONS
void CreateFromImage( const std::string& path );
void CreateFromString( const std::string& text, TTF_Font *pFont, const Color4f& textColor );
void CreateFromString( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor );
void CreateFromSurface( SDL_Surface *pSurface );
SDL_Surface * STBImageLoad( const std::string& path );
void DrawFilledRect(const Rectf& dstRect) const;
};

24
Engine/Transform.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "base.h"
#include "Vector2f.h"
struct Transform
{
public:
void ApplyTransformation() const
{
glPushMatrix();
glTranslatef(Position.x, Position.y, 0);
glRotatef(Rotation, 0, 0, 1);
glScalef(Scale.x, Scale.y, 1);
}
void ResetTransformation() const
{
glPopMatrix();
}
Vector2f Position{};
float Rotation{};
Vector2f Scale{ 1.f, 1.f };
};

236
Engine/Vector2f.cpp Normal file
View File

@@ -0,0 +1,236 @@
#include "base.h"
#include <cmath>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Vector2f.h"
//-----------------------------------------------------------------
// Vector2f Constructors
//-----------------------------------------------------------------
Vector2f::Vector2f( )
:Vector2f{ 0.0f, 0.0f }
{
}
Vector2f::Vector2f( float x, float y )
: x{ x }
, y{ y }
{
}
Vector2f::Vector2f( const Point2f& fromPoint, const Point2f& tillPoint )
: Vector2f{ tillPoint.x - fromPoint.x, tillPoint.y - fromPoint.y }
{
}
Vector2f::Vector2f(const Point2f & point)
: Vector2f{ Point2f{ 0.0f, 0.0f }, point }
{
}
// -------------------------
// Methods
// -------------------------
bool Vector2f::Equals(const Vector2f& other, float epsilon) const
{
return ( abs(x - other.x) < epsilon ) && ( abs(y - other.y) < epsilon );
}
Point2f Vector2f::ToPoint2f() const
{
return Point2f{ x, y };
}
float Vector2f::DotProduct(const Vector2f& other) const
{
return x * other.x + y * other.y;
}
float Vector2f::CrossProduct(const Vector2f& other) const
{
return x * other.y - y * other.x;
}
std::string Vector2f::ToString() const
{
std::stringstream buffer;
buffer << std::fixed;
buffer << std::setprecision( 2 );
buffer << "Vector2f(" << x << ", " << y << ")";
return buffer.str();
}
float Vector2f::Norm() const
{
return Length();
}
float Vector2f::Length() const
{
return sqrt( x * x + y * y );
}
float Vector2f::SquaredLength() const
{
return x * x + y * y;
}
float Vector2f::AngleWith(const Vector2f& other) const
{
//https://stackoverflow.com/questions/21483999/using-atan2-to-find-angle-between-two-vectors
// not calling dot and cross functions to prevent the overhead
return atan2(x * other.y - other.x * y, x * other.x + y * other.y);
}
Vector2f Vector2f::Normalized(float epsilon) const
{
float length{ Length( ) };
if ( length < epsilon )
{
return Vector2f{ 0, 0 };
}
else
{
return Vector2f{ x / length, y / length };
}
}
Vector2f Vector2f::Orthogonal() const
{
return Vector2f{ -y, x };
}
Vector2f Vector2f::Reflect( const Vector2f& surfaceNormal ) const
{
return (*this) - 2 * ( this->DotProduct( surfaceNormal ) * surfaceNormal );
}
void Vector2f::Set(float newX, float newY)
{
x = newX;
y = newY;
}
// -------------------------
// Member operators
// -------------------------
Vector2f Vector2f::operator-( ) const
{
return Vector2f{ -x, -y };
}
Vector2f Vector2f::operator+ ( ) const
{
return Vector2f{ x, y };
}
Vector2f& Vector2f::operator*=(float rhs)
{
x *= rhs;
y *= rhs;
return *this;
}
Vector2f& Vector2f::operator/=(float rhs)
{
*this *= 1 / rhs;
return *this;
}
Vector2f& Vector2f::operator+=(const Vector2f& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
Vector2f& Vector2f::operator-=(const Vector2f& rhs)
{
*this += -rhs;
return *this;
}
Vector2f::operator Point2f()
{
return Point2f{ x,y };
}
// -------------------------
// Non-member operators
// -------------------------
Vector2f operator*( float lhs, Vector2f rhs )
{
return rhs *= lhs;
}
Vector2f operator*( Vector2f lhs, float rhs )
{
return lhs *= rhs;
}
Vector2f operator/( Vector2f lhs, float rhs )
{
return lhs *= (1 / rhs);
}
Vector2f operator+( Vector2f lhs, const Vector2f& rhs )
{
return lhs += rhs;
}
Vector2f operator-( Vector2f lhs, const Vector2f& rhs )
{
return lhs += -rhs;
}
bool operator==( const Vector2f& lhs, const Vector2f& rhs )
{
return ( lhs.Equals( rhs ) );
}
bool operator!=( const Vector2f& lhs, const Vector2f& rhs )
{
return !( lhs == rhs );
}
std::ostream& operator<< ( std::ostream& lhs, const Vector2f& rhs )
{
lhs << rhs.ToString( );
return lhs;
}
// Point2f related operators
Point2f& operator+=(Point2f& lhs, const Vector2f& rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
return lhs;
}
Point2f operator+(Point2f lhs, const Vector2f& rhs)
{
lhs += rhs;
return lhs;
}
Point2f& operator-=(Point2f& lhs, const Vector2f& rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
return lhs;
}
Point2f operator-(Point2f lhs, const Vector2f& rhs)
{
lhs -= rhs;
return lhs;
}
Vector2f operator-(const Point2f& lhs, const Point2f& rhs)
{
Vector2f v{ lhs.x - rhs.x, lhs.y - rhs.y };
return v;
}

109
Engine/Vector2f.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include "structs.h"
struct Vector2f final
{
// -------------------------
// Constructors
// -------------------------
Vector2f( );
explicit Vector2f( float x, float y );
explicit Vector2f( const Point2f& fromPoint, const Point2f& tillPoint );
explicit Vector2f( const Point2f& point );
// -------------------------
// Member operators
// -------------------------
Vector2f operator-( ) const;
Vector2f operator+( ) const;
Vector2f& operator*=( float rhs);
Vector2f& operator/=( float rhs);
Vector2f& operator+=( const Vector2f& rhs);
Vector2f& operator-=( const Vector2f& rhs);
explicit operator Point2f();
// -------------------------
// Methods
// -------------------------
// Convert to Point2f
Point2f ToPoint2f( ) const;
// Are two vectors equal within a threshold?
// u.Equals(v)
bool Equals( const Vector2f& other, float epsilon = 0.001f ) const;
// Convert to String
std::string ToString( ) const;
// DotProduct
// float d = u.DotProduct(v);
float DotProduct( const Vector2f& other ) const;
// CrossProduct
// float d = u.CrossProduct(v);
float CrossProduct( const Vector2f& other ) const;
// Norm of a vector
// float l = v.Norm();
float Norm( ) const;
// Length of a vector:
// float l = v.Length();
float Length( ) const;
// Square Length of a vector.
// Faster alternative for Length, sqrt is not executed.
float SquaredLength( ) const;
// AngleWith returns the smallest angle with another vector within the range [-PI/2, PI/2].
// A pos angle is counter clockwise from this to the other
// float angle = u.AngleWith(v);
float AngleWith( const Vector2f& other ) const;
// Returns normalized form of a vector
// Vector2f n = v.Normalized();
Vector2f Normalized( float epsilon = 0.001f ) const;
// Returns the orthogonal of the Vector2f
// Vector2f w = v.Orthogonal();
Vector2f Orthogonal( ) const;
// Returns a vector that is the reflection of the Vector2f
// surfaceNormal: represents the normal of the surface at the reflection point
Vector2f Reflect( const Vector2f& surfaceNormal ) const;
// Sets the values of x and y
void Set( float newX, float newY );
// -------------------------
// Datamembers
// -------------------------
float x;
float y;
};
// -------------------------
// Non member operators
// -------------------------
Vector2f operator*( float lhs, Vector2f rhs );
Vector2f operator*( Vector2f lhs, float rhs );
Vector2f operator/( Vector2f lhs, float rhs );
Vector2f operator+( Vector2f lhs, const Vector2f& rhs );
Vector2f operator-( Vector2f lhs, const Vector2f& rhs );
bool operator==( const Vector2f& lhs, const Vector2f& rhs );
bool operator!=( const Vector2f& lhs, const Vector2f& rhs );
std::ostream& operator<< ( std::ostream& lhs, const Vector2f& rhs );
// Translating a point by a vector
Point2f& operator+=(Point2f& lhs, const Vector2f& rhs);
Point2f operator+(Point2f lhs, const Vector2f& rhs);
Point2f& operator-=(Point2f& lhs, const Vector2f& rhs);
Point2f operator-(Point2f lhs, const Vector2f& rhs);
// The difference vector between 2 points
Vector2f operator-( const Point2f& lhs, const Point2f& rhs);

36
Engine/base.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
//ML Detection Extension
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif
// SDL libs
#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")
// OpenGL libs
#pragma comment (lib,"opengl32.lib")
#pragma comment (lib,"Glu32.lib")
// SDL extension libs
#pragma comment(lib, "SDL2_image.lib")
#pragma comment(lib, "SDL2_ttf.lib")
#pragma comment(lib, "SDL2_mixer.lib")
// SDL and OpenGL Includes
#pragma warning(disable : 26812)
#pragma warning(disable : 4820)
#include <SDL.h>
#include <SDL_opengl.h>
#include <SDL_ttf.h>
#include <SDL_mixer.h>
#include <SDL_image.h>
#pragma warning(default : 26812)
#include "structs.h"

97
Engine/structs.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "base.h"
#include "structs.h"
//-----------------------------------------------------------------
// Window Constructors
//-----------------------------------------------------------------
Window::Window( const std::string& title , float width , float height , bool isVSyncOn )
:title{ title }
,width{ width }
,height{ height }
,isVSyncOn{ isVSyncOn }
{
}
//-----------------------------------------------------------------
// Point2f Constructors
//-----------------------------------------------------------------
Point2f::Point2f( )
:Point2f{ 0.0f, 0.0f }
{
}
Point2f::Point2f( float x, float y )
:x{ x }, y{ y }
{
}
//-----------------------------------------------------------------
// Rectf Constructors
//-----------------------------------------------------------------
Rectf::Rectf( )
:Rectf{ 0.0f, 0.0f, 0.0f, 0.0f }
{
}
Rectf::Rectf( float left, float bottom, float width, float height )
:left{ left }
,bottom{ bottom }
,width{ width }
,height{ height }
{
}
//-----------------------------------------------------------------
// Color4f Constructors
//-----------------------------------------------------------------
Color4f::Color4f( )
:Color4f{ 0.0f, 0.0f, 0.0f, 1.0f }
{
}
Color4f::Color4f( float r, float g, float b, float a )
:r{ r }
,g{ g }
,b{ b }
,a{ a }
{
}
//-----------------------------------------------------------------
// Circlef Constructors
//-----------------------------------------------------------------
Circlef::Circlef( )
:Circlef{ 0.0f, 0.0f, 0.0f }
{
}
Circlef::Circlef( float centerX, float centerY, float radius )
:Circlef{ Point2f{ centerX, centerY }, radius }
{
}
Circlef::Circlef( const Point2f& center, float radius )
:center{ center }
,radius{ radius }
{
}
//-----------------------------------------------------------------
// Ellipsef Constructors
//-----------------------------------------------------------------
Ellipsef::Ellipsef( )
:Ellipsef{ 0.0f, 0.0f, 0.0f, 0.0f }
{
}
Ellipsef::Ellipsef( const Point2f& center, float radiusX, float radiusY )
: center{ center }
, radiusX{ radiusX }
, radiusY{ radiusY }
{
}
Ellipsef::Ellipsef( float centerX, float centerY, float radiusX, float radiusY )
: Ellipsef{ Point2f{ centerX, centerY }, radiusX, radiusY }
{
}

73
Engine/structs.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <string>
struct Window
{
explicit Window( const std::string& title = "Title", float width = 320.0f,
float height = 180.0f, bool isVSyncOn = true );
std::string title;
float width;
float height;
bool isVSyncOn;
};
struct Point2f
{
Point2f( );
explicit Point2f( float x, float y );
float x;
float y;
};
struct Rectf
{
Rectf( );
explicit Rectf( float left, float bottom, float width, float height );
float left;
float bottom;
float width;
float height;
};
struct Color4f
{
Color4f( );
explicit Color4f( float r, float g, float b, float a );
float r;
float g;
float b;
float a;
};
struct Circlef
{
Circlef( );
explicit Circlef( const Point2f& center, float radius );
explicit Circlef( float centerX, float centerY, float radius );
Point2f center;
float radius;
};
struct Ellipsef
{
Ellipsef( );
explicit Ellipsef( const Point2f& center, float radiusX, float radiusY );
explicit Ellipsef( float centerX, float centerY, float radiusX, float radiusY );
Point2f center;
float radiusX;
float radiusY;
};

686
Engine/utils.cpp Normal file
View File

@@ -0,0 +1,686 @@
#include "base.h"
//#define _USE_MATH_DEFINES
#include <cmath>
#include <algorithm>
#include <iostream>
#include "utils.h"
#pragma region OpenGLDrawFunctionality
void utils::SetColor( const Color4f& color )
{
glColor4f( color.r, color.g, color.b, color.a );
}
void utils::DrawPoint( float x, float y, float pointSize )
{
glPointSize( pointSize );
glBegin( GL_POINTS );
{
glVertex2f( x, y );
}
glEnd( );
}
void utils::DrawPoint( const Point2f& p, float pointSize )
{
DrawPoint( p.x, p.y, pointSize );
}
void utils::DrawPoints( Point2f *pVertices, int nrVertices, float pointSize )
{
glPointSize( pointSize );
glBegin( GL_POINTS );
{
for ( int idx{ 0 }; idx < nrVertices; ++idx )
{
glVertex2f( pVertices[idx].x, pVertices[idx].y );
}
}
glEnd( );
}
void utils::DrawLine( float x1, float y1, float x2, float y2, float lineWidth )
{
glLineWidth( lineWidth );
glBegin( GL_LINES );
{
glVertex2f( x1, y1 );
glVertex2f( x2, y2 );
}
glEnd( );
}
void utils::DrawLine( const Point2f& p1, const Point2f& p2, float lineWidth )
{
DrawLine( p1.x, p1.y, p2.x, p2.y, lineWidth );
}
void utils::DrawTriangle(const Point2f& p1, const Point2f& p2, const Point2f& p3, float lineWidth)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
{
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glVertex2f(p3.x, p3.y);
}
glEnd();
}
void utils::FillTriangle(const Point2f& p1, const Point2f& p2, const Point2f& p3)
{
glBegin(GL_TRIANGLES);
{
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glVertex2f(p3.x, p3.y);
}
glEnd();
}
void utils::DrawRect( float left, float bottom, float width, float height, float lineWidth )
{
if (width > 0 && height > 0 && lineWidth > 0)
{
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
{
glVertex2f(left, bottom);
glVertex2f(left + width, bottom);
glVertex2f(left + width, bottom + height);
glVertex2f(left, bottom + height);
}
glEnd();
}
}
void utils::DrawRect( const Point2f& bottomLeft, float width, float height, float lineWidth )
{
DrawRect( bottomLeft.x, bottomLeft.y, width, height, lineWidth );
}
void utils::DrawRect( const Rectf& rect, float lineWidth )
{
DrawRect( rect.left, rect.bottom, rect.width, rect.height, lineWidth );
}
void utils::FillRect( float left, float bottom, float width, float height )
{
if (width > 0 && height > 0)
{
glBegin(GL_POLYGON);
{
glVertex2f(left, bottom);
glVertex2f(left + width, bottom);
glVertex2f(left + width, bottom + height);
glVertex2f(left, bottom + height);
}
glEnd();
}
}
void utils::FillRect( const Point2f& bottomLeft, float width, float height )
{
FillRect( bottomLeft.x, bottomLeft.y, width, height );
}
void utils::FillRect( const Rectf& rect )
{
FillRect( rect.left, rect.bottom, rect.width, rect.height );
}
void utils::DrawEllipse( float centerX, float centerY, float radX, float radY, float lineWidth )
{
if (radX > 0 && radY > 0 && lineWidth > 0)
{
float dAngle{ radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };
glLineWidth(lineWidth);
glBegin(GL_LINE_LOOP);
{
for (float angle = 0.0; angle < float(2 * g_Pi); angle += dAngle)
{
glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
}
}
glEnd();
}
}
void utils::DrawEllipse( const Point2f& center, float radX, float radY, float lineWidth )
{
DrawEllipse( center.x, center.y, radX, radY, lineWidth );
}
void utils::DrawEllipse( const Ellipsef& ellipse, float lineWidth )
{
DrawEllipse( ellipse.center.x, ellipse.center.y, ellipse.radiusX, ellipse.radiusY, lineWidth );
}
void utils::FillEllipse( float centerX, float centerY, float radX, float radY )
{
if (radX > 0 && radY > 0)
{
float dAngle{ radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };
glBegin(GL_POLYGON);
{
for (float angle = 0.0; angle < float(2 * g_Pi); angle += dAngle)
{
glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
}
}
glEnd();
}
}
void utils::FillEllipse( const Ellipsef& ellipse )
{
FillEllipse( ellipse.center.x, ellipse.center.y, ellipse.radiusX, ellipse.radiusY );
}
void utils::FillEllipse( const Point2f& center, float radX, float radY )
{
FillEllipse( center.x, center.y, radX, radY );
}
void utils::DrawArc( float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle, float lineWidth )
{
if ( fromAngle > tillAngle )
{
return;
}
float dAngle{ radX > radY ? float( g_Pi / radX ) : float( g_Pi / radY ) };
glLineWidth( lineWidth );
glBegin( GL_LINE_STRIP );
{
for ( float angle = fromAngle; angle < tillAngle; angle += dAngle )
{
glVertex2f( centerX + radX * cos( angle ), centerY + radY * sin( angle ) );
}
glVertex2f( centerX + radX * cos( tillAngle ), centerY + radY * sin( tillAngle ) );
}
glEnd( );
}
void utils::DrawArc( const Point2f& center, float radX, float radY, float fromAngle, float tillAngle, float lineWidth )
{
DrawArc( center.x, center.y, radX, radY, fromAngle, tillAngle, lineWidth );
}
void utils::FillArc( float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle )
{
if ( fromAngle > tillAngle )
{
return;
}
float dAngle{ radX > radY ? float( g_Pi / radX ) : float( g_Pi / radY ) };
glBegin( GL_POLYGON );
{
glVertex2f( centerX, centerY );
for ( float angle = fromAngle; angle < tillAngle; angle += dAngle )
{
glVertex2f( centerX + radX * cos( angle ), centerY + radY * sin( angle ) );
}
glVertex2f( centerX + radX * cos( tillAngle ), centerY + radY * sin( tillAngle ) );
}
glEnd( );
}
void utils::FillArc( const Point2f& center, float radX, float radY, float fromAngle, float tillAngle )
{
FillArc( center.x, center.y, radX, radY, fromAngle, tillAngle );
}
void utils::DrawPolygon( const std::vector<Point2f>& vertices, bool closed, float lineWidth )
{
DrawPolygon( vertices.data( ), vertices.size( ), closed, lineWidth );
}
void utils::DrawPolygon( const Point2f* pVertices, size_t nrVertices, bool closed, float lineWidth )
{
glLineWidth( lineWidth );
closed ? glBegin( GL_LINE_LOOP ) : glBegin( GL_LINE_STRIP );
{
for ( size_t idx{ 0 }; idx < nrVertices; ++idx )
{
glVertex2f( pVertices[idx].x, pVertices[idx].y );
}
}
glEnd( );
}
void utils::FillPolygon( const std::vector<Point2f>& vertices )
{
FillPolygon( vertices.data( ), vertices.size( ) );
}
void utils::FillPolygon( const Point2f *pVertices, size_t nrVertices )
{
glBegin( GL_POLYGON );
{
for ( size_t idx{ 0 }; idx < nrVertices; ++idx )
{
glVertex2f( pVertices[idx].x, pVertices[idx].y );
}
}
glEnd( );
}
#pragma endregion OpenGLDrawFunctionality
#pragma region CollisionFunctionality
float utils::GetDistance(float x1, float y1, float x2, float y2)
{
return (sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)));
}
float utils::GetDistance(const Point2f& p1, const Point2f& p2)
{
return GetDistance(p1.x, p1.y, p2.x, p2.y);
}
bool utils::IsPointInRect( const Point2f& p, const Rectf& r )
{
return ( p.x >= r.left &&
p.x <= r.left + r.width &&
p.y >= r.bottom &&
p.y <= r.bottom + r.height );
}
bool utils::IsPointInCircle( const Point2f& p, const Circlef& c )
{
float squaredDist{ (p.x - c.center.x) * (p.x - c.center.x) + (p.y - c.center.y) * (p.y - c.center.y) };
float squaredRadius{ c.radius * c.radius };
return ( squaredRadius >= squaredDist );
}
bool utils::IsOverlapping( const Point2f& a, const Point2f& b, const Rectf& r )
{
// if one of the line segment end points is in the rect
if ( utils::IsPointInRect( a, r ) || utils::IsPointInRect( b, r ) )
{
return true;
}
HitInfo hitInfo{};
Point2f vertices[]{ Point2f {r.left, r.bottom},
Point2f{ r.left + r.width, r.bottom },
Point2f{ r.left + r.width, r.bottom + r.height },
Point2f{ r.left, r.bottom + r.height } };
return Raycast( vertices, 4, a, b, hitInfo );
}
bool utils::IsOverlapping( const Rectf& r1, const Rectf& r2 )
{
// If one rectangle is on left side of the other
if ( ( r1.left + r1.width ) < r2.left || ( r2.left + r2.width ) < r1.left )
{
return false;
}
// If one rectangle is under the other
if ( r1.bottom > ( r2.bottom + r2.height ) || r2.bottom > ( r1.bottom + r1.height ) )
{
return false;
}
return true;
}
bool utils::IsOverlapping( const Rectf& r, const Circlef& c )
{
// Is center of circle in the rectangle?
if (IsPointInRect(c.center, r))
{
return true;
}
// Check line segments
if (utils::DistPointLineSegment(c.center, Point2f{ r.left, r.bottom }, Point2f{ r.left, r.bottom + r.height }) <= c.radius)
{
return true;
}
if ( utils::DistPointLineSegment( c.center, Point2f{ r.left, r.bottom }, Point2f{ r.left + r.width, r.bottom } ) <= c.radius )
{
return true;
}
if (utils::DistPointLineSegment(c.center, Point2f{ r.left + r.width, r.bottom + r.height }, Point2f{ r.left, r.bottom + r.height }) <= c.radius)
{
return true;
}
if (utils::DistPointLineSegment(c.center, Point2f{ r.left + r.width, r.bottom + r.height }, Point2f{ r.left + r.width, r.bottom }) <= c.radius)
{
return true;
}
return false;
}
bool utils::IsOverlapping( const Circlef& c1, const Circlef& c2 )
{
// squared distance between centers
float xDistance{ c1.center.x - c2.center.x };
float yDistance{ c1.center.y - c2.center.y };
float squaredDistance{ xDistance * xDistance + yDistance * yDistance };
float squaredTouchingDistance{ (c1.radius + c2.radius) * (c1.radius + c2.radius) };
return (squaredDistance < squaredTouchingDistance);
}
bool utils::IsOverlapping( const Point2f& a, const Point2f& b, const Circlef& c )
{
return utils::DistPointLineSegment( c.center, a, b ) <= c.radius;
}
bool utils::IsOverlapping( const std::vector<Point2f>& vertices, const Circlef& c )
{
return IsOverlapping( vertices.data( ), vertices.size( ), c );
}
bool utils::IsOverlapping( const Point2f* vertices, size_t nrVertices, const Circlef& c )
{
// Verify whether one of vertices is in circle
for ( size_t i{ 0 }; i < nrVertices; ++i )
{
if ( IsPointInCircle( vertices[i], c ) )
{
return true;
}
}
// Verify whether one of the polygon edges overlaps with circle
for ( size_t i{ 0 }; i < nrVertices; ++i )
{
if ( DistPointLineSegment( c.center, vertices[i], vertices[( i + 1 ) % nrVertices] ) <= c.radius )
{
return true;
}
}
// No overlapping with edges, verify whether circle is completely inside the polygon
if ( IsPointInPolygon( c.center, vertices, nrVertices ) )
{
return true;
}
return false;
}
bool utils::IsPointInPolygon( const Point2f& p, const std::vector<Point2f>& vertices )
{
return IsPointInPolygon( p, vertices.data( ), vertices.size( ) );
}
bool utils::IsPointInPolygon( const Point2f& p, const Point2f* vertices, size_t nrVertices )
{
if ( nrVertices < 2 )
{
return false;
}
// 1. First do a simple test with axis aligned bounding box around the polygon
float xMin{ vertices[0].x };
float xMax{ vertices[0].x };
float yMin{ vertices[0].y };
float yMax{ vertices[0].y };
for ( size_t idx{ 1 }; idx < nrVertices; ++idx )
{
if (xMin > vertices[idx].x)
{
xMin = vertices[idx].x;
}
if (xMax < vertices[idx].x)
{
xMax = vertices[idx].x;
}
if (yMin > vertices[idx].y)
{
yMin = vertices[idx].y;
}
if (yMax < vertices[idx].y)
{
yMax = vertices[idx].y;
}
}
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax)
{
return false;
}
// 2. Draw a virtual ray from anywhere outside the polygon to the point
// and count how often it hits any side of the polygon.
// If the number of hits is even, it's outside of the polygon, if it's odd, it's inside.
int numberOfIntersectionPoints{0};
Point2f p2{ xMax + 10.0f, p.y }; // Horizontal line from point to point outside polygon (p2)
// Count the number of intersection points
float lambda1{}, lambda2{};
for ( size_t i{ 0 }; i < nrVertices; ++i )
{
if ( IntersectLineSegments( vertices[i], vertices[( i + 1 ) % nrVertices], p, p2, lambda1, lambda2 ) )
{
if ( lambda1 > 0 && lambda1 <= 1 && lambda2 > 0 && lambda2 <= 1 )
{
++numberOfIntersectionPoints;
}
}
}
if (numberOfIntersectionPoints % 2 == 0)
{
return false;
}
else
{
return true;
}
}
bool utils::IntersectLineSegments( const Point2f& p1, const Point2f& p2, const Point2f& q1, const Point2f& q2, float& outLambda1, float& outLambda2, float epsilon )
{
bool intersecting{ false };
Vector2f p1p2{ p1, p2 };
Vector2f q1q2{ q1, q2 };
// Cross product to determine if parallel
float denom = p1p2.CrossProduct( q1q2 );
// Don't divide by zero
if ( std::abs( denom ) > epsilon )
{
intersecting = true;
Vector2f p1q1{ p1, q1 };
float num1 = p1q1.CrossProduct( q1q2 );
float num2 = p1q1.CrossProduct( p1p2 );
outLambda1 = num1 / denom;
outLambda2 = num2 / denom;
}
else // are parallel
{
// Connect start points
Vector2f p1q1{ p1, q1 };
// Cross product to determine if segments and the line connecting their start points are parallel,
// if so, than they are on a line
// if not, then there is no intersection
if (std::abs( p1q1.CrossProduct(q1q2) ) > epsilon)
{
return false;
}
// Check the 4 conditions
outLambda1 = 0;
outLambda2 = 0;
if (utils::IsPointOnLineSegment(p1, q1, q2) ||
utils::IsPointOnLineSegment(p2, q1, q2) ||
utils::IsPointOnLineSegment(q1, p1, p2) ||
utils::IsPointOnLineSegment(q2, p1, p2))
{
intersecting = true;
}
}
return intersecting;
}
bool utils::Raycast( const std::vector<Point2f>& vertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo )
{
return Raycast( vertices.data( ), vertices.size( ), rayP1, rayP2, hitInfo );
}
bool utils::Raycast( const Point2f* vertices, const size_t nrVertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo )
{
if ( nrVertices == 0 )
{
return false;
}
std::vector<HitInfo> hits;
Rectf r1, r2;
// r1: minimal AABB rect enclosing the ray
r1.left = std::min( rayP1.x, rayP2.x );
r1.bottom = std::min( rayP1.y, rayP2.y );
r1.width = std::max( rayP1.x, rayP2.x ) - r1.left;
r1.height = std::max( rayP1.y, rayP2.y ) - r1.bottom;
// Line-line intersections.
for ( size_t idx{ 0 }; idx <= nrVertices; ++idx )
{
// Consider line segment between 2 consecutive vertices
// (modulo to allow closed polygon, last - first vertice)
Point2f q1 = vertices[( idx + 0 ) % nrVertices];
Point2f q2 = vertices[( idx + 1 ) % nrVertices];
// r2: minimal AABB rect enclosing the 2 vertices
r2.left = std::min( q1.x, q2.x );
r2.bottom = std::min( q1.y, q2.y );
r2.width = std::max( q1.x, q2.x ) - r2.left;
r2.height = std::max( q1.y, q2.y ) - r2.bottom;
if ( IsOverlapping( r1, r2 ) )
{
float lambda1{};
float lambda2{};
if ( IntersectLineSegments( rayP1, rayP2, q1, q2, lambda1, lambda2 ) )
{
if ( lambda1 > 0 && lambda1 <= 1 && lambda2 > 0 && lambda2 <= 1 )
{
HitInfo linesHitInfo{};
linesHitInfo.lambda = lambda1;
linesHitInfo.intersectPoint = Point2f{ rayP1.x + ( ( rayP2.x - rayP1.x ) * lambda1 ), rayP1.y + ( ( rayP2.y - rayP1.y ) * lambda1 ) };
linesHitInfo.normal = Vector2f{ q2 - q1 }.Orthogonal( ).Normalized( );
hits.push_back(linesHitInfo);
}
}
}
}
if ( hits.size( ) == 0 )
{
return false;
}
// Get closest intersection point and copy it into the hitInfo parameter
hitInfo = *std::min_element
(
hits.begin( ), hits.end( ),
[]( const HitInfo& first, const HitInfo& last )
{
return first.lambda < last.lambda;
}
);
return true;
}
bool utils::IsPointOnLineSegment( const Point2f& p, const Point2f& a, const Point2f& b )
{
Vector2f ap{ a, p }, bp{ b, p };
// If not on same line, return false
if ( abs( ap.CrossProduct( bp ) ) > 0.001f )
{
return false;
}
// Both vectors must point in opposite directions if p is between a and b
if ( ap.DotProduct( bp ) > 0 )
{
return false;
}
return true;
}
float utils::DistPointLineSegment( const Point2f& p, const Point2f& a, const Point2f& b )
{
Vector2f ab{ a, b };
Vector2f ap{ a, p };
Vector2f abNorm{ ab.Normalized() };
float distToA{ abNorm.DotProduct(ap) };
// If distToA is negative, then the closest point is A
// return the distance a, p
if ( distToA < 0 )
{
return ap.Length( );
}
// If distToA is > than dist(a,b) then the closest point is B
// return the distance b, p
float distAB{ ab.Length() };
if ( distToA > distAB )
{
return Vector2f{ b, p }.Length( );
}
// Closest point is between A and B, calc intersection point
Vector2f intersection{ abNorm.DotProduct(ap) * abNorm + Vector2f{ a } };
return Vector2f{ p - intersection }.Length( );
}
bool utils::IntersectRectLine(const Rectf& r, const Point2f& p1, const Point2f& p2, float& intersectMin, float& intersectMax)
{
// Parameters
// input:
// r: axis aligned bounding box, start and end points of line segment.
// p1, p2: line
// output:
// intersectMin and intersectMax: in the interval [0,1] if intersection point is on the line segment.
// return
// true if there is an intersection
// Example of how to use
//float min{};
//float max{};
//if (utils::IntersectRectLine(rect, p1, p2, min, max))
//{
// Point2f intersectP1{ p1 + (Vector2f(p2) - Vector2f(p1)) * min };
// Point2f intersectP2{ p1 + (Vector2f(p2) - Vector2f(p1)) * max };
//}
// 4 floats to convert rect space to line space
// x1: value between 0 and 1 where 0 is on p1 and 1 is on p2, <0 and >1 means intersection is not on line segment
float x1{ (r.left - p1.x) / (p2.x - p1.x) };
float x2{ (r.left + r.width - p1.x) / (p2.x - p1.x) };
float y1{ (r.bottom - p1.y) / (p2.y - p1.y) };
float y2{ (r.bottom + r.height - p1.y) / (p2.y - p1.y) };
using std::max; using std::min;
float tMin{ max(min(x1,x2),min(y1,y2)) };
float tMax{ min(max(x1,x2), max(y1,y2)) };
if (tMin > tMax)
{
return false;
}
intersectMin = tMin;
intersectMax = tMax;
return true;
}
#pragma endregion CollisionFunctionality

86
Engine/utils.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include "Vector2f.h"
namespace utils
{
const float g_Pi{ 3.1415926535f };
#pragma region OpenGLDrawFunctionality
void SetColor( const Color4f& color );
void DrawPoint( float x, float y, float pointSize = 1.0f );
void DrawPoint( const Point2f& p, float pointSize = 1.0f );
void DrawPoints( Point2f *pVertices, int nrVertices, float pointSize = 1.0f );
void DrawLine( float x1, float y1, float x2, float y2, float lineWidth = 1.0f );
void DrawLine( const Point2f& p1, const Point2f& p2, float lineWidth = 1.0f );
void DrawTriangle(const Point2f& p1, const Point2f& p2, const Point2f& p3, float lineWidth = 1);
void FillTriangle(const Point2f& p1, const Point2f& p2, const Point2f& p3);
void DrawRect(float left, float bottom, float width, float height, float lineWidth = 1.0f);
void DrawRect(const Point2f& bottomLeft, float width, float height, float lineWidth = 1.0f);
void DrawRect(const Rectf& rect, float lineWidth = 1.0f);
void FillRect(float left, float bottom, float width, float height);
void FillRect(const Point2f& bottomLeft, float width, float height);
void FillRect(const Rectf& rect);
void DrawEllipse(float centerX, float centerY, float radX, float radY, float lineWidth = 1.0f);
void DrawEllipse(const Point2f& center, float radX, float radY, float lineWidth = 1.0f);
void DrawEllipse(const Ellipsef& ellipse , float lineWidth = 1.0f );
void FillEllipse( float centerX, float centerY, float radX, float radY );
void FillEllipse(const Ellipsef& ellipse );
void FillEllipse(const Point2f& center, float radX, float radY);
// Draws an arc. The angle parameters are in radians, not in degrees.
void DrawArc(float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle, float lineWidth = 1.0f);
// Draws an arc. The angle parameters are in radians, not in degrees.
void DrawArc(const Point2f& center, float radX, float radY, float fromAngle, float tillAngle, float lineWidth = 1.0f);
// Fills an arc. The angle parameters are in radians, not in degrees.
void FillArc(float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle);
// Fills an arc. The angle parameters are in radians, not in degrees.
void FillArc(const Point2f& center, float radX, float radY, float fromAngle, float tillAngle);
void DrawPolygon( const std::vector<Point2f>& vertices, bool closed = true, float lineWidth = 1.0f );
void DrawPolygon( const Point2f* pVertices, size_t nrVertices, bool closed = true, float lineWidth = 1.0f );
void FillPolygon( const std::vector<Point2f>& vertices);
void FillPolygon( const Point2f* pVertices, size_t nrVertices);
#pragma endregion OpenGLDrawFunctionality
#pragma region CollisionFunctionality
struct HitInfo
{
float lambda;
Point2f intersectPoint;
Vector2f normal;
};
float GetDistance(float x1, float y1, float x2, float y2);
float GetDistance(const Point2f& p1, const Point2f& p2);
bool IsPointInRect(const Point2f& p, const Rectf& r);
bool IsPointInCircle(const Point2f& p, const Circlef& c);
bool IsPointInPolygon( const Point2f& p, const std::vector<Point2f>& vertices );
bool IsPointInPolygon( const Point2f& p, const Point2f* vertices, size_t nrVertices );
bool IsOverlapping( const Point2f& a, const Point2f& b, const Circlef& c );
bool IsOverlapping( const Point2f& a, const Point2f& b, const Rectf& r );
bool IsOverlapping(const Rectf & r1, const Rectf & r2);
bool IsOverlapping( const Rectf& r, const Circlef& c );
bool IsOverlapping( const Circlef& c1, const Circlef& c2 );
bool IsOverlapping( const std::vector<Point2f>& vertices, const Circlef& c );
bool IsOverlapping( const Point2f* vertices, size_t nrVertices, const Circlef& c );
bool Raycast( const Point2f* vertices, const size_t nrVertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo );
bool Raycast( const std::vector<Point2f>& vertices, const Point2f& rayP1, const Point2f& rayP2, HitInfo& hitInfo );
bool IntersectLineSegments(const Point2f& p1, const Point2f& p2, const Point2f& q1, const Point2f& q2, float& outLambda1, float& outLambda2, float epsilon = 1e-6);
float DistPointLineSegment(const Point2f& p, const Point2f& a, const Point2f& b);
bool IsPointOnLineSegment(const Point2f& p, const Point2f& a, const Point2f& b);
bool IntersectRectLine(const Rectf& r, const Point2f& p1, const Point2f& p2, float& intersectMin, float& intersectMax);
#pragma endregion CollisionFunctionality
}