Arma Reforger Explorer 1.7.0.54
Arma Reforger Code Explorer by Zeroy - Thanks to MisterOutofTime
Loading...
Searching...
No Matches
SCR_ParallelShapeTool.c
Go to the documentation of this file.
1#ifdef WORKBENCH
2[WorkbenchToolAttribute(
3 name: "Parallel Shape Tool",
4 description: "Create a selected polyline's/spline's parallel shape",
5 awesomeFontCode: 0xF7A5)]
6class SCR_ParallelShapeTool : WorldEditorTool
7{
8 /*
9 Category: Margins
10 */
11
12 [Attribute(defvalue: "1", desc: "[m] Offset safety for neater inner curves (Spline only)", params: "0 10 0.01", precision: 2, uiwidget: UIWidgets.Slider, category: "Margins")]
13 protected float m_fOffsetSafetyMargin;
14
15 [Attribute(defvalue: "1", desc: "Minimum distance between points, as to not saturate inner curves (Spline only)", params: "0 20 0.1", precision: 1, uiwidget: UIWidgets.Slider, category: "Margins")]
16 protected float m_fMinDistanceBetweenPoints;
17
18 /*
19 Category: Debug
20 */
21
22 [Attribute(defvalue: "0", desc: "Show more logs", category: "Debug")]
23 protected bool m_bShowDebugLog;
24
25 /*
26 Category: Shape Creation
27 */
28
29 [Attribute(defvalue: "1", desc: "Snap the newly created shapes' points to the ground", category: "Shape Creation")]
30 protected bool m_bSnapToGround;
31
32 [Attribute(defvalue: "0", desc: "Use spline if shape is a spline, may help in sharp turns; generally not needed, polyline should be enough", category: "Shape Creation")]
33 protected bool m_bUseSplineIfPossible;
34
35 [Attribute(defvalue: "1", desc: "Create shapes as the selected shape(s)' children; otherwise, create them on the same level", category: "Shape Creation")]
36 protected bool m_bCreateShapeAsChild;
37
38 [Attribute(defvalue: "1", desc: "Create shapes on the provided offsets and their negative, bordering the selected shape(s)", category: "Shape Creation")]
39 protected bool m_bSymmetricalShapes;
40
41 [Attribute(defvalue: "1", desc: "Cleans up the duplicated offsets to only keep unique ones - also applies to Symmetrical Shapes above", category: "Shape Creation")]
42 protected bool m_bRemoveDuplicates;
43
44 [Attribute(defvalue: "0", desc: "New shapes' offset(s) - negative means \"to the left\" of the shape, positive means \"to the right\" (from its first to its last point)", params: "-50 50 0.1", precision: 1, uiwidget: UIWidgets.Slider, category: "Shape Creation")]
45 protected ref array<float> m_aOffsets;
46
47 protected static const ref array<float> DEFAULT_OFFSETS = { -10.0, 10.0 };
48
49 protected static const typename POLYLINE_TYPENAME = PolylineShapeEntity;
50 protected static const typename SPLINE_TYPENAME = SplineShapeEntity;
51 protected static const string POLYLINE_CLASSNAME = POLYLINE_TYPENAME.ToString();
52 protected static const string SPLINE_CLASSNAME = SPLINE_TYPENAME.ToString();
53
54 //------------------------------------------------------------------------------------------------
55 [ButtonAttribute("Create")]
56 protected void Run()
57 {
58 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
59 if (!worldEditorAPI)
60 {
61 Print("No WorldEditorAPI", LogLevel.ERROR);
62 return;
63 }
64
65 BaseWorld world = worldEditorAPI.GetWorld();
66 if (!world)
67 {
68 Print("No World loaded", LogLevel.WARNING);
69 return;
70 }
71
72 int selectedEntities = worldEditorAPI.GetSelectedEntitiesCount();
73 if (selectedEntities < 1)
74 {
75 Print("No entities selected", LogLevel.WARNING);
76 return;
77 }
78
79 bool updatePropertyPanel;
80 if (m_aOffsets.IsEmpty())
81 {
82 m_aOffsets.InsertAll(DEFAULT_OFFSETS);
83 updatePropertyPanel = true;
84 }
85
86 if (m_bRemoveDuplicates)
87 {
88 int offsetCounts = m_aOffsets.Count();
89 SCR_ArrayHelperT<float>.RemoveDuplicates(m_aOffsets); // removes from the end
90 if (offsetCounts != m_aOffsets.Count())
91 updatePropertyPanel = true;
92 }
93
94 if (updatePropertyPanel)
95 UpdatePropertyPanel();
96
97 array<float> offsets = {};
98 offsets.Copy(m_aOffsets);
99
100 if (m_bSymmetricalShapes)
101 {
102 offsets.Reserve(offsets.Count() * 2);
103 foreach (float offset : m_aOffsets)
104 {
105 if (offset != 0)
106 offsets.Insert(-offset);
107 }
108
109 if (m_bRemoveDuplicates)
110 SCR_ArrayHelperT<float>.RemoveDuplicates(offsets);
111 }
112
113 IEntity entity;
114 IEntitySource source;
115 ShapeEntity shapeEntity;
116 IEntity parent;
117 IEntitySource parentSource;
118 // creation
119 array<vector> newPoints;
120 ShapeEntity createdShape;
121 IEntitySource createdShapeSource;
122 int currentLayerId = worldEditorAPI.GetCurrentEntityLayerId();
123 map<IEntitySource, ref array<vector>> shapeSourcePoints = new map<IEntitySource, ref array<vector>>();
124 for (int selectedEntityIndex; selectedEntityIndex < selectedEntities; ++selectedEntityIndex)
125 {
126 source = worldEditorAPI.GetSelectedEntity(selectedEntityIndex);
127 if (!source) // ...how
128 {
129 Print("No entity selected, skipping index " + selectedEntityIndex, LogLevel.WARNING);
130 continue;
131 }
132
133 entity = worldEditorAPI.SourceToEntity(source);
134 if (!entity)
135 {
136 Print("Null entity; skipping index " + selectedEntityIndex, LogLevel.WARNING);
137 continue;
138 }
139
140 shapeEntity = ShapeEntity.Cast(entity);
141 if (!shapeEntity)
142 {
143 if (m_bShowDebugLog)
144 Print("The selected entity is not a shape (polyline/spline) at index " + selectedEntityIndex, LogLevel.NORMAL);
145
146 continue;
147 }
148
149 if (shapeEntity.GetPointCount() < 2)
150 {
151 Print("The selected shape #" + selectedEntityIndex + " requires at least two points - " + shapeEntity.GetOrigin(), LogLevel.NORMAL);
152 continue;
153 }
154
155 CreateParallelOffsetShapes(shapeEntity, offsets);
156 }
157 }
158
159 //------------------------------------------------------------------------------------------------
162 static IEntitySource CreateParallelOffsetShape(notnull ShapeEntity shapeEntity, float offset, SCR_ParallelShapeToolSettings settings = null)
163 {
164 array<IEntitySource> shapes = CreateParallelOffsetShapes(shapeEntity, { offset });
165 if (!shapes || shapes.IsEmpty())
166 return null;
167
168 return shapes[0];
169 }
170
171 //------------------------------------------------------------------------------------------------
178 static array<IEntitySource> CreateParallelOffsetShapes(notnull ShapeEntity shapeEntity, notnull array<float> offsets, SCR_ParallelShapeToolSettings settings = null)
179 {
180 WorldEditorAPI worldEditorAPI = ((WorldEditor)Workbench.GetModule(WorldEditor)).GetApi();
181 if (!worldEditorAPI)
182 {
183 Print("[SCR_ParallelShapeTool.CreateParallelOffsetShapes] WorldEditorAPI is not available (" + __FILE__ + " L" + __LINE__ + ")", LogLevel.WARNING);
184 return null;
185 }
186
187 if (shapeEntity.GetPointCount() < 2)
188 {
189 Print("[SCR_ParallelShapeTool.CreateParallelOffsetShapes] The provided shape requires at least two points (" + __FILE__ + " L" + __LINE__ + ")", LogLevel.WARNING);
190 return null;
191 }
192
193 array<IEntitySource> result = {};
194 int layerId = worldEditorAPI.GetCurrentEntityLayerId();
195
196 array<vector> newPoints;
197 IEntitySource createdShapeSource;
198 IEntity createdShape;
199
200 IEntitySource parentSource;
201 vector origin;
202 vector angles;
203
204 if (settings && settings.m_bCreateAsChildren)
205 {
206 parentSource = worldEditorAPI.EntityToSource(shapeEntity);
207 }
208 else // create in world
209 {
210 origin = shapeEntity.GetOrigin();
211 angles = shapeEntity.GetAngles();
212 }
213
214 bool manageEntityAction = worldEditorAPI.BeginEntityAction();
215
216 foreach (int offsetIndex, float offset : offsets)
217 {
218 float absOffset = offset;
219 if (absOffset < 0)
220 absOffset = -absOffset;
221
222 newPoints = SCR_ParallelShapeHelper.GetRelativeOffsetPointsFromShape(shapeEntity, offset, shapeEntity.IsClosed());
223
224 if (settings)
225 {
226 if (settings.m_fOffsetSafetyMargin > 0)
227 SCR_ParallelShapeHelper.RemovePointsCloseToShape(shapeEntity, absOffset - settings.m_fOffsetSafetyMargin, newPoints);
228
229 if (settings.m_bSnapToGround)
230 SCR_ParallelShapeHelper.SnapRelativePointsToGround(shapeEntity, newPoints);
231
232 // remove excess points
233 if (settings.m_fMinDistanceBetweenPoints > 0 && SplineShapeEntity.Cast(createdShape) != null) // spline only
234 SCR_ParallelShapeHelper.ReducePointsDensity(settings.m_fMinDistanceBetweenPoints, newPoints);
235 }
236
237 int newPointsCount = newPoints.Count();
238 if (newPointsCount < 2)
239 {
240 float minDistanceBetweenPoints;
241 if (settings)
242 minDistanceBetweenPoints = settings.m_fMinDistanceBetweenPoints;
243
244 Print(
245 "The number of created points (" + newPointsCount + ") is not enough for a proper shape - "
246 + "try changing the offset safety margin (current: " + offset.ToString(-1, 2) + "m) "
247 + "or the min distance between points (current: " + minDistanceBetweenPoints.ToString(-1, 2) + "m)", LogLevel.WARNING);
248 continue;
249 }
250
251 if (settings
252 && settings.m_bUseSplineForSpline
253 && shapeEntity.ClassName().ToType()
254 && shapeEntity.ClassName().ToType().IsInherited(SPLINE_TYPENAME))
255 createdShapeSource = worldEditorAPI.CreateEntity(SPLINE_CLASSNAME, string.Empty, layerId, parentSource, origin, angles);
256 else
257 createdShapeSource = worldEditorAPI.CreateEntity(POLYLINE_CLASSNAME, string.Empty, layerId, parentSource, origin, angles);
258
259 if (!createdShapeSource)
260 {
261 Print("Cannot create shape of offset #" + offsetIndex + "'s shape", LogLevel.ERROR);
262 continue;
263 }
264
265 createdShape = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(createdShapeSource));
266 if (!createdShape) // this check is technically useless, but let's cleanup after us just in case
267 {
268 Print("The created entity is not a shape", LogLevel.ERROR);
269 worldEditorAPI.DeleteEntity(createdShapeSource);
270 continue;
271 }
272
273 foreach (int i, vector newPoint : newPoints)
274 {
275 if (!worldEditorAPI.CreateObjectArrayVariableMember(createdShapeSource, null, "Points", "ShapePoint", i))
276 {
277 Print("[SCR_ParallelShapeHelper.CreateParallelOffsetShapes] Cannot create point " + i + " (" + __FILE__ + " L" + __LINE__ + ")", LogLevel.ERROR);
278 break;
279 }
280
281 newPoints[i] = shapeEntity.CoordToLocal(newPoint);
282 worldEditorAPI.SetVariableValue(createdShapeSource, { new ContainerIdPathEntry("Points", i) }, "Position", string.Format("%1 %2 %3", newPoint[0], newPoint[1], newPoint[2]));
283 }
284
285 result.Insert(createdShapeSource);
286 }
287
288 if (manageEntityAction)
289 worldEditorAPI.EndEntityAction();
290
291 return result;
292 }
293}
294
295class SCR_ParallelShapeToolSettings
296{
297 bool m_bCreateAsChildren;
298 bool m_bSnapToGround;
299 bool m_bUseSplineForSpline;
300
301 float m_fMinDistanceBetweenPoints;
302 float m_fOffsetSafetyMargin;
303}
304#endif
void ContainerIdPathEntry(string propertyName, int index=-1)
Definition worldEditor.c:30
ref array< string > angles
params precision
override void Run()
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
Definition LogLevel.c:14
SCR_FieldOfViewSettings Attribute