diff --git a/engine/inc/info/version.hpp b/engine/inc/info/version.hpp index d4be1d36..e5c53fca 100644 --- a/engine/inc/info/version.hpp +++ b/engine/inc/info/version.hpp @@ -21,7 +21,7 @@ class Version { ~Version() {} static const u8 major = 2; - static const u8 minor = 1; + static const u8 minor = 2; static const u8 patch = 0; static std::string toString() { diff --git a/engine/inc/renderer/core/2d/renderer_core_2d.hpp b/engine/inc/renderer/core/2d/renderer_core_2d.hpp index aaa1d4df..177687f3 100644 --- a/engine/inc/renderer/core/2d/renderer_core_2d.hpp +++ b/engine/inc/renderer/core/2d/renderer_core_2d.hpp @@ -13,6 +13,7 @@ #include "debug/debug.hpp" #include "renderer/core/2d/sprite/sprite.hpp" #include "renderer/core/texture/renderer_core_texture_buffers.hpp" +#include "renderer/3d/pipeline/shared/pipeline_texture_mapping_type.hpp" #include "renderer/core/texture/models/texture.hpp" #include "renderer/renderer_settings.hpp" #include @@ -30,6 +31,9 @@ class RendererCore2D { void render(const Sprite& sprite, const RendererCoreTextureBuffers& texBuffers, Texture* texture); + void setTextureMappingType( + const PipelineTextureMappingType textureMappingType); + private: void setPrim(); void setLod(); diff --git a/engine/inc/renderer/core/2d/sprite/sprite.hpp b/engine/inc/renderer/core/2d/sprite/sprite.hpp index 6580a2df..439a5c21 100644 --- a/engine/inc/renderer/core/2d/sprite/sprite.hpp +++ b/engine/inc/renderer/core/2d/sprite/sprite.hpp @@ -24,7 +24,7 @@ class Sprite { ~Sprite(); u32 id; - Vec2 position, size; + Vec2 position, size, offset; float scale; Color color; SpriteMode mode; diff --git a/engine/src/renderer/core/2d/renderer_core_2d.cpp b/engine/src/renderer/core/2d/renderer_core_2d.cpp index 3f4f397c..44fb7bdf 100644 --- a/engine/src/renderer/core/2d/renderer_core_2d.cpp +++ b/engine/src/renderer/core/2d/renderer_core_2d.cpp @@ -84,10 +84,12 @@ void RendererCore2D::render(const Sprite& sprite, else if (sizeY > sizeX) texS = texMax / (sizeY / sizeX); - rect->t0.s = sprite.flipHorizontal ? texS : 0.0F; - rect->t0.t = sprite.flipVertical ? texT : 0.0F; - rect->t1.s = sprite.flipHorizontal ? 0.0F : texS; - rect->t1.t = sprite.flipVertical ? 0.0F : texT; + rect->t0.s = + sprite.flipHorizontal ? (texS + sprite.offset.x) : sprite.offset.x; + rect->t0.t = sprite.flipVertical ? (texT + sprite.offset.y) : sprite.offset.y; + rect->t1.s = + sprite.flipHorizontal ? sprite.offset.x : (texS + sprite.offset.x); + rect->t1.t = sprite.flipVertical ? sprite.offset.y : (texT + sprite.offset.y); rect->color.r = sprite.color.r; rect->color.g = sprite.color.g; @@ -131,4 +133,10 @@ void RendererCore2D::render(const Sprite& sprite, context = !context; } +void RendererCore2D::setTextureMappingType( + const PipelineTextureMappingType textureMappingType) { + lod.mag_filter = textureMappingType; + lod.min_filter = textureMappingType; +} + } // namespace Tyra diff --git a/engine/src/renderer/core/2d/sprite/sprite.cpp b/engine/src/renderer/core/2d/sprite/sprite.cpp index 59a0e86e..98277494 100644 --- a/engine/src/renderer/core/2d/sprite/sprite.cpp +++ b/engine/src/renderer/core/2d/sprite/sprite.cpp @@ -18,6 +18,7 @@ Sprite::Sprite() { flipVertical = false; size.set(32.0F, 32.0F); position.set(100.0F, 100.0F); + offset.set(0.0F, 0.0F); scale = 1.0F; mode = MODE_REPEAT; setDefaultColor(); diff --git a/tutorials/10-sprite-sheet/Makefile b/tutorials/10-sprite-sheet/Makefile new file mode 100644 index 00000000..e7d6385e --- /dev/null +++ b/tutorials/10-sprite-sheet/Makefile @@ -0,0 +1,24 @@ +TARGET := tutorial_10.elf +ENGINEDIR := ../../engine + +#The Directories, Source, Includes, Objects, Binary and Resources +SRCDIR := src +INCDIR := inc +BUILDDIR := obj +TARGETDIR := bin +RESDIR := res +SRCEXT := cpp +VSMEXT := vsm +VCLEXT := vcl +VCLPPEXT := vclpp +DEPEXT := d +OBJEXT := o + +#Flags, Libraries and Includes +CFLAGS := +LIB := +LIBDIRS := +INC := -I$(INCDIR) +INCDEP := -I$(INCDIR) + +include ../Makefile.tutorials-base \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/bin/.gitignore b/tutorials/10-sprite-sheet/bin/.gitignore new file mode 100644 index 00000000..44c5ea8f --- /dev/null +++ b/tutorials/10-sprite-sheet/bin/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/inc/font_sprite.hpp b/tutorials/10-sprite-sheet/inc/font_sprite.hpp new file mode 100644 index 00000000..b1390036 --- /dev/null +++ b/tutorials/10-sprite-sheet/inc/font_sprite.hpp @@ -0,0 +1,37 @@ +/* +# _____ ____ ___ +# | \/ ____| |___| +# | | | \ | | +#----------------------------------------------------------------------- +# Copyright 2022-2023, tyra - https://github.com/h4570/tyra +# Licensed under Apache License 2.0 +# Guido Diego Quispe Robles +*/ + +#pragma once + +#include + +#define FONT_CHAR_SIZE 96 + +namespace Tyra { + +class Font { + public: + Font(); + void load(TextureRepository& repository, Renderer2D* renderer); + void free(TextureRepository& repository); + void drawText(const char* text, const int& x, const int& y, Color color); + void drawText(const std::string& text, const int& x, const int& y, + Color color); + + private: + const static int chars[FONT_CHAR_SIZE]; + const static int charWidths[FONT_CHAR_SIZE]; + + Renderer2D* renderer2D; + Sprite allFont; + std::array font; +}; + +} // namespace Tyra \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/inc/tutorial_10.hpp b/tutorials/10-sprite-sheet/inc/tutorial_10.hpp new file mode 100644 index 00000000..e11d626e --- /dev/null +++ b/tutorials/10-sprite-sheet/inc/tutorial_10.hpp @@ -0,0 +1,45 @@ +/* +# _____ ____ ___ +# | \/ ____| |___| +# | | | \ | | +#----------------------------------------------------------------------- +# Copyright 2022-2023, tyra - https://github.com/h4570/tyra +# Licensed under Apache License 2.0 +# Guido Diego Quispe Robles +*/ + +#pragma once + +#include +#include "font_sprite.hpp" + +namespace Tyra { + +class Tutorial10 : public Game { + public: + explicit Tutorial10(Engine* engine); + ~Tutorial10(); + + void init(); + void loop(); + + private: + void loadTexture(); + void loadSprite(); + void handlePad(); + + int padTimer; + Engine* engine; + Pad* pad; + Font font; + + Sprite sprite; + Sprite spriteFlip; + Sprite spriteScale; + Sprite spriteStretch; + std::string strFilter; + + PipelineTextureMappingType textureFilter; +}; + +} // namespace Tyra diff --git a/tutorials/10-sprite-sheet/obj/.gitignore b/tutorials/10-sprite-sheet/obj/.gitignore new file mode 100644 index 00000000..44c5ea8f --- /dev/null +++ b/tutorials/10-sprite-sheet/obj/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/res/.gitignore b/tutorials/10-sprite-sheet/res/.gitignore new file mode 100644 index 00000000..44c5ea8f --- /dev/null +++ b/tutorials/10-sprite-sheet/res/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/run.ps1 b/tutorials/10-sprite-sheet/run.ps1 new file mode 100644 index 00000000..74646ccd --- /dev/null +++ b/tutorials/10-sprite-sheet/run.ps1 @@ -0,0 +1,4 @@ +$ConfigFile = Join-Path $PSScriptRoot '../../windows-pcsx2.ps1' +. $ConfigFile + +RunPCSX2 diff --git a/tutorials/10-sprite-sheet/src/font_sprite.cpp b/tutorials/10-sprite-sheet/src/font_sprite.cpp new file mode 100644 index 00000000..93577a25 --- /dev/null +++ b/tutorials/10-sprite-sheet/src/font_sprite.cpp @@ -0,0 +1,109 @@ +/* +# _____ ____ ___ +# | \/ ____| |___| +# | | | \ | | +#----------------------------------------------------------------------- +# Copyright 2022-2023, tyra - https://github.com/h4570/tyra +# Licensed under Apache License 2.0 +# Guido Diego Quispe Robles +*/ + +#include "font_sprite.hpp" + +namespace Tyra { + +const int Font::chars[FONT_CHAR_SIZE]{ + ' ', '!', '"', ' ', '$', '%', ' ', '{', '(', ')', ' ', '+', ',', '-', + '.', '/', '0', '1', '2', '3', '4', '5', '6', '2', '8', '9', ':', ';', + '<', '=', '>', '?', ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', ' ', ' ', ' ', ' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', + 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', '}', ']', '~', ' '}; + +const int Font::charWidths[FONT_CHAR_SIZE]{ + 0, 1, 3, 0, 5, 9, 0, 9, 3, 3, 0, 5, 2, 2, 1, 4, 4, 2, 4, 4, 5, 4, 4, 4, + 4, 4, 1, 2, 4, 5, 4, 4, 0, 6, 5, 5, 5, 4, 4, 5, 5, 1, 4, 5, 4, 7, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 7, 5, 5, 4, 0, 0, 0, 0, 0, 0, 5, 4, 4, 4, 4, 3, 4, + 4, 1, 2, 4, 1, 7, 4, 4, 4, 4, 3, 4, 3, 4, 5, 7, 4, 4, 4, 2, 5, 2, 6, 0, +}; + +Font::Font() {} + +void Font::load(TextureRepository& repository, Renderer2D* renderer) { + renderer2D = renderer; + + float height = 16.0F; + float width = 16.0F; + + allFont.mode = MODE_REPEAT; + allFont.size = Vec2(255, 127); + + auto filepath = FileUtils::fromCwd("earthbound-Font.png"); + auto* texture = repository.add(filepath); + texture->addLink(allFont.id); + + int column = 0; + int arrow = 0; + + for (int i = 0; i < FONT_CHAR_SIZE; i++) { + font[i].id = allFont.id; + font[i].mode = MODE_REPEAT; + font[i].size = Vec2(width, height); + font[i].offset = Vec2(width * column, height * arrow); + column++; + + if (column == 16) { + arrow++; + column = 0; + } + } +} + +void Font::free(TextureRepository& repository) { + repository.freeBySprite(allFont); + for (int i = 0; i < FONT_CHAR_SIZE; i++) { + repository.freeBySprite(font[i]); + } +} + +void Font::drawText(const char* text, const int& x, const int& y, Color color) { + drawText(std::string(text), x, y, color); +} + +void Font::drawText(const std::string& text, const int& x, const int& y, + Color color) { + int sizeText = text.size(); + + int offsetY = 0; + int offsetX = 0; + + for (int i = 0; i < sizeText; i++) { + int fontPos = text[i]; + Sprite fontSpr = font[0]; + + for (int j = 0; j < FONT_CHAR_SIZE; j++) { + if (fontPos == chars[j]) { + fontPos = j; + fontSpr = font[j]; + fontSpr.color = color; + fontSpr.position = Vec2(x + offsetX, y + offsetY); + break; + } + } + + if (fontPos == '\n') { + offsetY += 18; + offsetX = 0.0f; + } else { + if ((fontPos != ' ') && (fontPos != '\t')) { + renderer2D->render(fontSpr); + offsetX += charWidths[fontPos] + 2; + } else { + offsetX += 2; + } + } + } +} + +} // namespace Tyra \ No newline at end of file diff --git a/tutorials/10-sprite-sheet/src/main.cpp b/tutorials/10-sprite-sheet/src/main.cpp new file mode 100644 index 00000000..d854e6cc --- /dev/null +++ b/tutorials/10-sprite-sheet/src/main.cpp @@ -0,0 +1,26 @@ +/* +# _____ ____ ___ +# | \/ ____| |___| +# | | | \ | | +#----------------------------------------------------------------------- +# Copyright 2022-2023, tyra - https://github.com/h4570/tyra +# Licensed under Apache License 2.0 +# Guido Diego Quispe Robles +*/ + +#include "engine.hpp" +#include "tutorial_10.hpp" + +/** + * In this tutorial we will learn: + * - How works the offset of the sprite + * - How works the texture mapping + * - How to write text (font) + */ + +int main() { + Tyra::Engine engine; + Tyra::Tutorial10 game(&engine); + engine.run(&game); + return 0; +} diff --git a/tutorials/10-sprite-sheet/src/tutorial_10.cpp b/tutorials/10-sprite-sheet/src/tutorial_10.cpp new file mode 100644 index 00000000..727b6cf3 --- /dev/null +++ b/tutorials/10-sprite-sheet/src/tutorial_10.cpp @@ -0,0 +1,290 @@ +/* +# _____ ____ ___ +# | \/ ____| |___| +# | | | \ | | +#----------------------------------------------------------------------- +# Copyright 2022-2023, tyra - https://github.com/h4570/tyra +# Licensed under Apache License 2.0 +# Guido Diego Quispe Robles +*/ + +#include +#include "tutorial_10.hpp" + +namespace Tyra { + +Tutorial10::Tutorial10(Engine* t_engine) + : padTimer(0), engine(t_engine), pad(&t_engine->pad) {} + +Tutorial10::~Tutorial10() { + engine->renderer.getTextureRepository().freeBySprite(sprite); + engine->renderer.getTextureRepository().freeBySprite(spriteFlip); + engine->renderer.getTextureRepository().freeBySprite(spriteScale); + engine->renderer.getTextureRepository().freeBySprite(spriteStretch); + font.free(engine->renderer.getTextureRepository()); +} + +void Tutorial10::init() { + engine->renderer.setClearScreenColor(Color(32.0F, 32.0F, 32.0F)); + + /** Load all the sprites of the sprite sheet. */ + font.load(engine->renderer.getTextureRepository(), + &engine->renderer.renderer2D); + + /** Sprite contains rectangle information. */ + loadSprite(); + + /** Texture contains png image. */ + loadTexture(); + + /** Set the texture filtering. */ + textureFilter = TyraNearest; + + engine->renderer.core.renderer2D.setTextureMappingType(textureFilter); +} + +void Tutorial10::loop() { + auto& renderer = engine->renderer; + + /** Change the texture filtering of the texture. + * + * The options are: + * TyraNearest (Point Sampling): + * uses the color of the texel closest to the pixel center for the pixel + * color. + * + * TyraLinear (Bilinear Sampling): + * the four nearest texels to the pixel center are sampled + * and their colors are combined by weighted average according to distance. + * + * By default it is linear. + */ + + renderer.core.renderer2D.setTextureMappingType(textureFilter); + + handlePad(); + + /** Begin frame will clear our screen. */ + renderer.beginFrame(); + + /** Render sprite. */ + renderer.renderer2D.render(sprite); + renderer.core.renderer2D.setTextureMappingType(TyraLinear); + renderer.renderer2D.render(spriteFlip); + renderer.core.renderer2D.setTextureMappingType(textureFilter); + renderer.renderer2D.render(spriteScale); + renderer.renderer2D.render(spriteStretch); + + font.drawText("tests with sprite sheet", (512 / 2) - 60, 10, + Color(255, 255, 255)); + font.drawText("Press Cross for nearest filter", (512 / 2) - 70, 50, + Color(255, 255, 255)); + font.drawText("Press Circle for linear filter", (512 / 2) - 65, 70, + Color(255, 255, 255)); + font.drawText("Use left stick for move the offsets of the sprites", + (512 / 2) - 110, 90, Color(255, 255, 255)); + font.drawText("sprite 32x32\n scaled x3", sprite.position.x + 16, + sprite.position.y - 40, Color(255, 255, 255)); + font.drawText("sprite 32x32\nscaled x3 with flip", spriteFlip.position.x + 4, + spriteFlip.position.y - 40, Color(255, 255, 255)); + font.drawText("sprite 96x96 with\n repeat mode", spriteScale.position.x + 6, + spriteScale.position.y - 40, Color(255, 255, 255)); + font.drawText("sprite 96x96 with\n stretch mode", + spriteStretch.position.x + 6, spriteStretch.position.y - 40, + Color(255, 255, 255)); + + if (textureFilter == TyraLinear) { + strFilter = "Filter: Linear"; + } else { + strFilter = "Filter: Nearest"; + } + + int posX1 = sprite.position.x + 16; + int posX2 = spriteFlip.position.x + 16; + int posX3 = spriteStretch.position.x + 16; + int posX4 = spriteScale.position.x + 16; + + int posY1 = sprite.position.y + (sprite.size.y * sprite.scale) + 16; + int posY2 = + spriteFlip.position.y + (spriteFlip.size.y * spriteFlip.scale) + 16; + int posY3 = spriteStretch.position.y + + (spriteStretch.size.y * spriteStretch.scale) + 16; + int posY4 = + spriteScale.position.y + (spriteScale.size.y * spriteScale.scale) + 16; + + font.drawText(strFilter, posX1, posY1, Color(255, 255, 255)); + font.drawText("Filter: Linear", posX2, posY2, Color(255, 255, 255)); + font.drawText(strFilter, posX3, posY3, Color(255, 255, 255)); + font.drawText(strFilter, posX4, posY4, Color(255, 255, 255)); + + /** End frame will perform vsync. */ + renderer.endFrame(); +} + +void Tutorial10::handlePad() { + if (engine->pad.getClicked().Cross) { + textureFilter = TyraNearest; + } else if (engine->pad.getClicked().Circle) { + textureFilter = TyraLinear; + } + + /** Move the offset of the sprites. + * + * The offset of the sprite need to be equal or greater to zero + * or a graphical error occurs. + * + * And the sum of the sprite size and the offset must be less than 1024 + * or a graphical error occurs. + * + * Sum with repeat mode: + * Max = sprite.size + sprite.offset. + * + * Sum with strecth mode: + * Max = original size of the image + sprite.offset. + * + * If it continues moving past these values + * in a moment it will return to normal but + * the error will be repeated with another value. + */ + + if (padTimer <= 0) { + if (pad->getLeftJoyPad().v <= 100) { // up + sprite.offset -= Vec2(0, 16); + spriteFlip.offset -= Vec2(0, 16); + spriteScale.offset -= Vec2(0, 16); + spriteStretch.offset -= Vec2(0, 16); + padTimer = 20; + printf("New offset: %f,%f\n", sprite.offset.x, sprite.offset.y); + } else if (pad->getLeftJoyPad().v >= 200) { // down + sprite.offset += Vec2(0, 16); + spriteFlip.offset += Vec2(0, 16); + spriteScale.offset += Vec2(0, 16); + spriteStretch.offset += Vec2(0, 16); + padTimer = 20; + printf("New offset: %f,%f\n", sprite.offset.x, sprite.offset.y); + } + + if (pad->getLeftJoyPad().h <= 100) { // left + sprite.offset -= Vec2(16, 0); + spriteFlip.offset -= Vec2(16, 0); + spriteScale.offset -= Vec2(16, 0); + spriteStretch.offset -= Vec2(16, 0); + padTimer = 20; + printf("New offset: %f,%f\n", sprite.offset.x, sprite.offset.y); + } else if (pad->getLeftJoyPad().h >= 200) { // right + sprite.offset += Vec2(16, 0); + spriteFlip.offset += Vec2(16, 0); + spriteScale.offset += Vec2(16, 0); + spriteStretch.offset += Vec2(16, 0); + padTimer = 20; + printf("New offset: %f,%f\n", sprite.offset.x, sprite.offset.y); + } + + } else { + padTimer--; + } +} + +void Tutorial10::loadSprite() { + const auto& screenSettings = engine->renderer.core.getSettings(); + + int offset = 25; + + sprite.mode = SpriteMode::MODE_REPEAT; + + /** Let's scale it down */ + sprite.size = Vec2(32.0F, 32.0F); + sprite.scale = 3; + + /** Set the position */ + sprite.position = + Vec2(offset, screenSettings.getHeight() / 2.0F - sprite.size.y / 2.0F); + + spriteFlip.mode = SpriteMode::MODE_REPEAT; + + spriteFlip.flipHorizontal = true; + spriteFlip.flipVertical = true; + + spriteFlip.size = Vec2(32.0F, 32.0F); + + spriteFlip.scale = 3; + + spriteFlip.position = + Vec2(((offset * 2) + (32.0F * 3)), + screenSettings.getHeight() / 2.0F - sprite.size.y / 2.0F); + + spriteScale.mode = SpriteMode::MODE_REPEAT; + + spriteScale.size = Vec2(96.0F, 96.0F); + + spriteScale.position = + Vec2(((offset * 3) + ((32.0F * 3) * 2)), + screenSettings.getHeight() / 2.0F - sprite.size.y / 2.0F); + + spriteStretch.mode = SpriteMode::MODE_STRETCH; + + spriteStretch.size = Vec2(96.0F, 96.0F); + + spriteStretch.position = + Vec2(((offset * 4) + ((32.0F * 3) * 3)), + screenSettings.getHeight() / 2.0F - sprite.size.y / 2.0F); + + TYRA_LOG("Sprite created!"); +} + +void Tutorial10::loadTexture() { + /** + * Renderer has high layer functions, + * which allows to render: + * - Sprite (2D) + * - Mesh (3D) + * + * It uses ONLY low layer functions which are in renderer.core + */ + auto& renderer = engine->renderer; + + /** + * TextureRepository is a repository of textures. + * It is a singleton class, with all game textures. + * We are linking these textures with sprite's (2D) and mesh (3D) materials. + */ + auto& textureRepository = renderer.getTextureRepository(); + + /** + * Texture is stored in "res" directory. + * Content of "res" directory is automatically copied into + * "bin" directory, which contains our final game. + * + * File utils automatically add's device prefix to the path, + * based on current working directory. + * + * In PS2 world: + * - USB has a "mass:" prefix + * - Our PC in PS2Link has a "host:" prefix + * - Our PC in PCSX2 has a "host:" prefix + */ + auto filepath = FileUtils::fromCwd("atlas.png"); + + /** + * Tyra supports following PNG formats: + * 32bpp (RGBA) + * 24bpp (RGB) + * 8bpp, palletized (RGBA) + * 4bpp, palletized (RGBA) + * + * 8bpp and 4bpp are the fastest. + * All of these formats can be easily exported via GIMP. + */ + auto* texture = textureRepository.add(filepath); + + /** Let's assign this texture to sprite. */ + texture->addLink(sprite.id); + + spriteFlip.id = sprite.id; + spriteScale.id = sprite.id; + spriteStretch.id = sprite.id; + + TYRA_LOG("Texture loaded!"); +} + +} // namespace Tyra