diff options
Diffstat (limited to 'source/game/StarMaterialRenderProfile.cpp')
-rw-r--r-- | source/game/StarMaterialRenderProfile.cpp | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/source/game/StarMaterialRenderProfile.cpp b/source/game/StarMaterialRenderProfile.cpp new file mode 100644 index 0000000..39012f1 --- /dev/null +++ b/source/game/StarMaterialRenderProfile.cpp @@ -0,0 +1,170 @@ +#include "StarMaterialRenderProfile.hpp" +#include "StarLexicalCast.hpp" +#include "StarJsonExtra.hpp" +#include "StarAssets.hpp" +#include "StarImageMetadataDatabase.hpp" +#include "StarRoot.hpp" + +namespace Star { + +EnumMap<MaterialJoinType> const MaterialJoinTypeNames = { + {MaterialJoinType::All, "All"}, {MaterialJoinType::Any, "Any"}, +}; + +MaterialRenderMatchList parseMaterialRenderMatchList(Json const& matchSpec, RuleMap const& ruleMap, PieceMap const& pieceMap, MatchMap const& matchMap) { + MaterialRenderMatchList matchList; + + for (auto const& matchConfig : matchSpec.toArray()) { + MaterialRenderMatchPtr match = make_shared<MaterialRenderMatch>(); + + Json matchPointList = JsonArray(); + if (auto matchAllPoints = matchConfig.opt("matchAllPoints")) { + matchPointList = *matchAllPoints; + match->matchJoin = MaterialJoinType::All; + } else if (auto matchAnyPoints = matchConfig.opt("matchAnyPoints")) { + matchPointList = *matchAnyPoints; + match->matchJoin = MaterialJoinType::Any; + } + + for (auto const& matchPointConfig : matchPointList.iterateArray()) { + MaterialMatchPoint matchPoint; + matchPoint.position = jsonToVec2I(matchPointConfig.get(0)); + if (abs(matchPoint.position[0]) > MaterialRenderProfileMaxNeighborDistance || abs(matchPoint.position[1]) > MaterialRenderProfileMaxNeighborDistance) + throw MaterialRenderProfileException(strf("Match position %s outside of maximum rule distance %s", + matchPoint.position, MaterialRenderProfileMaxNeighborDistance)); + matchPoint.rule = ruleMap.get(matchPointConfig.getString(1)); + match->matchPoints.append(move(matchPoint)); + } + + for (auto const& piece : matchConfig.getArray("pieces", {})) + match->resultingPieces.append({pieceMap.get(piece.getString(0)), jsonToVec2F(piece.get(1))}); + + auto subMatches = matchConfig.get("subMatches", {}); + if (subMatches.isType(Json::Type::String)) + match->subMatches = matchMap.get(subMatches.toString()); + else if (!subMatches.isNull()) + match->subMatches = parseMaterialRenderMatchList(subMatches, ruleMap, pieceMap, matchMap); + + match->requiredLayer = matchConfig.optString("requiredLayer").apply(bind(&EnumMap<TileLayer>::getLeft, &TileLayerNames, _1)); + match->haltOnMatch = matchConfig.getBool("haltOnMatch", false); + match->haltOnSubMatch = matchConfig.getBool("haltOnSubMatch", false); + + match->occlude = matchConfig.optBool("occlude"); + + matchList.append(match); + } + + return matchList; +} + +String MaterialRenderProfile::pieceImage(String const& pieceName, unsigned variant, MaterialColorVariant colorVariant, MaterialHue hueShift) const { + auto const& piece = pieces.get(pieceName); + + String texture = piece->texture; + if (hueShift != MaterialHue()) + texture = strf("%s?hueshift=%s", texture, materialHueToDegrees(hueShift)); + + auto const& rect = piece->variants.get(colorVariant).wrap(variant); + return strf("%s?crop=%s;%s;%s;%s", texture, rect.xMin(), rect.yMin(), rect.xMax(), rect.yMax()); +} + +pair<String, Vec2F> const& MaterialRenderProfile::damageImage(float damageLevel, TileDamageType damageType) const { + if (damageType == TileDamageType::Protected) + return protectedFrames.at(clamp<unsigned>(damageLevel * crackingFrames.size(), 0, crackingFrames.size() - 1)); + return crackingFrames.at(clamp<unsigned>(damageLevel * crackingFrames.size(), 0, crackingFrames.size() - 1)); +} + +MaterialRenderProfile parseMaterialRenderProfile(Json const& spec, String const& relativePath) { + MaterialRenderProfile profile; + + 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.occludesBehind = spec.getBool("occludesBelow", true); + profile.zLevel = spec.getUInt("zLevel", 0); + profile.radiantLight = Color::rgb(jsonToVec3B(spec.get("radiantLight", JsonArray{0, 0, 0}))).toRgbF(); + + profile.representativePiece = spec.getString("representativePiece"); + + for (auto const& pair : spec.get("rules").iterateObject()) { + auto rule = make_shared<MaterialRule>(); + rule->join = MaterialJoinTypeNames.getLeft(pair.second.getString("join", "all")); + for (auto const& ruleEntry : pair.second.getArray("entries", {})) { + bool inverse = ruleEntry.getBool("inverse", false); + String type = ruleEntry.getString("type"); + if (type.equalsIgnoreCase("Connects")) { + rule->entries.append({MaterialRule::RuleConnects(), inverse}); + } else if (type.equalsIgnoreCase("Shadows")) { + rule->entries.append({MaterialRule::RuleShadows(), inverse}); + } else if (type.equalsIgnoreCase("EqualsSelf")) { + rule->entries.append({MaterialRule::RuleEqualsSelf{ruleEntry.getBool("matchHue", false)}, inverse}); + } else if (type.equalsIgnoreCase("EqualsId")) { + rule->entries.append({MaterialRule::RuleEqualsId{(uint16_t)ruleEntry.getUInt("id")}, inverse}); + } else if (type.equalsIgnoreCase("PropertyEquals")) { + rule->entries.append({MaterialRule::RulePropertyEquals{ruleEntry.getString("propertyName"), ruleEntry.get("propertyValue")}, inverse}); + } + } + profile.rules[pair.first] = move(rule); + } + + for (auto const& pair : spec.get("pieces").iterateObject()) { + auto renderPiece = make_shared<MaterialRenderPiece>(); + renderPiece->pieceId = profile.pieces.size(); + + renderPiece->texture = AssetPath::relativeTo(relativePath, pair.second.getString("texture", spec.getString("texture"))); + unsigned variants = pair.second.getUInt("variants", spec.getUInt("variants", 1)); + + Vec2F textureSize = jsonToVec2F(pair.second.get("textureSize")); + Vec2F texturePosition = jsonToVec2F(pair.second.get("texturePosition")); + Vec2F variantStride = jsonToVec2F(pair.second.get("variantStride", JsonArray{0, 0})); + Vec2F colorStride = jsonToVec2F(pair.second.get("colorStride", JsonArray{0, 0})); + + // Need to flip texture coordinates because material rendering configs + // assume top down image coordinates + unsigned imageHeight = Root::singleton().imageMetadataDatabase()->imageSize(renderPiece->texture)[1]; + 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) { + RectF textureRect = RectF::withSize(texturePosition + variantStride * v + colorStride * c, textureSize); + renderPiece->variants[c].append(flipTextureCoordinates(textureRect)); + } + } else { + RectF textureRect = RectF::withSize(texturePosition + variantStride * v, textureSize); + renderPiece->variants[DefaultMaterialColorVariant].append(flipTextureCoordinates(textureRect)); + } + } + + profile.pieces[pair.first] = move(renderPiece); + } + + for (auto const& pair : spec.get("matches").iterateArray()) + profile.matches[pair.getString(0)] = parseMaterialRenderMatchList(pair.get(1), profile.rules, profile.pieces, profile.matches); + + profile.mainMatchList = profile.matches.get("main"); + + // TODO: Completely hard-coded for now + profile.crackingFrames = { + {"/tiles/blockdamage.png:1", Vec2F(0, 0)}, + {"/tiles/blockdamage.png:2", Vec2F(0, 0)}, + {"/tiles/blockdamage.png:3", Vec2F(0, 0)}, + {"/tiles/blockdamage.png:4", Vec2F(0, 0)}, + {"/tiles/blockdamage.png:5", Vec2F(0, 0)} + }; + + profile.protectedFrames = { + {"/tiles/blockprotection.png:1", Vec2F(0, 0)}, + {"/tiles/blockprotection.png:2", Vec2F(0, 0)}, + {"/tiles/blockprotection.png:3", Vec2F(0, 0)}, + {"/tiles/blockprotection.png:4", Vec2F(0, 0)}, + {"/tiles/blockprotection.png:5", Vec2F(0, 0)} + }; + + profile.ruleProperties = spec.get("ruleProperties", JsonObject()); + + return profile; +} + +} |