A bunch of progress on collectable occlusion

This commit is contained in:
Maximilian Giller 2023-07-09 03:43:47 +02:00
parent d009ef328c
commit c49d937e20
21 changed files with 388 additions and 42 deletions

View file

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View file

@ -6,7 +6,7 @@
#include "game/collectables/collectable_config.hpp"
std::map<std::string, CollectableConfig> const all_collectables = {
{"box", CollectableConfig("numbers")}
{"box", CollectableConfig("rosebush")}
};
#endif //HOLESOME_COLLECTABLES_HPP

View file

@ -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

View file

@ -0,0 +1,22 @@
#ifndef HOLESOME_MASKED_SPRITE_CONFIG_HPP
#define HOLESOME_MASKED_SPRITE_CONFIG_HPP
#include <string>
#include <SFML/System/Vector2.hpp>
#include <SFML/System/Time.hpp>
#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

View file

@ -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<sf::Texture> &sheetTexture, sf::IntRect textureRext, sf::Vector2f size)
: texture(sheetTexture), textureRect(textureRext)
{
image = std::make_shared<sf::Image>(sheetTexture->copyToImage());
sprite = std::make_shared<sf::Sprite>(*texture, textureRect);
setSize(size);
}
MaskedSprite::MaskedSprite(const std::shared_ptr<sf::Texture> &texture, sf::Vector2f size)
: texture(texture)
{
image = std::make_shared<sf::Image>(texture->copyToImage());
textureRect = sf::IntRect(0, 0, texture->getSize().x, texture->getSize().y);
sprite = std::make_shared<sf::Sprite>(*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<sf::Image>();
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<sf::Texture>();
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<float> 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<float> 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;
}

View file

@ -0,0 +1,45 @@
#ifndef HOLESOME_MASKED_SPRITE_HPP
#define HOLESOME_MASKED_SPRITE_HPP
#include <SFML/Graphics/Texture.hpp>
#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<sf::Texture>& texture, sf::Vector2f size = sf::Vector2f(0, 0));
MaskedSprite(const std::shared_ptr<sf::Texture> &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<sf::Sprite> sprite;
std::shared_ptr<sf::Texture> maskedTexture;
std::shared_ptr<sf::Texture> texture;
std::shared_ptr<sf::Image> image;
sf::IntRect textureRect;
float angle = 0;
sf::Vector2f renderPosition;
void updateFreshMaskedSprite();
sf::Color calculateNewPixelColor(sf::Color currentColor, sf::Vector2<float> position);
};
#endif //HOLESOME_MASKED_SPRITE_HPP

View file

@ -0,0 +1,51 @@
#ifndef HOLESOME_MASKED_SPRITE_HOLE_HPP
#define HOLESOME_MASKED_SPRITE_HOLE_HPP
#include <SFML/System/Vector2.hpp>
#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> &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

View file

@ -1,6 +1,7 @@
#include "sprite_factory.hpp"
#include "../texture_config.h"
#include "texture_manager.hpp"
#include "masked_sprite.hpp"
std::shared_ptr<SingleSprite> SpriteFactory::createSingleSprite(const std::string &name, sf::Vector2f size)
{
@ -136,3 +137,45 @@ std::shared_ptr<TileMap> SpriteFactory::createTileMap(TileMapConfig config)
return std::make_shared<TileMap>(tileSet, config.tiles);
}
std::shared_ptr<MaskedSprite> 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<MaskedSprite>(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<MaskedSprite>(texture, rect, size);
}

View file

@ -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<SingleSprite> createSingleSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0));
static std::shared_ptr<MaskedSprite> createMaskedSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0));
static std::shared_ptr<AnimatedSprite> createAnimatedSprite(const std::string& name, sf::Vector2f size = sf::Vector2f(0, 0));
static std::shared_ptr<SpriteSheet> createSheet(const std::string& name);
static std::shared_ptr<TileSet> createTileSet(const std::string &name);

View file

@ -17,12 +17,15 @@ SpriteSheet::SpriteSheet(const std::shared_ptr<sf::Texture>& 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<int> SpriteSheet::getRect(int spriteWidth, int spriteHeight, int row, int column) const
{ return sf::IntRect(column * spriteWidth, row * spriteHeight, spriteWidth, spriteHeight); }
std::shared_ptr<SingleSprite> SpriteSheet::getSprite(int sequenceIndex) const
{
if (sequenceIndex < 0 || sequenceIndex >= sprites.size())
@ -66,3 +69,12 @@ std::shared_ptr<sf::Texture> 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);
}

View file

@ -13,20 +13,24 @@ class SpriteSheet
public:
SpriteSheet(const std::shared_ptr<sf::Texture>& texture, int columns, int rows);
std::shared_ptr<SingleSprite> getSprite(int sequenceIndex) const;
[[nodiscard]] std::shared_ptr<SingleSprite> getSprite(int sequenceIndex) const;
std::shared_ptr<AnimatedSprite> getAnimation(int startingSequenceIndex, int numberOfFrames) const;
[[nodiscard]] std::shared_ptr<AnimatedSprite> getAnimation(int startingSequenceIndex, int numberOfFrames) const;
std::shared_ptr<sf::Texture> getTexture() const;
[[nodiscard]] std::shared_ptr<sf::Texture> 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<sf::Texture> texture;
std::vector<sf::Sprite> sprites;
[[nodiscard]] sf::Rect<int> getRect(int spriteWidth, int spriteHeight, int row, int column) const;
};

View file

@ -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<Sprite> 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);
}

View file

@ -23,10 +23,7 @@ public:
void setRotation(float angle) override;
private:
std::shared_ptr<SingleSprite> singleSprite = nullptr;
std::shared_ptr<AnimatedSprite> animatedSprite = nullptr;
std::shared_ptr<Sprite> getUsedSpritePtr() const;
std::shared_ptr<Sprite> sprite = nullptr;
};

View file

@ -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"
@ -21,6 +22,12 @@ std::map<std::string, std::string> const all_textures = {
{"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<std::string, SpriteConfig> 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<std::string, MaskedSpriteConfig> 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.

View file

@ -35,4 +35,13 @@ T sum(sf::Vector2<T> v)
return v.x + v.y;
}
template<typename T>
sf::Vector2<T> rotateVectorByAngle(sf::Vector2<T> 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<T>(x, y);
}
#endif //HOLESOME_VECTOR_UTILS_HPP