Skip to content

Commit a85dcca

Browse files
author
Adrien GIVRY
committed
Improving the actor duplication naming system
Previously, duplicating an actor name would add '(Copy)' after the name of the duplicated actor. However, it resulted into sometimes having multiple `(Copy) (Copy) (Copy)`. To prevent that, the behaviour of Unity has been mimicked (Incremental ID generated after name : `(1)`, `(2)`...).
1 parent cb7997e commit a85dcca

1 file changed

Lines changed: 68 additions & 6 deletions

File tree

Sources/Overload/OvEditor/src/OvEditor/Core/EditorActions.cpp

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,62 @@ bool OvEditor::Core::EditorActions::DestroyActor(OvCore::ECS::Actor & p_actor)
566566
return true;
567567
}
568568

569+
std::string FindDuplicatedActorUniqueName(OvCore::ECS::Actor& p_duplicated, OvCore::ECS::Actor& p_newActor, OvCore::SceneSystem::Scene& p_scene)
570+
{
571+
const auto sourceName = p_duplicated.GetName();
572+
auto sourceNameWithoutSuffix = sourceName;
573+
574+
auto suffixOpeningParenthesisPos = std::string::npos;
575+
auto suffixClosingParenthesisPos = std::string::npos;
576+
577+
// Keep track of the current character position when iterating onto `sourceName`
578+
auto currentPos = decltype(std::string::npos){sourceName.length() - 1};
579+
580+
// Here we search for `(` and `)` positions. (Needed to extract the number between those parenthesis)
581+
for (auto it = sourceName.rbegin(); it < sourceName.rend(); ++it, --currentPos)
582+
{
583+
auto c = *it;
584+
585+
if (suffixClosingParenthesisPos == std::string::npos && c == ')') suffixClosingParenthesisPos = currentPos;
586+
if (suffixClosingParenthesisPos != std::string::npos && c == '(') suffixOpeningParenthesisPos = currentPos;
587+
}
588+
589+
// We need to declare our `duplicationCounter` here to store the number between found parenthesis OR 1 (In the case no parenthesis, AKA, suffix, has been found)
590+
auto duplicationCounter = uint32_t{ 1 };
591+
592+
// If the two parenthis have been found AND the closing parenthesis is the last character AND there is a space before the opening parenthesis
593+
if (suffixOpeningParenthesisPos != std::string::npos && suffixClosingParenthesisPos == sourceName.length() - 1 && suffixOpeningParenthesisPos > 0 && sourceName[suffixOpeningParenthesisPos - 1] == ' ')
594+
{
595+
// Extract the string between those parenthesis
596+
const auto between = sourceName.substr(suffixOpeningParenthesisPos + 1, suffixClosingParenthesisPos - suffixOpeningParenthesisPos - 1);
597+
598+
// If the `between` string is composed of digits (AKA, `between` is a number)
599+
if (!between.empty() && std::find_if(between.begin(), between.end(), [](unsigned char c) { return !std::isdigit(c); }) == between.end())
600+
{
601+
duplicationCounter = static_cast<uint32_t>(std::atoi(between.c_str()));
602+
sourceNameWithoutSuffix = sourceName.substr(0, suffixOpeningParenthesisPos - 1);
603+
}
604+
}
605+
606+
const auto parent = p_newActor.GetParent();
607+
const auto adjacentActors = parent ? parent->GetChildren() : p_scene.GetActors();
608+
609+
auto foundName = sourceNameWithoutSuffix;
610+
611+
// Lambda that checks if the current `foundName` is used by the actor given in parameter (Also ensure that the given actor is adjacent to our new actor)
612+
// We call "adjacent" two actors that shares the same hierarchical level. (Ex: If [A] and [B] are both direction children of [C], then, they are adjacents)
613+
const auto isActorNameTaken = [&foundName, parent](auto actor) { return (parent || !actor->GetParent()) && actor->GetName() == foundName; };
614+
615+
// While there is an adjacent actor with the current `foundName`, we keep generating new names
616+
while (std::find_if(adjacentActors.begin(), adjacentActors.end(), isActorNameTaken) != adjacentActors.end())
617+
{
618+
// New names are composed of the `sourceNameWithoutSuffix` (Ex: "Cube (1)" name without suffix is "Cube")
619+
foundName = sourceNameWithoutSuffix + " (" + std::to_string(duplicationCounter++) + ")";
620+
}
621+
622+
return foundName;
623+
}
624+
569625
void OvEditor::Core::EditorActions::DuplicateActor(OvCore::ECS::Actor & p_toDuplicate, OvCore::ECS::Actor* p_forcedParent, bool p_focus)
570626
{
571627
tinyxml2::XMLDocument doc;
@@ -582,12 +638,18 @@ void OvEditor::Core::EditorActions::DuplicateActor(OvCore::ECS::Actor & p_toDupl
582638
newActor.SetParent(*p_forcedParent);
583639
else
584640
{
585-
newActor.SetName(p_toDuplicate.GetName() + " (Copy)"); // Dont overload the name if it has a forced parent
586-
if (newActor.GetParentID() > 0)
587-
{
588-
if (auto found = m_context.sceneManager.GetCurrentScene()->FindActorByID(newActor.GetParentID()); found)
589-
newActor.SetParent(*found);
590-
}
641+
auto currentScene = m_context.sceneManager.GetCurrentScene();
642+
643+
if (newActor.GetParentID() > 0)
644+
{
645+
if (auto found = currentScene->FindActorByID(newActor.GetParentID()); found)
646+
{
647+
newActor.SetParent(*found);
648+
}
649+
}
650+
651+
const auto uniqueName = FindDuplicatedActorUniqueName(p_toDuplicate, newActor, *currentScene);
652+
newActor.SetName(uniqueName);
591653
}
592654

593655
if (p_focus)

0 commit comments

Comments
 (0)