Browse Source

Sunday update 2

POSITIVE-MENTAL-ATTITUDE 8 years ago
parent
commit
2b486e9d6a

+ 25 - 0
ltbl/LICENSE.md

@@ -0,0 +1,25 @@
+LTBL2
+Copyright (C) 2014 Eric Laukien
+
+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.
+
+------------------------------------------------------------------------------
+
+LTBL2 uses the following external libraries:
+
+SFML - source code is licensed under the zlib/png license.
+Fallahn's TMX Loader https://github.com/fallahn/sfml-tmxloader

+ 116 - 0
ltbl/lighting/LightDirectionEmission.cpp

@@ -0,0 +1,116 @@
+#include "ltbl/lighting/LightDirectionEmission.h"
+#include "ltbl/lighting/LightShape.h"
+#include "ltbl/lighting/LightSystem.h"
+#include <cassert>
+
+using namespace ltbl;
+
+void LightDirectionEmission::render(const sf::View &view, sf::RenderTexture &lightTempTexture, sf::RenderTexture &antumbraTempTexture, const std::vector<QuadtreeOccupant*> &shapes, sf::Shader &unshadowShader, float shadowExtension) {
+    lightTempTexture.setView(view);
+
+    lightTempTexture.clear(sf::Color::White);
+
+    // Mask off light shape (over-masking - mask too much, reveal penumbra/antumbra afterwards)
+    for (unsigned i = 0; i < shapes.size(); i++) {
+        LightShape* pLightShape = static_cast<LightShape*>(shapes[i]);
+
+        // Get boundaries
+        std::vector<LightSystem::Penumbra> penumbras;
+        std::vector<int> innerBoundaryIndices;
+        std::vector<int> outerBoundaryIndices;
+        std::vector<sf::Vector2f> innerBoundaryVectors;
+        std::vector<sf::Vector2f> outerBoundaryVectors;
+
+        LightSystem::getPenumbrasDirection(penumbras, innerBoundaryIndices, innerBoundaryVectors, outerBoundaryIndices, outerBoundaryVectors, pLightShape->_shape, _castDirection, _sourceRadius, _sourceDistance);
+
+        if (innerBoundaryIndices.size() != 2 || outerBoundaryIndices.size() != 2)
+            continue;
+
+        antumbraTempTexture.clear(sf::Color::White);
+        antumbraTempTexture.setView(view);
+
+        sf::ConvexShape maskShape;
+
+        float maxDist = 0.0f;
+
+        for (unsigned j = 0; j < pLightShape->_shape.getPointCount(); j++)
+            maxDist = std::max(maxDist, vectorMagnitude(view.getCenter() - pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(j))));
+
+        float totalShadowExtension = shadowExtension + maxDist;
+
+        maskShape.setPointCount(4);
+
+        maskShape.setPoint(0, pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[0])));
+        maskShape.setPoint(1, pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[1])));
+        maskShape.setPoint(2, pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[1])) + vectorNormalize(innerBoundaryVectors[1]) * totalShadowExtension);
+        maskShape.setPoint(3, pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[0])) + vectorNormalize(innerBoundaryVectors[0]) * totalShadowExtension);
+
+        maskShape.setFillColor(sf::Color::Black);
+
+        antumbraTempTexture.draw(maskShape);
+
+        sf::VertexArray vertexArray;
+
+        vertexArray.setPrimitiveType(sf::PrimitiveType::Triangles);
+
+        vertexArray.resize(3);
+
+        {
+            sf::RenderStates states;
+            states.blendMode = sf::BlendAdd;
+            states.shader = &unshadowShader;
+
+            // Unmask with penumbras
+            for (unsigned j = 0; j < penumbras.size(); j++) {
+                unshadowShader.setParameter("lightBrightness", penumbras[j]._lightBrightness);
+                unshadowShader.setParameter("darkBrightness", penumbras[j]._darkBrightness);
+
+                vertexArray[0].position = penumbras[j]._source;
+                vertexArray[1].position = penumbras[j]._source + vectorNormalize(penumbras[j]._lightEdge) * totalShadowExtension;
+                vertexArray[2].position = penumbras[j]._source + vectorNormalize(penumbras[j]._darkEdge) * totalShadowExtension;
+
+                vertexArray[0].texCoords = sf::Vector2f(0.0f, 1.0f);
+                vertexArray[1].texCoords = sf::Vector2f(1.0f, 0.0f);
+                vertexArray[2].texCoords = sf::Vector2f(0.0f, 0.0f);
+
+                antumbraTempTexture.draw(vertexArray, states);
+            }
+        }
+
+        antumbraTempTexture.display();
+
+        // Multiply back to lightTempTexture
+        sf::RenderStates antumbraRenderStates;
+        antumbraRenderStates.blendMode = sf::BlendMultiply;
+
+        sf::Sprite s;
+
+        s.setTexture(antumbraTempTexture.getTexture());
+
+        lightTempTexture.setView(lightTempTexture.getDefaultView());
+
+        lightTempTexture.draw(s, antumbraRenderStates);
+
+        lightTempTexture.setView(view);
+    }
+
+    for (unsigned i = 0; i < shapes.size(); i++) {
+        LightShape* pLightShape = static_cast<LightShape*>(shapes[i]);
+
+        if (pLightShape->_renderLightOverShape) {
+            pLightShape->_shape.setFillColor(sf::Color::White);
+
+            lightTempTexture.draw(pLightShape->_shape);
+        }
+    }
+
+    // Multiplicatively blend the light over the shadows
+    sf::RenderStates lightRenderStates;
+    lightRenderStates.blendMode = sf::BlendMultiply;
+
+    lightTempTexture.setView(lightTempTexture.getDefaultView());
+
+    lightTempTexture.draw(_emissionSprite, lightRenderStates);
+
+    lightTempTexture.display();
+}

+ 22 - 0
ltbl/lighting/LightDirectionEmission.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+#include <ltbl/quadtree/QuadtreeOccupant.h>
+
+namespace ltbl {
+	class LightDirectionEmission {
+	private:
+	public:
+		sf::Sprite _emissionSprite;
+		sf::Vector2f _castDirection;
+
+		float _sourceRadius;
+		float _sourceDistance;
+
+		LightDirectionEmission()
+			: _castDirection(0.0f, 1.0f), _sourceRadius(5.0f), _sourceDistance(100.0f)
+		{}
+
+		void render(const sf::View &view, sf::RenderTexture &lightTempTexture, sf::RenderTexture &antumbraTempTexture, const std::vector<QuadtreeOccupant*> &shapes, sf::Shader &unshadowShader, float shadowExtension);
+	};
+}

+ 244 - 0
ltbl/lighting/LightPointEmission.cpp

@@ -0,0 +1,244 @@
+#include "ltbl/lighting/LightPointEmission.h"
+#include "ltbl/lighting/LightShape.h"
+#include "ltbl/lighting/LightSystem.h"
+#include <iostream>
+#include <cassert>
+#include <cmath>
+
+using namespace ltbl;
+
+void LightPointEmission::render(const sf::View& view,
+                                sf::RenderTexture& lightTempTexture, sf::RenderTexture& emissionTempTexture, sf::RenderTexture& antumbraTempTexture,
+                                const std::vector<QuadtreeOccupant*>& shapes,
+                                sf::Shader& unshadowShader, sf::Shader& lightOverShapeShader,
+                                bool normalsEnabled, sf::Shader& normalsShader)
+{
+    emissionTempTexture.clear();
+    emissionTempTexture.setView(view);
+    emissionTempTexture.draw(_emissionSprite);
+    emissionTempTexture.display();
+
+    // Note: We don't want origin from _emissionSprite.getTransform,
+    // as the center point is the origin itself.
+    sf::Transform t = _emissionSprite.getTransform();
+    t.translate(_emissionSprite.getOrigin());
+    sf::Vector2f castCenter = t.transformPoint(_localCastCenter);
+
+    float shadowExtension = _shadowOverExtendMultiplier * (getAABB().width + getAABB().height);
+
+    struct OuterEdges {
+        std::vector<int> _outerBoundaryIndices;
+        std::vector<sf::Vector2f> _outerBoundaryVectors;
+    };
+
+    std::vector<OuterEdges> outerEdges(shapes.size());
+
+    std::vector<int> innerBoundaryIndices;
+    std::vector<sf::Vector2f> innerBoundaryVectors;
+    std::vector<LightSystem::Penumbra> penumbras;
+
+    sf::RenderStates maskRenderStates;
+    maskRenderStates.blendMode = sf::BlendNone;
+
+    sf::RenderStates antumbraRenderStates;
+    antumbraRenderStates.blendMode = sf::BlendMultiply;
+
+    //----- Emission
+
+    lightTempTexture.clear();
+    lightTempTexture.setView(view);
+
+    if (normalsEnabled) {
+        auto oglLightPosition = lightTempTexture.mapCoordsToPixel(_emissionSprite.getPosition());
+        normalsShader.setParameter("lightPosition", oglLightPosition.x, static_cast<int>(lightTempTexture.getSize().y - oglLightPosition.y), 0.15f);
+
+        const auto& lightColor = _emissionSprite.getColor();
+        sf::Vector3f oglLightColor{lightColor.r / 255.f, lightColor.g / 255.f, lightColor.b / 255.f};
+        normalsShader.setParameter("lightColor", oglLightColor);
+
+        // TODO Having a better interface for emission sprite settings would make us able to precompute and stores these values
+        // But all that depends on the view
+        auto oglOrigin = lightTempTexture.mapCoordsToPixel({0.f, 0.f});
+        auto oglLightWidthPos = lightTempTexture.mapCoordsToPixel({getAABB().width, 0.f}) - oglOrigin;
+        auto oglLightHeightPos = lightTempTexture.mapCoordsToPixel({0.f, getAABB().height}) - oglOrigin;
+        float oglLightWidth = std::sqrt(oglLightWidthPos.x * oglLightWidthPos.x + oglLightWidthPos.y * oglLightWidthPos.y);
+        float oglLightHeight = std::sqrt(oglLightHeightPos.x * oglLightHeightPos.x + oglLightHeightPos.y * oglLightHeightPos.y);
+        normalsShader.setParameter("lightSize", oglLightWidth, oglLightHeight);
+
+        lightTempTexture.draw(_emissionSprite, &normalsShader);
+    }
+    else {
+        lightTempTexture.draw(_emissionSprite);
+    }
+
+    //----- Shapes
+
+    // Mask off light shape (over-masking - mask too much, reveal penumbra/antumbra afterwards)
+    unsigned shapesCount = shapes.size();
+    for (unsigned i = 0; i < shapesCount; ++i) {
+        LightShape* pLightShape = static_cast<LightShape*>(shapes[i]);
+
+        // Get boundaries
+        innerBoundaryIndices.clear();
+        innerBoundaryVectors.clear();
+        penumbras.clear();
+        LightSystem::getPenumbrasPoint(penumbras, innerBoundaryIndices, innerBoundaryVectors, outerEdges[i]._outerBoundaryIndices, outerEdges[i]._outerBoundaryVectors, pLightShape->_shape, castCenter, _sourceRadius);
+
+        if (innerBoundaryIndices.size() != 2 || outerEdges[i]._outerBoundaryIndices.size() != 2)
+            continue;
+
+        // Render shape
+        if (!pLightShape->_renderLightOverShape) {
+            pLightShape->_shape.setFillColor(sf::Color::Black);
+            lightTempTexture.draw(pLightShape->_shape);
+        }
+
+        sf::Vector2f as = pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(outerEdges[i]._outerBoundaryIndices[0]));
+        sf::Vector2f bs = pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(outerEdges[i]._outerBoundaryIndices[1]));
+        sf::Vector2f ad = outerEdges[i]._outerBoundaryVectors[0];
+        sf::Vector2f bd = outerEdges[i]._outerBoundaryVectors[1];
+
+        sf::Vector2f intersectionOuter;
+
+        // Handle antumbras as a seperate case
+        if (rayIntersect(as, ad, bs, bd, intersectionOuter)) {
+            sf::Vector2f asi = pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[0]));
+            sf::Vector2f bsi = pLightShape->_shape.getTransform().transformPoint(pLightShape->_shape.getPoint(innerBoundaryIndices[1]));
+            sf::Vector2f adi = innerBoundaryVectors[0];
+            sf::Vector2f bdi = innerBoundaryVectors[1];
+
+            antumbraTempTexture.clear(sf::Color::White);
+            antumbraTempTexture.setView(view);
+
+            sf::Vector2f intersectionInner;
+
+            if (rayIntersect(asi, adi, bsi, bdi, intersectionInner)) {
+                sf::ConvexShape maskShape;
+
+                maskShape.setPointCount(3);
+
+                maskShape.setPoint(0, asi);
+                maskShape.setPoint(1, bsi);
+                maskShape.setPoint(2, intersectionInner);
+
+                maskShape.setFillColor(sf::Color::Black);
+
+                antumbraTempTexture.draw(maskShape);
+            }
+            else {
+                sf::ConvexShape maskShape;
+
+                maskShape.setPointCount(4);
+
+                maskShape.setPoint(0, asi);
+                maskShape.setPoint(1, bsi);
+                maskShape.setPoint(2, bsi + vectorNormalize(bdi) * shadowExtension);
+                maskShape.setPoint(3, asi + vectorNormalize(adi) * shadowExtension);
+
+                maskShape.setFillColor(sf::Color::Black);
+
+                antumbraTempTexture.draw(maskShape);
+            }
+
+            // Add light back for antumbra/penumbras
+            sf::VertexArray vertexArray;
+
+            vertexArray.setPrimitiveType(sf::PrimitiveType::Triangles);
+
+            vertexArray.resize(3);
+
+            sf::RenderStates penumbraRenderStates;
+            penumbraRenderStates.blendMode = sf::BlendAdd;
+            penumbraRenderStates.shader = &unshadowShader;
+
+            // Unmask with penumbras
+            for (unsigned j = 0; j < penumbras.size(); j++) {
+                unshadowShader.setParameter("lightBrightness", penumbras[j]._lightBrightness);
+                unshadowShader.setParameter("darkBrightness", penumbras[j]._darkBrightness);
+
+                vertexArray[0].position = penumbras[j]._source;
+                vertexArray[1].position = penumbras[j]._source + vectorNormalize(penumbras[j]._lightEdge) * shadowExtension;
+                vertexArray[2].position = penumbras[j]._source + vectorNormalize(penumbras[j]._darkEdge) * shadowExtension;
+
+                vertexArray[0].texCoords = sf::Vector2f(0.0f, 1.0f);
+                vertexArray[1].texCoords = sf::Vector2f(1.0f, 0.0f);
+                vertexArray[2].texCoords = sf::Vector2f(0.0f, 0.0f);
+
+                antumbraTempTexture.draw(vertexArray, penumbraRenderStates);
+            }
+
+            antumbraTempTexture.display();
+
+            // Multiply back to lightTempTexture
+
+            sf::Sprite s;
+
+            s.setTexture(antumbraTempTexture.getTexture());
+
+            lightTempTexture.setView(lightTempTexture.getDefaultView());
+
+            lightTempTexture.draw(s, antumbraRenderStates);
+
+            lightTempTexture.setView(view);
+        }
+        else {
+            sf::ConvexShape maskShape;
+
+            maskShape.setPointCount(4);
+
+            maskShape.setPoint(0, as);
+            maskShape.setPoint(1, bs);
+            maskShape.setPoint(2, bs + vectorNormalize(bd) * shadowExtension);
+            maskShape.setPoint(3, as + vectorNormalize(ad) * shadowExtension);
+
+            maskShape.setFillColor(sf::Color::Black);
+
+            lightTempTexture.draw(maskShape);
+
+            sf::VertexArray vertexArray;
+
+            vertexArray.setPrimitiveType(sf::PrimitiveType::Triangles);
+
+            vertexArray.resize(3);
+
+            sf::RenderStates penumbraRenderStates;
+            penumbraRenderStates.blendMode = sf::BlendMultiply;
+            penumbraRenderStates.shader = &unshadowShader;
+
+            // Unmask with penumbras
+            for (unsigned j = 0; j < penumbras.size(); j++) {
+                unshadowShader.setParameter("lightBrightness", penumbras[j]._lightBrightness);
+                unshadowShader.setParameter("darkBrightness", penumbras[j]._darkBrightness);
+
+                vertexArray[0].position = penumbras[j]._source;
+                vertexArray[1].position = penumbras[j]._source + vectorNormalize(penumbras[j]._lightEdge) * shadowExtension;
+                vertexArray[2].position = penumbras[j]._source + vectorNormalize(penumbras[j]._darkEdge) * shadowExtension;
+
+                vertexArray[0].texCoords = sf::Vector2f(0.0f, 1.0f);
+                vertexArray[1].texCoords = sf::Vector2f(1.0f, 0.0f);
+                vertexArray[2].texCoords = sf::Vector2f(0.0f, 0.0f);
+
+                lightTempTexture.draw(vertexArray, penumbraRenderStates);
+            }
+        }
+    }
+
+    for (unsigned i = 0; i < shapesCount; i++) {
+        LightShape* pLightShape = static_cast<LightShape*>(shapes[i]);
+
+        if (pLightShape->_renderLightOverShape) {
+            pLightShape->_shape.setFillColor(sf::Color::White);
+
+            lightTempTexture.draw(pLightShape->_shape, &lightOverShapeShader);
+        }
+        else {
+            pLightShape->_shape.setFillColor(sf::Color::Black);
+
+            lightTempTexture.draw(pLightShape->_shape);
+        }
+    }
+
+    //----- Finish
+
+    lightTempTexture.display();
+}

+ 30 - 0
ltbl/lighting/LightPointEmission.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <ltbl/quadtree/QuadtreeOccupant.h>
+
+namespace ltbl {
+    class LightPointEmission : public QuadtreeOccupant {
+    private:
+    public:
+        sf::Sprite _emissionSprite;
+        sf::Vector2f _localCastCenter;
+
+        float _sourceRadius;
+
+        float _shadowOverExtendMultiplier;
+
+        LightPointEmission()
+            : _localCastCenter(0.0f, 0.0f), _sourceRadius(8.0f), _shadowOverExtendMultiplier(1.4f)
+        {}
+
+        sf::FloatRect getAABB() const {
+            return _emissionSprite.getGlobalBounds();
+        }
+
+        void render(const sf::View& view,
+                    sf::RenderTexture& lightTempTexture, sf::RenderTexture& emissionTempTexture, sf::RenderTexture& antumbraTempTexture,
+                    const std::vector<QuadtreeOccupant*>& shapes,
+                    sf::Shader& unshadowShader, sf::Shader& lightOverShapeShader,
+                    bool normalsEnabled, sf::Shader& normalsShader);
+    };
+}

+ 20 - 0
ltbl/lighting/LightShape.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <ltbl/quadtree/QuadtreeOccupant.h>
+
+namespace ltbl {
+	class LightShape : public QuadtreeOccupant {
+	public:
+		bool _renderLightOverShape;
+
+		sf::ConvexShape _shape;
+
+		LightShape()
+			: _renderLightOverShape(true)
+		{}
+
+		sf::FloatRect getAABB() const {
+			return _shape.getGlobalBounds();
+		}
+	};
+}

+ 755 - 0
ltbl/lighting/LightSystem.cpp

@@ -0,0 +1,755 @@
+#include "ltbl/lighting/LightSystem.h"
+#include <cassert>
+#include <iostream>
+#include <cmath>
+
+using namespace ltbl;
+
+void LightSystem::getPenumbrasPoint(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceCenter, float sourceRadius) {
+    const int numPoints = shape.getPointCount();
+
+    std::vector<bool> bothEdgesBoundaryWindings;
+    bothEdgesBoundaryWindings.reserve(2);
+
+    std::vector<bool> oneEdgeBoundaryWindings;
+    oneEdgeBoundaryWindings.reserve(2);
+
+    // Calculate front and back facing sides
+    std::vector<bool> facingFrontBothEdges;
+    facingFrontBothEdges.reserve(numPoints);
+
+    std::vector<bool> facingFrontOneEdge;
+    facingFrontOneEdge.reserve(numPoints);
+
+    for (int i = 0; i < numPoints; i++) {
+        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(i));
+
+        sf::Vector2f nextPoint;
+
+        if (i < numPoints - 1)
+            nextPoint = shape.getTransform().transformPoint(shape.getPoint(i + 1));
+        else
+            nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
+
+        sf::Vector2f firstEdgeRay;
+        sf::Vector2f secondEdgeRay;
+        sf::Vector2f firstNextEdgeRay;
+        sf::Vector2f secondNextEdgeRay;
+
+        {
+            sf::Vector2f sourceToPoint = point - sourceCenter;
+
+            sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);
+
+            perpendicularOffset = vectorNormalize(perpendicularOffset);
+            perpendicularOffset *= sourceRadius;
+
+            firstEdgeRay = point - (sourceCenter - perpendicularOffset);
+            secondEdgeRay = point - (sourceCenter + perpendicularOffset);
+        }
+
+        {
+            sf::Vector2f sourceToPoint = nextPoint - sourceCenter;
+
+            sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);
+
+            perpendicularOffset = vectorNormalize(perpendicularOffset);
+            perpendicularOffset *= sourceRadius;
+
+            firstNextEdgeRay = nextPoint - (sourceCenter - perpendicularOffset);
+            secondNextEdgeRay = nextPoint - (sourceCenter + perpendicularOffset);
+        }
+
+        sf::Vector2f pointToNextPoint = nextPoint - point;
+
+        sf::Vector2f normal = vectorNormalize(sf::Vector2f(-pointToNextPoint.y, pointToNextPoint.x));
+
+        // Front facing, mark it
+        facingFrontBothEdges.push_back((vectorDot(firstEdgeRay, normal) > 0.0f && vectorDot(secondEdgeRay, normal) > 0.0f) || (vectorDot(firstNextEdgeRay, normal) > 0.0f && vectorDot(secondNextEdgeRay, normal) > 0.0f));
+        facingFrontOneEdge.push_back((vectorDot(firstEdgeRay, normal) > 0.0f || vectorDot(secondEdgeRay, normal) > 0.0f) || vectorDot(firstNextEdgeRay, normal) > 0.0f || vectorDot(secondNextEdgeRay, normal) > 0.0f);
+    }
+
+    // Go through front/back facing list. Where the facing direction switches, there is a boundary
+    for (int i = 1; i < numPoints; i++)
+        if (facingFrontBothEdges[i] != facingFrontBothEdges[i - 1]) {
+            innerBoundaryIndices.push_back(i);
+            bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[i]);
+        }
+
+    // Check looping indices separately
+    if (facingFrontBothEdges[0] != facingFrontBothEdges[numPoints - 1]) {
+        innerBoundaryIndices.push_back(0);
+        bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[0]);
+    }
+
+    // Go through front/back facing list. Where the facing direction switches, there is a boundary
+    for (int i = 1; i < numPoints; i++)
+        if (facingFrontOneEdge[i] != facingFrontOneEdge[i - 1]) {
+            outerBoundaryIndices.push_back(i);
+            oneEdgeBoundaryWindings.push_back(facingFrontOneEdge[i]);
+        }
+
+    // Check looping indices separately
+    if (facingFrontOneEdge[0] != facingFrontOneEdge[numPoints - 1]) {
+        outerBoundaryIndices.push_back(0);
+        oneEdgeBoundaryWindings.push_back(facingFrontOneEdge[0]);
+    }
+
+    // Compute outer boundary vectors
+    for (unsigned bi = 0; bi < outerBoundaryIndices.size(); bi++) {
+        int penumbraIndex = outerBoundaryIndices[bi];
+        bool winding = oneEdgeBoundaryWindings[bi];
+
+        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+        sf::Vector2f sourceToPoint = point - sourceCenter;
+
+        sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);
+
+        perpendicularOffset = vectorNormalize(perpendicularOffset);
+        perpendicularOffset *= sourceRadius;
+
+        sf::Vector2f firstEdgeRay = point - (sourceCenter + perpendicularOffset);
+        sf::Vector2f secondEdgeRay = point - (sourceCenter - perpendicularOffset);
+
+        // Add boundary vector
+        outerBoundaryVectors.push_back(winding ? firstEdgeRay : secondEdgeRay);
+    }
+
+    for (unsigned bi = 0; bi < innerBoundaryIndices.size(); bi++) {
+        int penumbraIndex = innerBoundaryIndices[bi];
+        bool winding = bothEdgesBoundaryWindings[bi];
+
+        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+        sf::Vector2f sourceToPoint = point - sourceCenter;
+
+        sf::Vector2f perpendicularOffset(-sourceToPoint.y, sourceToPoint.x);
+
+        perpendicularOffset = vectorNormalize(perpendicularOffset);
+        perpendicularOffset *= sourceRadius;
+
+        sf::Vector2f firstEdgeRay = point - (sourceCenter + perpendicularOffset);
+        sf::Vector2f secondEdgeRay = point - (sourceCenter - perpendicularOffset);
+
+        // Add boundary vector
+        innerBoundaryVectors.push_back(winding ? secondEdgeRay : firstEdgeRay);
+        sf::Vector2f outerBoundaryVector = winding ? firstEdgeRay : secondEdgeRay;
+
+        if (innerBoundaryIndices.size() == 1)
+            innerBoundaryVectors.push_back(outerBoundaryVector);
+
+        // Add penumbras
+        bool hasPrevPenumbra = false;
+
+        sf::Vector2f prevPenumbraLightEdgeVector;
+
+        float prevBrightness = 1.0f;
+
+        int counter = 0;
+
+        while (penumbraIndex != -1) {
+            sf::Vector2f nextPoint;
+            int nextPointIndex;
+
+            if (penumbraIndex < numPoints - 1) {
+                nextPointIndex = penumbraIndex + 1;
+                nextPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex + 1));
+            }
+            else {
+                nextPointIndex = 0;
+                nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
+            }
+
+            sf::Vector2f pointToNextPoint = nextPoint - point;
+
+            sf::Vector2f prevPoint;
+            int prevPointIndex;
+
+            if (penumbraIndex > 0) {
+                prevPointIndex = penumbraIndex - 1;
+                prevPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex - 1));
+            }
+            else {
+                prevPointIndex = numPoints - 1;
+                prevPoint = shape.getTransform().transformPoint(shape.getPoint(numPoints - 1));
+            }
+
+            sf::Vector2f pointToPrevPoint = prevPoint - point;
+
+            LightSystem::Penumbra penumbra;
+
+            penumbra._source = point;
+
+            if (!winding) {
+                if (hasPrevPenumbra)
+                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
+                else
+                    penumbra._lightEdge = innerBoundaryVectors.back();
+
+                penumbra._darkEdge = outerBoundaryVector;
+
+                penumbra._lightBrightness = prevBrightness;
+
+                // Next point, check for intersection
+                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToNextPoint)));
+                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));
+
+                if (intersectionAngle < penumbraAngle) {
+                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;
+
+                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);
+
+                    penumbra._darkEdge = pointToNextPoint;
+
+                    penumbraIndex = nextPointIndex;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = true;
+
+                    prevPenumbraLightEdgeVector = penumbra._darkEdge;
+
+                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+                    sourceToPoint = point - sourceCenter;
+
+                    perpendicularOffset = sf::Vector2f(-sourceToPoint.y, sourceToPoint.x);
+
+                    perpendicularOffset = vectorNormalize(perpendicularOffset);
+                    perpendicularOffset *= sourceRadius;
+
+                    firstEdgeRay = point - (sourceCenter + perpendicularOffset);
+                    secondEdgeRay = point - (sourceCenter - perpendicularOffset);
+
+                    outerBoundaryVector = secondEdgeRay;
+
+                    if (!outerBoundaryVectors.empty()) {
+                        outerBoundaryVectors[0] = penumbra._darkEdge;
+                        outerBoundaryIndices[0] = penumbraIndex;
+                    }
+                }
+                else {
+                    penumbra._darkBrightness = 0.0f;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = false;
+
+                    if (!outerBoundaryVectors.empty()) {
+                        outerBoundaryVectors[0] = penumbra._darkEdge;
+                        outerBoundaryIndices[0] = penumbraIndex;
+                    }
+
+                    penumbraIndex = -1;
+                }
+            }
+            else {
+                if (hasPrevPenumbra)
+                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
+                else
+                    penumbra._lightEdge = innerBoundaryVectors.back();
+
+                penumbra._darkEdge = outerBoundaryVector;
+
+                penumbra._lightBrightness = prevBrightness;
+
+                // Next point, check for intersection
+                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToPrevPoint)));
+                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));
+
+                if (intersectionAngle < penumbraAngle) {
+                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;
+
+                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);
+
+                    penumbra._darkEdge = pointToPrevPoint;
+
+                    penumbraIndex = prevPointIndex;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = true;
+
+                    prevPenumbraLightEdgeVector = penumbra._darkEdge;
+
+                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+                    sourceToPoint = point - sourceCenter;
+
+                    perpendicularOffset = sf::Vector2f(-sourceToPoint.y, sourceToPoint.x);
+
+                    perpendicularOffset = vectorNormalize(perpendicularOffset);
+                    perpendicularOffset *= sourceRadius;
+
+                    firstEdgeRay = point - (sourceCenter + perpendicularOffset);
+                    secondEdgeRay = point - (sourceCenter - perpendicularOffset);
+
+                    outerBoundaryVector = firstEdgeRay;
+
+                    if (!outerBoundaryVectors.empty()) {
+                        outerBoundaryVectors[1] = penumbra._darkEdge;
+                        outerBoundaryIndices[1] = penumbraIndex;
+                    }
+                }
+                else {
+                    penumbra._darkBrightness = 0.0f;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = false;
+
+                    if (!outerBoundaryVectors.empty()) {
+                        outerBoundaryVectors[1] = penumbra._darkEdge;
+                        outerBoundaryIndices[1] = penumbraIndex;
+                    }
+
+                    penumbraIndex = -1;
+                }
+            }
+
+            penumbras.push_back(penumbra);
+
+            counter++;
+        }
+    }
+}
+
+void LightSystem::getPenumbrasDirection(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceDirection, float sourceRadius, float sourceDistance) {
+    const int numPoints = shape.getPointCount();
+
+    innerBoundaryIndices.reserve(2);
+    innerBoundaryVectors.reserve(2);
+    penumbras.reserve(2);
+
+    std::vector<bool> bothEdgesBoundaryWindings;
+    bothEdgesBoundaryWindings.reserve(2);
+
+    // Calculate front and back facing sides
+    std::vector<bool> facingFrontBothEdges;
+    facingFrontBothEdges.reserve(numPoints);
+
+    std::vector<bool> facingFrontOneEdge;
+    facingFrontOneEdge.reserve(numPoints);
+
+    for (int i = 0; i < numPoints; i++) {
+        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(i));
+
+        sf::Vector2f nextPoint;
+
+        if (i < numPoints - 1)
+            nextPoint = shape.getTransform().transformPoint(shape.getPoint(i + 1));
+        else
+            nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
+
+        sf::Vector2f firstEdgeRay;
+        sf::Vector2f secondEdgeRay;
+        sf::Vector2f firstNextEdgeRay;
+        sf::Vector2f secondNextEdgeRay;
+
+        sf::Vector2f perpendicularOffset(-sourceDirection.y, sourceDirection.x);
+
+        perpendicularOffset = vectorNormalize(perpendicularOffset);
+        perpendicularOffset *= sourceRadius;
+
+        firstEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);
+        secondEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
+
+        firstNextEdgeRay = nextPoint - (point - sourceDirection * sourceDistance - perpendicularOffset);
+        secondNextEdgeRay = nextPoint - (point - sourceDirection * sourceDistance + perpendicularOffset);
+
+        sf::Vector2f pointToNextPoint = nextPoint - point;
+
+        sf::Vector2f normal = vectorNormalize(sf::Vector2f(-pointToNextPoint.y, pointToNextPoint.x));
+
+        // Front facing, mark it
+        facingFrontBothEdges.push_back((vectorDot(firstEdgeRay, normal) > 0.0f && vectorDot(secondEdgeRay, normal) > 0.0f) || (vectorDot(firstNextEdgeRay, normal) > 0.0f && vectorDot(secondNextEdgeRay, normal) > 0.0f));
+        facingFrontOneEdge.push_back((vectorDot(firstEdgeRay, normal) > 0.0f || vectorDot(secondEdgeRay, normal) > 0.0f) || vectorDot(firstNextEdgeRay, normal) > 0.0f || vectorDot(secondNextEdgeRay, normal) > 0.0f);
+    }
+
+    // Go through front/back facing list. Where the facing direction switches, there is a boundary
+    for (int i = 1; i < numPoints; i++)
+        if (facingFrontBothEdges[i] != facingFrontBothEdges[i - 1]) {
+            innerBoundaryIndices.push_back(i);
+            bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[i]);
+        }
+
+    // Check looping indices separately
+    if (facingFrontBothEdges[0] != facingFrontBothEdges[numPoints - 1]) {
+        innerBoundaryIndices.push_back(0);
+        bothEdgesBoundaryWindings.push_back(facingFrontBothEdges[0]);
+    }
+
+    // Go through front/back facing list. Where the facing direction switches, there is a boundary
+    for (int i = 1; i < numPoints; i++)
+        if (facingFrontOneEdge[i] != facingFrontOneEdge[i - 1])
+            outerBoundaryIndices.push_back(i);
+
+    // Check looping indices separately
+    if (facingFrontOneEdge[0] != facingFrontOneEdge[numPoints - 1])
+        outerBoundaryIndices.push_back(0);
+
+    for (unsigned bi = 0; bi < innerBoundaryIndices.size(); bi++) {
+        int penumbraIndex = innerBoundaryIndices[bi];
+        bool winding = bothEdgesBoundaryWindings[bi];
+
+        sf::Vector2f point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+        sf::Vector2f perpendicularOffset(-sourceDirection.y, sourceDirection.x);
+
+        perpendicularOffset = vectorNormalize(perpendicularOffset);
+        perpendicularOffset *= sourceRadius;
+
+        sf::Vector2f firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
+        sf::Vector2f secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);
+
+        // Add boundary vector
+        innerBoundaryVectors.push_back(winding ? secondEdgeRay : firstEdgeRay);
+        sf::Vector2f outerBoundaryVector = winding ? firstEdgeRay : secondEdgeRay;
+
+        outerBoundaryVectors.push_back(outerBoundaryVector);
+
+        // Add penumbras
+        bool hasPrevPenumbra = false;
+
+        sf::Vector2f prevPenumbraLightEdgeVector;
+
+        float prevBrightness = 1.0f;
+
+        int counter = 0;
+
+        while (penumbraIndex != -1) {
+            sf::Vector2f nextPoint;
+            int nextPointIndex;
+
+            if (penumbraIndex < numPoints - 1) {
+                nextPointIndex = penumbraIndex + 1;
+                nextPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex + 1));
+            }
+            else {
+                nextPointIndex = 0;
+                nextPoint = shape.getTransform().transformPoint(shape.getPoint(0));
+            }
+
+            sf::Vector2f pointToNextPoint = nextPoint - point;
+
+            sf::Vector2f prevPoint;
+            int prevPointIndex;
+
+            if (penumbraIndex > 0) {
+                prevPointIndex = penumbraIndex - 1;
+                prevPoint = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex - 1));
+            }
+            else {
+                prevPointIndex = numPoints - 1;
+                prevPoint = shape.getTransform().transformPoint(shape.getPoint(numPoints - 1));
+            }
+
+            sf::Vector2f pointToPrevPoint = prevPoint - point;
+
+            LightSystem::Penumbra penumbra;
+
+            penumbra._source = point;
+
+            if (!winding) {
+                if (hasPrevPenumbra)
+                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
+                else
+                    penumbra._lightEdge = innerBoundaryVectors.back();
+
+                penumbra._darkEdge = outerBoundaryVector;
+
+                penumbra._lightBrightness = prevBrightness;
+
+                // Next point, check for intersection
+                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToNextPoint)));
+                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));
+
+                if (intersectionAngle < penumbraAngle) {
+                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;
+
+                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);
+
+                    penumbra._darkEdge = pointToNextPoint;
+
+                    penumbraIndex = nextPointIndex;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = true;
+
+                    prevPenumbraLightEdgeVector = penumbra._darkEdge;
+
+                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+                    perpendicularOffset = sf::Vector2f(-sourceDirection.y, sourceDirection.x);
+
+                    perpendicularOffset = vectorNormalize(perpendicularOffset);
+                    perpendicularOffset *= sourceRadius;
+
+                    firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
+                    secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);
+
+                    outerBoundaryVector = secondEdgeRay;
+                }
+                else {
+                    penumbra._darkBrightness = 0.0f;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = false;
+
+                    penumbraIndex = -1;
+                }
+            }
+            else {
+                if (hasPrevPenumbra)
+                    penumbra._lightEdge = prevPenumbraLightEdgeVector;
+                else
+                    penumbra._lightEdge = innerBoundaryVectors.back();
+
+                penumbra._darkEdge = outerBoundaryVector;
+
+                penumbra._lightBrightness = prevBrightness;
+
+                // Next point, check for intersection
+                float intersectionAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(pointToPrevPoint)));
+                float penumbraAngle = std::acos(vectorDot(vectorNormalize(penumbra._lightEdge), vectorNormalize(penumbra._darkEdge)));
+
+                if (intersectionAngle < penumbraAngle) {
+                    prevBrightness = penumbra._darkBrightness = intersectionAngle / penumbraAngle;
+
+                    assert(prevBrightness >= 0.0f && prevBrightness <= 1.0f);
+
+                    penumbra._darkEdge = pointToPrevPoint;
+
+                    penumbraIndex = prevPointIndex;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = true;
+
+                    prevPenumbraLightEdgeVector = penumbra._darkEdge;
+
+                    point = shape.getTransform().transformPoint(shape.getPoint(penumbraIndex));
+
+                    perpendicularOffset = sf::Vector2f(-sourceDirection.y, sourceDirection.x);
+
+                    perpendicularOffset = vectorNormalize(perpendicularOffset);
+                    perpendicularOffset *= sourceRadius;
+
+                    firstEdgeRay = point - (point - sourceDirection * sourceDistance + perpendicularOffset);
+                    secondEdgeRay = point - (point - sourceDirection * sourceDistance - perpendicularOffset);
+
+                    outerBoundaryVector = firstEdgeRay;
+                }
+                else {
+                    penumbra._darkBrightness = 0.0f;
+
+                    if (hasPrevPenumbra) {
+                        std::swap(penumbra._darkBrightness, penumbras.back()._darkBrightness);
+                        std::swap(penumbra._lightBrightness, penumbras.back()._lightBrightness);
+                    }
+
+                    hasPrevPenumbra = false;
+
+                    penumbraIndex = -1;
+                }
+            }
+
+            penumbras.push_back(penumbra);
+
+            counter++;
+        }
+    }
+}
+
+void LightSystem::create(const sf::FloatRect& rootRegion, const sf::Vector2u& imageSize,
+                         const sf::Texture& penumbraTexture,
+                         sf::Shader& unshadowShader, sf::Shader& lightOverShapeShader, sf::Shader& normalsShader)
+{
+    _shapeQuadtree.create(rootRegion);
+    _lightPointEmissionQuadtree.create(rootRegion);
+
+    _lightTempTexture.create(imageSize.x, imageSize.y);
+    _emissionTempTexture.create(imageSize.x, imageSize.y);
+    _antumbraTempTexture.create(imageSize.x, imageSize.y);
+    _compositionTexture.create(imageSize.x, imageSize.y);
+    _normalsTexture.create(imageSize.x, imageSize.y);
+
+    normalsTargetClear();
+
+    sf::Vector2f targetSizeInv = sf::Vector2f(1.0f / imageSize.x, 1.0f / imageSize.y);
+
+    unshadowShader.setParameter("penumbraTexture", penumbraTexture);
+
+    lightOverShapeShader.setParameter("emissionTexture", _emissionTempTexture.getTexture());
+    lightOverShapeShader.setParameter("targetSizeInv", targetSizeInv);
+
+    normalsShader.setParameter("normalsTexture", _normalsTexture.getTexture());
+    normalsShader.setParameter("targetSize", imageSize.x, imageSize.y);
+    normalsShader.setParameter("lightTexture", sf::Shader::CurrentTexture);
+}
+
+void LightSystem::render(const sf::View &view, sf::Shader &unshadowShader, sf::Shader &lightOverShapeShader, sf::Shader& normalsShader)
+{
+    _compositionTexture.clear(_ambientColor);
+    _compositionTexture.setView(_compositionTexture.getDefaultView());
+
+    // Get bounding rectangle of view
+    sf::FloatRect viewBounds = sf::FloatRect(view.getCenter().x, view.getCenter().y, 0.0f, 0.0f);
+    sf::FloatRect centeredViewBounds = rectRecenter(viewBounds, sf::Vector2f(0.0f, 0.0f));
+
+    _lightTempTexture.setView(view);
+
+    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(0, 0)));
+    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(_lightTempTexture.getSize().x, 0)));
+    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(_lightTempTexture.getSize().x, _lightTempTexture.getSize().y)));
+    viewBounds = rectExpand(viewBounds, _lightTempTexture.mapPixelToCoords(sf::Vector2i(0, _lightTempTexture.getSize().y)));
+
+    std::vector<QuadtreeOccupant*> viewPointEmissionLights;
+
+    _lightPointEmissionQuadtree.queryRegion(viewPointEmissionLights, viewBounds);
+
+    sf::RenderStates compoRenderStates;
+    compoRenderStates.blendMode = sf::BlendAdd;
+
+    //----- Point lights
+
+    std::vector<QuadtreeOccupant*> lightShapes;
+    sf::Sprite lightTempSprite(_lightTempTexture.getTexture());
+
+    for (auto occupant : viewPointEmissionLights) {
+        auto pPointEmissionLight = static_cast<LightPointEmission*>(occupant);
+
+        // Query shapes this light is affected by
+        lightShapes.clear();
+        _shapeQuadtree.queryRegion(lightShapes, pPointEmissionLight->getAABB());
+
+        pPointEmissionLight->render(view, _lightTempTexture, _emissionTempTexture, _antumbraTempTexture, lightShapes, unshadowShader, lightOverShapeShader, _normalsEnabled, normalsShader);
+        _compositionTexture.draw(lightTempSprite, compoRenderStates);
+    }
+
+    //----- Direction lights
+
+    for (const auto& directionEmissionLight : _directionEmissionLights) {
+        LightDirectionEmission* pDirectionEmissionLight = directionEmissionLight.get();
+
+        float maxDim = std::max(centeredViewBounds.width, centeredViewBounds.height);
+        sf::FloatRect extendedViewBounds = rectFromBounds(sf::Vector2f(-maxDim, -maxDim) * _directionEmissionRadiusMultiplier,
+                                                          sf::Vector2f(maxDim, maxDim) * _directionEmissionRadiusMultiplier + sf::Vector2f(_directionEmissionRange, 0.0f));
+        float shadowExtension = vectorMagnitude(rectLowerBound(centeredViewBounds)) * _directionEmissionRadiusMultiplier * 2.0f;
+
+        sf::ConvexShape directionShape = shapeFromRect(extendedViewBounds);
+        directionShape.setPosition(view.getCenter());
+
+        sf::Vector2f normalizedCastDirection = vectorNormalize(pDirectionEmissionLight->_castDirection);
+        directionShape.setRotation(_radToDeg * std::atan2(normalizedCastDirection.y, normalizedCastDirection.x));
+
+        std::vector<QuadtreeOccupant*> viewLightShapes;
+        _shapeQuadtree.queryShape(viewLightShapes, directionShape);
+
+        pDirectionEmissionLight->render(view, _lightTempTexture, _antumbraTempTexture, viewLightShapes, unshadowShader, shadowExtension);
+
+        sf::Sprite sprite;
+        sprite.setTexture(_lightTempTexture.getTexture());
+        _compositionTexture.draw(sprite, compoRenderStates);
+
+        // TODO Normals
+    }
+
+    _compositionTexture.display();
+}
+
+LightShape* LightSystem::allocateShape()
+{
+    return _lightShapesPool.newElement();
+}
+
+void LightSystem::deallocateShape(LightShape* pLightShape)
+{
+    _lightShapesPool.deleteElement(pLightShape);
+}
+
+void LightSystem::addShape(LightShape* pLightShape)
+{
+    _shapeQuadtree.add(pLightShape);
+}
+
+void LightSystem::removeShape(LightShape* pLightShape)
+{
+    pLightShape->quadtreeRemove();
+}
+
+void LightSystem::addLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight) {
+    _lightPointEmissionQuadtree.add(pointEmissionLight.get());
+    _pointEmissionLights.insert(pointEmissionLight);
+}
+
+void LightSystem::addLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight) {
+    _directionEmissionLights.insert(directionEmissionLight);
+}
+
+void LightSystem::removeLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight) {
+    std::unordered_set<std::shared_ptr<LightPointEmission>>::iterator it = _pointEmissionLights.find(pointEmissionLight);
+
+    if (it != _pointEmissionLights.end()) {
+        (*it)->quadtreeRemove();
+
+        _pointEmissionLights.erase(it);
+    }
+}
+
+void LightSystem::removeLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight) {
+    std::unordered_set<std::shared_ptr<LightDirectionEmission>>::iterator it = _directionEmissionLights.find(directionEmissionLight);
+    if (it != _directionEmissionLights.end())
+        _directionEmissionLights.erase(it);
+}
+
+//----- Normals -----//
+
+void LightSystem::normalsEnabled(bool enabled)
+{
+    _normalsEnabled = enabled;
+}
+
+void LightSystem::normalsTargetSetView(sf::View view)
+{
+    _normalsTexture.setView(view);
+}
+
+void LightSystem::normalsTargetClear()
+{
+    _normalsTexture.clear(sf::Color{127u, 127u, 255u});
+}
+
+void LightSystem::normalsTargetDisplay()
+{
+    _normalsTexture.display();
+}
+
+void LightSystem::normalsTargetDraw(const sf::Drawable& drawable, sf::RenderStates states)
+{
+    _normalsTexture.draw(drawable, states);
+}

+ 114 - 0
ltbl/lighting/LightSystem.h

@@ -0,0 +1,114 @@
+#pragma once
+
+#include <ltbl/quadtree/DynamicQuadtree.h>
+#include <ltbl/lighting/LightPointEmission.h>
+#include <ltbl/lighting/LightDirectionEmission.h>
+#include <ltbl/lighting/LightShape.h>
+#include <ltbl/lighting/NormalsSprite.h>
+
+#include <ltbl/tools/pool.h>
+
+#include <unordered_set>
+
+namespace ltbl
+{
+    class LightSystem : sf::NonCopyable
+    {
+        friend class LightPointEmission;
+        friend class LightDirectionEmission;
+        friend class LightShape;
+
+    public:
+        struct Penumbra {
+            sf::Vector2f _source;
+            sf::Vector2f _lightEdge;
+            sf::Vector2f _darkEdge;
+            float _lightBrightness;
+            float _darkBrightness;
+
+            float _distance;
+        };
+
+    private:
+        sf::RenderTexture _lightTempTexture, _emissionTempTexture, _antumbraTempTexture, _compositionTexture, _normalsTexture;
+
+        static void getPenumbrasPoint(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceCenter, float sourceRadius);
+        static void getPenumbrasDirection(std::vector<Penumbra> &penumbras, std::vector<int> &innerBoundaryIndices, std::vector<sf::Vector2f> &innerBoundaryVectors, std::vector<int> &outerBoundaryIndices, std::vector<sf::Vector2f> &outerBoundaryVectors, const sf::ConvexShape &shape, const sf::Vector2f &sourceDirection, float sourceRadius, float sourceDistance);
+
+        DynamicQuadtree _shapeQuadtree;
+        DynamicQuadtree _lightPointEmissionQuadtree;
+
+        std::unordered_set<std::shared_ptr<LightPointEmission>> _pointEmissionLights;
+        std::unordered_set<std::shared_ptr<LightDirectionEmission>> _directionEmissionLights;
+
+        //! Pool for light shapes.
+        //! The idea is that even if there will be cache misses (because of the QuadTree),
+        //! shapes are more likely to be in the CPU L2 or L3 cache.
+        //! We currently allow 2048 shapes maximum.
+        MemoryPool<LightShape, sizeof(LightShape) * 2048u> _lightShapesPool;
+
+    public:
+        float _directionEmissionRange;
+        float _directionEmissionRadiusMultiplier;
+        sf::Color _ambientColor;
+        bool _normalsEnabled = false;
+
+        LightSystem()
+            : _directionEmissionRange(10000.0f), _directionEmissionRadiusMultiplier(1.1f), _ambientColor(sf::Color(16, 16, 16))
+        {}
+
+        void create(const sf::FloatRect& rootRegion, const sf::Vector2u& imageSize,
+                    const sf::Texture& penumbraTexture,
+                    sf::Shader& unshadowShader, sf::Shader& lightOverShapeShader, sf::Shader& normalsShader);
+
+        void render(const sf::View &view,
+                    sf::Shader &unshadowShader, sf::Shader &lightOverShapeShader, sf::Shader& normalsShader);
+
+        //! Request a new shape from the pool.
+        LightShape* allocateShape();
+
+        //! Destroy a previously allocated shape from the pool.
+        void deallocateShape(LightShape* pLightShape);
+
+        //! Add the shape to the quadtree.
+        void addShape(LightShape* pLightShape);
+
+        //! Remove the shape from the quadtree.
+        void removeShape(LightShape* pLightShape);
+
+        void addLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight);
+        void addLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight);
+
+        void removeLight(const std::shared_ptr<LightPointEmission> &pointEmissionLight);
+        void removeLight(const std::shared_ptr<LightDirectionEmission> &directionEmissionLight);
+
+        void trimLightPointEmissionQuadtree() {
+            _lightPointEmissionQuadtree.trim();
+        }
+
+        void trimShapeQuadtree() {
+            _shapeQuadtree.trim();
+        }
+
+        const sf::Texture &getLightingTexture() const {
+            return _compositionTexture.getTexture();
+        }
+
+        //----- Normals -----//
+
+        //! Whether the light system should consider the normals target.
+        void normalsEnabled(bool enabled);
+
+        //! Set the normals texture view.
+        void normalsTargetSetView(sf::View view);
+
+        //! Clear the normals texture.
+        void normalsTargetClear();
+
+        //! Display the normals target.
+        void normalsTargetDisplay();
+
+        //! Draw a sf::Drawable (usually a Sprite containing the normals texture) into the texture target.
+        void normalsTargetDraw(const sf::Drawable& drawable, sf::RenderStates states = sf::RenderStates());
+    };
+}

+ 24 - 0
ltbl/lighting/NormalsSprite.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <ltbl/quadtree/QuadtreeOccupant.h>
+
+namespace ltbl
+{
+    //! A sprite reaction to lights.
+
+    class NormalsSprite : public QuadtreeOccupant
+    {
+    public:
+
+        NormalsSprite() {}
+
+        inline sf::FloatRect getAABB() const
+        {
+            return _normalsSprite.getGlobalBounds();
+        }
+
+    public:
+
+        sf::Sprite _normalsSprite;
+    };
+}

+ 124 - 0
ltbl/quadtree/DynamicQuadtree.cpp

@@ -0,0 +1,124 @@
+#include "ltbl/quadtree/DynamicQuadtree.h"
+#include <cassert>
+
+using namespace ltbl;
+
+void DynamicQuadtree::add(QuadtreeOccupant* oc) {
+    assert(created());
+
+    // If the occupant fits in the root node
+    if (rectContains(_pRootNode->getRegion(), oc->getAABB()))
+        _pRootNode->add(oc);
+    else
+        _outsideRoot.insert(oc);
+
+    setQuadtree(oc);
+}
+
+void DynamicQuadtree::expand() {
+    // Find direction with most occupants
+    sf::Vector2f averageDir(0.0f, 0.0f);
+
+    for (const auto& occupant :  _outsideRoot)
+        averageDir += vectorNormalize(rectCenter(occupant->getAABB()) - rectCenter(_pRootNode->getRegion()));
+
+    sf::Vector2f centerOffsetDist(rectHalfDims(_pRootNode->getRegion()) / _oversizeMultiplier); 
+
+    sf::Vector2f centerOffset((averageDir.x > 0.0f ? 1.0f : -1.0f) * centerOffsetDist.x, (averageDir.y > 0.0f ? 1.0f : -1.0f) * centerOffsetDist.y);
+
+    // Child node position of current root node
+    int rX = centerOffset.x > 0.0f ? 0 : 1;
+    int rY = centerOffset.y > 0.0f ? 0 : 1;
+
+    sf::FloatRect newRootAABB = rectFromBounds(sf::Vector2f(0.0f, 0.0f), centerOffsetDist * 4.0f);
+
+    newRootAABB = rectRecenter(newRootAABB, centerOffset + rectCenter(_pRootNode->getRegion()));
+
+    QuadtreeNode* pNewRoot = new QuadtreeNode(newRootAABB,  _pRootNode->_level + 1, nullptr, this);
+
+    // ----------------------- Manual Children Creation for New Root -------------------------
+
+    sf::Vector2f halfRegionDims = rectHalfDims(pNewRoot->_region);
+    sf::Vector2f regionLowerBound = rectLowerBound(pNewRoot->_region);
+    sf::Vector2f regionCenter = rectCenter(pNewRoot->_region);
+
+    // Create the children nodes
+    for(int x = 0; x < 2; x++)
+        for(int y = 0; y < 2; y++) {
+            if(x == rX && y == rY)
+                pNewRoot->_children[x + y * 2].reset(_pRootNode.release());
+            else {
+                sf::Vector2f offset(x * halfRegionDims.x, y * halfRegionDims.y);
+
+                sf::FloatRect childAABB = rectFromBounds(regionLowerBound + offset, regionCenter + offset);
+
+                // Scale up AABB by the oversize multiplier
+                sf::Vector2f center = rectCenter(childAABB);
+
+                childAABB.width *= _oversizeMultiplier;
+                childAABB.height *= _oversizeMultiplier;
+
+                childAABB = rectRecenter(childAABB, center);
+
+                pNewRoot->_children[x + y * 2].reset(new QuadtreeNode(childAABB, _pRootNode->_level, pNewRoot, this));
+            }
+        }
+
+    pNewRoot->_hasChildren = true;
+    pNewRoot->_numOccupantsBelow = _pRootNode->_numOccupantsBelow;
+    _pRootNode->_pParent = pNewRoot;
+
+    // Transfer ownership
+    _pRootNode.release();
+    _pRootNode.reset(pNewRoot);
+
+    // ----------------------- Try to Add Previously Outside Root -------------------------
+
+    // Make copy so don't try to re-add ones just added
+    std::unordered_set<QuadtreeOccupant*> outsideRootCopy(_outsideRoot);
+    _outsideRoot.clear();
+
+    for (auto& occupant : outsideRootCopy)
+        add(occupant);
+}
+
+void DynamicQuadtree::contract() {
+    assert(_pRootNode->_hasChildren);
+
+    // Find child with the most occupants and shrink to that
+    int maxIndex = 0;
+
+    for (int i = 1; i < 4; i++)
+        if (_pRootNode->_children[i]->getNumOccupantsBelow() >
+            _pRootNode->_children[maxIndex]->getNumOccupantsBelow())
+            maxIndex = i;
+
+    // Reorganize
+    for (int i = 0; i < 4; i++) {
+        if (i == maxIndex)
+            continue;
+
+        _pRootNode->_children[i]->removeForDeletion(_outsideRoot);
+    }
+
+    QuadtreeNode* pNewRoot = _pRootNode->_children[maxIndex].release();
+
+    _pRootNode->destroyChildren();
+
+    _pRootNode->removeForDeletion(_outsideRoot);
+
+    _pRootNode.reset(pNewRoot);
+
+    _pRootNode->_pParent = nullptr;
+}
+
+void DynamicQuadtree::trim() {
+    if(_pRootNode.get() == nullptr)
+        return;
+
+    // Check if should grow
+    if(_outsideRoot.size() > _maxOutsideRoot)
+        expand();
+    else if(_outsideRoot.size() < _minOutsideRoot && _pRootNode->_hasChildren)
+        contract();
+}

+ 47 - 0
ltbl/quadtree/DynamicQuadtree.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include <ltbl/quadtree/Quadtree.h>
+
+namespace ltbl {
+    class DynamicQuadtree : public Quadtree {
+    private:
+        void expand();
+        void contract();
+
+    public:
+        size_t _minOutsideRoot;
+        size_t _maxOutsideRoot;
+
+        DynamicQuadtree()
+            : _minOutsideRoot(1), _maxOutsideRoot(8)
+        {}
+
+        DynamicQuadtree(const sf::FloatRect &rootRegion)
+            : _minOutsideRoot(1), _maxOutsideRoot(8)
+        {
+            _pRootNode = std::unique_ptr<QuadtreeNode>(new QuadtreeNode(rootRegion, 0, nullptr, this));
+        }
+
+        void create(const sf::FloatRect &rootRegion) {
+            _pRootNode = std::unique_ptr<QuadtreeNode>(new QuadtreeNode(rootRegion, 0, nullptr, this));
+        }
+
+        // Inherited from Quadtree
+        void add(QuadtreeOccupant* oc);
+
+        void clear() {
+            _pRootNode.reset();
+        }
+
+        // Resizes Quadtree
+        void trim();
+
+        bool created() const {
+            return _pRootNode != nullptr;
+        }
+
+        const sf::FloatRect &getRootRegion() const {
+            return _pRootNode->getRegion();
+        }
+    };
+}

+ 158 - 0
ltbl/quadtree/Quadtree.cpp

@@ -0,0 +1,158 @@
+#include "ltbl/quadtree/Quadtree.h"
+#include <algorithm>
+#include <assert.h>
+
+using namespace ltbl;
+
+Quadtree::Quadtree()
+    : _minNumNodeOccupants(3),
+      _maxNumNodeOccupants(6),
+      _maxLevels(40),
+      _oversizeMultiplier(1.0f)
+{}
+
+void Quadtree::operator=(const Quadtree &other) {
+    _minNumNodeOccupants = other._minNumNodeOccupants;
+    _maxNumNodeOccupants = other._maxNumNodeOccupants;
+    _maxLevels = other._maxLevels;
+    _oversizeMultiplier = other._oversizeMultiplier;
+
+    _outsideRoot = other._outsideRoot;
+
+    if (other._pRootNode != nullptr) {
+        _pRootNode.reset(new QuadtreeNode());
+
+        recursiveCopy(_pRootNode.get(), other._pRootNode.get(), nullptr);
+    }
+}
+
+void Quadtree::setQuadtree(QuadtreeOccupant* oc) {
+    oc->_pQuadtree = this;
+}
+
+void Quadtree::recursiveCopy(QuadtreeNode* pThisNode, QuadtreeNode* pOtherNode, QuadtreeNode* pThisParent) {
+    pThisNode->_hasChildren = pOtherNode->_hasChildren;
+    pThisNode->_level = pOtherNode->_level;
+    pThisNode->_numOccupantsBelow = pOtherNode->_numOccupantsBelow;
+    pThisNode->_occupants = pOtherNode->_occupants;
+    pThisNode->_region = pOtherNode->_region;
+
+    pThisNode->_pParent = pThisParent;
+
+    pThisNode->_pQuadtree = this;
+
+    if (pThisNode->_hasChildren)
+        for (int i = 0; i < 4; i++) {
+            pThisNode->_children[i].reset(new QuadtreeNode());
+
+            recursiveCopy(pThisNode->_children[i].get(), pOtherNode->_children[i].get(), pThisNode);
+        }
+}
+
+void Quadtree::queryRegion(std::vector<QuadtreeOccupant*> &result, const sf::FloatRect &region) {
+    // Query outside root elements
+    for (auto oc : _outsideRoot) {
+        // Intersects, add to list
+        if (oc != nullptr && region.intersects(oc->getAABB()))
+            result.push_back(oc);
+    }
+
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(_pRootNode.get());
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        if (region.intersects(pCurrent->_region)) {
+            for (auto oc : pCurrent->_occupants) {
+                // Visible, add to list
+                if (oc != nullptr && region.intersects(oc->getAABB()))
+                    result.push_back(oc);
+            }
+
+            // Add children to open list if they intersect the region
+            if (pCurrent->_hasChildren)
+                for (int i = 0; i < 4; i++)
+                    if (pCurrent->_children[i]->getNumOccupantsBelow() != 0)
+                        open.push_back(pCurrent->_children[i].get());
+        }
+    }
+}
+
+void Quadtree::queryPoint(std::vector<QuadtreeOccupant*> &result, const sf::Vector2f &p) {
+    // Query outside root elements
+    for (std::unordered_set<QuadtreeOccupant*>::iterator it = _outsideRoot.begin(); it != _outsideRoot.end(); it++) {
+        QuadtreeOccupant* oc = *it;
+
+        if (oc != nullptr && oc->getAABB().contains(p))
+            // Intersects, add to list
+            result.push_back(oc);
+    }
+
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(_pRootNode.get());
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        if (pCurrent->_region.contains(p)) {
+            for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++) {
+                QuadtreeOccupant* oc = *it;
+
+                if (oc != nullptr && oc->getAABB().contains(p))
+                    // Visible, add to list
+                    result.push_back(oc);
+            }
+
+            // Add children to open list if they intersect the region
+            if (pCurrent->_hasChildren)
+                for (int i = 0; i < 4; i++)
+                    if (pCurrent->_children[i]->getNumOccupantsBelow() != 0)
+                        open.push_back(pCurrent->_children[i].get());
+        }
+    }
+}
+
+void Quadtree::queryShape(std::vector<QuadtreeOccupant*> &result, const sf::ConvexShape &shape) {
+    // Query outside root elements
+    for (std::unordered_set<QuadtreeOccupant*>::iterator it = _outsideRoot.begin(); it != _outsideRoot.end(); it++) {
+        QuadtreeOccupant* oc = *it;
+
+        if (oc != nullptr && shapeIntersection(shapeFromRect(oc->getAABB()), shape))
+            // Intersects, add to list
+            result.push_back(oc);
+    }
+
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(_pRootNode.get());
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        if (shapeIntersection(shapeFromRect(pCurrent->_region), shape)) {
+            for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++) {
+                QuadtreeOccupant* oc = *it;
+                sf::ConvexShape r = shapeFromRect(oc->getAABB());
+
+                if (oc != nullptr && shapeIntersection(shapeFromRect(oc->getAABB()), shape))
+                    // Visible, add to list
+                    result.push_back(oc);
+            }
+
+            // Add children to open list if they intersect the region
+            if (pCurrent->_hasChildren)
+                for (int i = 0; i < 4; i++)
+                    if (pCurrent->_children[i]->getNumOccupantsBelow() != 0)
+                        open.push_back(pCurrent->_children[i].get());
+        }
+    }
+}

+ 57 - 0
ltbl/quadtree/Quadtree.h

@@ -0,0 +1,57 @@
+#pragma once
+
+#include <ltbl/quadtree/QuadtreeNode.h>
+
+#include <memory>
+
+#include <unordered_set>
+#include <list>
+
+#include <mutex>
+#include <thread>
+
+namespace ltbl {
+    class QuadtreeOccupant;
+
+    // Base class for dynamic and static Quadtree types
+    class Quadtree {
+        friend class QuadtreeOccupant;
+        friend class QuadtreeNode;
+        friend class SceneObject;
+
+    protected:
+        std::unordered_set<QuadtreeOccupant*> _outsideRoot;
+
+        std::unique_ptr<QuadtreeNode> _pRootNode;
+
+        // Called whenever something is removed, an action can be defined by derived classes
+        // Defaults to doing nothing
+        virtual void onRemoval() {}
+
+        void setQuadtree(QuadtreeOccupant* oc);
+
+        void recursiveCopy(QuadtreeNode* pThisNode, QuadtreeNode* pOtherNode, QuadtreeNode* pThisParent);
+
+    public:
+        size_t _minNumNodeOccupants;
+        size_t _maxNumNodeOccupants;
+        size_t _maxLevels;
+
+        float _oversizeMultiplier;
+
+        Quadtree();
+        Quadtree(const Quadtree &other) {
+            *this = other;
+        }
+
+        virtual ~Quadtree() {}
+
+        void operator=(const Quadtree &other);
+
+        virtual void add(QuadtreeOccupant* oc) = 0;
+
+        void queryRegion(std::vector<QuadtreeOccupant*> &result, const sf::FloatRect &region);
+        void queryPoint(std::vector<QuadtreeOccupant*> &result, const sf::Vector2f &p);
+        void queryShape(std::vector<QuadtreeOccupant*> &result, const sf::ConvexShape &shape);
+    };
+}

+ 284 - 0
ltbl/quadtree/QuadtreeNode.cpp

@@ -0,0 +1,284 @@
+#include "ltbl/quadtree/QuadtreeNode.h"
+#include "ltbl/quadtree/Quadtree.h"
+#include <cassert>
+
+using namespace ltbl;
+
+QuadtreeNode::QuadtreeNode(const sf::FloatRect &region, int level, QuadtreeNode* pParent, Quadtree* pQuadtree)
+    : _pParent(pParent)
+    , _pQuadtree(pQuadtree)
+    , _region(region)
+    , _level(level)
+{}
+
+void QuadtreeNode::create(const sf::FloatRect &region, int level, QuadtreeNode* pParent, Quadtree* pQuadtree) {
+    _hasChildren = false;
+
+    _region = region;
+    _level = level;
+    _pParent = pParent;
+    _pQuadtree = pQuadtree;
+}
+
+void QuadtreeNode::getPossibleOccupantPosition(QuadtreeOccupant* oc, sf::Vector2i &point) {
+    // Compare the center of the AABB of the occupant to that of this node to determine
+    // which child it may (possibly, not certainly) fit in
+    const sf::Vector2f &occupantCenter = rectCenter(oc->getAABB());
+    const sf::Vector2f &nodeRegionCenter = rectCenter(_region);
+
+    point.x = occupantCenter.x > nodeRegionCenter.x ? 1 : 0;
+    point.y = occupantCenter.y > nodeRegionCenter.y ? 1 : 0;
+}
+
+void QuadtreeNode::addToThisLevel(QuadtreeOccupant* oc) {
+    oc->_pQuadtreeNode = this;
+
+    if (_occupants.find(oc) != _occupants.end())
+        return;
+
+    _occupants.insert(oc);
+}
+
+bool QuadtreeNode::addToChildren(QuadtreeOccupant* oc) {
+    assert(_hasChildren);
+
+    sf::Vector2i position;
+
+    getPossibleOccupantPosition(oc, position);
+
+    QuadtreeNode* pChild = _children[position.x + position.y * 2].get();
+
+    // See if the occupant fits in the child at the selected position
+    if (rectContains(pChild->_region, oc->getAABB())) {
+        // Fits, so can add to the child and finish
+        pChild->add(oc);
+
+        return true;
+    }
+
+    return false;
+}
+
+void QuadtreeNode::partition() {
+    assert(!_hasChildren);
+
+    sf::Vector2f halfRegionDims = rectHalfDims(_region);
+    sf::Vector2f regionLowerBound = rectLowerBound(_region);
+    sf::Vector2f regionCenter = rectCenter(_region);
+
+    int nextLowerLevel = _level - 1;
+
+    for (int x = 0; x < 2; x++)
+        for (int y = 0; y < 2; y++) {
+            sf::Vector2f offset(x * halfRegionDims.x, y * halfRegionDims.y);
+
+            sf::FloatRect childAABB = rectFromBounds(regionLowerBound + offset, regionCenter + offset);
+
+            // Scale up AABB by the oversize multiplier
+            sf::Vector2f newHalfDims = rectHalfDims(childAABB);
+            sf::Vector2f center = rectCenter(childAABB);
+            childAABB = rectFromBounds(center - newHalfDims, center + newHalfDims);
+
+            _children[x + y * 2].reset(new QuadtreeNode(childAABB, nextLowerLevel, this, _pQuadtree));
+        }
+
+    _hasChildren = true;
+}
+
+void QuadtreeNode::merge() {
+    if (_hasChildren) {
+        // Place all occupants at lower levels into this node
+        getOccupants(_occupants);
+
+        destroyChildren();
+    }
+}
+
+void QuadtreeNode::getOccupants(std::unordered_set<QuadtreeOccupant*> &occupants) {
+    // Iteratively parse subnodes in order to collect all occupants below this node
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(this);
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        // Get occupants
+        for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++)
+            if ((*it) != nullptr) {
+                // Assign new node
+                (*it)->_pQuadtreeNode = this;
+
+                // Add to this node
+                occupants.insert(*it);
+            }
+
+        // If the node has children, add them to the open list
+        if (pCurrent->_hasChildren)
+            for (int i = 0; i < 4; i++)
+                open.push_back(pCurrent->_children[i].get());
+    }
+}
+
+void QuadtreeNode::removeForDeletion(std::unordered_set<QuadtreeOccupant*> &occupants) {
+    // Iteratively parse subnodes in order to collect all occupants below this node
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(this);
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        // Get occupants
+        for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++)
+            if ((*it) != nullptr) {
+                // Since will be deleted, remove the reference
+                (*it)->_pQuadtreeNode = nullptr;
+
+                // Add to this node
+                occupants.insert(*it);
+            }
+
+        // If the node has children, add them to the open list
+        if (pCurrent->_hasChildren)
+            for (int i = 0; i < 4; i++)
+                open.push_back(pCurrent->_children[i].get());
+    }
+}
+
+void QuadtreeNode::getAllOccupantsBelow(std::vector<QuadtreeOccupant*> &occupants) {
+    // Iteratively parse subnodes in order to collect all occupants below this node
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(this);
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        // Get occupants
+        for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++)
+            if ((*it) != nullptr)
+                // Add to this node
+                occupants.push_back(*it);
+
+        // If the node has children, add them to the open list
+        if (pCurrent->_hasChildren)
+            for (int i = 0; i < 4; i++)
+                open.push_back(pCurrent->_children[i].get());
+    }
+}
+
+void QuadtreeNode::getAllOccupantsBelow(std::unordered_set<QuadtreeOccupant*> &occupants) {
+    // Iteratively parse subnodes in order to collect all occupants below this node
+    std::list<QuadtreeNode*> open;
+
+    open.push_back(this);
+
+    while (!open.empty()) {
+        // Depth-first (results in less memory usage), remove objects from open list
+        QuadtreeNode* pCurrent = open.back();
+        open.pop_back();
+
+        // Get occupants
+        for (std::unordered_set<QuadtreeOccupant*>::iterator it = pCurrent->_occupants.begin(); it != pCurrent->_occupants.end(); it++)
+            if ((*it) == nullptr)
+                // Add to this node
+                occupants.insert(*it);
+
+        // If the node has children, add them to the open list
+        if (pCurrent->_hasChildren)
+            for (int i = 0; i < 4; i++)
+                open.push_back(pCurrent->_children[i].get());
+    }
+}
+
+void QuadtreeNode::update(QuadtreeOccupant* oc) {
+    if (oc == nullptr)
+        return;
+
+    if (!_occupants.empty())
+        // Remove, may be re-added to this node later
+        _occupants.erase(oc);
+
+    // Propogate upwards, looking for a node that has room (the current one may still have room)
+    QuadtreeNode* pNode = this;
+
+    while (pNode != nullptr) {
+        pNode->_numOccupantsBelow--;
+
+        // If has room for 1 more, found a spot
+        if (rectContains(pNode->_region, oc->getAABB()))
+            break;
+
+        pNode = pNode->_pParent;
+    }
+
+    // If no node that could contain the occupant was found, add to outside root set
+    if (pNode == nullptr) {
+        assert(_pQuadtree != nullptr);
+
+        if (_pQuadtree->_outsideRoot.find(oc) != _pQuadtree->_outsideRoot.end())
+            return;
+
+        _pQuadtree->_outsideRoot.insert(oc);
+
+        oc->_pQuadtreeNode = nullptr;
+    }
+    else // Add to the selected node
+        pNode->add(oc);
+}
+
+void QuadtreeNode::remove(QuadtreeOccupant* oc) {
+    assert(!_occupants.empty());
+
+    // Remove from node
+    _occupants.erase(oc);
+
+    if (oc == nullptr)
+        return;
+
+    // Propogate upwards, merging if there are enough occupants in the node
+    QuadtreeNode* pNode = this;
+
+    while (pNode != nullptr) {
+        pNode->_numOccupantsBelow--;
+
+        if (pNode->_numOccupantsBelow > 0 && static_cast<size_t>(pNode->_numOccupantsBelow) >= _pQuadtree->_minNumNodeOccupants) {
+            pNode->merge();
+
+            break;
+        }
+
+        pNode = pNode->_pParent;
+    }
+}
+
+void QuadtreeNode::add(QuadtreeOccupant* oc) {
+    assert(oc != nullptr);
+
+    _numOccupantsBelow++;
+
+    // See if the occupant fits into any children (if there are any)
+    if (_hasChildren) {
+        if (addToChildren(oc))
+            return; // Fit, can stop
+    }
+    else {
+        // Check if we need a new partition
+        if (_occupants.size() >= _pQuadtree->_maxNumNodeOccupants && static_cast<size_t>(_level) < _pQuadtree->_maxLevels) {
+            partition();
+
+            if (addToChildren(oc))
+                return;
+        }
+    }
+
+    // Did not fit in anywhere, add to this level, even if it goes over the maximum size
+    addToThisLevel(oc);
+}

+ 83 - 0
ltbl/quadtree/QuadtreeNode.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include <ltbl/quadtree/QuadtreeOccupant.h>
+
+#include <memory>
+#include <array>
+#include <unordered_set>
+
+namespace ltbl {
+    class QuadtreeNode : public sf::NonCopyable {
+
+        friend class QuadtreeOccupant;
+        friend class Quadtree;
+        friend class DynamicQuadtree;
+
+    private:
+        QuadtreeNode* _pParent;
+        class Quadtree* _pQuadtree;
+
+        bool _hasChildren = false;
+
+        std::array<std::unique_ptr<QuadtreeNode>, 4> _children;
+        std::unordered_set<QuadtreeOccupant*> _occupants;
+
+        sf::FloatRect _region;
+
+        int _level;
+
+        int _numOccupantsBelow = 0;
+
+        void getPossibleOccupantPosition(QuadtreeOccupant* oc, sf::Vector2i &point);
+
+        void addToThisLevel(QuadtreeOccupant* oc);
+
+        // Returns true if occupant was added to children
+        bool addToChildren(QuadtreeOccupant* oc);
+
+        void destroyChildren() {
+            for (int i = 0; i < 4; i++)
+                _children[i].reset();
+
+            _hasChildren = false;
+        }
+
+        void getOccupants(std::unordered_set<QuadtreeOccupant*> &occupants);
+
+        void partition();
+
+        void merge();
+
+        void update(QuadtreeOccupant* oc);
+        void remove(QuadtreeOccupant* oc);
+
+        void removeForDeletion(std::unordered_set<QuadtreeOccupant*> &occupants);
+
+    public:
+        QuadtreeNode()
+            : _hasChildren(false), _numOccupantsBelow(0)
+        {}
+
+        QuadtreeNode(const sf::FloatRect &region, int level, QuadtreeNode* pParent, class Quadtree* pQuadtree);
+
+        // For use after using default constructor
+        void create(const sf::FloatRect &region, int level, QuadtreeNode* pParent, class Quadtree* pQuadtree);
+
+        class Quadtree* getTree() const {
+            return _pQuadtree;
+        }
+
+        void add(QuadtreeOccupant* oc);
+
+        const sf::FloatRect &getRegion() const {
+            return _region;
+        }
+
+        void getAllOccupantsBelow(std::vector<QuadtreeOccupant*> &occupants);
+        void getAllOccupantsBelow(std::unordered_set<QuadtreeOccupant*> &occupants);
+
+        int getNumOccupantsBelow() const {
+            return _numOccupantsBelow;
+        }
+    };
+}

+ 23 - 0
ltbl/quadtree/QuadtreeOccupant.cpp

@@ -0,0 +1,23 @@
+#include "ltbl/quadtree/QuadtreeOccupant.h"
+#include "ltbl/quadtree/QuadtreeNode.h"
+#include "ltbl/quadtree/Quadtree.h"
+#include <cassert>
+
+using namespace ltbl;
+
+void QuadtreeOccupant::quadtreeUpdate() {
+    if (_pQuadtreeNode != nullptr)
+        _pQuadtreeNode->update(this);
+    else {
+        _pQuadtree->_outsideRoot.erase(this);
+
+        _pQuadtree->add(this);
+    }
+}
+
+void QuadtreeOccupant::quadtreeRemove() {
+    if (_pQuadtreeNode != nullptr)
+        _pQuadtreeNode->remove(this);
+    else
+        _pQuadtree->_outsideRoot.erase(this);
+}

+ 33 - 0
ltbl/quadtree/QuadtreeOccupant.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <SFML/System.hpp>
+#include <SFML/Graphics.hpp>
+
+#include <ltbl/tools/Math.h>
+
+#include <memory>
+#include <array>
+#include <unordered_set>
+
+namespace ltbl {
+    class QuadtreeOccupant {
+        friend class Quadtree;
+        friend class QuadtreeNode;
+        friend class DynamicQuadtree;
+        friend class StaticQuadtree;
+
+    private:
+        class QuadtreeNode* _pQuadtreeNode;
+        class Quadtree* _pQuadtree;
+
+    public:
+        QuadtreeOccupant()
+            : _pQuadtreeNode(nullptr), _pQuadtree(nullptr)
+        {}
+
+        void quadtreeUpdate();
+        void quadtreeRemove();
+
+        virtual sf::FloatRect getAABB() const = 0;
+    };
+}

+ 16 - 0
ltbl/quadtree/StaticQuadtree.cpp

@@ -0,0 +1,16 @@
+#include "ltbl/quadtree/StaticQuadtree.h"
+#include <cassert>
+
+using namespace ltbl;
+
+void StaticQuadtree::add(QuadtreeOccupant* oc) {
+    assert(created());
+
+    setQuadtree(oc);
+
+    // If the occupant fits in the root node
+    if (rectContains(_pRootNode->getRegion(), oc->getAABB()))
+        _pRootNode->add(oc);
+    else
+        _outsideRoot.insert(oc);
+}

+ 34 - 0
ltbl/quadtree/StaticQuadtree.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <ltbl/quadtree/Quadtree.h>
+
+namespace ltbl
+{
+    class StaticQuadtree : public Quadtree
+    {
+    public:
+        StaticQuadtree() {}
+        StaticQuadtree(const sf::FloatRect &rootRegion) {
+            _pRootNode.reset(new QuadtreeNode(rootRegion, 0, nullptr, this));
+        }
+
+        void create(const sf::FloatRect &rootRegion) {
+            _pRootNode.reset(new QuadtreeNode(rootRegion, 0, nullptr, this));
+        }
+
+        // Inherited from Quadtree
+        void add(QuadtreeOccupant* oc);
+
+        void clear() {
+            _pRootNode.reset();
+        }
+
+        const sf::FloatRect &getRootRegion() const {
+            return _pRootNode->getRegion();
+        }
+
+        bool created() const {
+            return _pRootNode != nullptr;
+        }
+    };
+}

+ 268 - 0
ltbl/tools/Math.cpp

@@ -0,0 +1,268 @@
+#include "ltbl/tools/Math.h"
+#include <list>
+#include <assert.h>
+#include <cmath>
+
+using namespace ltbl;
+
+sf::Vector2f ltbl::rectCenter(const sf::FloatRect &rect) {
+    return sf::Vector2f(rect.left + rect.width * 0.5f, rect.top + rect.height * 0.5f);
+}
+
+bool ltbl::rectIntersects(const sf::FloatRect &rect, const sf::FloatRect &other) {
+    if (rect.left + rect.width < other.left)
+        return false;
+    if (rect.top + rect.height < other.top)
+        return false;
+    if (rect.left > other.left + other.width)
+        return false;
+    if (rect.top > other.top + other.height)
+        return false;
+
+    return true;
+}
+
+bool ltbl::rectContains(const sf::FloatRect &rect, const sf::FloatRect &other) {
+    if (other.left < rect.left)
+        return false;
+    if (other.top < rect.top)
+        return false;
+    if (other.left + other.width > rect.left + rect.width)
+        return false;
+    if (other.top + other.height > rect.top + rect.height)
+        return false;
+
+    return true;
+}
+
+sf::Vector2f ltbl::rectHalfDims(const sf::FloatRect &rect) {
+    return sf::Vector2f(rect.width * 0.5f, rect.height * 0.5f);
+}
+
+sf::Vector2f ltbl::rectDims(const sf::FloatRect &rect) {
+    return sf::Vector2f(rect.width, rect.height);
+}
+
+sf::Vector2f ltbl::rectLowerBound(const sf::FloatRect &rect) {
+    return sf::Vector2f(rect.left, rect.top);
+}
+
+sf::Vector2f ltbl::rectUpperBound(const sf::FloatRect &rect) {
+    return sf::Vector2f(rect.left + rect.width, rect.top + rect.height);
+}
+
+sf::FloatRect ltbl::rectFromBounds(const sf::Vector2f &lowerBound, const sf::Vector2f &upperBound) {
+    return sf::FloatRect(lowerBound.x, lowerBound.y, upperBound.x - lowerBound.x, upperBound.y - lowerBound.y);
+}
+
+float ltbl::vectorMagnitude(const sf::Vector2f &vector) {
+    return std::sqrt(vector.x * vector.x + vector.y * vector.y);
+}
+
+float ltbl::vectorMagnitudeSquared(const sf::Vector2f &vector) {
+    return vector.x * vector.x + vector.y * vector.y;
+}
+
+sf::Vector2f ltbl::vectorNormalize(const sf::Vector2f &vector) {
+    float magnitude = vectorMagnitude(vector);
+
+    if (magnitude == 0.0f)
+        return sf::Vector2f(1.0f, 0.0f);
+
+    float distInv = 1.0f / magnitude;
+
+    return sf::Vector2f(vector.x * distInv, vector.y * distInv);
+}
+
+float ltbl::vectorProject(const sf::Vector2f &left, const sf::Vector2f &right) {
+    assert(vectorMagnitudeSquared(right) != 0.0f);
+
+    return vectorDot(left, right) / vectorMagnitudeSquared(right);
+}
+
+sf::FloatRect ltbl::rectRecenter(const sf::FloatRect &rect, const sf::Vector2f &center) {
+    sf::Vector2f dims = rectDims(rect);
+
+    return sf::FloatRect(center - rectHalfDims(rect), dims);
+}
+
+float ltbl::vectorDot(const sf::Vector2f &left, const sf::Vector2f &right) {
+    return left.x * right.x + left.y * right.y;
+}
+
+sf::FloatRect ltbl::rectExpand(const sf::FloatRect &rect, const sf::Vector2f &point) {
+    sf::Vector2f lowerBound = rectLowerBound(rect);
+    sf::Vector2f upperBound = rectUpperBound(rect);
+
+    if (point.x < lowerBound.x)
+        lowerBound.x = point.x;
+    else if (point.x > upperBound.x)
+        upperBound.x = point.x;
+
+    if (point.y < lowerBound.y)
+        lowerBound.y = point.y;
+    else if (point.y > upperBound.y)
+        upperBound.y = point.y;
+
+    return rectFromBounds(lowerBound, upperBound);
+}
+
+bool ltbl::shapeIntersection(const sf::ConvexShape &left, const sf::ConvexShape &right) {
+    std::vector<sf::Vector2f> transformedLeft(left.getPointCount());
+
+    for (unsigned i = 0; i < left.getPointCount(); i++)
+        transformedLeft[i] = left.getTransform().transformPoint(left.getPoint(i));
+
+    std::vector<sf::Vector2f> transformedRight(right.getPointCount());
+
+    for (unsigned i = 0; i < right.getPointCount(); i++)
+        transformedRight[i] = right.getTransform().transformPoint(right.getPoint(i));
+
+    for (unsigned i = 0; i < left.getPointCount(); i++) {
+        sf::Vector2f point = transformedLeft[i];
+        sf::Vector2f nextPoint;
+
+        if (i == left.getPointCount() - 1u)
+            nextPoint = transformedLeft[0];
+        else
+            nextPoint = transformedLeft[i + 1];
+
+        sf::Vector2f edge = nextPoint - point;
+
+        // Project points from other shape onto perpendicular
+        sf::Vector2f edgePerpendicular = sf::Vector2f(edge.y, -edge.x);
+
+        float pointProj = vectorProject(point, edgePerpendicular);
+
+        float minRightProj = vectorProject(transformedRight[0], edgePerpendicular);
+
+        for (unsigned j = 1; j < right.getPointCount(); j++) {
+            float proj = vectorProject(transformedRight[j], edgePerpendicular);
+
+            minRightProj = std::min(minRightProj, proj);
+        }
+
+        if (minRightProj > pointProj)
+            return false;
+    }
+
+    for (unsigned i = 0; i < right.getPointCount(); i++) {
+        sf::Vector2f point = transformedRight[i];
+        sf::Vector2f nextPoint;
+
+        if (i == right.getPointCount() - 1u)
+            nextPoint = transformedRight[0];
+        else
+            nextPoint = transformedRight[i + 1];
+
+        sf::Vector2f edge = nextPoint - point;
+
+        // Project points from other shape onto perpendicular
+        sf::Vector2f edgePerpendicular = sf::Vector2f(edge.y, -edge.x);
+
+        float pointProj = vectorProject(point, edgePerpendicular);
+
+        float minRightProj = vectorProject(transformedLeft[0], edgePerpendicular);
+
+        for (unsigned j = 1; j < left.getPointCount(); j++) {
+            float proj = vectorProject(transformedLeft[j], edgePerpendicular);
+
+            minRightProj = std::min(minRightProj, proj);
+        }
+
+        if (minRightProj > pointProj)
+            return false;
+    }
+
+    return true;
+}
+
+sf::ConvexShape ltbl::shapeFromRect(const sf::FloatRect &rect) {
+    sf::ConvexShape shape(4);
+
+    sf::Vector2f halfDims = rectHalfDims(rect);
+
+    shape.setPoint(0, sf::Vector2f(-halfDims.x, -halfDims.y));
+    shape.setPoint(1, sf::Vector2f(halfDims.x, -halfDims.y));
+    shape.setPoint(2, sf::Vector2f(halfDims.x, halfDims.y));
+    shape.setPoint(3, sf::Vector2f(-halfDims.x, halfDims.y));
+
+    shape.setPosition(rectCenter(rect));
+
+    return shape;
+}
+
+sf::ConvexShape ltbl::shapeFixWinding(const sf::ConvexShape &shape) {
+    sf::Vector2f center = sf::Vector2f(0.0f, 0.0f);
+    std::list<sf::Vector2f> points;
+
+    for (unsigned i = 0; i < shape.getPointCount(); i++) {
+        points.push_back(shape.getPoint(i));
+        center += shape.getPoint(i);
+    }
+
+    center /= static_cast<float>(shape.getPointCount());
+
+    // Fix winding
+    sf::Vector2f lastPoint = points.front();
+    points.pop_front();
+
+    std::vector<sf::Vector2f> fixedPoints;
+
+    fixedPoints.push_back(lastPoint);
+
+    while (fixedPoints.size() < shape.getPointCount()) {
+        sf::Vector2f centerToLastPoint = lastPoint - center;
+        sf::Vector2f lastPointDirection = ltbl::vectorNormalize(sf::Vector2f(-centerToLastPoint.y, centerToLastPoint.x));
+
+        float maxD = -999999.0f;
+
+        std::list<sf::Vector2f>::iterator nextPointIt;
+
+        // Get next point
+        for (std::list<sf::Vector2f>::iterator it = points.begin(); it != points.end(); it++) {
+            sf::Vector2f toPointNormalized = ltbl::vectorNormalize(*it - lastPoint);
+
+            float d = ltbl::vectorDot(toPointNormalized, lastPointDirection);
+
+            if (d > maxD) {
+                maxD = d;
+                nextPointIt = it;
+            }
+        }
+
+        fixedPoints.push_back(*nextPointIt);
+
+        points.erase(nextPointIt);
+    }
+
+    sf::ConvexShape fixedShape(shape.getPointCount());
+
+    for (unsigned i = 0; i < shape.getPointCount(); i++)
+        fixedShape.setPoint(i, fixedPoints[i]);
+
+    return fixedShape;
+}
+
+bool ltbl::rayIntersect(const sf::Vector2f &as, const sf::Vector2f &ad, const sf::Vector2f &bs, const sf::Vector2f &bd, sf::Vector2f &intersection) {
+    float dx = bs.x - as.x;
+    float dy = bs.y - as.y;
+    float det = bd.x * ad.y - bd.y * ad.x;
+
+    if (det == 0.0f)
+        return false;
+
+    float u = (dy * bd.x - dx * bd.y) / det;
+
+    if (u < 0.0f)
+        return false;
+
+    float v = (dy * ad.x - dx * ad.y) / det;
+
+    if (v < 0.0f)
+        return false;
+
+    intersection = as + ad * u;
+
+    return true;
+}

+ 28 - 0
ltbl/tools/Math.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <SFML/Graphics.hpp>
+
+namespace ltbl {
+	const float _pi = 3.14159265f;
+	const float _radToDeg = 180.0f / _pi;
+
+	sf::Vector2f rectCenter(const sf::FloatRect &rect);
+	bool rectContains(const sf::FloatRect &rect, const sf::FloatRect &other);
+	bool rectIntersects(const sf::FloatRect &rect, const sf::FloatRect &other);
+	sf::Vector2f rectHalfDims(const sf::FloatRect &rect);
+	sf::Vector2f rectDims(const sf::FloatRect &rect);
+	sf::Vector2f rectLowerBound(const sf::FloatRect &rect);
+	sf::Vector2f rectUpperBound(const sf::FloatRect &rect);
+	sf::FloatRect rectFromBounds(const sf::Vector2f &lowerBound, const sf::Vector2f &upperBound);
+	float vectorMagnitude(const sf::Vector2f &vector);
+	float vectorMagnitudeSquared(const sf::Vector2f &vector);
+	sf::Vector2f vectorNormalize(const sf::Vector2f &vector);
+	float vectorProject(const sf::Vector2f &left, const sf::Vector2f &right);
+	sf::FloatRect rectRecenter(const sf::FloatRect &rect, const sf::Vector2f &center);
+	float vectorDot(const sf::Vector2f &left, const sf::Vector2f &right);
+	sf::FloatRect rectExpand(const sf::FloatRect &rect, const sf::Vector2f &point);
+	bool shapeIntersection(const sf::ConvexShape &left, const sf::ConvexShape &right);
+	sf::ConvexShape shapeFromRect(const sf::FloatRect &rect);
+	sf::ConvexShape shapeFixWinding(const sf::ConvexShape &shape);
+	bool rayIntersect(const sf::Vector2f &as, const sf::Vector2f &ad, const sf::Vector2f &bs, const sf::Vector2f &bd, sf::Vector2f &intersection);
+}

+ 98 - 0
ltbl/tools/pool.h

@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MEMORY_POOL_H
+#define MEMORY_POOL_H
+
+#include <climits>
+#include <cstddef>
+
+template <typename T, size_t BlockSize = 4096>
+class MemoryPool
+{
+public:
+    /* Member types */
+    typedef T               value_type;
+    typedef T*              pointer;
+    typedef T&              reference;
+    typedef const T*        const_pointer;
+    typedef const T&        const_reference;
+    typedef size_t          size_type;
+    typedef ptrdiff_t       difference_type;
+    typedef std::false_type propagate_on_container_copy_assignment;
+    typedef std::true_type  propagate_on_container_move_assignment;
+    typedef std::true_type  propagate_on_container_swap;
+
+    template <typename U> struct rebind {
+        typedef MemoryPool<U> other;
+    };
+
+    /* Member functions */
+    MemoryPool() noexcept;
+    MemoryPool(const MemoryPool& memoryPool) noexcept;
+    MemoryPool(MemoryPool&& memoryPool) noexcept;
+    template <class U> MemoryPool(const MemoryPool<U>& memoryPool) noexcept;
+
+    ~MemoryPool() noexcept;
+
+    MemoryPool& operator=(const MemoryPool& memoryPool) = delete;
+    MemoryPool& operator=(MemoryPool&& memoryPool) noexcept;
+
+    pointer address(reference x) const noexcept;
+    const_pointer address(const_reference x) const noexcept;
+
+    // Can only allocate one object at a time. n and hint are ignored
+    pointer allocate(size_type n = 1, const_pointer hint = 0);
+    void deallocate(pointer p, size_type n = 1);
+
+    size_type max_size() const noexcept;
+
+    template <class U, class... Args> void construct(U* p, Args&&... args);
+    template <class U> void destroy(U* p);
+
+    template <class... Args> pointer newElement(Args&&... args);
+    void deleteElement(pointer p);
+
+private:
+    union Slot_ {
+        value_type element;
+        Slot_* next;
+    };
+
+    typedef char* data_pointer_;
+    typedef Slot_ slot_type_;
+    typedef Slot_* slot_pointer_;
+
+    slot_pointer_ currentBlock_;
+    slot_pointer_ currentSlot_;
+    slot_pointer_ lastSlot_;
+    slot_pointer_ freeSlots_;
+
+    size_type padPointer(data_pointer_ p, size_type align) const noexcept;
+    void allocateBlock();
+
+    static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
+};
+
+#include "pool.inl"
+
+#endif // MEMORY_POOL_H

+ 235 - 0
ltbl/tools/pool.inl

@@ -0,0 +1,235 @@
+/*-
+ * Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef MEMORY_BLOCK_TCC
+#define MEMORY_BLOCK_TCC
+
+
+
+template <typename T, size_t BlockSize>
+inline typename MemoryPool<T, BlockSize>::size_type
+MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
+const noexcept
+{
+    uintptr_t result = reinterpret_cast<uintptr_t>(p);
+    return ((align - result) % align);
+}
+
+
+
+template <typename T, size_t BlockSize>
+MemoryPool<T, BlockSize>::MemoryPool()
+noexcept
+{
+    currentBlock_ = nullptr;
+    currentSlot_ = nullptr;
+    lastSlot_ = nullptr;
+    freeSlots_ = nullptr;
+}
+
+
+
+template <typename T, size_t BlockSize>
+MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
+noexcept :
+    MemoryPool()
+{}
+
+
+
+template <typename T, size_t BlockSize>
+MemoryPool<T, BlockSize>::MemoryPool(MemoryPool&& memoryPool)
+noexcept
+{
+    currentBlock_ = memoryPool.currentBlock_;
+    memoryPool.currentBlock_ = nullptr;
+    currentSlot_ = memoryPool.currentSlot_;
+    lastSlot_ = memoryPool.lastSlot_;
+    freeSlots_ = memoryPool.freeSlots;
+}
+
+
+template <typename T, size_t BlockSize>
+template<class U>
+MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
+noexcept :
+    MemoryPool()
+{}
+
+
+
+template <typename T, size_t BlockSize>
+MemoryPool<T, BlockSize>&
+MemoryPool<T, BlockSize>::operator=(MemoryPool&& memoryPool)
+noexcept
+{
+    if (this != &memoryPool)
+    {
+        std::swap(currentBlock_, memoryPool.currentBlock_);
+        currentSlot_ = memoryPool.currentSlot_;
+        lastSlot_ = memoryPool.lastSlot_;
+        freeSlots_ = memoryPool.freeSlots;
+    }
+    return *this;
+}
+
+
+
+template <typename T, size_t BlockSize>
+MemoryPool<T, BlockSize>::~MemoryPool()
+noexcept
+{
+    slot_pointer_ curr = currentBlock_;
+    while (curr != nullptr) {
+        slot_pointer_ prev = curr->next;
+        operator delete(reinterpret_cast<void*>(curr));
+        curr = prev;
+    }
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline typename MemoryPool<T, BlockSize>::pointer
+MemoryPool<T, BlockSize>::address(reference x)
+const noexcept
+{
+    return &x;
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline typename MemoryPool<T, BlockSize>::const_pointer
+MemoryPool<T, BlockSize>::address(const_reference x)
+const noexcept
+{
+    return &x;
+}
+
+
+
+template <typename T, size_t BlockSize>
+void
+MemoryPool<T, BlockSize>::allocateBlock()
+{
+    // Allocate space for the new block and store a pointer to the previous one
+    data_pointer_ newBlock = reinterpret_cast<data_pointer_>
+            (operator new(BlockSize));
+    reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
+    currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
+    // Pad block body to staisfy the alignment requirements for elements
+    data_pointer_ body = newBlock + sizeof(slot_pointer_);
+    size_type bodyPadding = padPointer(body, alignof(slot_type_));
+    currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
+    lastSlot_ = reinterpret_cast<slot_pointer_>
+            (newBlock + BlockSize - sizeof(slot_type_) + 1);
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline typename MemoryPool<T, BlockSize>::pointer
+MemoryPool<T, BlockSize>::allocate(size_type /*n*/, const_pointer /*hint*/)
+{
+    if (freeSlots_ != nullptr) {
+        pointer result = reinterpret_cast<pointer>(freeSlots_);
+        freeSlots_ = freeSlots_->next;
+        return result;
+    }
+    else {
+        if (currentSlot_ >= lastSlot_)
+            allocateBlock();
+        return reinterpret_cast<pointer>(currentSlot_++);
+    }
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline void
+MemoryPool<T, BlockSize>::deallocate(pointer p, size_type /*n*/)
+{
+    if (p != nullptr) {
+        reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;
+        freeSlots_ = reinterpret_cast<slot_pointer_>(p);
+    }
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline typename MemoryPool<T, BlockSize>::size_type
+MemoryPool<T, BlockSize>::max_size()
+const noexcept
+{
+    size_type maxBlocks = -1 / BlockSize;
+    return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
+}
+
+
+
+template <typename T, size_t BlockSize>
+template <class U, class... Args>
+inline void
+MemoryPool<T, BlockSize>::construct(U* p, Args&&... args)
+{
+    new (p) U (std::forward<Args>(args)...);
+}
+
+
+
+template <typename T, size_t BlockSize>
+template <class U>
+inline void
+MemoryPool<T, BlockSize>::destroy(U* p)
+{
+    p->~U();
+}
+
+
+
+template <typename T, size_t BlockSize>
+template <class... Args>
+inline typename MemoryPool<T, BlockSize>::pointer
+MemoryPool<T, BlockSize>::newElement(Args&&... args)
+{
+    pointer result = allocate();
+    construct<value_type>(result, std::forward<Args>(args)...);
+    return result;
+}
+
+
+
+template <typename T, size_t BlockSize>
+inline void
+MemoryPool<T, BlockSize>::deleteElement(pointer p)
+{
+    if (p != nullptr) {
+        p->~value_type();
+        deallocate(p);
+    }
+}
+
+
+
+#endif // MEMORY_BLOCK_TCC