Skip to content

Commit 1879065

Browse files
authored
QtFRED waypoint path generator tool (#7335)
1 parent 18fd870 commit 1879065

9 files changed

Lines changed: 978 additions & 0 deletions

qtfred/source_groups.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ add_file_folder("Source/Mission/Dialogs"
100100
src/mission/dialogs/VolumetricNebulaDialogModel.h
101101
src/mission/dialogs/WaypointEditorDialogModel.cpp
102102
src/mission/dialogs/WaypointEditorDialogModel.h
103+
src/mission/dialogs/WaypointPathGeneratorDialogModel.cpp
104+
src/mission/dialogs/WaypointPathGeneratorDialogModel.h
103105
src/mission/dialogs/WingEditorDialogModel.cpp
104106
src/mission/dialogs/WingEditorDialogModel.h
105107
)
@@ -210,6 +212,8 @@ add_file_folder("Source/UI/Dialogs"
210212
src/ui/dialogs/VolumetricNebulaDialog.cpp
211213
src/ui/dialogs/WaypointEditorDialog.cpp
212214
src/ui/dialogs/WaypointEditorDialog.h
215+
src/ui/dialogs/WaypointPathGeneratorDialog.cpp
216+
src/ui/dialogs/WaypointPathGeneratorDialog.h
213217
src/ui/dialogs/WingEditorDialog.cpp
214218
src/ui/dialogs/WingEditorDialog.h
215219
)
@@ -337,6 +341,7 @@ add_file_folder("UI"
337341
ui/VoiceActingManager.ui
338342
ui/VolumetricNebulaDialog.ui
339343
ui/WaypointEditorDialog.ui
344+
ui/WaypointPathGeneratorDialog.ui
340345
ui/ShipEditorDialog.ui
341346
ui/ShipInitialStatus.ui
342347
ui/ShipGoalsDialog.ui
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
#include "WaypointPathGeneratorDialogModel.h"
2+
3+
#include <globalincs/linklist.h>
4+
#include <globalincs/pstypes.h>
5+
#include <iff_defs/iff_defs.h>
6+
#include <jumpnode/jumpnode.h>
7+
#include <math/floating.h>
8+
#include <mission/object.h>
9+
#include <object/waypoint.h>
10+
#include <ship/ship.h>
11+
12+
namespace fso::fred::dialogs {
13+
14+
WaypointPathGeneratorDialogModel::WaypointPathGeneratorDialogModel(QObject* parent, EditorViewport* viewport)
15+
: AbstractDialogModel(parent, viewport)
16+
{
17+
// Generate a default unique path name
18+
char name_buf[NAME_LENGTH];
19+
waypoint_find_unique_name(name_buf, 1);
20+
_pathName = name_buf;
21+
22+
// Populate scene objects list
23+
for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) {
24+
int objnum = OBJ_INDEX(ptr);
25+
if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START) {
26+
_sceneObjects.emplace_back(Ships[ptr->instance].ship_name, objnum);
27+
} else if (ptr->type == OBJ_WAYPOINT) {
28+
SCP_string text;
29+
waypoint_stuff_name(text, ptr->instance);
30+
_sceneObjects.emplace_back(text, objnum);
31+
} else if (ptr->type == OBJ_JUMP_NODE) {
32+
for (auto& jn : Jump_nodes) {
33+
if (jn.GetSCPObjectNumber() == objnum) {
34+
_sceneObjects.emplace_back(jn.GetName(), objnum);
35+
break;
36+
}
37+
}
38+
}
39+
}
40+
}
41+
42+
bool WaypointPathGeneratorDialogModel::apply()
43+
{
44+
_bypass_errors = false;
45+
46+
if (!validateData()) {
47+
return false;
48+
}
49+
50+
// Determine center position
51+
vec3d center;
52+
if (_useObjectCenter && _centerObjectObjnum >= 0 && query_valid_object(_centerObjectObjnum)) {
53+
center = Objects[_centerObjectObjnum].pos;
54+
} else {
55+
center = { { { _centerX, _centerY, _centerZ } } };
56+
}
57+
58+
int totalPoints = _numPoints * _loops;
59+
if (totalPoints <= 0) {
60+
showErrorDialog("Total waypoints value is invalid.");
61+
return false;
62+
}
63+
64+
// Generate positions
65+
SCP_vector<vec3d> positions;
66+
positions.reserve(totalPoints);
67+
68+
for (int i = 0; i < totalPoints; ++i) {
69+
// _numPoints >= 3 is guaranteed by validateData(), so no division by zero
70+
float angle = (2.0f * PI * i) / static_cast<float>(_numPoints);
71+
72+
float driftOffset = 0.0f;
73+
if (totalPoints > 1) {
74+
driftOffset = _drift * (i / static_cast<float>(totalPoints - 1)) - _drift * 0.5f;
75+
}
76+
77+
auto var = [](float v) -> float {
78+
return (v != 0.0f) ? frand_range(-v, v) : 0.0f;
79+
};
80+
81+
vec3d pos;
82+
switch (_axis) {
83+
case GeneratorAxis::XZ:
84+
pos.xyz.x = center.xyz.x + _radius * cosf(angle) + var(_varianceX);
85+
pos.xyz.y = center.xyz.y + driftOffset + var(_varianceY);
86+
pos.xyz.z = center.xyz.z + _radius * sinf(angle) + var(_varianceZ);
87+
break;
88+
case GeneratorAxis::XY:
89+
pos.xyz.x = center.xyz.x + _radius * cosf(angle) + var(_varianceX);
90+
pos.xyz.y = center.xyz.y + _radius * sinf(angle) + var(_varianceY);
91+
pos.xyz.z = center.xyz.z + driftOffset + var(_varianceZ);
92+
break;
93+
case GeneratorAxis::ZY:
94+
pos.xyz.x = center.xyz.x + driftOffset + var(_varianceX);
95+
pos.xyz.y = center.xyz.y + _radius * sinf(angle) + var(_varianceY);
96+
pos.xyz.z = center.xyz.z + _radius * cosf(angle) + var(_varianceZ);
97+
break;
98+
}
99+
100+
positions.push_back(pos);
101+
}
102+
103+
// Create the first waypoint, this creates a new list with an auto-generated name.
104+
// Capture the index before the call since waypoint_add always appends.
105+
int listIndex = static_cast<int>(Waypoint_lists.size());
106+
waypoint_add(positions.data(), -1);
107+
Assertion(listIndex < static_cast<int>(Waypoint_lists.size()), "waypoint_add() failed to append a new waypoint list!");
108+
109+
// Rename the list to the user specified name
110+
Waypoint_lists[listIndex].set_name(_pathName.c_str());
111+
112+
// Add remaining waypoints to the same list
113+
for (int i = 1; i < static_cast<int>(positions.size()); ++i) {
114+
waypoint_add(positions.data() + i, calc_waypoint_instance(listIndex, i - 1));
115+
}
116+
117+
// Select all waypoints in the new path
118+
_editor->unmark_all();
119+
for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) {
120+
if (ptr->type == OBJ_WAYPOINT && calc_waypoint_list_index(ptr->instance) == listIndex) {
121+
_editor->markObject(OBJ_INDEX(ptr));
122+
}
123+
}
124+
125+
_editor->missionChanged();
126+
127+
return true;
128+
}
129+
130+
void WaypointPathGeneratorDialogModel::reject()
131+
{
132+
// One shot generator, nothing to undo
133+
}
134+
135+
bool WaypointPathGeneratorDialogModel::validateData()
136+
{
137+
if (_pathName.empty()) {
138+
showErrorDialog("Please enter a name for the waypoint path.");
139+
return false;
140+
}
141+
142+
if (_pathName[0] == '<') {
143+
showErrorDialog("Waypoint path names may not begin with '<'.");
144+
return false;
145+
}
146+
147+
// Wing name collision
148+
for (auto& wing : Wings) {
149+
if (!stricmp(wing.name, _pathName.c_str())) {
150+
showErrorDialog("This name is already used by a wing.");
151+
return false;
152+
}
153+
}
154+
155+
// Ship name collision
156+
for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) {
157+
if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START) {
158+
if (!stricmp(_pathName.c_str(), Ships[ptr->instance].ship_name)) {
159+
showErrorDialog("This name is already used by a ship.");
160+
return false;
161+
}
162+
}
163+
}
164+
165+
// Target priority group collision
166+
for (auto& ai : Ai_tp_list) {
167+
if (!stricmp(_pathName.c_str(), ai.name)) {
168+
showErrorDialog("This name is already used by a target priority group.");
169+
return false;
170+
}
171+
}
172+
173+
// Waypoint path name collision
174+
for (const auto& wpl : Waypoint_lists) {
175+
if (!stricmp(wpl.get_name(), _pathName.c_str())) {
176+
showErrorDialog("This name is already used by another waypoint path.");
177+
return false;
178+
}
179+
}
180+
181+
// Jump node name collision
182+
if (jumpnode_get_by_name(_pathName.c_str()) != nullptr) {
183+
showErrorDialog("This name is already used by a jump node.");
184+
return false;
185+
}
186+
187+
if (_radius <= 0.0f) {
188+
showErrorDialog("Radius must be greater than zero.");
189+
return false;
190+
}
191+
192+
if (_numPoints < 3) {
193+
showErrorDialog("Number of points must be at least 3.");
194+
return false;
195+
}
196+
197+
if (_loops < 1) {
198+
showErrorDialog("Loops must be at least 1.");
199+
return false;
200+
}
201+
202+
return true;
203+
}
204+
205+
void WaypointPathGeneratorDialogModel::showErrorDialog(const SCP_string& message)
206+
{
207+
if (_bypass_errors) {
208+
return;
209+
}
210+
_bypass_errors = true;
211+
_viewport->dialogProvider->showButtonDialog(DialogType::Error, "Error", message, {DialogButton::Ok});
212+
_bypass_errors = false;
213+
}
214+
215+
// --- Getters/Setters ---
216+
217+
const SCP_string& WaypointPathGeneratorDialogModel::getPathName() const { return _pathName; }
218+
void WaypointPathGeneratorDialogModel::setPathName(const SCP_string& v) { _pathName = v; }
219+
220+
bool WaypointPathGeneratorDialogModel::getUseObjectCenter() const { return _useObjectCenter; }
221+
void WaypointPathGeneratorDialogModel::setUseObjectCenter(bool v) { _useObjectCenter = v; }
222+
223+
int WaypointPathGeneratorDialogModel::getCenterObjectObjnum() const { return _centerObjectObjnum; }
224+
void WaypointPathGeneratorDialogModel::setCenterObjectObjnum(int objnum) { _centerObjectObjnum = objnum; }
225+
226+
float WaypointPathGeneratorDialogModel::getCenterX() const { return _centerX; }
227+
void WaypointPathGeneratorDialogModel::setCenterX(float v) { _centerX = v; }
228+
float WaypointPathGeneratorDialogModel::getCenterY() const { return _centerY; }
229+
void WaypointPathGeneratorDialogModel::setCenterY(float v) { _centerY = v; }
230+
float WaypointPathGeneratorDialogModel::getCenterZ() const { return _centerZ; }
231+
void WaypointPathGeneratorDialogModel::setCenterZ(float v) { _centerZ = v; }
232+
233+
GeneratorAxis WaypointPathGeneratorDialogModel::getAxis() const { return _axis; }
234+
void WaypointPathGeneratorDialogModel::setAxis(GeneratorAxis v) { _axis = v; }
235+
236+
int WaypointPathGeneratorDialogModel::getNumPoints() const { return _numPoints; }
237+
void WaypointPathGeneratorDialogModel::setNumPoints(int v) { _numPoints = v; }
238+
239+
int WaypointPathGeneratorDialogModel::getLoops() const { return _loops; }
240+
void WaypointPathGeneratorDialogModel::setLoops(int v) { _loops = v; }
241+
242+
float WaypointPathGeneratorDialogModel::getRadius() const { return _radius; }
243+
void WaypointPathGeneratorDialogModel::setRadius(float v) { _radius = v; }
244+
245+
float WaypointPathGeneratorDialogModel::getDrift() const { return _drift; }
246+
void WaypointPathGeneratorDialogModel::setDrift(float v) { _drift = v; }
247+
248+
float WaypointPathGeneratorDialogModel::getVarianceX() const { return _varianceX; }
249+
void WaypointPathGeneratorDialogModel::setVarianceX(float v) { _varianceX = v; }
250+
float WaypointPathGeneratorDialogModel::getVarianceY() const { return _varianceY; }
251+
void WaypointPathGeneratorDialogModel::setVarianceY(float v) { _varianceY = v; }
252+
float WaypointPathGeneratorDialogModel::getVarianceZ() const { return _varianceZ; }
253+
void WaypointPathGeneratorDialogModel::setVarianceZ(float v) { _varianceZ = v; }
254+
255+
const SCP_vector<std::pair<SCP_string, int>>& WaypointPathGeneratorDialogModel::getSceneObjects() const
256+
{
257+
return _sceneObjects;
258+
}
259+
260+
} // namespace fso::fred::dialogs
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#pragma once
2+
3+
#include "AbstractDialogModel.h"
4+
5+
namespace fso::fred::dialogs {
6+
7+
enum class GeneratorAxis { XZ, XY, ZY };
8+
9+
class WaypointPathGeneratorDialogModel : public AbstractDialogModel {
10+
Q_OBJECT
11+
public:
12+
explicit WaypointPathGeneratorDialogModel(QObject* parent, EditorViewport* viewport);
13+
14+
bool apply() override;
15+
void reject() override;
16+
17+
const SCP_string& getPathName() const;
18+
void setPathName(const SCP_string& v);
19+
20+
bool getUseObjectCenter() const;
21+
void setUseObjectCenter(bool v);
22+
23+
int getCenterObjectObjnum() const;
24+
void setCenterObjectObjnum(int objnum);
25+
26+
float getCenterX() const;
27+
void setCenterX(float v);
28+
float getCenterY() const;
29+
void setCenterY(float v);
30+
float getCenterZ() const;
31+
void setCenterZ(float v);
32+
33+
GeneratorAxis getAxis() const;
34+
void setAxis(GeneratorAxis v);
35+
36+
int getNumPoints() const;
37+
void setNumPoints(int v);
38+
39+
int getLoops() const;
40+
void setLoops(int v);
41+
42+
float getRadius() const;
43+
void setRadius(float v);
44+
45+
float getDrift() const;
46+
void setDrift(float v);
47+
48+
float getVarianceX() const;
49+
void setVarianceX(float v);
50+
float getVarianceY() const;
51+
void setVarianceY(float v);
52+
float getVarianceZ() const;
53+
void setVarianceZ(float v);
54+
55+
const SCP_vector<std::pair<SCP_string, int>>& getSceneObjects() const;
56+
57+
private:
58+
bool validateData();
59+
void showErrorDialog(const SCP_string& message);
60+
61+
SCP_string _pathName;
62+
bool _useObjectCenter = false;
63+
int _centerObjectObjnum = -1;
64+
float _centerX = 0.0f;
65+
float _centerY = 0.0f;
66+
float _centerZ = 0.0f;
67+
GeneratorAxis _axis = GeneratorAxis::XZ;
68+
int _numPoints = 8;
69+
int _loops = 1;
70+
float _radius = 1000.0f;
71+
float _drift = 0.0f;
72+
float _varianceX = 0.0f;
73+
float _varianceY = 0.0f;
74+
float _varianceZ = 0.0f;
75+
76+
SCP_vector<std::pair<SCP_string, int>> _sceneObjects; // (display name, objnum)
77+
78+
bool _bypass_errors = false;
79+
};
80+
81+
} // namespace fso::fred::dialogs

qtfred/src/ui/FredView.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <ui/dialogs/VolumetricNebulaDialog.h>
2424
#include <ui/dialogs/BriefingEditorDialog.h>
2525
#include <ui/dialogs/WaypointEditorDialog.h>
26+
#include <ui/dialogs/WaypointPathGeneratorDialog.h>
2627
#include <ui/dialogs/JumpNodeEditorDialog.h>
2728
#include <ui/dialogs/CampaignEditorDialog.h>
2829
#include <ui/dialogs/MissionGoalsDialog.h>
@@ -1765,5 +1766,11 @@ void FredView::on_actionFiction_Viewer_triggered(bool) {
17651766
dialog->show();
17661767
}
17671768

1769+
void FredView::on_actionWaypointPathGenerator_triggered(bool) {
1770+
auto dialog = new dialogs::WaypointPathGeneratorDialog(this, _viewport);
1771+
dialog->setAttribute(Qt::WA_DeleteOnClose);
1772+
dialog->show();
1773+
}
1774+
17681775
} // namespace fred
17691776
} // namespace fso

qtfred/src/ui/FredView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ class FredView: public QMainWindow, public IDialogProvider {
167167
void on_actionMission_Goals_triggered(bool);
168168
void on_actionMusic_Player_triggered(bool);
169169
void on_actionCalculate_Relative_Coordinates_triggered(bool);
170+
void on_actionWaypointPathGenerator_triggered(bool);
170171
signals:
171172
/**
172173
* @brief Special version of FredApplication::onIdle which is limited to the lifetime of this object

0 commit comments

Comments
 (0)