/
SelectionGroupMerger.h
247 lines (189 loc) · 8.45 KB
/
SelectionGroupMerger.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#pragma once
#include "icomparablenode.h"
#include "imap.h"
#include "iselectiongroup.h"
#include <map>
#include <functional>
#include "NodeUtils.h"
#include "selectionlib.h"
namespace scene
{
namespace merge
{
/**
* Merger class taking care of adjusting the given target scene such that the
* the groups match what is defined in the source scene.
*
* It tries to keep group links between nodes intact if the nodes are only present
* in the base scene (were not removed during the geometry merge phase).
*/
class SelectionGroupMerger
{
private:
IMapRootNodePtr _sourceRoot;
IMapRootNodePtr _baseRoot;
selection::ISelectionGroupManager& _sourceManager;
selection::ISelectionGroupManager& _baseManager;
std::map<std::string, INodePtr> _sourceNodes;
std::map<std::string, INodePtr> _baseNodes;
std::vector<std::size_t> _baseGroupIdsToRemove;
std::stringstream _log;
public:
SelectionGroupMerger(const IMapRootNodePtr& sourceRoot, const IMapRootNodePtr& baseRoot) :
_sourceRoot(sourceRoot),
_baseRoot(baseRoot),
_sourceManager(_sourceRoot->getSelectionGroupManager()),
_baseManager(_baseRoot->getSelectionGroupManager())
{}
std::string getLogMessages() const
{
return _log.str();
}
void adjustBaseGroups()
{
// Collect all source and base nodes for easier lookup
_sourceRoot->foreachNode([&](const INodePtr& node)
{
if (!std::dynamic_pointer_cast<IGroupSelectable>(node)) return true;
_sourceNodes.emplace(NodeUtils::GetGroupMemberFingerprint(node), node);
return true;
});
_log << "Got " << _sourceNodes.size() << " groups in the source map" << std::endl;
_baseRoot->foreachNode([&](const INodePtr& node)
{
if (!std::dynamic_pointer_cast<IGroupSelectable>(node)) return true;
_baseNodes.emplace(NodeUtils::GetGroupMemberFingerprint(node), node);
return true;
});
_log << "Got " << _baseNodes.size() << " in the base map" << std::endl;
_log << "Start Processing base groups" << std::endl;
// Remove all base groups not present in the source scene, unless we decide to keep it
_baseManager.foreachSelectionGroup(
std::bind(&SelectionGroupMerger::processBaseGroup, this, std::placeholders::_1));
_log << "Start Processing source groups" << std::endl;
_sourceManager.foreachSelectionGroup(
std::bind(&SelectionGroupMerger::processSourceGroup, this, std::placeholders::_1));
_log << "Removing " << _baseGroupIdsToRemove.size() << " base groups that have been marked for removal." << std::endl;
// Remove all base groups that are no longer necessary
for (auto baseGroupId : _baseGroupIdsToRemove)
{
_baseManager.deleteSelectionGroup(baseGroupId);
}
// Run a final pass over the node membership to ensure the group sizes are ascending for each node
// Each group on every node is a superset of any group that was set on that node before
ensureGroupSizeOrder();
}
private:
using GroupMembers = std::map<std::string, scene::INodePtr>;
void processBaseGroup(selection::ISelectionGroup& group)
{
// Check if there's a counter-part in the source scene
auto sourceGroup = _sourceManager.getSelectionGroup(group.getId());
// Existing groups are ok, leave them, conflicts will be resolved in processSourceGroup
if (sourceGroup)
{
_log << "Base group " << group.getId() << " is present in source too, skipping." << std::endl;
return;
}
// This base group is no longer present in the source scene,
// we'll remove it, unless it's referenced by nodes which are base-only
// which indicates that the base nodes have explicitly been chosen by the user
// to be kept during the merge operation
std::vector<INodePtr> nodesToRemove;
group.foreachNode([&](const INodePtr& node)
{
auto fingerprint = NodeUtils::GetGroupMemberFingerprint(node);
// All nodes that are also present in the source map are removed from this group
// we only keep the base nodes that are preserved during merge
if (_sourceNodes.count(fingerprint) > 0)
{
nodesToRemove.push_back(node);
}
});
for (const auto& node : nodesToRemove)
{
_log << "Removing node " << node->name() << " from group " <<
group.getId() << ", since it is not exclusive to the base map." << std::endl;
group.removeNode(node);
}
if (group.size() < 2)
{
_log << "Base group " << group.getId() << " ends up with less than two members and is marked for removal." << std::endl;
_baseGroupIdsToRemove.push_back(group.getId());
}
}
void processSourceGroup(selection::ISelectionGroup& group)
{
_log << "Processing source group with ID: " << group.getId() << ", size: " << group.size() << std::endl;
// Make sure the group exists in the base
auto baseGroup = _baseManager.getSelectionGroup(group.getId());
if (!baseGroup)
{
_log << "Creating group with ID " << group.getId() << " in the base map" << std::endl;
baseGroup = _baseManager.createSelectionGroup(group.getId());
}
// Ensure the correct members are in the group, if they are available in the map
auto desiredGroupMembers = getGroupMemberFingerprints(group);
auto currentGroupMembers = getGroupMemberFingerprints(*baseGroup);
std::vector<GroupMembers::value_type> membersToBeRemoved;
std::vector<GroupMembers::value_type> membersToBeAdded;
auto compareFingerprint = [](const GroupMembers::value_type& left, const GroupMembers::value_type& right)
{
return left.first < right.first;
};
std::set_difference(currentGroupMembers.begin(), currentGroupMembers.end(),
desiredGroupMembers.begin(), desiredGroupMembers.end(),
std::back_inserter(membersToBeRemoved), compareFingerprint);
std::set_difference(desiredGroupMembers.begin(), desiredGroupMembers.end(),
currentGroupMembers.begin(), currentGroupMembers.end(),
std::back_inserter(membersToBeAdded), compareFingerprint);
_log << "Members to be added: " << membersToBeAdded.size() << ", members to be removed: " << membersToBeRemoved.size() << std::endl;
for (const auto& pair : membersToBeRemoved)
{
_log << "Removing node " << pair.second->name() << " from group " << group.getId() << std::endl;
baseGroup->removeNode(pair.second);
}
for (const auto& pair : membersToBeAdded)
{
_log << "Adding node " << pair.second->name() << " to group " << group.getId() << std::endl;
baseGroup->addNode(pair.second);
}
}
GroupMembers getGroupMemberFingerprints(selection::ISelectionGroup& group)
{
GroupMembers members;
group.foreachNode([&](const INodePtr& member)
{
members.emplace(NodeUtils::GetGroupMemberFingerprint(member), member);
});
return members;
}
void ensureGroupSizeOrder()
{
std::map<std::size_t, std::size_t> baseGroupSizes;
_baseManager.foreachSelectionGroup([&](selection::ISelectionGroup& group)
{
baseGroupSizes.emplace(group.getId(), group.size());
});
_log << "Checking size ordering, got " << baseGroupSizes.size() << " base groups" << std::endl;
_baseRoot->foreachNode([&](const INodePtr& node)
{
auto selectable = std::dynamic_pointer_cast<IGroupSelectable>(node);
if (!selectable) return true;
auto copiedSet = selectable->getGroupIds();
std::sort(copiedSet.begin(), copiedSet.end(), [&](std::size_t a, std::size_t b)
{
return baseGroupSizes[a] < baseGroupSizes[b];
});
if (copiedSet != selectable->getGroupIds())
{
_log << "Group membership order in node " << node->name() << " has changed." << std::endl;
// Re-assign the group to the sorted set
selection::assignNodeToSelectionGroups(node, copiedSet);
}
return true;
});
}
};
}
}