diff --git a/res/shaders/light_attenuation_shader.frag b/res/shaders/light_attenuation_shader.frag new file mode 100644 index 0000000..d542375 --- /dev/null +++ b/res/shaders/light_attenuation_shader.frag @@ -0,0 +1,26 @@ +uniform vec2 lightPos; + +uniform vec3 lightColor; + +uniform float radius; + +uniform float bleed; + +uniform float linearizeFactor; + +void main() +{ + float dist = length(lightPos - gl_FragCoord.xy); + + float distFromFalloff = radius - dist; + + // Still has absolute falloff point + float attenuation = distFromFalloff * (bleed / pow(dist, 2.0) + linearizeFactor / radius); + + // Optional, clamp it to prevent overcoloring + attenuation = clamp(attenuation, 0.0, 1.0); + + vec4 color = vec4(attenuation, attenuation, attenuation, 1.0) * vec4(lightColor.r, lightColor.g, lightColor.b, 1.0); + + gl_FragColor = color; +} \ No newline at end of file diff --git a/res/textures/light_fin.png b/res/textures/light_fin.png new file mode 100644 index 0000000..838d442 Binary files /dev/null and b/res/textures/light_fin.png differ diff --git a/res/yaml/light.yaml b/res/yaml/light.yaml new file mode 100644 index 0000000..844c7e9 --- /dev/null +++ b/res/yaml/light.yaml @@ -0,0 +1,7 @@ +# Color values for player lights, each between 0 and 255 +color_red: 255 +color_green: 255 +color_blue: 127 + +# Total angle of the player light cone +light_cone_angle: 90.0 \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 52d8584..c0707b3 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -13,6 +13,7 @@ #include "sprites/Enemy.h" #include "sprites/Player.h" #include "sprites/items/HealthOrb.h" +#include "util/Angles.h" #include "util/Loader.h" #include "util/Yaml.h" @@ -22,12 +23,15 @@ Game::Game(tgui::Window& window) : mWindow(window), mWorldView(Vector2f(0, 0), mWindow.getView().getSize()), - mGenerator(mWorld, mPathfinder, Yaml("generation.yaml")) { + mLightSystem(AABB(Vec2f(-100000, -100000), Vec2f(100000, 10000)), &window, + "res/textures/light_fin.png", "res/shaders/light_attenuation_shader.frag"), + mGenerator(mWorld, mPathfinder, mLightSystem, Yaml("generation.yaml")) { mWindow.setFramerateLimit(FPS_GOAL); mWindow.setKeyRepeatEnabled(false); srand(time(nullptr)); initPlayer(); + initLight(); mCrosshairTexture = Loader::i().fromFile("crosshair.png"); mCrosshair.setTexture(*mCrosshairTexture, true); @@ -47,7 +51,15 @@ Game::Game(tgui::Window& window) : mPickupInstruction->setTextSize(14); } -void Game::initPlayer() { +/** + * Closes window. + */ +Game::~Game() { + mWindow.close(); +} + +void +Game::initPlayer() { Character::EquippedItems playerItems = { Weapon::WeaponType::PISTOL, Weapon::WeaponType::KNIFE, Gadget::GadgetType::NONE, Gadget::GadgetType::NONE @@ -58,11 +70,37 @@ void Game::initPlayer() { mWorld.insertCharacter(mPlayer); } -/** - * Closes window. - */ -Game::~Game() { - mWindow.close(); +void +Game::initLight() { + Yaml config("light.yaml"); + Color3f lightColor(config.get("color_red", 0) / 255.0f, + config.get("color_green", 0) / 255.0f, + config.get("color_blue", 0) / 255.0f); + mLightSystem.m_checkForHullIntersect = false; + mLightSystem.m_useBloom = false; + + mPlayerAreaLight->m_radius = 250.0f; + mPlayerAreaLight->m_size = 1.0f; + mPlayerAreaLight->m_softSpreadAngle = 0; + mPlayerAreaLight->m_spreadAngle = 2.0f * M_PI; + mPlayerAreaLight->m_intensity = 1.1f; + mPlayerAreaLight->m_bleed = 0; + mPlayerAreaLight->m_color = lightColor; + mPlayerDirectionLight->m_linearizeFactor = 0.5; + mPlayerAreaLight->CalculateAABB(); + mLightSystem.AddLight(mPlayerAreaLight); + + mPlayerDirectionLight->m_radius = 500.0f; + mPlayerDirectionLight->m_size = 25.0f; + mPlayerDirectionLight->m_softSpreadAngle = 0.1f * M_PI; + mPlayerDirectionLight->m_spreadAngle = + degreeToRadian(config.get("light_cone_angle", 0.0f)); + mPlayerDirectionLight->m_intensity = 5; + mPlayerDirectionLight->m_bleed = 0; + mPlayerDirectionLight->m_color = lightColor; + mPlayerDirectionLight->m_linearizeFactor = 1; + mPlayerDirectionLight->CalculateAABB(); + mLightSystem.AddLight(mPlayerDirectionLight); } /** @@ -73,9 +111,9 @@ Game::loop() { while (!mQuit) { input(); - int elapsed = mClock.restart().asMilliseconds(); - if (mPaused) - elapsed = 0; + int elapsed = (mPaused) + ? 0 + : mClock.restart().asMilliseconds(); mWorld.think(elapsed); if (mPlayer->getHealth() == 0) { @@ -301,6 +339,15 @@ Game::render() { mWindow.setView(mWorldView); mWindow.draw(mWorld); + // Update light + mPlayerAreaLight->SetCenter(mPlayer->getPosition().toVec2f()); + mPlayerDirectionLight->SetCenter(mPlayer->getPosition().toVec2f()); + mPlayerDirectionLight->SetDirectionAngle(degreeToRadian(90 - mPlayer->getDirection())); + + mLightSystem.SetView(mWorldView); + mLightSystem.RenderLights(); + mLightSystem.RenderLightTexture(); + // Render GUI and static stuff. mWindow.setView(mWindow.getDefaultView()); mWindow.drawGUI(); diff --git a/src/Game.h b/src/Game.h index 22b310e..41e7c43 100644 --- a/src/Game.h +++ b/src/Game.h @@ -10,6 +10,9 @@ #include +#include +#include + #include "generator/Generator.h" #include "Pathfinder.h" #include "World.h" @@ -39,6 +42,7 @@ private: Vector2 convertCoordinates(int x, int y); void updateGui(); void initPlayer(); + void initLight(); private: static const int FPS_GOAL = 60; @@ -57,7 +61,11 @@ private: World mWorld; Pathfinder mPathfinder; + ltbl::LightSystem mLightSystem; Generator mGenerator; + ltbl::Light_Point* mPlayerAreaLight = new ltbl::Light_Point(); + ltbl::Light_Point* mPlayerDirectionLight = new ltbl::Light_Point(); + std::shared_ptr mPlayer; bool mQuit = false; diff --git a/src/generator/Generator.cpp b/src/generator/Generator.cpp index c91873a..39b6c01 100644 --- a/src/generator/Generator.cpp +++ b/src/generator/Generator.cpp @@ -24,14 +24,16 @@ /** * Generates new random seed. */ -Generator::Generator(World& world, Pathfinder& pathfinder, const Yaml& config) : +Generator::Generator(World& world, Pathfinder& pathfinder, + ltbl::LightSystem& lightSystem, const Yaml& config) : mAreaSize(config.get("generate_area_size", 1)), mMaxRange((config.get("generate_area_range", 1.0f) / mAreaSize) / Tile::TILE_SIZE.x), mRoomSizeValue(config.get("room_size_value", 1.0f)), mRoomConnectionValue(config.get("room_connection_value", 1.0f)), mEnemyGenerationChance(config.get("enemy_generation_chance", 0.0f) * 2 - 1), mWorld(world), - mPathfinder(pathfinder) { + mPathfinder(pathfinder), + mLightSystem(lightSystem) { } /** @@ -188,6 +190,12 @@ Generator::connectRooms(const Vector2i& start) { for (const auto& p : path) { mTiles[p.x][p.y] = Tile::Type::FLOOR; Tile::setTile(p, Tile::Type::FLOOR, mWorld); + for (auto it = mHulls.begin(); it != mHulls.end(); it++) + if ((*it)->GetWorldCenter() == Tile::toPosition(Vector2i(p.x, p.y)).toVec2f()) { + mLightSystem.RemoveConvexHull(*it); + mHulls.erase(it); + break; + } } } } @@ -245,11 +253,24 @@ Generator::generateTiles(const sf::IntRect& area) { mTiles[x][y] = Tile::Type::FLOOR; connectRooms(start); - for (int x = area.left; x < area.left + area.width; x++) - for (int y = area.top; y < area.top + area.height; y++) + for (int y = area.top; y < area.top + area.height; y++) { mWorld.insert(std::shared_ptr( new Tile(Vector2i(x, y), mTiles[x][y]))); + if (mTiles[x][y] == Tile::Type::WALL) { + ltbl::ConvexHull* tileHull = new ltbl::ConvexHull(); + tileHull->m_vertices.push_back(Vec2f(-37.5f, 37.5f)); + tileHull->m_vertices.push_back(Vec2f(-37.5f, -37.5f)); + tileHull->m_vertices.push_back(Vec2f( 37.5f, -37.5f)); + tileHull->m_vertices.push_back(Vec2f( 37.5f, 37.5f)); + tileHull->m_renderLightOverHull = false; + tileHull->CalculateNormals(); + tileHull->CalculateAABB(); + tileHull->SetWorldCenter(Tile::toPosition(Vector2i(x, y)).toVec2f()); + mLightSystem.AddConvexHull(tileHull); + mHulls.push_back(tileHull); + } + } generateAreas(area); mPathfinder.generatePortals(); diff --git a/src/generator/Generator.h b/src/generator/Generator.h index 1d0b757..4dbdab4 100644 --- a/src/generator/Generator.h +++ b/src/generator/Generator.h @@ -10,6 +10,8 @@ #include +#include + #include "../sprites/abstract/Character.h" #include "../sprites/Tile.h" #include "SimplexNoise.h" @@ -24,7 +26,8 @@ class Yaml; */ class Generator : public sf::Drawable { public: - explicit Generator(World& world, Pathfinder& pathfinder, const Yaml& config); + explicit Generator(World& world, Pathfinder& pathfinder, + ltbl::LightSystem& lightSystem, const Yaml& config); void generateCurrentAreaIfNeeded(const Vector2f& position, const Character::EquippedItems& playerItems); Vector2f getPlayerSpawn() const; @@ -51,6 +54,7 @@ private: World& mWorld; Pathfinder& mPathfinder; + ltbl::LightSystem& mLightSystem; /// Contains values of all tiles that have yet been generated. array mTiles; /// Stores where tiles have already been generated. @@ -59,6 +63,7 @@ private: SimplexNoise mTileNoise; /// Perlin noise used for character placement. SimplexNoise mCharacterNoise; + std::vector mHulls; /// Used only for debug drawing. std::vector > mPaths; }; diff --git a/src/main.cpp b/src/main.cpp index 12e207a..f56e74c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include "util/Yaml.h" #include "util/Log.h" +#include "util/Vector.h" + /** * Creates Game object. */ @@ -21,6 +23,7 @@ int main(int argc, char* argv[]) { tgui::Window window(sf::VideoMode(1024, 768, 32), "Dungeon Gunner", sf::Style::Close | sf::Style::Titlebar); + Vector2f::SCREEN_HEIGHT = window.getSize().y; if (!window.globalFont.loadFromFile("res/DejaVuSans.ttf")) LOG_W("Failed to load font at 'res/DejaVuSans.ttf'"); diff --git a/src/sprites/Tile.cpp b/src/sprites/Tile.cpp index 0c11578..e2edda0 100644 --- a/src/sprites/Tile.cpp +++ b/src/sprites/Tile.cpp @@ -22,8 +22,8 @@ const Vector2i Tile::TILE_SIZE = Vector2i(75, 75); * * @param pType Type of the tile to create. */ -Tile::Tile(const Vector2i& position, Type type) : - Rectangle(Vector2f(thor::cwiseProduct(position, TILE_SIZE)), +Tile::Tile(const Vector2i& tilePosition, Type type) : + Rectangle(toPosition(tilePosition), CATEGORY_WORLD, (isSolid(type)) ? 0xffff : 0, Yaml(getConfig(type))), mType(type) { } @@ -42,8 +42,7 @@ Tile::setTile(const Vector2i& position, Type type, World& world) { std::shared_ptr converted = std::dynamic_pointer_cast(c); // Direct comparison of floats as both are from the same generation // on the same CPU. - if (converted.get() != nullptr && - converted->getPosition() == worldPosition && + if (converted && converted->getPosition() == worldPosition && converted->getType() != type) { world.remove(converted); break; @@ -78,6 +77,14 @@ Tile::isSolid(Type type) { } } +/** + * Converts a tile position to world/pixel position. + */ +Vector2f +Tile::toPosition(const Vector2i& tilePosition) { + return Vector2f(thor::cwiseProduct(tilePosition, TILE_SIZE)); +} + /** * Returns the Type of this tile. */ diff --git a/src/sprites/Tile.h b/src/sprites/Tile.h index fda23c8..f1a628c 100644 --- a/src/sprites/Tile.h +++ b/src/sprites/Tile.h @@ -24,12 +24,13 @@ public: static const Vector2i TILE_SIZE; //< Tile size in pixels. public: - explicit Tile(const Vector2i& position, Type type); + explicit Tile(const Vector2i& tilePosition, Type type); Type getType() const; static void setTile(const Vector2i& position, Type type, World& world); static std::string getConfig(Type type); static bool isSolid(Type type); + static Vector2f toPosition(const Vector2i& tilePosition); private: Type mType; diff --git a/src/sprites/abstract/Sprite.cpp b/src/sprites/abstract/Sprite.cpp index 95b7ca1..5c6e2a9 100755 --- a/src/sprites/abstract/Sprite.cpp +++ b/src/sprites/abstract/Sprite.cpp @@ -45,10 +45,15 @@ Sprite::getSpeed() const { * Returns the angle of the sprite. */ Vector2f -Sprite::getDirection() const { +Sprite::getDirectionVector() const { return thor::rotatedVector(Vector2f(0, - 1), mShape.getRotation()); } +float +Sprite::getDirection() const { + return mShape.getRotation(); +} + /** * Returns true if this object should be deleted. */ diff --git a/src/sprites/abstract/Sprite.h b/src/sprites/abstract/Sprite.h index ba85a8b..1553d4d 100755 --- a/src/sprites/abstract/Sprite.h +++ b/src/sprites/abstract/Sprite.h @@ -48,7 +48,8 @@ public: Vector2f getPosition() const; Vector2f getSpeed() const; - Vector2f getDirection() const; + Vector2f getDirectionVector() const; + float getDirection() const; bool getDelete() const; Category getCategory() const; Vector2f getSize() const; diff --git a/src/sprites/items/RingOfFire.cpp b/src/sprites/items/RingOfFire.cpp index 7c13115..403b497 100644 --- a/src/sprites/items/RingOfFire.cpp +++ b/src/sprites/items/RingOfFire.cpp @@ -35,7 +35,7 @@ void RingOfFire::onThink(int elapsed) { if (mCurrentWave < mWavesPerUse && mTimer.isExpired()) { for (int angle = mCurrentWave * 10; angle <= 360; angle += 360 / mBulletsPerWave) { - Vector2f direction(thor::rotatedVector(mCharacter->getDirection(), (float) angle) * + Vector2f direction(thor::rotatedVector(mCharacter->getDirectionVector(), (float) angle) * mCharacter->getRadius()); std::shared_ptr projectile(new Bullet(mCharacter->getPosition() + direction, diff --git a/src/sprites/items/Shield.cpp b/src/sprites/items/Shield.cpp index 555febf..5eab3bc 100644 --- a/src/sprites/items/Shield.cpp +++ b/src/sprites/items/Shield.cpp @@ -22,7 +22,7 @@ Shield::onUse(Character& character) { mCharacter = &character; if (mRotatingShield) mRotatingShield->setDelete(true); - Vector2f offset = mCharacter->getDirection() * mCharacter->getRadius(); + Vector2f offset = mCharacter->getDirectionVector() * mCharacter->getRadius(); mRotatingShield = std::shared_ptr( new RotatingShield(mCharacter->getPosition() + offset)); mCharacter->mWorld.insert(mRotatingShield); diff --git a/src/sprites/items/Weapon.cpp b/src/sprites/items/Weapon.cpp index dd31baa..a4f1a59 100755 --- a/src/sprites/items/Weapon.cpp +++ b/src/sprites/items/Weapon.cpp @@ -181,7 +181,7 @@ Weapon::setHolder(Character& holder) { */ void Weapon::insertProjectile(float angle) { - Vector2f offset(mHolder->getDirection() * mHolder->getRadius()); + Vector2f offset(mHolder->getDirectionVector() * mHolder->getRadius()); float spread = (mHolder->getSpeed() == Vector2f()) ? mSpread @@ -189,7 +189,7 @@ Weapon::insertProjectile(float angle) { std::uniform_real_distribution distribution(- spread, spread); angle += distribution(mGenerator) + 90.0f; - Vector2f direction(thor::rotatedVector(mHolder->getDirection(), angle)); + Vector2f direction(thor::rotatedVector(mHolder->getDirectionVector(), angle)); std::shared_ptr projectile(new Bullet(mHolder->getPosition() + offset, *mHolder, direction, mProjectile, mProjectileSpeed, diff --git a/src/util/Angles.cpp b/src/util/Angles.cpp new file mode 100644 index 0000000..e85e601 --- /dev/null +++ b/src/util/Angles.cpp @@ -0,0 +1,18 @@ +/* + * Angles.cpp + * + * Created on: 12.09.2013 + * Author: felix + */ + +#include "Angles.h" + +#include + +float radianToDegree(float radian) { + return radian * 180 / M_PI; +} + +float degreeToRadian(float degree) { + return degree * M_PI / 180; +} diff --git a/src/util/Angles.h b/src/util/Angles.h new file mode 100644 index 0000000..5f3092c --- /dev/null +++ b/src/util/Angles.h @@ -0,0 +1,15 @@ +/* + * Angles.h + * + * Created on: 12.09.2013 + * Author: felix + */ + +#ifndef ANGLES_H_ +#define ANGLES_H_ + +float radianToDegree(float radian); + +float degreeToRadian(float degree); + +#endif /* ANGLES_H_ */ diff --git a/src/util/Vector.h b/src/util/Vector.h index 9691ec2..dee4f21 100644 --- a/src/util/Vector.h +++ b/src/util/Vector.h @@ -10,6 +10,8 @@ #include +#include + /** * Vector class with comparison operator. All other operators are inherited * from sf::Vector2. @@ -17,6 +19,9 @@ template class Vector2 : public sf::Vector2 { public: + /// Needed for conversion to Vec2f. + static int SCREEN_HEIGHT; + Vector2() : sf::Vector2() {}; Vector2(T x, T y) : sf::Vector2(x, y) {}; /** @@ -26,8 +31,15 @@ public: template Vector2(const sf::Vector2& vector) : sf::Vector2(vector) {}; + Vec2f + toVec2f() { + return Vec2f(sf::Vector2f::x, SCREEN_HEIGHT - sf::Vector2f::y); + } }; +template +int Vector2::SCREEN_HEIGHT = 0; + /** * Comparison operator meant for containers like std::set. *