From add17da988f4518c451abc4d0bacf536071e1c0b Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:37:52 +1000 Subject: [PATCH] directive-based color variants for materials --- source/core/StarDirectives.hpp | 2 +- source/game/StarMaterialDatabase.cpp | 24 ++++++++++++-- source/game/StarMaterialDatabase.hpp | 2 ++ source/game/StarMaterialRenderProfile.cpp | 8 +++-- source/game/StarMaterialRenderProfile.hpp | 4 ++- source/game/StarTileDrawer.cpp | 38 ++++++++++++++++------- source/rendering/StarTilePainter.cpp | 33 ++++++++++++-------- 7 files changed, 80 insertions(+), 31 deletions(-) diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index e6b0a6e..9290649 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -121,7 +121,7 @@ private: void buildString(String& string, const DirectivesGroup& directives) const; List m_directives; - size_t m_count; + size_t m_count = 0; }; template <> diff --git a/source/game/StarMaterialDatabase.cpp b/source/game/StarMaterialDatabase.cpp index 6363c5d..99ce32e 100644 --- a/source/game/StarMaterialDatabase.cpp +++ b/source/game/StarMaterialDatabase.cpp @@ -402,11 +402,31 @@ ItemDescriptor MaterialDatabase::modItemDrop(ModId modId) const { return {}; } +MaterialColorVariant MaterialDatabase::materialColorVariants(MaterialId materialId) const { + if (isRealMaterial(materialId)) { + auto const& matInfo = getMaterialInfo(materialId); + if (matInfo->materialRenderProfile) + return matInfo->materialRenderProfile->colorVariants; + } + + return 0; +} + +MaterialColorVariant MaterialDatabase::modColorVariants(ModId modId) const { + if (isRealMod(modId)) { + auto const& modInfo = getModInfo(modId); + if (modInfo->modRenderProfile) + return modInfo->modRenderProfile->colorVariants; + } + + return 0; +} + bool MaterialDatabase::isMultiColor(MaterialId materialId) const { if (isRealMaterial(materialId)) { auto const& matInfo = getMaterialInfo(materialId); if (matInfo->materialRenderProfile) - return matInfo->materialRenderProfile->multiColor; + return matInfo->materialRenderProfile->colorVariants > 0; } return false; @@ -568,7 +588,7 @@ shared_ptr const& MaterialDatabase::getMat shared_ptr const& MaterialDatabase::getModInfo(ModId modId) const { if (modId >= m_mods.size() || !m_mods[modId]) - throw MaterialException(strf("No such modId id: {}\n", modId)); + throw MaterialException(strf("No such mod id: {}\n", modId)); else return m_mods[modId]; } diff --git a/source/game/StarMaterialDatabase.hpp b/source/game/StarMaterialDatabase.hpp index f20a3e2..1d7d709 100644 --- a/source/game/StarMaterialDatabase.hpp +++ b/source/game/StarMaterialDatabase.hpp @@ -69,6 +69,8 @@ public: ItemDescriptor materialItemDrop(MaterialId materialId) const; ItemDescriptor modItemDrop(ModId modId) const; + MaterialColorVariant materialColorVariants(MaterialId materialId) const; + MaterialColorVariant modColorVariants(ModId modId) const; bool isMultiColor(MaterialId materialId) const; bool foregroundLightTransparent(MaterialId materialId) const; bool backgroundLightTransparent(MaterialId materialId) const; diff --git a/source/game/StarMaterialRenderProfile.cpp b/source/game/StarMaterialRenderProfile.cpp index 975e7fa..1272e83 100644 --- a/source/game/StarMaterialRenderProfile.cpp +++ b/source/game/StarMaterialRenderProfile.cpp @@ -80,7 +80,9 @@ MaterialRenderProfile parseMaterialRenderProfile(Json const& spec, String const& bool lightTransparent = spec.getBool("lightTransparent", false); profile.foregroundLightTransparent = spec.getBool("foregroundLightTransparent", lightTransparent); profile.backgroundLightTransparent = spec.getBool("backgroundLightTransparent", lightTransparent); - profile.multiColor = spec.getBool("multiColored", false); + profile.colorVariants = spec.getBool("multiColored", false) ? spec.getUInt("colorVariants", MaxMaterialColorVariant) : 0; + for (auto& entry : spec.getArray("colorDirectives", JsonArray())) + profile.colorDirectives.append(entry.toString()); profile.occludesBehind = spec.getBool("occludesBelow", true); profile.zLevel = spec.getUInt("zLevel", 0); profile.radiantLight = Color::rgb(jsonToVec3B(spec.get("radiantLight", JsonArray{0, 0, 0}))).toRgbF(); @@ -126,8 +128,8 @@ MaterialRenderProfile parseMaterialRenderProfile(Json const& spec, String const& auto flipTextureCoordinates = [imageHeight]( RectF const& rect) { return RectF::withSize(Vec2F(rect.xMin(), imageHeight - rect.yMax()), rect.size()); }; for (unsigned v = 0; v < variants; ++v) { - if (profile.multiColor) { - for (MaterialColorVariant c = 0; c <= MaxMaterialColorVariant; ++c) { + if (profile.colorVariants > 0) { + for (MaterialColorVariant c = 0; c <= profile.colorVariants; ++c) { RectF textureRect = RectF::withSize(texturePosition + variantStride * v + colorStride * c, textureSize); renderPiece->variants[c].append(flipTextureCoordinates(textureRect)); } diff --git a/source/game/StarMaterialRenderProfile.hpp b/source/game/StarMaterialRenderProfile.hpp index 658cd89..f721cdb 100644 --- a/source/game/StarMaterialRenderProfile.hpp +++ b/source/game/StarMaterialRenderProfile.hpp @@ -6,6 +6,7 @@ #include "StarMultiArray.hpp" #include "StarGameTypes.hpp" #include "StarTileDamage.hpp" +#include "StarDirectives.hpp" namespace Star { @@ -96,11 +97,12 @@ struct MaterialRenderProfile { MaterialRenderMatchList mainMatchList; List> crackingFrames; List> protectedFrames; + List colorDirectives; Json ruleProperties; bool foregroundLightTransparent; bool backgroundLightTransparent; - bool multiColor; + uint8_t colorVariants; bool occludesBehind; uint32_t zLevel; Vec3F radiantLight; diff --git a/source/game/StarTileDrawer.cpp b/source/game/StarTileDrawer.cpp index 70cd7d0..2fa5051 100644 --- a/source/game/StarTileDrawer.cpp +++ b/source/game/StarTileDrawer.cpp @@ -65,7 +65,7 @@ bool TileDrawer::produceTerrainDrawables(Drawables& drawables, MaterialId material = EmptyMaterialId; MaterialHue materialHue = 0; - MaterialColorVariant materialColorVariant = 0; + MaterialColorVariant colorVariant = 0; ModId mod = NoModId; MaterialHue modHue = 0; float damageLevel = 0.0f; @@ -77,7 +77,7 @@ bool TileDrawer::produceTerrainDrawables(Drawables& drawables, if (terrainLayer == TerrainLayer::Background) { material = tile.background; materialHue = tile.backgroundHueShift; - materialColorVariant = tile.backgroundColorVariant; + colorVariant = tile.backgroundColorVariant; mod = tile.backgroundMod; modHue = tile.backgroundModHueShift; damageLevel = byteToFloat(tile.backgroundDamageLevel); @@ -86,7 +86,7 @@ bool TileDrawer::produceTerrainDrawables(Drawables& drawables, } else { material = tile.foreground; materialHue = tile.foregroundHueShift; - materialColorVariant = tile.foregroundColorVariant; + colorVariant = tile.foregroundColorVariant; mod = tile.foregroundMod; modHue = tile.foregroundModHueShift; damageLevel = byteToFloat(tile.foregroundDamageLevel); @@ -99,10 +99,14 @@ bool TileDrawer::produceTerrainDrawables(Drawables& drawables, if ((isBlock && terrainLayer == TerrainLayer::Midground) || (!isBlock && terrainLayer == TerrainLayer::Foreground)) return false; - auto getPieceImage = [](MaterialRenderPieceConstPtr const& piece, Box const& box, MaterialHue hue) -> String { - return hue == 0 + auto getPieceImage = [](MaterialRenderPieceConstPtr const& piece, Box const& box, MaterialHue hue, Directives const* directives) -> AssetPath { + AssetPath image = hue == 0 ? strf("{}?crop={};{};{};{}", piece->texture, box.xMin(), box.yMin(), box.xMax(), box.yMax()) : strf("{}?crop={};{};{};{}?hueshift={}", piece->texture, box.xMin(), box.yMin(), box.xMax(), box.yMax(), materialHueToDegrees(hue)); + if (directives) + image.directives += *directives; + + return image; }; auto materialRenderProfile = materialDatabase->materialRenderProfile(material); @@ -110,31 +114,43 @@ bool TileDrawer::produceTerrainDrawables(Drawables& drawables, if (materialRenderProfile) { occlude = materialRenderProfile->occludesBehind; - + auto materialColorVariant = materialRenderProfile->colorVariants > 0 ? colorVariant % materialRenderProfile->colorVariants : 0; uint32_t variance = staticRandomU32(renderData.geometry.xwrap(pos[0]) + offset[0], pos[1] + offset[1], (int)variantLayer.value(terrainLayer), "main"); auto& drawList = drawables[materialZLevel(materialRenderProfile->zLevel, material, materialHue, materialColorVariant)]; MaterialPieceResultList pieces; determineMatchingPieces(pieces, &occlude, materialDatabase, materialRenderProfile->mainMatchList, renderData, pos, terrainLayer == TerrainLayer::Background ? TileLayer::Background : TileLayer::Foreground, false); + Directives const* directives = materialRenderProfile->colorDirectives.empty() + ? nullptr + : &materialRenderProfile->colorDirectives.wrap(materialColorVariant); for (auto const& piecePair : pieces) { - auto& variant = piecePair.first->variants.get(materialColorVariant).wrap(variance); - auto image = getPieceImage(piecePair.first, variant, materialHue); + auto variant = piecePair.first->variants.ptr(materialColorVariant); + if (!variant) variant = piecePair.first->variants.ptr(0); + if (!variant) continue; + auto& textureCoords = variant->wrap(variance); + auto image = getPieceImage(piecePair.first, textureCoords, materialHue, directives); drawList.emplace_back(Drawable::makeImage(image, scale, false, piecePair.second * scale + Vec2F(pos), color)); } } if (modRenderProfile) { - auto modColorVariant = modRenderProfile->multiColor ? materialColorVariant : 0; + auto modColorVariant = modRenderProfile->colorVariants > 0 ? colorVariant % modRenderProfile->colorVariants : 0; uint32_t variance = staticRandomU32(renderData.geometry.xwrap(pos[0]), pos[1], (int)variantLayer.value(terrainLayer), "mod"); auto& drawList = drawables[modZLevel(modRenderProfile->zLevel, mod, modHue, modColorVariant)]; MaterialPieceResultList pieces; determineMatchingPieces(pieces, &occlude, materialDatabase, modRenderProfile->mainMatchList, renderData, pos, terrainLayer == TerrainLayer::Background ? TileLayer::Background : TileLayer::Foreground, true); + Directives const* directives = modRenderProfile->colorDirectives.empty() + ? nullptr + : &modRenderProfile->colorDirectives.wrap(modColorVariant); for (auto const& piecePair : pieces) { - auto& variant = piecePair.first->variants.get(modColorVariant).wrap(variance); - auto image = getPieceImage(piecePair.first, variant, modHue); + auto variant = piecePair.first->variants.ptr(modColorVariant); + if (!variant) variant = piecePair.first->variants.ptr(0); + if (!variant) continue; + auto& textureCoords = variant->wrap(variance); + auto image = getPieceImage(piecePair.first, textureCoords, modHue, directives); drawList.emplace_back(Drawable::makeImage(image, scale, false, piecePair.second * scale + Vec2F(pos), color)); } } diff --git a/source/rendering/StarTilePainter.cpp b/source/rendering/StarTilePainter.cpp index f9cff34..eef54b6 100644 --- a/source/rendering/StarTilePainter.cpp +++ b/source/rendering/StarTilePainter.cpp @@ -230,7 +230,7 @@ bool TilePainter::produceTerrainPrimitives(HashMappieceId, hue, mod), [&](auto const&) { - String texture; - if (hue == 0) - texture = piece->texture; - else - texture = strf("{}?hueshift={}", piece->texture, materialHueToDegrees(hue)); + AssetPath texture = (hue == 0) ? piece->texture : strf("{}?hueshift={}", piece->texture, materialHueToDegrees(hue)); + + if (directives) + texture.directives += *directives; return m_textureGroup->create(*assets->image(texture)); }); @@ -281,16 +280,20 @@ bool TilePainter::produceTerrainPrimitives(HashMapoccludesBehind; - + auto materialColorVariant = materialRenderProfile->colorVariants > 0 ? colorVariant % materialRenderProfile->colorVariants : 0; uint32_t variance = staticRandomU32(renderData.geometry.xwrap(pos[0]), pos[1], (int)terrainLayer, "main"); auto& quadList = primitives[materialZLevel(materialRenderProfile->zLevel, material, materialHue, materialColorVariant)]; MaterialPieceResultList pieces; determineMatchingPieces(pieces, &occlude, materialDatabase, materialRenderProfile->mainMatchList, renderData, pos, terrainLayer == TerrainLayer::Background ? TileLayer::Background : TileLayer::Foreground, false); + Directives const* directives = materialRenderProfile->colorDirectives.empty() + ? nullptr + : &materialRenderProfile->colorDirectives.wrap(materialColorVariant); for (auto const& piecePair : pieces) { - TexturePtr texture = getPieceTexture(material, piecePair.first, materialHue, false); + TexturePtr texture = getPieceTexture(material, piecePair.first, materialHue, directives, false); auto variant = piecePair.first->variants.ptr(materialColorVariant); + if (!variant) variant = piecePair.first->variants.ptr(0); if (!variant) continue; RectF textureCoords = variant->wrap(variance); RectF worldCoords = RectF::withSize(piecePair.second / TilePixels + Vec2F(pos), textureCoords.size() / TilePixels); @@ -308,16 +311,20 @@ bool TilePainter::produceTerrainPrimitives(HashMapmultiColor ? materialColorVariant : 0; + auto modColorVariant = modRenderProfile->colorVariants > 0 ? colorVariant % modRenderProfile->colorVariants : 0; uint32_t variance = staticRandomU32(renderData.geometry.xwrap(pos[0]), pos[1], (int)terrainLayer, "mod"); auto& quadList = primitives[modZLevel(modRenderProfile->zLevel, mod, modHue, modColorVariant)]; MaterialPieceResultList pieces; determineMatchingPieces(pieces, &occlude, materialDatabase, modRenderProfile->mainMatchList, renderData, pos, terrainLayer == TerrainLayer::Background ? TileLayer::Background : TileLayer::Foreground, true); + Directives const* directives = modRenderProfile->colorDirectives.empty() + ? nullptr + : &modRenderProfile->colorDirectives.wrap(modColorVariant); for (auto const& piecePair : pieces) { - auto texture = getPieceTexture(mod, piecePair.first, modHue, true); + auto texture = getPieceTexture(mod, piecePair.first, modHue, directives, true); auto variant = piecePair.first->variants.ptr(modColorVariant); + if (!variant) variant = piecePair.first->variants.ptr(0); if (!variant) continue; auto& textureCoords = variant->wrap(variance); RectF worldCoords = RectF::withSize(piecePair.second / TilePixels + Vec2F(pos), textureCoords.size() / TilePixels);