aboutsummaryrefslogtreecommitdiffstats
path: root/yage
diff options
context:
space:
mode:
authorYann Herklotz <ymherklotz@gmail.com>2017-09-09 07:55:22 +0100
committerYann Herklotz <ymherklotz@gmail.com>2017-09-09 07:55:22 +0100
commit60072c1d8089ffd3294e76636198d14710be95b8 (patch)
tree511d459e9afe69ca58d05880eb53ce44a9a183c6 /yage
parent660996bd750dbb5fcdce85845ee6b260f3ed23eb (diff)
downloadYAGE-60072c1d8089ffd3294e76636198d14710be95b8.tar.gz
YAGE-60072c1d8089ffd3294e76636198d14710be95b8.zip
Restructuring
Diffstat (limited to 'yage')
-rw-r--r--yage/CMakeLists.txt33
-rw-r--r--yage/base/CMakeLists.txt13
-rw-r--r--yage/base/camera2d.cpp45
-rw-r--r--yage/base/camera2d.h38
-rw-r--r--yage/base/glslprogram.cpp162
-rw-r--r--yage/base/glslprogram.h52
-rw-r--r--yage/base/imageloader.cpp60
-rw-r--r--yage/base/imageloader.h27
-rw-r--r--yage/base/inputmanager.cpp33
-rw-r--r--yage/base/inputmanager.h28
-rw-r--r--yage/base/iomanager.cpp42
-rw-r--r--yage/base/iomanager.h27
-rw-r--r--yage/base/picopng.cpp1118
-rw-r--r--yage/base/picopng.h20
-rw-r--r--yage/base/resourcemanager.cpp21
-rw-r--r--yage/base/resourcemanager.h31
-rw-r--r--yage/base/sprite.cpp97
-rw-r--r--yage/base/sprite.h52
-rw-r--r--yage/base/spritebatch.cpp193
-rw-r--r--yage/base/spritebatch.h105
-rw-r--r--yage/base/spritesheet.h25
-rw-r--r--yage/base/texture.h25
-rw-r--r--yage/base/texturecache.cpp30
-rw-r--r--yage/base/texturecache.h32
-rw-r--r--yage/base/vertex.h84
-rw-r--r--yage/base/window.cpp97
-rw-r--r--yage/base/window.h55
-rw-r--r--yage/math/CMakeLists.txt1
-rw-r--r--yage/math/math.h14
-rw-r--r--yage/math/matrix.h424
-rw-r--r--yage/physics/CMakeLists.txt6
-rw-r--r--yage/physics/README.org27
-rw-r--r--yage/physics/body.cpp34
-rw-r--r--yage/physics/body.h56
-rw-r--r--yage/physics/collider.h43
-rw-r--r--yage/physics/collisionbody.h28
-rw-r--r--yage/physics/particlebody.cpp54
-rw-r--r--yage/physics/particlebody.h33
-rw-r--r--yage/physics/physics.h19
-rw-r--r--yage/physics/rectanglecollider.cpp36
-rw-r--r--yage/physics/rectanglecollider.h30
-rw-r--r--yage/physics/rigidbody.cpp20
-rw-r--r--yage/physics/rigidbody.h28
-rw-r--r--yage/yage.h63
44 files changed, 3461 insertions, 0 deletions
diff --git a/yage/CMakeLists.txt b/yage/CMakeLists.txt
new file mode 100644
index 00000000..d45ffa90
--- /dev/null
+++ b/yage/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_policy(SET CMP0048 NEW)
+
+project(yage
+ VERSION 0.1.1.0
+ LANGUAGES CXX
+ )
+
+include(base/CMakeLists.txt)
+include(physics/CMakeLists.txt)
+include(math/CMakeLists.txt)
+
+set(YAGE_SOURCES
+ ${YAGE_BASE_SOURCES}
+ ${YAGE_PHYSICS_SOURCES}
+ ${YAGE_MATH_SOURCES}
+ )
+
+set(${PROJECT_NAME}_DIR
+ ${PROJECT_SOURCE_DIR})
+
+add_library(${PROJECT_NAME}
+ ${YAGE_SOURCES}
+ )
+
+target_include_directories(${PROJECT_NAME}
+ PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+
+target_link_libraries(${PROJECT_NAME}
+ ${OPENGL_LIBRARIES}
+ ${GLEW_LIBRARIES}
+ ${SDL2_LIBRARIES}
+ )
diff --git a/yage/base/CMakeLists.txt b/yage/base/CMakeLists.txt
new file mode 100644
index 00000000..acdb78bf
--- /dev/null
+++ b/yage/base/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(YAGE_BASE_SOURCES
+ base/imageloader.cpp
+ base/window.cpp
+ base/texturecache.cpp
+ base/glslprogram.cpp
+ base/spritebatch.cpp
+ base/resourcemanager.cpp
+ base/sprite.cpp
+ base/inputmanager.cpp
+ base/picopng.cpp
+ base/camera2d.cpp
+ base/iomanager.cpp
+ )
diff --git a/yage/base/camera2d.cpp b/yage/base/camera2d.cpp
new file mode 100644
index 00000000..292f998a
--- /dev/null
+++ b/yage/base/camera2d.cpp
@@ -0,0 +1,45 @@
+/* ----------------------------------------------------------------------------
+ * camera2d.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/camera2d.h>
+
+#include <GL/glew.h>
+
+namespace yage
+{
+
+Camera2D::Camera2D(int screen_width, int screen_height)
+ : position_(0.f, 0.f), camera_matrix_(1.f),
+ ortho_matrix_(
+ glm::ortho(0.f, (float)screen_width, 0.f, (float)screen_height))
+{
+}
+
+void Camera2D::update(GlslProgram &program)
+{
+ if (update_matrix_) {
+ glm::vec3 translate(-position_.x, -position_.y, 0.f);
+ glm::vec3 scale(scale_, scale_, 0.f);
+
+ camera_matrix_ = glm::translate(ortho_matrix_, translate);
+ camera_matrix_ = glm::scale(glm::mat4(1.f), scale) * camera_matrix_;
+
+ update_matrix_ = false;
+ }
+
+ GLint matrix_location = program.getUniformLocation("P");
+ glUniformMatrix4fv(matrix_location, 1, GL_FALSE, &(camera_matrix_[0][0]));
+}
+
+void Camera2D::move(const glm::vec2 &direction)
+{
+ position_ += direction;
+ update_matrix_ = true;
+}
+
+} // namespace yage
diff --git a/yage/base/camera2d.h b/yage/base/camera2d.h
new file mode 100644
index 00000000..a60893ac
--- /dev/null
+++ b/yage/base/camera2d.h
@@ -0,0 +1,38 @@
+/* ----------------------------------------------------------------------------
+ * camera2d.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com>
+ * MIT License, see LICENSE file for more details.
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_CAMERA2D_H
+#define YAGE_CAMERA2D_H
+
+#include "glslprogram.h"
+
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+
+namespace yage
+{
+
+class Camera2D
+{
+private:
+ bool update_matrix_ = true;
+ float scale_ = 1;
+ glm::vec2 position_;
+ glm::mat4 camera_matrix_;
+ glm::mat4 ortho_matrix_;
+
+public:
+ Camera2D(int screen_width = 1280, int screen_height = 720);
+
+ void update(GlslProgram &program);
+ void move(const glm::vec2 &direction);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/glslprogram.cpp b/yage/base/glslprogram.cpp
new file mode 100644
index 00000000..11a3191e
--- /dev/null
+++ b/yage/base/glslprogram.cpp
@@ -0,0 +1,162 @@
+/* ----------------------------------------------------------------------------
+ * glslprogram.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/glslprogram.h>
+
+#include <fstream>
+#include <stdexcept>
+#include <vector>
+
+namespace yage
+{
+
+GlslProgram::~GlslProgram()
+{
+ // cleanup all the shaders and the program
+ if (fragment_shader_id_ != 0) {
+ glDeleteShader(fragment_shader_id_);
+ }
+
+ if (vertex_shader_id_ != 0) {
+ glDeleteShader(vertex_shader_id_);
+ }
+
+ if (program_id_ != 0) {
+ glDeleteProgram(program_id_);
+ }
+}
+
+void GlslProgram::compileShader(const GLuint &shader,
+ const std::string &file_path)
+{
+ // get a string with the input from the shader file
+ std::ifstream file(file_path);
+ if (!file.is_open()) {
+ throw std::runtime_error("Failed to open '" + file_path + "'");
+ }
+
+ std::string content = "";
+ std::string line;
+
+ while (std::getline(file, line)) {
+ content += line + "\n";
+ }
+ file.close();
+
+ // cast source to a c string to get the address of it and input it for
+ // compilation
+ const auto *vertex_source = (const GLchar *)content.c_str();
+ glShaderSource(shader, 1, &vertex_source, nullptr);
+ glCompileShader(shader);
+
+ // check if compilation was successful
+ GLint is_compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, (int *)&is_compiled);
+
+ // if it isn't compiled throw exception to clean up
+ if (is_compiled == GL_FALSE) {
+ GLint max_length = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
+
+ std::vector<GLchar> error_log(max_length);
+ glGetShaderInfoLog(shader, max_length, &max_length, &error_log[0]);
+ std::string error_log_str((const char *)&error_log[0]);
+
+ throw std::runtime_error("Couldn't compile " + file_path + " : " +
+ error_log_str);
+ }
+}
+
+void GlslProgram::compileShaders(const std::string &vertex_shader_path,
+ const std::string &fragment_shader_path)
+{
+ // create the program that will be run on GPU
+ program_id_ = glCreateProgram();
+
+ // create vertex shader
+ vertex_shader_id_ = glCreateShader(GL_VERTEX_SHADER);
+ if (vertex_shader_id_ == 0) {
+ throw std::runtime_error("Vertex shader failed to be created");
+ }
+
+ // create fragment shader
+ fragment_shader_id_ = glCreateShader(GL_FRAGMENT_SHADER);
+ if (fragment_shader_id_ == 0) {
+ throw std::runtime_error("Fragment shader failed to be created");
+ }
+
+ // compile the two shaders
+ compileShader(vertex_shader_id_, vertex_shader_path);
+ compileShader(fragment_shader_id_, fragment_shader_path);
+}
+
+void GlslProgram::linkShaders()
+{
+ // attach the shaders that we want
+ glAttachShader(program_id_, vertex_shader_id_);
+ glAttachShader(program_id_, fragment_shader_id_);
+
+ // link our program
+ glLinkProgram(program_id_);
+
+ GLint is_linked = 0;
+ glGetProgramiv(program_id_, GL_LINK_STATUS, (int *)&is_linked);
+ if (is_linked == GL_FALSE) {
+ GLint max_length = 0;
+ glGetProgramiv(program_id_, GL_INFO_LOG_LENGTH, &max_length);
+
+ std::vector<GLchar> error_log(max_length);
+ glGetProgramInfoLog(program_id_, max_length, &max_length,
+ &error_log[0]);
+
+ std::string error_log_str((const char *)&error_log[0]);
+
+ throw std::runtime_error("Could not link program : " + error_log_str);
+ }
+
+ // detach shaders after successful link
+ glDetachShader(program_id_, fragment_shader_id_);
+ glDetachShader(program_id_, vertex_shader_id_);
+
+ // we can then delete the shaders once we have the program
+ glDeleteShader(fragment_shader_id_);
+ glDeleteShader(vertex_shader_id_);
+}
+
+void GlslProgram::addAttribute(const std::string &attribute_name)
+{
+ glBindAttribLocation(program_id_, attribute_index_++,
+ attribute_name.c_str());
+}
+
+GLint GlslProgram::getUniformLocation(const std::string &uniform_name)
+{
+ GLint location = glGetUniformLocation(program_id_, uniform_name.c_str());
+ if ((GLuint)location == GL_INVALID_INDEX) {
+ throw std::runtime_error("'" + uniform_name + "' not found");
+ }
+ return location;
+}
+
+void GlslProgram::use()
+{
+ glUseProgram(program_id_);
+ for (int i = 0; i < attribute_index_; ++i) {
+ glEnableVertexAttribArray(i);
+ }
+}
+
+void GlslProgram::unuse()
+{
+ for (int i = 0; i < attribute_index_; ++i) {
+ glDisableVertexAttribArray(i);
+ }
+ glUseProgram(0);
+}
+
+} // namespace yage
diff --git a/yage/base/glslprogram.h b/yage/base/glslprogram.h
new file mode 100644
index 00000000..fbe5ac5c
--- /dev/null
+++ b/yage/base/glslprogram.h
@@ -0,0 +1,52 @@
+/* ----------------------------------------------------------------------------
+ * glslprogram.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef GLSL_PROGRAM_H
+#define GLSL_PROGRAM_H
+
+#include <GL/glew.h>
+
+#include <string>
+
+namespace yage
+{
+
+class GlslProgram
+{
+private:
+ /// compiled shader program id
+ GLuint program_id_ = 0;
+ GLuint vertex_shader_id_ = 0;
+ GLuint fragment_shader_id_ = 0;
+ int attribute_index_ = 0;
+
+ /// compiles one shader
+ void compileShader(const GLuint &shader, const std::string &file_path);
+
+public:
+ GlslProgram() = default;
+ GlslProgram(const GlslProgram &) = delete;
+ GlslProgram(GlslProgram &&) = delete;
+ ~GlslProgram();
+
+ GlslProgram &operator=(const GlslProgram &) = delete;
+ GlslProgram &operator=(GlslProgram &&) = delete;
+
+ /// compiles vertex and fragment shader
+ void compileShaders(const std::string &vertex_shader_path,
+ const std::string &fragment_shader_path);
+ void linkShaders();
+ void addAttribute(const std::string &attribute_name);
+ GLint getUniformLocation(const std::string &uniform_name);
+ void use();
+ void unuse();
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/imageloader.cpp b/yage/base/imageloader.cpp
new file mode 100644
index 00000000..812110d8
--- /dev/null
+++ b/yage/base/imageloader.cpp
@@ -0,0 +1,60 @@
+/* ----------------------------------------------------------------------------
+ * imageloader.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/imageloader.h>
+#include <YAGE/iomanager.h>
+#include <YAGE/picopng.h>
+
+#include <stdexcept>
+
+namespace yage
+{
+
+Texture ImageLoader::loadPng(const std::string &file_path)
+{
+ Texture texture = {};
+
+ std::vector<unsigned char> in;
+ std::vector<unsigned char> out;
+ unsigned long width, height;
+
+ if (!IoManager::readFileToBuffer(file_path, in)) {
+ throw std::runtime_error("Failed to load '" + file_path +
+ "' to buffer");
+ }
+
+ int error_code = decodePNG(out, width, height, &in[0], in.size());
+ if (error_code != 0) {
+ throw std::runtime_error("Failed to load '" + file_path +
+ "' to png with error code" +
+ std::to_string(error_code));
+ }
+
+ glGenTextures(1, &texture.id);
+
+ glBindTexture(GL_TEXTURE_2D, texture.id);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, &out[0]);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR_MIPMAP_LINEAR);
+
+ glGenerateMipmap(GL_TEXTURE_2D);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ texture.width = (int)width;
+ texture.height = (int)height;
+
+ return texture;
+}
+
+} // namespace yage
diff --git a/yage/base/imageloader.h b/yage/base/imageloader.h
new file mode 100644
index 00000000..8d5c5cd1
--- /dev/null
+++ b/yage/base/imageloader.h
@@ -0,0 +1,27 @@
+/* ----------------------------------------------------------------------------
+ * imageloader.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef IMAGE_LOADER_H
+#define IMAGE_LOADER_H
+
+#include "texture.h"
+
+#include <string>
+
+namespace yage
+{
+
+class ImageLoader
+{
+public:
+ static Texture loadPng(const std::string &file_path);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/inputmanager.cpp b/yage/base/inputmanager.cpp
new file mode 100644
index 00000000..d429abd7
--- /dev/null
+++ b/yage/base/inputmanager.cpp
@@ -0,0 +1,33 @@
+/* ----------------------------------------------------------------------------
+ * inputmanager.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/inputmanager.h>
+
+namespace yage
+{
+
+void InputManager::keyPressed(unsigned key)
+{
+ key_map_[key] = true;
+}
+
+void InputManager::keyReleased(unsigned key)
+{
+ key_map_[key] = false;
+}
+
+bool InputManager::isKeyPressed(unsigned key) const
+{
+ auto key_index = key_map_.find(key);
+ if (key_index != key_map_.end()) {
+ return key_index->second;
+ }
+ return false;
+}
+
+} // namespace yage
diff --git a/yage/base/inputmanager.h b/yage/base/inputmanager.h
new file mode 100644
index 00000000..84728fff
--- /dev/null
+++ b/yage/base/inputmanager.h
@@ -0,0 +1,28 @@
+/* ----------------------------------------------------------------------------
+ * inputmanager.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef INPUT_MANAGER_H
+#define INPUT_MANAGER_H
+
+#include <unordered_map>
+
+namespace yage
+{
+
+class InputManager
+{
+private:
+ std::unordered_map<unsigned, bool> key_map_;
+
+public:
+ void keyPressed(unsigned key);
+ void keyReleased(unsigned key);
+ bool isKeyPressed(unsigned key) const;
+};
+} // namespace yage
+#endif
diff --git a/yage/base/iomanager.cpp b/yage/base/iomanager.cpp
new file mode 100644
index 00000000..93ab41c9
--- /dev/null
+++ b/yage/base/iomanager.cpp
@@ -0,0 +1,42 @@
+/* ----------------------------------------------------------------------------
+ * iomanager.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/iomanager.h>
+
+#include <fstream>
+#include <stdexcept>
+
+namespace yage
+{
+
+bool IoManager::readFileToBuffer(const std::string &file_path,
+ std::vector<unsigned char> &buffer)
+{
+ std::ifstream file(file_path, std::ios::binary);
+ if (!file.is_open()) {
+ throw std::runtime_error("Could not open '" + file_path + "'");
+ }
+
+ // seek to the end
+ file.seekg(0, std::ios::end);
+
+ // get the file size
+ int file_size = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ // reduce file size by header bytes
+ file_size -= file.tellg();
+
+ buffer.resize(file_size);
+ file.read((char *)&buffer[0], file_size);
+ file.close();
+
+ return true;
+}
+
+} // namespace yage
diff --git a/yage/base/iomanager.h b/yage/base/iomanager.h
new file mode 100644
index 00000000..95abd652
--- /dev/null
+++ b/yage/base/iomanager.h
@@ -0,0 +1,27 @@
+/* ----------------------------------------------------------------------------
+ * iomanager.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef IO_MANAGER_H
+#define IO_MANAGER_H
+
+#include <string>
+#include <vector>
+
+namespace yage
+{
+
+class IoManager
+{
+public:
+ static bool readFileToBuffer(const std::string &file_path,
+ std::vector<unsigned char> &buffer);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/picopng.cpp b/yage/base/picopng.cpp
new file mode 100644
index 00000000..dcc4b367
--- /dev/null
+++ b/yage/base/picopng.cpp
@@ -0,0 +1,1118 @@
+#include <cstdlib>
+#include <vector>
+
+namespace yage
+{
+
+/*
+ decodePNG: The picoPNG function, decodes a PNG file buffer in memory, into a
+ raw pixel buffer.
+ out_image: output parameter, this will contain the raw pixels after decoding.
+ By default the output is 32-bit RGBA color.
+ The std::vector is automatically resized to the correct size.
+ image_width: output_parameter, this will contain the width of the image in
+ pixels.
+ image_height: output_parameter, this will contain the height of the image in
+ pixels.
+ in_png: pointer to the buffer of the PNG file in memory. To get it from a file
+ on
+ disk, load it and store it in a memory buffer yourself first.
+ in_size: size of the input PNG file in bytes.
+ convert_to_rgba32: optional parameter, true by default.
+ Set to true to get the output in RGBA 32-bit (8 bit per channel) color format
+ no matter what color type the original PNG image had. This gives predictable,
+ useable data from any random input PNG.
+ Set to false to do no color conversion at all. The result then has the same
+ data
+ type as the PNG image, which can range from 1 bit to 64 bits per pixel.
+ Information about the color type or palette colors are not provided. You need
+ to know this information yourself to be able to use the data so this only
+ works for trusted PNG files. Use LodePNG instead of picoPNG if you need this
+ information.
+ return: 0 if success, not 0 if some error occured.
+*/
+int decodePNG(std::vector<unsigned char> &out_image, unsigned long &image_width,
+ unsigned long &image_height, const unsigned char *in_png,
+ size_t in_size, bool convert_to_rgba32)
+{
+ // picoPNG version 20101224
+ // Copyright (c) 2005-2010 Lode Vandevenne
+ //
+ // This software is provided 'as-is', without any express or implied
+ // warranty. In no event will the authors be held liable for any damages
+ // arising from the use of this software.
+ //
+ // Permission is granted to anyone to use this software for any purpose,
+ // including commercial applications, and to alter it and redistribute it
+ // freely, subject to the following restrictions:
+ //
+ // 1. The origin of this software must not be misrepresented; you must
+ // not
+ // claim that you wrote the original software. If you use this software
+ // in a product, an acknowledgment in the product documentation would be
+ // appreciated but is not required.
+ // 2. Altered source versions must be plainly marked as such, and must
+ // not be
+ // misrepresented as being the original software.
+ // 3. This notice may not be removed or altered from any source
+ // distribution.
+
+ // picoPNG is a PNG decoder in one C++ function of around 500 lines. Use
+ // picoPNG for
+ // programs that need only 1 .cpp file. Since it's a single function, it's
+ // very limited,
+ // it can convert a PNG to raw pixel data either converted to 32-bit RGBA
+ // color or
+ // with no color conversion at all. For anything more complex, another tiny
+ // library
+ // is available: LodePNG (lodepng.c(pp)), which is a single source and
+ // header file.
+ // Apologies for the compact code style, it's to make this tiny.
+
+ static const unsigned long LENBASE[29] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
+ 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
+ static const unsigned long LENEXTRA[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 0};
+ static const unsigned long DISTBASE[30] = {
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25,
+ 33, 49, 65, 97, 129, 193, 257, 385, 513, 769,
+ 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+ static const unsigned long DISTEXTRA[30] = {
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
+ 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+ static const unsigned long CLCL[19] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
+ 11, 4, 12, 3, 13, 2, 14, 1, 15}; // code length code lengths
+ struct Zlib // nested functions for zlib decompression
+ {
+ static unsigned long readBitFromStream(size_t &bitp,
+ const unsigned char *bits)
+ {
+ unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1;
+ bitp++;
+ return result;
+ }
+ static unsigned long readBitsFromStream(size_t &bitp,
+ const unsigned char *bits,
+ size_t nbits)
+ {
+ unsigned long result = 0;
+ for (size_t i = 0; i < nbits; i++) {
+ result += (readBitFromStream(bitp, bits)) << i;
+ }
+ return result;
+ }
+ struct HuffmanTree {
+ int makeFromLengths(const std::vector<unsigned long> &bitlen,
+ unsigned long maxbitlen)
+ { // make tree given the lengths
+ unsigned long numcodes = (unsigned long)(bitlen.size()),
+ treepos = 0, nodefilled = 0;
+ std::vector<unsigned long> tree1d(numcodes),
+ blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0);
+ for (unsigned long bits = 0; bits < numcodes; bits++) {
+ blcount[bitlen[bits]]++; // count number of instances of
+ }
+ // each code length
+ for (unsigned long bits = 1; bits <= maxbitlen; bits++) {
+ nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1])
+ << 1;
+ }
+ for (unsigned long n = 0; n < numcodes; n++) {
+ if (bitlen[n] != 0) {
+ tree1d[n] =
+ nextcode[bitlen[n]]++; // generate all the codes
+ }
+ }
+ tree2d.clear();
+ tree2d.resize(numcodes * 2, 32767); // 32767 here means the
+ // tree2d isn't filled
+ // there yet
+ for (unsigned long n = 0; n < numcodes; n++) { // the codes
+ for (unsigned long i = 0; i < bitlen[n];
+ i++) // the bits for this code
+ {
+ unsigned long bit =
+ (tree1d[n] >> (bitlen[n] - i - 1)) & 1;
+ if (treepos > numcodes - 2) {
+ return 55;
+ }
+ if (tree2d[2 * treepos + bit] ==
+ 32767) // not yet filled in
+ {
+ if (i + 1 == bitlen[n]) {
+ tree2d[2 * treepos + bit] = n;
+ treepos = 0;
+ } // last bit
+ else {
+ tree2d[2 * treepos + bit] =
+ ++nodefilled + numcodes;
+ treepos = nodefilled;
+ } // addresses are encoded as values > numcodes
+ } else {
+ treepos = tree2d[2 * treepos + bit] -
+ numcodes; // subtract numcodes from
+ }
+ // address to get address value
+ }
+ }
+ return 0;
+ }
+ int decode(bool &decoded, unsigned long &result, size_t &treepos,
+ unsigned long bit) const
+ { // Decodes a symbol from the tree
+ unsigned long numcodes = (unsigned long)tree2d.size() / 2;
+ if (treepos >= numcodes) {
+ return 11; // error: you appeared outside the codetree
+ }
+ result = tree2d[2 * treepos + bit];
+ decoded = (result < numcodes);
+ treepos = decoded ? 0 : result - numcodes;
+ return 0;
+ }
+ std::vector<unsigned long> tree2d; // 2D representation of a
+ // huffman tree: The one
+ // dimension is "0" or "1", the
+ // other contains all nodes and
+ // leaves of the tree.
+ };
+ struct Inflator {
+ int error;
+ void inflate(std::vector<unsigned char> &out,
+ const std::vector<unsigned char> &in, size_t inpos = 0)
+ {
+ size_t bp = 0, pos = 0; // bit pointer and byte pointer
+ error = 0;
+ unsigned long BFINAL = 0;
+ while (!BFINAL && !error) {
+ if (bp >> 3 >= in.size()) {
+ error = 52;
+ return;
+ } // error, bit pointer will jump past memory
+ BFINAL = readBitFromStream(bp, &in[inpos]);
+ unsigned long BTYPE = readBitFromStream(bp, &in[inpos]);
+ BTYPE += 2 * readBitFromStream(bp, &in[inpos]);
+ if (BTYPE == 3) {
+ error = 20;
+ return;
+ } // error: invalid BTYPE
+ else if (BTYPE == 0) {
+ inflateNoCompression(out, &in[inpos], bp, pos,
+ in.size());
+ } else {
+ inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(),
+ BTYPE);
+ }
+ }
+ if (!error) {
+ out.resize(pos); // Only now we know the true size of out,
+ }
+ // resize it to that
+ }
+ void generateFixedTrees(HuffmanTree &tree,
+ HuffmanTree &treeD) // get the tree of a
+ // deflated block with
+ // fixed tree
+ {
+ std::vector<unsigned long> bitlen(288, 8), bitlenD(32, 5);
+ ;
+ for (size_t i = 144; i <= 255; i++) {
+ bitlen[i] = 9;
+ }
+ for (size_t i = 256; i <= 279; i++) {
+ bitlen[i] = 7;
+ }
+ tree.makeFromLengths(bitlen, 15);
+ treeD.makeFromLengths(bitlenD, 15);
+ }
+ HuffmanTree codetree, codetreeD,
+ codelengthcodetree; // the code tree for Huffman codes, dist
+ // codes, and code length codes
+ unsigned long huffmanDecodeSymbol(const unsigned char *in,
+ size_t &bp,
+ const HuffmanTree &codetree,
+ size_t inlength)
+ { // decode a single symbol from given list of
+ // bits with given code tree. return value
+ // is the symbol
+ bool decoded;
+ unsigned long ct;
+ for (size_t treepos = 0;;) {
+ if ((bp & 0x07) == 0 && (bp >> 3) > inlength) {
+ error = 10;
+ return 0;
+ } // error: end reached without endcode
+ error = codetree.decode(decoded, ct, treepos,
+ readBitFromStream(bp, in));
+ if (error) {
+ return 0; // stop, an error happened
+ }
+ if (decoded) {
+ return ct;
+ }
+ }
+ }
+ void getTreeInflateDynamic(HuffmanTree &tree, HuffmanTree &treeD,
+ const unsigned char *in, size_t &bp,
+ size_t inlength)
+ { // get the tree of a deflated block with
+ // dynamic tree, the tree itself is also
+ // Huffman compressed with a known tree
+ std::vector<unsigned long> bitlen(288, 0), bitlenD(32, 0);
+ if (bp >> 3 >= inlength - 2) {
+ error = 49;
+ return;
+ } // the bit pointer is or will go past the memory
+ size_t HLIT = readBitsFromStream(bp, in, 5) +
+ 257; // number of literal/length codes + 257
+ size_t HDIST = readBitsFromStream(bp, in, 5) +
+ 1; // number of dist codes + 1
+ size_t HCLEN = readBitsFromStream(bp, in, 4) +
+ 4; // number of code length codes + 4
+ std::vector<unsigned long> codelengthcode(
+ 19); // lengths of tree to decode the lengths of the
+ // dynamic tree
+ for (size_t i = 0; i < 19; i++) {
+ codelengthcode[CLCL[i]] =
+ (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0;
+ }
+ error = codelengthcodetree.makeFromLengths(codelengthcode, 7);
+ if (error) {
+ return;
+ }
+ size_t i = 0, replength;
+ while (i < HLIT + HDIST) {
+ unsigned long code = huffmanDecodeSymbol(
+ in, bp, codelengthcodetree, inlength);
+ if (error) {
+ return;
+ }
+ if (code <= 15) {
+ if (i < HLIT) {
+ bitlen[i++] = code;
+ } else {
+ bitlenD[i++ - HLIT] = code;
+ }
+ } // a length code
+ else if (code == 16) // repeat previous
+ {
+ if (bp >> 3 >= inlength) {
+ error = 50;
+ return;
+ } // error, bit pointer jumps past memory
+ replength = 3 + readBitsFromStream(bp, in, 2);
+ unsigned long value; // set value to the previous code
+ if ((i - 1) < HLIT) {
+ value = bitlen[i - 1];
+ } else {
+ value = bitlenD[i - HLIT - 1];
+ }
+ for (size_t n = 0; n < replength;
+ n++) // repeat this value in the next lengths
+ {
+ if (i >= HLIT + HDIST) {
+ error = 13;
+ return;
+ } // error: i is larger than the amount of codes
+ if (i < HLIT) {
+ bitlen[i++] = value;
+ } else {
+ bitlenD[i++ - HLIT] = value;
+ }
+ }
+ } else if (code == 17) // repeat "0" 3-10 times
+ {
+ if (bp >> 3 >= inlength) {
+ error = 50;
+ return;
+ } // error, bit pointer jumps past memory
+ replength = 3 + readBitsFromStream(bp, in, 3);
+ for (size_t n = 0; n < replength;
+ n++) // repeat this value in the next lengths
+ {
+ if (i >= HLIT + HDIST) {
+ error = 14;
+ return;
+ } // error: i is larger than the amount of codes
+ if (i < HLIT) {
+ bitlen[i++] = 0;
+ } else {
+ bitlenD[i++ - HLIT] = 0;
+ }
+ }
+ } else if (code == 18) // repeat "0" 11-138 times
+ {
+ if (bp >> 3 >= inlength) {
+ error = 50;
+ return;
+ } // error, bit pointer jumps past memory
+ replength = 11 + readBitsFromStream(bp, in, 7);
+ for (size_t n = 0; n < replength;
+ n++) // repeat this value in the next lengths
+ {
+ if (i >= HLIT + HDIST) {
+ error = 15;
+ return;
+ } // error: i is larger than the amount of codes
+ if (i < HLIT) {
+ bitlen[i++] = 0;
+ } else {
+ bitlenD[i++ - HLIT] = 0;
+ }
+ }
+ } else {
+ error = 16;
+ return;
+ } // error: somehow an unexisting code appeared. This can
+ // never happen.
+ }
+ if (bitlen[256] == 0) {
+ error = 64;
+ return;
+ } // the length of the end code 256 must be larger than 0
+ error = tree.makeFromLengths(bitlen, 15);
+ if (error) {
+ return; // now we've finally got HLIT and HDIST, so
+ }
+ // generate the code trees, and the function is
+ // done
+ error = treeD.makeFromLengths(bitlenD, 15);
+ if (error) {
+ return;
+ }
+ }
+ void inflateHuffmanBlock(std::vector<unsigned char> &out,
+ const unsigned char *in, size_t &bp,
+ size_t &pos, size_t inlength,
+ unsigned long btype)
+ {
+ if (btype == 1) {
+ generateFixedTrees(codetree, codetreeD);
+ } else if (btype == 2) {
+ getTreeInflateDynamic(codetree, codetreeD, in, bp,
+ inlength);
+ if (error) {
+ return;
+ }
+ }
+ for (;;) {
+ unsigned long code =
+ huffmanDecodeSymbol(in, bp, codetree, inlength);
+ if (error) {
+ return;
+ }
+ if (code == 256) {
+ return; // end code
+ } else if (code <= 255) // literal symbol
+ {
+ if (pos >= out.size()) {
+ out.resize((pos + 1) * 2); // reserve more room
+ }
+ out[pos++] = (unsigned char)(code);
+ } else if (code >= 257 && code <= 285) // length code
+ {
+ size_t length = LENBASE[code - 257],
+ numextrabits = LENEXTRA[code - 257];
+ if ((bp >> 3) >= inlength) {
+ error = 51;
+ return;
+ } // error, bit pointer will jump past memory
+ length += readBitsFromStream(bp, in, numextrabits);
+ unsigned long codeD =
+ huffmanDecodeSymbol(in, bp, codetreeD, inlength);
+ if (error) {
+ return;
+ }
+ if (codeD > 29) {
+ error = 18;
+ return;
+ } // error: invalid dist code (30-31 are never used)
+ unsigned long dist = DISTBASE[codeD],
+ numextrabitsD = DISTEXTRA[codeD];
+ if ((bp >> 3) >= inlength) {
+ error = 51;
+ return;
+ } // error, bit pointer will jump past memory
+ dist += readBitsFromStream(bp, in, numextrabitsD);
+ size_t start = pos, back = start - dist; // backwards
+ if (pos + length >= out.size()) {
+ out.resize((pos + length) * 2); // reserve more
+ }
+ // room
+ for (size_t i = 0; i < length; i++) {
+ out[pos++] = out[back++];
+ if (back >= start) {
+ back = start - dist;
+ }
+ }
+ }
+ }
+ }
+ void inflateNoCompression(std::vector<unsigned char> &out,
+ const unsigned char *in, size_t &bp,
+ size_t &pos, size_t inlength)
+ {
+ while ((bp & 0x7) != 0) {
+ bp++; // go to first boundary of byte
+ }
+ size_t p = bp / 8;
+ if (p >= inlength - 4) {
+ error = 52;
+ return;
+ } // error, bit pointer will jump past memory
+ unsigned long LEN = in[p] + 256 * in[p + 1],
+ NLEN = in[p + 2] + 256 * in[p + 3];
+ p += 4;
+ if (LEN + NLEN != 65535) {
+ error = 21;
+ return;
+ } // error: NLEN is not one's complement of LEN
+ if (pos + LEN >= out.size()) {
+ out.resize(pos + LEN);
+ }
+ if (p + LEN > inlength) {
+ error = 23;
+ return;
+ } // error: reading outside of in buffer
+ for (unsigned long n = 0; n < LEN; n++) {
+ out[pos++] = in[p++]; // read LEN bytes of literal data
+ }
+ bp = p * 8;
+ }
+ };
+ int
+ decompress(std::vector<unsigned char> &out,
+ const std::vector<unsigned char> &in) // returns error value
+ {
+ Inflator inflator;
+ if (in.size() < 2) {
+ return 53;
+ } // error, size of zlib data too small
+ if ((in[0] * 256 + in[1]) % 31 != 0) {
+ return 24;
+ } // error: 256 * in[0] + in[1] must be a multiple of 31, the
+ // FCHECK value is supposed to be made that way
+ unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15,
+ FDICT = (in[1] >> 5) & 1;
+ if (CM != 8 || CINFO > 7) {
+ return 25;
+ } // error: only compression method 8: inflate with sliding window
+ // of 32k is supported by the PNG spec
+ if (FDICT != 0) {
+ return 26;
+ } // error: the specification of PNG says about the zlib stream:
+ // "The additional flags shall not specify a preset dictionary."
+ inflator.inflate(out, in, 2);
+ return inflator
+ .error; // note: adler32 checksum was skipped and ignored
+ }
+ };
+ struct PNG // nested functions for PNG decoding
+ {
+ struct Info {
+ unsigned long width, height, colorType, bitDepth, compressionMethod,
+ filterMethod, interlaceMethod, key_r, key_g, key_b;
+ bool key_defined; // is a transparent color key given?
+ std::vector<unsigned char> palette;
+ } info;
+ int error;
+ void decode(std::vector<unsigned char> &out, const unsigned char *in,
+ size_t size, bool convert_to_rgba32)
+ {
+ error = 0;
+ if (size == 0 || in == nullptr) {
+ error = 48;
+ return;
+ } // the given data is empty
+ readPngHeader(&in[0], size);
+ if (error) {
+ return;
+ }
+ size_t pos = 33; // first byte of the first chunk after the header
+ std::vector<unsigned char> idat; // the data from idat chunks
+ bool IEND = false, known_type = true;
+ info.key_defined = false;
+ while (!IEND) // loop through the chunks, ignoring unknown chunks
+ // and stopping at IEND chunk. IDAT data is put at
+ // the start of the in buffer
+ {
+ if (pos + 8 >= size) {
+ error = 30;
+ return;
+ } // error: size of the in buffer too small to contain next
+ // chunk
+ size_t chunkLength = read32bitInt(&in[pos]);
+ pos += 4;
+ if (chunkLength > 2147483647) {
+ error = 63;
+ return;
+ }
+ if (pos + chunkLength >= size) {
+ error = 35;
+ return;
+ } // error: size of the in buffer too small to contain next
+ // chunk
+ if (in[pos + 0] == 'I' && in[pos + 1] == 'D' &&
+ in[pos + 2] == 'A' &&
+ in[pos + 3] ==
+ 'T') // IDAT chunk, containing compressed image data
+ {
+ idat.insert(idat.end(), &in[pos + 4],
+ &in[pos + 4 + chunkLength]);
+ pos += (4 + chunkLength);
+ } else if (in[pos + 0] == 'I' && in[pos + 1] == 'E' &&
+ in[pos + 2] == 'N' && in[pos + 3] == 'D') {
+ pos += 4;
+ IEND = true;
+ } else if (in[pos + 0] == 'P' && in[pos + 1] == 'L' &&
+ in[pos + 2] == 'T' &&
+ in[pos + 3] == 'E') // palette chunk (PLTE)
+ {
+ pos += 4; // go after the 4 letters
+ info.palette.resize(4 * (chunkLength / 3));
+ if (info.palette.size() > (4 * 256)) {
+ error = 38;
+ return;
+ } // error: palette too big
+ for (size_t i = 0; i < info.palette.size(); i += 4) {
+ for (size_t j = 0; j < 3; j++) {
+ info.palette[i + j] = in[pos++]; // RGB
+ }
+ info.palette[i + 3] = 255; // alpha
+ }
+ } else if (in[pos + 0] == 't' && in[pos + 1] == 'R' &&
+ in[pos + 2] == 'N' &&
+ in[pos + 3] ==
+ 'S') // palette transparency chunk (tRNS)
+ {
+ pos += 4; // go after the 4 letters
+ if (info.colorType == 3) {
+ if (4 * chunkLength > info.palette.size()) {
+ error = 39;
+ return;
+ } // error: more alpha values given than there are
+ // palette entries
+ for (size_t i = 0; i < chunkLength; i++) {
+ info.palette[4 * i + 3] = in[pos++];
+ }
+ } else if (info.colorType == 0) {
+ if (chunkLength != 2) {
+ error = 40;
+ return;
+ } // error: this chunk must be 2 bytes for greyscale
+ // image
+ info.key_defined = true;
+ info.key_r = info.key_g = info.key_b =
+ 256 * in[pos] + in[pos + 1];
+ pos += 2;
+ } else if (info.colorType == 2) {
+ if (chunkLength != 6) {
+ error = 41;
+ return;
+ } // error: this chunk must be 6 bytes for RGB image
+ info.key_defined = true;
+ info.key_r = 256 * in[pos] + in[pos + 1];
+ pos += 2;
+ info.key_g = 256 * in[pos] + in[pos + 1];
+ pos += 2;
+ info.key_b = 256 * in[pos] + in[pos + 1];
+ pos += 2;
+ } else {
+ error = 42;
+ return;
+ } // error: tRNS chunk not allowed for other color models
+ } else // it's not an implemented chunk type, so ignore it:
+ // skip over the data
+ {
+ if (!(in[pos + 0] & 32)) {
+ error = 69;
+ return;
+ } // error: unknown critical chunk (5th bit of first byte
+ // of chunk type is 0)
+ pos += (chunkLength + 4); // skip 4 letters and
+ // uninterpreted data of
+ // unimplemented chunk
+ known_type = false;
+ }
+ pos += 4; // step over CRC (which is ignored)
+ }
+ unsigned long bpp = getBpp(info);
+ std::vector<unsigned char> scanlines(
+ ((info.width * (info.height * bpp + 7)) / 8) +
+ info.height); // now the out buffer will be filled
+ Zlib zlib; // decompress with the Zlib decompressor
+ error = zlib.decompress(scanlines, idat);
+ if (error) {
+ return; // stop if the zlib decompressor returned an error
+ }
+ size_t bytewidth = (bpp + 7) / 8,
+ outlength = (info.height * info.width * bpp + 7) / 8;
+ out.resize(outlength); // time to fill the out buffer
+ unsigned char *out_ =
+ outlength ? &out[0] : nullptr; // use a regular pointer to the
+ // std::vector for faster code if
+ // compiled without optimization
+ if (info.interlaceMethod == 0) // no interlace, just filter
+ {
+ size_t linestart = 0,
+ linelength = (info.width * bpp + 7) /
+ 8; // length in bytes of a scanline,
+ // excluding the filtertype byte
+ if (bpp >= 8) { // byte per byte
+ for (unsigned long y = 0; y < info.height; y++) {
+ unsigned long filterType = scanlines[linestart];
+ const unsigned char *prevline =
+ (y == 0) ? nullptr
+ : &out_[(y - 1) * info.width * bytewidth];
+ unFilterScanline(&out_[linestart - y],
+ &scanlines[linestart + 1], prevline,
+ bytewidth, filterType, linelength);
+ if (error) {
+ return;
+ }
+ linestart +=
+ (1 + linelength); // go to start of next scanline
+ }
+ } else // less than 8 bits per pixel, so fill it up bit per bit
+ {
+ std::vector<unsigned char> templine(
+ (info.width * bpp + 7) >> 3); // only used if bpp < 8
+ for (size_t y = 0, obp = 0; y < info.height; y++) {
+ unsigned long filterType = scanlines[linestart];
+ const unsigned char *prevline =
+ (y == 0) ? nullptr
+ : &out_[(y - 1) * info.width * bytewidth];
+ unFilterScanline(&templine[0],
+ &scanlines[linestart + 1], prevline,
+ bytewidth, filterType, linelength);
+ if (error) {
+ return;
+ }
+ for (size_t bp = 0; bp < info.width * bpp;) {
+ setBitOfReversedStream(
+ obp, out_,
+ readBitFromReversedStream(bp, &templine[0]));
+ }
+ linestart +=
+ (1 + linelength); // go to start of next scanline
+ }
+ }
+ } else // interlaceMethod is 1 (Adam7)
+ {
+ size_t passw[7] = {(info.width + 7) / 8, (info.width + 3) / 8,
+ (info.width + 3) / 4, (info.width + 1) / 4,
+ (info.width + 1) / 2, (info.width + 0) / 2,
+ (info.width + 0) / 1};
+ size_t passh[7] = {(info.height + 7) / 8, (info.height + 7) / 8,
+ (info.height + 3) / 8, (info.height + 3) / 4,
+ (info.height + 1) / 4, (info.height + 1) / 2,
+ (info.height + 0) / 2};
+ size_t passstart[7] = {0};
+ size_t pattern[28] = {0, 4, 0, 2, 0, 1, 0, 0, 0, 4,
+ 0, 2, 0, 1, 8, 8, 4, 4, 2, 2,
+ 1, 8, 8, 8, 4, 4, 2, 2}; // values for the
+ // adam7 passes
+ for (int i = 0; i < 6; i++) {
+ passstart[i + 1] = passstart[i] +
+ passh[i] * ((passw[i] ? 1 : 0) +
+ (passw[i] * bpp + 7) / 8);
+ }
+ std::vector<unsigned char> scanlineo((info.width * bpp + 7) /
+ 8),
+ scanlinen((info.width * bpp + 7) /
+ 8); //"old" and "new" scanline
+ for (int i = 0; i < 7; i++) {
+ adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0],
+ &scanlines[passstart[i]], info.width, pattern[i],
+ pattern[i + 7], pattern[i + 14], pattern[i + 21],
+ passw[i], passh[i], bpp);
+ }
+ }
+ if (convert_to_rgba32 && (info.colorType != 6 ||
+ info.bitDepth != 8)) // conversion needed
+ {
+ std::vector<unsigned char> data = out;
+ error = convert(out, &data[0], info, info.width, info.height);
+ }
+ }
+ void readPngHeader(const unsigned char *in,
+ size_t inlength) // read the information from the
+ // header and store it in the Info
+ {
+ if (inlength < 29) {
+ error = 27;
+ return;
+ } // error: the data length is smaller than the length of the
+ // header
+ if (in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 ||
+ in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) {
+ error = 28;
+ return;
+ } // no PNG signature
+ if (in[12] != 'I' || in[13] != 'H' || in[14] != 'D' ||
+ in[15] != 'R') {
+ error = 29;
+ return;
+ } // error: it doesn't start with a IHDR chunk!
+ info.width = read32bitInt(&in[16]);
+ info.height = read32bitInt(&in[20]);
+ info.bitDepth = in[24];
+ info.colorType = in[25];
+ info.compressionMethod = in[26];
+ if (in[26] != 0) {
+ error = 32;
+ return;
+ } // error: only compression method 0 is allowed in the
+ // specification
+ info.filterMethod = in[27];
+ if (in[27] != 0) {
+ error = 33;
+ return;
+ } // error: only filter method 0 is allowed in the specification
+ info.interlaceMethod = in[28];
+ if (in[28] > 1) {
+ error = 34;
+ return;
+ } // error: only interlace methods 0 and 1 exist in the
+ // specification
+ error = checkColorValidity(info.colorType, info.bitDepth);
+ }
+ void unFilterScanline(unsigned char *recon,
+ const unsigned char *scanline,
+ const unsigned char *precon, size_t bytewidth,
+ unsigned long filterType, size_t length)
+ {
+ switch (filterType) {
+ case 0:
+ for (size_t i = 0; i < length; i++) {
+ recon[i] = scanline[i];
+ }
+ break;
+ case 1:
+ for (size_t i = 0; i < bytewidth; i++) {
+ recon[i] = scanline[i];
+ }
+ for (size_t i = bytewidth; i < length; i++) {
+ recon[i] = scanline[i] + recon[i - bytewidth];
+ }
+ break;
+ case 2:
+ if (precon) {
+ for (size_t i = 0; i < length; i++) {
+ recon[i] = scanline[i] + precon[i];
+ }
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ recon[i] = scanline[i];
+ }
+ }
+ break;
+ case 3:
+ if (precon) {
+ for (size_t i = 0; i < bytewidth; i++) {
+ recon[i] = scanline[i] + precon[i] / 2;
+ }
+ for (size_t i = bytewidth; i < length; i++) {
+ recon[i] = scanline[i] +
+ ((recon[i - bytewidth] + precon[i]) / 2);
+ }
+ } else {
+ for (size_t i = 0; i < bytewidth; i++) {
+ recon[i] = scanline[i];
+ }
+ for (size_t i = bytewidth; i < length; i++) {
+ recon[i] = scanline[i] + recon[i - bytewidth] / 2;
+ }
+ }
+ break;
+ case 4:
+ if (precon) {
+ for (size_t i = 0; i < bytewidth; i++) {
+ recon[i] =
+ scanline[i] + paethPredictor(0, precon[i], 0);
+ }
+ for (size_t i = bytewidth; i < length; i++) {
+ recon[i] =
+ scanline[i] + paethPredictor(recon[i - bytewidth],
+ precon[i],
+ precon[i - bytewidth]);
+ }
+ } else {
+ for (size_t i = 0; i < bytewidth; i++) {
+ recon[i] = scanline[i];
+ }
+ for (size_t i = bytewidth; i < length; i++) {
+ recon[i] = scanline[i] +
+ paethPredictor(recon[i - bytewidth], 0, 0);
+ }
+ }
+ break;
+ default:
+ error = 36;
+ return; // error: unexisting filter type given
+ }
+ }
+ void adam7Pass(unsigned char *out, unsigned char *linen,
+ unsigned char *lineo, const unsigned char *in,
+ unsigned long w, size_t passleft, size_t passtop,
+ size_t spacex, size_t spacey, size_t passw, size_t passh,
+ unsigned long bpp)
+ { // filter and reposition the pixels
+ // into the output when the image
+ // is Adam7 interlaced. This
+ // function can only do it after
+ // the full image is already
+ // decoded. The out buffer must
+ // have the correct allocated
+ // memory size already.
+ if (passw == 0) {
+ return;
+ }
+ size_t bytewidth = (bpp + 7) / 8,
+ linelength = 1 + ((bpp * passw + 7) / 8);
+ for (unsigned long y = 0; y < passh; y++) {
+ unsigned char filterType = in[y * linelength],
+ *prevline = (y == 0) ? nullptr : lineo;
+ unFilterScanline(linen, &in[y * linelength + 1], prevline,
+ bytewidth, filterType, (w * bpp + 7) / 8);
+ if (error) {
+ return;
+ }
+ if (bpp >= 8) {
+ for (size_t i = 0; i < passw; i++) {
+ for (size_t b = 0; b < bytewidth;
+ b++) { // b = current byte of this pixel
+ out[bytewidth * w * (passtop + spacey * y) +
+ bytewidth * (passleft + spacex * i) + b] =
+ linen[bytewidth * i + b];
+ }
+ }
+ } else {
+ for (size_t i = 0; i < passw; i++) {
+ size_t obp = bpp * w * (passtop + spacey * y) +
+ bpp * (passleft + spacex * i),
+ bp = i * bpp;
+ for (size_t b = 0; b < bpp; b++) {
+ setBitOfReversedStream(
+ obp, out,
+ readBitFromReversedStream(bp, &linen[0]));
+ }
+ }
+ }
+ unsigned char *temp = linen;
+ linen = lineo;
+ lineo = temp; // swap the two buffer pointers "line old" and
+ // "line new"
+ }
+ }
+ static unsigned long
+ readBitFromReversedStream(size_t &bitp, const unsigned char *bits)
+ {
+ unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1;
+ bitp++;
+ return result;
+ }
+ static unsigned long
+ readBitsFromReversedStream(size_t &bitp, const unsigned char *bits,
+ unsigned long nbits)
+ {
+ unsigned long result = 0;
+ for (size_t i = nbits - 1; i < nbits; i--) {
+ result += ((readBitFromReversedStream(bitp, bits)) << i);
+ }
+ return result;
+ }
+ void setBitOfReversedStream(size_t &bitp, unsigned char *bits,
+ unsigned long bit)
+ {
+ bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7)));
+ bitp++;
+ }
+ unsigned long read32bitInt(const unsigned char *buffer)
+ {
+ return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) |
+ buffer[3];
+ }
+ int checkColorValidity(
+ unsigned long colorType,
+ unsigned long bd) // return type is a LodePNG error code
+ {
+ if ((colorType == 2 || colorType == 4 || colorType == 6)) {
+ if (!(bd == 8 || bd == 16)) {
+ return 37;
+ } else {
+ return 0;
+ }
+ } else if (colorType == 0) {
+ if (!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) {
+ return 37;
+ } else {
+ return 0;
+ }
+ } else if (colorType == 3) {
+ if (!(bd == 1 || bd == 2 || bd == 4 || bd == 8)) {
+ return 37;
+ } else {
+ return 0;
+ }
+ } else {
+ return 31; // unexisting color type
+ }
+ }
+ unsigned long getBpp(const Info &info)
+ {
+ if (info.colorType == 2) {
+ return (3 * info.bitDepth);
+ } else if (info.colorType >= 4) {
+ return (info.colorType - 2) * info.bitDepth;
+ } else {
+ return info.bitDepth;
+ }
+ }
+ int convert(std::vector<unsigned char> &out, const unsigned char *in,
+ Info &infoIn, unsigned long w, unsigned long h)
+ { // converts from any color type to
+ // 32-bit. return value = LodePNG error
+ // code
+ size_t numpixels = w * h, bp = 0;
+ out.resize(numpixels * 4);
+ unsigned char *out_ =
+ out.empty()
+ ? nullptr
+ : &out[0]; // faster if compiled without optimization
+ if (infoIn.bitDepth == 8 && infoIn.colorType == 0) { // greyscale
+ for (size_t i = 0; i < numpixels; i++) {
+ out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i];
+ out_[4 * i + 3] =
+ (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255;
+ }
+ } else if (infoIn.bitDepth == 8 && infoIn.colorType == 2) { // RGB
+ // color
+ for (size_t i = 0; i < numpixels; i++) {
+ for (size_t c = 0; c < 3; c++) {
+ out_[4 * i + c] = in[3 * i + c];
+ }
+ out_[4 * i + 3] = (infoIn.key_defined == 1 &&
+ in[3 * i + 0] == infoIn.key_r &&
+ in[3 * i + 1] == infoIn.key_g &&
+ in[3 * i + 2] == infoIn.key_b)
+ ? 0
+ : 255;
+ }
+ } else if (infoIn.bitDepth == 8 &&
+ infoIn.colorType == 3) { // indexed color (palette)
+ for (size_t i = 0; i < numpixels; i++) {
+ if (4U * in[i] >= infoIn.palette.size()) {
+ return 46;
+ }
+ for (size_t c = 0; c < 4; c++) {
+ out_[4 * i + c] =
+ infoIn.palette[4 * in[i] + c]; // get rgb colors
+ }
+ // from the palette
+ }
+ } else if (infoIn.bitDepth == 8 &&
+ infoIn.colorType == 4) { // greyscale with alpha
+ for (size_t i = 0; i < numpixels; i++) {
+ out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] =
+ in[2 * i + 0];
+ out_[4 * i + 3] = in[2 * i + 1];
+ }
+ } else if (infoIn.bitDepth == 8 && infoIn.colorType == 6) {
+ for (size_t i = 0; i < numpixels; i++) {
+ for (size_t c = 0; c < 4; c++) {
+ out_[4 * i + c] = in[4 * i + c]; // RGB with alpha
+ }
+ }
+ } else if (infoIn.bitDepth == 16 &&
+ infoIn.colorType == 0) { // greyscale
+ for (size_t i = 0; i < numpixels; i++) {
+ out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] =
+ in[2 * i];
+ out_[4 * i + 3] = (infoIn.key_defined &&
+ 256U * in[i] + in[i + 1] == infoIn.key_r)
+ ? 0
+ : 255;
+ }
+ } else if (infoIn.bitDepth == 16 &&
+ infoIn.colorType == 2) { // RGB color
+ for (size_t i = 0; i < numpixels; i++) {
+ for (size_t c = 0; c < 3; c++) {
+ out_[4 * i + c] = in[6 * i + 2 * c];
+ }
+ out_[4 * i + 3] =
+ (infoIn.key_defined &&
+ 256U * in[6 * i + 0] + in[6 * i + 1] == infoIn.key_r &&
+ 256U * in[6 * i + 2] + in[6 * i + 3] == infoIn.key_g &&
+ 256U * in[6 * i + 4] + in[6 * i + 5] == infoIn.key_b)
+ ? 0
+ : 255;
+ }
+ } else if (infoIn.bitDepth == 16 &&
+ infoIn.colorType == 4) { // greyscale with alpha
+ for (size_t i = 0; i < numpixels; i++) {
+ out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] =
+ in[4 * i]; // most significant byte
+ out_[4 * i + 3] = in[4 * i + 2];
+ }
+ } else if (infoIn.bitDepth == 16 && infoIn.colorType == 6) {
+ for (size_t i = 0; i < numpixels; i++) {
+ for (size_t c = 0; c < 4; c++) {
+ out_[4 * i + c] = in[8 * i + 2 * c]; // RGB with alpha
+ }
+ }
+ } else if (infoIn.bitDepth < 8 &&
+ infoIn.colorType == 0) { // greyscale
+ for (size_t i = 0; i < numpixels; i++) {
+ unsigned long value =
+ (readBitsFromReversedStream(bp, in, infoIn.bitDepth) *
+ 255) /
+ ((1 << infoIn.bitDepth) -
+ 1); // scale value from 0 to 255
+ out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] =
+ (unsigned char)(value);
+ out_[4 * i + 3] =
+ (infoIn.key_defined && value &&
+ ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r &&
+ ((1U << infoIn.bitDepth) - 1U))
+ ? 0
+ : 255;
+ }
+ } else if (infoIn.bitDepth < 8 &&
+ infoIn.colorType == 3) { // palette
+ for (size_t i = 0; i < numpixels; i++) {
+ unsigned long value =
+ readBitsFromReversedStream(bp, in, infoIn.bitDepth);
+ if (4 * value >= infoIn.palette.size()) {
+ return 47;
+ }
+ for (size_t c = 0; c < 4; c++) {
+ out_[4 * i + c] =
+ infoIn.palette[4 * value + c]; // get rgb colors
+ }
+ // from the palette
+ }
+ }
+ return 0;
+ }
+ unsigned char
+ paethPredictor(short a, short b,
+ short c) // Paeth predicter, used by PNG filter type 4
+ {
+ short p = a + b - c, pa = p > a ? (p - a) : (a - p),
+ pb = p > b ? (p - b) : (b - p),
+ pc = p > c ? (p - c) : (c - p);
+ return (unsigned char)((pa <= pb && pa <= pc) ? a
+ : pb <= pc ? b : c);
+ }
+ };
+ PNG decoder;
+ decoder.decode(out_image, in_png, in_size, convert_to_rgba32);
+ image_width = decoder.info.width;
+ image_height = decoder.info.height;
+ return decoder.error;
+}
+
+} // namespace yage
diff --git a/yage/base/picopng.h b/yage/base/picopng.h
new file mode 100644
index 00000000..095bf68a
--- /dev/null
+++ b/yage/base/picopng.h
@@ -0,0 +1,20 @@
+/* ----------------------------------------------------------------------------
+ * picopng.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <cstdlib>
+#include <vector>
+
+namespace yage
+{
+
+extern int decodePNG(std::vector<unsigned char> &out_image,
+ unsigned long &image_width, unsigned long &image_height,
+ const unsigned char *in_png, size_t in_size,
+ bool convert_to_rgba32 = true);
+
+} // namespace yage
diff --git a/yage/base/resourcemanager.cpp b/yage/base/resourcemanager.cpp
new file mode 100644
index 00000000..473ea37e
--- /dev/null
+++ b/yage/base/resourcemanager.cpp
@@ -0,0 +1,21 @@
+/* ----------------------------------------------------------------------------
+ * resourcemanager.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/resourcemanager.h>
+
+namespace yage
+{
+
+TextureCache ResourceManager::texture_cache_;
+
+Texture ResourceManager::getTexture(const std::string &texture_path)
+{
+ return texture_cache_.getTexture(texture_path);
+}
+
+} // namespace yage
diff --git a/yage/base/resourcemanager.h b/yage/base/resourcemanager.h
new file mode 100644
index 00000000..3c5081c4
--- /dev/null
+++ b/yage/base/resourcemanager.h
@@ -0,0 +1,31 @@
+/* ----------------------------------------------------------------------------
+ * resourcemanager.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef RESOURCE_MANAGER_H
+#define RESOURCE_MANAGER_H
+
+#include "texture.h"
+#include "texturecache.h"
+
+#include <string>
+
+namespace yage
+{
+
+class ResourceManager
+{
+private:
+ static TextureCache texture_cache_;
+
+public:
+ static Texture getTexture(const std::string &texture_path);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/sprite.cpp b/yage/base/sprite.cpp
new file mode 100644
index 00000000..68e08e5d
--- /dev/null
+++ b/yage/base/sprite.cpp
@@ -0,0 +1,97 @@
+/* ----------------------------------------------------------------------------
+ * sprite.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/sprite.h>
+#include <YAGE/resourcemanager.h>
+#include <YAGE/vertex.h>
+
+#include <cstddef>
+
+namespace yage
+{
+
+Sprite::Sprite() = default;
+
+Sprite::~Sprite()
+{
+ if (vbo_id_ != 0) {
+ glDeleteBuffers(1, &vbo_id_);
+ }
+}
+
+void Sprite::init(float x, float y, float width, float height,
+ const std::string &texture_path)
+{
+ x_ = x;
+ y_ = y;
+ width_ = width;
+ height_ = height;
+ texture_ = ResourceManager::getTexture(texture_path);
+
+ if (vbo_id_ == 0) {
+ glGenBuffers(1, &vbo_id_);
+ }
+
+ Vertex vertex_data[6];
+
+ vertex_data[0].setPosition(x + width, y + height);
+ vertex_data[0].setUv(1.f, 1.f);
+
+ vertex_data[1].setPosition(x, y + height);
+ vertex_data[1].setUv(0.f, 1.f);
+
+ vertex_data[2].setPosition(x, y);
+ vertex_data[2].setUv(0.f, 0.f);
+
+ vertex_data[3].setPosition(x, y);
+ vertex_data[3].setUv(0.f, 0.f);
+
+ vertex_data[4].setPosition(x + width, y + height);
+ vertex_data[4].setUv(1.f, 1.f);
+
+ vertex_data[5].setPosition(x + width, y);
+ vertex_data[5].setUv(1.f, 0.f);
+
+ for (auto &i : vertex_data) {
+ i.setColor(255, 0, 255, 255);
+ }
+
+ vertex_data[1].setColor(0, 255, 255, 255);
+ vertex_data[4].setColor(255, 0, 0, 255);
+
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_id_);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+void Sprite::draw()
+{
+ glBindTexture(GL_TEXTURE_2D, texture_.id);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_id_);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
+ (void *)offsetof(Vertex, position));
+ glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex),
+ (void *)offsetof(Vertex, color));
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
+ (void *)offsetof(Vertex, uv));
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ glDisableVertexAttribArray(2);
+ glDisableVertexAttribArray(1);
+ glDisableVertexAttribArray(0);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+} // namespace yage
diff --git a/yage/base/sprite.h b/yage/base/sprite.h
new file mode 100644
index 00000000..5b9baf91
--- /dev/null
+++ b/yage/base/sprite.h
@@ -0,0 +1,52 @@
+/* ----------------------------------------------------------------------------
+ * sprite.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+/** @file
+ */
+
+#ifndef SPRITE_H
+#define SPRITE_H
+
+#include "texture.h"
+
+#include <GL/glew.h>
+
+#include <string>
+
+namespace yage
+{
+
+/** @deprecated Use SpriteBatch instead
+ */
+class Sprite
+{
+private:
+ float x_;
+ float y_;
+ float width_;
+ float height_;
+ GLuint vbo_id_ = 0;
+ Texture texture_;
+
+public:
+ Sprite();
+ Sprite(const Sprite &) = delete;
+ Sprite(Sprite &&) = delete;
+ ~Sprite();
+
+ Sprite &operator=(const Sprite &) = delete;
+ Sprite &operator=(Sprite &&) = delete;
+
+ void init(float x, float y, float width, float height,
+ const std::string &texture_path);
+ void draw();
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/spritebatch.cpp b/yage/base/spritebatch.cpp
new file mode 100644
index 00000000..ac98130b
--- /dev/null
+++ b/yage/base/spritebatch.cpp
@@ -0,0 +1,193 @@
+/* ----------------------------------------------------------------------------
+ * spritebatch.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/spritebatch.h>
+
+#include <algorithm>
+#include <stdexcept>
+
+namespace yage
+{
+
+const int SpriteBatch::NUM_VERTICES;
+
+Glyph::Glyph(GLuint texture, float depth, const Vertex &top_left,
+ const Vertex &top_right, const Vertex &bottom_right,
+ const Vertex &bottom_left)
+ : texture_(texture), depth_(depth), top_left_(top_left),
+ top_right_(top_right), bottom_right_(bottom_right),
+ bottom_left_(bottom_left)
+{
+}
+
+RenderBatch::RenderBatch(GLint offset, GLsizei num_vertices, GLuint texture)
+ : num_vertices_(num_vertices), offset_(offset), texture_(texture)
+{
+}
+
+SpriteBatch::SpriteBatch() = default;
+
+SpriteBatch::~SpriteBatch()
+{
+ if (vao_ != 0) {
+ glDeleteVertexArrays(1, &vao_);
+ }
+
+ if (vbo_ != 0) {
+ glDeleteVertexArrays(1, &vbo_);
+ }
+}
+
+void SpriteBatch::init()
+{
+ createVertexArray();
+}
+
+void SpriteBatch::begin()
+{
+ glyphs_.clear();
+ glyph_ptrs_.clear();
+ render_batches_.clear();
+}
+
+void SpriteBatch::end()
+{
+ sortGlyphs();
+ createRenderBatches();
+}
+
+void SpriteBatch::draw(const glm::vec4 &destination_rect,
+ const glm::vec4 &uv_rect, GLuint texture,
+ const Color &color, float depth)
+{
+ Vertex top_left, top_right, bottom_right, bottom_left;
+
+ top_left.color = color;
+ top_left.setPosition(destination_rect.x,
+ destination_rect.y + destination_rect.w);
+ top_left.setUv(uv_rect.x, uv_rect.y + uv_rect.w);
+
+ top_right.color = color;
+ top_right.setPosition(destination_rect.x + destination_rect.z,
+ destination_rect.y + destination_rect.w);
+ top_right.setUv(uv_rect.x + uv_rect.z, uv_rect.y + uv_rect.w);
+
+ bottom_right.color = color;
+ bottom_right.setPosition(destination_rect.x + destination_rect.z,
+ destination_rect.y);
+ bottom_right.setUv(uv_rect.x + uv_rect.z, uv_rect.y);
+
+ bottom_left.color = color;
+ bottom_left.setPosition(destination_rect.x, destination_rect.y);
+ bottom_left.setUv(uv_rect.x, uv_rect.y);
+
+ // deal with fragmenting by creating vector of pointers
+ glyphs_.emplace_back(texture, depth, top_left, top_right, bottom_right,
+ bottom_left);
+ glyph_ptrs_.push_back(&glyphs_.back());
+}
+
+void SpriteBatch::render()
+{
+ glBindVertexArray(vao_);
+ for (auto &&batch : render_batches_) {
+ glBindTexture(GL_TEXTURE_2D, batch.texture());
+ glDrawArrays(GL_TRIANGLES, batch.offset(), batch.num_vertices());
+ }
+ glBindVertexArray(0);
+}
+
+void SpriteBatch::createVertexArray()
+{
+ if (vao_ == 0) {
+ glGenVertexArrays(1, &vao_);
+ if (vao_ == 0) {
+ throw std::runtime_error("glGenVertexArrays failed");
+ }
+ }
+ // bind vertex array object
+ glBindVertexArray(vao_);
+
+ if (vbo_ == 0) {
+ glGenBuffers(1, &vbo_);
+ if (vbo_ == 0) {
+ throw std::runtime_error("glGenBuffers failed");
+ }
+ }
+ // bind vertex buffer object
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_);
+
+ // enable vertex attribute arrays
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+
+ // set the vertex attribute pointers
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
+ (void *)offsetof(Vertex, position));
+ glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex),
+ (void *)offsetof(Vertex, color));
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
+ (void *)offsetof(Vertex, uv));
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ // unbind vertex array object
+ glBindVertexArray(0);
+}
+
+void SpriteBatch::createRenderBatches()
+{
+ std::vector<Vertex> vertices;
+ if (glyph_ptrs_.empty()) {
+ return;
+ }
+
+ render_batches_.reserve(glyph_ptrs_.size() * NUM_VERTICES);
+
+ for (int i = 0; i < (int)glyph_ptrs_.size(); ++i) {
+ if (i == 0 || (i > 0 && (glyph_ptrs_[i]->texture() !=
+ glyph_ptrs_[i - 1]->texture()))) {
+ render_batches_.emplace_back(i * NUM_VERTICES, NUM_VERTICES,
+ glyph_ptrs_[i]->texture());
+ } else {
+ render_batches_.back().num_vertices_ += NUM_VERTICES;
+ }
+
+ vertices.push_back(glyph_ptrs_[i]->bottom_left());
+ vertices.push_back(glyph_ptrs_[i]->top_left());
+ vertices.push_back(glyph_ptrs_[i]->top_right());
+ vertices.push_back(glyph_ptrs_[i]->bottom_left());
+ vertices.push_back(glyph_ptrs_[i]->bottom_right());
+ vertices.push_back(glyph_ptrs_[i]->top_right());
+
+ // bind vbo
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_);
+ // orphan the buffer
+ glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), nullptr,
+ GL_DYNAMIC_DRAW);
+ // upload the data
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(Vertex),
+ vertices.data());
+ // unbind buffer
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+}
+
+void SpriteBatch::sortGlyphs()
+{
+ // sort using introsort or quicksort
+ std::sort(glyph_ptrs_.begin(), glyph_ptrs_.end(),
+ [](Glyph *a, Glyph *b) -> bool {
+ if (a->depth() == b->depth()) {
+ return a->texture() < b->texture();
+ }
+ return a->depth() < b->depth();
+ });
+}
+
+} // namespace yage
diff --git a/yage/base/spritebatch.h b/yage/base/spritebatch.h
new file mode 100644
index 00000000..7235bd25
--- /dev/null
+++ b/yage/base/spritebatch.h
@@ -0,0 +1,105 @@
+/* ----------------------------------------------------------------------------
+ * spritebatch.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_SPRITE_BATCH_H
+#define YAGE_SPRITE_BATCH_H
+
+#include "vertex.h"
+
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+#include <vector>
+
+namespace yage
+{
+
+class SpriteBatch;
+
+/** Glyph with information of the texture.
+ */
+class Glyph
+{
+private:
+ GLuint texture_;
+ float depth_;
+ Vertex top_left_;
+ Vertex top_right_;
+ Vertex bottom_right_;
+ Vertex bottom_left_;
+
+public:
+ Glyph(GLuint texture, float depth, const Vertex &top_left,
+ const Vertex &top_right, const Vertex &bottom_right,
+ const Vertex &bottom_left);
+
+ GLuint texture() const { return texture_; }
+ float depth() const { return depth_; }
+ Vertex top_left() const { return top_left_; }
+ Vertex top_right() const { return top_right_; }
+ Vertex bottom_right() const { return bottom_right_; }
+ Vertex bottom_left() const { return bottom_left_; }
+};
+
+class RenderBatch
+{
+ friend SpriteBatch;
+
+private:
+ GLsizei num_vertices_;
+ GLint offset_;
+ GLuint texture_;
+
+public:
+ RenderBatch(GLint offset, GLsizei num_vertices, GLuint texture);
+
+ GLint offset() const { return offset_; }
+ GLsizei num_vertices() const { return num_vertices_; }
+ GLuint texture() const { return texture_; }
+};
+
+class SpriteBatch
+{
+public:
+ static const int NUM_VERTICES = 6;
+
+private:
+ GLuint vbo_ = 0;
+ GLuint vao_ = 0;
+ std::vector<Glyph> glyphs_;
+ std::vector<Glyph *> glyph_ptrs_;
+ std::vector<RenderBatch> render_batches_;
+
+public:
+ SpriteBatch();
+ SpriteBatch(const SpriteBatch &) = delete;
+ SpriteBatch(SpriteBatch &&) = delete;
+ ~SpriteBatch();
+
+ SpriteBatch &operator=(const SpriteBatch &) = delete;
+ SpriteBatch &operator=(SpriteBatch &&) = delete;
+
+ // initialize vaos and vbos
+ void init();
+ void begin();
+ void end();
+ // adds a sprite to the sprite batch to be rendered later
+ void draw(const glm::vec4 &destination_rect, const glm::vec4 &uv_rect,
+ GLuint texture, const Color &color, float depth);
+ // render the batch
+ void render();
+
+private:
+ void createVertexArray();
+ void createRenderBatches();
+ void sortGlyphs();
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/spritesheet.h b/yage/base/spritesheet.h
new file mode 100644
index 00000000..2b70ad8b
--- /dev/null
+++ b/yage/base/spritesheet.h
@@ -0,0 +1,25 @@
+/* ----------------------------------------------------------------------------
+ * spritesheet.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com>
+ * MIT License, see LICENSE file for more details.
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_SPRITESHEET_H
+#define YAGE_SPRITESHEET_H
+
+#include "texture.h"
+
+namespace yage
+{
+
+class SpriteSheet
+{
+private:
+ Texture texture_;
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/texture.h b/yage/base/texture.h
new file mode 100644
index 00000000..d1fdcbf2
--- /dev/null
+++ b/yage/base/texture.h
@@ -0,0 +1,25 @@
+/* ----------------------------------------------------------------------------
+ * texture.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef GL_TEXTURE_H
+#define GL_TEXTURE_H
+
+#include <GL/glew.h>
+
+namespace yage
+{
+
+struct Texture {
+ GLuint id;
+ int width;
+ int height;
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/texturecache.cpp b/yage/base/texturecache.cpp
new file mode 100644
index 00000000..fda5fcd9
--- /dev/null
+++ b/yage/base/texturecache.cpp
@@ -0,0 +1,30 @@
+/* ----------------------------------------------------------------------------
+ * texturecache.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/texturecache.h>
+#include <YAGE/imageloader.h>
+
+namespace yage
+{
+
+TextureCache::TextureCache() = default;
+
+Texture TextureCache::getTexture(const std::string &texture_path)
+{
+ auto itr = texture_map_.find(texture_path);
+
+ if (itr == texture_map_.end()) {
+ Texture new_texture = ImageLoader::loadPng(texture_path);
+ texture_map_.insert(make_pair(texture_path, new_texture));
+ return new_texture;
+ }
+
+ return itr->second;
+}
+
+} // namespace yage
diff --git a/yage/base/texturecache.h b/yage/base/texturecache.h
new file mode 100644
index 00000000..414c9ec3
--- /dev/null
+++ b/yage/base/texturecache.h
@@ -0,0 +1,32 @@
+/* ----------------------------------------------------------------------------
+ * texturecache.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef TEXTURE_CACHE_H
+#define TEXTURE_CACHE_H
+
+#include "texture.h"
+
+#include <unordered_map>
+
+namespace yage
+{
+
+class TextureCache
+{
+private:
+ std::unordered_map<std::string, Texture> texture_map_;
+
+public:
+ TextureCache();
+
+ Texture getTexture(const std::string &texture_path);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/vertex.h b/yage/base/vertex.h
new file mode 100644
index 00000000..15b46ed9
--- /dev/null
+++ b/yage/base/vertex.h
@@ -0,0 +1,84 @@
+/* ----------------------------------------------------------------------------
+ * vertex.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef VERTEX_H
+#define VERTEX_H
+
+#include <GL/glew.h>
+
+namespace yage
+{
+
+struct Position {
+ float x;
+ float y;
+
+ Position() = default;
+
+ Position(float x_, float y_) : x(x_), y(y_) {}
+};
+
+struct Color {
+ GLubyte r;
+ GLubyte g;
+ GLubyte b;
+ GLubyte a;
+
+ Color() = default;
+
+ Color(GLubyte r_, GLubyte g_, GLubyte b_, GLubyte a_)
+ : r(r_), g(g_), b(b_), a(a_)
+ {
+ }
+};
+
+struct UV {
+ float u;
+ float v;
+
+ UV() = default;
+
+ UV(float u_, float v_) : u(u_), v(v_) {}
+};
+
+struct Vertex {
+ Position position;
+ Color color;
+ UV uv;
+
+ Vertex() = default;
+
+ Vertex(const Position &position_, const Color &color_, const UV &uv_)
+ : position(position_), color(color_), uv(uv_)
+ {
+ }
+
+ void setPosition(float x, float y)
+ {
+ position.x = x;
+ position.y = y;
+ }
+
+ void setColor(GLubyte r, GLubyte g, GLubyte b, GLubyte a)
+ {
+ color.r = r;
+ color.g = g;
+ color.b = b;
+ color.a = a;
+ }
+
+ void setUv(float u, float v)
+ {
+ uv.u = u;
+ uv.v = v;
+ }
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/base/window.cpp b/yage/base/window.cpp
new file mode 100644
index 00000000..143b1d5d
--- /dev/null
+++ b/yage/base/window.cpp
@@ -0,0 +1,97 @@
+/* ----------------------------------------------------------------------------
+ * window.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/window.h>
+#include <GL/glew.h>
+
+#include <iostream>
+#include <stdexcept>
+
+namespace yage
+{
+
+Window::Window() = default;
+
+Window::~Window()
+{
+ SDL_DestroyWindow(window_);
+}
+
+void Window::create(const std::string &window_name, int width, int height,
+ unsigned flags)
+{
+ Uint32 gl_window_states = 0;
+
+ // set the correct input flags
+ if (flags & WindowFlags::SHOWN) {
+ gl_window_states |= SDL_WINDOW_OPENGL;
+ }
+ if (flags & WindowFlags::HIDDEN) {
+ gl_window_states |= SDL_WINDOW_HIDDEN;
+ }
+ if (flags & WindowFlags::FULLSCREEN) {
+ gl_window_states |= SDL_WINDOW_FULLSCREEN;
+ }
+ if (flags & WindowFlags::BORDERLESS) {
+ gl_window_states |= SDL_WINDOW_BORDERLESS;
+ }
+
+ // SDL_GL options
+
+ // SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 4);
+ // SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 5);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ // create the SDL window
+ window_ = SDL_CreateWindow(window_name.c_str(), SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED, width, height,
+ gl_window_states);
+ if (window_ == nullptr) {
+ throw std::runtime_error("SDL_CreateWindow failed");
+ }
+
+ // initialize the GL context in the window
+ SDL_GLContext gl_context = SDL_GL_CreateContext(window_);
+ if (gl_context == nullptr) {
+ throw std::runtime_error("SDL_GL_CreateContext failed");
+ }
+
+ // initialize glew
+ GLenum error = glewInit();
+ if (error != GLEW_OK) {
+ throw std::runtime_error("glewInit failed");
+ }
+
+ // print out the current OpenGL version to debug
+ std::cout << "*** OpenGL version: " << glGetString(GL_VERSION)
+ << " ***\n";
+
+ // set vsync on instead of custom fps limiting
+ SDL_GL_SetSwapInterval(1);
+ // set the clear color to black
+ glClearColor(0.f, 0.5f, 0.f, 1.f);
+ // set alpha blending
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void Window::swapBuffer()
+{
+ // swap the window buffer
+ SDL_GL_SwapWindow(window_);
+}
+
+void Window::clearBuffer()
+{
+ // set the clear depth
+ glClearDepth(1.f);
+ // clears buffer with clear color
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+} // namespace yage
diff --git a/yage/base/window.h b/yage/base/window.h
new file mode 100644
index 00000000..8639e075
--- /dev/null
+++ b/yage/base/window.h
@@ -0,0 +1,55 @@
+/* ----------------------------------------------------------------------------
+ * window.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#include <SDL2/SDL.h>
+
+#include <string>
+
+namespace yage
+{
+
+// window flags that can change it's appearance
+enum WindowFlags : unsigned {
+ SHOWN = 0x1,
+ HIDDEN = 0x2,
+ FULLSCREEN = 0x4,
+ BORDERLESS = 0x8,
+};
+
+// window wrapper around SDL_Window pointer
+class Window
+{
+private:
+ /// window handle
+ SDL_Window *window_ = nullptr;
+
+public:
+ Window();
+ Window(const Window &) = delete;
+ Window(Window &&) = delete;
+ /// destroys the window handle
+ ~Window();
+
+ Window &operator=(const Window &) = delete;
+ Window &operator=(Window &&) = delete;
+
+ /// create the window, initialize the handle and update the width and height
+ void create(const std::string &window_name, int width, int height,
+ unsigned flags = WindowFlags::SHOWN);
+ /// swap the buffer
+ void swapBuffer();
+ /// clear buffer
+ void clearBuffer();
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/math/CMakeLists.txt b/yage/math/CMakeLists.txt
new file mode 100644
index 00000000..761ab59f
--- /dev/null
+++ b/yage/math/CMakeLists.txt
@@ -0,0 +1 @@
+set(YAGE_MATH_SOURCES)
diff --git a/yage/math/math.h b/yage/math/math.h
new file mode 100644
index 00000000..b729dbe6
--- /dev/null
+++ b/yage/math/math.h
@@ -0,0 +1,14 @@
+/* ----------------------------------------------------------------------------
+ * math.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_MATH_H
+#define YAGE_MATH_H
+
+#include "matrix.h"
+
+#endif
diff --git a/yage/math/matrix.h b/yage/math/matrix.h
new file mode 100644
index 00000000..3992acfe
--- /dev/null
+++ b/yage/math/matrix.h
@@ -0,0 +1,424 @@
+/* ----------------------------------------------------------------------------
+ * matrix.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+/** @file
+ */
+
+#ifndef YAGE_MATH_MATRIX_H
+#define YAGE_MATH_MATRIX_H
+
+#include <algorithm>
+#include <exception>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace yage
+{
+
+template <int Rows, int Cols, class Type>
+class Matrix;
+
+/** @internal Namespace for internal details.
+ *
+ * Detail Namespace
+ * ================
+ *
+ * This is the namespace used for implementation detail.
+ */
+namespace detail
+{
+
+/** @internal Internal Row class used by the Matrix class to return the
+ * internal data structure of the Matrix.
+ *
+ * Row
+ * ===
+ *
+ * Internal Row class to return a value in the row of the matrix.
+ */
+template <int Rows, int Cols, class Type>
+class Row
+{
+private:
+ Matrix<Rows, Cols, Type> *parent_;
+ int index_;
+
+public:
+ Row<Rows, Cols, Type>(Matrix<Rows, Cols, Type> *parent, int index)
+ : parent_(parent), index_(index)
+ {
+ }
+
+ Type &operator[](int col)
+ {
+ // The index is the y-position of the element in the matrix
+ return parent_->data_[index_ * Cols + col];
+ }
+
+ const Type &operator[](int col) const
+ {
+ return parent_->data_[index_ * Cols + col];
+ }
+};
+
+} // namespace detail
+
+/** Base Matrix class used by other similar classes.
+ */
+template <int Rows = 4, int Cols = 4, class Type = double>
+class Matrix
+{
+ // friended with the row class so that it can access protected member data.
+ friend class detail::Row<Rows, Cols, Type>;
+
+protected:
+ /// Vector containing the data of the matrix.
+ std::vector<Type> data_;
+
+public:
+ /// Initializes the size of the data_ vector.
+ Matrix<Rows, Cols, Type>() : data_(Rows * Cols) {}
+ Matrix<Rows, Cols, Type>(const std::vector<Type> &data) : data_(data) {}
+
+ /// Returns the row size of the Matrix.
+ int rowSize() const { return Rows; }
+
+ /// Returns the column size of the Matrix.
+ int colSize() const { return Cols; }
+
+ /** Return the row specified row as a Matrix with only one row.
+ *
+ * @param row Row number to be returned.
+ * @return The row that is specified by the row variables.
+ */
+ Matrix<1, Cols, Type> getRow(int row) const
+ {
+ Matrix<1, Cols, Type> rowMatrix;
+ for (int i = 0; i < Cols; ++i) {
+ rowMatrix[0][i] = data_[row][i];
+ }
+ return rowMatrix;
+ }
+
+ /** Get a specific column in a column vector.
+ *
+ * @param col Column number to be returned.
+ * @return Column Matrix of the selected column.
+ */
+ Matrix<Rows, 1, Type> getCol(int col) const
+ {
+ Matrix<Rows, 1, Type> colMatrix;
+ for (int i = 0; i < Rows; ++i) {
+ colMatrix[i][0] = data_[i][col];
+ }
+ return colMatrix;
+ }
+
+ /** Iterator support for the start.
+ *
+ * @return Iterator pointing to the start of the data.
+ */
+ typename std::vector<Type>::iterator begin() { return data_.begin(); }
+
+ /** Iterator support for the end.
+ *
+ * @return Iterator pointing to the end of the data.
+ */
+ typename std::vector<Type>::iterator end() { return data_.end(); }
+
+ /** Prints out the matrix, but can also be implemented by other classes to
+ * print data differently.
+ *
+ * @bug When printing certain matrices, it omits a row or column. Still
+ * need to determine under which conditions.
+ */
+ virtual std::string toString() const
+ {
+ std::stringstream ss;
+ ss << '[';
+ for (int i = 0; i < Rows - 1; ++i) {
+ ss << '[';
+ for (int j = 0; j < Cols - 1; ++j) {
+ ss << data_[i * Cols + j] << ' ';
+ }
+ ss << data_[(Rows - 1) * Cols + Cols - 1] << "],";
+ }
+ ss << '[';
+ for (int j = 0; j < Cols - 1; ++j) {
+ ss << data_[(Rows - 1) * Cols + j] << ' ';
+ }
+ ss << data_[(Rows - 1) * Cols + Cols - 1] << "]]";
+ return ss.str();
+ }
+
+ detail::Row<Rows, Cols, Type> operator[](int row)
+ {
+ return detail::Row<Rows, Cols, Type>(this, row);
+ }
+
+ detail::Row<Rows, Cols, Type> operator[](int row) const
+ {
+ return detail::Row<Rows, Cols, Type>((Matrix<Rows, Cols, Type> *)this,
+ row);
+ }
+
+ Matrix<Rows, Cols, Type> &operator+=(const Matrix<Rows, Cols, Type> &rhs)
+ {
+ std::vector<Type> out;
+ out.reserve(data_.size());
+ std::transform(data_.begin(), data_.end(), rhs.data_.begin(),
+ std::back_inserter(out),
+ [](Type a, Type b) { return a + b; });
+ data_ = std::move(out);
+ return *this;
+ }
+
+ Matrix<Rows, Cols, Type> &operator-=(const Matrix<Rows, Cols, Type> &rhs)
+ {
+ std::vector<Type> out;
+ out.reserve(data_.size());
+ std::transform(data_.begin(), data_.end(), rhs.begin(),
+ std::back_inserter(out),
+ [](Type a, Type b) { return a - b; });
+ data_ = std::move(out);
+ return *this;
+ }
+};
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator+(Matrix<M, N, T> lhs, const Matrix<M, N, T> &rhs)
+{
+ lhs += rhs;
+ return lhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator-(Matrix<M, N, T> lhs, const Matrix<M, N, T> &rhs)
+{
+ lhs -= rhs;
+ return lhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator+(Matrix<M, N, T> lhs, const T &rhs)
+{
+ for (auto &data : lhs) {
+ data += rhs;
+ }
+ return lhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator+(const T &lhs, Matrix<M, N, T> rhs)
+{
+ for (auto &data : rhs) {
+ data += lhs;
+ }
+ return rhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator-(Matrix<M, N, T> lhs, const T &rhs)
+{
+ for (auto &data : lhs) {
+ data -= rhs;
+ }
+ return lhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator-(const T &lhs, Matrix<M, N, T> rhs)
+{
+ for (auto &data : rhs) {
+ data = lhs - data;
+ }
+ return rhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator*(Matrix<M, N, T> lhs, const T &rhs)
+{
+ for (auto &data : lhs) {
+ data *= rhs;
+ }
+ return lhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator*(const T &lhs, Matrix<M, N, T> rhs)
+{
+ for (auto &data : rhs) {
+ data *= lhs;
+ }
+ return rhs;
+}
+
+template <int M, int N, class T>
+Matrix<M, N, T> operator/(Matrix<M, N, T> lhs, const T &rhs)
+{
+ for (auto &data : lhs) {
+ data /= rhs;
+ }
+ return lhs;
+}
+
+template <int M, int N, class T>
+bool operator==(const Matrix<M, N, T> &lhs, const Matrix<M, N, T> &rhs)
+{
+ for (int i = 0; i < M; ++i) {
+ for (int j = 0; j < N; ++j) {
+ if (lhs[i][j] != rhs[i][j]) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+template <int M, int N, class T>
+std::ostream &operator<<(std::ostream &os, const Matrix<M, N, T> &mat)
+{
+ return os << mat.toString();
+}
+
+template <int Rows = 2, class Type = double>
+class Vector : public Matrix<Rows, 1, Type>
+{
+public:
+ Vector<Rows, Type>() : Matrix<Rows, 1, Type>() {}
+ Vector<Rows, Type>(const Matrix<Rows, 1, Type> &other)
+ : Matrix<Rows, 1, Type>(other)
+ {
+ }
+
+ Vector<Rows, Type>(const std::vector<Type> &data)
+ : Matrix<Rows, 1, Type>(data)
+ {
+ }
+
+ Type &operator[](int col) { return this->data_[col]; }
+
+ const Type &operator[](int col) const { return this->data_[col]; }
+
+ std::string toString() const override
+ {
+ std::stringstream ss;
+ ss << "[";
+ for (std::size_t i = 0; i < this->data_.size() - 1; ++i) {
+ ss << this->data_[i] << " ";
+ }
+ ss << this->data_[this->data_.size() - 1] << "]";
+ return ss.str();
+ }
+};
+
+/** 2D Vector class.
+ *
+ * Two dimensional vector class.
+ */
+template <class Type = double>
+class Vector2 : public Vector<2, Type>
+{
+public:
+ Vector2<Type>() : Vector<2, Type>() {}
+ Vector2<Type>(const std::vector<Type> &data) : Vector<2, Type>(data) {}
+
+ Vector2<Type>(Type x, Type y)
+ {
+ this->data_[0] = x;
+ this->data_[1] = y;
+ }
+
+ Vector2<Type>(const Matrix<2, 1, Type> &other) : Vector<2, Type>(other) {}
+
+ Type &x() { return this->data_[0]; }
+
+ const Type &x() const { return this->data_[0]; }
+
+ Type &y() { return this->data_[1]; }
+
+ const Type &y() const { return this->data_[1]; }
+};
+
+/** Definition of a 2D vector.
+ */
+using Vector2d = Vector2<double>;
+
+/** Namespace containing functions that operate on matrices.
+ *
+ * Implementations defined here are meant to operate on anything that inherits
+ * from the base Matrix class.
+ */
+namespace matrix
+{
+
+/** Transposes a matrix and returns the result
+ *
+ * @param m input matrix.
+ */
+template <int M, int N, class T>
+Matrix<N, M, T> transpose(const Matrix<M, N, T> &m)
+{
+ Matrix<N, M, T> trans;
+ for (int i = 0; i < M; ++i) {
+ for (int j = 0; j < N; ++j) {
+ trans[j][i] = m[i][j];
+ }
+ }
+ return trans;
+}
+
+/** Returns the dot product between two vectors
+ *
+ * @param m1,m2 Input matrices.
+ */
+template <int R, class T>
+T dot(const Matrix<R, 1, T> &m1, const Matrix<R, 1, T> &m2)
+{
+ T sum = 0;
+ for (int i = 0; i < R; ++i) {
+ sum += m1[i][0] * m2[i][0];
+ }
+ return sum;
+}
+
+/** Multiplies two matrices together.
+ *
+ * @param m1,m2 Matrix inputs
+ *
+ * Requires the two matrices to be compatible with multiplication.
+ */
+template <int M, int N, int P, int Q, class T>
+Matrix<M, Q, T> multiply(const Matrix<M, N, T> &m1, const Matrix<P, Q, T> &m2)
+{
+ /// @todo Think if this should be a static_assert.
+ if (N != P) {
+ throw std::runtime_error(
+ "Matrices don't have the right dimensions for multiplication");
+ }
+
+ Matrix<M, Q, T> res;
+
+ /// Performs multiplication by getting the rows and columns, transposing
+ /// one of them and then doting the result.
+ for (int i = 0; i < M; ++i) {
+ for (int j = 0; j < Q; ++j) {
+ res[i][j] = dot(transpose(m1.getRow(i)), m2.getCol(j));
+ }
+ }
+
+ return res;
+}
+
+} // namespace matrix
+
+} // namespace yage
+
+#endif
diff --git a/yage/physics/CMakeLists.txt b/yage/physics/CMakeLists.txt
new file mode 100644
index 00000000..46c97596
--- /dev/null
+++ b/yage/physics/CMakeLists.txt
@@ -0,0 +1,6 @@
+set(YAGE_PHYSICS_SOURCES
+ physics/rectanglecollider.cpp
+ physics/rigidbody.cpp
+ physics/particlebody.cpp
+ physics/body.cpp
+ )
diff --git a/yage/physics/README.org b/yage/physics/README.org
new file mode 100644
index 00000000..0620cc93
--- /dev/null
+++ b/yage/physics/README.org
@@ -0,0 +1,27 @@
+#+ TITLE : README
+#+ DATE : <2017 - 04 - 17 Mon>
+#+ AUTHOR:
+#+ EMAIL : yannherklotz @yann - arch
+#+ OPTIONS : ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline
+#+ OPTIONS : author : t c : nil creator : comment d : (not"LOGBOOK") date : t
+#+ OPTIONS : e : t email : nil f : t inline : t num : t p : nil pri : nil stat : t
+#+ OPTIONS : tags : t tasks : t tex : t timestamp : t toc : t todo : t | : t
+#+ CREATOR : Emacs 25.1.1(Org mode 8.2.10)
+#+ DESCRIPTION:
+#+ EXCLUDE_TAGS : noexport
+#+ KEYWORDS:
+#+ LANGUAGE : en
+#+ SELECT_TAGS : export
+
+*Physics Engine
+
+ **Acceleration,
+ speed and position
+
+ I have a = dv / dt;
+v = dp / dt;
+
+I am going to use the second order runga kutta method with a = 0, b = 1,
+ alpha =
+ 1 / 2 and beta =
+ 1 / 2
diff --git a/yage/physics/body.cpp b/yage/physics/body.cpp
new file mode 100644
index 00000000..8d38e70a
--- /dev/null
+++ b/yage/physics/body.cpp
@@ -0,0 +1,34 @@
+/* ----------------------------------------------------------------------------
+ * body.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/Physics/body.h>
+
+#include <utility>
+
+namespace yage
+{
+
+const double Body::GRAVITY = -9.81;
+
+double Body::xPosition() const
+{
+ return position_[0];
+}
+
+double Body::yPosition() const
+{
+ return position_[1];
+}
+
+Body::Body(Vector2d position, double mass, Vector2d velocity, bool gravity)
+ : position_(std::move(position)), mass_(mass),
+ velocity_(std::move(velocity)), gravity_(gravity)
+{
+}
+
+} // namespace yage
diff --git a/yage/physics/body.h b/yage/physics/body.h
new file mode 100644
index 00000000..bd33a9ac
--- /dev/null
+++ b/yage/physics/body.h
@@ -0,0 +1,56 @@
+/* ----------------------------------------------------------------------------
+ * body.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_PHYSICS_BODY_H
+#define YAGE_PHYSICS_BODY_H
+
+#include <YAGE/Math/matrix.h>
+
+namespace yage
+{
+class Body
+{
+public:
+ // gravity constant
+ static const double GRAVITY;
+
+protected:
+ // center of mass of the object
+ Vector2d position_ = Vector2d(0, 0);
+
+ // mass of the object
+ double mass_ = 1;
+
+ // current velocity of the object
+ Vector2d velocity_ = Vector2d(0, 0);
+
+ // boolean that defines if gravity can act on the object
+ bool gravity_ = true;
+
+ // current acceleration
+ Vector2d acceleration_ = Vector2d(0, 0);
+
+ // force acting on the body
+ Vector2d force_ = Vector2d(0, 0);
+
+public:
+ // apply force to the object and update the velocity
+ virtual void applyForce(const Vector2d &force) = 0;
+ virtual void update() = 0;
+
+ double xPosition() const;
+ double yPosition() const;
+
+protected:
+ // protected constructor to initialize member variables
+ Body(Vector2d position = Vector2d(0, 0), double mass = 1,
+ Vector2d velocity = Vector2d(0, 0), bool gravity = false);
+};
+} // namespace yage
+
+#endif
diff --git a/yage/physics/collider.h b/yage/physics/collider.h
new file mode 100644
index 00000000..2fd2ff89
--- /dev/null
+++ b/yage/physics/collider.h
@@ -0,0 +1,43 @@
+/* ----------------------------------------------------------------------------
+ * collider.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_PHYSICS_COLLIDER_H
+#define YAGE_PHYSICS_COLLIDER_H
+
+#include <glm/glm.hpp>
+
+namespace yage
+{
+
+// The Collider class helps collision detection by providing a general shape
+// for different shapes to have their own collision algorithms.
+class Collider
+{
+protected:
+ // position of the object
+ glm::vec2 position_;
+
+ // size of the object
+ glm::vec2 size_;
+
+public:
+ Collider(const glm::vec2 &position, const glm::vec2 &size)
+ : position_(position), size_(size)
+ {
+ }
+
+ // function that checks if two colliders are colliding
+ virtual bool collides(const Collider &collider) const = 0;
+
+ // function that returns if a point is inside the shape
+ virtual bool inside(const glm::vec2 &point) const = 0;
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/physics/collisionbody.h b/yage/physics/collisionbody.h
new file mode 100644
index 00000000..715c4a54
--- /dev/null
+++ b/yage/physics/collisionbody.h
@@ -0,0 +1,28 @@
+/* ----------------------------------------------------------------------------
+ * collisionbody.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_COLLISION_BODY_H
+#define YAGE_COLLISION_BODY_H
+
+#include "body.h"
+
+namespace yage
+{
+
+// a collision body will be a body that is static and not affected by gravity,
+// with infinite mass
+class CollisionBody : public Body
+{
+public:
+ CollisionBody();
+ virtual ~CollisionBody();
+};
+
+} // yage
+
+#endif
diff --git a/yage/physics/particlebody.cpp b/yage/physics/particlebody.cpp
new file mode 100644
index 00000000..bdb81eac
--- /dev/null
+++ b/yage/physics/particlebody.cpp
@@ -0,0 +1,54 @@
+/* ----------------------------------------------------------------------------
+ * particlebody.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/Physics/particlebody.h>
+
+#include <cmath>
+#include <iostream>
+
+namespace yage
+{
+
+ParticleBody::ParticleBody(const Vector2d &position, double mass,
+ const Vector2d &velocity, bool gravity)
+ : Body(position, mass, velocity, gravity)
+{
+}
+
+void ParticleBody::applyForce(const Vector2d &force)
+{
+ force_ += force;
+}
+
+void ParticleBody::update()
+{
+ // set the time_step for 60fps
+ double time_step = 1.0 / 60.0;
+
+ // set the last acceleration
+ Vector2d last_acceleration = acceleration_;
+
+ // update the position of the body
+ position_ += velocity_ * time_step +
+ (0.5 * last_acceleration * std::pow(time_step, 2));
+
+ // update the acceleration
+ if (gravity_) {
+ acceleration_ =
+ Vector2d(force_.x() / mass_, (GRAVITY + force_.y()) / mass_);
+ } else {
+ acceleration_ = Vector2d(force_.x() / mass_, force_.y() / mass_);
+ }
+
+ Vector2d avg_acceleration = (acceleration_ + last_acceleration) / 2.0;
+
+ // update the velocity of the body
+ velocity_ += avg_acceleration * time_step;
+}
+
+} // namespace yage
diff --git a/yage/physics/particlebody.h b/yage/physics/particlebody.h
new file mode 100644
index 00000000..a0b9bdad
--- /dev/null
+++ b/yage/physics/particlebody.h
@@ -0,0 +1,33 @@
+/* ----------------------------------------------------------------------------
+ * particlebody.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_PHYSICS_PARTICLE_BODY_H
+#define YAGE_PHYSICS_PARTICLE_BODY_H
+
+#include "body.h"
+
+#include <YAGE/Math/matrix.h>
+
+namespace yage
+{
+
+class ParticleBody : public Body
+{
+public:
+ ParticleBody(const Vector2d &position = Vector2d(0, 0), double mass = 1,
+ const Vector2d &velocity = Vector2d(0, 0),
+ bool gravity = true);
+
+ // apply a force to the rigid body
+ void applyForce(const Vector2d &force) override;
+ void update() override;
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/physics/physics.h b/yage/physics/physics.h
new file mode 100644
index 00000000..900f4b6a
--- /dev/null
+++ b/yage/physics/physics.h
@@ -0,0 +1,19 @@
+/* ----------------------------------------------------------------------------
+ * physics.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_PHYSICS_H
+#define YAGE_PHYSICS_H
+
+#include "body.h"
+#include "collider.h"
+#include "collisionbody.h"
+#include "particlebody.h"
+#include "rectanglecollider.h"
+#include "rigidbody.h"
+
+#endif
diff --git a/yage/physics/rectanglecollider.cpp b/yage/physics/rectanglecollider.cpp
new file mode 100644
index 00000000..64887278
--- /dev/null
+++ b/yage/physics/rectanglecollider.cpp
@@ -0,0 +1,36 @@
+/* ----------------------------------------------------------------------------
+ * rectanglecollider.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/Physics/rectanglecollider.h>
+
+namespace yage
+{
+
+RectangleCollider::RectangleCollider(const glm::vec2 &position,
+ const glm::vec2 &size)
+ : Collider(position, size)
+{
+}
+
+bool RectangleCollider::collides(const Collider &collider) const
+{
+ for (int i = position_.x; i < position_.x + size_.x; ++i) {
+ for (int j = position_.y; j < position_.y + size_.y; ++j) {
+ return collider.inside(glm::vec2(i, j));
+ }
+ }
+ return false;
+}
+
+inline bool RectangleCollider::inside(const glm::vec2 &point) const
+{
+ return position_.x < point.x && position_.x + size_.x > point.x &&
+ position_.y < point.y && position_.y + size_.y > point.y;
+}
+
+} // namespace yage
diff --git a/yage/physics/rectanglecollider.h b/yage/physics/rectanglecollider.h
new file mode 100644
index 00000000..c009f665
--- /dev/null
+++ b/yage/physics/rectanglecollider.h
@@ -0,0 +1,30 @@
+/* ----------------------------------------------------------------------------
+ * rectanglecollider.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_RECTANGLE_COLLIDER_H
+#define YAGE_RECTANGLE_COLLIDER_H
+
+#include "collider.h"
+
+#include <glm/glm.hpp>
+
+namespace yage
+{
+
+class RectangleCollider : public Collider
+{
+public:
+ RectangleCollider(const glm::vec2 &position, const glm::vec2 &size);
+
+ bool collides(const Collider &collider) const override;
+ bool inside(const glm::vec2 &point) const override;
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/physics/rigidbody.cpp b/yage/physics/rigidbody.cpp
new file mode 100644
index 00000000..dcab5f2f
--- /dev/null
+++ b/yage/physics/rigidbody.cpp
@@ -0,0 +1,20 @@
+/* ----------------------------------------------------------------------------
+ * rigidbody.cpp
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#include <YAGE/Physics/rigidbody.h>
+
+namespace yage
+{
+
+RigidBody::RigidBody(const Vector2d &position, double mass,
+ const Vector2d &velocity, bool gravity)
+ : ParticleBody(position, mass, velocity, gravity)
+{
+}
+
+} // namespace yage
diff --git a/yage/physics/rigidbody.h b/yage/physics/rigidbody.h
new file mode 100644
index 00000000..67ccb4ca
--- /dev/null
+++ b/yage/physics/rigidbody.h
@@ -0,0 +1,28 @@
+/* ----------------------------------------------------------------------------
+ * rigidbody.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef YAGE_RIGID_BODY_H
+#define YAGE_RIGID_BODY_H
+
+#include "particlebody.h"
+
+#include <glm/glm.hpp>
+
+namespace yage
+{
+
+class RigidBody : public ParticleBody
+{
+public:
+ RigidBody(const Vector2d &position = Vector2d(0, 0), double mass = 1,
+ const Vector2d &velocity = Vector2d(0, 0), bool gravity = true);
+};
+
+} // namespace yage
+
+#endif
diff --git a/yage/yage.h b/yage/yage.h
new file mode 100644
index 00000000..6157e6bf
--- /dev/null
+++ b/yage/yage.h
@@ -0,0 +1,63 @@
+/* ----------------------------------------------------------------------------
+ * yage.h
+ *
+ * Copyright (c) 2017 Yann Herklotz Grave <ymherklotz@gmail.com> -- MIT License
+ * See file LICENSE for more details
+ * ----------------------------------------------------------------------------
+ */
+
+/** @file Includes all the headers in the main YAGE project.
+ *
+ * This does not include
+ */
+
+#ifndef YAGE_YAGE_H
+#define YAGE_YAGE_H
+
+#include "camera2d.h"
+#include "glslprogram.h"
+#include "imageloader.h"
+#include "inputmanager.h"
+#include "iomanager.h"
+#include "picopng.h"
+#include "resourcemanager.h"
+#include "spritebatch.h"
+#include "texture.h"
+#include "vertex.h"
+#include "window.h"
+
+#include <SDL2/SDL.h>
+
+#include <stdexcept>
+
+/** Project namespace.
+ *
+ * Avoids collision as all the classes and global functions are wrapped in.
+ * it.
+ */
+namespace yage
+{
+
+/** Initializes YAGE.
+ *
+ * This is only there to initialize SDL2.
+ *
+ * @return Returns true if the initialization was successful.
+ */
+bool init()
+{
+ return SDL_Init(SDL_INIT_VIDEO);
+}
+
+/** Quit and cleanup YAGE
+ *
+ * SDL2 needs to clean itself up.
+ */
+void quit()
+{
+ SDL_Quit();
+}
+
+} // namespace yage
+
+#endif