|
|
/*
|
|
|
olcPixelGameEngine.h
|
|
|
|
|
|
+-------------------------------------------------------------+
|
|
|
| OneLoneCoder Pixel Game Engine v2.06 |
|
|
|
| "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 - 2020 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 stdc++fs
|
|
|
|
|
|
Set these compiler options: -std=c++17
|
|
|
|
|
|
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 Eremiell, slavka, gurkanctn, Phantim, IProgramInCPP
|
|
|
JackOJC, KrossX, Huhlig, Dragoneye, Appa, JustinRichardsMusic, SliceNDice, dandistine
|
|
|
Ralakus, Gorbit99, raoul, joshinils, benedani, Moros1138, SaladinAkara & MagetzUb
|
|
|
for advice, ideas and testing, and I'd like to extend my appreciation to the
|
|
|
164K YouTube followers, 70+ Patreons and 8K Discord server members who give me
|
|
|
the motivation to keep going with all this :D
|
|
|
|
|
|
Significant Contributors: @Moros1138, @SaladinAkara, @MaGetzUb, @slavka, @Dragoneye & @Gorbit99
|
|
|
|
|
|
Special thanks to those who bring gifts!
|
|
|
GnarGnarHead.......Domina
|
|
|
Gorbit99...........Bastion, Ori & The Blind Forest, Terraria
|
|
|
Marti Morta........Gris
|
|
|
Danicron...........Terraria
|
|
|
SaladinAkara.......Aseprite
|
|
|
AlterEgo...........Final Fantasy XII - The Zodiac Age
|
|
|
|
|
|
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, <EFBFBD>OneLoneCoder 2018, 2019, 2020
|
|
|
|
|
|
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: Added DrawPartialWarpedDecal(), Added DrawPartialRotatedDecal()
|
|
|
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 dpecified 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
|
|
|
*/
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
// 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 you 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;
|
|
|
}
|
|
|
|
|
|
*/
|
|
|
|
|
|
#ifndef OLC_PGE_DEF
|
|
|
#define OLC_PGE_DEF
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | STANDARD INCLUDES |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#include <cmath>
|
|
|
#include <cstdint>
|
|
|
#include <string>
|
|
|
#include <iostream>
|
|
|
#include <streambuf>
|
|
|
#include <sstream>
|
|
|
#include <chrono>
|
|
|
#include <vector>
|
|
|
#include <list>
|
|
|
#include <thread>
|
|
|
#include <atomic>
|
|
|
#include <fstream>
|
|
|
#include <map>
|
|
|
#include <functional>
|
|
|
#include <algorithm>
|
|
|
#include <array>
|
|
|
#include <cstring>
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | COMPILER CONFIGURATION ODDITIES |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#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__)
|
|
|
#if __cplusplus >= 201703L
|
|
|
#undef USE_EXPERIMENTAL_FS
|
|
|
#endif
|
|
|
#endif
|
|
|
|
|
|
#if defined(USE_EXPERIMENTAL_FS) || defined(FORCE_EXPERIMENTAL_FS)
|
|
|
// C++14
|
|
|
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
|
|
|
#include <experimental/filesystem>
|
|
|
namespace _gfs = std::experimental::filesystem::v1;
|
|
|
#else
|
|
|
// C++17
|
|
|
#include <filesystem>
|
|
|
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)
|
|
|
|
|
|
|
|
|
#if !defined(OLC_GFX_OPENGL33) && !defined(OLC_GFX_DIRECTX10)
|
|
|
#define OLC_GFX_OPENGL10
|
|
|
#endif
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olcPixelGameEngine INTERFACE DECLARATION |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
namespace olc
|
|
|
{
|
|
|
class PixelGameEngine;
|
|
|
|
|
|
// Pixel Game Engine Advanced Configuration
|
|
|
constexpr uint8_t nMouseButtons = 5;
|
|
|
constexpr uint8_t nDefaultAlpha = 0xFF;
|
|
|
constexpr uint32_t nDefaultPixel = (nDefaultAlpha << 24);
|
|
|
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);
|
|
|
bool operator==(const Pixel& p) const;
|
|
|
bool operator!=(const Pixel& p) const;
|
|
|
};
|
|
|
|
|
|
Pixel PixelF(float red, float green, float blue, float alpha = 1.0f);
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olc::vX2d - A generic 2D vector type |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#if !defined(OLC_IGNORE_VEC2D)
|
|
|
template <class T>
|
|
|
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){ }
|
|
|
T mag() { return std::sqrt(x * x + y * y); }
|
|
|
T mag2() { return x * x + y * y; }
|
|
|
v2d_generic norm() { T r = 1 / mag(); return v2d_generic(x*r, y*r); }
|
|
|
v2d_generic perp() { return v2d_generic(-y, x); }
|
|
|
T dot(const v2d_generic& rhs) { return this->x * rhs.x + this->y * rhs.y; }
|
|
|
T cross(const v2d_generic& rhs) { 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; }
|
|
|
operator v2d_generic<int32_t>() const { return { static_cast<int32_t>(this->x), static_cast<int32_t>(this->y) }; }
|
|
|
operator v2d_generic<float>() const { return { static_cast<float>(this->x), static_cast<float>(this->y) }; }
|
|
|
operator v2d_generic<double>() const { return { static_cast<double>(this->x), static_cast<double>(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<class T> inline v2d_generic<T> operator * (const float& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs * (float)rhs.x), (T)(lhs * (float)rhs.y)); }
|
|
|
template<class T> inline v2d_generic<T> operator * (const double& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs * (double)rhs.x), (T)(lhs * (double)rhs.y)); }
|
|
|
template<class T> inline v2d_generic<T> operator * (const int& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs * (int)rhs.x), (T)(lhs * (int)rhs.y)); }
|
|
|
template<class T> inline v2d_generic<T> operator / (const float& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs / (float)rhs.x), (T)(lhs / (float)rhs.y)); }
|
|
|
template<class T> inline v2d_generic<T> operator / (const double& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs / (double)rhs.x), (T)(lhs / (double)rhs.y)); }
|
|
|
template<class T> inline v2d_generic<T> operator / (const int& lhs, const v2d_generic<T>& rhs)
|
|
|
{ return v2d_generic<T>((T)(lhs / (int)rhs.x), (T)(lhs / (int)rhs.y)); }
|
|
|
|
|
|
typedef v2d_generic<int32_t> vi2d;
|
|
|
typedef v2d_generic<uint32_t> vu2d;
|
|
|
typedef v2d_generic<float> vf2d;
|
|
|
typedef v2d_generic<double> vd2d;
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
// 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::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<char> 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<std::string, sResourceFile> mapFiles;
|
|
|
std::ifstream baseFile;
|
|
|
std::vector<char> scramble(const std::vector<char>& data, const std::string& key);
|
|
|
std::string makeposix(const std::string& path);
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
public:
|
|
|
olc::rcode LoadFromFile(const std::string& sImageFile, olc::ResourcePack *pack = nullptr);
|
|
|
olc::rcode LoadFromPGESprFile(const std::string& sImageFile, olc::ResourcePack *pack = nullptr);
|
|
|
olc::rcode SaveToPGESprFile(const std::string& sImageFile);
|
|
|
|
|
|
public:
|
|
|
int32_t width = 0;
|
|
|
int32_t height = 0;
|
|
|
enum Mode { NORMAL, PERIODIC };
|
|
|
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();
|
|
|
Pixel *pColData = nullptr;
|
|
|
Mode modeSample = Mode::NORMAL;
|
|
|
};
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olc::Decal - A GPU resident storage of an olc::Sprite |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
class Decal
|
|
|
{
|
|
|
public:
|
|
|
Decal(olc::Sprite* spr);
|
|
|
virtual ~Decal();
|
|
|
void Update();
|
|
|
|
|
|
public: // But dont touch
|
|
|
int32_t id = -1;
|
|
|
olc::Sprite* sprite = nullptr;
|
|
|
olc::vf2d vUVScale = { 1.0f, 1.0f };
|
|
|
};
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olc::Renderable - Convenience class to keep a sprite and decal together |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
class Renderable
|
|
|
{
|
|
|
public:
|
|
|
Renderable() = default;
|
|
|
olc::rcode Load(const std::string& sFile, ResourcePack* pack = nullptr);
|
|
|
void Create(uint32_t width, uint32_t height);
|
|
|
olc::Decal* Decal() const;
|
|
|
olc::Sprite* Sprite() const;
|
|
|
|
|
|
private:
|
|
|
std::unique_ptr<olc::Sprite> pSprite = nullptr;
|
|
|
std::unique_ptr<olc::Decal> pDecal = nullptr;
|
|
|
};
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | Auxilliary components internal to engine |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
|
|
|
struct DecalInstance
|
|
|
{
|
|
|
olc::Decal* decal = nullptr;
|
|
|
olc::vf2d pos[4] = {{ 0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}};
|
|
|
olc::vf2d uv[4] = {{ 0.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f}};
|
|
|
float w[4] = { 1, 1, 1, 1 };
|
|
|
olc::Pixel tint[4] = { olc::WHITE, olc::WHITE, olc::WHITE, olc::WHITE };;
|
|
|
};
|
|
|
|
|
|
struct DecalTriangleInstance
|
|
|
{
|
|
|
olc::vf2d points[3];
|
|
|
olc::vf2d texture[3];
|
|
|
olc::Pixel colours[3];
|
|
|
olc::Decal* decal = nullptr;
|
|
|
};
|
|
|
|
|
|
struct LayerDesc
|
|
|
{
|
|
|
olc::vf2d vOffset = { 0, 0 };
|
|
|
olc::vf2d vScale = { 1, 1 };
|
|
|
bool bShow = false;
|
|
|
bool bUpdate = false;
|
|
|
olc::Sprite* pDrawTarget = nullptr;
|
|
|
uint32_t nResID = 0;
|
|
|
std::vector<DecalInstance> vecDecalInstance;
|
|
|
olc::Pixel tint = olc::WHITE;
|
|
|
std::function<void()> funcHook = nullptr;
|
|
|
};
|
|
|
|
|
|
class Renderer
|
|
|
{
|
|
|
public:
|
|
|
virtual void PrepareDevice() = 0;
|
|
|
virtual olc::rcode CreateDevice(std::vector<void*> params, bool bFullScreen, bool bVSYNC) = 0;
|
|
|
virtual olc::rcode DestroyDevice() = 0;
|
|
|
virtual void DisplayFrame() = 0;
|
|
|
virtual void PrepareDrawing() = 0;
|
|
|
virtual void DrawLayerQuad(const olc::vf2d& offset, const olc::vf2d& scale, const olc::Pixel tint) = 0;
|
|
|
virtual void DrawDecalQuad(const olc::DecalInstance& decal) = 0;
|
|
|
virtual uint32_t CreateTexture(const uint32_t width, const uint32_t height) = 0;
|
|
|
virtual void UpdateTexture(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 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;
|
|
|
};
|
|
|
|
|
|
static std::unique_ptr<Renderer> renderer;
|
|
|
static std::unique_ptr<Platform> platform;
|
|
|
static std::map<size_t, uint8_t> 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);
|
|
|
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();
|
|
|
|
|
|
public: // Hardware Interfaces
|
|
|
// Returns true if window is currently in focus
|
|
|
bool IsFocused();
|
|
|
// Get the state of a specific keyboard button
|
|
|
HWButton GetKey(Key k);
|
|
|
// Get the state of a specific mouse button
|
|
|
HWButton GetMouse(uint32_t b);
|
|
|
// Get Mouse X coordinate in "pixel" space
|
|
|
int32_t GetMouseX();
|
|
|
// Get Mouse Y coordinate in "pixel" space
|
|
|
int32_t GetMouseY();
|
|
|
// Get Mouse Wheel Delta
|
|
|
int32_t GetMouseWheel();
|
|
|
// Get the ouse in window space
|
|
|
const olc::vi2d& GetWindowMouse() const;
|
|
|
|
|
|
public: // Utility
|
|
|
// Returns the width of the screen in "pixels"
|
|
|
int32_t ScreenWidth();
|
|
|
// Returns the height of the screen in "pixels"
|
|
|
int32_t ScreenHeight();
|
|
|
// Returns the width of the currently selected drawing target in "pixels"
|
|
|
int32_t GetDrawTargetWidth();
|
|
|
// Returns the height of the currently selected drawing target in "pixels"
|
|
|
int32_t GetDrawTargetHeight();
|
|
|
// Returns the currently active draw target
|
|
|
olc::Sprite* GetDrawTarget();
|
|
|
// 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();
|
|
|
// Gets last update of elapsed time
|
|
|
const float GetElapsedTime() const;
|
|
|
// Gets Actual Window size
|
|
|
const olc::vi2d& GetWindowSize() const;
|
|
|
|
|
|
public: // CONFIGURATION ROUTINES
|
|
|
// Layer targeting functions
|
|
|
void SetDrawTarget(uint8_t layer);
|
|
|
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<void()> f);
|
|
|
|
|
|
std::vector<LayerDesc>& 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<olc::Pixel(const int x, const int y, const olc::Pixel& pSource, const olc::Pixel& pDest)> pixelMode);
|
|
|
// Change the blend factor form between 0.0f to 1.0f;
|
|
|
void SetPixelBlend(float fBlend);
|
|
|
// Offset texels by sub-pixel amount (advanced, do not use)
|
|
|
[[deprecated]]
|
|
|
void SetSubPixelOffset(float ox, float oy);
|
|
|
|
|
|
|
|
|
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 well in my defencelocation (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);
|
|
|
|
|
|
// Decal Quad functions
|
|
|
|
|
|
// 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);
|
|
|
// 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<olc::vf2d, 4>& 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<olc::vf2d, 4>& 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 });
|
|
|
// 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 a single line of text
|
|
|
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);
|
|
|
// Clears entire draw target to Pixel
|
|
|
void Clear(Pixel p);
|
|
|
// Clears the rendering back buffer
|
|
|
void ClearBuffer(Pixel p, bool bDepth = true);
|
|
|
|
|
|
|
|
|
public: // Branding
|
|
|
std::string sAppName;
|
|
|
|
|
|
private: // Inner mysterious workings
|
|
|
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 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;
|
|
|
Sprite* fontSprite = nullptr;
|
|
|
Decal* fontDecal = nullptr;
|
|
|
Sprite* pDefaultDrawTarget = nullptr;
|
|
|
std::vector<LayerDesc> vLayers;
|
|
|
uint8_t nTargetLayer = 0;
|
|
|
uint32_t nLastFPS = 0;
|
|
|
std::function<olc::Pixel(const int x, const int y, const olc::Pixel&, const olc::Pixel&)> funcPixelMode;
|
|
|
std::chrono::time_point<std::chrono::system_clock> m_tp1, m_tp2;
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
// At the very end of this file, chooses which
|
|
|
// components to compile
|
|
|
void olc_ConfigureSystem();
|
|
|
|
|
|
// If anything sets this flag to false, the engine
|
|
|
// "should" shut down gracefully
|
|
|
static std::atomic<bool> 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();
|
|
|
|
|
|
// 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 };
|
|
|
|
|
|
friend class PGEX;
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | PGE EXTENSION BASE CLASS - Permits access to PGE functions from extension |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
class PGEX
|
|
|
{
|
|
|
friend class olc::PixelGameEngine;
|
|
|
protected:
|
|
|
static PixelGameEngine* pge;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
#endif // OLC_PGE_DEF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
Object Oriented Mode
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
If the olcPixelGameEngine.h is called from several sources it can cause
|
|
|
multiple definitions of objects. To prevent this, ONLY ONE of the pathways
|
|
|
to including this file must have OLC_PGE_APPLICATION defined before it. This prevents
|
|
|
the definitions being duplicated.
|
|
|
|
|
|
If all else fails, create a file called "olcPixelGameEngine.cpp" with the following
|
|
|
two lines. Then you can just #include "olcPixelGameEngine.h" as normal without worrying
|
|
|
about defining things. Dont forget to include that cpp file as part of your build!
|
|
|
|
|
|
#define OLC_PGE_APPLICATION
|
|
|
#include "olcPixelGameEngine.h"
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
// 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
|
|
|
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 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));
|
|
|
}
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olc::Sprite IMPLEMENTATION |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
Sprite::Sprite()
|
|
|
{ pColData = nullptr; width = 0; height = 0; }
|
|
|
|
|
|
Sprite::Sprite(const std::string& sImageFile, olc::ResourcePack *pack)
|
|
|
{ LoadFromFile(sImageFile, pack); }
|
|
|
|
|
|
Sprite::Sprite(int32_t w, int32_t h)
|
|
|
{
|
|
|
if(pColData) delete[] pColData;
|
|
|
width = w; height = h;
|
|
|
pColData = new Pixel[width * height];
|
|
|
for (int32_t i = 0; i < width*height; i++)
|
|
|
pColData[i] = Pixel();
|
|
|
}
|
|
|
|
|
|
Sprite::~Sprite()
|
|
|
{ if (pColData) delete[] pColData; }
|
|
|
|
|
|
|
|
|
olc::rcode Sprite::LoadFromPGESprFile(const std::string& sImageFile, olc::ResourcePack *pack)
|
|
|
{
|
|
|
if (pColData) delete[] pColData;
|
|
|
auto ReadData = [&](std::istream &is)
|
|
|
{
|
|
|
is.read((char*)&width, sizeof(int32_t));
|
|
|
is.read((char*)&height, sizeof(int32_t));
|
|
|
pColData = new Pixel[width * height];
|
|
|
is.read((char*)pColData, (size_t)width * (size_t)height * sizeof(uint32_t));
|
|
|
};
|
|
|
|
|
|
// These are essentially Memory Surfaces represented by olc::Sprite
|
|
|
// which load very fast, but are completely uncompressed
|
|
|
if (pack == nullptr)
|
|
|
{
|
|
|
std::ifstream ifs;
|
|
|
ifs.open(sImageFile, std::ifstream::binary);
|
|
|
if (ifs.is_open())
|
|
|
{
|
|
|
ReadData(ifs);
|
|
|
return olc::OK;
|
|
|
}
|
|
|
else
|
|
|
return olc::FAIL;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
ResourceBuffer rb = pack->GetFileBuffer(sImageFile);
|
|
|
std::istream is(&rb);
|
|
|
ReadData(is);
|
|
|
return olc::OK;
|
|
|
}
|
|
|
return olc::FAIL;
|
|
|
}
|
|
|
|
|
|
olc::rcode Sprite::SaveToPGESprFile(const std::string& sImageFile)
|
|
|
{
|
|
|
if (pColData == nullptr) return olc::FAIL;
|
|
|
|
|
|
std::ofstream ofs;
|
|
|
ofs.open(sImageFile, std::ifstream::binary);
|
|
|
if (ofs.is_open())
|
|
|
{
|
|
|
ofs.write((char*)&width, sizeof(int32_t));
|
|
|
ofs.write((char*)&height, sizeof(int32_t));
|
|
|
ofs.write((char*)pColData, (size_t)width * (size_t)height * sizeof(uint32_t));
|
|
|
ofs.close();
|
|
|
return olc::OK;
|
|
|
}
|
|
|
|
|
|
return olc::FAIL;
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
|
|
return pColData[abs(y%height)*width + abs(x%width)];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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; }
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olc::Decal IMPLEMENTATION |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
Decal::Decal(olc::Sprite* spr)
|
|
|
{
|
|
|
id = -1;
|
|
|
if (spr == nullptr) return;
|
|
|
sprite = spr;
|
|
|
id = renderer->CreateTexture(sprite->width, sprite->height);
|
|
|
Update();
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
Decal::~Decal()
|
|
|
{
|
|
|
if (id != -1)
|
|
|
{
|
|
|
renderer->DeleteTexture(id);
|
|
|
id = -1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void Renderable::Create(uint32_t width, uint32_t height)
|
|
|
{
|
|
|
pSprite = std::make_unique<olc::Sprite>(width, height);
|
|
|
pDecal = std::make_unique<olc::Decal>(pSprite.get());
|
|
|
}
|
|
|
|
|
|
olc::rcode Renderable::Load(const std::string& sFile, ResourcePack *pack)
|
|
|
{
|
|
|
pSprite = std::make_unique<olc::Sprite>();
|
|
|
if (pSprite->LoadFromFile(sFile, pack))
|
|
|
{
|
|
|
pDecal = std::make_unique<olc::Decal>(pSprite.get());
|
|
|
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<char> buffer(nIndexSize);
|
|
|
for (uint32_t j = 0; j < nIndexSize; j++)
|
|
|
buffer[j] = baseFile.get();
|
|
|
|
|
|
std::vector<char> 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<uint8_t> 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<char> 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<char> 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<char> ResourcePack::scramble(const std::vector<char>& data, const std::string& key)
|
|
|
{
|
|
|
if (key.empty()) return data;
|
|
|
std::vector<char> 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)
|
|
|
{
|
|
|
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 };
|
|
|
for (auto& layer : vLayers)
|
|
|
{
|
|
|
delete layer.pDrawTarget; // Erase existing layer sprites
|
|
|
layer.pDrawTarget = new Sprite(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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::SetDrawTarget(uint8_t layer)
|
|
|
{
|
|
|
if (layer < vLayers.size())
|
|
|
{
|
|
|
pDrawTarget = vLayers[layer].pDrawTarget;
|
|
|
vLayers[layer].bUpdate = true;
|
|
|
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<void()> f)
|
|
|
{ if (layer < vLayers.size()) vLayers[layer].funcHook = f; }
|
|
|
|
|
|
std::vector<LayerDesc>& PixelGameEngine::GetLayers()
|
|
|
{ return vLayers; }
|
|
|
|
|
|
uint32_t PixelGameEngine::CreateLayer()
|
|
|
{
|
|
|
LayerDesc ld;
|
|
|
ld.pDrawTarget = new olc::Sprite(vScreenSize.x, vScreenSize.y);
|
|
|
ld.nResID = renderer->CreateTexture(vScreenSize.x, vScreenSize.y);
|
|
|
renderer->UpdateTexture(ld.nResID, ld.pDrawTarget);
|
|
|
vLayers.push_back(ld);
|
|
|
return uint32_t(vLayers.size()) - 1;
|
|
|
}
|
|
|
|
|
|
Sprite* PixelGameEngine::GetDrawTarget()
|
|
|
{ return pDrawTarget; }
|
|
|
|
|
|
int32_t PixelGameEngine::GetDrawTargetWidth()
|
|
|
{
|
|
|
if (pDrawTarget)
|
|
|
return pDrawTarget->width;
|
|
|
else
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
int32_t PixelGameEngine::GetDrawTargetHeight()
|
|
|
{
|
|
|
if (pDrawTarget)
|
|
|
return pDrawTarget->height;
|
|
|
else
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
uint32_t PixelGameEngine::GetFPS()
|
|
|
{ return nLastFPS; }
|
|
|
|
|
|
bool PixelGameEngine::IsFocused()
|
|
|
{ return bHasInputFocus; }
|
|
|
|
|
|
HWButton PixelGameEngine::GetKey(Key k)
|
|
|
{ return pKeyboardState[k]; }
|
|
|
|
|
|
HWButton PixelGameEngine::GetMouse(uint32_t b)
|
|
|
{ return pMouseState[b]; }
|
|
|
|
|
|
int32_t PixelGameEngine::GetMouseX()
|
|
|
{ return vMousePos.x; }
|
|
|
|
|
|
int32_t PixelGameEngine::GetMouseY()
|
|
|
{ return vMousePos.y; }
|
|
|
|
|
|
int32_t PixelGameEngine::GetMouseWheel()
|
|
|
{ return nMouseWheelDelta; }
|
|
|
|
|
|
int32_t PixelGameEngine::ScreenWidth()
|
|
|
{ return vScreenSize.x; }
|
|
|
|
|
|
int32_t PixelGameEngine::ScreenHeight()
|
|
|
{ return vScreenSize.y; }
|
|
|
|
|
|
const float PixelGameEngine::GetElapsedTime() const
|
|
|
{ return fLastElapsed; }
|
|
|
|
|
|
const olc::vi2d& PixelGameEngine::GetWindowSize() const
|
|
|
{ return vWindowSize; }
|
|
|
|
|
|
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::SetSubPixelOffset(float ox, float oy)
|
|
|
{
|
|
|
//vSubPixelOffset.x = ox * vPixel.x;
|
|
|
//vSubPixelOffset.y = oy * vPixel.y;
|
|
|
}
|
|
|
|
|
|
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; };
|
|
|
|
|
|
// 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);
|
|
|
}
|
|
|
|
|
|
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::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) * -1.0f
|
|
|
};
|
|
|
|
|
|
olc::vf2d vScreenSpaceDim =
|
|
|
{
|
|
|
vScreenSpacePos.x + (2.0f * source_size.x * vInvScreenSize.x) * scale.x,
|
|
|
vScreenSpacePos.y - (2.0f * source_size.y * vInvScreenSize.y) * scale.y
|
|
|
};
|
|
|
|
|
|
DecalInstance di; di.decal = decal; di.tint[0] = tint;
|
|
|
|
|
|
di.pos[0] = { vScreenSpacePos.x, vScreenSpacePos.y };
|
|
|
di.pos[1] = { vScreenSpacePos.x, vScreenSpaceDim.y };
|
|
|
di.pos[2] = { vScreenSpaceDim.x, vScreenSpaceDim.y };
|
|
|
di.pos[3] = { vScreenSpaceDim.x, vScreenSpacePos.y };
|
|
|
|
|
|
olc::vf2d uvtl = source_pos * decal->vUVScale;
|
|
|
olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale);
|
|
|
di.uv[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y };
|
|
|
di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y };
|
|
|
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.decal = decal; di.tint[0] = tint;
|
|
|
|
|
|
di.pos[0] = { vScreenSpacePos.x, vScreenSpacePos.y };
|
|
|
di.pos[1] = { vScreenSpacePos.x, vScreenSpaceDim.y };
|
|
|
di.pos[2] = { vScreenSpaceDim.x, vScreenSpaceDim.y };
|
|
|
di.pos[3] = { vScreenSpaceDim.x, vScreenSpacePos.y };
|
|
|
|
|
|
olc::vf2d uvtl = source_pos * decal->vUVScale;
|
|
|
olc::vf2d uvbr = uvtl + (source_size * decal->vUVScale);
|
|
|
di.uv[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y };
|
|
|
di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y };
|
|
|
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.tint[0] = tint;
|
|
|
di.pos[0] = { vScreenSpacePos.x, vScreenSpacePos.y };
|
|
|
di.pos[1] = { vScreenSpacePos.x, vScreenSpaceDim.y };
|
|
|
di.pos[2] = { vScreenSpaceDim.x, vScreenSpaceDim.y };
|
|
|
di.pos[3] = { vScreenSpaceDim.x, vScreenSpacePos.y };
|
|
|
vLayers[nTargetLayer].vecDecalInstance.push_back(di);
|
|
|
}
|
|
|
|
|
|
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.tint[0] = tint;
|
|
|
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;
|
|
|
}
|
|
|
vLayers[nTargetLayer].vecDecalInstance.push_back(di);
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::DrawExplicitDecal(olc::Decal* decal, const olc::vf2d* pos, const olc::vf2d* uv, const olc::Pixel* col)
|
|
|
{
|
|
|
DecalInstance di;
|
|
|
for (int i = 0; i < 4; 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];
|
|
|
}
|
|
|
vLayers[nTargetLayer].vecDecalInstance.push_back(di);
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::FillRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col)
|
|
|
{
|
|
|
std::array<olc::vf2d, 4> points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } };
|
|
|
std::array<olc::vf2d, 4> uvs = { {{0,0},{0,0},{0,0},{0,0}} };
|
|
|
std::array<olc::Pixel, 4> cols = { {col, col, col, col} };
|
|
|
DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data());
|
|
|
}
|
|
|
|
|
|
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<olc::vf2d, 4> points = { { {pos}, {pos.x, pos.y + size.y}, {pos + size}, {pos.x + size.x, pos.y} } };
|
|
|
std::array<olc::vf2d, 4> uvs = { {{0,0},{0,0},{0,0},{0,0}} };
|
|
|
std::array<olc::Pixel, 4> cols = { {colTL, colBL, colBR, colTR} };
|
|
|
DrawExplicitDecal(nullptr, points.data(), uvs.data(), cols.data());
|
|
|
}
|
|
|
|
|
|
|
|
|
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.tint[0] = tint;
|
|
|
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[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y };
|
|
|
di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { uvbr.x, uvtl.y };
|
|
|
|
|
|
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.decal = decal;
|
|
|
di.tint[0] = tint;
|
|
|
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[0] = { uvtl.x, uvtl.y }; di.uv[1] = { uvtl.x, uvbr.y };
|
|
|
di.uv[2] = { uvbr.x, uvbr.y }; di.uv[3] = { 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 };
|
|
|
}
|
|
|
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.decal = decal;
|
|
|
di.tint[0] = tint;
|
|
|
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 };
|
|
|
}
|
|
|
vLayers[nTargetLayer].vecDecalInstance.push_back(di);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::DrawWarpedDecal(olc::Decal* decal, const std::array<olc::vf2d, 4>& 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<olc::vf2d, 4>& 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
|
|
|
{
|
|
|
int32_t ox = (c - 32) % 16;
|
|
|
int32_t oy = (c - 32) / 16;
|
|
|
DrawPartialDecal(pos + spos, fontDecal, { float(ox) * 8.0f, float(oy) * 8.0f }, { 8.0f, 8.0f }, scale, col);
|
|
|
spos.x += 8.0f * scale.x;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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 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(col.a != 255) SetPixelMode(Pixel::ALPHA);
|
|
|
else SetPixelMode(Pixel::MASK);
|
|
|
for (auto c : sText)
|
|
|
{
|
|
|
if (c == '\n')
|
|
|
{
|
|
|
sx = 0; sy += 8 * 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 (fontSprite->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 (fontSprite->GetPixel(i + ox * 8, j + oy * 8).r > 0)
|
|
|
Draw(x + sx + i, y + sy + j, col);
|
|
|
}
|
|
|
sx += 8 * scale;
|
|
|
}
|
|
|
}
|
|
|
SetPixelMode(m);
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::SetPixelMode(Pixel::Mode m)
|
|
|
{ nPixelMode = m; }
|
|
|
|
|
|
Pixel::Mode PixelGameEngine::GetPixelMode()
|
|
|
{ return nPixelMode; }
|
|
|
|
|
|
void PixelGameEngine::SetPixelMode(std::function<olc::Pixel(const int x, const int y, const olc::Pixel&, const olc::Pixel&)> 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;
|
|
|
}
|
|
|
|
|
|
// 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::olc_UpdateViewport()
|
|
|
{
|
|
|
int32_t ww = vScreenSize.x * vPixelSize.x;
|
|
|
int32_t wh = vScreenSize.y * vPixelSize.y;
|
|
|
float wasp = (float)ww / (float)wh;
|
|
|
|
|
|
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_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
|
|
|
if (!OnUserCreate()) bAtomActive = false;
|
|
|
|
|
|
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<float> elapsedTime = m_tp2 - m_tp1;
|
|
|
m_tp1 = m_tp2;
|
|
|
|
|
|
// Our time per frame coefficient
|
|
|
float fElapsedTime = elapsedTime.count();
|
|
|
fLastElapsed = fElapsedTime;
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
renderer->ClearBuffer(olc::BLACK, true);
|
|
|
|
|
|
// Handle Frame Update
|
|
|
if (!OnUserUpdate(fElapsedTime))
|
|
|
bAtomActive = false;
|
|
|
|
|
|
// Display Frame
|
|
|
renderer->UpdateViewport(vViewPos, vViewSize);
|
|
|
renderer->ClearBuffer(olc::BLACK, true);
|
|
|
|
|
|
// Layer 0 must always exist
|
|
|
vLayers[0].bUpdate = true;
|
|
|
vLayers[0].bShow = true;
|
|
|
renderer->PrepareDrawing();
|
|
|
|
|
|
for (auto layer = vLayers.rbegin(); layer != vLayers.rend(); ++layer)
|
|
|
{
|
|
|
if (layer->bShow)
|
|
|
{
|
|
|
if (layer->funcHook == nullptr)
|
|
|
{
|
|
|
renderer->ApplyTexture(layer->nResID);
|
|
|
if (layer->bUpdate)
|
|
|
{
|
|
|
renderer->UpdateTexture(layer->nResID, layer->pDrawTarget);
|
|
|
layer->bUpdate = false;
|
|
|
}
|
|
|
|
|
|
renderer->DrawLayerQuad(layer->vOffset, layer->vScale, layer->tint);
|
|
|
|
|
|
// Display Decals in order for this layer
|
|
|
for (auto& decal : layer->vecDecalInstance)
|
|
|
renderer->DrawDecalQuad(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 + " - FPS: " + std::to_string(nFrameCount);
|
|
|
platform->SetWindowTitle(sTitle);
|
|
|
nFrameCount = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void PixelGameEngine::olc_ConstructFontSheet()
|
|
|
{
|
|
|
std::string data;
|
|
|
data += "?Q`0001oOch0o01o@F40o0<AGD4090LAGD<090@A7ch0?00O7Q`0600>00000000";
|
|
|
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`<O`000GP800000000";
|
|
|
data += "?P9PL020O`<`N3R0@E4HC7b0@ET<ATB0@@l6C4B0O`H3N7b0?P01L3R000000020";
|
|
|
|
|
|
fontSprite = new olc::Sprite(128, 48);
|
|
|
int px = 0, py = 0;
|
|
|
for (size_t b = 0; b < 1024; b += 4)
|
|
|
{
|
|
|
uint32_t sym1 = (uint32_t)data[b + 0] - 48;
|
|
|
uint32_t sym2 = (uint32_t)data[b + 1] - 48;
|
|
|
uint32_t sym3 = (uint32_t)data[b + 2] - 48;
|
|
|
uint32_t sym4 = (uint32_t)data[b + 3] - 48;
|
|
|
uint32_t r = sym1 << 18 | sym2 << 12 | sym3 << 6 | sym4;
|
|
|
|
|
|
for (int i = 0; i < 24; i++)
|
|
|
{
|
|
|
int k = r & (1 << i) ? 255 : 0;
|
|
|
fontSprite->SetPixel(px, py, olc::Pixel(k, k, k, k));
|
|
|
if (++py == 48) { px++; py = 0; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fontDecal = new olc::Decal(fontSprite);
|
|
|
}
|
|
|
|
|
|
// Need a couple of statics as these are singleton instances
|
|
|
// read from multiple locations
|
|
|
std::atomic<bool> PixelGameEngine::bAtomActive{ false };
|
|
|
olc::PixelGameEngine* olc::PGEX::pge = nullptr;
|
|
|
olc::PixelGameEngine* olc::Platform::ptrPGE = nullptr;
|
|
|
olc::PixelGameEngine* olc::Renderer::ptrPGE = nullptr;
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | olcPixelGameEngine PLATFORM SPECIFIC IMPLEMENTATIONS |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | START RENDERER: OpenGL 1.0 (the original, the best...) |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#if defined(OLC_GFX_OPENGL10)
|
|
|
#if defined(_WIN32)
|
|
|
#include <windows.h>
|
|
|
#include <GL/gl.h>
|
|
|
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 <GL/gl.h>
|
|
|
namespace X11
|
|
|
{
|
|
|
#include <GL/glx.h>
|
|
|
#include <X11/X.h>
|
|
|
#include <X11/Xlib.h>
|
|
|
}
|
|
|
#include <png.h>
|
|
|
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
|
|
|
|
|
|
namespace olc
|
|
|
{
|
|
|
class Renderer_OGL10 : public olc::Renderer
|
|
|
{
|
|
|
private:
|
|
|
glDeviceContext_t glDeviceContext = 0;
|
|
|
glRenderContext_t glRenderContext = 0;
|
|
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
X11::Display* olc_Display = nullptr;
|
|
|
X11::Window* olc_Window = nullptr;
|
|
|
X11::XVisualInfo* olc_VisualInfo = nullptr;
|
|
|
#endif
|
|
|
|
|
|
public:
|
|
|
void PrepareDevice() override
|
|
|
{ }
|
|
|
|
|
|
olc::rcode CreateDevice(std::vector<void*> params, bool bFullScreen, bool bVSYNC) override
|
|
|
{
|
|
|
#if defined(_WIN32)
|
|
|
// 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);
|
|
|
#endif
|
|
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
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
|
|
|
|
|
|
glEnable(GL_TEXTURE_2D); // Turn on texturing
|
|
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
|
|
|
return olc::rcode::OK;
|
|
|
}
|
|
|
|
|
|
olc::rcode DestroyDevice() override
|
|
|
{
|
|
|
#if defined(_WIN32)
|
|
|
wglDeleteContext(glRenderContext);
|
|
|
#endif
|
|
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
glXMakeCurrent(olc_Display, None, NULL);
|
|
|
glXDestroyContext(olc_Display, glDeviceContext);
|
|
|
#endif
|
|
|
return olc::rcode::OK;
|
|
|
}
|
|
|
|
|
|
void DisplayFrame() override
|
|
|
{
|
|
|
#if defined(_WIN32)
|
|
|
SwapBuffers(glDeviceContext);
|
|
|
#endif
|
|
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
X11::glXSwapBuffers(olc_Display, *olc_Window);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
void PrepareDrawing() override
|
|
|
{
|
|
|
glEnable(GL_BLEND);
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
}
|
|
|
|
|
|
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 DrawDecalQuad(const olc::DecalInstance& decal) override
|
|
|
{
|
|
|
if (decal.decal == nullptr)
|
|
|
{
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
glBegin(GL_QUADS);
|
|
|
glColor4ub(decal.tint[0].r, decal.tint[0].g, decal.tint[0].b, decal.tint[0].a);
|
|
|
glTexCoord4f(decal.uv[0].x, decal.uv[0].y, 0.0f, decal.w[0]); glVertex2f(decal.pos[0].x, decal.pos[0].y);
|
|
|
glColor4ub(decal.tint[1].r, decal.tint[1].g, decal.tint[1].b, decal.tint[1].a);
|
|
|
glTexCoord4f(decal.uv[1].x, decal.uv[1].y, 0.0f, decal.w[1]); glVertex2f(decal.pos[1].x, decal.pos[1].y);
|
|
|
glColor4ub(decal.tint[2].r, decal.tint[2].g, decal.tint[2].b, decal.tint[2].a);
|
|
|
glTexCoord4f(decal.uv[2].x, decal.uv[2].y, 0.0f, decal.w[2]); glVertex2f(decal.pos[2].x, decal.pos[2].y);
|
|
|
glColor4ub(decal.tint[3].r, decal.tint[3].g, decal.tint[3].b, decal.tint[3].a);
|
|
|
glTexCoord4f(decal.uv[3].x, decal.uv[3].y, 0.0f, decal.w[3]); glVertex2f(decal.pos[3].x, decal.pos[3].y);
|
|
|
glEnd();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
glBindTexture(GL_TEXTURE_2D, decal.decal->id);
|
|
|
glBegin(GL_QUADS);
|
|
|
glColor4ub(decal.tint[0].r, decal.tint[0].g, decal.tint[0].b, decal.tint[0].a);
|
|
|
glTexCoord4f(decal.uv[0].x, decal.uv[0].y, 0.0f, decal.w[0]); glVertex2f(decal.pos[0].x, decal.pos[0].y);
|
|
|
glTexCoord4f(decal.uv[1].x, decal.uv[1].y, 0.0f, decal.w[1]); glVertex2f(decal.pos[1].x, decal.pos[1].y);
|
|
|
glTexCoord4f(decal.uv[2].x, decal.uv[2].y, 0.0f, decal.w[2]); glVertex2f(decal.pos[2].x, decal.pos[2].y);
|
|
|
glTexCoord4f(decal.uv[3].x, decal.uv[3].y, 0.0f, decal.w[3]); glVertex2f(decal.pos[3].x, decal.pos[3].y);
|
|
|
glEnd();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
uint32_t CreateTexture(const uint32_t width, const uint32_t height) override
|
|
|
{
|
|
|
uint32_t id = 0;
|
|
|
glGenTextures(1, &id);
|
|
|
glBindTexture(GL_TEXTURE_2D, id);
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
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
|
|
|
{
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, spr->width, spr->height, 0, 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
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | START PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#if defined(_WIN32)
|
|
|
#if !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
|
|
|
#pragma comment(lib, "gdiplus.lib")
|
|
|
#pragma comment(lib, "Shlwapi.lib")
|
|
|
#else
|
|
|
// 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
|
|
|
#endif
|
|
|
|
|
|
// Include WinAPI
|
|
|
#if !defined(NOMINMAX)
|
|
|
#define NOMINMAX
|
|
|
#endif
|
|
|
#define VC_EXTRALEAN
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
#include <windows.h>
|
|
|
#include <gdiplus.h>
|
|
|
#include <Shlwapi.h>
|
|
|
|
|
|
namespace olc
|
|
|
{
|
|
|
// Little utility function to convert from char to wchar in Windows environments
|
|
|
// depending upon how the compiler is configured. This should not be necessary
|
|
|
// on linux platforms
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
// 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;
|
|
|
ULONG_PTR token;
|
|
|
Gdiplus::GdiplusStartup(&token, &startupInput, NULL);
|
|
|
};
|
|
|
} gdistartup;
|
|
|
|
|
|
|
|
|
class Platform_Windows : public olc::Platform
|
|
|
{
|
|
|
private:
|
|
|
HWND olc_hWnd = nullptr;
|
|
|
std::wstring wsAppName;
|
|
|
|
|
|
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;
|
|
|
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_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); return 0;
|
|
|
}
|
|
|
return DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// On Windows load images using GDI+ library
|
|
|
olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack *pack)
|
|
|
{
|
|
|
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
|
|
|
{
|
|
|
// Load sprite from file
|
|
|
bmp = Gdiplus::Bitmap::FromFile(ConvertS2W(sImageFile).c_str());
|
|
|
}
|
|
|
|
|
|
if (bmp->GetLastStatus() != Gdiplus::Ok) return olc::NO_FILE;
|
|
|
width = bmp->GetWidth();
|
|
|
height = bmp->GetHeight();
|
|
|
pColData = new Pixel[width * height];
|
|
|
|
|
|
for (int y = 0; y < height; y++)
|
|
|
for (int x = 0; x < width; x++)
|
|
|
{
|
|
|
Gdiplus::Color c;
|
|
|
bmp->GetPixel(x, y, &c);
|
|
|
SetPixel(x, y, olc::Pixel(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()));
|
|
|
}
|
|
|
delete bmp;
|
|
|
return olc::OK;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | END PLATFORM: MICROSOFT WINDOWS XP, VISTA, 7, 8, 10 |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | START PLATFORM: LINUX |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
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
|
|
|
{ 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;
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
olc::rcode Sprite::LoadFromFile(const std::string& sImageFile, olc::ResourcePack* pack)
|
|
|
{
|
|
|
UNUSED(pack);
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
// 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;
|
|
|
width = png_get_image_width(png, info);
|
|
|
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) * height);
|
|
|
for (int y = 0; y < height; y++) {
|
|
|
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
|
|
|
}
|
|
|
png_read_image(png, row_pointers);
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
// Create sprite array
|
|
|
pColData = new Pixel[width * height];
|
|
|
// Iterate through image rows, converting into sprite format
|
|
|
for (int y = 0; y < height; y++)
|
|
|
{
|
|
|
png_bytep row = row_pointers[y];
|
|
|
for (int x = 0; x < width; x++)
|
|
|
{
|
|
|
png_bytep px = &(row[x * 4]);
|
|
|
SetPixel(x, y, Pixel(px[0], px[1], px[2], px[3]));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (int y = 0; y < 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::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::OK;
|
|
|
|
|
|
fail_load:
|
|
|
width = 0;
|
|
|
height = 0;
|
|
|
pColData = nullptr;
|
|
|
return olc::FAIL;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | END PLATFORM: LINUX |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
|
|
|
namespace olc
|
|
|
{
|
|
|
void PixelGameEngine::olc_ConfigureSystem()
|
|
|
{
|
|
|
#if defined(_WIN32)
|
|
|
platform = std::make_unique<olc::Platform_Windows>();
|
|
|
#endif
|
|
|
|
|
|
#if defined(__linux__) || defined(__FreeBSD__)
|
|
|
platform = std::make_unique<olc::Platform_Linux>();
|
|
|
#endif
|
|
|
|
|
|
#if defined(OLC_GFX_OPENGL10)
|
|
|
renderer = std::make_unique<olc::Renderer_OGL10>();
|
|
|
#endif
|
|
|
|
|
|
#if defined(OLC_GFX_OPENGL33)
|
|
|
renderer = std::make_unique<olc::Renderer_OGL33>();
|
|
|
#endif
|
|
|
|
|
|
#if defined(OLC_GFX_DIRECTX10)
|
|
|
renderer = std::make_unique<olc::Renderer_DX10>();
|
|
|
#endif
|
|
|
|
|
|
//// Associate components with PGE instance
|
|
|
platform->ptrPGE = this;
|
|
|
renderer->ptrPGE = this;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endif // End olc namespace
|
|
|
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
// | END OF OLC_PGE_APPLICATION |
|
|
|
// O------------------------------------------------------------------------------O
|
|
|
|
|
|
|