From c01e387b89e46244ae91fe81f9dae8d901fa2b75 Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Thu, 6 Apr 2023 21:54:39 -0500 Subject: [PATCH] Add project files. --- Faceball2030.sln | 31 + Faceball2030/Faceball2030.vcxproj | 138 + Faceball2030/Faceball2030.vcxproj.filters | 27 + Faceball2030/High.png | Bin 0 -> 223792 bytes Faceball2030/main.cpp | 673 +++ Faceball2030/pixelGameEngine.h | 6431 +++++++++++++++++++++ 6 files changed, 7300 insertions(+) create mode 100644 Faceball2030.sln create mode 100644 Faceball2030/Faceball2030.vcxproj create mode 100644 Faceball2030/Faceball2030.vcxproj.filters create mode 100644 Faceball2030/High.png create mode 100644 Faceball2030/main.cpp create mode 100644 Faceball2030/pixelGameEngine.h diff --git a/Faceball2030.sln b/Faceball2030.sln new file mode 100644 index 0000000..bef44aa --- /dev/null +++ b/Faceball2030.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Faceball2030", "Faceball2030\Faceball2030.vcxproj", "{735BD839-ABF4-49EC-8A84-FAD819015CCC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Debug|x64.ActiveCfg = Debug|x64 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Debug|x64.Build.0 = Debug|x64 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Debug|x86.ActiveCfg = Debug|Win32 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Debug|x86.Build.0 = Debug|Win32 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Release|x64.ActiveCfg = Release|x64 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Release|x64.Build.0 = Release|x64 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Release|x86.ActiveCfg = Release|Win32 + {735BD839-ABF4-49EC-8A84-FAD819015CCC}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {518BE9CB-D573-4303-9DBB-D322A58CE88A} + EndGlobalSection +EndGlobal diff --git a/Faceball2030/Faceball2030.vcxproj b/Faceball2030/Faceball2030.vcxproj new file mode 100644 index 0000000..ce74af2 --- /dev/null +++ b/Faceball2030/Faceball2030.vcxproj @@ -0,0 +1,138 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {735bd839-abf4-49ec-8a84-fad819015ccc} + Faceball2030 + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/Faceball2030/Faceball2030.vcxproj.filters b/Faceball2030/Faceball2030.vcxproj.filters new file mode 100644 index 0000000..e77fe10 --- /dev/null +++ b/Faceball2030/Faceball2030.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/Faceball2030/High.png b/Faceball2030/High.png new file mode 100644 index 0000000000000000000000000000000000000000..0a9d1316a83078e9a961684496d543230a909109 GIT binary patch literal 223792 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

UdHbe)3Z{&(mz} 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 +#include + +using namespace olc; + +const float PI = 3.14159f; + +struct vec2d +{ + float u = 0; + float v = 0; + float w = 1; +}; + + +struct vec3d +{ + float x = 0; + float y = 0; + float z = 0; + float w = 1; +}; + +struct triangle +{ + vec3d p[3]; + vec2d uv[3]; + Pixel col; +}; + +struct mesh +{ + std::vector tris; + Decal* texture; + + void Parse(std::string str, int& v, int& uv) { + std::cout << str << "\n"; + std::stringstream s(str.substr(0, str.find("/") + 1)); + s >> v; + str.erase(0, str.find("/") + 1); + std::stringstream s2(str.substr(0, str.find("/") + 1)); + s2 >> uv; + //std::cout<<" "< verts; + std::vector uvs; + + std::string data; + while (f.good()) { + f >> data; + if (data == "v") { + float x, y, z; + f >> x >> y >> z; + verts.push_back({ x,y,z }); + //std::cout<> u >> v; + uvs.push_back({ u,v }); + //std::cout<> t1 >> t2 >> t3; + int v1, v2, v3, uv1, uv2, uv3; + Parse(t1, v1, uv1); + Parse(t2, v2, uv2); + Parse(t3, v3, uv3); + tris.push_back({ verts[v1 - 1],verts[v2 - 1],verts[v3 - 1], + uvs[uv1 - 1],uvs[uv2 - 1],uvs[uv3 - 1] }); + } + + } + + return true; + } + +}; + +struct mat4x4 +{ + float m[4][4] = { 0 }; +}; + +class olcEngine3D : public PixelGameEngine +{ + +public: + + Decal* texture; + olcEngine3D() + { + sAppName = "3D Demo"; + } + + +private: + mesh meshCube; + mat4x4 matProj; + + vec3d vCamera = { 0,0,0 }; + vec3d vLookDir; + + float zOffset = 2; + + float fTheta = 0; + float fYaw = 0; + float pitch = -PI / 6; + + vec3d + Matrix_MultiplyVector(mat4x4& m, vec3d& i) + { + vec3d v; + v.x = i.x * m.m[0][0] + i.y * m.m[1][0] + i.z * m.m[2][0] + i.w * m.m[3][0]; + v.y = i.x * m.m[0][1] + i.y * m.m[1][1] + i.z * m.m[2][1] + i.w * m.m[3][1]; + v.z = i.x * m.m[0][2] + i.y * m.m[1][2] + i.z * m.m[2][2] + i.w * m.m[3][2]; + v.w = i.x * m.m[0][3] + i.y * m.m[1][3] + i.z * m.m[2][3] + i.w * m.m[3][3]; + return v; + } + + mat4x4 Matrix_MakeIdentity() + { + mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + mat4x4 Matrix_MakeRotationX(float fAngleRad) + { + mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[1][2] = sinf(fAngleRad); + matrix.m[2][1] = -sinf(fAngleRad); + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + mat4x4 Matrix_MakeRotationY(float fAngleRad) + { + mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][2] = sinf(fAngleRad); + matrix.m[2][0] = -sinf(fAngleRad); + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = cosf(fAngleRad); + matrix.m[3][3] = 1.0f; + return matrix; + } + + mat4x4 Matrix_MakeRotationZ(float fAngleRad) + { + mat4x4 matrix; + matrix.m[0][0] = cosf(fAngleRad); + matrix.m[0][1] = sinf(fAngleRad); + matrix.m[1][0] = -sinf(fAngleRad); + matrix.m[1][1] = cosf(fAngleRad); + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + return matrix; + } + + mat4x4 Matrix_MakeTranslation(float x, float y, float z) + { + mat4x4 matrix; + matrix.m[0][0] = 1.0f; + matrix.m[1][1] = 1.0f; + matrix.m[2][2] = 1.0f; + matrix.m[3][3] = 1.0f; + matrix.m[3][0] = x; + matrix.m[3][1] = y; + matrix.m[3][2] = z; + return matrix; + } + + mat4x4 Matrix_MakeProjection(float fFovDegrees, float fAspectRatio, float fNear, float fFar) + { + float fFovRad = 1.0f / tanf(fFovDegrees * 0.5f / 180.0f * 3.14159f); + mat4x4 matrix; + matrix.m[0][0] = fAspectRatio * fFovRad; + matrix.m[1][1] = fFovRad; + matrix.m[2][2] = fFar / (fFar - fNear); + matrix.m[3][2] = (-fFar * fNear) / (fFar - fNear); + matrix.m[2][3] = 1.0f; + matrix.m[3][3] = 0.0f; + return matrix; + } + + mat4x4 Matrix_MultiplyMatrix(mat4x4& m1, mat4x4& m2) + { + mat4x4 matrix; + for (int c = 0; c < 4; c++) + for (int r = 0; r < 4; r++) + matrix.m[r][c] = m1.m[r][0] * m2.m[0][c] + m1.m[r][1] * m2.m[1][c] + m1.m[r][2] * m2.m[2][c] + m1.m[r][3] * m2.m[3][c]; + return matrix; + } + + mat4x4 Matrix_PointAt(vec3d& pos, vec3d& target, vec3d& up) + { + // Calculate new forward direction + vec3d newForward = Vector_Sub(target, pos); + newForward = Vector_Normalise(newForward); + + // Calculate new Up direction + vec3d a = Vector_Mul(newForward, Vector_DotProduct(up, newForward)); + vec3d newUp = Vector_Sub(up, a); + newUp = Vector_Normalise(newUp); + + // New Right direction is easy, its just cross product + vec3d newRight = Vector_CrossProduct(newUp, newForward); + + // Construct Dimensioning and Translation Matrix + mat4x4 matrix; + matrix.m[0][0] = newRight.x; matrix.m[0][1] = newRight.y; matrix.m[0][2] = newRight.z; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = newUp.x; matrix.m[1][1] = newUp.y; matrix.m[1][2] = newUp.z; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = newForward.x; matrix.m[2][1] = newForward.y; matrix.m[2][2] = newForward.z; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = pos.x; matrix.m[3][1] = pos.y; matrix.m[3][2] = pos.z; matrix.m[3][3] = 1.0f; + return matrix; + + } + + mat4x4 Matrix_QuickInverse(mat4x4& m) // Only for Rotation/Translation Matrices + { + mat4x4 matrix; + matrix.m[0][0] = m.m[0][0]; matrix.m[0][1] = m.m[1][0]; matrix.m[0][2] = m.m[2][0]; matrix.m[0][3] = 0.0f; + matrix.m[1][0] = m.m[0][1]; matrix.m[1][1] = m.m[1][1]; matrix.m[1][2] = m.m[2][1]; matrix.m[1][3] = 0.0f; + matrix.m[2][0] = m.m[0][2]; matrix.m[2][1] = m.m[1][2]; matrix.m[2][2] = m.m[2][2]; matrix.m[2][3] = 0.0f; + matrix.m[3][0] = -(m.m[3][0] * matrix.m[0][0] + m.m[3][1] * matrix.m[1][0] + m.m[3][2] * matrix.m[2][0]); + matrix.m[3][1] = -(m.m[3][0] * matrix.m[0][1] + m.m[3][1] * matrix.m[1][1] + m.m[3][2] * matrix.m[2][1]); + matrix.m[3][2] = -(m.m[3][0] * matrix.m[0][2] + m.m[3][1] * matrix.m[1][2] + m.m[3][2] * matrix.m[2][2]); + matrix.m[3][3] = 1.0f; + return matrix; + } + + vec3d Vector_Add(vec3d& v1, vec3d& v2) + { + return { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + } + + vec3d Vector_Sub(vec3d& v1, vec3d& v2) + { + return { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + } + + vec3d Vector_Mul(vec3d& v1, float k) + { + return { v1.x * k, v1.y * k, v1.z * k }; + } + + vec3d Vector_Div(vec3d& v1, float k) + { + return { v1.x / k, v1.y / k, v1.z / k }; + } + + float Vector_DotProduct(vec3d& v1, vec3d& v2) + { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + } + + float Vector_Length(vec3d& v) + { + return sqrtf(Vector_DotProduct(v, v)); + } + + vec3d Vector_Normalise(vec3d& v) + { + float l = Vector_Length(v); + return { v.x / l, v.y / l, v.z / l }; + } + + vec3d Vector_CrossProduct(vec3d& v1, vec3d& v2) + { + vec3d v; + v.x = v1.y * v2.z - v1.z * v2.y; + v.y = v1.z * v2.x - v1.x * v2.z; + v.z = v1.x * v2.y - v1.y * v2.x; + return v; + } + + vec3d Vector_IntersectPlane(vec3d& plane_p, vec3d& plane_n, vec3d& lineStart, vec3d& lineEnd, float& t) + { + plane_n = Vector_Normalise(plane_n); + float plane_d = -Vector_DotProduct(plane_n, plane_p); + float ad = Vector_DotProduct(lineStart, plane_n); + float bd = Vector_DotProduct(lineEnd, plane_n); + t = (-plane_d - ad) / (bd - ad); + vec3d lineStartToEnd = Vector_Sub(lineEnd, lineStart); + vec3d lineToIntersect = Vector_Mul(lineStartToEnd, t); + return Vector_Add(lineStart, lineToIntersect); + } + + + int Triangle_ClipAgainstPlane(vec3d plane_p, vec3d plane_n, triangle& in_tri, triangle& out_tri1, triangle& out_tri2) + { + // Make sure plane normal is indeed normal + plane_n = Vector_Normalise(plane_n); + + // Return signed shortest distance from point to plane, plane normal must be normalised + auto dist = [&](vec3d& p) + { + vec3d n = Vector_Normalise(p); + return (plane_n.x * p.x + plane_n.y * p.y + plane_n.z * p.z - Vector_DotProduct(plane_n, plane_p)); + }; + + // Create two temporary storage arrays to classify points either side of plane + // If distance sign is positive, point lies on "inside" of plane + vec3d* inside_points[3]; int nInsidePointCount = 0; + vec3d* outside_points[3]; int nOutsidePointCount = 0; + vec2d* inside_tex[3]; int nInsideTexCount = 0; + vec2d* outside_tex[3]; int nOutsideTexCount = 0; + + + // Get signed distance of each point in triangle to plane + float d0 = dist(in_tri.p[0]); + float d1 = dist(in_tri.p[1]); + float d2 = dist(in_tri.p[2]); + + if (d0 >= 0) { inside_points[nInsidePointCount++] = &in_tri.p[0]; inside_tex[nInsideTexCount++] = &in_tri.uv[0]; } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[0]; outside_tex[nOutsideTexCount++] = &in_tri.uv[0]; + } + if (d1 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[1]; inside_tex[nInsideTexCount++] = &in_tri.uv[1]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[1]; outside_tex[nOutsideTexCount++] = &in_tri.uv[1]; + } + if (d2 >= 0) { + inside_points[nInsidePointCount++] = &in_tri.p[2]; inside_tex[nInsideTexCount++] = &in_tri.uv[2]; + } + else { + outside_points[nOutsidePointCount++] = &in_tri.p[2]; outside_tex[nOutsideTexCount++] = &in_tri.uv[2]; + } + + // Now classify triangle points, and break the input triangle into + // smaller output triangles if required. There are four possible + // outcomes... + + if (nInsidePointCount == 0) + { + // All points lie on the outside of plane, so clip whole triangle + // It ceases to exist + + return 0; // No returned triangles are valid + } + + if (nInsidePointCount == 3) + { + // All points lie on the inside of plane, so do nothing + // and allow the triangle to simply pass through + out_tri1 = in_tri; + + return 1; // Just the one returned original triangle is valid + } + + if (nInsidePointCount == 1 && nOutsidePointCount == 2) + { + // Triangle should be clipped. As two points lie outside + // the plane, the triangle simply becomes a smaller triangle + + // Copy appearance info to new triangle + out_tri1.col = in_tri.col; + + // The inside point is valid, so keep that... + out_tri1.p[0] = *inside_points[0]; + out_tri1.uv[0] = *inside_tex[0]; + + // but the two new points are at the locations where the + // original sides of the triangle (lines) intersect with the plane + float t; + out_tri1.p[1] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.uv[1].u = t * (outside_tex[0]->u - inside_tex[0]->u) + inside_tex[0]->u; + out_tri1.uv[1].v = t * (outside_tex[0]->v - inside_tex[0]->v) + inside_tex[0]->v; + out_tri1.uv[1].w = t * (outside_tex[0]->w - inside_tex[0]->w) + inside_tex[0]->w; + + out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[1], t); + out_tri1.uv[2].u = t * (outside_tex[1]->u - inside_tex[0]->u) + inside_tex[0]->u; + out_tri1.uv[2].v = t * (outside_tex[1]->v - inside_tex[0]->v) + inside_tex[0]->v; + out_tri1.uv[2].w = t * (outside_tex[1]->w - inside_tex[0]->w) + inside_tex[0]->w; + + return 1; // Return the newly formed single triangle + } + + if (nInsidePointCount == 2 && nOutsidePointCount == 1) + { + // Triangle should be clipped. As two points lie inside the plane, + // the clipped triangle becomes a "quad". Fortunately, we can + // represent a quad with two new triangles + + // Copy appearance info to new triangles + out_tri1.col = in_tri.col; + out_tri2.col = in_tri.col; + + // The first triangle consists of the two inside points and a new + // point determined by the location where one side of the triangle + // intersects with the plane + out_tri1.p[0] = *inside_points[0]; + out_tri1.p[1] = *inside_points[1]; + out_tri1.uv[0] = *inside_tex[0]; + out_tri1.uv[1] = *inside_tex[1]; + + float t; + out_tri1.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[0], *outside_points[0], t); + out_tri1.uv[2].u = t * (outside_tex[0]->u - inside_tex[0]->u) + inside_tex[0]->u; + out_tri1.uv[2].v = t * (outside_tex[0]->v - inside_tex[0]->v) + inside_tex[0]->v; + out_tri1.uv[2].w = t * (outside_tex[0]->w - inside_tex[0]->w) + inside_tex[0]->w; + + // The second triangle is composed of one of he inside points, a + // new point determined by the intersection of the other side of the + // triangle and the plane, and the newly created point above + out_tri2.p[0] = *inside_points[1]; + out_tri2.uv[0] = *inside_tex[1]; + out_tri2.p[1] = out_tri1.p[2]; + out_tri2.uv[1] = out_tri1.uv[2]; + out_tri2.p[2] = Vector_IntersectPlane(plane_p, plane_n, *inside_points[1], *outside_points[0], t); + out_tri2.uv[2].u = t * (outside_tex[0]->u - inside_tex[1]->u) + inside_tex[1]->u; + out_tri2.uv[2].v = t * (outside_tex[0]->v - inside_tex[1]->v) + inside_tex[1]->v; + out_tri2.uv[2].w = t * (outside_tex[0]->w - inside_tex[1]->w) + inside_tex[1]->w; + return 2; // Return two newly formed triangles which form a quad + } + } + + +public: + bool OnUserCreate() override + { + texture = new Decal(new Sprite("High.png")); + meshCube.LoadFromObjectFile("Artisans Hub.obj"); + + matProj = Matrix_MakeProjection(90.0f, (float)ScreenHeight() / (float)ScreenWidth(), 0.1f, 1000.0f); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + if (GetKey(olc::DOWN).bHeld) { + pitch -= 1 * fElapsedTime; + } + if (GetKey(olc::UP).bHeld) { + pitch += 1 * fElapsedTime; + } + vec3d vForward = Vector_Mul(vLookDir, 20 * fElapsedTime); + if (GetKey(olc::W).bHeld) { + vCamera = Vector_Add(vCamera, vForward); + } + if (GetKey(olc::S).bHeld) { + vCamera = Vector_Sub(vCamera, vForward); + } + if (GetKey(olc::A).bHeld) { + fYaw -= 2 * fElapsedTime; + } + if (GetKey(olc::D).bHeld) { + fYaw += 2 * fElapsedTime; + } + + // Set up rotation matrices + mat4x4 matRotZ, matRotX, matTrans, matWorld; + + matRotZ = Matrix_MakeRotationZ(fTheta * 0.5f); + matRotX = Matrix_MakeRotationX(fTheta); + + matTrans = Matrix_MakeTranslation(0.0f, 0.0f, 5.0f); + matWorld = Matrix_MakeIdentity(); + matWorld = Matrix_MultiplyMatrix(matRotZ, matRotX); + matWorld = Matrix_MultiplyMatrix(matWorld, matTrans); + + vec3d vUp = { 0,1,0 }; + vec3d vTarget = { 0,sinf(pitch),cosf(pitch) }; + mat4x4 matCameraRot = Matrix_MakeRotationY(fYaw); + vLookDir = Matrix_MultiplyVector(matCameraRot, vTarget); + vTarget = Vector_Add(vCamera, vLookDir); + mat4x4 matCamera = Matrix_PointAt(vCamera, vTarget, vUp); + mat4x4 matView = Matrix_QuickInverse(matCamera); + + std::vectorvecTrianglesToRaster; + + // Draw Triangles + for (auto& tri : meshCube.tris) + { + triangle triProjected, triTransformed, triViewed; + + triTransformed.p[0] = Matrix_MultiplyVector(matWorld, tri.p[0]); + triTransformed.p[1] = Matrix_MultiplyVector(matWorld, tri.p[1]); + triTransformed.p[2] = Matrix_MultiplyVector(matWorld, tri.p[2]); + triTransformed.uv[0] = tri.uv[0]; + triTransformed.uv[1] = tri.uv[1]; + triTransformed.uv[2] = tri.uv[2]; + + vec3d normal, line1, line2; + line1 = Vector_Sub(triTransformed.p[1], triTransformed.p[0]); + line2 = Vector_Sub(triTransformed.p[2], triTransformed.p[0]); + + normal = Vector_CrossProduct(line1, line2); + normal = Vector_Normalise(normal); + + vec3d vCameraRay = Vector_Sub(triTransformed.p[0], vCamera); + + if (Vector_DotProduct(normal, vCameraRay) < 0) { + vec3d light_dir = Vector_Mul(vLookDir, -1); + light_dir = Vector_Normalise(light_dir); + + float dp = std::max(0.7f, Vector_DotProduct(light_dir, normal)); + + triViewed.p[0] = Matrix_MultiplyVector(matView, triTransformed.p[0]); + triViewed.p[1] = Matrix_MultiplyVector(matView, triTransformed.p[1]); + triViewed.p[2] = Matrix_MultiplyVector(matView, triTransformed.p[2]); + triViewed.uv[0] = triTransformed.uv[0]; + triViewed.uv[1] = triTransformed.uv[1]; + triViewed.uv[2] = triTransformed.uv[2]; + triViewed.col = Pixel(255 * dp * dp, 255 * dp * dp, 255 * dp * dp); + + int nClippedTriangles = 0; + triangle clipped[2]; + nClippedTriangles = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.1f }, { 0.0f, 0.0f, 1.0f }, triViewed, clipped[0], clipped[1]); + + for (int n = 0; n < nClippedTriangles; n++) { + // Project triangles from 3D --> 2D + triProjected.p[0] = Matrix_MultiplyVector(matProj, clipped[n].p[0]); + triProjected.p[1] = Matrix_MultiplyVector(matProj, clipped[n].p[1]); + triProjected.p[2] = Matrix_MultiplyVector(matProj, clipped[n].p[2]); + triProjected.col = clipped[n].col; + triProjected.uv[0] = clipped[n].uv[0]; + triProjected.uv[1] = clipped[n].uv[1]; + triProjected.uv[2] = clipped[n].uv[2]; + triProjected.uv[0].u = triProjected.uv[0].u / triProjected.p[0].w; + triProjected.uv[1].u = triProjected.uv[1].u / triProjected.p[1].w; + triProjected.uv[2].u = triProjected.uv[2].u / triProjected.p[2].w; + + triProjected.uv[0].v = triProjected.uv[0].v / triProjected.p[0].w; + triProjected.uv[1].v = triProjected.uv[1].v / triProjected.p[1].w; + triProjected.uv[2].v = triProjected.uv[2].v / triProjected.p[2].w; + + triProjected.uv[0].w = 1.0f / triProjected.p[0].w; + triProjected.uv[1].w = 1.0f / triProjected.p[1].w; + triProjected.uv[2].w = 1.0f / triProjected.p[2].w; + + + triProjected.p[0] = Vector_Div(triProjected.p[0], triProjected.p[0].w); + triProjected.p[1] = Vector_Div(triProjected.p[1], triProjected.p[1].w); + triProjected.p[2] = Vector_Div(triProjected.p[2], triProjected.p[2].w); + + triProjected.p[0].x *= -1.0f; + triProjected.p[1].x *= -1.0f; + triProjected.p[2].x *= -1.0f; + triProjected.p[0].y *= -1.0f; + triProjected.p[1].y *= -1.0f; + triProjected.p[2].y *= -1.0f; + + // Scale into view + vec3d vOffsetView = { 1,1,0 }; + triProjected.p[0] = Vector_Add(triProjected.p[0], vOffsetView); + triProjected.p[1] = Vector_Add(triProjected.p[1], vOffsetView); + triProjected.p[2] = Vector_Add(triProjected.p[2], vOffsetView); + triProjected.p[0].x *= 0.5f * (float)ScreenWidth(); + triProjected.p[0].y *= 0.5f * (float)ScreenHeight(); + triProjected.p[1].x *= 0.5f * (float)ScreenWidth(); + triProjected.p[1].y *= 0.5f * (float)ScreenHeight(); + triProjected.p[2].x *= 0.5f * (float)ScreenWidth(); + triProjected.p[2].y *= 0.5f * (float)ScreenHeight(); + + vecTrianglesToRaster.push_back(triProjected); + } + } + } + + //std::sort(vecTrianglesToRaster.begin(),vecTrianglesToRaster.end(),[](triangle&t1,triangle&t2){return (t1.p[0].z+t1.p[1].z+t1.p[2].z)/3.0f>(t2.p[0].z+t2.p[1].z+t2.p[2].z)/3.0f;}); + ClearBuffer(BLACK, true); + int triRenderCount = 0; + for (auto& triToRaster : vecTrianglesToRaster) { + + triangle clipped[2]; + std::listlistTriangles; + listTriangles.push_back(triToRaster); + int nNewTriangles = 1; + + for (int p = 0; p < 4; p++) + { + int nTrisToAdd = 0; + while (nNewTriangles > 0) + { + // Take triangle from front of queue + triangle test = listTriangles.front(); + listTriangles.pop_front(); + nNewTriangles--; + + // Clip it against a plane. We only need to test each + // subsequent plane, against subsequent new triangles + // as all triangles after a plane clip are guaranteed + // to lie on the inside of the plane. I like how this + // comment is almost completely and utterly justified + switch (p) + { + case 0: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, test, clipped[0], clipped[1]); break; + case 1: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, (float)ScreenHeight() - 1, 0.0f }, { 0.0f, -1.0f, 0.0f }, test, clipped[0], clipped[1]); break; + case 2: nTrisToAdd = Triangle_ClipAgainstPlane({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break; + case 3: nTrisToAdd = Triangle_ClipAgainstPlane({ (float)ScreenWidth() - 1, 0.0f, 0.0f }, { -1.0f, 0.0f, 0.0f }, test, clipped[0], clipped[1]); break; + } + + // Clipping may yield a variable number of triangles, so + // add these new ones to the back of the queue for subsequent + // clipping against next planes + for (int w = 0; w < nTrisToAdd; w++) + listTriangles.push_back(clipped[w]); + } + nNewTriangles = listTriangles.size(); + } + + for (auto& t : listTriangles) { + // Rasterize triangle + SetDecalStructure(DecalStructure::LIST); + SetDecalMode(DecalMode::NORMAL); + DrawPolygonDecal(texture, { + {t.p[0].x, t.p[0].y}, + {t.p[1].x, t.p[1].y}, + {t.p[2].x, t.p[2].y} + }, { + {t.uv[0].u,t.uv[0].v}, + {t.uv[1].u,t.uv[1].v}, + {t.uv[2].u,t.uv[2].v}, + }, { t.uv[0].w,t.uv[1].w,t.uv[2].w }, { t.p[0].z,t.p[1].z,t.p[2].z }, { t.col,t.col,t.col }); + /*SetDecalMode(DecalMode::WIREFRAME); + DrawPolygonDecal(nullptr,{ + {t.p[0].x, t.p[0].y}, + {t.p[1].x, t.p[1].y}, + {t.p[2].x, t.p[2].y} + },{ + {0,0}, + {0,0}, + {0,0}, + },WHITE);*/ + SetDecalStructure(DecalStructure::FAN); + triRenderCount++; + } + } + + SetDecalMode(DecalMode::NORMAL); + DrawStringDecal({ 0,0 }, "Triangles: " + std::to_string(triRenderCount), WHITE, { 2,2 }); + + + return true; + } + +}; + + + + +int main() +{ + olcEngine3D demo; + if (demo.Construct(1280, 720, 1, 1)) + demo.Start(); + return 0; +} diff --git a/Faceball2030/pixelGameEngine.h b/Faceball2030/pixelGameEngine.h new file mode 100644 index 0000000..89bd0bf --- /dev/null +++ b/Faceball2030/pixelGameEngine.h @@ -0,0 +1,6431 @@ +#pragma region license_and_help +/* + olcPixelGameEngine.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v2.19 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2022 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + Twitter: https://www.twitter.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 + Community: https://community.onelonecoder.com + + + + Compiling in Linux + ~~~~~~~~~~~~~~~~~~ + You will need a modern C++ compiler, so update yours! + To compile use the command: + + g++ -o YourProgName YourSource.cpp -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17 + + On some Linux configurations, the frame rate is locked to the refresh + rate of the monitor. This engine tries to unlock it but may not be + able to, in which case try launching your program like this: + + vblank_mode=0 ./YourProgName + + + + Compiling in Code::Blocks on Windows + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Well I wont judge you, but make sure your Code::Blocks installation + is really up to date - you may even consider updating your C++ toolchain + to use MinGW32-W64. + + Guide for installing recent GCC for Windows: + https://www.msys2.org/ + Guide for configuring code::blocks: + https://solarianprogrammer.com/2019/11/05/install-gcc-windows/ + https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/ + + Add these libraries to "Linker Options": + user32 gdi32 opengl32 gdiplus Shlwapi dwmapi stdc++fs + + Set these compiler options: -std=c++17 + + + + Compiling on Mac - EXPERIMENTAL! PROBABLY HAS BUGS + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Yes yes, people use Macs for C++ programming! Who knew? Anyway, enough + arguing, thanks to Mumflr the PGE is now supported on Mac. Now I know nothing + about Mac, so if you need support, I suggest checking out the instructions + here: https://github.com/MumflrFumperdink/olcPGEMac + + clang++ -arch x86_64 -std=c++17 -mmacosx-version-min=10.15 -Wall -framework OpenGL + -framework GLUT -framework Carbon -lpng YourSource.cpp -o YourProgName + + + + Compiling with Emscripten (New & Experimental) + ~~~~~~~~~~~~~~~~~~~~~~~~~ + Emscripten compiler will turn your awesome C++ PixelGameEngine project into WASM! + This means you can run your application in teh browser, great for distributing + and submission in to jams and things! It's a bit new at the moment. + + em++ -std=c++17 -O2 -s ALLOW_MEMORY_GROWTH=1 -s MAX_WEBGL_VERSION=2 -s MIN_WEBGL_VERSION=2 -s USE_LIBPNG=1 ./YourSource.cpp -o pge.html + + + + Using stb_image.h + ~~~~~~~~~~~~~~~~~ + The PGE will load png images by default (with help from libpng on non-windows systems). + However, the excellent "stb_image.h" can be used instead, supporting a variety of + image formats, and has no library dependence - something we like at OLC studios ;) + To use stb_image.h, make sure it's in your code base, and simply: + + #define OLC_IMAGE_STB + + Before including the olcPixelGameEngine.h header file. stb_image.h works on many systems + and can be downloaded here: https://github.com/nothings/stb/blob/master/stb_image.h + + + + Multiple cpp file projects? + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + As a single header solution, the OLC_PGE_APPLICATION definition is used to + insert the engine implementation at a project location of your choosing. + The simplest way to setup multifile projects is to create a file called + "olcPixelGameEngine.cpp" which includes the following: + + #define OLC_PGE_APPLICATION + #include "olcPixelGameEngine.h" + + That's all it should include. You can also include PGEX includes and + defines in here too. With this in place, you dont need to + #define OLC_PGE_APPLICATION anywhere, and can simply include this + header file as an when you need to. + + + + Ports + ~~~~~ + olc::PixelGameEngine has been ported and tested with varying degrees of + success to: WinXP, Win7, Win8, Win10, Various Linux, Raspberry Pi, + Chromebook, Playstation Portable (PSP) and Nintendo Switch. If you are + interested in the details of these ports, come and visit the Discord! + + + + Thanks + ~~~~~~ + I'd like to extend thanks to Ian McKay, Bispoo, Eremiell, slavka, Kwizatz77, gurkanctn, Phantim, + IProgramInCPP, JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice, + dandistine, Ralakus, Gorbit99, raoul, joshinils, benedani, Moros1138, Alexio, SaladinAkara + & MagetzUb for advice, ideas and testing, and I'd like to extend my appreciation to the + 250K YouTube followers, 80+ Patreons, 4.8K Twitch followers and 10K Discord server members + who give me the motivation to keep going with all this :D + + Significant Contributors: @Moros1138, @SaladinAkara, @MaGetzUb, @slavka, + @Dragoneye, @Gorbit99, @dandistine & @Mumflr + + Special thanks to those who bring gifts! + GnarGnarHead.......Domina + Gorbit99...........Bastion, Ori & The Blind Forest, Terraria, Spelunky 2, Skully + Marti Morta........Gris + Danicron...........Terraria + SaladinAkara.......Aseprite, Inside, Quern: Undying Thoughts, Outer Wilds + AlterEgo...........Final Fantasy XII - The Zodiac Age + SlicEnDicE.........Noita, Inside + TGD................Voucher Gift + + Special thanks to my Patreons too - I wont name you on here, but I've + certainly enjoyed my tea and flapjacks :D + + + + Author + ~~~~~~ + David Barr, aka javidx9, �OneLoneCoder 2018, 2019, 2020, 2021, 2022 +*/ +#pragma endregion + +#pragma region version_history +/* + 2.01: Made renderer and platform static for multifile projects + 2.02: Added Decal destructor, optimised Pixel constructor + 2.03: Added FreeBSD flags, Added DrawStringDecal() + 2.04: Windows Full-Screen bug fixed + 2.05: +DrawPartialWarpedDecal() - draws a warped decal from a subset image + +DrawPartialRotatedDecal() - draws a rotated decal from a subset image + 2.06: +GetTextSize() - returns area occupied by multiline string + +GetWindowSize() - returns actual window size + +GetElapsedTime() - returns last calculated fElapsedTime + +GetWindowMouse() - returns actual mouse location in window + +DrawExplicitDecal() - bow-chikka-bow-bow + +DrawPartialDecal(pos, size) - draws a partial decal to specified area + +FillRectDecal() - draws a flat shaded rectangle as a decal + +GradientFillRectDecal() - draws a rectangle, with unique colour corners + +Modified DrawCircle() & FillCircle() - Thanks IanM-Matrix1 (#PR121) + +Gone someway to appeasing pedants + 2.07: +GetPixelSize() - returns user specified pixel size + +GetScreenPixelSize() - returns actual size in monitor pixels + +Pixel Cohesion Mode (flag in Construct()) - disallows arbitrary window scaling + +Working VSYNC in Windows windowed application - now much smoother + +Added string conversion for olc::vectors + +Added comparator operators for olc::vectors + +Added DestroyWindow() on windows platforms for serial PGE launches + +Added GetMousePos() to stop TarriestPython whinging + 2.08: Fix SetScreenSize() aspect ratio pre-calculation + Fix DrawExplicitDecal() - stupid oversight with multiple decals + Disabled olc::Sprite copy constructor + +olc::Sprite Duplicate() - produces a new clone of the sprite + +olc::Sprite Duplicate(pos, size) - produces a new sprite from the region defined + +Unary operators for vectors + +More pedant mollification - Thanks TheLandfill + +ImageLoader modules - user selectable image handling core, gdi+, libpng, stb_image + +Mac Support via GLUT - thanks Mumflr! + 2.09: Fix olc::Renderable Image load error - Thanks MaGetzUb & Zij-IT for finding and moaning about it + Fix file rejection in image loaders when using resource packs + Tidied Compiler defines per platform - Thanks slavka + +Pedant fixes, const correctness in parts + +DecalModes - Normal, Additive, Multiplicative blend modes + +Pixel Operators & Lerping + +Filtered Decals - If you hate pixels, then erase this file + +DrawStringProp(), GetTextSizeProp(), DrawStringPropDecal() - Draws non-monospaced font + 2.10: Fix PixelLerp() - oops my bad, lerped the wrong way :P + Fix "Shader" support for strings - thanks Megarev for crying about it + Fix GetTextSizeProp() - Height was just plain wrong... + +vec2d operator overloads (element wise *=, /=) + +vec2d comparison operators... :| yup... hmmmm... + +vec2d ceil(), floor(), min(), max() functions - surprising how often I do it manually + +DrawExplicitDecal(... uint32_t elements) - complete control over convex polygons and lines + +DrawPolygonDecal() - to keep Bispoo happy, required significant rewrite of EVERYTHING, but hey ho + +Complete rewrite of decal renderer + +OpenGL 3.3 Renderer (also supports Raspberry Pi) + +PGEX Break-In Hooks - with a push from Dandistine + +Wireframe Decal Mode - For debug overlays + 2.11: Made PGEX hooks optional - (provide true to super constructor) + 2.12: Fix for MinGW compiler non-compliance :( - why is its sdk structure different?? why??? + 2.13: +GetFontSprite() - allows access to font data + 2.14: Fix WIN32 Definition reshuffle + Fix DrawPartialDecal() - messed up dimension during renderer experiment, didnt remove junk code, thanks Alexio + Fix? Strange error regarding GDI+ Image Loader not knowing about COM, SDK change? + 2.15: Big Reformat + +WASM Platform (via Emscripten) - Big Thanks to OLC Community - See Platform for details + +Sample Mode for Decals + +Made olc_ConfigureSystem() accessible + +Added OLC_----_CUSTOM_EX for externalised platforms, renderers and image loaders + =Refactored olc::Sprite pixel data store + -Deprecating LoadFromPGESprFile() + -Deprecating SaveToPGESprFile() + Fix Pixel -= operator (thanks Au Lit) + 2.16: FIX Emscripten JS formatting in VS IDE (thanks Moros) + +"Headless" Mode + +DrawLineDecal() + +Mouse Button Constants + +Move Constructor for olc::Renderable + +Polar/Cartesian conversion for v2d_generic + +DrawRotatedStringDecal()/DrawRotatedStringPropDecal() (thanks Oso-Grande/Sopadeoso (PR #209)) + =Using olc::Renderable for layer surface + +Major Mac and GLUT Update (thanks Mumflr) + 2.17: +Clipping for DrawLine() functions + +Reintroduced sub-pixel decals + +Modified DrawPartialDecal() to quantise and correctly sample from tile atlasses + +olc::Sprite::GetPixel() - Clamp Mode + 2.18: +Option to not "dirty" layers with SetDrawTarget() - Thanks TerasKasi! + =Detection for Mac M1, fix for scroll wheel interrogation - Thanks ruarq! + 2.19: Textual Input(of)course Edition! + =Built in font is now olc::Renderable + +EnablePixelTransfer() - Gate if layer content transfers occur (speedup in decal only apps) + +TextEntryEnable() - Enables/Disables text entry mode + +TextEntryGetString() - Gets the current accumulated string in text entry mode + +TextEntryGetCursor() - Gets the current cursor position in text entry mode + +IsTextEntryEnabled() - Returns true if text entry mode is activated + +OnTextEntryComplete() - Override is called when user presses "ENTER" in text entry mode + +Potential for regional keyboard mappings - needs volunteers to do this + +ConsoleShow() - Opens built in command console + +ConsoleClear() - Clears built in command console output + +ConsoleOut() - Stream strings to command console output + +ConsoleCaptureStdOut() - Capture std::cout by redirecting to built-in console + +IsConsoleShowing() - Returns true if console is currently active + +OnConsoleCommand() - Override is called when command is entered into built in console + + !! Apple Platforms will not see these updates immediately - Sorry, I dont have a mac to test... !! + !! Volunteers willing to help appreciated, though PRs are manually integrated with credit !! +*/ +#pragma endregion + +#pragma region hello_world_example +// O------------------------------------------------------------------------------O +// | Example "Hello World" Program (main.cpp) | +// O------------------------------------------------------------------------------O +/* + +#define OLC_PGE_APPLICATION +#include "olcPixelGameEngine.h" + +// Override base class with your custom functionality +class Example : public olc::PixelGameEngine +{ +public: + Example() + { + // Name your application + sAppName = "Example"; + } + +public: + bool OnUserCreate() override + { + // Called once at the start, so create things here + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + // Called once per frame, draws random coloured pixels + for (int x = 0; x < ScreenWidth(); x++) + for (int y = 0; y < ScreenHeight(); y++) + Draw(x, y, olc::Pixel(rand() % 256, rand() % 256, rand() % 256)); + return true; + } +}; + +int main() +{ + Example demo; + if (demo.Construct(256, 240, 4, 4)) + demo.Start(); + return 0; +} + +*/ +#pragma endregion + +#ifndef OLC_PGE_DEF +#define OLC_PGE_DEF + +#pragma region std_includes +// O------------------------------------------------------------------------------O +// | STANDARD INCLUDES | +// O------------------------------------------------------------------------------O +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma endregion + +#define PGE_VER 219 + +// O------------------------------------------------------------------------------O +// | COMPILER CONFIGURATION ODDITIES | +// O------------------------------------------------------------------------------O +#pragma region compiler_config +#define USE_EXPERIMENTAL_FS +#if defined(_WIN32) +#if _MSC_VER >= 1920 && _MSVC_LANG >= 201703L +#undef USE_EXPERIMENTAL_FS +#endif +#endif +#if defined(__linux__) || defined(__MINGW32__) || defined(__EMSCRIPTEN__) || defined(__FreeBSD__) || defined(__APPLE__) +#if __cplusplus >= 201703L +#undef USE_EXPERIMENTAL_FS +#endif +#endif + +#if !defined(OLC_KEYBOARD_UK) +#define OLC_KEYBOARD_UK +#endif + + +#if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS) + // C++14 +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#include +namespace _gfs = std::experimental::filesystem::v1; +#else + // C++17 +#include +namespace _gfs = std::filesystem; +#endif + +#if defined(UNICODE) || defined(_UNICODE) +#define olcT(s) L##s +#else +#define olcT(s) s +#endif + +#define UNUSED(x) (void)(x) + +// O------------------------------------------------------------------------------O +// | PLATFORM SELECTION CODE, Thanks slavka! | +// O------------------------------------------------------------------------------O + +// Platform +#if !defined(OLC_PLATFORM_WINAPI) && !defined(OLC_PLATFORM_X11) && !defined(OLC_PLATFORM_GLUT) && !defined(OLC_PLATFORM_EMSCRIPTEN) +#if !defined(OLC_PLATFORM_CUSTOM_EX) +#if defined(_WIN32) +#define OLC_PLATFORM_WINAPI +#endif +#if defined(__linux__) || defined(__FreeBSD__) +#define OLC_PLATFORM_X11 +#endif +#if defined(__APPLE__) +#define GL_SILENCE_DEPRECATION +#define OLC_PLATFORM_GLUT +#endif +#if defined(__EMSCRIPTEN__) +#define OLC_PLATFORM_EMSCRIPTEN +#endif +#endif +#endif + +// Start Situation +#if defined(OLC_PLATFORM_GLUT) || defined(OLC_PLATFORM_EMSCRIPTEN) +#define PGE_USE_CUSTOM_START +#endif + +// Renderer +#if !defined(OLC_GFX_OPENGL10) && !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10) +#if !defined(OLC_GFX_CUSTOM_EX) +#if defined(OLC_PLATFORM_EMSCRIPTEN) +#define OLC_GFX_OPENGL33 +#else +#define OLC_GFX_OPENGL10 +#endif +#endif +#endif + +// Image loader +#if !defined(OLC_IMAGE_STB) && !defined(OLC_IMAGE_GDI) && !defined(OLC_IMAGE_LIBPNG) +#if !defined(OLC_IMAGE_CUSTOM_EX) +#if defined(_WIN32) +#define OLC_IMAGE_GDI +#endif +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) +#define OLC_IMAGE_LIBPNG +#endif +#endif +#endif + + +// O------------------------------------------------------------------------------O +// | PLATFORM-SPECIFIC DEPENDENCIES | +// O------------------------------------------------------------------------------O +#if !defined(OLC_PGE_HEADLESS) +#if defined(OLC_PLATFORM_WINAPI) +#define _WINSOCKAPI_ // Thanks Cornchipss +#if !defined(VC_EXTRALEAN) +#define VC_EXTRALEAN +#endif +#if !defined(NOMINMAX) +#define NOMINMAX +#endif + +// In Code::Blocks +#if !defined(_WIN32_WINNT) +#ifdef HAVE_MSMF +#define _WIN32_WINNT 0x0600 // Windows Vista +#else +#define _WIN32_WINNT 0x0500 // Windows 2000 +#endif +#endif + +#include +#undef _WINSOCKAPI_ +#endif + +#if defined(OLC_PLATFORM_X11) +namespace X11 +{ +#include +#include +} +#endif + +#if defined(OLC_PLATFORM_GLUT) +#if defined(__linux__) +#include +#include +#endif +#if defined(__APPLE__) +#include +#include +#include +#endif +#endif +#endif +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE DECLARATION | +// O------------------------------------------------------------------------------O +#pragma region pge_declaration +namespace olc +{ + class PixelGameEngine; + class Sprite; + + // Pixel Game Engine Advanced Configuration + constexpr uint8_t nMouseButtons = 5; + constexpr uint8_t nDefaultAlpha = 0xFF; + constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24); + constexpr uint8_t nTabSizeInSpaces = 4; + enum rcode { FAIL = 0, OK = 1, NO_FILE = -1 }; + + // O------------------------------------------------------------------------------O + // | olc::Pixel - Represents a 32-Bit RGBA colour | + // O------------------------------------------------------------------------------O + struct Pixel + { + union + { + uint32_t n = nDefaultPixel; + struct { uint8_t r; uint8_t g; uint8_t b; uint8_t a; }; + }; + + enum Mode { NORMAL, MASK, ALPHA, CUSTOM }; + + Pixel(); + Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = nDefaultAlpha); + Pixel(uint32_t p); + Pixel& operator = (const Pixel& v) = default; + bool operator ==(const Pixel& p) const; + bool operator !=(const Pixel& p) const; + Pixel operator * (const float i) const; + Pixel operator / (const float i) const; + Pixel& operator *=(const float i); + Pixel& operator /=(const float i); + Pixel operator + (const Pixel& p) const; + Pixel operator - (const Pixel& p) const; + Pixel& operator +=(const Pixel& p); + Pixel& operator -=(const Pixel& p); + Pixel inv() const; + }; + + Pixel PixelF(float red, float green, float blue, float alpha = 1.0f); + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t); + + + // O------------------------------------------------------------------------------O + // | USEFUL CONSTANTS | + // O------------------------------------------------------------------------------O + static const Pixel + GREY(192, 192, 192), DARK_GREY(128, 128, 128), VERY_DARK_GREY(64, 64, 64), + RED(255, 0, 0), DARK_RED(128, 0, 0), VERY_DARK_RED(64, 0, 0), + YELLOW(255, 255, 0), DARK_YELLOW(128, 128, 0), VERY_DARK_YELLOW(64, 64, 0), + GREEN(0, 255, 0), DARK_GREEN(0, 128, 0), VERY_DARK_GREEN(0, 64, 0), + CYAN(0, 255, 255), DARK_CYAN(0, 128, 128), VERY_DARK_CYAN(0, 64, 64), + BLUE(0, 0, 255), DARK_BLUE(0, 0, 128), VERY_DARK_BLUE(0, 0, 64), + MAGENTA(255, 0, 255), DARK_MAGENTA(128, 0, 128), VERY_DARK_MAGENTA(64, 0, 64), + WHITE(255, 255, 255), BLACK(0, 0, 0), BLANK(0, 0, 0, 0); + + // Thanks to scripticuk and others for updating the key maps + // NOTE: The GLUT platform will need updating, open to contributions ;) + enum Key + { + NONE, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + K0, K1, K2, K3, K4, K5, K6, K7, K8, K9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + UP, DOWN, LEFT, RIGHT, + SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, + BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, + NP0, NP1, NP2, NP3, NP4, NP5, NP6, NP7, NP8, NP9, + NP_MUL, NP_DIV, NP_ADD, NP_SUB, NP_DECIMAL, PERIOD, + EQUALS, COMMA, MINUS, + OEM_1, OEM_2, OEM_3, OEM_4, OEM_5, OEM_6, OEM_7, OEM_8, + CAPS_LOCK, ENUM_END + }; + + namespace Mouse + { + static constexpr int32_t LEFT = 0; + static constexpr int32_t RIGHT = 1; + static constexpr int32_t MIDDLE = 2; + }; + + // O------------------------------------------------------------------------------O + // | olc::HWButton - Represents the state of a hardware button (mouse/key/joy) | + // O------------------------------------------------------------------------------O + struct HWButton + { + bool bPressed = false; // Set once during the frame the event occurs + bool bReleased = false; // Set once during the frame the event occurs + bool bHeld = false; // Set true for all frames between pressed and released events + }; + + + + + // O------------------------------------------------------------------------------O + // | olc::vX2d - A generic 2D vector type | + // O------------------------------------------------------------------------------O +#if !defined(OLC_IGNORE_VEC2D) + template + struct v2d_generic + { + T x = 0; + T y = 0; + v2d_generic() : x(0), y(0) {} + v2d_generic(T _x, T _y) : x(_x), y(_y) {} + v2d_generic(const v2d_generic& v) : x(v.x), y(v.y) {} + v2d_generic& operator=(const v2d_generic& v) = default; + T mag() const { return T(std::sqrt(x * x + y * y)); } + T mag2() const { return x * x + y * y; } + v2d_generic norm() const { T r = 1 / mag(); return v2d_generic(x * r, y * r); } + v2d_generic perp() const { return v2d_generic(-y, x); } + v2d_generic floor() const { return v2d_generic(std::floor(x), std::floor(y)); } + v2d_generic ceil() const { return v2d_generic(std::ceil(x), std::ceil(y)); } + v2d_generic max(const v2d_generic& v) const { return v2d_generic(std::max(x, v.x), std::max(y, v.y)); } + v2d_generic min(const v2d_generic& v) const { return v2d_generic(std::min(x, v.x), std::min(y, v.y)); } + v2d_generic cart() { return { std::cos(y) * x, std::sin(y) * x }; } + v2d_generic polar() { return { mag(), std::atan2(y, x) }; } + T dot(const v2d_generic& rhs) const { return this->x * rhs.x + this->y * rhs.y; } + T cross(const v2d_generic& rhs) const { return this->x * rhs.y - this->y * rhs.x; } + v2d_generic operator + (const v2d_generic& rhs) const { return v2d_generic(this->x + rhs.x, this->y + rhs.y); } + v2d_generic operator - (const v2d_generic& rhs) const { return v2d_generic(this->x - rhs.x, this->y - rhs.y); } + v2d_generic operator * (const T& rhs) const { return v2d_generic(this->x * rhs, this->y * rhs); } + v2d_generic operator * (const v2d_generic& rhs) const { return v2d_generic(this->x * rhs.x, this->y * rhs.y); } + v2d_generic operator / (const T& rhs) const { return v2d_generic(this->x / rhs, this->y / rhs); } + v2d_generic operator / (const v2d_generic& rhs) const { return v2d_generic(this->x / rhs.x, this->y / rhs.y); } + v2d_generic& operator += (const v2d_generic& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } + v2d_generic& operator -= (const v2d_generic& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } + v2d_generic& operator *= (const T& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + v2d_generic& operator /= (const T& rhs) { this->x /= rhs; this->y /= rhs; return *this; } + v2d_generic& operator *= (const v2d_generic& rhs) { this->x *= rhs.x; this->y *= rhs.y; return *this; } + v2d_generic& operator /= (const v2d_generic& rhs) { this->x /= rhs.x; this->y /= rhs.y; return *this; } + v2d_generic operator + () const { return { +x, +y }; } + v2d_generic operator - () const { return { -x, -y }; } + bool operator == (const v2d_generic& rhs) const { return (this->x == rhs.x && this->y == rhs.y); } + bool operator != (const v2d_generic& rhs) const { return (this->x != rhs.x || this->y != rhs.y); } + const std::string str() const { return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + ")"; } + friend std::ostream& operator << (std::ostream& os, const v2d_generic& rhs) { os << rhs.str(); return os; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + operator v2d_generic() const { return { static_cast(this->x), static_cast(this->y) }; } + }; + + // Note: joshinils has some good suggestions here, but they are complicated to implement at this moment, + // however they will appear in a future version of PGE + template inline v2d_generic operator * (const float& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); + } + template inline v2d_generic operator * (const double& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); + } + template inline v2d_generic operator * (const int& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); + } + template inline v2d_generic operator / (const float& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); + } + template inline v2d_generic operator / (const double& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); + } + template inline v2d_generic operator / (const int& lhs, const v2d_generic& rhs) + { + return v2d_generic((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); + } + + // To stop dandistine crying... + template inline bool operator < (const v2d_generic& lhs, const v2d_generic& rhs) + { + return lhs.y < rhs.y || (lhs.y == rhs.y && lhs.x < rhs.x); + } + template inline bool operator > (const v2d_generic& lhs, const v2d_generic& rhs) + { + return lhs.y > rhs.y || (lhs.y == rhs.y && lhs.x > rhs.x); + } + + typedef v2d_generic vi2d; + typedef v2d_generic vu2d; + typedef v2d_generic vf2d; + typedef v2d_generic vd2d; +#endif + + + + + + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack - A virtual scrambled filesystem to pack your assets into | + // O------------------------------------------------------------------------------O + struct ResourceBuffer : public std::streambuf + { + ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size); + std::vector vMemory; + }; + + class ResourcePack : public std::streambuf + { + public: + ResourcePack(); + ~ResourcePack(); + bool AddFile(const std::string& sFile); + bool LoadPack(const std::string& sFile, const std::string& sKey); + bool SavePack(const std::string& sFile, const std::string& sKey); + ResourceBuffer GetFileBuffer(const std::string& sFile); + bool Loaded(); + private: + struct sResourceFile { uint32_t nSize; uint32_t nOffset; }; + std::map mapFiles; + std::ifstream baseFile; + std::vector scramble(const std::vector& data, const std::string& key); + std::string makeposix(const std::string& path); + }; + + + class ImageLoader + { + public: + ImageLoader() = default; + virtual ~ImageLoader() = default; + virtual olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) = 0; + virtual olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) = 0; + }; + + + // O------------------------------------------------------------------------------O + // | olc::Sprite - An image represented by a 2D array of olc::Pixel | + // O------------------------------------------------------------------------------O + class Sprite + { + public: + Sprite(); + Sprite(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + Sprite(int32_t w, int32_t h); + Sprite(const olc::Sprite&) = delete; + ~Sprite(); + + public: + olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack = nullptr); + + public: + int32_t width = 0; + int32_t height = 0; + enum Mode { NORMAL, PERIODIC, CLAMP }; + enum Flip { NONE = 0, HORIZ = 1, VERT = 2 }; + + public: + void SetSampleMode(olc::Sprite::Mode mode = olc::Sprite::Mode::NORMAL); + Pixel GetPixel(int32_t x, int32_t y) const; + bool SetPixel(int32_t x, int32_t y, Pixel p); + Pixel GetPixel(const olc::vi2d& a) const; + bool SetPixel(const olc::vi2d& a, Pixel p); + Pixel Sample(float x, float y) const; + Pixel SampleBL(float u, float v) const; + Pixel* GetData(); + olc::Sprite* Duplicate(); + olc::Sprite* Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize); + std::vector pColData; + Mode modeSample = Mode::NORMAL; + + static std::unique_ptr loader; + }; + + // O------------------------------------------------------------------------------O + // | olc::Decal - A GPU resident storage of an olc::Sprite | + // O------------------------------------------------------------------------------O + class Decal + { + public: + Decal(olc::Sprite* spr, bool filter = false, bool clamp = true); + Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr); + virtual ~Decal(); + void Update(); + void UpdateSprite(); + + public: // But dont touch + int32_t id = -1; + olc::Sprite* sprite = nullptr; + olc::vf2d vUVScale = { 1.0f, 1.0f }; + }; + + enum class DecalMode + { + NORMAL, + ADDITIVE, + MULTIPLICATIVE, + STENCIL, + ILLUMINATE, + WIREFRAME, + MODEL3D, + }; + + enum class DecalStructure + { + LINE, + FAN, + STRIP, + LIST + }; + + // O------------------------------------------------------------------------------O + // | olc::Renderable - Convenience class to keep a sprite and decal together | + // O------------------------------------------------------------------------------O + class Renderable + { + public: + Renderable() = default; + Renderable(Renderable&& r) : pSprite(std::move(r.pSprite)), pDecal(std::move(r.pDecal)) {} + Renderable(const Renderable&) = delete; + olc::rcode Load(const std::string& sFile, ResourcePack* pack = nullptr, bool filter = false, bool clamp = true); + void Create(uint32_t width, uint32_t height, bool filter = false, bool clamp = true); + olc::Decal* Decal() const; + olc::Sprite* Sprite() const; + + private: + std::unique_ptr pSprite = nullptr; + std::unique_ptr pDecal = nullptr; + }; + + + // O------------------------------------------------------------------------------O + // | Auxilliary components internal to engine | + // O------------------------------------------------------------------------------O + + struct DecalInstance + { + olc::Decal* decal = nullptr; + std::vector pos; + std::vector uv; + std::vector w; + std::vector z; + std::vector tint; + olc::DecalMode mode = olc::DecalMode::NORMAL; + olc::DecalStructure structure = olc::DecalStructure::FAN; + uint32_t points = 0; + }; + + struct LayerDesc + { + olc::vf2d vOffset = { 0, 0 }; + olc::vf2d vScale = { 1, 1 }; + bool bShow = false; + bool bUpdate = false; + olc::Renderable pDrawTarget; + uint32_t nResID = 0; + std::vector vecDecalInstance; + olc::Pixel tint = olc::WHITE; + std::function funcHook = nullptr; + }; + + class Renderer + { + public: + virtual ~Renderer() = default; + virtual void PrepareDevice() = 0; + virtual olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) = 0; + virtual olc::rcode DestroyDevice() = 0; + virtual void DisplayFrame() = 0; + virtual void PrepareDrawing() = 0; + virtual void SetDecalMode(const olc::DecalMode& mode) = 0; + virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0; + virtual void DrawDecal(const olc::DecalInstance& decal) = 0; + virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered = false, const bool clamp = true) = 0; + virtual void UpdateTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual void ReadTexture(uint32_t id, olc::Sprite* spr) = 0; + virtual uint32_t DeleteTexture(const uint32_t id) = 0; + virtual void ApplyTexture(uint32_t id) = 0; + virtual void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) = 0; + virtual void ClearBuffer(olc::Pixel p, bool bDepth) = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class Platform + { + public: + virtual ~Platform() = default; + virtual olc::rcode ApplicationStartUp() = 0; + virtual olc::rcode ApplicationCleanUp() = 0; + virtual olc::rcode ThreadStartUp() = 0; + virtual olc::rcode ThreadCleanUp() = 0; + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) = 0; + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) = 0; + virtual olc::rcode SetWindowTitle(const std::string& s) = 0; + virtual olc::rcode StartSystemEventLoop() = 0; + virtual olc::rcode HandleSystemEvent() = 0; + static olc::PixelGameEngine* ptrPGE; + }; + + class PGEX; + + // The Static Twins (plus one) + static std::unique_ptr renderer; + static std::unique_ptr platform; + static std::map mapKeys; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine - The main BASE class for your application | + // O------------------------------------------------------------------------------O + class PixelGameEngine + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + public: + olc::rcode Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, + bool full_screen = false, bool vsync = false, bool cohesion = false); + olc::rcode Start(); + + public: // User Override Interfaces + // Called once on application startup, use to load your resources + virtual bool OnUserCreate(); + // Called every frame, and provides you with a time per frame value + virtual bool OnUserUpdate(float fElapsedTime); + // Called once on application termination, so you can be one clean coder + virtual bool OnUserDestroy(); + + // Called when a text entry is confirmed with "enter" key + virtual void OnTextEntryComplete(const std::string& sText); + // Called when a console command is executed + virtual bool OnConsoleCommand(const std::string& sCommand); + + public: // Hardware Interfaces + // Returns true if window is currently in focus + bool IsFocused() const; + // Get the state of a specific keyboard button + HWButton GetKey(Key k) const; + // Get the state of a specific mouse button + HWButton GetMouse(uint32_t b) const; + // Get Mouse X coordinate in "pixel" space + int32_t GetMouseX() const; + // Get Mouse Y coordinate in "pixel" space + int32_t GetMouseY() const; + // Get Mouse Wheel Delta + int32_t GetMouseWheel() const; + // Get the mouse in window space + const olc::vi2d& GetWindowMouse() const; + // Gets the mouse as a vector to keep Tarriest happy + const olc::vi2d& GetMousePos() const; + + static const std::map& GetKeyMap() { return mapKeys; } + + public: // Utility + // Returns the width of the screen in "pixels" + int32_t ScreenWidth() const; + // Returns the height of the screen in "pixels" + int32_t ScreenHeight() const; + // Returns the width of the currently selected drawing target in "pixels" + int32_t GetDrawTargetWidth() const; + // Returns the height of the currently selected drawing target in "pixels" + int32_t GetDrawTargetHeight() const; + // Returns the currently active draw target + olc::Sprite* GetDrawTarget() const; + // Resize the primary screen sprite + void SetScreenSize(int w, int h); + // Specify which Sprite should be the target of drawing functions, use nullptr + // to specify the primary screen + void SetDrawTarget(Sprite* target); + // Gets the current Frames Per Second + uint32_t GetFPS() const; + // Gets last update of elapsed time + float GetElapsedTime() const; + // Gets Actual Window size + const olc::vi2d& GetWindowSize() const; + // Gets pixel scale + const olc::vi2d& GetPixelSize() const; + // Gets actual pixel scale + const olc::vi2d& GetScreenPixelSize() const; + + public: // CONFIGURATION ROUTINES + // Layer targeting functions + void SetDrawTarget(uint8_t layer, bool bDirty = true); + void EnableLayer(uint8_t layer, bool b); + void SetLayerOffset(uint8_t layer, const olc::vf2d& offset); + void SetLayerOffset(uint8_t layer, float x, float y); + void SetLayerScale(uint8_t layer, const olc::vf2d& scale); + void SetLayerScale(uint8_t layer, float x, float y); + void SetLayerTint(uint8_t layer, const olc::Pixel& tint); + void SetLayerCustomRenderFunction(uint8_t layer, std::function f); + + std::vector& GetLayers(); + uint32_t CreateLayer(); + + // Change the pixel mode for different optimisations + // olc::Pixel::NORMAL = No transparency + // olc::Pixel::MASK = Transparent if alpha is < 255 + // olc::Pixel::ALPHA = Full transparency + void SetPixelMode(Pixel::Mode m); + Pixel::Mode GetPixelMode(); + // Use a custom blend function + void SetPixelMode(std::function pixelMode); + // Change the blend factor from between 0.0f to 1.0f; + void SetPixelBlend(float fBlend); + + + + public: // DRAWING ROUTINES + // Draws a single Pixel + virtual bool Draw(int32_t x, int32_t y, Pixel p = olc::WHITE); + bool Draw(const olc::vi2d& pos, Pixel p = olc::WHITE); + // Draws a line from (x1,y1) to (x2,y2) + void DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + void DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p = olc::WHITE, uint32_t pattern = 0xFFFFFFFF); + // Draws a circle located at (x,y) with radius + void DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + void DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE, uint8_t mask = 0xFF); + // Fills a circle located at (x,y) with radius + void FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p = olc::WHITE); + void FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p = olc::WHITE); + // Draws a rectangle at (x,y) to (x+w,y+h) + void DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Fills a rectangle at (x,y) to (x+w,y+h) + void FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p = olc::WHITE); + void FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p = olc::WHITE); + // Draws a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Flat fills a triangle between points (x1,y1), (x2,y2) and (x3,y3) + void FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p = olc::WHITE); + void FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p = olc::WHITE); + // Draws an entire sprite at location (x,y) + void DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws an area of a sprite at location (x,y), where the + // selected area is (ox,oy) to (ox+w,oy+h) + void DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + void DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale = 1, uint8_t flip = olc::Sprite::NONE); + // Draws a single line of text - traditional monospaced + void DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSize(const std::string& s); + // Draws a single line of text - non-monospaced + void DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + void DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col = olc::WHITE, uint32_t scale = 1); + olc::vi2d GetTextSizeProp(const std::string& s); + + // Decal Quad functions + void SetDecalMode(const olc::DecalMode& mode); + void SetDecalStructure(const olc::DecalStructure& structure); + // Draws a whole decal, with optional scale and tinting + void DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a region of a decal, with optional scale and tinting + void DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws fully user controlled 4 vertices, pos(pixels), uv(pixels), colours + void DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements = 4); + // Draws a decal with 4 arbitrary points, warping the texture to look "correct" + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint = olc::WHITE); + void DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint = olc::WHITE); + // As above, but you can specify a region of a decal source sprite + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + void DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint = olc::WHITE); + // Draws a decal rotated to specified angle, wit point of rotation offset + void DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::vf2d& scale = { 1.0f,1.0f }, const olc::Pixel& tint = olc::WHITE); + void DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale = { 1.0f, 1.0f }, const olc::Pixel& tint = olc::WHITE); + // Draws a multiline string as a decal, with tiniting and scaling + void DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Draws a single shaded filled rectangle as a decal + void FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::WHITE); + // Draws a corner shaded rectangle as a decal + void GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR); + // Draws an arbitrary convex textured polygon using GPU + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint = olc::WHITE); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& tint); + void DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& w, const std::vector& z, const std::vector& tint); + + // Draws a line in Decal Space + void DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p = olc::WHITE); + void DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + void DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center = { 0.0f, 0.0f }, const olc::Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }); + // Clears entire draw target to Pixel + void Clear(Pixel p); + // Clears the rendering back buffer + void ClearBuffer(Pixel p, bool bDepth = true); + // Returns the font image + olc::Sprite* GetFontSprite(); + + // Clip a line segment to visible area + bool ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2); + + // Dont allow PGE to mark layers as dirty, so pixel graphics don't update + void EnablePixelTransfer(const bool bEnable = true); + + // Command Console Routines + void ConsoleShow(const olc::Key& keyExit, bool bSuspendTime = true); + bool IsConsoleShowing() const; + void ConsoleClear(); + std::stringstream& ConsoleOut(); + void ConsoleCaptureStdOut(const bool bCapture); + + // Text Entry Routines + void TextEntryEnable(const bool bEnable, const std::string& sText = ""); + std::string TextEntryGetString() const; + int32_t TextEntryGetCursor() const; + bool IsTextEntryEnabled() const; + void SetFPSDisplay(bool display); + + + + private: + void UpdateTextEntry(); + void UpdateConsole(); + + public: + + // Experimental Lightweight 3D Routines ================ +#ifdef OLC_ENABLE_EXPERIMENTAL + // Set Manual View Matrix + void LW3D_View(const std::array& m); + // Set Manual World Matrix + void LW3D_World(const std::array& m); + // Set Manual Projection Matrix + void LW3D_Projection(const std::array& m); + + // Draws a vector of vertices, interprted as individual triangles + void LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col); + + void LW3D_ModelTranslate(const float x, const float y, const float z); + + // Camera convenience functions + void LW3D_SetCameraAtTarget(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fTargetX, const float fTargetY, const float fTargetZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + void LW3D_SetCameraAlongDirection(const float fEyeX, const float fEyeY, const float fEyeZ, + const float fDirX, const float fDirY, const float fDirZ, + const float fUpX = 0.0f, const float fUpY = 1.0f, const float fUpZ = 0.0f); + + // 3D Rendering Flags + void LW3D_EnableDepthTest(const bool bEnableDepth); + void LW3D_EnableBackfaceCulling(const bool bEnableCull); +#endif + public: // Branding + std::string sAppName; + + private: // Inner mysterious workings + olc::Sprite* pDrawTarget = nullptr; + Pixel::Mode nPixelMode = Pixel::NORMAL; + float fBlendFactor = 1.0f; + olc::vi2d vScreenSize = { 256, 240 }; + olc::vf2d vInvScreenSize = { 1.0f / 256.0f, 1.0f / 240.0f }; + olc::vi2d vPixelSize = { 4, 4 }; + olc::vi2d vScreenPixelSize = { 4, 4 }; + olc::vi2d vMousePos = { 0, 0 }; + int32_t nMouseWheelDelta = 0; + olc::vi2d vMousePosCache = { 0, 0 }; + olc::vi2d vMouseWindowPos = { 0, 0 }; + int32_t nMouseWheelDeltaCache = 0; + olc::vi2d vWindowSize = { 0, 0 }; + olc::vi2d vViewPos = { 0, 0 }; + olc::vi2d vViewSize = { 0,0 }; + bool bFullScreen = false; + olc::vf2d vPixel = { 1.0f, 1.0f }; + bool bHasInputFocus = false; + bool bHasMouseFocus = false; + bool bEnableVSYNC = false; + float fFrameTimer = 1.0f; + float fLastElapsed = 0.0f; + int nFrameCount = 0; + bool showFPS = true; + bool bSuspendTextureTransfer = false; + Renderable fontRenderable; + std::vector vLayers; + uint8_t nTargetLayer = 0; + uint32_t nLastFPS = 0; + bool bPixelCohesion = false; + DecalMode nDecalMode = DecalMode::NORMAL; + DecalStructure nDecalStructure = DecalStructure::FAN; + std::function funcPixelMode; + std::chrono::time_point m_tp1, m_tp2; + std::vector vFontSpacing; + + // Command Console Specific + bool bConsoleShow = false; + bool bConsoleSuspendTime = false; + olc::Key keyConsoleExit = olc::Key::F1; + std::stringstream ssConsoleOutput; + std::streambuf* sbufOldCout = nullptr; + olc::vi2d vConsoleSize; + olc::vi2d vConsoleCursor = { 0,0 }; + olc::vf2d vConsoleCharacterScale = { 1.0f, 2.0f }; + std::vector sConsoleLines; + std::list sCommandHistory; + std::list::iterator sCommandHistoryIt; + + // Text Entry Specific + bool bTextEntryEnable = false; + std::string sTextEntryString = ""; + int32_t nTextEntryCursor = 0; + std::vector> vKeyboardMap; + + + + // State of keyboard + bool pKeyNewState[256] = { 0 }; + bool pKeyOldState[256] = { 0 }; + HWButton pKeyboardState[256] = { 0 }; + + // State of mouse + bool pMouseNewState[nMouseButtons] = { 0 }; + bool pMouseOldState[nMouseButtons] = { 0 }; + HWButton pMouseState[nMouseButtons] = { 0 }; + + // The main engine thread + void EngineThread(); + + + // If anything sets this flag to false, the engine + // "should" shut down gracefully + static std::atomic bAtomActive; + + public: + // "Break In" Functions + void olc_UpdateMouse(int32_t x, int32_t y); + void olc_UpdateMouseWheel(int32_t delta); + void olc_UpdateWindowSize(int32_t x, int32_t y); + void olc_UpdateViewport(); + void olc_ConstructFontSheet(); + void olc_CoreUpdate(); + void olc_PrepareEngine(); + void olc_UpdateMouseState(int32_t button, bool state); + void olc_UpdateKeyState(int32_t key, bool state); + void olc_UpdateMouseFocus(bool state); + void olc_UpdateKeyFocus(bool state); + void olc_Terminate(); + void olc_Reanimate(); + bool olc_IsRunning(); + + // At the very end of this file, chooses which + // components to compile + virtual void olc_ConfigureSystem(); + + // NOTE: Items Here are to be deprecated, I have left them in for now + // in case you are using them, but they will be removed. + // olc::vf2d vSubPixelOffset = { 0.0f, 0.0f }; + + public: // PGEX Stuff + friend class PGEX; + void pgex_Register(olc::PGEX* pgex); + + private: + std::vector vExtensions; + }; + + + + // O------------------------------------------------------------------------------O + // | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension | + // O------------------------------------------------------------------------------O + class PGEX + { + friend class olc::PixelGameEngine; + public: + PGEX(bool bHook = false); + + protected: + virtual void OnBeforeUserCreate(); + virtual void OnAfterUserCreate(); + virtual bool OnBeforeUserUpdate(float& fElapsedTime); + virtual void OnAfterUserUpdate(float fElapsedTime); + + protected: + static PixelGameEngine* pge; + }; +} + +#pragma endregion + +#endif // OLC_PGE_DEF + + +// O------------------------------------------------------------------------------O +// | START OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O +#ifdef OLC_PGE_APPLICATION +#undef OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine INTERFACE IMPLEMENTATION (CORE) | +// | Note: The core implementation is platform independent | +// O------------------------------------------------------------------------------O +#pragma region pge_implementation +namespace olc +{ + // O------------------------------------------------------------------------------O + // | olc::Pixel IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Pixel::Pixel() + { + r = 0; g = 0; b = 0; a = nDefaultAlpha; + } + + Pixel::Pixel(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) + { + n = red | (green << 8) | (blue << 16) | (alpha << 24); + } // Thanks jarekpelczar + + Pixel::Pixel(uint32_t p) + { + n = p; + } + + bool Pixel::operator==(const Pixel& p) const + { + return n == p.n; + } + + bool Pixel::operator!=(const Pixel& p) const + { + return n != p.n; + } + + Pixel Pixel::operator * (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) * i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) * i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) * i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel Pixel::operator / (const float i) const + { + float fR = std::min(255.0f, std::max(0.0f, float(r) / i)); + float fG = std::min(255.0f, std::max(0.0f, float(g) / i)); + float fB = std::min(255.0f, std::max(0.0f, float(b) / i)); + return Pixel(uint8_t(fR), uint8_t(fG), uint8_t(fB), a); + } + + Pixel& Pixel::operator *=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) * i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) * i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) * i))); + return *this; + } + + Pixel& Pixel::operator /=(const float i) + { + this->r = uint8_t(std::min(255.0f, std::max(0.0f, float(r) / i))); + this->g = uint8_t(std::min(255.0f, std::max(0.0f, float(g) / i))); + this->b = uint8_t(std::min(255.0f, std::max(0.0f, float(b) / i))); + return *this; + } + + Pixel Pixel::operator + (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel Pixel::operator - (const Pixel& p) const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel& Pixel::operator += (const Pixel& p) + { + this->r = uint8_t(std::min(255, std::max(0, int(r) + int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) + int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) + int(p.b)))); + return *this; + } + + Pixel& Pixel::operator -= (const Pixel& p) // Thanks Au Lit + { + this->r = uint8_t(std::min(255, std::max(0, int(r) - int(p.r)))); + this->g = uint8_t(std::min(255, std::max(0, int(g) - int(p.g)))); + this->b = uint8_t(std::min(255, std::max(0, int(b) - int(p.b)))); + return *this; + } + + Pixel Pixel::inv() const + { + uint8_t nR = uint8_t(std::min(255, std::max(0, 255 - int(r)))); + uint8_t nG = uint8_t(std::min(255, std::max(0, 255 - int(g)))); + uint8_t nB = uint8_t(std::min(255, std::max(0, 255 - int(b)))); + return Pixel(nR, nG, nB, a); + } + + Pixel PixelF(float red, float green, float blue, float alpha) + { + return Pixel(uint8_t(red * 255.0f), uint8_t(green * 255.0f), uint8_t(blue * 255.0f), uint8_t(alpha * 255.0f)); + } + + Pixel PixelLerp(const olc::Pixel& p1, const olc::Pixel& p2, float t) + { + return (p2 * t) + p1 * (1.0f - t); + } + + // O------------------------------------------------------------------------------O + // | olc::Sprite IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Sprite::Sprite() + { + width = 0; height = 0; + } + + Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack* pack) + { + LoadFromFile(sImageFile, pack); + } + + Sprite::Sprite(int32_t w, int32_t h) + { + width = w; height = h; + pColData.resize(width * height); + pColData.resize(width * height, nDefaultPixel); + } + + Sprite::~Sprite() + { + pColData.clear(); + } + + void Sprite::SetSampleMode(olc::Sprite::Mode mode) + { + modeSample = mode; + } + + Pixel Sprite::GetPixel(const olc::vi2d& a) const + { + return GetPixel(a.x, a.y); + } + + bool Sprite::SetPixel(const olc::vi2d& a, Pixel p) + { + return SetPixel(a.x, a.y, p); + } + + Pixel Sprite::GetPixel(int32_t x, int32_t y) const + { + if (modeSample == olc::Sprite::Mode::NORMAL) + { + if (x >= 0 && x < width && y >= 0 && y < height) + return pColData[y * width + x]; + else + return Pixel(0, 0, 0, 0); + } + else + { + if (modeSample == olc::Sprite::Mode::PERIODIC) + return pColData[abs(y % height) * width + abs(x % width)]; + else + return pColData[std::max(0, std::min(y, height - 1)) * width + std::max(0, std::min(x, width - 1))]; + } + } + + bool Sprite::SetPixel(int32_t x, int32_t y, Pixel p) + { + if (x >= 0 && x < width && y >= 0 && y < height) + { + pColData[y * width + x] = p; + return true; + } + else + return false; + } + + Pixel Sprite::Sample(float x, float y) const + { + int32_t sx = std::min((int32_t)((x * (float)width)), width - 1); + int32_t sy = std::min((int32_t)((y * (float)height)), height - 1); + return GetPixel(sx, sy); + } + + Pixel Sprite::SampleBL(float u, float v) const + { + u = u * width - 0.5f; + v = v * height - 0.5f; + int x = (int)floor(u); // cast to int rounds toward zero, not downward + int y = (int)floor(v); // Thanks @joshinils + float u_ratio = u - x; + float v_ratio = v - y; + float u_opposite = 1 - u_ratio; + float v_opposite = 1 - v_ratio; + + olc::Pixel p1 = GetPixel(std::max(x, 0), std::max(y, 0)); + olc::Pixel p2 = GetPixel(std::min(x + 1, (int)width - 1), std::max(y, 0)); + olc::Pixel p3 = GetPixel(std::max(x, 0), std::min(y + 1, (int)height - 1)); + olc::Pixel p4 = GetPixel(std::min(x + 1, (int)width - 1), std::min(y + 1, (int)height - 1)); + + return olc::Pixel( + (uint8_t)((p1.r * u_opposite + p2.r * u_ratio) * v_opposite + (p3.r * u_opposite + p4.r * u_ratio) * v_ratio), + (uint8_t)((p1.g * u_opposite + p2.g * u_ratio) * v_opposite + (p3.g * u_opposite + p4.g * u_ratio) * v_ratio), + (uint8_t)((p1.b * u_opposite + p2.b * u_ratio) * v_opposite + (p3.b * u_opposite + p4.b * u_ratio) * v_ratio)); + } + + Pixel* Sprite::GetData() + { + return pColData.data(); + } + + + olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack) + { + UNUSED(pack); + return loader->LoadImageResource(this, sImageFile, pack); + } + + olc::Sprite* Sprite::Duplicate() + { + olc::Sprite* spr = new olc::Sprite(width, height); + std::memcpy(spr->GetData(), GetData(), width * height * sizeof(olc::Pixel)); + spr->modeSample = modeSample; + return spr; + } + + olc::Sprite* Sprite::Duplicate(const olc::vi2d& vPos, const olc::vi2d& vSize) + { + olc::Sprite* spr = new olc::Sprite(vSize.x, vSize.y); + for (int y = 0; y < vSize.y; y++) + for (int x = 0; x < vSize.x; x++) + spr->SetPixel(x, y, GetPixel(vPos.x + x, vPos.y + y)); + return spr; + } + + // O------------------------------------------------------------------------------O + // | olc::Decal IMPLEMENTATION | + // O------------------------------------------------------------------------------O + Decal::Decal(olc::Sprite* spr, bool filter, bool clamp) + { + id = -1; + if (spr == nullptr) return; + sprite = spr; + id = renderer->CreateTexture(sprite->width, sprite->height, filter, clamp); + Update(); + } + + Decal::Decal(const uint32_t nExistingTextureResource, olc::Sprite* spr) + { + if (spr == nullptr) return; + id = nExistingTextureResource; + } + + void Decal::Update() + { + if (sprite == nullptr) return; + vUVScale = { 1.0f / float(sprite->width), 1.0f / float(sprite->height) }; + renderer->ApplyTexture(id); + renderer->UpdateTexture(id, sprite); + } + + void Decal::UpdateSprite() + { + if (sprite == nullptr) return; + renderer->ApplyTexture(id); + renderer->ReadTexture(id, sprite); + } + + Decal::~Decal() + { + if (id != -1) + { + renderer->DeleteTexture(id); + id = -1; + } + } + + void Renderable::Create(uint32_t width, uint32_t height, bool filter, bool clamp) + { + pSprite = std::make_unique(width, height); + pDecal = std::make_unique(pSprite.get(), filter, clamp); + } + + olc::rcode Renderable::Load(const std::string& sFile, ResourcePack* pack, bool filter, bool clamp) + { + pSprite = std::make_unique(); + if (pSprite->LoadFromFile(sFile, pack) == olc::rcode::OK) + { + pDecal = std::make_unique(pSprite.get(), filter, clamp); + return olc::rcode::OK; + } + else + { + pSprite.release(); + pSprite = nullptr; + return olc::rcode::NO_FILE; + } + } + + olc::Decal* Renderable::Decal() const + { + return pDecal.get(); + } + + olc::Sprite* Renderable::Sprite() const + { + return pSprite.get(); + } + + // O------------------------------------------------------------------------------O + // | olc::ResourcePack IMPLEMENTATION | + // O------------------------------------------------------------------------------O + + + //============================================================= + // Resource Packs - Allows you to store files in one large + // scrambled file - Thanks MaGetzUb for debugging a null char in std::stringstream bug + ResourceBuffer::ResourceBuffer(std::ifstream& ifs, uint32_t offset, uint32_t size) + { + vMemory.resize(size); + ifs.seekg(offset); ifs.read(vMemory.data(), vMemory.size()); + setg(vMemory.data(), vMemory.data(), vMemory.data() + size); + } + + ResourcePack::ResourcePack() { } + ResourcePack::~ResourcePack() { baseFile.close(); } + + bool ResourcePack::AddFile(const std::string& sFile) + { + const std::string file = makeposix(sFile); + + if (_gfs::exists(file)) + { + sResourceFile e; + e.nSize = (uint32_t)_gfs::file_size(file); + e.nOffset = 0; // Unknown at this stage + mapFiles[file] = e; + return true; + } + return false; + } + + bool ResourcePack::LoadPack(const std::string& sFile, const std::string& sKey) + { + // Open the resource file + baseFile.open(sFile, std::ifstream::binary); + if (!baseFile.is_open()) return false; + + // 1) Read Scrambled index + uint32_t nIndexSize = 0; + baseFile.read((char*)&nIndexSize, sizeof(uint32_t)); + + std::vector buffer(nIndexSize); + for (uint32_t j = 0; j < nIndexSize; j++) + buffer[j] = baseFile.get(); + + std::vector decoded = scramble(buffer, sKey); + size_t pos = 0; + auto read = [&decoded, &pos](char* dst, size_t size) { + memcpy((void*)dst, (const void*)(decoded.data() + pos), size); + pos += size; + }; + + auto get = [&read]() -> int { char c; read(&c, 1); return c; }; + + // 2) Read Map + uint32_t nMapEntries = 0; + read((char*)&nMapEntries, sizeof(uint32_t)); + for (uint32_t i = 0; i < nMapEntries; i++) + { + uint32_t nFilePathSize = 0; + read((char*)&nFilePathSize, sizeof(uint32_t)); + + std::string sFileName(nFilePathSize, ' '); + for (uint32_t j = 0; j < nFilePathSize; j++) + sFileName[j] = get(); + + sResourceFile e; + read((char*)&e.nSize, sizeof(uint32_t)); + read((char*)&e.nOffset, sizeof(uint32_t)); + mapFiles[sFileName] = e; + } + + // Don't close base file! we will provide a stream + // pointer when the file is requested + return true; + } + + bool ResourcePack::SavePack(const std::string& sFile, const std::string& sKey) + { + // Create/Overwrite the resource file + std::ofstream ofs(sFile, std::ofstream::binary); + if (!ofs.is_open()) return false; + + // Iterate through map + uint32_t nIndexSize = 0; // Unknown for now + ofs.write((char*)&nIndexSize, sizeof(uint32_t)); + uint32_t nMapSize = uint32_t(mapFiles.size()); + ofs.write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + ofs.write((char*)&nPathSize, sizeof(uint32_t)); + ofs.write(e.first.c_str(), nPathSize); + + // Write the file entry properties + ofs.write((char*)&e.second.nSize, sizeof(uint32_t)); + ofs.write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + + // 2) Write the individual Data + std::streampos offset = ofs.tellp(); + nIndexSize = (uint32_t)offset; + for (auto& e : mapFiles) + { + // Store beginning of file offset within resource pack file + e.second.nOffset = (uint32_t)offset; + + // Load the file to be added + std::vector vBuffer(e.second.nSize); + std::ifstream i(e.first, std::ifstream::binary); + i.read((char*)vBuffer.data(), e.second.nSize); + i.close(); + + // Write the loaded file into resource pack file + ofs.write((char*)vBuffer.data(), e.second.nSize); + offset += e.second.nSize; + } + + // 3) Scramble Index + std::vector stream; + auto write = [&stream](const char* data, size_t size) { + size_t sizeNow = stream.size(); + stream.resize(sizeNow + size); + memcpy(stream.data() + sizeNow, data, size); + }; + + // Iterate through map + write((char*)&nMapSize, sizeof(uint32_t)); + for (auto& e : mapFiles) + { + // Write the path of the file + size_t nPathSize = e.first.size(); + write((char*)&nPathSize, sizeof(uint32_t)); + write(e.first.c_str(), nPathSize); + + // Write the file entry properties + write((char*)&e.second.nSize, sizeof(uint32_t)); + write((char*)&e.second.nOffset, sizeof(uint32_t)); + } + std::vector sIndexString = scramble(stream, sKey); + uint32_t nIndexStringLen = uint32_t(sIndexString.size()); + // 4) Rewrite Map (it has been updated with offsets now) + // at start of file + ofs.seekp(0, std::ios::beg); + ofs.write((char*)&nIndexStringLen, sizeof(uint32_t)); + ofs.write(sIndexString.data(), nIndexStringLen); + ofs.close(); + return true; + } + + ResourceBuffer ResourcePack::GetFileBuffer(const std::string& sFile) + { + return ResourceBuffer(baseFile, mapFiles[sFile].nOffset, mapFiles[sFile].nSize); + } + + bool ResourcePack::Loaded() + { + return baseFile.is_open(); + } + + std::vector ResourcePack::scramble(const std::vector& data, const std::string& key) + { + if (key.empty()) return data; + std::vector o; + size_t c = 0; + for (auto s : data) o.push_back(s ^ key[(c++) % key.size()]); + return o; + }; + + std::string ResourcePack::makeposix(const std::string& path) + { + std::string o; + for (auto s : path) o += std::string(1, s == '\\' ? '/' : s); + return o; + }; + + // O------------------------------------------------------------------------------O + // | olc::PixelGameEngine IMPLEMENTATION | + // O------------------------------------------------------------------------------O + PixelGameEngine::PixelGameEngine() + { + sAppName = "Undefined"; + olc::PGEX::pge = this; + + // Bring in relevant Platform & Rendering systems depending + // on compiler parameters + olc_ConfigureSystem(); + } + + PixelGameEngine::~PixelGameEngine() + {} + + + olc::rcode PixelGameEngine::Construct(int32_t screen_w, int32_t screen_h, int32_t pixel_w, int32_t pixel_h, bool full_screen, bool vsync, bool cohesion) + { + bPixelCohesion = cohesion; + vScreenSize = { screen_w, screen_h }; + vInvScreenSize = { 1.0f / float(screen_w), 1.0f / float(screen_h) }; + vPixelSize = { pixel_w, pixel_h }; + vWindowSize = vScreenSize * vPixelSize; + bFullScreen = full_screen; + bEnableVSYNC = vsync; + vPixel = 2.0f / vScreenSize; + + if (vPixelSize.x <= 0 || vPixelSize.y <= 0 || vScreenSize.x <= 0 || vScreenSize.y <= 0) + return olc::FAIL; + return olc::OK; + } + + + void PixelGameEngine::SetScreenSize(int w, int h) + { + vScreenSize = { w, h }; + vInvScreenSize = { 1.0f / float(w), 1.0f / float(h) }; + for (auto& layer : vLayers) + { + layer.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + layer.bUpdate = true; + } + SetDrawTarget(nullptr); + renderer->ClearBuffer(olc::BLACK, true); + renderer->DisplayFrame(); + renderer->ClearBuffer(olc::BLACK, true); + renderer->UpdateViewport(vViewPos, vViewSize); + } + +#if !defined(PGE_USE_CUSTOM_START) + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Start the thread + bAtomActive = true; + std::thread t = std::thread(&PixelGameEngine::EngineThread, this); + + // Some implementations may form an event loop here + platform->StartSystemEventLoop(); + + // Wait for thread to be exited + t.join(); + + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +#endif + + void PixelGameEngine::SetDrawTarget(Sprite* target) + { + if (target) + { + pDrawTarget = target; + } + else + { + nTargetLayer = 0; + pDrawTarget = vLayers[0].pDrawTarget.Sprite(); + } + } + + void PixelGameEngine::SetDrawTarget(uint8_t layer, bool bDirty) + { + if (layer < vLayers.size()) + { + pDrawTarget = vLayers[layer].pDrawTarget.Sprite(); + vLayers[layer].bUpdate = bDirty; + nTargetLayer = layer; + } + } + + void PixelGameEngine::EnableLayer(uint8_t layer, bool b) + { + if (layer < vLayers.size()) vLayers[layer].bShow = b; + } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, const olc::vf2d& offset) + { + SetLayerOffset(layer, offset.x, offset.y); + } + + void PixelGameEngine::SetLayerOffset(uint8_t layer, float x, float y) + { + if (layer < vLayers.size()) vLayers[layer].vOffset = { x, y }; + } + + void PixelGameEngine::SetLayerScale(uint8_t layer, const olc::vf2d& scale) + { + SetLayerScale(layer, scale.x, scale.y); + } + + void PixelGameEngine::SetLayerScale(uint8_t layer, float x, float y) + { + if (layer < vLayers.size()) vLayers[layer].vScale = { x, y }; + } + + void PixelGameEngine::SetLayerTint(uint8_t layer, const olc::Pixel& tint) + { + if (layer < vLayers.size()) vLayers[layer].tint = tint; + } + + void PixelGameEngine::SetLayerCustomRenderFunction(uint8_t layer, std::function f) + { + if (layer < vLayers.size()) vLayers[layer].funcHook = f; + } + + std::vector& PixelGameEngine::GetLayers() + { + return vLayers; + } + + uint32_t PixelGameEngine::CreateLayer() + { + LayerDesc ld; + ld.pDrawTarget.Create(vScreenSize.x, vScreenSize.y); + vLayers.push_back(std::move(ld)); + return uint32_t(vLayers.size()) - 1; + } + + Sprite* PixelGameEngine::GetDrawTarget() const + { + return pDrawTarget; + } + + int32_t PixelGameEngine::GetDrawTargetWidth() const + { + if (pDrawTarget) + return pDrawTarget->width; + else + return 0; + } + + int32_t PixelGameEngine::GetDrawTargetHeight() const + { + if (pDrawTarget) + return pDrawTarget->height; + else + return 0; + } + + uint32_t PixelGameEngine::GetFPS() const + { + return nLastFPS; + } + + bool PixelGameEngine::IsFocused() const + { + return bHasInputFocus; + } + + HWButton PixelGameEngine::GetKey(Key k) const + { + return pKeyboardState[k]; + } + + HWButton PixelGameEngine::GetMouse(uint32_t b) const + { + return pMouseState[b]; + } + + int32_t PixelGameEngine::GetMouseX() const + { + return vMousePos.x; + } + + int32_t PixelGameEngine::GetMouseY() const + { + return vMousePos.y; + } + + const olc::vi2d& PixelGameEngine::GetMousePos() const + { + return vMousePos; + } + + int32_t PixelGameEngine::GetMouseWheel() const + { + return nMouseWheelDelta; + } + + int32_t PixelGameEngine::ScreenWidth() const + { + return vScreenSize.x; + } + + int32_t PixelGameEngine::ScreenHeight() const + { + return vScreenSize.y; + } + + float PixelGameEngine::GetElapsedTime() const + { + return fLastElapsed; + } + + const olc::vi2d& PixelGameEngine::GetWindowSize() const + { + return vWindowSize; + } + + const olc::vi2d& PixelGameEngine::GetPixelSize() const + { + return vPixelSize; + } + + const olc::vi2d& PixelGameEngine::GetScreenPixelSize() const + { + return vScreenPixelSize; + } + + const olc::vi2d& PixelGameEngine::GetWindowMouse() const + { + return vMouseWindowPos; + } + + bool PixelGameEngine::Draw(const olc::vi2d& pos, Pixel p) + { + return Draw(pos.x, pos.y, p); + } + + // This is it, the critical function that plots a pixel + bool PixelGameEngine::Draw(int32_t x, int32_t y, Pixel p) + { + if (!pDrawTarget) return false; + + if (nPixelMode == Pixel::NORMAL) + { + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::MASK) + { + if (p.a == 255) + return pDrawTarget->SetPixel(x, y, p); + } + + if (nPixelMode == Pixel::ALPHA) + { + Pixel d = pDrawTarget->GetPixel(x, y); + float a = (float)(p.a / 255.0f) * fBlendFactor; + float c = 1.0f - a; + float r = a * (float)p.r + c * (float)d.r; + float g = a * (float)p.g + c * (float)d.g; + float b = a * (float)p.b + c * (float)d.b; + return pDrawTarget->SetPixel(x, y, Pixel((uint8_t)r, (uint8_t)g, (uint8_t)b/*, (uint8_t)(p.a * fBlendFactor)*/)); + } + + if (nPixelMode == Pixel::CUSTOM) + { + return pDrawTarget->SetPixel(x, y, funcPixelMode(x, y, p, pDrawTarget->GetPixel(x, y))); + } + + return false; + } + + + void PixelGameEngine::DrawLine(const olc::vi2d& pos1, const olc::vi2d& pos2, Pixel p, uint32_t pattern) + { + DrawLine(pos1.x, pos1.y, pos2.x, pos2.y, p, pattern); + } + + void PixelGameEngine::DrawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, Pixel p, uint32_t pattern) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; dy = y2 - y1; + + auto rol = [&](void) { pattern = (pattern << 1) | (pattern >> 31); return pattern & 1; }; + + olc::vi2d p1(x1, y1), p2(x2, y2); + //if (!ClipLineToScreen(p1, p2)) + // return; + x1 = p1.x; y1 = p1.y; + x2 = p2.x; y2 = p2.y; + + // straight lines idea by gurkanctn + if (dx == 0) // Line is vertical + { + if (y2 < y1) std::swap(y1, y2); + for (y = y1; y <= y2; y++) if (rol()) Draw(x1, y, p); + return; + } + + if (dy == 0) // Line is horizontal + { + if (x2 < x1) std::swap(x1, x2); + for (x = x1; x <= x2; x++) if (rol()) Draw(x, y1, p); + return; + } + + // Line is Funk-aye + dx1 = abs(dx); dy1 = abs(dy); + px = 2 * dy1 - dx1; py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; y = y1; xe = x2; + } + else + { + x = x2; y = y2; xe = x1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; x < xe; i++) + { + x = x + 1; + if (px < 0) + px = px + 2 * dy1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) y = y + 1; else y = y - 1; + px = px + 2 * (dy1 - dx1); + } + if (rol()) Draw(x, y, p); + } + } + else + { + if (dy >= 0) + { + x = x1; y = y1; ye = y2; + } + else + { + x = x2; y = y2; ye = y1; + } + + if (rol()) Draw(x, y, p); + + for (i = 0; y < ye; i++) + { + y = y + 1; + if (py <= 0) + py = py + 2 * dx1; + else + { + if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0)) x = x + 1; else x = x - 1; + py = py + 2 * (dx1 - dy1); + } + if (rol()) Draw(x, y, p); + } + } + } + + void PixelGameEngine::DrawCircle(const olc::vi2d& pos, int32_t radius, Pixel p, uint8_t mask) + { + DrawCircle(pos.x, pos.y, radius, p, mask); + } + + void PixelGameEngine::DrawCircle(int32_t x, int32_t y, int32_t radius, Pixel p, uint8_t mask) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + while (y0 >= x0) // only formulate 1/8 of circle + { + // Draw even octants + if (mask & 0x01) Draw(x + x0, y - y0, p);// Q6 - upper right right + if (mask & 0x04) Draw(x + y0, y + x0, p);// Q4 - lower lower right + if (mask & 0x10) Draw(x - x0, y + y0, p);// Q2 - lower left left + if (mask & 0x40) Draw(x - y0, y - x0, p);// Q0 - upper upper left + if (x0 != 0 && x0 != y0) + { + if (mask & 0x02) Draw(x + y0, y - x0, p);// Q7 - upper upper right + if (mask & 0x08) Draw(x + x0, y + y0, p);// Q5 - lower right right + if (mask & 0x20) Draw(x - y0, y + x0, p);// Q3 - lower lower left + if (mask & 0x80) Draw(x - x0, y - y0, p);// Q1 - upper left left + } + + if (d < 0) + d += 4 * x0++ + 6; + else + d += 4 * (x0++ - y0--) + 10; + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::FillCircle(const olc::vi2d& pos, int32_t radius, Pixel p) + { + FillCircle(pos.x, pos.y, radius, p); + } + + void PixelGameEngine::FillCircle(int32_t x, int32_t y, int32_t radius, Pixel p) + { // Thanks to IanM-Matrix1 #PR121 + if (radius < 0 || x < -radius || y < -radius || x - GetDrawTargetWidth() > radius || y - GetDrawTargetHeight() > radius) + return; + + if (radius > 0) + { + int x0 = 0; + int y0 = radius; + int d = 3 - 2 * radius; + + auto drawline = [&](int sx, int ex, int y) + { + for (int x = sx; x <= ex; x++) + Draw(x, y, p); + }; + + while (y0 >= x0) + { + drawline(x - y0, x + y0, y - x0); + if (x0 > 0) drawline(x - y0, x + y0, y + x0); + + if (d < 0) + d += 4 * x0++ + 6; + else + { + if (x0 != y0) + { + drawline(x - x0, x + x0, y - y0); + drawline(x - x0, x + x0, y + y0); + } + d += 4 * (x0++ - y0--) + 10; + } + } + } + else + Draw(x, y, p); + } + + void PixelGameEngine::DrawRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { + DrawRect(pos.x, pos.y, size.x, size.y, p); + } + + void PixelGameEngine::DrawRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + DrawLine(x, y, x + w, y, p); + DrawLine(x + w, y, x + w, y + h, p); + DrawLine(x + w, y + h, x, y + h, p); + DrawLine(x, y + h, x, y, p); + } + + void PixelGameEngine::Clear(Pixel p) + { + int pixels = GetDrawTargetWidth() * GetDrawTargetHeight(); + Pixel* m = GetDrawTarget()->GetData(); + for (int i = 0; i < pixels; i++) m[i] = p; + } + + void PixelGameEngine::ClearBuffer(Pixel p, bool bDepth) + { + renderer->ClearBuffer(p, bDepth); + } + + olc::Sprite* PixelGameEngine::GetFontSprite() + { + return fontRenderable.Sprite(); + } + + bool PixelGameEngine::ClipLineToScreen(olc::vi2d& in_p1, olc::vi2d& in_p2) + { + // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm + static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; + auto Segment = [&vScreenSize = vScreenSize](const olc::vi2d& v) + { + int i = SEG_I; + if (v.x < 0) i |= SEG_L; else if (v.x > vScreenSize.x) i |= SEG_R; + if (v.y < 0) i |= SEG_B; else if (v.y > vScreenSize.y) i |= SEG_T; + return i; + }; + + int s1 = Segment(in_p1), s2 = Segment(in_p2); + + while (true) + { + if (!(s1 | s2)) return true; + else if (s1 & s2) return false; + else + { + int s3 = s2 > s1 ? s2 : s1; + olc::vi2d n; + if (s3 & SEG_T) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (vScreenSize.y - in_p1.y) / (in_p2.y - in_p1.y); n.y = vScreenSize.y; } + else if (s3 & SEG_B) { n.x = in_p1.x + (in_p2.x - in_p1.x) * (0 - in_p1.y) / (in_p2.y - in_p1.y); n.y = 0; } + else if (s3 & SEG_R) { n.x = vScreenSize.x; n.y = in_p1.y + (in_p2.y - in_p1.y) * (vScreenSize.x - in_p1.x) / (in_p2.x - in_p1.x); } + else if (s3 & SEG_L) { n.x = 0; n.y = in_p1.y + (in_p2.y - in_p1.y) * (0 - in_p1.x) / (in_p2.x - in_p1.x); } + if (s3 == s1) { in_p1 = n; s1 = Segment(in_p1); } + else { in_p2 = n; s2 = Segment(in_p2); } + } + } + return true; + } + + void PixelGameEngine::EnablePixelTransfer(const bool bEnable) + { + bSuspendTextureTransfer = !bEnable; + } + + + void PixelGameEngine::FillRect(const olc::vi2d& pos, const olc::vi2d& size, Pixel p) + { + FillRect(pos.x, pos.y, size.x, size.y, p); + } + + void PixelGameEngine::FillRect(int32_t x, int32_t y, int32_t w, int32_t h, Pixel p) + { + int32_t x2 = x + w; + int32_t y2 = y + h; + + if (x < 0) x = 0; + if (x >= (int32_t)GetDrawTargetWidth()) x = (int32_t)GetDrawTargetWidth(); + if (y < 0) y = 0; + if (y >= (int32_t)GetDrawTargetHeight()) y = (int32_t)GetDrawTargetHeight(); + + if (x2 < 0) x2 = 0; + if (x2 >= (int32_t)GetDrawTargetWidth()) x2 = (int32_t)GetDrawTargetWidth(); + if (y2 < 0) y2 = 0; + if (y2 >= (int32_t)GetDrawTargetHeight()) y2 = (int32_t)GetDrawTargetHeight(); + + for (int i = x; i < x2; i++) + for (int j = y; j < y2; j++) + Draw(i, j, p); + } + + void PixelGameEngine::DrawTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { + DrawTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); + } + + void PixelGameEngine::DrawTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + DrawLine(x1, y1, x2, y2, p); + DrawLine(x2, y2, x3, y3, p); + DrawLine(x3, y3, x1, y1, p); + } + + void PixelGameEngine::FillTriangle(const olc::vi2d& pos1, const olc::vi2d& pos2, const olc::vi2d& pos3, Pixel p) + { + FillTriangle(pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y, p); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void PixelGameEngine::FillTriangle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3, Pixel p) + { + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, p); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1 > y2) { std::swap(y1, y2); std::swap(x1, x2); } + if (y1 > y3) { std::swap(y1, y3); std::swap(x1, x3); } + if (y2 > y3) { std::swap(y2, y3); std::swap(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); + if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); + if (dx2 < 0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { std::swap(dx1, dy1); changed1 = true; } + if (dy2 > dx2) { std::swap(dy2, dx2); changed2 = true; } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + i++; + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); // Draw line from min to max points found on the y + // Now increase y + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y == y2) break; + } + next: + // Second half + dx1 = (int)(x3 - x2); if (dx1 < 0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y3 - y2); + t1x = x2; + + if (dy1 > dx1) { // swap values + std::swap(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x < t2x) { minx = t1x; maxx = t2x; } + else { minx = t2x; maxx = t1x; } + // process first line until y value is about to change + while (i < dx1) { + e1 += dy1; + while (e1 >= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i < dx1) i++; + } + next3: + // process second line until y value is about to change + while (t2x != x3) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx > t1x) minx = t1x; + if (minx > t2x) minx = t2x; + if (maxx < t1x) maxx = t1x; + if (maxx < t2x) maxx = t2x; + drawline(minx, maxx, y); + if (!changed1) t1x += signx1; + t1x += t1xp; + if (!changed2) t2x += signx2; + t2x += t2xp; + y += 1; + if (y > y3) return; + } + } + + void PixelGameEngine::DrawSprite(const olc::vi2d& pos, Sprite* sprite, uint32_t scale, uint8_t flip) + { + DrawSprite(pos.x, pos.y, sprite, scale, flip); + } + + void PixelGameEngine::DrawSprite(int32_t x, int32_t y, Sprite* sprite, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = sprite->width - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = sprite->height - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx, fy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < sprite->width; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < sprite->height; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx, fy)); + } + } + } + + void PixelGameEngine::DrawPartialSprite(const olc::vi2d& pos, Sprite* sprite, const olc::vi2d& sourcepos, const olc::vi2d& size, uint32_t scale, uint8_t flip) + { + DrawPartialSprite(pos.x, pos.y, sprite, sourcepos.x, sourcepos.y, size.x, size.y, scale, flip); + } + + void PixelGameEngine::DrawPartialSprite(int32_t x, int32_t y, Sprite* sprite, int32_t ox, int32_t oy, int32_t w, int32_t h, uint32_t scale, uint8_t flip) + { + if (sprite == nullptr) + return; + + int32_t fxs = 0, fxm = 1, fx = 0; + int32_t fys = 0, fym = 1, fy = 0; + if (flip & olc::Sprite::Flip::HORIZ) { fxs = w - 1; fxm = -1; } + if (flip & olc::Sprite::Flip::VERT) { fys = h - 1; fym = -1; } + + if (scale > 1) + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + (i * scale) + is, y + (j * scale) + js, sprite->GetPixel(fx + ox, fy + oy)); + } + } + else + { + fx = fxs; + for (int32_t i = 0; i < w; i++, fx += fxm) + { + fy = fys; + for (int32_t j = 0; j < h; j++, fy += fym) + Draw(x + i, y + j, sprite->GetPixel(fx + ox, fy + oy)); + } + } + } + + void PixelGameEngine::SetDecalMode(const olc::DecalMode& mode) + { + nDecalMode = mode; + } + + void PixelGameEngine::SetDecalStructure(const olc::DecalStructure& structure) + { + nDecalStructure = structure; + } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + -((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + + olc::vf2d vScreenSpaceDim = + { + ((pos.x + source_size.x * scale.x) * vInvScreenSize.x) * 2.0f - 1.0f, + -(((pos.y + source_size.y * scale.y) * vInvScreenSize.y) * 2.0f - 1.0f) + }; + + olc::vf2d vWindow = olc::vf2d(vViewSize); + olc::vf2d vQuantisedPos = ((vScreenSpacePos * vWindow) + olc::vf2d(0.5f, 0.5f)).floor() / vWindow; + olc::vf2d vQuantisedDim = ((vScreenSpaceDim * vWindow) + olc::vf2d(0.5f, -0.5f)).ceil() / vWindow; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vQuantisedPos.x, vQuantisedPos.y }, { vQuantisedPos.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedDim.y }, { vQuantisedDim.x, vQuantisedPos.y } }; + olc::vf2d uvtl = (source_pos + olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + olc::vf2d uvbr = (source_pos + source_size - olc::vf2d(0.0001f, 0.0001f)) * decal->vUVScale; + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.z = { 0,0,0,0 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialDecal(const olc::vf2d& pos, const olc::vf2d& size, olc::Decal* decal, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * size.x * vInvScreenSize.x), + vScreenSpacePos.y - (2.0f * size.y * vInvScreenSize.y) + }; + + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + olc::vf2d uvtl = (source_pos)*decal->vUVScale; + olc::vf2d uvbr = uvtl + ((source_size)*decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.w = { 1,1,1,1 }; + di.z = { 0,0,0,0 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawDecal(const olc::vf2d& pos, olc::Decal* decal, const olc::vf2d& scale, const olc::Pixel& tint) + { + olc::vf2d vScreenSpacePos = + { + (pos.x * vInvScreenSize.x) * 2.0f - 1.0f, + ((pos.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f + }; + + olc::vf2d vScreenSpaceDim = + { + vScreenSpacePos.x + (2.0f * (float(decal->sprite->width) * vInvScreenSize.x)) * scale.x, + vScreenSpacePos.y - (2.0f * (float(decal->sprite->height) * vInvScreenSize.y)) * scale.y + }; + + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.pos = { { vScreenSpacePos.x, vScreenSpacePos.y }, { vScreenSpacePos.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpaceDim.y }, { vScreenSpaceDim.x, vScreenSpacePos.y } }; + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.z = { 0,0,0,0 }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col, uint32_t elements) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(elements); + di.uv.resize(elements); + di.w.resize(elements); + di.tint.resize(elements); + di.points = elements; + for (uint32_t i = 0; i < elements; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = col[i]; + di.w[i] = 1.0f; + di.z[i] = 0.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + di.z[i] = 0.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& w, const std::vector& z, const std::vector& tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.z.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint[i]; + di.w[i] = w[i]; + di.z[i] = z[i]; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& uv, const std::vector& tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint[i]; + di.w[i] = 1.0f; + di.z[i] = 0.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPolygonDecal(olc::Decal* decal, const std::vector& pos, const std::vector& depth, const std::vector& uv, const olc::Pixel tint) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[i] = uv[i]; + di.tint[i] = tint; + di.w[i] = 1.0f; + di.z[i] = 0.0f; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + +#ifdef OLC_ENABLE_EXPERIMENTAL + // Lightweight 3D + void PixelGameEngine::LW3D_DrawTriangles(olc::Decal* decal, const std::vector>& pos, const std::vector& tex, const std::vector& col) + { + DecalInstance di; + di.decal = decal; + di.points = uint32_t(pos.size()); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + for (uint32_t i = 0; i < di.points; i++) + { + di.pos[i] = { pos[i][0], pos[i][1] }; + di.w[i] = pos[i][2]; + di.z[i] = pos[i][2]; + di.uv[i] = tex[i]; + di.tint[i] = col[i]; + } + di.mode = DecalMode::MODEL3D; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } +#endif + + void PixelGameEngine::DrawLineDecal(const olc::vf2d& pos1, const olc::vf2d& pos2, Pixel p) + { + DecalInstance di; + di.decal = nullptr; + di.points = uint32_t(2); + di.pos.resize(di.points); + di.uv.resize(di.points); + di.w.resize(di.points); + di.tint.resize(di.points); + di.pos[0] = { (pos1.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos1.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[0] = { 0.0f, 0.0f }; + di.tint[0] = p; + di.w[0] = 1.0f; + di.z[0] = 0.0f; + di.pos[1] = { (pos2.x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos2.y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + di.uv[1] = { 0.0f, 0.0f }; + di.tint[1] = p; + di.w[1] = 1.0f; + di.z[1] = 0.0f; + di.mode = olc::DecalMode::WIREFRAME; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) + { + olc::vf2d vNewSize = (size - olc::vf2d(0.375f, 0.375f)).ceil(); + std::array points = { { {pos}, {pos.x, pos.y + vNewSize.y}, {pos + vNewSize}, {pos.x + vNewSize.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {col, col, col, col} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::GradientFillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel colTR) + { + std::array points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } }; + std::array uvs = { {{0,0},{0,0},{0,0},{0,0}} }; + std::array cols = { {colTL, colBL, colBR, colTR} }; + DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data(), 4); + } + + void PixelGameEngine::DrawRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + di.w = { 1, 1, 1, 1 }; + di.tint = { tint, tint, tint, tint }; + di.points = 4; + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, float(decal->sprite->height)) - center) * scale; + di.pos[2] = (olc::vf2d(float(decal->sprite->width), float(decal->sprite->height)) - center) * scale; + di.pos[3] = (olc::vf2d(float(decal->sprite->width), 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + di.w[i] = 1; + di.z[i] = 0; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + + void PixelGameEngine::DrawPartialRotatedDecal(const olc::vf2d& pos, olc::Decal* decal, const float fAngle, const olc::vf2d& center, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::vf2d& scale, const olc::Pixel& tint) + { + DecalInstance di; + di.decal = decal; + di.points = 4; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.z = { 0,0,0,0 }; + di.pos.resize(4); + di.pos[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + di.pos[1] = (olc::vf2d(0.0f, source_size.y) - center) * scale; + di.pos[2] = (olc::vf2d(source_size.x, source_size.y) - center) * scale; + di.pos[3] = (olc::vf2d(source_size.x, 0.0f) - center) * scale; + float c = cos(fAngle), s = sin(fAngle); + for (int i = 0; i < 4; i++) + { + di.pos[i] = pos + olc::vf2d(di.pos[i].x * c - di.pos[i].y * s, di.pos[i].x * s + di.pos[i].y * c); + di.pos[i] = di.pos[i] * vInvScreenSize * 2.0f - olc::vf2d(1.0f, 1.0f); + di.pos[i].y *= -1.0f; + } + + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.z = { 0,0,0,0 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + olc::vf2d uvtl = source_pos * decal->vUVScale; + olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale); + di.uv = { { uvtl.x, uvtl.y }, { uvtl.x, uvbr.y }, { uvbr.x, uvbr.y }, { uvbr.x, uvtl.y } }; + + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::Pixel& tint) + { + // Thanks Nathan Reed, a brilliant article explaining whats going on here + // http://www.reedbeta.com/blog/quadrilateral-interpolation-part-1/ + DecalInstance di; + di.points = 4; + di.decal = decal; + di.tint = { tint, tint, tint, tint }; + di.w = { 1, 1, 1, 1 }; + di.z = { 0,0,0,0 }; + di.pos.resize(4); + di.uv = { { 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f} }; + olc::vf2d center; + float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd; + float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]); + float d[4]; for (int i = 0; i < 4; i++) d[i] = (pos[i] - center).mag(); + for (int i = 0; i < 4; i++) + { + float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3]; + di.uv[i] *= q; di.w[i] *= q; + di.pos[i] = { (pos[i].x * vInvScreenSize.x) * 2.0f - 1.0f, ((pos[i].y * vInvScreenSize.y) * 2.0f - 1.0f) * -1.0f }; + } + di.mode = nDecalMode; + di.structure = nDecalStructure; + vLayers[nTargetLayer].vecDecalInstance.push_back(di); + } + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::Pixel& tint) + { + DrawWarpedDecal(decal, pos.data(), tint); + } + + void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::Pixel& tint) + { + DrawWarpedDecal(decal, &pos[0], tint); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const std::array& pos, const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint); + } + + void PixelGameEngine::DrawPartialWarpedDecal(olc::Decal* decal, const olc::vf2d(&pos)[4], const olc::vf2d& source_pos, const olc::vf2d& source_size, const olc::Pixel& tint) + { + DrawPartialWarpedDecal(decal, &pos[0], source_pos, source_size, tint); + } + + void PixelGameEngine::DrawStringDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x += 8.0f * scale.x; + } + } + } + + void PixelGameEngine::DrawStringPropDecal(const olc::vf2d& pos, const std::string& sText, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = { 0.0f, 0.0f }; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = 0; spos.y += 8.0f * scale.y; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialDecal(pos + spos, fontRenderable.Decal(), { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x += float(vFontSpacing[c - 32].y) * scale.x; + } + } + } + // Thanks Oso-Grande/Sopadeoso For these awesom and stupidly clever Text Rotation routines... duh XD + void PixelGameEngine::DrawRotatedStringDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col); + spos.x -= 8.0f; + } + } + } + + void PixelGameEngine::DrawRotatedStringPropDecal(const olc::vf2d& pos, const std::string& sText, const float fAngle, const olc::vf2d& center, const Pixel col, const olc::vf2d& scale) + { + olc::vf2d spos = center; + for (auto c : sText) + { + if (c == '\n') + { + spos.x = center.x; spos.y -= 8.0f; + } + else if (c == '\t') + { + spos.x += 8.0f * float(nTabSizeInSpaces) * scale.x; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + DrawPartialRotatedDecal(pos, fontRenderable.Decal(), fAngle, spos, { float(ox) * 8.0f + float(vFontSpacing[c - 32].x), float(oy) * 8.0f }, { float(vFontSpacing[c - 32].y), 8.0f }, scale, col); + spos.x -= float(vFontSpacing[c - 32].y); + } + } + } + + olc::vi2d PixelGameEngine::GetTextSize(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y++; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces; } + else pos.x++; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + return size * 8; + } + + void PixelGameEngine::DrawString(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { + DrawString(pos.x, pos.y, sText, col, scale); + } + + void PixelGameEngine::DrawString(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + // Thanks @tucna, spotted bug with col.ALPHA :P + if (m != Pixel::CUSTOM) // Thanks @Megarev, required for "shaders" + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + for (uint32_t is = 0; is < scale; is++) + for (uint32_t js = 0; js < scale; js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (uint32_t i = 0; i < 8; i++) + for (uint32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += 8 * scale; + } + } + SetPixelMode(m); + } + + olc::vi2d PixelGameEngine::GetTextSizeProp(const std::string& s) + { + olc::vi2d size = { 0,1 }; + olc::vi2d pos = { 0,1 }; + for (auto c : s) + { + if (c == '\n') { pos.y += 1; pos.x = 0; } + else if (c == '\t') { pos.x += nTabSizeInSpaces * 8; } + else pos.x += vFontSpacing[c - 32].y; + size.x = std::max(size.x, pos.x); + size.y = std::max(size.y, pos.y); + } + + size.y *= 8; + return size; + } + + void PixelGameEngine::DrawStringProp(const olc::vi2d& pos, const std::string& sText, Pixel col, uint32_t scale) + { + DrawStringProp(pos.x, pos.y, sText, col, scale); + } + + void PixelGameEngine::DrawStringProp(int32_t x, int32_t y, const std::string& sText, Pixel col, uint32_t scale) + { + int32_t sx = 0; + int32_t sy = 0; + Pixel::Mode m = nPixelMode; + + if (m != Pixel::CUSTOM) + { + if (col.a != 255) SetPixelMode(Pixel::ALPHA); + else SetPixelMode(Pixel::MASK); + } + for (auto c : sText) + { + if (c == '\n') + { + sx = 0; sy += 8 * scale; + } + else if (c == '\t') + { + sx += 8 * nTabSizeInSpaces * scale; + } + else + { + int32_t ox = (c - 32) % 16; + int32_t oy = (c - 32) / 16; + + if (scale > 1) + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + for (int32_t is = 0; is < int(scale); is++) + for (int32_t js = 0; js < int(scale); js++) + Draw(x + sx + (i * scale) + is, y + sy + (j * scale) + js, col); + } + else + { + for (int32_t i = 0; i < vFontSpacing[c - 32].y; i++) + for (int32_t j = 0; j < 8; j++) + if (fontRenderable.Sprite()->GetPixel(i + ox * 8 + vFontSpacing[c - 32].x, j + oy * 8).r > 0) + Draw(x + sx + i, y + sy + j, col); + } + sx += vFontSpacing[c - 32].y * scale; + } + } + SetPixelMode(m); + } + + void PixelGameEngine::SetPixelMode(Pixel::Mode m) + { + nPixelMode = m; + } + + Pixel::Mode PixelGameEngine::GetPixelMode() + { + return nPixelMode; + } + + void PixelGameEngine::SetPixelMode(std::function pixelMode) + { + funcPixelMode = pixelMode; + nPixelMode = Pixel::Mode::CUSTOM; + } + + void PixelGameEngine::SetPixelBlend(float fBlend) + { + fBlendFactor = fBlend; + if (fBlendFactor < 0.0f) fBlendFactor = 0.0f; + if (fBlendFactor > 1.0f) fBlendFactor = 1.0f; + } + + std::stringstream& PixelGameEngine::ConsoleOut() + { + return ssConsoleOutput; + } + + bool PixelGameEngine::IsConsoleShowing() const + { + return bConsoleShow; + } + + void PixelGameEngine::ConsoleShow(const olc::Key& keyExit, bool bSuspendTime) + { + if (bConsoleShow) + return; + + bConsoleShow = true; + bConsoleSuspendTime = bSuspendTime; + TextEntryEnable(true); + keyConsoleExit = keyExit; + pKeyboardState[keyConsoleExit].bHeld = false; + pKeyboardState[keyConsoleExit].bPressed = false; + pKeyboardState[keyConsoleExit].bReleased = true; + } + + void PixelGameEngine::ConsoleClear() + { + sConsoleLines.clear(); + } + + void PixelGameEngine::ConsoleCaptureStdOut(const bool bCapture) + { + if (bCapture) + sbufOldCout = std::cout.rdbuf(ssConsoleOutput.rdbuf()); + else + std::cout.rdbuf(sbufOldCout); + } + + void PixelGameEngine::UpdateConsole() + { + if (GetKey(keyConsoleExit).bPressed) + { + TextEntryEnable(false); + bConsoleSuspendTime = false; + bConsoleShow = false; + return; + } + + // Keep Console sizes based in real screen dimensions + vConsoleCharacterScale = olc::vf2d(1.0f, 2.0f) / (olc::vf2d(vViewSize) * vInvScreenSize); + vConsoleSize = (vViewSize / olc::vi2d(8, 16)) - olc::vi2d(2, 4); + + // If console has changed size, simply reset it + if (vConsoleSize.y != sConsoleLines.size()) + { + vConsoleCursor = { 0,0 }; + sConsoleLines.clear(); + sConsoleLines.resize(vConsoleSize.y); + } + + auto TypeCharacter = [&](const char c) + { + if (c >= 32 && c < 127) + { + sConsoleLines[vConsoleCursor.y].append(1, c); + vConsoleCursor.x++; + } + + if (c == '\n' || vConsoleCursor.x >= vConsoleSize.x) + { + vConsoleCursor.y++; vConsoleCursor.x = 0; + } + + if (vConsoleCursor.y >= vConsoleSize.y) + { + vConsoleCursor.y = vConsoleSize.y - 1; + for (size_t i = 1; i < vConsoleSize.y; i++) + sConsoleLines[i - 1] = sConsoleLines[i]; + sConsoleLines[vConsoleCursor.y].clear(); + } + }; + + // Empty out "std::cout", parsing as we go + while (ssConsoleOutput.rdbuf()->sgetc() != -1) + { + char c = ssConsoleOutput.rdbuf()->sbumpc(); + TypeCharacter(c); + } + + // Draw Shadow + GradientFillRectDecal({ 0,0 }, olc::vf2d(vScreenSize), olc::PixelF(0, 0, 0.5f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f), olc::PixelF(0, 0, 0.25f, 0.5f)); + + // Draw the console buffer + SetDecalMode(olc::DecalMode::NORMAL); + for (int32_t nLine = 0; nLine < vConsoleSize.y; nLine++) + DrawStringDecal(olc::vf2d(1, 1 + float(nLine)) * vConsoleCharacterScale * 8.0f, sConsoleLines[nLine], olc::WHITE, vConsoleCharacterScale); + + // Draw Input State + FillRectDecal(olc::vf2d(1 + float((TextEntryGetCursor() + 1)), 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, olc::vf2d(8, 8) * vConsoleCharacterScale, olc::DARK_CYAN); + DrawStringDecal(olc::vf2d(1, 1 + float((vConsoleSize.y - 1))) * vConsoleCharacterScale * 8.0f, std::string(">") + TextEntryGetString(), olc::YELLOW, vConsoleCharacterScale); + } + + + + void PixelGameEngine::TextEntryEnable(const bool bEnable, const std::string& sText) + { + if (bEnable) + { + nTextEntryCursor = int32_t(sText.size()); + sTextEntryString = sText; + bTextEntryEnable = true; + } + else + { + bTextEntryEnable = false; + } + } + + std::string PixelGameEngine::TextEntryGetString() const + { + return sTextEntryString; + } + + int32_t PixelGameEngine::TextEntryGetCursor() const + { + return nTextEntryCursor; + } + + bool PixelGameEngine::IsTextEntryEnabled() const + { + return bTextEntryEnable; + } + void PixelGameEngine::SetFPSDisplay(bool display) + { + showFPS = display; + } + + + void PixelGameEngine::UpdateTextEntry() + { + // Check for typed characters + for (const auto& key : vKeyboardMap) + if (GetKey(std::get<0>(key)).bPressed) + { + sTextEntryString.insert(nTextEntryCursor, GetKey(olc::Key::SHIFT).bHeld ? std::get<2>(key) : std::get<1>(key)); + nTextEntryCursor++; + } + + // Check for command characters + if (GetKey(olc::Key::LEFT).bPressed) + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + if (GetKey(olc::Key::RIGHT).bPressed) + nTextEntryCursor = std::min(int32_t(sTextEntryString.size()), nTextEntryCursor + 1); + if (GetKey(olc::Key::BACK).bPressed && nTextEntryCursor > 0) + { + sTextEntryString.erase(nTextEntryCursor - 1, 1); + nTextEntryCursor = std::max(0, nTextEntryCursor - 1); + } + if (GetKey(olc::Key::DEL).bPressed && nTextEntryCursor < sTextEntryString.size()) + sTextEntryString.erase(nTextEntryCursor, 1); + + if (GetKey(olc::Key::UP).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.begin()) + sCommandHistoryIt--; + + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + } + + if (GetKey(olc::Key::DOWN).bPressed) + { + if (!sCommandHistory.empty()) + { + if (sCommandHistoryIt != sCommandHistory.end()) + { + sCommandHistoryIt++; + if (sCommandHistoryIt != sCommandHistory.end()) + { + nTextEntryCursor = int32_t(sCommandHistoryIt->size()); + sTextEntryString = *sCommandHistoryIt; + } + else + { + nTextEntryCursor = 0; + sTextEntryString = ""; + } + } + } + } + + if (GetKey(olc::Key::ENTER).bPressed) + { + if (bConsoleShow) + { + std::cout << ">" + sTextEntryString + "\n"; + if (OnConsoleCommand(sTextEntryString)) + { + sCommandHistory.push_back(sTextEntryString); + sCommandHistoryIt = sCommandHistory.end(); + } + sTextEntryString.clear(); + nTextEntryCursor = 0; + } + else + { + OnTextEntryComplete(sTextEntryString); + TextEntryEnable(false); + } + } + } + + // User must override these functions as required. I have not made + // them abstract because I do need a default behaviour to occur if + // they are not overwritten + + bool PixelGameEngine::OnUserCreate() + { + return false; + } + + bool PixelGameEngine::OnUserUpdate(float fElapsedTime) + { + UNUSED(fElapsedTime); return false; + } + + bool PixelGameEngine::OnUserDestroy() + { + return true; + } + + void PixelGameEngine::OnTextEntryComplete(const std::string& sText) { UNUSED(sText); } + bool PixelGameEngine::OnConsoleCommand(const std::string& sCommand) { UNUSED(sCommand); return false; } + + + // Externalised API + void PixelGameEngine::olc_UpdateViewport() + { + int32_t ww = vScreenSize.x * vPixelSize.x; + int32_t wh = vScreenSize.y * vPixelSize.y; + float wasp = (float)ww / (float)wh; + + if (bPixelCohesion) + { + vScreenPixelSize = (vWindowSize / vScreenSize); + vViewSize = (vWindowSize / vScreenSize) * vScreenSize; + } + else + { + vViewSize.x = (int32_t)vWindowSize.x; + vViewSize.y = (int32_t)((float)vViewSize.x / wasp); + + if (vViewSize.y > vWindowSize.y) + { + vViewSize.y = vWindowSize.y; + vViewSize.x = (int32_t)((float)vViewSize.y * wasp); + } + } + + vViewPos = (vWindowSize - vViewSize) / 2; + } + + void PixelGameEngine::olc_UpdateWindowSize(int32_t x, int32_t y) + { + vWindowSize = { x, y }; + olc_UpdateViewport(); + } + + void PixelGameEngine::olc_UpdateMouseWheel(int32_t delta) + { + nMouseWheelDeltaCache += delta; + } + + void PixelGameEngine::olc_UpdateMouse(int32_t x, int32_t y) + { + // Mouse coords come in screen space + // But leave in pixel space + bHasMouseFocus = true; + vMouseWindowPos = { x, y }; + // Full Screen mode may have a weird viewport we must clamp to + x -= vViewPos.x; + y -= vViewPos.y; + vMousePosCache.x = (int32_t)(((float)x / (float)(vWindowSize.x - (vViewPos.x * 2)) * (float)vScreenSize.x)); + vMousePosCache.y = (int32_t)(((float)y / (float)(vWindowSize.y - (vViewPos.y * 2)) * (float)vScreenSize.y)); + if (vMousePosCache.x >= (int32_t)vScreenSize.x) vMousePosCache.x = vScreenSize.x - 1; + if (vMousePosCache.y >= (int32_t)vScreenSize.y) vMousePosCache.y = vScreenSize.y - 1; + if (vMousePosCache.x < 0) vMousePosCache.x = 0; + if (vMousePosCache.y < 0) vMousePosCache.y = 0; + } + + void PixelGameEngine::olc_UpdateMouseState(int32_t button, bool state) + { + pMouseNewState[button] = state; + } + + void PixelGameEngine::olc_UpdateKeyState(int32_t key, bool state) + { + pKeyNewState[key] = state; + } + + void PixelGameEngine::olc_UpdateMouseFocus(bool state) + { + bHasMouseFocus = state; + } + + void PixelGameEngine::olc_UpdateKeyFocus(bool state) + { + bHasInputFocus = state; + } + + void PixelGameEngine::olc_Reanimate() + { + bAtomActive = true; + } + + bool PixelGameEngine::olc_IsRunning() + { + return bAtomActive; + } + + void PixelGameEngine::olc_Terminate() + { + bAtomActive = false; + } + + void PixelGameEngine::EngineThread() + { + // Allow platform to do stuff here if needed, since its now in the + // context of this thread + if (platform->ThreadStartUp() == olc::FAIL) return; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + while (bAtomActive) + { + // Run as fast as possible + while (bAtomActive) { olc_CoreUpdate(); } + + // Allow the user to free resources if they have overrided the destroy function + if (!OnUserDestroy()) + { + // User denied destroy for some reason, so continue running + bAtomActive = true; + } + } + + platform->ThreadCleanUp(); + } + + void PixelGameEngine::olc_PrepareEngine() + { + // Start OpenGL, the context is owned by the game thread + if (platform->CreateGraphics(bFullScreen, bEnableVSYNC, vViewPos, vViewSize) == olc::FAIL) return; + + // Construct default font sheet + olc_ConstructFontSheet(); + + // Create Primary Layer "0" + CreateLayer(); + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDrawTarget(nullptr); + + m_tp1 = std::chrono::system_clock::now(); + m_tp2 = std::chrono::system_clock::now(); + } + + + void PixelGameEngine::olc_CoreUpdate() + { + // Handle Timing + m_tp2 = std::chrono::system_clock::now(); + std::chrono::duration elapsedTime = m_tp2 - m_tp1; + m_tp1 = m_tp2; + + // Our time per frame coefficient + float fElapsedTime = elapsedTime.count(); + fLastElapsed = fElapsedTime; + + if (bConsoleSuspendTime) + fElapsedTime = 0.0f; + + // Some platforms will need to check for events + platform->HandleSystemEvent(); + + // Compare hardware input states from previous frame + auto ScanHardware = [&](HWButton* pKeys, bool* pStateOld, bool* pStateNew, uint32_t nKeyCount) + { + for (uint32_t i = 0; i < nKeyCount; i++) + { + pKeys[i].bPressed = false; + pKeys[i].bReleased = false; + if (pStateNew[i] != pStateOld[i]) + { + if (pStateNew[i]) + { + pKeys[i].bPressed = !pKeys[i].bHeld; + pKeys[i].bHeld = true; + } + else + { + pKeys[i].bReleased = true; + pKeys[i].bHeld = false; + } + } + pStateOld[i] = pStateNew[i]; + } + }; + + ScanHardware(pKeyboardState, pKeyOldState, pKeyNewState, 256); + ScanHardware(pMouseState, pMouseOldState, pMouseNewState, nMouseButtons); + + // Cache mouse coordinates so they remain consistent during frame + vMousePos = vMousePosCache; + nMouseWheelDelta = nMouseWheelDeltaCache; + nMouseWheelDeltaCache = 0; + + if (bTextEntryEnable) + { + UpdateTextEntry(); + } + + // Handle Frame Update + bool bExtensionBlockFrame = false; + for (auto& ext : vExtensions) bExtensionBlockFrame |= ext->OnBeforeUserUpdate(fElapsedTime); + if (!bExtensionBlockFrame) + { + if (!OnUserUpdate(fElapsedTime)) bAtomActive = false; + } + for (auto& ext : vExtensions) ext->OnAfterUserUpdate(fElapsedTime); + + if (bConsoleShow) + { + SetDrawTarget((uint8_t)0); + UpdateConsole(); + } + + // Display Frame + renderer->UpdateViewport(vViewPos, vViewSize); + renderer->ClearBuffer(olc::BLACK, true); + + // Layer 0 must always exist + vLayers[0].bUpdate = true; + vLayers[0].bShow = true; + SetDecalMode(DecalMode::NORMAL); + renderer->PrepareDrawing(); + + for (auto layer = vLayers.rbegin(); layer != vLayers.rend(); ++layer) + { + if (layer->bShow) + { + if (layer->funcHook == nullptr) + { + renderer->ApplyTexture(layer->pDrawTarget.Decal()->id); + if (!bSuspendTextureTransfer && layer->bUpdate) + { + layer->pDrawTarget.Decal()->Update(); + layer->bUpdate = false; + } + + renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint); + + // Display Decals in order for this layer + for (auto& decal : layer->vecDecalInstance) + renderer->DrawDecal(decal); + layer->vecDecalInstance.clear(); + } + else + { + // Mwa ha ha.... Have Fun!!! + layer->funcHook(); + } + } + } + + + + // Present Graphics to screen + renderer->DisplayFrame(); + + // Update Title Bar + fFrameTimer += fElapsedTime; + nFrameCount++; + if (fFrameTimer >= 1.0f) + { + nLastFPS = nFrameCount; + fFrameTimer -= 1.0f; + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine - " + sAppName + ((showFPS) ? " - FPS: " + std::to_string(nFrameCount) : ""); + platform->SetWindowTitle(sTitle); + nFrameCount = 0; + } + } + + void PixelGameEngine::olc_ConstructFontSheet() + { + std::string data; + data += "?Q`0001oOch0o01o@F40o000000000"; + data += "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400"; + data += "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000"; + data += "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000"; + data += "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000"; + data += "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000"; + data += "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000"; + data += "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000"; + data += "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000"; + data += "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000"; + data += "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000"; + data += "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000"; + data += "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000"; + data += "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0"; + data += "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g`SetPixel(px, py, olc::Pixel(k, k, k, k)); + if (++py == 48) { px++; py = 0; } + } + } + + fontRenderable.Decal()->Update(); + + constexpr std::array vSpacing = { { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00, } }; + + for (auto c : vSpacing) vFontSpacing.push_back({ c >> 4, c & 15 }); + + // UK Standard Layout +#ifdef OLC_KEYBOARD_UK + vKeyboardMap = + { + {olc::Key::A, "a", "A"}, {olc::Key::B, "b", "B"}, {olc::Key::C, "c", "C"}, {olc::Key::D, "d", "D"}, {olc::Key::E, "e", "E"}, + {olc::Key::F, "f", "F"}, {olc::Key::G, "g", "G"}, {olc::Key::H, "h", "H"}, {olc::Key::I, "i", "I"}, {olc::Key::J, "j", "J"}, + {olc::Key::K, "k", "K"}, {olc::Key::L, "l", "L"}, {olc::Key::M, "m", "M"}, {olc::Key::N, "n", "N"}, {olc::Key::O, "o", "O"}, + {olc::Key::P, "p", "P"}, {olc::Key::Q, "q", "Q"}, {olc::Key::R, "r", "R"}, {olc::Key::S, "s", "S"}, {olc::Key::T, "t", "T"}, + {olc::Key::U, "u", "U"}, {olc::Key::V, "v", "V"}, {olc::Key::W, "w", "W"}, {olc::Key::X, "x", "X"}, {olc::Key::Y, "y", "Y"}, + {olc::Key::Z, "z", "Z"}, + + {olc::Key::K0, "0", ")"}, {olc::Key::K1, "1", "!"}, {olc::Key::K2, "2", "\""}, {olc::Key::K3, "3", "#"}, {olc::Key::K4, "4", "$"}, + {olc::Key::K5, "5", "%"}, {olc::Key::K6, "6", "^"}, {olc::Key::K7, "7", "&"}, {olc::Key::K8, "8", "*"}, {olc::Key::K9, "9", "("}, + + {olc::Key::NP0, "0", "0"}, {olc::Key::NP1, "1", "1"}, {olc::Key::NP2, "2", "2"}, {olc::Key::NP3, "3", "3"}, {olc::Key::NP4, "4", "4"}, + {olc::Key::NP5, "5", "5"}, {olc::Key::NP6, "6", "6"}, {olc::Key::NP7, "7", "7"}, {olc::Key::NP8, "8", "8"}, {olc::Key::NP9, "9", "9"}, + {olc::Key::NP_MUL, "*", "*"}, {olc::Key::NP_DIV, "/", "/"}, {olc::Key::NP_ADD, "+", "+"}, {olc::Key::NP_SUB, "-", "-"}, {olc::Key::NP_DECIMAL, ".", "."}, + + {olc::Key::PERIOD, ".", ">"}, {olc::Key::EQUALS, "=", "+"}, {olc::Key::COMMA, ",", "<"}, {olc::Key::MINUS, "-", "_"}, {olc::Key::SPACE, " ", " "}, + + {olc::Key::OEM_1, ";", ":"}, {olc::Key::OEM_2, "/", "?"}, {olc::Key::OEM_3, "\'", "@"}, {olc::Key::OEM_4, "[", "{"}, + {olc::Key::OEM_5, "\\", "|"}, {olc::Key::OEM_6, "]", "}"}, {olc::Key::OEM_7, "#", "~"}, + + // {olc::Key::TAB, "\t", "\t"} + }; +#endif + } + + void PixelGameEngine::pgex_Register(olc::PGEX* pgex) + { + if (std::find(vExtensions.begin(), vExtensions.end(), pgex) == vExtensions.end()) + vExtensions.push_back(pgex); + } + + + PGEX::PGEX(bool bHook) { if (bHook) pge->pgex_Register(this); } + void PGEX::OnBeforeUserCreate() {} + void PGEX::OnAfterUserCreate() {} + bool PGEX::OnBeforeUserUpdate(float& fElapsedTime) { return false; } + void PGEX::OnAfterUserUpdate(float fElapsedTime) {} + + // Need a couple of statics as these are singleton instances + // read from multiple locations + std::atomic PixelGameEngine::bAtomActive{ false }; + olc::PixelGameEngine* olc::PGEX::pge = nullptr; + olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr; + olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr; + std::unique_ptr olc::Sprite::loader = nullptr; +}; +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Renderers - the draw-y bits | +// O------------------------------------------------------------------------------O + +#if !defined(OLC_PGE_HEADLESS) + +#pragma region renderer_ogl10 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL10) + +#if defined(OLC_PLATFORM_WINAPI) +#include +#include +#if !defined(__MINGW32__) +#pragma comment(lib, "Dwmapi.lib") +#endif +typedef BOOL(WINAPI wglSwapInterval_t) (int interval); +static wglSwapInterval_t* wglSwapInterval = nullptr; +typedef HDC glDeviceContext_t; +typedef HGLRC glRenderContext_t; +#endif + +#if defined(__linux__) || defined(__FreeBSD__) +#include +#endif + +#if defined(OLC_PLATFORM_X11) +namespace X11 +{ +#include +} +typedef int(glSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); +static glSwapInterval_t* glSwapIntervalEXT; +typedef X11::GLXContext glDeviceContext_t; +typedef X11::GLXContext glRenderContext_t; +#endif + +#if defined(__APPLE__) +#define GL_SILENCE_DEPRECATION +#include +#include +#include +#endif + +namespace olc +{ + class Renderer_OGL10 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; +#endif + + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo + olc::DecalStructure nDecalStructure = olc::DecalStructure(-1); +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Remove Frame cap + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + if (wglSwapInterval && !bVSYNC) wglSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + glSwapIntervalEXT = nullptr; + glSwapIntervalEXT = (glSwapInterval_t*)glXGetProcAddress((unsigned char*)"glXSwapIntervalEXT"); + + if (glSwapIntervalEXT == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (glSwapIntervalEXT != nullptr && !bVSYNC) + glSwapIntervalEXT(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + } + + void PrepareDrawing() override + { + + //ClearBuffer(olc::GREEN, true); + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + nDecalStructure = DecalStructure::FAN; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void SetDecalMode(const olc::DecalMode& mode) + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: + case olc::DecalMode::MODEL3D: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::ADDITIVE: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case olc::DecalMode::MULTIPLICATIVE: + glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); + break; + case olc::DecalMode::STENCIL: + glBlendFunc(GL_ZERO, GL_SRC_ALPHA); + break; + case olc::DecalMode::ILLUMINATE: + glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); + break; + case olc::DecalMode::WIREFRAME: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + glBegin(GL_QUADS); + glColor4ub(tint.r, tint.g, tint.b, tint.a); + glTexCoord2f(0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(-1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, 1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glTexCoord2f(1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y); + glVertex3f(1.0f /*+ vSubPixelOffset.x*/, -1.0f /*+ vSubPixelOffset.y*/, 0.0f); + glEnd(); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, 0); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + if (nDecalMode == DecalMode::MODEL3D) + { +#ifdef OLC_ENABLE_EXPERIMENTAL + glMatrixMode(GL_PROJECTION); glPushMatrix(); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-1.0f, 1.0f, -1.0f, 1.0f, 1, 1000); + +#pragma comment (lib, "winmm.lib") + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslatef(0, -40, -200); + glRotatef(float(clock()) * 0.1f, 1, 0, 0); + glRotatef(float(clock()) * 0.1f * 2, 0, 1, 0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBegin(GL_TRIANGLES); + + + // Render as 3D Spatial Entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord2f(decal.uv[n].x, decal.uv[n].y); + glVertex3f(decal.pos[n].x, decal.pos[n].y, decal.w[n]); + } + + glEnd(); + + glMatrixMode(GL_PROJECTION); glPopMatrix(); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); + glDisable(GL_DEPTH_TEST); +#endif + } + else + { + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + if (nDecalMode == DecalMode::WIREFRAME) + glBegin(GL_LINE_LOOP); + else + { + if (decal.structure == olc::DecalStructure::FAN) + glBegin(GL_TRIANGLE_FAN); + else if (decal.structure == olc::DecalStructure::STRIP) + glBegin(GL_TRIANGLE_STRIP); + else if (decal.structure == olc::DecalStructure::LIST) + glBegin(GL_TRIANGLES); + } + + // Render as 2D Spatial entity + for (uint32_t n = 0; n < decal.points; n++) + { + glColor4ub(decal.tint[n].r, decal.tint[n].g, decal.tint[n].b, decal.tint[n].a); + glTexCoord4f(decal.uv[n].x, decal.uv[n].y, 0.0f, decal.w[n]); + glVertex3f(decal.pos[n].x, decal.pos[n].y, decal.z[n]); + } + + glEnd(); + } + + + glDisable(GL_DEPTH_TEST); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 1.0 (the original, the best...) | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region renderer_ogl33 +// O------------------------------------------------------------------------------O +// | START RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#if defined(OLC_GFX_OPENGL33) + +#if defined(OLC_PLATFORM_WINAPI) +#include +#include +#if !defined(__MINGW32__) +#pragma comment(lib, "Dwmapi.lib") +#endif +typedef void __stdcall locSwapInterval_t(GLsizei n); +typedef HDC glDeviceContext_t; +typedef HGLRC glRenderContext_t; +#define CALLSTYLE __stdcall +#define OGL_LOAD(t, n) (t*)wglGetProcAddress(#n) +#endif + +#if defined(__linux__) || defined(__FreeBSD__) +#include +#endif + +#if defined(OLC_PLATFORM_X11) +namespace X11 +{ +#include +} +typedef int(locSwapInterval_t)(X11::Display* dpy, X11::GLXDrawable drawable, int interval); +typedef X11::GLXContext glDeviceContext_t; +typedef X11::GLXContext glRenderContext_t; +#define CALLSTYLE +#define OGL_LOAD(t, n) (t*)glXGetProcAddress((unsigned char*)#n); +#endif + +#if defined(__APPLE__) +#define GL_SILENCE_DEPRECATION +#include +#include +#include +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) +#include +#include +#define GL_GLEXT_PROTOTYPES +#include +#include +#define CALLSTYLE +typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval); +#define GL_CLAMP GL_CLAMP_TO_EDGE +#define OGL_LOAD(t, n) n; +#endif + +namespace olc +{ + typedef char GLchar; + typedef ptrdiff_t GLsizeiptr; + typedef GLuint CALLSTYLE locCreateShader_t(GLenum type); + typedef GLuint CALLSTYLE locCreateProgram_t(void); + typedef void CALLSTYLE locDeleteShader_t(GLuint shader); +#if defined(OLC_PLATFORM_EMSCRIPTEN) + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); +#else + typedef void CALLSTYLE locShaderSource_t(GLuint shader, GLsizei count, const GLchar** string, const GLint* length); +#endif + typedef void CALLSTYLE locCompileShader_t(GLuint shader); + typedef void CALLSTYLE locLinkProgram_t(GLuint program); + typedef void CALLSTYLE locDeleteProgram_t(GLuint program); + typedef void CALLSTYLE locAttachShader_t(GLuint program, GLuint shader); + typedef void CALLSTYLE locBindBuffer_t(GLenum target, GLuint buffer); + typedef void CALLSTYLE locBufferData_t(GLenum target, GLsizeiptr size, const void* data, GLenum usage); + typedef void CALLSTYLE locGenBuffers_t(GLsizei n, GLuint* buffers); + typedef void CALLSTYLE locVertexAttribPointer_t(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); + typedef void CALLSTYLE locEnableVertexAttribArray_t(GLuint index); + typedef void CALLSTYLE locUseProgram_t(GLuint program); + typedef void CALLSTYLE locBindVertexArray_t(GLuint array); + typedef void CALLSTYLE locGenVertexArrays_t(GLsizei n, GLuint* arrays); + typedef void CALLSTYLE locGetShaderInfoLog_t(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + + constexpr size_t OLC_MAX_VERTS = 128; + + class Renderer_OGL33 : public olc::Renderer + { + private: +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLDisplay olc_Display; + EGLConfig olc_Config; + EGLContext olc_Context; + EGLSurface olc_Surface; +#endif + +#if defined(OLC_PLATFORM_GLUT) + bool mFullScreen = false; +#else +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glDeviceContext_t glDeviceContext = 0; + glRenderContext_t glRenderContext = 0; +#endif +#endif + bool bSync = false; + olc::DecalMode nDecalMode = olc::DecalMode(-1); // Thanks Gusgo & Bispoo +#if defined(OLC_PLATFORM_X11) + X11::Display* olc_Display = nullptr; + X11::Window* olc_Window = nullptr; + X11::XVisualInfo* olc_VisualInfo = nullptr; +#endif + + private: + locCreateShader_t* locCreateShader = nullptr; + locShaderSource_t* locShaderSource = nullptr; + locCompileShader_t* locCompileShader = nullptr; + locDeleteShader_t* locDeleteShader = nullptr; + locCreateProgram_t* locCreateProgram = nullptr; + locDeleteProgram_t* locDeleteProgram = nullptr; + locLinkProgram_t* locLinkProgram = nullptr; + locAttachShader_t* locAttachShader = nullptr; + locBindBuffer_t* locBindBuffer = nullptr; + locBufferData_t* locBufferData = nullptr; + locGenBuffers_t* locGenBuffers = nullptr; + locVertexAttribPointer_t* locVertexAttribPointer = nullptr; + locEnableVertexAttribArray_t* locEnableVertexAttribArray = nullptr; + locUseProgram_t* locUseProgram = nullptr; + locBindVertexArray_t* locBindVertexArray = nullptr; + locGenVertexArrays_t* locGenVertexArrays = nullptr; + locSwapInterval_t* locSwapInterval = nullptr; + locGetShaderInfoLog_t* locGetShaderInfoLog = nullptr; + + uint32_t m_nFS = 0; + uint32_t m_nVS = 0; + uint32_t m_nQuadShader = 0; + uint32_t m_vbQuad = 0; + uint32_t m_vaQuad = 0; + + struct locVertex + { + float pos[3]; + olc::vf2d tex; + olc::Pixel col; + }; + + locVertex pVertexMem[OLC_MAX_VERTS]; + + olc::Renderable rendBlankQuad; + + public: + void PrepareDevice() override + { +#if defined(OLC_PLATFORM_GLUT) + //glutInit has to be called with main() arguments, make fake ones + int argc = 0; + char* argv[1] = { (char*)"" }; + glutInit(&argc, argv); + glutInitWindowPosition(0, 0); + glutInitWindowSize(512, 512); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); + // Creates the window and the OpenGL context for it + glutCreateWindow("OneLoneCoder.com - Pixel Game Engine"); + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif + } + + olc::rcode CreateDevice(std::vector params, bool bFullScreen, bool bVSYNC) override + { + // Create OpenGL Context +#if defined(OLC_PLATFORM_WINAPI) + // Create Device Context + glDeviceContext = GetDC((HWND)(params[0])); + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) return olc::FAIL; + SetPixelFormat(glDeviceContext, pf, &pfd); + + if (!(glRenderContext = wglCreateContext(glDeviceContext))) return olc::FAIL; + wglMakeCurrent(glDeviceContext, glRenderContext); + + // Set Vertical Sync + locSwapInterval = OGL_LOAD(locSwapInterval_t, "wglSwapIntervalEXT"); + if (locSwapInterval && !bVSYNC) locSwapInterval(0); + bSync = bVSYNC; +#endif + +#if defined(OLC_PLATFORM_X11) + using namespace X11; + // Linux has tighter coupling between OpenGL and X11, so we store + // various "platform" handles in the renderer + olc_Display = (X11::Display*)(params[0]); + olc_Window = (X11::Window*)(params[1]); + olc_VisualInfo = (X11::XVisualInfo*)(params[2]); + + glDeviceContext = glXCreateContext(olc_Display, olc_VisualInfo, nullptr, GL_TRUE); + glXMakeCurrent(olc_Display, *olc_Window, glDeviceContext); + + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, *olc_Window, &gwa); + glViewport(0, 0, gwa.width, gwa.height); + + locSwapInterval = OGL_LOAD(locSwapInterval_t, "glXSwapIntervalEXT"); + + if (locSwapInterval == nullptr && !bVSYNC) + { + printf("NOTE: Could not disable VSYNC, glXSwapIntervalEXT() was not found!\n"); + printf(" Don't worry though, things will still work, it's just the\n"); + printf(" frame rate will be capped to your monitors refresh rate - javidx9\n"); + } + + if (locSwapInterval != nullptr && !bVSYNC) + locSwapInterval(olc_Display, *olc_Window, 0); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + EGLint const attribute_list[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; + EGLint const context_config[] = { EGL_CONTEXT_CLIENT_VERSION , 2, EGL_NONE }; + EGLint num_config; + + olc_Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(olc_Display, nullptr, nullptr); + eglChooseConfig(olc_Display, attribute_list, &olc_Config, 1, &num_config); + + /* create an EGL rendering context */ + olc_Context = eglCreateContext(olc_Display, olc_Config, EGL_NO_CONTEXT, context_config); + olc_Surface = eglCreateWindowSurface(olc_Display, olc_Config, NULL, nullptr); + eglMakeCurrent(olc_Display, olc_Surface, olc_Surface, olc_Context); + //eglSwapInterval is currently a NOP, plement anyways in case it becomes supported + locSwapInterval = &eglSwapInterval; + locSwapInterval(olc_Display, bVSYNC ? 1 : 0); +#endif + +#if defined(OLC_PLATFORM_GLUT) + mFullScreen = bFullScreen; + if (!bVSYNC) + { +#if defined(__APPLE__) + GLint sync = 0; + CGLContextObj ctx = CGLGetCurrentContext(); + if (ctx) CGLSetParameter(ctx, kCGLCPSwapInterval, &sync); +#endif + } +#else +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glEnable(GL_TEXTURE_2D); // Turn on texturing + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); +#endif +#endif + // Load External OpenGL Functions + locCreateShader = OGL_LOAD(locCreateShader_t, glCreateShader); + locCompileShader = OGL_LOAD(locCompileShader_t, glCompileShader); + locShaderSource = OGL_LOAD(locShaderSource_t, glShaderSource); + locDeleteShader = OGL_LOAD(locDeleteShader_t, glDeleteShader); + locCreateProgram = OGL_LOAD(locCreateProgram_t, glCreateProgram); + locDeleteProgram = OGL_LOAD(locDeleteProgram_t, glDeleteProgram); + locLinkProgram = OGL_LOAD(locLinkProgram_t, glLinkProgram); + locAttachShader = OGL_LOAD(locAttachShader_t, glAttachShader); + locBindBuffer = OGL_LOAD(locBindBuffer_t, glBindBuffer); + locBufferData = OGL_LOAD(locBufferData_t, glBufferData); + locGenBuffers = OGL_LOAD(locGenBuffers_t, glGenBuffers); + locVertexAttribPointer = OGL_LOAD(locVertexAttribPointer_t, glVertexAttribPointer); + locEnableVertexAttribArray = OGL_LOAD(locEnableVertexAttribArray_t, glEnableVertexAttribArray); + locUseProgram = OGL_LOAD(locUseProgram_t, glUseProgram); + locGetShaderInfoLog = OGL_LOAD(locGetShaderInfoLog_t, glGetShaderInfoLog); +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + locBindVertexArray = OGL_LOAD(locBindVertexArray_t, glBindVertexArray); + locGenVertexArrays = OGL_LOAD(locGenVertexArrays_t, glGenVertexArrays); +#else + locBindVertexArray = glBindVertexArrayOES; + locGenVertexArrays = glGenVertexArraysOES; +#endif + + // Load & Compile Quad Shader - assumes no errors + m_nFS = locCreateShader(0x8B30); + const GLchar* strFS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "out vec4 pixel;\n""in vec2 oTex;\n" + "in vec4 oCol;\n""uniform sampler2D sprTex;\n""void main(){pixel = texture(sprTex, oTex) * oCol;}"; + locShaderSource(m_nFS, 1, &strFS, NULL); + locCompileShader(m_nFS); + + m_nVS = locCreateShader(0x8B31); + const GLchar* strVS = +#if defined(__arm__) || defined(OLC_PLATFORM_EMSCRIPTEN) + "#version 300 es\n" + "precision mediump float;" +#else + "#version 330 core\n" +#endif + "layout(location = 0) in vec3 aPos;\n""layout(location = 1) in vec2 aTex;\n" + "layout(location = 2) in vec4 aCol;\n""out vec2 oTex;\n""out vec4 oCol;\n" + "void main(){ float p = 1.0 / aPos.z; gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0); oTex = p * aTex; oCol = aCol;}"; + locShaderSource(m_nVS, 1, &strVS, NULL); + locCompileShader(m_nVS); + + m_nQuadShader = locCreateProgram(); + locAttachShader(m_nQuadShader, m_nFS); + locAttachShader(m_nQuadShader, m_nVS); + locLinkProgram(m_nQuadShader); + + // Create Quad + locGenBuffers(1, &m_vbQuad); + locGenVertexArrays(1, &m_vaQuad); + locBindVertexArray(m_vaQuad); + locBindBuffer(0x8892, m_vbQuad); + + locVertex verts[OLC_MAX_VERTS]; + locBufferData(0x8892, sizeof(locVertex) * OLC_MAX_VERTS, verts, 0x88E0); + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); + locBindBuffer(0x8892, 0); + locBindVertexArray(0); + + // Create blank texture for spriteless decals + rendBlankQuad.Create(1, 1); + rendBlankQuad.Sprite()->GetData()[0] = olc::WHITE; + rendBlankQuad.Decal()->Update(); + return olc::rcode::OK; + } + + olc::rcode DestroyDevice() override + { +#if defined(OLC_PLATFORM_WINAPI) + wglDeleteContext(glRenderContext); +#endif + +#if defined(OLC_PLATFORM_X11) + glXMakeCurrent(olc_Display, None, NULL); + glXDestroyContext(olc_Display, glDeviceContext); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutDestroyWindow(glutGetWindow()); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglMakeCurrent(olc_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(olc_Display, olc_Context); + eglDestroySurface(olc_Display, olc_Surface); + eglTerminate(olc_Display); + olc_Display = EGL_NO_DISPLAY; + olc_Surface = EGL_NO_SURFACE; + olc_Context = EGL_NO_CONTEXT; +#endif + return olc::rcode::OK; + } + + void DisplayFrame() override + { +#if defined(OLC_PLATFORM_WINAPI) + SwapBuffers(glDeviceContext); + if (bSync) DwmFlush(); // Woooohooooooo!!!! SMOOOOOOOTH! +#endif + +#if defined(OLC_PLATFORM_X11) + X11::glXSwapBuffers(olc_Display, *olc_Window); +#endif + +#if defined(OLC_PLATFORM_GLUT) + glutSwapBuffers(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + eglSwapBuffers(olc_Display, olc_Surface); +#endif + } + + void PrepareDrawing() override + { + glEnable(GL_BLEND); + nDecalMode = DecalMode::NORMAL; + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + locUseProgram(m_nQuadShader); + locBindVertexArray(m_vaQuad); + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + locVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(locVertex), 0); locEnableVertexAttribArray(0); + locVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(locVertex), (void*)(3 * sizeof(float))); locEnableVertexAttribArray(1); + locVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(locVertex), (void*)(5 * sizeof(float))); locEnableVertexAttribArray(2); +#endif + } + + void SetDecalMode(const olc::DecalMode& mode) override + { + if (mode != nDecalMode) + { + switch (mode) + { + case olc::DecalMode::NORMAL: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; + case olc::DecalMode::MULTIPLICATIVE: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); break; + case olc::DecalMode::STENCIL: glBlendFunc(GL_ZERO, GL_SRC_ALPHA); break; + case olc::DecalMode::ILLUMINATE: glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); break; + case olc::DecalMode::WIREFRAME: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; + } + + nDecalMode = mode; + } + } + + void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) override + { + locBindBuffer(0x8892, m_vbQuad); + locVertex verts[4] = { + {{-1.0f, -1.0f, 1.0}, {0.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{+1.0f, -1.0f, 1.0}, {1.0f * scale.x + offset.x, 1.0f * scale.y + offset.y}, tint}, + {{-1.0f, +1.0f, 1.0}, {0.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + {{+1.0f, +1.0f, 1.0}, {1.0f * scale.x + offset.x, 0.0f * scale.y + offset.y}, tint}, + }; + + locBufferData(0x8892, sizeof(locVertex) * 4, verts, 0x88E0); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + void DrawDecal(const olc::DecalInstance& decal) override + { + SetDecalMode(decal.mode); + if (decal.decal == nullptr) + glBindTexture(GL_TEXTURE_2D, rendBlankQuad.Decal()->id); + else + glBindTexture(GL_TEXTURE_2D, decal.decal->id); + + locBindBuffer(0x8892, m_vbQuad); + + for (uint32_t i = 0; i < decal.points; i++) + pVertexMem[i] = { { decal.pos[i].x, decal.pos[i].y, decal.w[i] }, { decal.uv[i].x, decal.uv[i].y }, decal.tint[i] }; + + locBufferData(0x8892, sizeof(locVertex) * decal.points, pVertexMem, 0x88E0); + + if (nDecalMode == DecalMode::WIREFRAME) + glDrawArrays(GL_LINE_LOOP, 0, decal.points); + else + glDrawArrays(GL_TRIANGLE_FAN, 0, decal.points); + } + + uint32_t CreateTexture(const uint32_t width, const uint32_t height, const bool filtered, const bool clamp) override + { + UNUSED(width); + UNUSED(height); + uint32_t id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + if (filtered) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + + if (clamp) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } + else + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } +#if !defined(OLC_PLATFORM_EMSCRIPTEN) + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); +#endif + return id; + } + + uint32_t DeleteTexture(const uint32_t id) override + { + glDeleteTextures(1, &id); + return id; + } + + void UpdateTexture(uint32_t id, olc::Sprite* spr) override + { + UNUSED(id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ReadTexture(uint32_t id, olc::Sprite* spr) override + { + glReadPixels(0, 0, spr->width, spr->height, GL_RGBA, GL_UNSIGNED_BYTE, spr->GetData()); + } + + void ApplyTexture(uint32_t id) override + { + glBindTexture(GL_TEXTURE_2D, id); + } + + void ClearBuffer(olc::Pixel p, bool bDepth) override + { + glClearColor(float(p.r) / 255.0f, float(p.g) / 255.0f, float(p.b) / 255.0f, float(p.a) / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (bDepth) glClear(GL_DEPTH_BUFFER_BIT); + } + + void UpdateViewport(const olc::vi2d& pos, const olc::vi2d& size) override + { + glViewport(pos.x, pos.y, size.x, size.y); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END RENDERER: OpenGL 3.3 (3.0 es) (sh-sh-sh-shaders....) | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Image loaders | +// O------------------------------------------------------------------------------O + +#pragma region image_gdi +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: GDI+, Windows Only, always exists, a little slow | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_GDI) + +#define min(a, b) ((a < b) ? a : b) +#define max(a, b) ((a > b) ? a : b) +#include +#include +#if defined(__MINGW32__) // Thanks Gusgo & Dandistine, but c'mon mingw!! wtf?! +#include +#else +#include +#endif +#include +#undef min +#undef max + +#if !defined(__MINGW32__) +#pragma comment(lib, "gdiplus.lib") +#pragma comment(lib, "Shlwapi.lib") +#endif + +namespace olc +{ + // Thanks @MaGetzUb for this, which allows sprites to be defined + // at construction, by initialising the GDI subsystem + static class GDIPlusStartup + { + public: + GDIPlusStartup() + { + Gdiplus::GdiplusStartupInput startupInput; + GdiplusStartup(&token, &startupInput, NULL); + } + + ULONG_PTR token; + + ~GDIPlusStartup() + { + // Well, MarcusTU thought this was important :D + Gdiplus::GdiplusShutdown(token); + } + } gdistartup; + + class ImageLoader_GDIPlus : public olc::ImageLoader + { + private: + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + ImageLoader_GDIPlus() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + // clear out existing sprite + spr->pColData.clear(); + + // Open file + UNUSED(pack); + Gdiplus::Bitmap* bmp = nullptr; + if (pack != nullptr) + { + // Load sprite from input stream + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bmp = Gdiplus::Bitmap::FromStream(SHCreateMemStream((BYTE*)rb.vMemory.data(), UINT(rb.vMemory.size()))); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + + // Load sprite from file + bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str()); + } + + if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::rcode::FAIL; + spr->width = bmp->GetWidth(); + spr->height = bmp->GetHeight(); + + spr->pColData.resize(spr->width * spr->height); + + for (int y = 0; y < spr->height; y++) + for (int x = 0; x < spr->width; x++) + { + Gdiplus::Color c; + bmp->GetPixel(x, y, &c); + spr->SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha())); + } + delete bmp; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: GDI+ | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_libpng +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: libpng, default on linux, requires -lpng (libpng-dev) | +// O------------------------------------------------------------------------------O +#if defined(OLC_IMAGE_LIBPNG) +#include +namespace olc +{ + void pngReadStream(png_structp pngPtr, png_bytep data, png_size_t length) + { + png_voidp a = png_get_io_ptr(pngPtr); + ((std::istream*)a)->read((char*)data, length); + } + + class ImageLoader_LibPNG : public olc::ImageLoader + { + public: + ImageLoader_LibPNG() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + + // clear out existing sprite + spr->pColData.clear(); + + //////////////////////////////////////////////////////////////////////////// + // Use libpng, Thanks to Guillaume Cottenceau + // https://gist.github.com/niw/5963798 + // Also reading png from streams + // http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/ + png_structp png; + png_infop info; + + auto loadPNG = [&]() + { + png_read_info(png, info); + png_byte color_type; + png_byte bit_depth; + png_bytep* row_pointers; + spr->width = png_get_image_width(png, info); + spr->height = png_get_image_height(png, info); + color_type = png_get_color_type(png, info); + bit_depth = png_get_bit_depth(png, info); + if (bit_depth == 16) png_set_strip_16(png); + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + png_read_update_info(png, info); + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * spr->height); + for (int y = 0; y < spr->height; y++) { + row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info)); + } + png_read_image(png, row_pointers); + //////////////////////////////////////////////////////////////////////////// + // Create sprite array + spr->pColData.resize(spr->width * spr->height); + // Iterate through image rows, converting into sprite format + for (int y = 0; y < spr->height; y++) + { + png_bytep row = row_pointers[y]; + for (int x = 0; x < spr->width; x++) + { + png_bytep px = &(row[x * 4]); + spr->SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3])); + } + } + + for (int y = 0; y < spr->height; y++) // Thanks maksym33 + free(row_pointers[y]); + free(row_pointers); + png_destroy_read_struct(&png, &info, nullptr); + }; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png) goto fail_load; + + info = png_create_info_struct(png); + if (!info) goto fail_load; + + if (setjmp(png_jmpbuf(png))) goto fail_load; + + if (pack == nullptr) + { + FILE* f = fopen(sImageFile.c_str(), "rb"); + if (!f) return olc::rcode::NO_FILE; + png_init_io(png, f); + loadPNG(); + fclose(f); + } + else + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + std::istream is(&rb); + png_set_read_fn(png, (png_voidp)&is, pngReadStream); + loadPNG(); + } + + return olc::rcode::OK; + + fail_load: + spr->width = 0; + spr->height = 0; + spr->pColData.clear(); + return olc::rcode::FAIL; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END IMAGE LOADER: | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region image_stb +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h, all systems, very fast | +// O------------------------------------------------------------------------------O +// Thanks to Sean Barrett - https://github.com/nothings/stb/blob/master/stb_image.h +// MIT License - Copyright(c) 2017 Sean Barrett + +// Note you need to download the above file into your project folder, and +// #define OLC_IMAGE_STB +// #define OLC_PGE_APPLICATION +// #include "olcPixelGameEngine.h" + +#if defined(OLC_IMAGE_STB) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +namespace olc +{ + class ImageLoader_STB : public olc::ImageLoader + { + public: + ImageLoader_STB() : ImageLoader() + {} + + olc::rcode LoadImageResource(olc::Sprite* spr, const std::string& sImageFile, olc::ResourcePack* pack) override + { + UNUSED(pack); + // clear out existing sprite + spr->pColData.clear(); + // Open file + stbi_uc* bytes = nullptr; + int w = 0, h = 0, cmp = 0; + if (pack != nullptr) + { + ResourceBuffer rb = pack->GetFileBuffer(sImageFile); + bytes = stbi_load_from_memory((unsigned char*)rb.vMemory.data(), rb.vMemory.size(), &w, &h, &cmp, 4); + } + else + { + // Check file exists + if (!_gfs::exists(sImageFile)) return olc::rcode::NO_FILE; + bytes = stbi_load(sImageFile.c_str(), &w, &h, &cmp, 4); + } + + if (!bytes) return olc::rcode::FAIL; + spr->width = w; spr->height = h; + spr->pColData.resize(spr->width * spr->height); + std::memcpy(spr->pColData.data(), bytes, spr->width * spr->height * 4); + delete[] bytes; + return olc::rcode::OK; + } + + olc::rcode SaveImageResource(olc::Sprite* spr, const std::string& sImageFile) override + { + return olc::rcode::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | START IMAGE LOADER: stb_image.h | +// O------------------------------------------------------------------------------O +#pragma endregion + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Platforms | +// O------------------------------------------------------------------------------O + +#pragma region platform_windows +// O------------------------------------------------------------------------------O +// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_WINAPI) + +#if defined(_WIN32) && !defined(__MINGW32__) +#pragma comment(lib, "user32.lib") // Visual Studio Only +#pragma comment(lib, "gdi32.lib") // For other Windows Compilers please add +#pragma comment(lib, "opengl32.lib") // these libs to your linker input +#endif + +namespace olc +{ + class Platform_Windows : public olc::Platform + { + private: + HWND olc_hWnd = nullptr; + std::wstring wsAppName; + + std::wstring ConvertS2W(std::string s) + { +#ifdef __MINGW32__ + wchar_t* buffer = new wchar_t[s.length() + 1]; + mbstowcs(buffer, s.c_str(), s.length()); + buffer[s.length()] = L'\0'; +#else + int count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t* buffer = new wchar_t[count]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, buffer, count); +#endif + std::wstring w(buffer); + delete[] buffer; + return w; + } + + public: + virtual olc::rcode ApplicationStartUp() override { return olc::rcode::OK; } + virtual olc::rcode ApplicationCleanUp() override { return olc::rcode::OK; } + virtual olc::rcode ThreadStartUp() override { return olc::rcode::OK; } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + PostMessage(olc_hWnd, WM_DESTROY, 0, 0); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_hWnd }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + WNDCLASS wc; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpfnWndProc = olc_WindowEvent; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.lpszMenuName = nullptr; + wc.hbrBackground = nullptr; + wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE"); + RegisterClass(&wc); + + // Define window furniture + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME; + + olc::vi2d vTopLeft = vWindowPos; + + // Handle Fullscreen + if (bFullScreen) + { + dwExStyle = 0; + dwStyle = WS_VISIBLE | WS_POPUP; + HMONITOR hmon = MonitorFromWindow(olc_hWnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi) }; + if (!GetMonitorInfo(hmon, &mi)) return olc::rcode::FAIL; + vWindowSize = { mi.rcMonitor.right, mi.rcMonitor.bottom }; + vTopLeft.x = 0; + vTopLeft.y = 0; + } + + // Keep client size as requested + RECT rWndRect = { 0, 0, vWindowSize.x, vWindowSize.y }; + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + olc_hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE"), olcT(""), dwStyle, + vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x41] = Key::A; mapKeys[0x42] = Key::B; mapKeys[0x43] = Key::C; mapKeys[0x44] = Key::D; mapKeys[0x45] = Key::E; + mapKeys[0x46] = Key::F; mapKeys[0x47] = Key::G; mapKeys[0x48] = Key::H; mapKeys[0x49] = Key::I; mapKeys[0x4A] = Key::J; + mapKeys[0x4B] = Key::K; mapKeys[0x4C] = Key::L; mapKeys[0x4D] = Key::M; mapKeys[0x4E] = Key::N; mapKeys[0x4F] = Key::O; + mapKeys[0x50] = Key::P; mapKeys[0x51] = Key::Q; mapKeys[0x52] = Key::R; mapKeys[0x53] = Key::S; mapKeys[0x54] = Key::T; + mapKeys[0x55] = Key::U; mapKeys[0x56] = Key::V; mapKeys[0x57] = Key::W; mapKeys[0x58] = Key::X; mapKeys[0x59] = Key::Y; + mapKeys[0x5A] = Key::Z; + + mapKeys[VK_F1] = Key::F1; mapKeys[VK_F2] = Key::F2; mapKeys[VK_F3] = Key::F3; mapKeys[VK_F4] = Key::F4; + mapKeys[VK_F5] = Key::F5; mapKeys[VK_F6] = Key::F6; mapKeys[VK_F7] = Key::F7; mapKeys[VK_F8] = Key::F8; + mapKeys[VK_F9] = Key::F9; mapKeys[VK_F10] = Key::F10; mapKeys[VK_F11] = Key::F11; mapKeys[VK_F12] = Key::F12; + + mapKeys[VK_DOWN] = Key::DOWN; mapKeys[VK_LEFT] = Key::LEFT; mapKeys[VK_RIGHT] = Key::RIGHT; mapKeys[VK_UP] = Key::UP; + //mapKeys[VK_RETURN] = Key::ENTER;// mapKeys[VK_RETURN] = Key::RETURN; + + mapKeys[VK_BACK] = Key::BACK; mapKeys[VK_ESCAPE] = Key::ESCAPE; mapKeys[VK_RETURN] = Key::ENTER; mapKeys[VK_PAUSE] = Key::PAUSE; + mapKeys[VK_SCROLL] = Key::SCROLL; mapKeys[VK_TAB] = Key::TAB; mapKeys[VK_DELETE] = Key::DEL; mapKeys[VK_HOME] = Key::HOME; + mapKeys[VK_END] = Key::END; mapKeys[VK_PRIOR] = Key::PGUP; mapKeys[VK_NEXT] = Key::PGDN; mapKeys[VK_INSERT] = Key::INS; + mapKeys[VK_SHIFT] = Key::SHIFT; mapKeys[VK_CONTROL] = Key::CTRL; + mapKeys[VK_SPACE] = Key::SPACE; + + mapKeys[0x30] = Key::K0; mapKeys[0x31] = Key::K1; mapKeys[0x32] = Key::K2; mapKeys[0x33] = Key::K3; mapKeys[0x34] = Key::K4; + mapKeys[0x35] = Key::K5; mapKeys[0x36] = Key::K6; mapKeys[0x37] = Key::K7; mapKeys[0x38] = Key::K8; mapKeys[0x39] = Key::K9; + + mapKeys[VK_NUMPAD0] = Key::NP0; mapKeys[VK_NUMPAD1] = Key::NP1; mapKeys[VK_NUMPAD2] = Key::NP2; mapKeys[VK_NUMPAD3] = Key::NP3; mapKeys[VK_NUMPAD4] = Key::NP4; + mapKeys[VK_NUMPAD5] = Key::NP5; mapKeys[VK_NUMPAD6] = Key::NP6; mapKeys[VK_NUMPAD7] = Key::NP7; mapKeys[VK_NUMPAD8] = Key::NP8; mapKeys[VK_NUMPAD9] = Key::NP9; + mapKeys[VK_MULTIPLY] = Key::NP_MUL; mapKeys[VK_ADD] = Key::NP_ADD; mapKeys[VK_DIVIDE] = Key::NP_DIV; mapKeys[VK_SUBTRACT] = Key::NP_SUB; mapKeys[VK_DECIMAL] = Key::NP_DECIMAL; + + // Thanks scripticuk + mapKeys[VK_OEM_1] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[VK_OEM_2] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[VK_OEM_3] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[VK_OEM_4] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[VK_OEM_5] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[VK_OEM_6] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[VK_OEM_7] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[VK_OEM_8] = Key::OEM_8; // miscellaneous characters. Varies by keyboard + mapKeys[VK_OEM_PLUS] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[VK_OEM_COMMA] = Key::COMMA; // the comma key on any keyboard + mapKeys[VK_OEM_MINUS] = Key::MINUS; // the minus key on any keyboard + mapKeys[VK_OEM_PERIOD] = Key::PERIOD; // the period key on any keyboard + mapKeys[VK_CAPITAL] = Key::CAPS_LOCK; + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { +#ifdef UNICODE + SetWindowText(olc_hWnd, ConvertS2W(s).c_str()); +#else + SetWindowText(olc_hWnd, s.c_str()); +#endif + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override { return olc::rcode::FAIL; } + + // Windows Event Handler - this is statically connected to the windows event system + static LRESULT CALLBACK olc_WindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_MOUSEMOVE: + { + // Thanks @ForAbby (Discord) + uint16_t x = lParam & 0xFFFF; uint16_t y = (lParam >> 16) & 0xFFFF; + int16_t ix = *(int16_t*)&x; int16_t iy = *(int16_t*)&y; + ptrPGE->olc_UpdateMouse(ix, iy); + return 0; + } + case WM_SIZE: ptrPGE->olc_UpdateWindowSize(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; + case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; + case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; + case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; + case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + case WM_KEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_KEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_SYSKEYDOWN: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], true); return 0; + case WM_SYSKEYUP: ptrPGE->olc_UpdateKeyState(mapKeys[wParam], false); return 0; + case WM_LBUTTONDOWN:ptrPGE->olc_UpdateMouseState(0, true); return 0; + case WM_LBUTTONUP: ptrPGE->olc_UpdateMouseState(0, false); return 0; + case WM_RBUTTONDOWN:ptrPGE->olc_UpdateMouseState(1, true); return 0; + case WM_RBUTTONUP: ptrPGE->olc_UpdateMouseState(1, false); return 0; + case WM_MBUTTONDOWN:ptrPGE->olc_UpdateMouseState(2, true); return 0; + case WM_MBUTTONUP: ptrPGE->olc_UpdateMouseState(2, false); return 0; + case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); return 0; + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_linux +// O------------------------------------------------------------------------------O +// | START PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#if defined(OLC_PLATFORM_X11) +namespace olc +{ + class Platform_Linux : public olc::Platform + { + private: + X11::Display* olc_Display = nullptr; + X11::Window olc_WindowRoot; + X11::Window olc_Window; + X11::XVisualInfo* olc_VisualInfo; + X11::Colormap olc_ColourMap; + X11::XSetWindowAttributes olc_SetWindowAttribs; + + public: + virtual olc::rcode ApplicationStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + XDestroyWindow(olc_Display, olc_Window); + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({ olc_Display, &olc_Window, olc_VisualInfo }, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + using namespace X11; + XInitThreads(); + + // Grab the deafult display and window + olc_Display = XOpenDisplay(NULL); + olc_WindowRoot = DefaultRootWindow(olc_Display); + + // Based on the display capabilities, configure the appearance of the window + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); + olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); + olc_SetWindowAttribs.colormap = olc_ColourMap; + + // Register which events we are interested in receiving + olc_SetWindowAttribs.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask | StructureNotifyMask; + + // Create the window + olc_Window = XCreateWindow(olc_Display, olc_WindowRoot, vWindowPos.x, vWindowPos.y, + vWindowSize.x, vWindowSize.y, + 0, olc_VisualInfo->depth, InputOutput, olc_VisualInfo->visual, + CWColormap | CWEventMask, &olc_SetWindowAttribs); + + Atom wmDelete = XInternAtom(olc_Display, "WM_DELETE_WINDOW", true); + XSetWMProtocols(olc_Display, olc_Window, &wmDelete, 1); + + XMapWindow(olc_Display, olc_Window); + XStoreName(olc_Display, olc_Window, "OneLoneCoder.com - Pixel Game Engine"); + + if (bFullScreen) // Thanks DragonEye, again :D + { + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = olc_Window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, olc_Window); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + vWindowSize.x = gwa.width; + vWindowSize.y = gwa.height; + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys[0x61] = Key::A; mapKeys[0x62] = Key::B; mapKeys[0x63] = Key::C; mapKeys[0x64] = Key::D; mapKeys[0x65] = Key::E; + mapKeys[0x66] = Key::F; mapKeys[0x67] = Key::G; mapKeys[0x68] = Key::H; mapKeys[0x69] = Key::I; mapKeys[0x6A] = Key::J; + mapKeys[0x6B] = Key::K; mapKeys[0x6C] = Key::L; mapKeys[0x6D] = Key::M; mapKeys[0x6E] = Key::N; mapKeys[0x6F] = Key::O; + mapKeys[0x70] = Key::P; mapKeys[0x71] = Key::Q; mapKeys[0x72] = Key::R; mapKeys[0x73] = Key::S; mapKeys[0x74] = Key::T; + mapKeys[0x75] = Key::U; mapKeys[0x76] = Key::V; mapKeys[0x77] = Key::W; mapKeys[0x78] = Key::X; mapKeys[0x79] = Key::Y; + mapKeys[0x7A] = Key::Z; + + mapKeys[XK_F1] = Key::F1; mapKeys[XK_F2] = Key::F2; mapKeys[XK_F3] = Key::F3; mapKeys[XK_F4] = Key::F4; + mapKeys[XK_F5] = Key::F5; mapKeys[XK_F6] = Key::F6; mapKeys[XK_F7] = Key::F7; mapKeys[XK_F8] = Key::F8; + mapKeys[XK_F9] = Key::F9; mapKeys[XK_F10] = Key::F10; mapKeys[XK_F11] = Key::F11; mapKeys[XK_F12] = Key::F12; + + mapKeys[XK_Down] = Key::DOWN; mapKeys[XK_Left] = Key::LEFT; mapKeys[XK_Right] = Key::RIGHT; mapKeys[XK_Up] = Key::UP; + mapKeys[XK_KP_Enter] = Key::ENTER; mapKeys[XK_Return] = Key::ENTER; + + mapKeys[XK_BackSpace] = Key::BACK; mapKeys[XK_Escape] = Key::ESCAPE; mapKeys[XK_Linefeed] = Key::ENTER; mapKeys[XK_Pause] = Key::PAUSE; + mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; + mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; + mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; + + mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; + mapKeys[XK_5] = Key::K5; mapKeys[XK_6] = Key::K6; mapKeys[XK_7] = Key::K7; mapKeys[XK_8] = Key::K8; mapKeys[XK_9] = Key::K9; + + mapKeys[XK_KP_0] = Key::NP0; mapKeys[XK_KP_1] = Key::NP1; mapKeys[XK_KP_2] = Key::NP2; mapKeys[XK_KP_3] = Key::NP3; mapKeys[XK_KP_4] = Key::NP4; + mapKeys[XK_KP_5] = Key::NP5; mapKeys[XK_KP_6] = Key::NP6; mapKeys[XK_KP_7] = Key::NP7; mapKeys[XK_KP_8] = Key::NP8; mapKeys[XK_KP_9] = Key::NP9; + mapKeys[XK_KP_Multiply] = Key::NP_MUL; mapKeys[XK_KP_Add] = Key::NP_ADD; mapKeys[XK_KP_Divide] = Key::NP_DIV; mapKeys[XK_KP_Subtract] = Key::NP_SUB; mapKeys[XK_KP_Decimal] = Key::NP_DECIMAL; + + // These keys vary depending on the keyboard. I've included comments for US and UK keyboard layouts + mapKeys[XK_semicolon] = Key::OEM_1; // On US and UK keyboards this is the ';:' key + mapKeys[XK_slash] = Key::OEM_2; // On US and UK keyboards this is the '/?' key + mapKeys[XK_asciitilde] = Key::OEM_3; // On US keyboard this is the '~' key + mapKeys[XK_bracketleft] = Key::OEM_4; // On US and UK keyboards this is the '[{' key + mapKeys[XK_backslash] = Key::OEM_5; // On US keyboard this is '\|' key. + mapKeys[XK_bracketright] = Key::OEM_6; // On US and UK keyboards this is the ']}' key + mapKeys[XK_apostrophe] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key + mapKeys[XK_numbersign] = Key::OEM_8; // miscellaneous characters. Varies by keyboard. I believe this to be the '#~' key on UK keyboards + mapKeys[XK_equal] = Key::EQUALS; // the '+' key on any keyboard + mapKeys[XK_comma] = Key::COMMA; // the comma key on any keyboard + mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard + + mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + X11::XStoreName(olc_Display, olc_Window, s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + using namespace X11; + // Handle Xlib Message Loop - we do this in the + // same thread that OpenGL was created so we dont + // need to worry too much about multithreading with X11 + XEvent xev; + while (XPending(olc_Display)) + { + XNextEvent(olc_Display, &xev); + if (xev.type == Expose) + { + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, olc_Window, &gwa); + ptrPGE->olc_UpdateWindowSize(gwa.width, gwa.height); + } + else if (xev.type == ConfigureNotify) + { + XConfigureEvent xce = xev.xconfigure; + ptrPGE->olc_UpdateWindowSize(xce.width, xce.height); + } + else if (xev.type == KeyPress) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + XKeyEvent* e = (XKeyEvent*)&xev; // Because DragonEye loves numpads + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], true); + } + else if (xev.type == KeyRelease) + { + KeySym sym = XLookupKeysym(&xev.xkey, 0); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + XKeyEvent* e = (XKeyEvent*)&xev; + XLookupString(e, NULL, 0, &sym, NULL); + ptrPGE->olc_UpdateKeyState(mapKeys[sym], false); + } + else if (xev.type == ButtonPress) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, true); break; + case 2: ptrPGE->olc_UpdateMouseState(2, true); break; + case 3: ptrPGE->olc_UpdateMouseState(1, true); break; + case 4: ptrPGE->olc_UpdateMouseWheel(120); break; + case 5: ptrPGE->olc_UpdateMouseWheel(-120); break; + default: break; + } + } + else if (xev.type == ButtonRelease) + { + switch (xev.xbutton.button) + { + case 1: ptrPGE->olc_UpdateMouseState(0, false); break; + case 2: ptrPGE->olc_UpdateMouseState(2, false); break; + case 3: ptrPGE->olc_UpdateMouseState(1, false); break; + default: break; + } + } + else if (xev.type == MotionNotify) + { + ptrPGE->olc_UpdateMouse(xev.xmotion.x, xev.xmotion.y); + } + else if (xev.type == FocusIn) + { + ptrPGE->olc_UpdateKeyFocus(true); + } + else if (xev.type == FocusOut) + { + ptrPGE->olc_UpdateKeyFocus(false); + } + else if (xev.type == ClientMessage) + { + ptrPGE->olc_Terminate(); + } + } + return olc::OK; + } + }; +} +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: LINUX | +// O------------------------------------------------------------------------------O +#pragma endregion + +#pragma region platform_glut +// O------------------------------------------------------------------------------O +// | START PLATFORM: GLUT (used to make it simple for Apple) | +// O------------------------------------------------------------------------------O +// +// VERY IMPORTANT!!! The Apple port was originally created by @Mumflr (discord) +// and the repo for the development of this project can be found here: +// https://github.com/MumflrFumperdink/olcPGEMac which contains maccy goodness +// and support on how to setup your build environment. +// +// "MASSIVE MASSIVE THANKS TO MUMFLR" - Javidx9 +#if defined(OLC_PLATFORM_GLUT) +namespace olc { + + class Platform_GLUT : public olc::Platform + { + public: + static std::atomic* bActiveRef; + + virtual olc::rcode ApplicationStartUp() override { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); + return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + static void ExitMainLoop() { + if (!ptrPGE->OnUserDestroy()) { + *bActiveRef = true; + return; + } + platform->ThreadCleanUp(); + platform->ApplicationCleanUp(); + exit(0); + } + +#if defined(__APPLE__) + static void scrollWheelUpdate(id selff, SEL _sel, id theEvent) { + static const SEL deltaYSel = sel_registerName("deltaY"); + +#if defined(__aarch64__) // Thanks ruarq! + double deltaY = ((double (*)(id, SEL))objc_msgSend)(theEvent, deltaYSel); +#else + double deltaY = ((double (*)(id, SEL))objc_msgSend_fpret)(theEvent, deltaYSel); +#endif + + for (int i = 0; i < abs(deltaY); i++) { + if (deltaY > 0) { + ptrPGE->olc_UpdateMouseWheel(-1); + } + else if (deltaY < 0) { + ptrPGE->olc_UpdateMouseWheel(1); + } + } + } +#endif + static void ThreadFunct() { +#if defined(__APPLE__) + static bool hasEnabledCocoa = false; + if (!hasEnabledCocoa) { + // Objective-C Wizardry + Class NSApplicationClass = objc_getClass("NSApplication"); + + // NSApp = [NSApplication sharedApplication] + SEL sharedApplicationSel = sel_registerName("sharedApplication"); + id NSApp = ((id(*)(Class, SEL))objc_msgSend)(NSApplicationClass, sharedApplicationSel); + // window = [NSApp mainWindow] + SEL mainWindowSel = sel_registerName("mainWindow"); + id window = ((id(*)(id, SEL))objc_msgSend)(NSApp, mainWindowSel); + + // [window setStyleMask: NSWindowStyleMaskClosable | ~NSWindowStyleMaskResizable] + SEL setStyleMaskSel = sel_registerName("setStyleMask:"); + ((void (*)(id, SEL, NSUInteger))objc_msgSend)(window, setStyleMaskSel, 7); + + hasEnabledCocoa = true; + } +#endif + if (!*bActiveRef) { + ExitMainLoop(); + return; + } + glutPostRedisplay(); + } + + static void DrawFunct() { + ptrPGE->olc_CoreUpdate(); + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { +#if defined(__APPLE__) + Class GLUTViewClass = objc_getClass("GLUTView"); + + SEL scrollWheelSel = sel_registerName("scrollWheel:"); + bool resultAddMethod = class_addMethod(GLUTViewClass, scrollWheelSel, (IMP)scrollWheelUpdate, "v@:@"); + assert(resultAddMethod); +#endif + + renderer->PrepareDevice(); + + if (bFullScreen) + { + vWindowSize.x = glutGet(GLUT_SCREEN_WIDTH); + vWindowSize.y = glutGet(GLUT_SCREEN_HEIGHT); + glutFullScreen(); + } + else + { + if (vWindowSize.x > glutGet(GLUT_SCREEN_WIDTH) || vWindowSize.y > glutGet(GLUT_SCREEN_HEIGHT)) + { + perror("ERROR: The specified window dimensions do not fit on your screen\n"); + return olc::FAIL; + } + glutReshapeWindow(vWindowSize.x, vWindowSize.y - 1); + } + + // Create Keyboard Mapping + mapKeys[0x00] = Key::NONE; + mapKeys['A'] = Key::A; mapKeys['B'] = Key::B; mapKeys['C'] = Key::C; mapKeys['D'] = Key::D; mapKeys['E'] = Key::E; + mapKeys['F'] = Key::F; mapKeys['G'] = Key::G; mapKeys['H'] = Key::H; mapKeys['I'] = Key::I; mapKeys['J'] = Key::J; + mapKeys['K'] = Key::K; mapKeys['L'] = Key::L; mapKeys['M'] = Key::M; mapKeys['N'] = Key::N; mapKeys['O'] = Key::O; + mapKeys['P'] = Key::P; mapKeys['Q'] = Key::Q; mapKeys['R'] = Key::R; mapKeys['S'] = Key::S; mapKeys['T'] = Key::T; + mapKeys['U'] = Key::U; mapKeys['V'] = Key::V; mapKeys['W'] = Key::W; mapKeys['X'] = Key::X; mapKeys['Y'] = Key::Y; + mapKeys['Z'] = Key::Z; + + mapKeys[GLUT_KEY_F1] = Key::F1; mapKeys[GLUT_KEY_F2] = Key::F2; mapKeys[GLUT_KEY_F3] = Key::F3; mapKeys[GLUT_KEY_F4] = Key::F4; + mapKeys[GLUT_KEY_F5] = Key::F5; mapKeys[GLUT_KEY_F6] = Key::F6; mapKeys[GLUT_KEY_F7] = Key::F7; mapKeys[GLUT_KEY_F8] = Key::F8; + mapKeys[GLUT_KEY_F9] = Key::F9; mapKeys[GLUT_KEY_F10] = Key::F10; mapKeys[GLUT_KEY_F11] = Key::F11; mapKeys[GLUT_KEY_F12] = Key::F12; + + mapKeys[GLUT_KEY_DOWN] = Key::DOWN; mapKeys[GLUT_KEY_LEFT] = Key::LEFT; mapKeys[GLUT_KEY_RIGHT] = Key::RIGHT; mapKeys[GLUT_KEY_UP] = Key::UP; + mapKeys[13] = Key::ENTER; + + mapKeys[127] = Key::BACK; mapKeys[27] = Key::ESCAPE; + mapKeys[9] = Key::TAB; mapKeys[GLUT_KEY_HOME] = Key::HOME; + mapKeys[GLUT_KEY_END] = Key::END; mapKeys[GLUT_KEY_PAGE_UP] = Key::PGUP; mapKeys[GLUT_KEY_PAGE_DOWN] = Key::PGDN; mapKeys[GLUT_KEY_INSERT] = Key::INS; + mapKeys[32] = Key::SPACE; mapKeys[46] = Key::PERIOD; + + mapKeys[48] = Key::K0; mapKeys[49] = Key::K1; mapKeys[50] = Key::K2; mapKeys[51] = Key::K3; mapKeys[52] = Key::K4; + mapKeys[53] = Key::K5; mapKeys[54] = Key::K6; mapKeys[55] = Key::K7; mapKeys[56] = Key::K8; mapKeys[57] = Key::K9; + + // NOTE: MISSING KEYS :O + + glutKeyboardFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, true); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, true); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutKeyboardUpFunc([](unsigned char key, int x, int y) -> void { + switch (glutGetModifiers()) { + case 0: //This is when there are no modifiers + if ('a' <= key && key <= 'z') key -= 32; + break; + case GLUT_ACTIVE_SHIFT: + ptrPGE->olc_UpdateKeyState(Key::SHIFT, false); + break; + case GLUT_ACTIVE_CTRL: + if ('a' <= key && key <= 'z') key -= 32; + ptrPGE->olc_UpdateKeyState(Key::CTRL, false); + break; + case GLUT_ACTIVE_ALT: + if ('a' <= key && key <= 'z') key -= 32; + //No ALT in PGE + break; + } + + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + //Special keys + glutSpecialFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], true); + }); + + glutSpecialUpFunc([](int key, int x, int y) -> void { + if (mapKeys[key]) + ptrPGE->olc_UpdateKeyState(mapKeys[key], false); + }); + + glutMouseFunc([](int button, int state, int x, int y) -> void { + switch (button) { + case GLUT_LEFT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(0, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(0, true); + break; + case GLUT_MIDDLE_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(2, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(2, true); + break; + case GLUT_RIGHT_BUTTON: + if (state == GLUT_UP) ptrPGE->olc_UpdateMouseState(1, false); + else if (state == GLUT_DOWN) ptrPGE->olc_UpdateMouseState(1, true); + break; + } + }); + + auto mouseMoveCall = [](int x, int y) -> void { + ptrPGE->olc_UpdateMouse(x, y); + }; + + glutMotionFunc(mouseMoveCall); + glutPassiveMotionFunc(mouseMoveCall); + + glutEntryFunc([](int state) -> void { + if (state == GLUT_ENTERED) ptrPGE->olc_UpdateKeyFocus(true); + else if (state == GLUT_LEFT) ptrPGE->olc_UpdateKeyFocus(false); + }); + + glutDisplayFunc(DrawFunct); + glutIdleFunc(ThreadFunct); + + return olc::OK; + } + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + glutSetWindowTitle(s.c_str()); + return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override { + glutMainLoop(); + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + return olc::OK; + } + }; + + std::atomic* Platform_GLUT::bActiveRef{ nullptr }; + + //Custom Start + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + olc_PrepareEngine(); + if (!OnUserCreate()) return olc::FAIL; + Platform_GLUT::bActiveRef = &bAtomActive; + glutWMCloseFunc(Platform_GLUT::ExitMainLoop); + bAtomActive = true; + platform->StartSystemEventLoop(); + + //This code will not even be run but why not + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + + return olc::OK; + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: GLUT | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#pragma region platform_emscripten +// O------------------------------------------------------------------------------O +// | START PLATFORM: Emscripten - Totally Game Changing... | +// O------------------------------------------------------------------------------O + +// +// Firstly a big mega thank you to members of the OLC Community for sorting this +// out. Making a browser compatible version has been a priority for quite some +// time, but I lacked the expertise to do it. This awesome feature is possible +// because a group of former strangers got together and formed friendships over +// their shared passion for code. If anything demonstrates how powerful helping +// each other can be, it's this. - Javidx9 + +// Emscripten Platform: MaGetzUb, Moros1138, Slavka, Dandistine, Gorbit99, Bispoo +// also: Ishidex, Gusgo99, SlicEnDicE, Alexio + + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + +#include +#include + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline int olc_OnPageUnload() + { + olc::platform->ApplicationCleanUp(); return 0; + } +} + +namespace olc +{ + class Platform_Emscripten : public olc::Platform + { + public: + + virtual olc::rcode ApplicationStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ApplicationCleanUp() override + { + ThreadCleanUp(); return olc::rcode::OK; + } + + virtual olc::rcode ThreadStartUp() override + { + return olc::rcode::OK; + } + + virtual olc::rcode ThreadCleanUp() override + { + renderer->DestroyDevice(); return olc::OK; + } + + virtual olc::rcode CreateGraphics(bool bFullScreen, bool bEnableVSYNC, const olc::vi2d& vViewPos, const olc::vi2d& vViewSize) override + { + if (renderer->CreateDevice({}, bFullScreen, bEnableVSYNC) == olc::rcode::OK) + { + renderer->UpdateViewport(vViewPos, vViewSize); + return olc::rcode::OK; + } + else + return olc::rcode::FAIL; + } + + virtual olc::rcode CreateWindowPane(const olc::vi2d& vWindowPos, olc::vi2d& vWindowSize, bool bFullScreen) override + { + emscripten_set_canvas_element_size("#canvas", vWindowSize.x, vWindowSize.y); + + mapKeys[DOM_PK_UNKNOWN] = Key::NONE; + mapKeys[DOM_PK_A] = Key::A; mapKeys[DOM_PK_B] = Key::B; mapKeys[DOM_PK_C] = Key::C; mapKeys[DOM_PK_D] = Key::D; + mapKeys[DOM_PK_E] = Key::E; mapKeys[DOM_PK_F] = Key::F; mapKeys[DOM_PK_G] = Key::G; mapKeys[DOM_PK_H] = Key::H; + mapKeys[DOM_PK_I] = Key::I; mapKeys[DOM_PK_J] = Key::J; mapKeys[DOM_PK_K] = Key::K; mapKeys[DOM_PK_L] = Key::L; + mapKeys[DOM_PK_M] = Key::M; mapKeys[DOM_PK_N] = Key::N; mapKeys[DOM_PK_O] = Key::O; mapKeys[DOM_PK_P] = Key::P; + mapKeys[DOM_PK_Q] = Key::Q; mapKeys[DOM_PK_R] = Key::R; mapKeys[DOM_PK_S] = Key::S; mapKeys[DOM_PK_T] = Key::T; + mapKeys[DOM_PK_U] = Key::U; mapKeys[DOM_PK_V] = Key::V; mapKeys[DOM_PK_W] = Key::W; mapKeys[DOM_PK_X] = Key::X; + mapKeys[DOM_PK_Y] = Key::Y; mapKeys[DOM_PK_Z] = Key::Z; + mapKeys[DOM_PK_0] = Key::K0; mapKeys[DOM_PK_1] = Key::K1; mapKeys[DOM_PK_2] = Key::K2; + mapKeys[DOM_PK_3] = Key::K3; mapKeys[DOM_PK_4] = Key::K4; mapKeys[DOM_PK_5] = Key::K5; + mapKeys[DOM_PK_6] = Key::K6; mapKeys[DOM_PK_7] = Key::K7; mapKeys[DOM_PK_8] = Key::K8; + mapKeys[DOM_PK_9] = Key::K9; + mapKeys[DOM_PK_F1] = Key::F1; mapKeys[DOM_PK_F2] = Key::F2; mapKeys[DOM_PK_F3] = Key::F3; mapKeys[DOM_PK_F4] = Key::F4; + mapKeys[DOM_PK_F5] = Key::F5; mapKeys[DOM_PK_F6] = Key::F6; mapKeys[DOM_PK_F7] = Key::F7; mapKeys[DOM_PK_F8] = Key::F8; + mapKeys[DOM_PK_F9] = Key::F9; mapKeys[DOM_PK_F10] = Key::F10; mapKeys[DOM_PK_F11] = Key::F11; mapKeys[DOM_PK_F12] = Key::F12; + mapKeys[DOM_PK_ARROW_UP] = Key::UP; mapKeys[DOM_PK_ARROW_DOWN] = Key::DOWN; + mapKeys[DOM_PK_ARROW_LEFT] = Key::LEFT; mapKeys[DOM_PK_ARROW_RIGHT] = Key::RIGHT; + mapKeys[DOM_PK_SPACE] = Key::SPACE; mapKeys[DOM_PK_TAB] = Key::TAB; + mapKeys[DOM_PK_SHIFT_LEFT] = Key::SHIFT; mapKeys[DOM_PK_SHIFT_RIGHT] = Key::SHIFT; + mapKeys[DOM_PK_CONTROL_LEFT] = Key::CTRL; mapKeys[DOM_PK_CONTROL_RIGHT] = Key::CTRL; + mapKeys[DOM_PK_INSERT] = Key::INS; mapKeys[DOM_PK_DELETE] = Key::DEL; mapKeys[DOM_PK_HOME] = Key::HOME; + mapKeys[DOM_PK_END] = Key::END; mapKeys[DOM_PK_PAGE_UP] = Key::PGUP; mapKeys[DOM_PK_PAGE_DOWN] = Key::PGDN; + mapKeys[DOM_PK_BACKSPACE] = Key::BACK; mapKeys[DOM_PK_ESCAPE] = Key::ESCAPE; + mapKeys[DOM_PK_ENTER] = Key::ENTER; mapKeys[DOM_PK_NUMPAD_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_NUMPAD_ENTER] = Key::ENTER; mapKeys[DOM_PK_PAUSE] = Key::PAUSE; + mapKeys[DOM_PK_SCROLL_LOCK] = Key::SCROLL; + mapKeys[DOM_PK_NUMPAD_0] = Key::NP0; mapKeys[DOM_PK_NUMPAD_1] = Key::NP1; mapKeys[DOM_PK_NUMPAD_2] = Key::NP2; + mapKeys[DOM_PK_NUMPAD_3] = Key::NP3; mapKeys[DOM_PK_NUMPAD_4] = Key::NP4; mapKeys[DOM_PK_NUMPAD_5] = Key::NP5; + mapKeys[DOM_PK_NUMPAD_6] = Key::NP6; mapKeys[DOM_PK_NUMPAD_7] = Key::NP7; mapKeys[DOM_PK_NUMPAD_8] = Key::NP8; + mapKeys[DOM_PK_NUMPAD_9] = Key::NP9; + mapKeys[DOM_PK_NUMPAD_MULTIPLY] = Key::NP_MUL; mapKeys[DOM_PK_NUMPAD_DIVIDE] = Key::NP_DIV; + mapKeys[DOM_PK_NUMPAD_ADD] = Key::NP_ADD; mapKeys[DOM_PK_NUMPAD_SUBTRACT] = Key::NP_SUB; + mapKeys[DOM_PK_NUMPAD_DECIMAL] = Key::NP_DECIMAL; + mapKeys[DOM_PK_PERIOD] = Key::PERIOD; mapKeys[DOM_PK_EQUAL] = Key::EQUALS; + mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; + mapKeys[DOM_PK_CAPS_LOCK] = Key::CAPS_LOCK; + mapKeys[DOM_PK_SEMICOLON] = Key::OEM_1; mapKeys[DOM_PK_SLASH] = Key::OEM_2; mapKeys[DOM_PK_BACKQUOTE] = Key::OEM_3; + mapKeys[DOM_PK_BRACKET_LEFT] = Key::OEM_4; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_5; mapKeys[DOM_PK_BRACKET_RIGHT] = Key::OEM_6; + mapKeys[DOM_PK_QUOTE] = Key::OEM_7; mapKeys[DOM_PK_BACKSLASH] = Key::OEM_8; + + // Keyboard Callbacks + emscripten_set_keydown_callback("#canvas", 0, 1, keyboard_callback); + emscripten_set_keyup_callback("#canvas", 0, 1, keyboard_callback); + + // Mouse Callbacks + emscripten_set_wheel_callback("#canvas", 0, 1, wheel_callback); + emscripten_set_mousedown_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mouseup_callback("#canvas", 0, 1, mouse_callback); + emscripten_set_mousemove_callback("#canvas", 0, 1, mouse_callback); + + // Touch Callbacks + emscripten_set_touchstart_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchmove_callback("#canvas", 0, 1, touch_callback); + emscripten_set_touchend_callback("#canvas", 0, 1, touch_callback); + + // Canvas Focus Callbacks + emscripten_set_blur_callback("#canvas", 0, 1, focus_callback); + emscripten_set_focus_callback("#canvas", 0, 1, focus_callback); + +#pragma warning disable format + EM_ASM(window.onunload = Module._olc_OnPageUnload; ); + + // IMPORTANT! - Sorry About This... + // + // In order to handle certain browser based events, such as resizing and + // going to full screen, we have to effectively inject code into the container + // running the PGE. Yes, I vomited about 11 times too when the others were + // convincing me this is the future. Well, this isnt the future, and if it + // were to be, I want no part of what must be a miserable distopian free + // for all of anarchic code injection to get rudimentary events like "Resize()". + // + // Wake up people! Of course theres a spoon. There has to be to keep feeding + // the giant web baby. + + + // Fullscreen and Resize Observers + EM_ASM({ + + // cache for reuse + Module._olc_EmscriptenShellCss = "width: 100%; height: 70vh; margin-left: auto; margin-right: auto;"; + + // width / height = aspect ratio + Module._olc_WindowAspectRatio = $0 / $1; + Module.canvas.parentNode.addEventListener("resize", function(e) { + + if (e.defaultPrevented) { e.stopPropagation(); return; } + var viewWidth = e.detail.width; + var viewHeight = e.detail.width / Module._olc_WindowAspectRatio; + if (viewHeight > e.detail.height) + { + viewHeight = e.detail.height; + viewWidth = e.detail.height * Module._olc_WindowAspectRatio; + } + + if (Module.canvas.parentNode.className == 'emscripten_border') + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss + " width: " + viewWidth.toString() + "px; height: " + viewHeight.toString() + "px;"; + + Module.canvas.setAttribute("width", viewWidth); + Module.canvas.setAttribute("height", viewHeight); + + if (document.fullscreenElement != null) + { + var top = (e.detail.height - viewHeight) / 2; + var left = (e.detail.width - viewWidth) / 2; + Module.canvas.style.position = "fixed"; + Module.canvas.style.top = top.toString() + "px"; + Module.canvas.style.left = left.toString() + "px"; + Module.canvas.style.width = ""; + Module.canvas.style.height = ""; + } + + // trigger PGE update + Module._olc_PGE_UpdateWindowSize(viewWidth, viewHeight); + // this is really only needed when enter/exiting fullscreen + Module.canvas.focus(); + // prevent this event from ever affecting the document beyond this element + e.stopPropagation(); + }); + + // helper function to prevent repeating the same code everywhere + Module._olc_ResizeCanvas = function() + { + // yes, we still have to wait, sigh.. + setTimeout(function() + { + // if default template, stretch width as well + if (Module.canvas.parentNode.className == 'emscripten_border') + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; + + // override it's styling so we can get it's stretched size + Module.canvas.style.cssText = "width: 100%; height: 100%; outline: none;"; + + // setup custom resize event + var resizeEvent = new CustomEvent('resize', + { + detail: { + width: Module.canvas.clientWidth, + height : Module.canvas.clientHeight + }, + bubbles : true, + cancelable : true + }); + + // trigger custom resize event on canvas element + Module.canvas.dispatchEvent(resizeEvent); + }, 50); + }; + + + // Disable Refresh Gesture on mobile + document.body.style.cssText += " overscroll-behavior-y: contain;"; + + if (Module.canvas.parentNode.className == 'emscripten_border') + { + // force body to have no margin in emscripten's minimal shell + document.body.style.margin = "0"; + Module.canvas.parentNode.style.cssText = Module._olc_EmscriptenShellCss; + } + + Module._olc_ResizeCanvas(); + + // observe and react to resizing of the container element + var resizeObserver = new ResizeObserver(function(entries) { Module._olc_ResizeCanvas(); }).observe(Module.canvas.parentNode); + + // observe and react to changes that occur when entering/exiting fullscreen + var mutationObserver = new MutationObserver(function(mutationsList, observer) + { + // a change has occurred, let's check them out! + for (var i = 0; i < mutationsList.length; i++) + { + // cycle through all of the newly added elements + for (var j = 0; j < mutationsList[i].addedNodes.length; j++) + { + // if this element is a our canvas, trigger resize + if (mutationsList[i].addedNodes[j].id == 'canvas') + Module._olc_ResizeCanvas(); + } + } + }).observe(Module.canvas.parentNode, + { + attributes: false, + childList : true, + subtree : false + }); + + // add resize listener on window + window.addEventListener("resize", function(e) { Module._olc_ResizeCanvas(); }); + + }, vWindowSize.x, vWindowSize.y); // Fullscreen and Resize Observers +#pragma warning restore format + return olc::rcode::OK; + } + + // Interface PGE's UpdateWindowSize, for use in Javascript + void UpdateWindowSize(int width, int height) + { + ptrPGE->olc_UpdateWindowSize(width, height); + } + + //TY Gorbit + static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + ptrPGE->olc_UpdateKeyFocus(false); + ptrPGE->olc_UpdateMouseFocus(false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + ptrPGE->olc_UpdateKeyFocus(true); + ptrPGE->olc_UpdateMouseFocus(true); + } + + return 0; + } + + //TY Moros + static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_KEYDOWN) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], true); + + // THANK GOD!! for this compute function. And thanks Dandistine for pointing it out! + if (eventType == EMSCRIPTEN_EVENT_KEYUP) + ptrPGE->olc_UpdateKeyState(mapKeys[emscripten_compute_dom_pk_code(e->code)], false); + + //Consume keyboard events so that keys like F1 and F5 don't do weird things + return EM_TRUE; + } + + //TY Moros + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + ptrPGE->olc_UpdateMouseWheel(-1 * e->deltaY); + + return EM_TRUE; + } + + //TY Bispoo + static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) + { + // Move + if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + } + + // Start + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) + { + ptrPGE->olc_UpdateMouse(e->touches->targetX, e->touches->targetY); + ptrPGE->olc_UpdateMouseState(0, true); + } + + // End + if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) + { + ptrPGE->olc_UpdateMouseState(0, false); + } + + return EM_TRUE; + } + + //TY Moros + static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) + { + //Mouse Movement + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) + ptrPGE->olc_UpdateMouse(e->targetX, e->targetY); + + + //Mouse button press + if (e->button == 0) // left click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(0, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(0, false); + } + + if (e->button == 2) // right click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(1, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(1, false); + + } + + if (e->button == 1) // middle click + { + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) + ptrPGE->olc_UpdateMouseState(2, true); + else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) + ptrPGE->olc_UpdateMouseState(2, false); + + //at the moment only middle mouse needs to consume events. + return EM_TRUE; + } + + return EM_FALSE; + } + + + virtual olc::rcode SetWindowTitle(const std::string& s) override + { + emscripten_set_window_title(s.c_str()); return olc::OK; + } + + virtual olc::rcode StartSystemEventLoop() override + { + return olc::OK; + } + + virtual olc::rcode HandleSystemEvent() override + { + return olc::OK; + } + + static void MainLoop() + { + olc::Platform::ptrPGE->olc_CoreUpdate(); + if (!ptrPGE->olc_IsRunning()) + { + if (ptrPGE->OnUserDestroy()) + { + emscripten_cancel_main_loop(); + platform->ApplicationCleanUp(); + } + else + { + ptrPGE->olc_Reanimate(); + } + } + } + }; + + //Emscripten needs a special Start function + //Much of this is usually done in EngineThread, but that isn't used here + olc::rcode PixelGameEngine::Start() + { + if (platform->ApplicationStartUp() != olc::OK) return olc::FAIL; + + // Construct the window + if (platform->CreateWindowPane({ 30,30 }, vWindowSize, bFullScreen) != olc::OK) return olc::FAIL; + olc_UpdateWindowSize(vWindowSize.x, vWindowSize.y); + + // Some implementations may form an event loop here + if (platform->ThreadStartUp() == olc::FAIL) return olc::FAIL; + + // Do engine context specific initialisation + olc_PrepareEngine(); + + // Consider the "thread" started + bAtomActive = true; + + // Create user resources as part of this thread + for (auto& ext : vExtensions) ext->OnBeforeUserCreate(); + if (!OnUserCreate()) bAtomActive = false; + for (auto& ext : vExtensions) ext->OnAfterUserCreate(); + + platform->StartSystemEventLoop(); + + //This causes a heap memory corruption in Emscripten for some reason + //Platform_Emscripten::bActiveRef = &bAtomActive; + emscripten_set_main_loop(&Platform_Emscripten::MainLoop, 0, 1); + + // Wait for thread to be exited + if (platform->ApplicationCleanUp() != olc::OK) return olc::FAIL; + return olc::OK; + } +} + +extern "C" +{ + EMSCRIPTEN_KEEPALIVE inline void olc_PGE_UpdateWindowSize(int width, int height) + { + emscripten_set_canvas_element_size("#canvas", width, height); + // Thanks slavka + ((olc::Platform_Emscripten*)olc::platform.get())->UpdateWindowSize(width, height); + } +} + +#endif +// O------------------------------------------------------------------------------O +// | END PLATFORM: Emscripten | +// O------------------------------------------------------------------------------O +#pragma endregion + + +#endif // Headless + +// O------------------------------------------------------------------------------O +// | olcPixelGameEngine Auto-Configuration | +// O------------------------------------------------------------------------------O +#pragma region pge_config +namespace olc +{ + void PixelGameEngine::olc_ConfigureSystem() + { + +#if !defined(OLC_PGE_HEADLESS) + +#if defined(OLC_IMAGE_GDI) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_LIBPNG) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_STB) + olc::Sprite::loader = std::make_unique(); +#endif + +#if defined(OLC_IMAGE_CUSTOM_EX) + olc::Sprite::loader = std::make_unique(); +#endif + + + + +#if defined(OLC_PLATFORM_WINAPI) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_X11) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_GLUT) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_EMSCRIPTEN) + platform = std::make_unique(); +#endif + +#if defined(OLC_PLATFORM_CUSTOM_EX) + platform = std::make_unique(); +#endif + + + +#if defined(OLC_GFX_OPENGL10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGL33) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_OPENGLES2) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX10) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_DIRECTX11) + renderer = std::make_unique(); +#endif + +#if defined(OLC_GFX_CUSTOM_EX) + renderer = std::make_unique(); +#endif + + // Associate components with PGE instance + platform->ptrPGE = this; + renderer->ptrPGE = this; +#else + olc::Sprite::loader = nullptr; + platform = nullptr; + renderer = nullptr; +#endif + } +} + +#pragma endregion + +#endif // End OLC_PGE_APPLICATION + +// O------------------------------------------------------------------------------O +// | END OF OLC_PGE_APPLICATION | +// O------------------------------------------------------------------------------O