diff --git a/CMakeLists.txt b/CMakeLists.txt index d340689..eaae4c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,7 +126,10 @@ set(SOURCES src/game/physics/holes/ground/collectable_sim_ground.cpp src/game/physics/holes/ground/collectable_sim_ground.hpp src/game/physics/body_adapter.cpp - src/game/physics/body_adapter.hpp) + src/game/physics/body_adapter.hpp + src/sprites/masked_sprite.cpp + src/sprites/masked_sprite.hpp + src/sprites/configs/masked_sprite_config.hpp src/sprites/masked_sprite_hole.hpp) set(PHYSICS_00_SOURCES src/prototypes/physics_00.cpp) diff --git a/assets/collectables/bike.png b/assets/collectables/bike.png new file mode 100644 index 0000000..96fdb10 Binary files /dev/null and b/assets/collectables/bike.png differ diff --git a/assets/collectables/rose.png b/assets/collectables/rose.png new file mode 100644 index 0000000..5f86907 Binary files /dev/null and b/assets/collectables/rose.png differ diff --git a/assets/collectables/rosebush.png b/assets/collectables/rosebush.png new file mode 100644 index 0000000..a00326d Binary files /dev/null and b/assets/collectables/rosebush.png differ diff --git a/assets/collectables/small-tree.png b/assets/collectables/small-tree.png new file mode 100644 index 0000000..b54e231 Binary files /dev/null and b/assets/collectables/small-tree.png differ diff --git a/assets/collectables/stone.png b/assets/collectables/stone.png new file mode 100644 index 0000000..242b863 Binary files /dev/null and b/assets/collectables/stone.png differ diff --git a/assets/collectables/tram.png b/assets/collectables/tram.png new file mode 100644 index 0000000..b2cbf78 Binary files /dev/null and b/assets/collectables/tram.png differ diff --git a/src/collectables.hpp b/src/collectables.hpp index c1c76d4..4db4939 100644 --- a/src/collectables.hpp +++ b/src/collectables.hpp @@ -6,7 +6,7 @@ #include "game/collectables/collectable_config.hpp" std::map const all_collectables = { - {"box", CollectableConfig("numbers")} + {"box", CollectableConfig("rosebush")} }; #endif //HOLESOME_COLLECTABLES_HPP diff --git a/src/config.h b/src/config.h index fde27a4..2092052 100644 --- a/src/config.h +++ b/src/config.h @@ -31,6 +31,7 @@ #define ISOMETRIC_SKEW (16.f/32.f) #define MOVEMENT_SKEW sf::Vector2f(1.f, 1/ISOMETRIC_SKEW/2.f) #define WORLD_TO_ISO_SCALE 50.0f // 50.f, don't change. Rather adjust the zoom of the camera +#define MASKED_HOLE_BORDER_TRANSITION_SIZE 0.2f // Tracking view defaults #define DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD false diff --git a/src/sprites/configs/masked_sprite_config.hpp b/src/sprites/configs/masked_sprite_config.hpp new file mode 100644 index 0000000..86112dc --- /dev/null +++ b/src/sprites/configs/masked_sprite_config.hpp @@ -0,0 +1,22 @@ +#ifndef HOLESOME_MASKED_SPRITE_CONFIG_HPP +#define HOLESOME_MASKED_SPRITE_CONFIG_HPP + +#include +#include +#include +#include "sprite_config.hpp" + +struct MaskedSpriteConfig +{ + SpriteConfig spriteConfig; + + MaskedSpriteConfig(std::string sheetName, int sheetIndex, sf::Vector2f size = sf::Vector2f(0, 0)) + : spriteConfig(std::move(sheetName), sheetIndex, size) + {} + + explicit MaskedSpriteConfig(std::string textureName, sf::Vector2f size = sf::Vector2f(0, 0)) + : spriteConfig(std::move(textureName), size) + {} +}; + +#endif //HOLESOME_MASKED_SPRITE_CONFIG_HPP diff --git a/src/sprites/masked_sprite.cpp b/src/sprites/masked_sprite.cpp new file mode 100644 index 0000000..e522d25 --- /dev/null +++ b/src/sprites/masked_sprite.cpp @@ -0,0 +1,144 @@ +#include "masked_sprite.hpp" +#include "../config.h" +#include "../logging/easylogging++.h" +#include "../game/player/player_collection.hpp" + +MaskedSprite::MaskedSprite(const std::shared_ptr &sheetTexture, sf::IntRect textureRext, sf::Vector2f size) + : texture(sheetTexture), textureRect(textureRext) +{ + image = std::make_shared(sheetTexture->copyToImage()); + sprite = std::make_shared(*texture, textureRect); + setSize(size); +} + +MaskedSprite::MaskedSprite(const std::shared_ptr &texture, sf::Vector2f size) + : texture(texture) +{ + image = std::make_shared(texture->copyToImage()); + textureRect = sf::IntRect(0, 0, texture->getSize().x, texture->getSize().y); + sprite = std::make_shared(*texture, textureRect); + setSize(size); +} + +void MaskedSprite::preRenderUpdate() +{ + GameObject::preRenderUpdate(); + + // TODO: Did anything change? Is update of masked sprite necessary? + + if (angle == 0) + { + renderPosition = coordinates->isometric().toScreen(); + } else + { + auto position = coordinates->isometric().toScreen(); + renderPosition = calculateRotatedCornerPosition(position, angle); + } + sprite->setPosition(renderPosition); + sprite->setRotation(angle); + + updateFreshMaskedSprite(); +} + +void MaskedSprite::draw(sf::RenderWindow *window) +{ + GameObject::draw(window); + + window->draw(*sprite); +} + +void MaskedSprite::updateFreshMaskedSprite() +{ + // TODO: Calculate depth per pixel + auto maskedImage = std::make_shared(); + maskedImage->create(textureRect.width, textureRect.height, sf::Color::Transparent); + +// todo: or use sf::RenderTexture? + + maskedImage->copy(*image, 0, 0); + + // Calculate world coordinates for each pixel + auto topLeftCorner = sf::Vector2f(coordinates->diagonalWorld().horizontal, coordinates->diagonalWorld().vertical); + auto xAxis = rotateVectorByAngle(sf::Vector2f(1, 0), angle); + auto yAxis = rotateVectorByAngle(sf::Vector2f(0, -1), angle); + float xFactorPerPixel = sprite->getScale().x / WORLD_TO_ISO_SCALE; + float yFactorPerPixel = sprite->getScale().y / WORLD_TO_ISO_SCALE; + for (int yOffset = 0; yOffset < textureRect.height; yOffset++) + { + for (int xOffset = 0; xOffset < textureRect.width; xOffset++) + { + int y = yOffset + textureRect.top; + int x = xOffset + textureRect.left; + + auto pixelPosition = + topLeftCorner + xAxis * (xOffset * xFactorPerPixel) + yAxis * (yOffset * yFactorPerPixel); + if (pixelPosition.y >= 0) + { + continue; + } + + auto pixelColor = image->getPixel(x, y); + auto newPixelColor = calculateNewPixelColor(pixelColor, pixelPosition); + maskedImage->setPixel(x, y, newPixelColor); + } + } + + maskedTexture = std::make_shared(); + maskedTexture->loadFromImage(*maskedImage); + + sprite->setTexture(*maskedTexture); + sprite->setTextureRect(sf::IntRect(0, 0, textureRect.width, textureRect.height)); +} + +void MaskedSprite::setSize(const sf::Vector2f &size) +{ + if (size == sf::Vector2f(0, 0)) + { + return; + } + + sprite->setScale(size.x / textureRect.width, size.y / textureRect.height); +} + +sf::Vector2f MaskedSprite::getSize() const +{ + return {textureRect.width * sprite->getScale().x, textureRect.height * sprite->getScale().y}; +} + +sf::Sprite MaskedSprite::getSprite() const +{ + return *sprite; +} + +void MaskedSprite::setRotation(float angle) +{ + this->angle = restrictAngle(angle); + sprite->setRotation(angle); +} + +sf::Color MaskedSprite::calculateNewPixelColor(sf::Color currentColor, sf::Vector2 position) +{ + if (position.y >= 0) + { + // Pixel is above ground + return currentColor; + } + + // Change color based on height and hole + // Cut off pixels that are hidden in the ground, beyond the hole + float depth = coordinates->diagonalWorld().depth; + auto pixelCoordinates = TranslatedCoordinates(DiagonalWorldCoordinates(position.x, position.y, depth)); + + std::vector holeAlphaFactors{}; + auto players = PlayerCollection::getInstance()->getPlayers(); + for (auto &player: players) + { + auto holeMask = MaskedSpriteHole(player); + float holeAlphaFactor = holeMask.isPointInHoleFactor(pixelCoordinates); + holeAlphaFactors.push_back(holeAlphaFactor); + } + + float biggestHoleAlphaFactor = *std::max_element(holeAlphaFactors.begin(), holeAlphaFactors.end()); + currentColor.a *= biggestHoleAlphaFactor; + return currentColor; +} diff --git a/src/sprites/masked_sprite.hpp b/src/sprites/masked_sprite.hpp new file mode 100644 index 0000000..fa0b649 --- /dev/null +++ b/src/sprites/masked_sprite.hpp @@ -0,0 +1,45 @@ +#ifndef HOLESOME_MASKED_SPRITE_HPP +#define HOLESOME_MASKED_SPRITE_HPP + + +#include +#include "../game/game_object.h" +#include "versatile_sprite.hpp" +#include "masked_sprite_hole.hpp" + +class MaskedSprite : public Sprite, public GameObject +{ +public: + explicit MaskedSprite(const std::shared_ptr& texture, sf::Vector2f size = sf::Vector2f(0, 0)); + + MaskedSprite(const std::shared_ptr &sheetTexture, sf::IntRect textureRext, + sf::Vector2f size = sf::Vector2f(0, 0)); + + void preRenderUpdate() override; + + void draw(sf::RenderWindow *window) override; + + void setSize(const sf::Vector2f &size) override; + + sf::Vector2f getSize() const override; + + sf::Sprite getSprite() const override; + + void setRotation(float angle) override; + +private: + std::shared_ptr sprite; + std::shared_ptr maskedTexture; + std::shared_ptr texture; + std::shared_ptr image; + sf::IntRect textureRect; + float angle = 0; + sf::Vector2f renderPosition; + + void updateFreshMaskedSprite(); + + sf::Color calculateNewPixelColor(sf::Color currentColor, sf::Vector2 position); +}; + + +#endif //HOLESOME_MASKED_SPRITE_HPP diff --git a/src/sprites/masked_sprite_hole.hpp b/src/sprites/masked_sprite_hole.hpp new file mode 100644 index 0000000..28cb359 --- /dev/null +++ b/src/sprites/masked_sprite_hole.hpp @@ -0,0 +1,51 @@ +#ifndef HOLESOME_MASKED_SPRITE_HOLE_HPP +#define HOLESOME_MASKED_SPRITE_HOLE_HPP + +#include +#include "../coordinates/translated_coordinates.h" +#include "../game/player/player.hpp" + +struct MaskedSpriteHole +{ + sf::Vector2f center = sf::Vector2f(0, 0); + float majorRadiusSquared = 0; + float minorRadiusSquared = 0; + + MaskedSpriteHole() = default; + + MaskedSpriteHole(const std::shared_ptr &player) + { + auto iso = player->coordinates->isometric(); + this->center = sf::Vector2f(iso.x, iso.y); + + float radius = player->getWorldRadius() * WORLD_TO_ISO_SCALE * sqrt(2); + this->majorRadiusSquared = pow(radius, 2); + this->minorRadiusSquared = pow(radius * ISOMETRIC_SKEW, 2); + } + + float isPointInHoleFactor(TranslatedCoordinates point) const + { + if (majorRadiusSquared == 0 || minorRadiusSquared == 0) + return 1.f; + + // Calculate the value of the ellipse equation + auto iso = point.isometric(); + auto relativeCoordinates = center - sf::Vector2f(iso.x, iso.y); + float L = pow(relativeCoordinates.x, 2) / majorRadiusSquared + pow(relativeCoordinates.y, 2) / minorRadiusSquared; + + // Compare the value of L to 1 + float lowerBound = 1 - MASKED_HOLE_BORDER_TRANSITION_SIZE / 2.f; + float upperBound = 1 + MASKED_HOLE_BORDER_TRANSITION_SIZE / 2.f; + if (L <= lowerBound) + // Inside + return 1.f; + else if (L < upperBound) + // Linear transition for anti-aliasing + return (upperBound - L) / MASKED_HOLE_BORDER_TRANSITION_SIZE; + else + // Outside + return 0.f; + } +}; + +#endif //HOLESOME_MASKED_SPRITE_HOLE_HPP diff --git a/src/sprites/sprite_factory.cpp b/src/sprites/sprite_factory.cpp index 63fb81b..32db7eb 100644 --- a/src/sprites/sprite_factory.cpp +++ b/src/sprites/sprite_factory.cpp @@ -1,6 +1,7 @@ #include "sprite_factory.hpp" #include "../texture_config.h" #include "texture_manager.hpp" +#include "masked_sprite.hpp" std::shared_ptr SpriteFactory::createSingleSprite(const std::string &name, sf::Vector2f size) { @@ -136,3 +137,45 @@ std::shared_ptr SpriteFactory::createTileMap(TileMapConfig config) return std::make_shared(tileSet, config.tiles); } + +std::shared_ptr SpriteFactory::createMaskedSprite(const std::string &name, sf::Vector2f size) +{ + // Get sprite config + auto masked_sprite_config = all_masked_sprites.find(name); + + if (masked_sprite_config == all_masked_sprites.end()) + { + LOG(ERROR) << "MaskedSprite " << name << " not found. Could not create masked sprite."; + return nullptr; + } + + // Construct sprite + auto spriteConfig = masked_sprite_config->second.spriteConfig; + + // Construct simply from texture + if (!spriteConfig.isFromSheet) + { + auto texture = TextureManager::getInstance()->getTexture(spriteConfig.resourceName); + if (texture == nullptr) + { + LOG(ERROR) << "Texture " << spriteConfig.resourceName << " not found. Could not create masked sprite."; + return nullptr; + } + + LOG(INFO) << "Creating masked sprite from texture " << spriteConfig.resourceName; + return std::make_shared(texture, size); + } + + // Construct from sheet + auto sheet = createSheet(spriteConfig.resourceName); + if (sheet == nullptr) + { + LOG(ERROR) << "Sheet " << spriteConfig.resourceName << " not found. Could not create masked sprite."; + return nullptr; + } + + LOG(INFO) << "Creating single masked from sheet " << spriteConfig.resourceName; + auto texture = sheet->getTexture(); + auto rect = sheet->getTextureRect(spriteConfig.sheetIndex); + return std::make_shared(texture, rect, size); +} diff --git a/src/sprites/sprite_factory.hpp b/src/sprites/sprite_factory.hpp index e3dec4e..ebc3b3b 100644 --- a/src/sprites/sprite_factory.hpp +++ b/src/sprites/sprite_factory.hpp @@ -11,11 +11,13 @@ #include "tiling/tilemap.hpp" #include "tiling/tileset.hpp" #include "tiling/tilemap_config.hpp" +#include "masked_sprite.hpp" class SpriteFactory { public: static std::shared_ptr createSingleSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0)); + static std::shared_ptr createMaskedSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0)); static std::shared_ptr createAnimatedSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0)); static std::shared_ptr createSheet(const std::string& name); static std::shared_ptr createTileSet(const std::string &name); diff --git a/src/sprites/sprite_sheet.cpp b/src/sprites/sprite_sheet.cpp index a044761..9ea6774 100644 --- a/src/sprites/sprite_sheet.cpp +++ b/src/sprites/sprite_sheet.cpp @@ -1,7 +1,7 @@ #include "sprite_sheet.hpp" #include "single_sprite.hpp" -SpriteSheet::SpriteSheet(const std::shared_ptr& texture, int columns, int rows) +SpriteSheet::SpriteSheet(const std::shared_ptr &texture, int columns, int rows) { this->texture = texture; this->columns = columns; @@ -17,12 +17,15 @@ SpriteSheet::SpriteSheet(const std::shared_ptr& texture, int column { sf::Sprite sprite; sprite.setTexture(*texture); - sprite.setTextureRect(sf::IntRect(x * spriteWidth, y * spriteHeight, spriteWidth, spriteHeight)); + sprite.setTextureRect(getRect(spriteWidth, spriteHeight, y, x)); sprites.push_back(sprite); } } } +sf::Rect SpriteSheet::getRect(int spriteWidth, int spriteHeight, int row, int column) const +{ return sf::IntRect(column * spriteWidth, row * spriteHeight, spriteWidth, spriteHeight); } + std::shared_ptr SpriteSheet::getSprite(int sequenceIndex) const { if (sequenceIndex < 0 || sequenceIndex >= sprites.size()) @@ -66,3 +69,12 @@ std::shared_ptr SpriteSheet::getTexture() const { return texture; } + +sf::IntRect SpriteSheet::getTextureRect(int sequenceIndex) const +{ + int column = sequenceIndex % columns; + int row = sequenceIndex / columns; + int spriteWidth = texture->getSize().x / columns; + int spriteHeight = texture->getSize().y / rows; + return getRect(spriteWidth, spriteHeight, row, column); +} diff --git a/src/sprites/sprite_sheet.hpp b/src/sprites/sprite_sheet.hpp index 02959b2..cf8a017 100644 --- a/src/sprites/sprite_sheet.hpp +++ b/src/sprites/sprite_sheet.hpp @@ -13,20 +13,24 @@ class SpriteSheet public: SpriteSheet(const std::shared_ptr& texture, int columns, int rows); - std::shared_ptr getSprite(int sequenceIndex) const; + [[nodiscard]] std::shared_ptr getSprite(int sequenceIndex) const; - std::shared_ptr getAnimation(int startingSequenceIndex, int numberOfFrames) const; + [[nodiscard]] std::shared_ptr getAnimation(int startingSequenceIndex, int numberOfFrames) const; - std::shared_ptr getTexture() const; + [[nodiscard]] std::shared_ptr getTexture() const; - int getColumns() const; - int getRows() const; + [[nodiscard]] sf::IntRect getTextureRect(int sequenceIndex) const; + + [[nodiscard]] int getColumns() const; + [[nodiscard]] int getRows() const; private: int columns; int rows; std::shared_ptr texture; std::vector sprites; + + [[nodiscard]] sf::Rect getRect(int spriteWidth, int spriteHeight, int row, int column) const; }; diff --git a/src/sprites/versatile_sprite.cpp b/src/sprites/versatile_sprite.cpp index 5aa21d9..df7a276 100644 --- a/src/sprites/versatile_sprite.cpp +++ b/src/sprites/versatile_sprite.cpp @@ -7,12 +7,19 @@ VersatileSprite::VersatileSprite(const std::string &name, sf::Vector2f size) // Try to find in sprites if (all_sprites.find(name) != all_sprites.end()) { - singleSprite = SpriteFactory::createSingleSprite(name, size); - addChild(singleSprite); + auto spriteObject = SpriteFactory::createSingleSprite(name, size); + addChild(spriteObject); + sprite = spriteObject; } else if (all_animations.find(name) != all_animations.end()) { - animatedSprite = SpriteFactory::createAnimatedSprite(name, size); - addChild(animatedSprite); + auto spriteObject = SpriteFactory::createAnimatedSprite(name, size); + addChild(spriteObject); + sprite = spriteObject; + } else if (all_masked_sprites.find(name) != all_masked_sprites.end()) + { + auto spriteObject = SpriteFactory::createMaskedSprite(name, size); + addChild(spriteObject); + sprite = spriteObject; } else { LOG(ERROR) << "Sprite " << name << " not found. Could not create versatile sprite."; @@ -22,34 +29,20 @@ VersatileSprite::VersatileSprite(const std::string &name, sf::Vector2f size) void VersatileSprite::setSize(const sf::Vector2f &size) { - getUsedSpritePtr()->setSize(size); + sprite->setSize(size); } sf::Vector2f VersatileSprite::getSize() const { - return getUsedSpritePtr()->getSize(); + return sprite->getSize(); } sf::Sprite VersatileSprite::getSprite() const { - return getUsedSpritePtr()->getSprite(); + return sprite->getSprite(); } void VersatileSprite::setRotation(float angle) { - getUsedSpritePtr()->setRotation(angle); -} - -std::shared_ptr VersatileSprite::getUsedSpritePtr() const -{ - if (singleSprite != nullptr) - { - return singleSprite; - } else if (animatedSprite != nullptr) - { - return animatedSprite; - } else - { - throw std::runtime_error("Versatile sprite has no sprite"); - } + sprite->setRotation(angle); } diff --git a/src/sprites/versatile_sprite.hpp b/src/sprites/versatile_sprite.hpp index c5444ae..f25ea81 100644 --- a/src/sprites/versatile_sprite.hpp +++ b/src/sprites/versatile_sprite.hpp @@ -23,10 +23,7 @@ public: void setRotation(float angle) override; private: - std::shared_ptr singleSprite = nullptr; - std::shared_ptr animatedSprite = nullptr; - - std::shared_ptr getUsedSpritePtr() const; + std::shared_ptr sprite = nullptr; }; diff --git a/src/texture_config.h b/src/texture_config.h index e6878ec..0b8ee3a 100644 --- a/src/texture_config.h +++ b/src/texture_config.h @@ -7,6 +7,7 @@ #include "sprites/configs/sheet_config.hpp" #include "sprites/configs/sprite_config.hpp" #include "sprites/tiling/tileset_config.hpp" +#include "sprites/configs/masked_sprite_config.hpp" #define PLAYER_SKIN "hole" @@ -15,13 +16,19 @@ * The key is the name of the texture, the value is the path to the texture. */ std::map const all_textures = { - {"numbers", "assets/numbers.png"}, - {"64", "assets/64.png"}, - {"edge", "assets/edge.png"}, - {"ring", "assets/ring.png"}, - {"grasses", "assets/grass_plus.png"}, - {"hole", "assets/hole.png"}, - {"iso-tiles", "assets/isometric-tiles.png"} + {"numbers", "assets/numbers.png"}, + {"64", "assets/64.png"}, + {"edge", "assets/edge.png"}, + {"ring", "assets/ring.png"}, + {"grasses", "assets/grass_plus.png"}, + {"hole", "assets/hole.png"}, + {"bike", "assets/collectables/bike.png"}, + {"rose", "assets/collectables/rose.png"}, + {"rosebush", "assets/collectables/rosebush.png"}, + {"small-tree", "assets/collectables/small-tree.png"}, + {"stone", "assets/collectables/stone.png"}, + {"tram", "assets/collectables/tram.png"}, + {"iso-tiles", "assets/isometric-tiles.png"} }; /** @@ -53,6 +60,19 @@ std::map const all_sprites = { {"hole", SpriteConfig("hole")} }; +/** + * All masked sprites used in the game. + * The key is the name of the MaskedSprite, the value is the MaskedSprite config. + */ +std::map const all_masked_sprites = { + {"bike", MaskedSpriteConfig("bike")}, + {"rose", MaskedSpriteConfig("rose")}, + {"rosebush", MaskedSpriteConfig("rosebush")}, + {"stone", MaskedSpriteConfig("stone")}, + {"tram", MaskedSpriteConfig("tram")}, + {"small-tree", MaskedSpriteConfig("small-tree")} +}; + /** * All tilesets used in the game. * The key is the name of the tileset, the value is the tileset config. diff --git a/src/utilities/vector_utils.hpp b/src/utilities/vector_utils.hpp index 2b1df8d..b1c13ad 100644 --- a/src/utilities/vector_utils.hpp +++ b/src/utilities/vector_utils.hpp @@ -35,4 +35,13 @@ T sum(sf::Vector2 v) return v.x + v.y; } +template +sf::Vector2 rotateVectorByAngle(sf::Vector2 v, float angle) +{ + float radians = -angle * M_PI / 180; + auto x = v.x * std::cos(radians) - v.y * std::sin(radians); + auto y = v.x * std::sin(radians) + v.y * std::cos(radians); + return sf::Vector2(x, y); +} + #endif //HOLESOME_VECTOR_UTILS_HPP