mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-08-04 11:55:07 -04:00
The Stranglethorn Vale troll ruins sway like trees because "thorn" is a foliage token and it sits inside "Stranglethorn". Every StranglethornRuins piece, and the cliff rocks with it, was classified as foliage and handed to the wind shader. Foliage tokens have to be matched as substrings — model names run words together with no separator, so StranglethornFern01 would not match on a word boundary — and that is what lets a short token land inside an unrelated word. An audit of every M2 in the data turned up the same fault well beyond the reported one: "corn" inside Corner put wind on wall and arch corners, "hops" inside ShopSign on shop signs, "tree" inside StreetSign on street signs, "crop" inside Outcrop on rock outcrops, "herb" inside Herbalism on profession signs, "vine" inside DivineShield. A list of exceptions would not hold, so rank the matches instead. These names are head-final compounds — the last word says what the model is — so the match ending furthest right wins, with a longer token taking a tie at the same end position, which is how "corner" beats the "corn" inside it. StranglethornRuins is then a ruin while DustwallowTree, StoneTree and DeadwindPassRockTree stay trees, none of which a veto list would have managed. 73 models stop swaying; no plant loses its wind. isFoliageLike also drives collision and animation, so these props were walk-through with their animation disabled as well.
99 lines
4.3 KiB
C++
99 lines
4.3 KiB
C++
#include <catch_amalgamated.hpp>
|
|
|
|
#include "rendering/m2_model_classifier.hpp"
|
|
|
|
#include <string>
|
|
|
|
using wowee::rendering::classifyM2Model;
|
|
|
|
namespace {
|
|
|
|
// Bounds and counts stand in for a mid-size doodad. None of the cases below
|
|
// turn on geometry — they are all decided by the name — but the classifier
|
|
// needs something plausible to reason about.
|
|
wowee::rendering::M2ClassificationResult classify(const std::string& name,
|
|
float horiz = 3.0f,
|
|
float vert = 3.0f) {
|
|
return classifyM2Model(name,
|
|
glm::vec3(-horiz * 0.5f, -horiz * 0.5f, 0.0f),
|
|
glm::vec3(horiz * 0.5f, horiz * 0.5f, vert),
|
|
500, 0);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Foliage tokens are substring-matched because model names concatenate words
|
|
// with no separator, so a short token can land inside an unrelated word. Every
|
|
// name here is a real asset that swayed in the wind because of it.
|
|
TEST_CASE("rigid props whose names contain a foliage token do not sway",
|
|
"[m2][classifier][foliage]") {
|
|
SECTION("zone name inside the filename") {
|
|
// "thorn" inside Stranglethorn — the Stranglethorn Vale troll ruins.
|
|
for (const char* n : {"STRANGLETHORNRUINS01", "StranglethornRuins14",
|
|
"stranglethornruins21", "StranglethornRuins_Pylon",
|
|
"StranglethornCliffRock02"}) {
|
|
INFO(n);
|
|
CHECK_FALSE(classify(n).isFoliageLike);
|
|
}
|
|
}
|
|
|
|
SECTION("token inside a longer structural word") {
|
|
// "corn" in Corner, "hops" in ShopSign, "crop" in Outcrop,
|
|
// "herb" in Herbalism, "tree" in StreetSign, "melon"/"vine" likewise.
|
|
CHECK_FALSE(classify("Azjol_Wall_Corner").isFoliageLike);
|
|
CHECK_FALSE(classify("AquaductStone_Corner1").isFoliageLike);
|
|
CHECK_FALSE(classify("ZulDrak_Ruin_CornerTall01").isFoliageLike);
|
|
CHECK_FALSE(classify("WineShopSign01").isFoliageLike);
|
|
CHECK_FALSE(classify("HumanMagicShopSign").isFoliageLike);
|
|
CHECK_FALSE(classify("AeriePeaksRockOutcrop01").isFoliageLike);
|
|
CHECK_FALSE(classify("BE_Signs_Herbalism").isFoliageLike);
|
|
CHECK_FALSE(classify("Dwarfsign_Herbalist").isFoliageLike);
|
|
CHECK_FALSE(classify("GnomeStreetSign01").isFoliageLike);
|
|
CHECK_FALSE(classify("UldamanStreetSign").isFoliageLike);
|
|
CHECK_FALSE(classify("DivineShield_Low_Base").isFoliageLike);
|
|
}
|
|
|
|
SECTION("full paths are matched on the basename") {
|
|
CHECK_FALSE(classify("WORLD\\AZEROTH\\STRANGLETHORN\\PASSIVEDOODADS"
|
|
"\\RUINS\\STRANGLETHORNRUINS03.M2").isFoliageLike);
|
|
}
|
|
}
|
|
|
|
// The fix ranks matches by where they end, so a structural word only wins when
|
|
// it comes last. These names carry one too, and are still plants.
|
|
TEST_CASE("plants keep swaying when a structural word comes first",
|
|
"[m2][classifier][foliage]") {
|
|
SECTION("place name first, plant last") {
|
|
for (const char* n : {"DustwallowTree04", "DustwallowBush01",
|
|
"DustwallowShrub03", "StoneTree06",
|
|
"BurntStoneTree07", "DeadwindPassRockTree02",
|
|
"AO_BridgeTree01"}) {
|
|
INFO(n);
|
|
CHECK(classify(n, 8.0f, 9.0f).isFoliageLike);
|
|
}
|
|
}
|
|
|
|
SECTION("ordinary foliage is unaffected") {
|
|
for (const char* n : {"StranglethornFern01", "StranglethornPlant02",
|
|
"ElwynnMelon01", "G_Watermelon",
|
|
"NorthshireBush01", "DesolaceCactus02"}) {
|
|
INFO(n);
|
|
CHECK(classify(n).isFoliageLike);
|
|
}
|
|
}
|
|
}
|
|
|
|
// isFoliageLike also drives collision and animation, so a false positive did
|
|
// more than add wind: it made ruins walk-through and froze their animation.
|
|
TEST_CASE("a ruin misread as foliage would also lose its collision",
|
|
"[m2][classifier][foliage]") {
|
|
const auto ruin = classify("StranglethornRuins07", 9.0f, 7.0f);
|
|
CHECK_FALSE(ruin.isFoliageLike);
|
|
CHECK_FALSE(ruin.collisionNoBlock);
|
|
CHECK_FALSE(ruin.disableAnimation);
|
|
|
|
const auto tree = classify("DustwallowTree04", 9.0f, 7.0f);
|
|
CHECK(tree.isFoliageLike);
|
|
CHECK(tree.disableAnimation);
|
|
}
|