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
5 [
Attribute(defvalue:
"1",
desc:
"Debug shapes (bone locations and names) are drawn as debug shapes")]
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;
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>();
22 protected int m_iX = INVALID_CURSOR_POS;
23 protected int m_iY = INVALID_CURSOR_POS;
27 protected static const int INVALID_CURSOR_POS = -1;
29 protected static const int BONE_SPHERE_COLOUR = 0x8855FF00;
30 protected static const int BONE_SPHERE_COLOUR_HIGHLIGHT = 0xFFFFFF00;
32 protected static const float BONE_TEXT_SIZE = 12;
33 protected static const int BONE_TEXT_COLOUR = 0xFFFFFFFF;
34 protected static const int BONE_TEXT_COLOUR_BACKGROUND = 0xFF000000;
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;
42 protected IEntitySource GetDragAndDroppedEntitySource()
44 IEntitySource entitySource;
45 for (
int i = m_API.GetEditorEntityCount() - 1; i >= 0; --i)
47 entitySource = m_API.GetEditorEntity(i);
48 if (entitySource == m_EditedEntitySource)
51 if (entitySource == m_WorldEntitySource)
61 protected void RefreshState(
bool showError)
63 RefreshEntitySources(showError);
64 if (!m_EditedEntitySource)
67 RefreshBoneMatrixMap();
69 RefreshBoneEntitySourceMap();
74 protected bool RefreshEntitySources(
bool showError)
76 int entityCount = m_API.GetEditorEntityCount();
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);
84 Debug.DumpStack(text);
92 m_WorldEntitySource = m_API.GetEditorEntity(0);
93 m_EditedEntitySource = m_API.GetEditorEntity(1);
94 if (!m_EditedEntitySource || !m_EditedEntitySource.GetAncestor())
96 Print(
"Edited Entity Source is not a Prefab",
LogLevel.ERROR);
97 m_EditedEntitySource = null;
108 protected void RefreshBoneMatrixMap()
110 IEntity entity = m_API.SourceToEntity(m_EditedEntitySource);
118 BONE_MATRICES.Clear();
124 array<string> boneNames = {};
125 animation.GetBoneNames(boneNames);
127 foreach (
string boneName : boneNames)
129 TNodeId boneIndex = animation.GetBoneIndex(boneName);
131 if (!animation.GetBoneMatrix(boneIndex, mat))
133 Print(
"cannot get bone " + boneName +
"'s matrix",
LogLevel.WARNING);
137 BONE_MATRICES.Insert(boneName, { mat[0], mat[1], mat[2], mat[3] });
142 protected void RefreshDebugShapes()
145 BONE_DEBUGTEXTS.Clear();
146 BONE_DEBUGSHAPES.Clear();
150 BaseWorld world = m_API.GetWorld();
152 DebugTextScreenSpace debugText;
154 foreach (
string boneName, array<vector> matrix : BONE_MATRICES)
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);
163 if (boneName == SCENE_ROOT_BONE)
168 BONE_DEBUGSHAPES.Insert(boneName, debugShape);
175 protected void RefreshBoneEntitySourceMap()
177 BONE_ENTITYSOURCES.Clear();
178 array<IEntitySource> children = {};
181 for (
int i, count = m_EditedEntitySource.GetNumChildren(); i < count; ++i)
183 child = m_EditedEntitySource.GetChild(i);
184 if (!child.Get(
"coords", childRelPos))
187 foreach (
string boneName, array<vector> matrix : BONE_MATRICES)
189 if (childRelPos == matrix[3])
191 BONE_ENTITYSOURCES.Insert(boneName, child);
201 protected void SnapNewEntitySource(notnull IEntitySource newEntitySource)
203 IEntitySource parent = newEntitySource.GetParent();
207 vector newEntitySourcePos;
208 if (!newEntitySource.Get(
"coords", newEntitySourcePos))
211 int boneCount = BONE_MATRICES.Count();
214 if (boneCount > 0 && BONE_MATRICES.GetKey(0) == SCENE_ROOT_BONE)
219 m_API.MoveEntitiesToPrefab(m_EditedEntitySource, m_EditedEntitySource.GetAncestor(), { newEntitySource });
228 WorkspaceWidget workspace =
game.GetWorkspace();
232 BaseWorld world = m_API.GetWorld();
233 vector entityScreenPos = workspace.ProjWorldToScreen(newEntitySourcePos, world);
234 entityScreenPos = { entityScreenPos[0], 0, entityScreenPos[1] };
236 string selectedBoneName;
237 array<vector> selectedMatrix;
238 float resultDistanceSq =
float.INFINITY;
239 foreach (
string boneName, array<vector> matrix : BONE_MATRICES)
241 if (boneName == SCENE_ROOT_BONE)
244 vector boneScreenPos = workspace.ProjWorldToScreen(matrix[3], world);
245 boneScreenPos = { boneScreenPos[0], 0, boneScreenPos[1] };
247 float distanceSq = vector.DistanceSqXZ(entityScreenPos, boneScreenPos);
248 if (distanceSq < resultDistanceSq)
250 selectedBoneName = boneName;
251 selectedMatrix = matrix;
252 resultDistanceSq = distanceSq;
259 if (BONE_ENTITYSOURCES.Get(selectedBoneName))
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));
272 vector
angles = Math3D.MatrixToAngles(mat);
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();
280 BONE_ENTITYSOURCES.Set(selectedBoneName, newEntitySource);
284 protected void UpdateDebugShapes(
float x,
float y)
287 if (BONE_DEBUGTEXTS.IsEmpty())
290 BaseWorld world = m_API.GetWorld();
291 WorkspaceWidget workspace =
GetGame().GetWorkspace();
293 vector traceStart, traceEnd, traceDir;
294 if (!m_API.TraceWorldPos(x, y,
TraceFlags.WORLD, traceStart, traceEnd, traceDir))
295 traceEnd = traceStart + traceDir * 50;
303 DebugTextScreenSpace debugText;
304 foreach (
string boneName, array<vector> matrix : BONE_MATRICES)
306 debugText = BONE_DEBUGTEXTS.Get(boneName);
310 if (Math3D.PointLineSegmentDistanceSqr(matrix[3], traceStart, traceEnd) <= m_fBoneDebugSphereRadius * m_fBoneDebugSphereRadius)
312 debugText.SetText(boneName);
313 BONE_DEBUGSHAPES.Get(boneName).SetColor(BONE_SPHERE_COLOUR_HIGHLIGHT);
317 debugText.SetText(
string.Empty);
318 if (boneName == SCENE_ROOT_BONE)
319 BONE_DEBUGSHAPES.Get(boneName).SetColor(ROOT_BONE_SPHERE_COLOUR);
321 BONE_DEBUGSHAPES.Get(boneName).SetColor(BONE_SPHERE_COLOUR);
334 protected override void OnKeyPressEvent(
KeyCode key,
bool isAutoRepeat)
339 m_API.ClearEntitySelection();
348 protected override void OnLeaveEvent()
350 if (!m_EditedEntitySource)
353 m_iX = INVALID_CURSOR_POS;
354 m_iY = INVALID_CURSOR_POS;
358 protected override void OnMouseMoveEvent(
float x,
float y)
360 if (!m_EditedEntitySource)
364 UpdateDebugShapes(x, y);
366 if (m_iX != INVALID_CURSOR_POS && m_iY != INVALID_CURSOR_POS)
372 IEntitySource newEntitySource = GetDragAndDroppedEntitySource();
373 if (!newEntitySource)
376 Print(
"added " + newEntitySource.GetAncestor().GetResourceName(),
LogLevel.DEBUG);
377 SnapNewEntitySource(newEntitySource);
384 protected override void OnMousePressEvent(
float x,
float y, WETMouseButtonFlag buttons)
386 if (m_API.IsPrefabEditMode())
391 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
396 m_API.ClearEntitySelection();
399 vector traceStart, traceEnd, traceDir;
400 if (!m_API.TraceWorldPos(x, y,
TraceFlags.ENTS, traceStart, traceEnd, traceDir, entity))
406 IEntitySource entitySource = m_API.EntityToSource(entity);
410 m_API.SetEntitySelection(entitySource);
419 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
420 if (!worldEditor || !worldEditor.IsPrefabEditMode())
427 protected override void OnDeActivate()
434 protected override void OnAfterLoadWorld()
440 protected override void OnBeforeUnloadWorld()
442 m_EditedEntitySource = null;
447 protected void ButtonRefresh()
449 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
453 if (!worldEditor.IsPrefabEditMode())
461 protected void ButtonOpenPrefabInEditMode()
463 WorldEditor worldEditor = SCR_WorldEditorToolHelper.GetWorldEditor();
467 if (worldEditor.IsPrefabEditMode())
470 WorldEditorAPI worldEditorAPI = worldEditor.GetApi();
474 if (worldEditorAPI.GetSelectedEntitiesCount() != 1)
477 IEntitySource selectedEntitySource = worldEditorAPI.GetSelectedEntity(0);
478 if (!selectedEntitySource)
481 while (selectedEntitySource.GetParent())
483 selectedEntitySource = selectedEntitySource.GetParent();
486 m_API.SetEntitySelection(selectedEntitySource);
488 IEntitySource ancestorSource = selectedEntitySource.GetAncestor();
495 ResourceName
resourceName = ancestorSource.GetResourceName();
499 if (!Workbench.ScriptDialog(
"",
"This will open the selected Prefab in Prefab Edit mode. Continue?\nSelected Prefab: " +
resourceName,
new WorkbenchDialog_OKCancel()))
502 if (!SCR_WorldEditorToolHelper.GetWorldEditor().SetOpenedResource(
resourceName))
ArmaReforgerScripted GetGame()
ref array< string > angles
ResourceName resourceName
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.
LogLevel
Enum with severity of the logging message.
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.
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.