Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_PrefabBoneSnappingTool.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2[WorkbenchToolAttribute(name: "Prefab Bone-Snapping Tool", description: "Drag & drop Prefabs from the Resource Browser\ninto the viewport to snap them to the nearest bone", awesomeFontCode: 0xF4C4)]
3class SCR_PrefabBoneSnappingTool : WorldEditorTool
4{
5 [Attribute(defvalue: "1", desc: "Debug shapes (bone locations and names) are drawn as debug shapes")]
6 protected bool m_bDrawDebugShapes;
7
8 [Attribute(defvalue: "0.1", desc: "Bone's debug sphere radius [m]", uiwidget: UIWidgets.Slider, params: "0.1 10 0.1")]
9 protected float m_fBoneDebugSphereRadius;
10
11 protected IEntitySource m_WorldEntitySource;
12 protected IEntitySource m_EditedEntitySource;
13 protected const ref map<string, ref array<vector>> BONE_MATRICES = new map<string, ref array<vector>>();
14 protected const ref map<string, IEntitySource> BONE_ENTITYSOURCES = new map<string, IEntitySource>();
15 protected const ref map<string, ref DebugTextScreenSpace> BONE_DEBUGTEXTS = new map<string, ref DebugTextScreenSpace>();
16 protected const ref map<string, Shape> BONE_DEBUGSHAPES = new map<string, Shape>();
17 // matrix[0] = vectorRight
18 // matrix[1] = vectorUp
19 // matrix[2] = vectorDir
20 // matrix[3] = pos
21
22 protected int m_iX = INVALID_CURSOR_POS;
23 protected int m_iY = INVALID_CURSOR_POS;
24
25 protected ref SCR_DebugShapeManager m_DebugShapeManager = new SCR_DebugShapeManager();
26
27 protected static const int INVALID_CURSOR_POS = -1;
28
29 protected static const int BONE_SPHERE_COLOUR = 0x8855FF00;
30 protected static const int BONE_SPHERE_COLOUR_HIGHLIGHT = 0xFFFFFF00;
31
32 protected static const float BONE_TEXT_SIZE = 12; // 2D size, so in pixels
33 protected static const int BONE_TEXT_COLOUR = 0xFFFFFFFF;
34 protected static const int BONE_TEXT_COLOUR_BACKGROUND = 0xFF000000;
35
36 protected static const string SCENE_ROOT_BONE = "Scene_Root";
37 protected static const int ROOT_BONE_SPHERE_COLOUR = 0x88FFAA55;
38 protected static const float ROOT_BONE_SPHERE_RADIUS = 0.05;
39
40 //------------------------------------------------------------------------------------------------
42 protected IEntitySource GetDragAndDroppedEntitySource()
43 {
44 IEntitySource entitySource;
45 for (int i = m_API.GetEditorEntityCount() - 1; i >= 0; --i)
46 {
47 entitySource = m_API.GetEditorEntity(i);
48 if (entitySource == m_EditedEntitySource)
49 continue;
50
51 if (entitySource == m_WorldEntitySource)
52 continue;
53
54 return entitySource; // always pick the last one
55 }
56
57 return null;
58 }
59
60 //------------------------------------------------------------------------------------------------
61 protected void RefreshState(bool showError)
62 {
63 RefreshEntitySources(showError);
64 if (!m_EditedEntitySource)
65 return;
66
67 RefreshBoneMatrixMap();
68 RefreshDebugShapes();
69 RefreshBoneEntitySourceMap();
70 }
71
72 //------------------------------------------------------------------------------------------------
74 protected bool RefreshEntitySources(bool showError)
75 {
76 int entityCount = m_API.GetEditorEntityCount();
77 if (entityCount < 2)
78 {
79 if (showError)
80 {
81 string text = "Not enough or too many Entities at world's root in Prefab Edit mode; fix that then click Refresh";
82 Workbench.Dialog("", text);
83 Print(text, LogLevel.WARNING);
84 Debug.DumpStack(text);
85 Print(text, LogLevel.WARNING);
86 }
87
88 return false;
89 }
90
91 // let's see how strong this is
92 m_WorldEntitySource = m_API.GetEditorEntity(0);
93 m_EditedEntitySource = m_API.GetEditorEntity(1);
94 if (!m_EditedEntitySource || !m_EditedEntitySource.GetAncestor())
95 {
96 Print("Edited Entity Source is not a Prefab", LogLevel.ERROR);
97 m_EditedEntitySource = null;
98 return false;
99 }
100
101 return true;
102 }
103
104 //------------------------------------------------------------------------------------------------
108 protected void RefreshBoneMatrixMap()
109 {
110 IEntity entity = m_API.SourceToEntity(m_EditedEntitySource);
111 if (!entity)
112 {
113 Print(m_EditedEntitySource, LogLevel.WARNING);
114 Print(entity, LogLevel.WARNING);
115 return;
116 }
117
118 BONE_MATRICES.Clear();
119
120 Animation animation = entity.GetAnimation();
121 if (!animation)
122 return;
123
124 array<string> boneNames = {};
125 animation.GetBoneNames(boneNames);
126 vector mat[4];
127 foreach (string boneName : boneNames)
128 {
129 TNodeId boneIndex = animation.GetBoneIndex(boneName);
130
131 if (!animation.GetBoneMatrix(boneIndex, mat))
132 {
133 Print("cannot get bone " + boneName + "'s matrix", LogLevel.WARNING);
134 continue;
135 }
136
137 BONE_MATRICES.Insert(boneName, { mat[0], mat[1], mat[2], mat[3] });
138 }
139 }
140
141 //------------------------------------------------------------------------------------------------
142 protected void RefreshDebugShapes()
143 {
145 BONE_DEBUGTEXTS.Clear();
146 BONE_DEBUGSHAPES.Clear();
148 return;
149
150 BaseWorld world = m_API.GetWorld();
151// DebugTextWorldSpace text;
152 DebugTextScreenSpace debugText;
153 Shape debugShape;
154 foreach (string boneName, array<vector> matrix : BONE_MATRICES)
155 {
156// text = m_DebugShapeManager.AddText(boneName, matrix[3] + vector.Up * (m_fBoneDebugSphereRadius + BONE_TEXT_SIZE), BONE_TEXT_SIZE);
157// text.SetText(string.Empty);
158// BONE_DEBUGTEXTS.Insert(boneName, text);
159
160 debugText = DebugTextScreenSpace.Create(world, boneName, DebugTextFlags.FACE_CAMERA, 1, 1, BONE_TEXT_SIZE, BONE_TEXT_COLOUR, BONE_TEXT_COLOUR_BACKGROUND);
161 BONE_DEBUGTEXTS.Insert(boneName, debugText);
162
163 if (boneName == SCENE_ROOT_BONE)
164 debugShape = m_DebugShapeManager.AddSphere(matrix[3], m_fBoneDebugSphereRadius * 0.5, ROOT_BONE_SPHERE_COLOUR, ShapeFlags.NOOUTLINE);
165 else
166 debugShape = m_DebugShapeManager.AddSphere(matrix[3], m_fBoneDebugSphereRadius, BONE_SPHERE_COLOUR, ShapeFlags.NOOUTLINE);
167
168 BONE_DEBUGSHAPES.Insert(boneName, debugShape);
169 }
170 }
171
172 //------------------------------------------------------------------------------------------------
175 protected void RefreshBoneEntitySourceMap()
176 {
177 BONE_ENTITYSOURCES.Clear();
178 array<IEntitySource> children = {};
179 IEntitySource child;
180 vector childRelPos;
181 for (int i, count = m_EditedEntitySource.GetNumChildren(); i < count; ++i)
182 {
183 child = m_EditedEntitySource.GetChild(i);
184 if (!child.Get("coords", childRelPos))
185 continue;
186
187 foreach (string boneName, array<vector> matrix : BONE_MATRICES)
188 {
189 if (childRelPos == matrix[3]) // pos only
190 {
191 BONE_ENTITYSOURCES.Insert(boneName, child);
192 break;
193 }
194 }
195 }
196 }
197
198 //------------------------------------------------------------------------------------------------
201 protected void SnapNewEntitySource(notnull IEntitySource newEntitySource)
202 {
203 IEntitySource parent = newEntitySource.GetParent();
204 if (parent)
205 return;
206
207 vector newEntitySourcePos;
208 if (!newEntitySource.Get("coords", newEntitySourcePos))
209 return;
210
211 int boneCount = BONE_MATRICES.Count();
212 if (boneCount < 2) // set as child and leave
213 {
214 if (boneCount > 0 && BONE_MATRICES.GetKey(0) == SCENE_ROOT_BONE)
215 --boneCount;
216
217 if (boneCount < 1)
218 {
219 m_API.MoveEntitiesToPrefab(m_EditedEntitySource, m_EditedEntitySource.GetAncestor(), { newEntitySource });
220 return;
221 }
222 }
223
224 ArmaReforgerScripted game = GetGame();
225 if (!game)
226 return;
227
228 WorkspaceWidget workspace = game.GetWorkspace();
229 if (!workspace)
230 return;
231
232 BaseWorld world = m_API.GetWorld();
233 vector entityScreenPos = workspace.ProjWorldToScreen(newEntitySourcePos, world);
234 entityScreenPos = { entityScreenPos[0], 0, entityScreenPos[1] };
235
236 string selectedBoneName;
237 array<vector> selectedMatrix;
238 float resultDistanceSq = float.INFINITY;
239 foreach (string boneName, array<vector> matrix : BONE_MATRICES)
240 {
241 if (boneName == SCENE_ROOT_BONE) // do not snap to scene root bone
242 continue;
243
244 vector boneScreenPos = workspace.ProjWorldToScreen(matrix[3], world);
245 boneScreenPos = { boneScreenPos[0], 0, boneScreenPos[1] };
246
247 float distanceSq = vector.DistanceSqXZ(entityScreenPos, boneScreenPos);
248 if (distanceSq < resultDistanceSq)
249 {
250 selectedBoneName = boneName;
251 selectedMatrix = matrix;
252 resultDistanceSq = distanceSq;
253 }
254 }
255
256 if (!selectedMatrix)
257 return;
258
259 if (BONE_ENTITYSOURCES.Get(selectedBoneName))
260 {
261 PrintFormat("Bone %1 is already occupied by an entity - move or remove it first", level: LogLevel.NORMAL);
262 m_API.SetEntitySelection(BONE_ENTITYSOURCES.Get(selectedBoneName));
263 return;
264 }
265
266 vector mat[3] = {
267 selectedMatrix[0],
268 selectedMatrix[1],
269 selectedMatrix[2],
270 };
271
272 vector angles = Math3D.MatrixToAngles(mat);
273
274 m_API.BeginEntityAction();
275 m_API.SetVariableValue(newEntitySource, null, "coords", selectedMatrix[3].ToString(false));
276 m_API.SetVariableValue(newEntitySource, null, "angles", string.Format("%1 %2 %3", angles[1], angles[0], angles[2]));
277 m_API.MoveEntitiesToPrefab(m_EditedEntitySource, m_EditedEntitySource.GetAncestor(), { newEntitySource });
278 m_API.EndEntityAction();
279
280 BONE_ENTITYSOURCES.Set(selectedBoneName, newEntitySource);
281 }
282
283 //------------------------------------------------------------------------------------------------
284 protected void UpdateDebugShapes(float x, float y)
285 {
286 // refresh debug texts to only display those close to the cursor
287 if (BONE_DEBUGTEXTS.IsEmpty())
288 return;
289
290 BaseWorld world = m_API.GetWorld();
291 WorkspaceWidget workspace = GetGame().GetWorkspace();
292
293 vector traceStart, traceEnd, traceDir;
294 if (!m_API.TraceWorldPos(x, y, TraceFlags.WORLD, traceStart, traceEnd, traceDir))
295 traceEnd = traceStart + traceDir * 50;
296
297// vector cursorPos2D = workspace.ProjWorldToScreen(traceEnd, world);
298// vector debugTextPos2D = workspace.ProjWorldToScreen(matrix[3], world);
299// float distance = vector.Distance(cursorPos, debugTextPos);
300// if (vector.Distance(cursorPos2D, debugTextPos2D) < 100)
301
302 // DebugTextWorldSpace debugText;
303 DebugTextScreenSpace debugText;
304 foreach (string boneName, array<vector> matrix : BONE_MATRICES)
305 {
306 debugText = BONE_DEBUGTEXTS.Get(boneName);
307 if (!debugText)
308 break;
309
310 if (Math3D.PointLineSegmentDistanceSqr(matrix[3], traceStart, traceEnd) <= m_fBoneDebugSphereRadius * m_fBoneDebugSphereRadius)
311 {
312 debugText.SetText(boneName);
313 BONE_DEBUGSHAPES.Get(boneName).SetColor(BONE_SPHERE_COLOUR_HIGHLIGHT);
314 }
315 else
316 {
317 debugText.SetText(string.Empty);
318 if (boneName == SCENE_ROOT_BONE)
319 BONE_DEBUGSHAPES.Get(boneName).SetColor(ROOT_BONE_SPHERE_COLOUR);
320 else
321 BONE_DEBUGSHAPES.Get(boneName).SetColor(BONE_SPHERE_COLOUR);
322 }
323
324 }
325 }
326
327 //------------------------------------------------------------------------------------------------
328 //------------------------------------------------------------------------------------------------
329 // World Editor Tool events below
330 //------------------------------------------------------------------------------------------------
331 //------------------------------------------------------------------------------------------------
332
333 //------------------------------------------------------------------------------------------------
334 protected override void OnKeyPressEvent(KeyCode key, bool isAutoRepeat)
335 {
336 // in all modes
337 if (key == KeyCode.KC_ESCAPE)
338 {
339 m_API.ClearEntitySelection();
340 return;
341 }
342 }
343
344// protected override void OnKeyReleaseEvent(KeyCode key, bool isAutoRepeat);
345// protected override void OnEnterEvent(); // drag & drop triggers that, useless
346
347 //------------------------------------------------------------------------------------------------
348 protected override void OnLeaveEvent()
349 {
350 if (!m_EditedEntitySource)
351 return;
352
353 m_iX = INVALID_CURSOR_POS;
354 m_iY = INVALID_CURSOR_POS;
355 }
356
357 //------------------------------------------------------------------------------------------------
358 protected override void OnMouseMoveEvent(float x, float y)
359 {
360 if (!m_EditedEntitySource)
361 return;
362
364 UpdateDebugShapes(x, y);
365
366 if (m_iX != INVALID_CURSOR_POS && m_iY != INVALID_CURSOR_POS)
367 return;
368
369 m_iX = x;
370 m_iY = y;
371
372 IEntitySource newEntitySource = GetDragAndDroppedEntitySource();
373 if (!newEntitySource)
374 return;
375
376 Print("added " + newEntitySource.GetAncestor().GetResourceName(), LogLevel.DEBUG);
377 SnapNewEntitySource(newEntitySource);
378 }
379
380 //------------------------------------------------------------------------------------------------
381// protected override void OnMouseDoubleClickEvent(float x, float y, WETMouseButtonFlag buttons);
382
383 //------------------------------------------------------------------------------------------------
384 protected override void OnMousePressEvent(float x, float y, WETMouseButtonFlag buttons)
385 {
386 if (m_API.IsPrefabEditMode())
387 return; // normal behaviour please
388
389 // world mode
390
391 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
392 if (!worldEditor)
393 return;
394
395 // deselect everything and select the entity under the cursor
396 m_API.ClearEntitySelection();
397
398 IEntity entity;
399 vector traceStart, traceEnd, traceDir;
400 if (!m_API.TraceWorldPos(x, y, TraceFlags.ENTS, traceStart, traceEnd, traceDir, entity))
401 return;
402
403 if (!entity)
404 return;
405
406 IEntitySource entitySource = m_API.EntityToSource(entity);
407 if (!entitySource)
408 return;
409
410 m_API.SetEntitySelection(entitySource);
411 }
412
413// protected override void OnMouseReleaseEvent(float x, float y, WETMouseButtonFlag buttons); // this event is not triggered on Prefab drag & drop
414// protected override void OnWheelEvent(int delta);
415
416 //------------------------------------------------------------------------------------------------
417 protected override void OnActivate()
418 {
419 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
420 if (!worldEditor || !worldEditor.IsPrefabEditMode())
421 return;
422
423 RefreshState(false);
424 }
425
426 //------------------------------------------------------------------------------------------------
427 protected override void OnDeActivate()
428 {
430 }
431
432 //------------------------------------------------------------------------------------------------
433 // entities are not yet loaded
434 protected override void OnAfterLoadWorld()
435 {
436 RefreshState(false);
437 }
438
439 //------------------------------------------------------------------------------------------------
440 protected override void OnBeforeUnloadWorld()
441 {
442 m_EditedEntitySource = null;
443 }
444
445 //------------------------------------------------------------------------------------------------
446 [ButtonAttribute("Refresh")]
447 protected void ButtonRefresh()
448 {
449 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
450 if (!worldEditor)
451 return;
452
453 if (!worldEditor.IsPrefabEditMode())
454 return;
455
456 RefreshState(true);
457 }
458
459 //------------------------------------------------------------------------------------------------
460 [ButtonAttribute("Open world-selected Prefab in Edit Mode")]
461 protected void ButtonOpenPrefabInEditMode()
462 {
463 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
464 if (!worldEditor)
465 return;
466
467 if (worldEditor.IsPrefabEditMode())
468 return;
469
470 WorldEditorAPI worldEditorAPI = worldEditor.GetApi();
471 if (!worldEditorAPI)
472 return;
473
474 if (worldEditorAPI.GetSelectedEntitiesCount() != 1)
475 return;
476
477 IEntitySource selectedEntitySource = worldEditorAPI.GetSelectedEntity(0);
478 if (!selectedEntitySource)
479 return;
480
481 while (selectedEntitySource.GetParent())
482 {
483 selectedEntitySource = selectedEntitySource.GetParent();
484 }
485
486 m_API.SetEntitySelection(selectedEntitySource);
487
488 IEntitySource ancestorSource = selectedEntitySource.GetAncestor();
489 if (!ancestorSource)
490 {
491 Print("Selected entity is not a Prefab", LogLevel.NORMAL);
492 return;
493 }
494
495 ResourceName resourceName = ancestorSource.GetResourceName();
496 if (!resourceName) // .IsEmpty()
497 return;
498
499 if (!Workbench.ScriptDialog("", "This will open the selected Prefab in Prefab Edit mode. Continue?\nSelected Prefab: " + resourceName, new WorkbenchDialog_OKCancel()))
500 return;
501
502 if (!SCR_WorldEditorToolHelper.GetWorldEditor().SetOpenedResource(resourceName))
503 {
504 Print("Failed to open resource", LogLevel.WARNING);
505 return;
506 }
507
508 RefreshState(false);
509 }
510}
511#endif // WORKBENCH
ref DSGameConfig game
Definition DSConfig.c:81
bool m_bDrawDebugShapes
ArmaReforgerScripted GetGame()
Definition game.c:1398
ref array< string > angles
ResourceName resourceName
Definition SCR_AIGroup.c:66
override void OnActivate()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
ref SCR_DebugShapeManager m_DebugShapeManager
enum EVehicleType IEntity
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto external Animation GetAnimation()
void Clear()
Remove all stored shapes and texts.
Shape AddSphere(vector centre, float radius, int colour=DEFAULT_SHAPE_COLOUR, ShapeFlags additionalFlags=0)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
DebugTextFlags
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
ShapeFlags
Definition ShapeFlags.c:13
proto void PrintFormat(string fmt, void param1=NULL, void param2=NULL, void param3=NULL, void param4=NULL, void param5=NULL, void param6=NULL, void param7=NULL, void param8=NULL, void param9=NULL, LogLevel level=LogLevel.NORMAL)
SCR_FieldOfViewSettings Attribute
int TNodeId
Node global id is a hash of name of the node.
Definition EnEntity.c:18
KeyCode
Definition KeyCode.c:13
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
void Debug()
Definition Types.c:327
TraceFlags
Definition TraceFlags.c:13