Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
0e33d3fffb | |||
e9ba22354c | |||
4a1eb1cd61 | |||
3b74654413 | |||
9ba8d5c407 | |||
4b3bda3391 | |||
d0a1d2e29c |
219
TEST_Camera2D.cpp
Normal file
219
TEST_Camera2D.cpp
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
Example file for olcUTIL_Camera2D.h
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define OLC_PGE_APPLICATION
|
||||||
|
#include "olcPixelGameEngine.h"
|
||||||
|
|
||||||
|
#define OLC_PGEX_TRANSFORMEDVIEW
|
||||||
|
#include "extensions/olcPGEX_TransformedView.h"
|
||||||
|
|
||||||
|
#include "utilities/olcUTIL_Camera2D.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
To demonstrate the camera, we need a world. In this case its a simle tile
|
||||||
|
world of 80x75 tiles, and each tile is 32x32 screen pixels.
|
||||||
|
|
||||||
|
A transformed view is used to navigate the world manually via the middle
|
||||||
|
mouse button in "free roam" mode, or controlled by the camera.
|
||||||
|
|
||||||
|
Specifically a Tile Transformed View is used, which means all units for
|
||||||
|
drawing and for the camera are specified in tile space, i.e. 1 tile is
|
||||||
|
1x1 units (regardless of pixel size)
|
||||||
|
|
||||||
|
No assets are used for this application, so the world is constructed
|
||||||
|
out of coloured squares so you can see you are moving through it.
|
||||||
|
|
||||||
|
Pressing "TAB" key will swap between "free roam" and "play" modes. In
|
||||||
|
free roam mode, you can use middle mouse button to pan and zoom around
|
||||||
|
the world. The camera's visible area to the player is highlighted in red.
|
||||||
|
In play mode, the camera behaves as it would in a 2D game, depending upon
|
||||||
|
the selected mode.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TEST_Camera2D : public olc::PixelGameEngine
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TEST_Camera2D()
|
||||||
|
{
|
||||||
|
sAppName = "Camera2D Utility Test";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformed view object to make world offsetting simple
|
||||||
|
olc::TileTransformedView tv;
|
||||||
|
|
||||||
|
// Conveninet constants to define tile map world
|
||||||
|
olc::vi2d m_vWorldSize = { 80, 75 };
|
||||||
|
olc::vi2d m_vTileSize = { 32, 32 };
|
||||||
|
|
||||||
|
// The camera!
|
||||||
|
olc::utils::Camera2D camera;
|
||||||
|
|
||||||
|
// The point that represents the player, it is "tracked"
|
||||||
|
// by the camera
|
||||||
|
olc::vf2d vTrackedPoint;
|
||||||
|
|
||||||
|
// Flag whether we are in "free roam" or "play" mode
|
||||||
|
bool bFreeRoam = false;
|
||||||
|
|
||||||
|
// The world map, stored as a 1D array
|
||||||
|
std::vector<uint8_t> vWorldMap;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool OnUserCreate() override
|
||||||
|
{
|
||||||
|
// Construct transform view
|
||||||
|
tv = olc::TileTransformedView(GetScreenSize(), m_vTileSize);
|
||||||
|
|
||||||
|
// Construct Camera
|
||||||
|
vTrackedPoint = { 20.0f, 20.0f };
|
||||||
|
camera = olc::utils::Camera2D(GetScreenSize() / m_vTileSize, vTrackedPoint);
|
||||||
|
|
||||||
|
// Configure Camera
|
||||||
|
camera.SetTarget(vTrackedPoint);
|
||||||
|
camera.SetMode(olc::utils::Camera2D::Mode::Simple);
|
||||||
|
camera.SetWorldBoundary({ 0.0f, 0.0f }, m_vWorldSize);
|
||||||
|
camera.EnableWorldBoundary(true);
|
||||||
|
|
||||||
|
// Create "tile map" world with just two tiles
|
||||||
|
vWorldMap.resize(m_vWorldSize.x * m_vWorldSize.y);
|
||||||
|
for (int i = 0; i < vWorldMap.size(); i++)
|
||||||
|
vWorldMap[i] = ((rand() % 20) == 1) ? 1 : 0;
|
||||||
|
|
||||||
|
// Set background colour
|
||||||
|
Clear(olc::CYAN);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OnUserUpdate(float fElapsedTime) override
|
||||||
|
{
|
||||||
|
// In free roam, middle mouse button pans & zooms
|
||||||
|
if (bFreeRoam)
|
||||||
|
tv.HandlePanAndZoom();
|
||||||
|
|
||||||
|
// Handle player "physics" in response to key presses
|
||||||
|
olc::vf2d vVel = { 0.0f, 0.0f };
|
||||||
|
if (GetKey(olc::Key::W).bHeld) vVel += {0, -1};
|
||||||
|
if (GetKey(olc::Key::S).bHeld) vVel += {0, +1};
|
||||||
|
if (GetKey(olc::Key::A).bHeld) vVel += {-1, 0};
|
||||||
|
if (GetKey(olc::Key::D).bHeld) vVel += {+1, 0};
|
||||||
|
vTrackedPoint += vVel * 8.0f * fElapsedTime;
|
||||||
|
|
||||||
|
// Switch between "free roam" and "play" mode with TAB key
|
||||||
|
if (GetKey(olc::Key::TAB).bPressed)
|
||||||
|
{
|
||||||
|
// Always setup camera to play mode when tab key pressed
|
||||||
|
tv.SetWorldOffset(camera.GetViewPosition());
|
||||||
|
tv.SetWorldScale(m_vTileSize);
|
||||||
|
bFreeRoam = !bFreeRoam;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch camera mode in operation
|
||||||
|
if (GetKey(olc::Key::K1).bPressed)
|
||||||
|
camera.SetMode(olc::utils::Camera2D::Mode::Simple);
|
||||||
|
if (GetKey(olc::Key::K2).bPressed)
|
||||||
|
camera.SetMode(olc::utils::Camera2D::Mode::EdgeMove);
|
||||||
|
if (GetKey(olc::Key::K3).bPressed)
|
||||||
|
camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow);
|
||||||
|
if (GetKey(olc::Key::K4).bPressed)
|
||||||
|
camera.SetMode(olc::utils::Camera2D::Mode::FixedScreens);
|
||||||
|
|
||||||
|
// Update the camera, if teh tracked object remains visible,
|
||||||
|
// true is returned
|
||||||
|
bool bOnScreen = camera.Update(fElapsedTime);
|
||||||
|
|
||||||
|
// In "play" mode, set the transformed view to that required by
|
||||||
|
// the camera
|
||||||
|
if (!bFreeRoam)
|
||||||
|
tv.SetWorldOffset(camera.GetViewPosition());
|
||||||
|
|
||||||
|
// Render "tile map", by getting visible tiles
|
||||||
|
olc::vi2d vTileTL = tv.GetTopLeftTile().max({ 0,0 });
|
||||||
|
olc::vi2d vTileBR = tv.GetBottomRightTile().min(m_vWorldSize);
|
||||||
|
olc::vi2d vTile;
|
||||||
|
// Then looping through them and drawing them
|
||||||
|
for (vTile.y = vTileTL.y; vTile.y < vTileBR.y; vTile.y++)
|
||||||
|
for (vTile.x = vTileTL.x; vTile.x < vTileBR.x; vTile.x++)
|
||||||
|
{
|
||||||
|
int idx = vTile.y * m_vWorldSize.x + vTile.x;
|
||||||
|
|
||||||
|
if (vWorldMap[idx] == 0)
|
||||||
|
tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(40, 40, 40));
|
||||||
|
|
||||||
|
if (vWorldMap[idx] == 1)
|
||||||
|
tv.FillRectDecal(vTile, { 1.0f, 1.0f }, olc::Pixel(60, 60, 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the "player" as a 1x1 cell
|
||||||
|
tv.FillRectDecal(vTrackedPoint - olc::vf2d(0.5f, 0.5f), {1.0f, 1.0f}, olc::BLUE);
|
||||||
|
|
||||||
|
// Overlay with information
|
||||||
|
if (bFreeRoam)
|
||||||
|
{
|
||||||
|
tv.FillRectDecal(camera.GetViewPosition(), camera.GetViewSize(), olc::PixelF(1.0f, 0.0f, 0.0f, 0.5f));
|
||||||
|
DrawStringPropDecal({ 2, 2 }, "TAB: Free Mode, M-Btn to Pan & Zoom", olc::YELLOW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawStringPropDecal({ 2,2 }, "TAB: Play Mode", olc::YELLOW);
|
||||||
|
|
||||||
|
DrawStringPropDecal({ 2,12 }, "WASD : Move", olc::YELLOW);
|
||||||
|
DrawStringPropDecal({ 2,22 }, "CAMERA: 1) Simple 2) EdgeMove 3) LazyFollow 4) Screens", olc::YELLOW);
|
||||||
|
DrawStringPropDecal({ 2,42 }, vTrackedPoint.str(), olc::YELLOW);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
TEST_Camera2D demo;
|
||||||
|
if (demo.Construct(512, 480, 2, 2))
|
||||||
|
demo.Start();
|
||||||
|
return 0;
|
||||||
|
}
|
197
diff
Normal file
197
diff
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
diff --git a/.gitignore b/.gitignore
|
||||||
|
index e915029..dd400cf 100644
|
||||||
|
--- a/.gitignore
|
||||||
|
+++ b/.gitignore
|
||||||
|
@@ -3,3 +3,5 @@
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
/.vs
|
||||||
|
+/utilities/olcUTIL_AffineView.h
|
||||||
|
+/utilities/olcUTIL_Maths.h
|
||||||
|
diff --git a/olcPixelGameEngine.h b/olcPixelGameEngine.h
|
||||||
|
index 5480a15..92dee60 100644
|
||||||
|
--- a/olcPixelGameEngine.h
|
||||||
|
+++ b/olcPixelGameEngine.h
|
||||||
|
@@ -3,7 +3,7 @@
|
||||||
|
olcPixelGameEngine.h
|
||||||
|
|
||||||
|
+-------------------------------------------------------------+
|
||||||
|
- | OneLoneCoder Pixel Game Engine v2.21 |
|
||||||
|
+ | OneLoneCoder Pixel Game Engine v2.23 |
|
||||||
|
| "What do you need? Pixels... Lots of Pixels..." - javidx9 |
|
||||||
|
+-------------------------------------------------------------+
|
||||||
|
|
||||||
|
@@ -197,7 +197,7 @@
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
- David Barr, aka javidx9, <20>OneLoneCoder 2018, 2019, 2020, 2021, 2022
|
||||||
|
+ David Barr, aka javidx9, (c) OneLoneCoder 2018, 2019, 2020, 2021, 2022
|
||||||
|
*/
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
@@ -315,6 +315,9 @@
|
||||||
|
+FillTexturedTriangle() - Software rasterizes a textured, coloured, triangle
|
||||||
|
+FillTexturedPolygon() - Hijacks DecalStructure for configuration
|
||||||
|
+olc::vf2d arguments for Sprite::Sample() functions
|
||||||
|
+ 2.22: = Fix typo on dragged file buffers for unicode builds
|
||||||
|
+ 2.23: Fixed Emscripten host sizing errors - Thanks Moros
|
||||||
|
+ Fixed v2d_generic.clamp() function
|
||||||
|
|
||||||
|
!! 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 !!
|
||||||
|
@@ -394,7 +397,7 @@ int main()
|
||||||
|
#include <cstring>
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
-#define PGE_VER 221
|
||||||
|
+#define PGE_VER 223
|
||||||
|
|
||||||
|
// O------------------------------------------------------------------------------O
|
||||||
|
// | COMPILER CONFIGURATION ODDITIES |
|
||||||
|
@@ -682,7 +685,7 @@ namespace olc
|
||||||
|
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) }; }
|
||||||
|
- v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1)->min(v2); }
|
||||||
|
+ v2d_generic clamp(const v2d_generic& v1, const v2d_generic& v2) const { return this->max(v1).min(v2); }
|
||||||
|
v2d_generic lerp(const v2d_generic& v1, const double t) { return this->operator*(T(1.0 - t)) + (v1 * T(t)); }
|
||||||
|
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; }
|
||||||
|
@@ -1357,8 +1360,9 @@ namespace olc
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(OLC_PLATFORM_X11)
|
||||||
|
- namespace X11
|
||||||
|
- {#include <GL/glx.h>}
|
||||||
|
+ namespace X11 {
|
||||||
|
+ #include <GL/glx.h>
|
||||||
|
+ }
|
||||||
|
#define CALLSTYLE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@@ -4594,17 +4598,17 @@ namespace olc
|
||||||
|
// #include <OpenGL/glu.h>
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
-//#if defined(OLC_PLATFORM_EMSCRIPTEN)
|
||||||
|
-// #include <EGL/egl.h>
|
||||||
|
-// #include <GLES2/gl2.h>
|
||||||
|
-// #define GL_GLEXT_PROTOTYPES
|
||||||
|
-// #include <GLES2/gl2ext.h>
|
||||||
|
-// #include <emscripten/emscripten.h>
|
||||||
|
-// #define CALLSTYLE
|
||||||
|
-// typedef EGLBoolean(locSwapInterval_t)(EGLDisplay display, EGLint interval);
|
||||||
|
-// #define GL_CLAMP GL_CLAMP_TO_EDGE
|
||||||
|
-// #define OGL_LOAD(t, n) n;
|
||||||
|
-//#endif
|
||||||
|
+#if defined(OLC_PLATFORM_EMSCRIPTEN)
|
||||||
|
+ #include <EGL/egl.h>
|
||||||
|
+ #include <GLES2/gl2.h>
|
||||||
|
+ #define GL_GLEXT_PROTOTYPES
|
||||||
|
+ #include <GLES2/gl2ext.h>
|
||||||
|
+ #include <emscripten/emscripten.h>
|
||||||
|
+ #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
|
||||||
|
{
|
||||||
|
@@ -5574,7 +5578,7 @@ namespace olc
|
||||||
|
vFiles.push_back(std::string(buffer));
|
||||||
|
delete[] buffer;
|
||||||
|
#else
|
||||||
|
- vFiles.push_back(std::string(dbuffer));
|
||||||
|
+ vFiles.push_back(std::string(dfbuffer));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -6318,8 +6322,8 @@ namespace olc
|
||||||
|
let isFullscreen = (document.fullscreenElement != null);
|
||||||
|
|
||||||
|
// get the width of the containing element
|
||||||
|
- let width = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerWidth : Module.canvas.parentNode.clientWidth;
|
||||||
|
- let height = (isFullscreen || !Module.olc_AssumeDefaultShells) ? window.innerHeight : Module.canvas.parentNode.clientHeight;
|
||||||
|
+ let width = (isFullscreen) ? window.innerWidth : Module.canvas.parentNode.clientWidth;
|
||||||
|
+ let height = (isFullscreen) ? window.innerHeight : Module.canvas.parentNode.clientHeight;
|
||||||
|
|
||||||
|
// calculate the expected viewport size
|
||||||
|
let viewWidth = width;
|
||||||
|
diff --git a/utilities/olcUTIL_Geometry2D.h b/utilities/olcUTIL_Geometry2D.h
|
||||||
|
index 801c1d3..3b8f363 100644
|
||||||
|
--- a/utilities/olcUTIL_Geometry2D.h
|
||||||
|
+++ b/utilities/olcUTIL_Geometry2D.h
|
||||||
|
@@ -277,43 +277,60 @@ namespace olc::utils::geom2d
|
||||||
|
|
||||||
|
// Returns closest point to point
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
- inline olc::v2d_generic<T2> closest(const olc::v2d_generic<T1>& p1, const olc::v2d_generic<T2>& p2)
|
||||||
|
+ inline olc::v2d_generic<T1> closest(const olc::v2d_generic<T1>& p1, const olc::v2d_generic<T2>& p2)
|
||||||
|
{
|
||||||
|
return p1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns closest point on line to point
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
- inline olc::v2d_generic<T2> closest(const line<T1>& l, const olc::v2d_generic<T2>& p)
|
||||||
|
+ inline olc::v2d_generic<T1> closest(const line<T1>& l, const olc::v2d_generic<T2>& p)
|
||||||
|
{
|
||||||
|
auto d = l.vector();
|
||||||
|
- double u = std::clamp(double(d.dot(p - l.start) / d.mag2()), 0.0, 1.0);
|
||||||
|
- return l.start + d * u;
|
||||||
|
+ double u = std::clamp(double(d.dot(p - l.start)) / d.mag2(), 0.0, 1.0);
|
||||||
|
+ return l.start + u * d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns closest point on circle to point
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
- inline olc::v2d_generic<T2> closest(const circle<T1>& c, const olc::v2d_generic<T2>& p)
|
||||||
|
+ inline olc::v2d_generic<T1> closest(const circle<T1>& c, const olc::v2d_generic<T2>& p)
|
||||||
|
{
|
||||||
|
- return c.pos + (p - c.pos).norm() * c.radius;
|
||||||
|
+ return c.pos + olc::vd2d(p - c.pos).norm() * c.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns closest point on rectangle to point
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
- inline olc::v2d_generic<T2> closest(const rect<T1>& r, const olc::v2d_generic<T2>& p)
|
||||||
|
+ inline olc::v2d_generic<T1> closest(const rect<T1>& r, const olc::v2d_generic<T2>& p)
|
||||||
|
{
|
||||||
|
// This could be a "constrain" function hmmmm
|
||||||
|
// TODO: Not quite what i wanted, should restrain to boundary
|
||||||
|
- return olc::v2d_generic<T2>{ std::clamp(p.x, r.pos.x, r.pos.x + r.size.x), std::clamp(p.y, r.pos.y, r.pos.y + r.size.y) };
|
||||||
|
+ return olc::v2d_generic<T1>{ std::clamp(p.x, r.pos.x, r.pos.x + r.size.x), std::clamp(p.y, r.pos.y, r.pos.y + r.size.y) };
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns closest point on triangle to point
|
||||||
|
template<typename T1, typename T2>
|
||||||
|
- inline olc::v2d_generic<T2> closest(const triangle<T1>& t, const olc::v2d_generic<T2>& p)
|
||||||
|
+ inline olc::v2d_generic<T1> closest(const triangle<T1>& t, const olc::v2d_generic<T2>& p)
|
||||||
|
{
|
||||||
|
- // TODO:
|
||||||
|
- return olc::v2d_generic<T2>();
|
||||||
|
+ olc::utils::geom2d::line<T1> l{t.pos[0], t.pos[1]};
|
||||||
|
+ auto p0 = closest(l, p);
|
||||||
|
+ auto d0 = (p0 - p).mag2();
|
||||||
|
+
|
||||||
|
+ l.end = t.pos[2];
|
||||||
|
+ auto p1 = closest(l, p);
|
||||||
|
+ auto d1 = (p1 - p).mag2();
|
||||||
|
+
|
||||||
|
+ l.start = t.pos[1];
|
||||||
|
+ auto p2 = closest(l, p);
|
||||||
|
+ auto d2 = (p2 - p).mag2();
|
||||||
|
+
|
||||||
|
+ if((d0 <= d1) && (d0 <= d2)) {
|
||||||
|
+ return p0;
|
||||||
|
+ } else if((d1 <= d0) && (d1 <= d2)) {
|
||||||
|
+ return p1;
|
||||||
|
+ } else {
|
||||||
|
+ return p2;
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
6711
olcPixelGameEngine.h.orig
Normal file
6711
olcPixelGameEngine.h.orig
Normal file
File diff suppressed because it is too large
Load Diff
12
olcPixelGameEngine.h.rej
Normal file
12
olcPixelGameEngine.h.rej
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
--- olcPixelGameEngine.h
|
||||||
|
+++ olcPixelGameEngine.h
|
||||||
|
@@ -315,6 +315,9 @@
|
||||||
|
+FillTexturedTriangle() - Software rasterizes a textured, coloured, triangle
|
||||||
|
+FillTexturedPolygon() - Hijacks DecalStructure for configuration
|
||||||
|
+olc::vf2d arguments for Sprite::Sample() functions
|
||||||
|
+ 2.22: = Fix typo on dragged file buffers for unicode builds
|
||||||
|
+ 2.23: Fixed Emscripten host sizing errors - Thanks Moros
|
||||||
|
+ Fixed v2d_generic.clamp() function
|
||||||
|
|
||||||
|
!! 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 !!
|
258
olcUTIL_Camera2D.h
Normal file
258
olcUTIL_Camera2D.h
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
OneLoneCoder - Camera2D v1.00
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
A 2D world camera with various modes
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "olcPixelGameEngine.h"
|
||||||
|
|
||||||
|
namespace olc::utils
|
||||||
|
{
|
||||||
|
class Camera2D
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class Mode : uint8_t
|
||||||
|
{
|
||||||
|
Simple, // No motion, just directly settable
|
||||||
|
EdgeMove, // Moves as target crosses boundary
|
||||||
|
LazyFollow, // Lazily follows the target
|
||||||
|
FixedScreens, // Moves statically between "screens"
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline Camera2D() : m_pTarget(&m_vLocalTarget) {}
|
||||||
|
|
||||||
|
// Construct a camera with a viewable area size, and an optional starting position
|
||||||
|
inline Camera2D(const olc::vf2d& vViewSize, const olc::vf2d& vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget)
|
||||||
|
{
|
||||||
|
m_vViewSize = vViewSize;
|
||||||
|
m_vViewPos = vViewPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the operational mode of this camera
|
||||||
|
inline void SetMode(const Mode t)
|
||||||
|
{
|
||||||
|
m_nMode = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the operational mode of this camera
|
||||||
|
inline Mode GetMode() const
|
||||||
|
{
|
||||||
|
return m_nMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the position of the target being tracked by this camera
|
||||||
|
inline const olc::vf2d& GetTarget() const
|
||||||
|
{
|
||||||
|
return *m_pTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the position of the cameras focus point
|
||||||
|
inline const olc::vf2d& GetPosition() const
|
||||||
|
{
|
||||||
|
return m_vPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the top left of teh cameras visible area in world space
|
||||||
|
inline const olc::vf2d& GetViewPosition() const
|
||||||
|
{
|
||||||
|
return m_vViewPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the camera's visible area
|
||||||
|
inline const olc::vf2d& GetViewSize() const
|
||||||
|
{
|
||||||
|
return m_vViewSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tracked point via pointer
|
||||||
|
inline void SetTarget(olc::vf2d& vTarget)
|
||||||
|
{
|
||||||
|
m_pTarget = &vTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tracked point via const ref - {10, 35} for example
|
||||||
|
inline void SetTarget(const olc::vf2d&& vTarget)
|
||||||
|
{
|
||||||
|
m_vLocalTarget = vTarget;
|
||||||
|
m_pTarget = &m_vLocalTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set world boundary rectangle
|
||||||
|
inline void SetWorldBoundary(const olc::vf2d& vPos, const olc::vf2d& vSize)
|
||||||
|
{
|
||||||
|
m_vWorldBoundaryPos = vPos;
|
||||||
|
m_vWorldBoundarySize = vSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instruct camera to respect world boundaries
|
||||||
|
inline void EnableWorldBoundary(const bool bEnable)
|
||||||
|
{
|
||||||
|
m_bWorldBoundary = bEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we using a world boundary?
|
||||||
|
inline bool IsWorldBoundaryEnabled() const
|
||||||
|
{
|
||||||
|
return m_bWorldBoundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the world boundary rectangle position
|
||||||
|
inline const olc::vf2d& GetWorldBoundaryPosition() const
|
||||||
|
{
|
||||||
|
return m_vWorldBoundaryPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the world boundary rectangle size
|
||||||
|
inline const olc::vf2d& GetWorldBoundarySize() const
|
||||||
|
{
|
||||||
|
return m_vWorldBoundarySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the velocity at which the lazy follower reaches tracked point
|
||||||
|
inline void SetLazyFollowRate(const float fRate)
|
||||||
|
{
|
||||||
|
m_fLazyFollowRate = fRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the velocity at which the lazy follower reaches tracked point
|
||||||
|
inline float GetLazyFollowRate() const
|
||||||
|
{
|
||||||
|
return m_fLazyFollowRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set distance from tracked point to start nudging screen
|
||||||
|
inline void SetEdgeTriggerDistance(const olc::vf2d& vEdge)
|
||||||
|
{
|
||||||
|
m_vEdgeTriggerDistance = vEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return disance from tracked point that screen will nudge
|
||||||
|
inline const olc::vf2d& GetEdgeTriggerDistance() const
|
||||||
|
{
|
||||||
|
return m_vEdgeTriggerDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update camera, animating if necessary, obeying world boundary rules
|
||||||
|
// returns true if target is visible
|
||||||
|
inline virtual bool Update(const float fElapsedTime)
|
||||||
|
{
|
||||||
|
switch (m_nMode)
|
||||||
|
{
|
||||||
|
case Mode::Simple:
|
||||||
|
{
|
||||||
|
m_vPosition = GetTarget();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Mode::EdgeMove:
|
||||||
|
{
|
||||||
|
olc::vf2d vOverlap = GetTarget() - m_vPosition;
|
||||||
|
if (vOverlap.x > m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x - m_vEdgeTriggerDistance.x;
|
||||||
|
if (vOverlap.x < -m_vEdgeTriggerDistance.x) m_vPosition.x += vOverlap.x + m_vEdgeTriggerDistance.x;
|
||||||
|
if (vOverlap.y > m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y - m_vEdgeTriggerDistance.y;
|
||||||
|
if (vOverlap.y < -m_vEdgeTriggerDistance.y) m_vPosition.y += vOverlap.y + m_vEdgeTriggerDistance.y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Mode::LazyFollow:
|
||||||
|
{
|
||||||
|
m_vPosition += (GetTarget() - m_vPosition) * m_fLazyFollowRate * fElapsedTime;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Mode::FixedScreens:
|
||||||
|
{
|
||||||
|
m_vPosition = olc::vf2d(olc::vi2d(GetTarget() / m_vScreenSize) * olc::vi2d(m_vScreenSize)) + (m_vViewSize * 0.5f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make camera target the middle of the view
|
||||||
|
m_vViewPos = m_vPosition - (m_vViewSize * 0.5f);
|
||||||
|
|
||||||
|
// Clamp to World Boundary (if in place)
|
||||||
|
if (m_bWorldBoundary)
|
||||||
|
{
|
||||||
|
m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetTarget().x >= m_vViewPos.x && GetTarget().x < (m_vViewPos.x + m_vViewSize.x) &&
|
||||||
|
GetTarget().y >= m_vViewPos.y && GetTarget().y < (m_vViewPos.y + m_vViewSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Position of camera focus point in the world
|
||||||
|
olc::vf2d m_vPosition;
|
||||||
|
// Rectangular size of camera viewing area
|
||||||
|
olc::vf2d m_vViewSize;
|
||||||
|
// Top left coordinate of camera viewing area
|
||||||
|
olc::vf2d m_vViewPos;
|
||||||
|
// Camera movement mode
|
||||||
|
Mode m_nMode = Mode::Simple;
|
||||||
|
|
||||||
|
// Target Vector2D object camera should follow (either ref or ptr)
|
||||||
|
olc::vf2d* m_pTarget = nullptr;
|
||||||
|
olc::vf2d m_vLocalTarget;
|
||||||
|
|
||||||
|
// World Boundary
|
||||||
|
bool m_bWorldBoundary = false;
|
||||||
|
olc::vf2d m_vWorldBoundaryPos = { 0.0f, 0.0f };
|
||||||
|
olc::vf2d m_vWorldBoundarySize = { 256.0f, 240.0f };
|
||||||
|
|
||||||
|
// Mode specific
|
||||||
|
olc::vf2d m_vEdgeTriggerDistance = { 1.0f, 1.0f };
|
||||||
|
float m_fLazyFollowRate = 4.0f;
|
||||||
|
olc::vi2d m_vScreenSize = { 16,15 };
|
||||||
|
};
|
||||||
|
}
|
158
olcUTIL_Container.h
Normal file
158
olcUTIL_Container.h
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
OneLoneCoder - Container v1.00
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Assortment of std::container like objects with access specific mechanisms
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
SingleSelection
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
This container behaves like a std::vector in all circumstances but features
|
||||||
|
additional methods that allow it to surve as a list of items, one of which
|
||||||
|
can be selected, and the items can be manipulated.
|
||||||
|
|
||||||
|
An example of this would be the image layers in Photoshop.
|
||||||
|
|
||||||
|
For convenience, all container operations operate on an item index rather
|
||||||
|
than an iterator
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace olc::utils::Container
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class SingleSelection : public std::vector<T>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SingleSelection() : std::vector<T>()
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
SingleSelection(std::initializer_list<T> list) : std::vector<T>(list)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Returns selected item in container
|
||||||
|
size_t selection() const
|
||||||
|
{
|
||||||
|
return m_nSelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the item actually selected
|
||||||
|
const T& selected() const
|
||||||
|
{
|
||||||
|
return this->operator[](m_nSelectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the item actually selected
|
||||||
|
T& selected()
|
||||||
|
{
|
||||||
|
return this->operator[](m_nSelectedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select item in container
|
||||||
|
void select(const size_t item)
|
||||||
|
{
|
||||||
|
m_nSelectedItem = std::clamp(item, size_t(0), this->size() - size_t(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move selected item positively
|
||||||
|
void move_up()
|
||||||
|
{
|
||||||
|
if(move_up(m_nSelectedItem))
|
||||||
|
m_nSelectedItem++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move selected item negatively
|
||||||
|
void move_down()
|
||||||
|
{
|
||||||
|
if(move_down(m_nSelectedItem))
|
||||||
|
m_nSelectedItem--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move specified item negatively
|
||||||
|
bool move_down(const size_t item)
|
||||||
|
{
|
||||||
|
// Are there at least two items and not first one selected?
|
||||||
|
if (this->size() >= 2 && item > 0)
|
||||||
|
{
|
||||||
|
std::swap(this->operator[](item - 1), this->operator[](item));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move specified item positively
|
||||||
|
bool move_up(const size_t item)
|
||||||
|
{
|
||||||
|
// Are there at least two items and not last one selected?
|
||||||
|
if (this->size() >= 2 && item < this->size() - 1)
|
||||||
|
{
|
||||||
|
std::swap(this->operator[](item + 1), this->operator[](item));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_after(const size_t idx, const T& value)
|
||||||
|
{
|
||||||
|
this->insert(idx, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t m_nSelectedItem = 0;
|
||||||
|
};
|
||||||
|
}
|
435
olcUTIL_DataFile.h
Normal file
435
olcUTIL_DataFile.h
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
/*
|
||||||
|
OneLoneCoder - DataFile v1.00
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
An "easy to use" serialisation/deserialisation class that yields
|
||||||
|
human readable hierachical files.
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
|
#include <fstream>
|
||||||
|
#include <stack>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace olc::utils
|
||||||
|
{
|
||||||
|
class datafile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline datafile() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Sets the String Value of a Property (for a given index)
|
||||||
|
inline void SetString(const std::string& sString, const size_t nItem = 0)
|
||||||
|
{
|
||||||
|
if (nItem >= m_vContent.size())
|
||||||
|
m_vContent.resize(nItem + 1);
|
||||||
|
|
||||||
|
m_vContent[nItem] = sString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the String Value of a Property (for a given index) or ""
|
||||||
|
inline const std::string GetString(const size_t nItem = 0) const
|
||||||
|
{
|
||||||
|
if (nItem >= m_vContent.size())
|
||||||
|
return "";
|
||||||
|
else
|
||||||
|
return m_vContent[nItem];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the Real Value of a Property (for a given index) or 0.0
|
||||||
|
inline const double GetReal(const size_t nItem = 0) const
|
||||||
|
{
|
||||||
|
return std::atof(GetString(nItem).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the Real Value of a Property (for a given index)
|
||||||
|
inline void SetReal(const double d, const size_t nItem = 0)
|
||||||
|
{
|
||||||
|
SetString(std::to_string(d), nItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the Integer Value of a Property (for a given index) or 0
|
||||||
|
inline const int32_t GetInt(const size_t nItem = 0) const
|
||||||
|
{
|
||||||
|
return std::atoi(GetString(nItem).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the Integer Value of a Property (for a given index)
|
||||||
|
inline void SetInt(const int32_t n, const size_t nItem = 0)
|
||||||
|
{
|
||||||
|
SetString(std::to_string(n), nItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of Values a property consists of
|
||||||
|
inline size_t GetValueCount() const
|
||||||
|
{
|
||||||
|
return m_vContent.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a property exists - useful to avoid creating properties
|
||||||
|
// via reading them, though non-essential
|
||||||
|
inline bool HasProperty(const std::string& sName) const
|
||||||
|
{
|
||||||
|
return m_mapObjects.count(sName) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access a datafile via a convenient name - "root.node.something.property"
|
||||||
|
inline datafile& GetProperty(const std::string& name)
|
||||||
|
{
|
||||||
|
size_t x = name.find_first_of('.');
|
||||||
|
if (x != std::string::npos)
|
||||||
|
{
|
||||||
|
std::string sProperty = name.substr(0, x);
|
||||||
|
if (HasProperty(sProperty))
|
||||||
|
return operator[](sProperty).GetProperty(name.substr(x + 1, name.size()));
|
||||||
|
else
|
||||||
|
return operator[](sProperty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return operator[](name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access a numbered element - "node[23]", or "root[56].node"
|
||||||
|
inline datafile& GetIndexedProperty(const std::string& name, const size_t nIndex)
|
||||||
|
{
|
||||||
|
return GetProperty(name + "[" + std::to_string(nIndex) + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Writes a "datafile" node (and all of its child nodes and properties) recursively
|
||||||
|
// to a file.
|
||||||
|
inline static bool Write(const datafile& n, const std::string& sFileName, const std::string& sIndent = "\t", const char sListSep = ',')
|
||||||
|
{
|
||||||
|
// Cache indentation level
|
||||||
|
size_t nIndentCount = 0;
|
||||||
|
// Cache sperator string for convenience
|
||||||
|
std::string sSeperator = std::string(1, sListSep) + " ";
|
||||||
|
|
||||||
|
// Fully specified lambda, because this lambda is recursive!
|
||||||
|
std::function<void(const datafile&, std::ofstream&)> write = [&](const datafile& n, std::ofstream& file)
|
||||||
|
{
|
||||||
|
// Lambda creates string given indentation preferences
|
||||||
|
auto indent = [&](const std::string& sString, const size_t nCount)
|
||||||
|
{
|
||||||
|
std::string sOut;
|
||||||
|
for (size_t n = 0; n < nCount; n++) sOut += sString;
|
||||||
|
return sOut;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate through each property of this node
|
||||||
|
for (auto const& property : n.m_vecObjects)
|
||||||
|
{
|
||||||
|
// Does property contain any sub objects?
|
||||||
|
if (property.second.m_vecObjects.empty())
|
||||||
|
{
|
||||||
|
// No, so it's an assigned field and should just be written. If the property
|
||||||
|
// is flagged as comment, it has no assignment potential. First write the
|
||||||
|
// property name
|
||||||
|
file << indent(sIndent, nIndentCount) << property.first << (property.second.m_bIsComment ? "" : " = ");
|
||||||
|
|
||||||
|
// Second, write the property value (or values, seperated by provided
|
||||||
|
// separation charater
|
||||||
|
size_t nItems = property.second.GetValueCount();
|
||||||
|
for (size_t i = 0; i < property.second.GetValueCount(); i++)
|
||||||
|
{
|
||||||
|
// If the Value being written, in string form, contains the separation
|
||||||
|
// character, then the value must be written inside quotation marks. Note,
|
||||||
|
// that if the Value is the last of a list of Values for a property, it is
|
||||||
|
// not suffixed with the separator
|
||||||
|
size_t x = property.second.GetString(i).find_first_of(sListSep);
|
||||||
|
if (x != std::string::npos)
|
||||||
|
{
|
||||||
|
// Value contains separator, so wrap in quotes
|
||||||
|
file << "\"" << property.second.GetString(i) << "\"" << ((nItems > 1) ? sSeperator : "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Value does not contain separator, so just write out
|
||||||
|
file << property.second.GetString(i) << ((nItems > 1) ? sSeperator : "");
|
||||||
|
}
|
||||||
|
nItems--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property written, move to next line
|
||||||
|
file << "\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Yes, property has properties of its own, so it's a node
|
||||||
|
// Force a new line and write out the node's name
|
||||||
|
file << "\n" << indent(sIndent, nIndentCount) << property.first << "\n";
|
||||||
|
// Open braces, and update indentation
|
||||||
|
file << indent(sIndent, nIndentCount) << "{\n";
|
||||||
|
nIndentCount++;
|
||||||
|
// Recursively write that node
|
||||||
|
write(property.second, file);
|
||||||
|
// Node written, so close braces
|
||||||
|
file << indent(sIndent, nIndentCount) << "}\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've finished writing out a node, regardless of state, our indentation
|
||||||
|
// must decrease, unless we're top level
|
||||||
|
if (nIndentCount > 0) nIndentCount--;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start Here! Open the file for writing
|
||||||
|
std::ofstream file(sFileName);
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
// Write the file starting form the supplied node
|
||||||
|
write(n, file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool Read(datafile& n, const std::string& sFileName, const char sListSep = ',')
|
||||||
|
{
|
||||||
|
// Open the file!
|
||||||
|
std::ifstream file(sFileName);
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
// These variables are outside of the read loop, as we will
|
||||||
|
// need to refer to previous iteration values in certain conditions
|
||||||
|
std::string sPropName = "";
|
||||||
|
std::string sPropValue = "";
|
||||||
|
|
||||||
|
// The file is fundamentally structured as a stack, so we will read it
|
||||||
|
// in a such, but note the data structure in memory is not explicitly
|
||||||
|
// stored in a stack, but one is constructed implicitly via the nodes
|
||||||
|
// owning other nodes (aka a tree)
|
||||||
|
|
||||||
|
// I dont want to accidentally create copies all over the place, nor do
|
||||||
|
// I want to use pointer syntax, so being a bit different and stupidly
|
||||||
|
// using std::reference_wrapper, so I can store references to datafile
|
||||||
|
// nodes in a std::container.
|
||||||
|
std::stack<std::reference_wrapper<datafile>> stkPath;
|
||||||
|
stkPath.push(n);
|
||||||
|
|
||||||
|
|
||||||
|
// Read file line by line and process
|
||||||
|
while (!file.eof())
|
||||||
|
{
|
||||||
|
// Read line
|
||||||
|
std::string line;
|
||||||
|
std::getline(file, line);
|
||||||
|
|
||||||
|
// This little lambda removes whitespace from
|
||||||
|
// beginning and end of supplied string
|
||||||
|
auto trim = [](std::string& s)
|
||||||
|
{
|
||||||
|
s.erase(0, s.find_first_not_of(" \t\n\r\f\v"));
|
||||||
|
s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
trim(line);
|
||||||
|
|
||||||
|
// If line has content
|
||||||
|
if (!line.empty())
|
||||||
|
{
|
||||||
|
// Test if its a comment...
|
||||||
|
if (line[0] == '#')
|
||||||
|
{
|
||||||
|
// ...it is a comment, so ignore
|
||||||
|
datafile comment;
|
||||||
|
comment.m_bIsComment = true;
|
||||||
|
stkPath.top().get().m_vecObjects.push_back({ line, comment });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ...it is content, so parse. Firstly, find if the line
|
||||||
|
// contains an assignment. If it does then it's a property...
|
||||||
|
size_t x = line.find_first_of('=');
|
||||||
|
if (x != std::string::npos)
|
||||||
|
{
|
||||||
|
// ...so split up the property into a name, and its values!
|
||||||
|
|
||||||
|
// Extract the property name, which is all characters up to
|
||||||
|
// first assignment, trim any whitespace from ends
|
||||||
|
sPropName = line.substr(0, x);
|
||||||
|
trim(sPropName);
|
||||||
|
|
||||||
|
// Extract the property value, which is all characters after
|
||||||
|
// the first assignment operator, trim any whitespace from ends
|
||||||
|
sPropValue = line.substr(x + 1, line.size());
|
||||||
|
trim(sPropValue);
|
||||||
|
|
||||||
|
// The value may be in list form: a, b, c, d, e, f etc and some of those
|
||||||
|
// elements may exist in quotes a, b, c, "d, e", f. So we need to iterate
|
||||||
|
// character by character and break up the value
|
||||||
|
bool bInQuotes = false;
|
||||||
|
std::string sToken;
|
||||||
|
size_t nTokenCount = 0;
|
||||||
|
for (const auto c : sPropValue)
|
||||||
|
{
|
||||||
|
// Is character a quote...
|
||||||
|
if (c == '\"')
|
||||||
|
{
|
||||||
|
// ...yes, so toggle quote state
|
||||||
|
bInQuotes = !bInQuotes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ...no, so proceed creating token. If we are in quote state
|
||||||
|
// then just append characters until we exit quote state.
|
||||||
|
if (bInQuotes)
|
||||||
|
{
|
||||||
|
sToken.append(1, c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Is the character our seperator? If it is
|
||||||
|
if (c == sListSep)
|
||||||
|
{
|
||||||
|
// Clean up the token
|
||||||
|
trim(sToken);
|
||||||
|
// Add it to the vector of values for this property
|
||||||
|
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
|
||||||
|
// Reset our token state
|
||||||
|
sToken.clear();
|
||||||
|
nTokenCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// It isnt, so just append to token
|
||||||
|
sToken.append(1, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any residual characters at this point just make up the final token,
|
||||||
|
// so clean it up and add it to the vector of values
|
||||||
|
if (!sToken.empty())
|
||||||
|
{
|
||||||
|
trim(sToken);
|
||||||
|
stkPath.top().get()[sPropName].SetString(sToken, nTokenCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ...but if it doesnt, then it's something structural
|
||||||
|
if (line[0] == '{')
|
||||||
|
{
|
||||||
|
// Open brace, so push this node to stack, subsequent properties
|
||||||
|
// will belong to the new node
|
||||||
|
stkPath.push(stkPath.top().get()[sPropName]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (line[0] == '}')
|
||||||
|
{
|
||||||
|
// Close brace, so this node has been defined, pop it from the
|
||||||
|
// stack
|
||||||
|
stkPath.pop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Line is a property with no assignment. Who knows whether this is useful,
|
||||||
|
// but we can simply add it as a valueless property...
|
||||||
|
sPropName = line;
|
||||||
|
// ...actually it is useful, as valuless properties are typically
|
||||||
|
// going to be the names of new datafile nodes on the next iteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close and exit!
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// File not found, so fail
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline datafile& operator[](const std::string& name)
|
||||||
|
{
|
||||||
|
// Check if this "node"'s map already contains an object with this name...
|
||||||
|
if (m_mapObjects.count(name) == 0)
|
||||||
|
{
|
||||||
|
// ...it did not! So create this object in the map. First get a vector id
|
||||||
|
// and link it with the name in the unordered_map
|
||||||
|
m_mapObjects[name] = m_vecObjects.size();
|
||||||
|
// then creating the new, blank object in the vector of objects
|
||||||
|
m_vecObjects.push_back({ name, datafile() });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...it exists! so return the object, by getting its index from the map, and using that
|
||||||
|
// index to look up a vector element.
|
||||||
|
return m_vecObjects[m_mapObjects[name]].second;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The "list of strings" that make up a property value
|
||||||
|
std::vector<std::string> m_vContent;
|
||||||
|
|
||||||
|
// Linkage to create "ordered" unordered_map. We have a vector of
|
||||||
|
// "properties", and the index to a specific element is mapped.
|
||||||
|
std::vector<std::pair<std::string, datafile>> m_vecObjects;
|
||||||
|
std::unordered_map<std::string, size_t> m_mapObjects;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Used to identify if a property is a comment or not, not user facing
|
||||||
|
bool m_bIsComment = false;
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user