Skip to content

Commit cdb7773

Browse files
authored
Merge pull request #53 from klaussilveira/feat/decal-shooter-improvements
Added new features for the decal shooter tool
2 parents 775d878 + 69a0521 commit cdb7773

4 files changed

Lines changed: 293 additions & 2 deletions

File tree

radiant/camera/tools/DecalShooterTool.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "ui/decalshooter/DecalShooterPanel.h"
1414

1515
#include <cmath>
16+
#include <random>
1617

1718
namespace ui
1819
{
@@ -64,6 +65,22 @@ MouseTool::Result DecalShooterTool::onMouseDown(Event& ev)
6465
double width = panel ? panel->getDecalWidth() : 128.0;
6566
double height = panel ? panel->getDecalHeight() : 128.0;
6667
double offset = panel ? panel->getDecalOffset() : 0.125;
68+
69+
bool randomRotation = panel ? panel->isRandomRotationEnabled() : false;
70+
double rotation = 0.0;
71+
if (randomRotation)
72+
{
73+
static std::random_device rd;
74+
static std::mt19937 gen(rd());
75+
static std::uniform_real_distribution<double> dist(-180.0, 180.0);
76+
rotation = dist(gen);
77+
}
78+
else
79+
{
80+
rotation = panel ? panel->getDecalRotation() : 0.0;
81+
}
82+
83+
bool flip = panel ? panel->isFlipEnabled() : false;
6784
std::string material = panel ? panel->getDecalMaterial() : "textures/common/decal";
6885

6986
createDecalAtFace(
@@ -72,6 +89,8 @@ MouseTool::Result DecalShooterTool::onMouseDown(Event& ev)
7289
width,
7390
height,
7491
offset,
92+
rotation,
93+
flip,
7594
material
7695
);
7796
}
@@ -101,6 +120,8 @@ void DecalShooterTool::createDecalAtFace(
101120
double width,
102121
double height,
103122
double offset,
123+
double rotationDegrees,
124+
bool flip,
104125
const std::string& material)
105126
{
106127
UndoableCommand cmd("PlaceDecal");
@@ -133,6 +154,24 @@ void DecalShooterTool::createDecalAtFace(
133154
Vector3 tangent = normal.cross(up).getNormalised();
134155
Vector3 bitangent = tangent.cross(normal).getNormalised();
135156

157+
if (std::abs(rotationDegrees) > 0.001)
158+
{
159+
double radians = rotationDegrees * (M_PI / 180.0);
160+
double cosAngle = std::cos(radians);
161+
double sinAngle = std::sin(radians);
162+
163+
Vector3 rotatedTangent = tangent * cosAngle + bitangent * sinAngle;
164+
Vector3 rotatedBitangent = bitangent * cosAngle - tangent * sinAngle;
165+
166+
tangent = rotatedTangent;
167+
bitangent = rotatedBitangent;
168+
}
169+
170+
if (flip)
171+
{
172+
bitangent = -bitangent;
173+
}
174+
136175
Vector3 center = intersectionPoint + normal * offset;
137176

138177
double halfWidth = width * 0.5;
@@ -167,6 +206,12 @@ void DecalShooterTool::createDecalAtFace(
167206
worldspawn->addChildNode(patchNode);
168207
}
169208

209+
DecalShooterPanel* panel = DecalShooterPanel::getInstance();
210+
if (panel)
211+
{
212+
panel->onDecalCreated(patchNode);
213+
}
214+
170215
GlobalSelectionSystem().setSelectedAll(false);
171216
Node_setSelected(patchNode, true);
172217
}

radiant/camera/tools/DecalShooterTool.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class DecalShooterTool :
3434
double width,
3535
double height,
3636
double offset,
37+
double rotationDegrees,
38+
bool flip,
3739
const std::string& material
3840
);
3941
};

radiant/ui/decalshooter/DecalShooterPanel.cpp

Lines changed: 218 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
#include <wx/textctrl.h>
77
#include <wx/button.h>
88
#include <wx/bmpbuttn.h>
9+
#include <wx/checkbox.h>
10+
#include <wx/choice.h>
911

1012
#include "i18n.h"
13+
#include "imap.h"
14+
#include "ilayer.h"
1115
#include "wxutil/Bitmap.h"
1216
#include "ui/materials/MaterialChooser.h"
1317
#include "ui/materials/MaterialSelector.h"
@@ -22,15 +26,29 @@ DecalShooterPanel::DecalShooterPanel(wxWindow* parent) :
2226
_widthCtrl(nullptr),
2327
_heightCtrl(nullptr),
2428
_offsetCtrl(nullptr),
29+
_rotationCtrl(nullptr),
30+
_randomRotationCheckbox(nullptr),
2531
_materialEntry(nullptr),
26-
_browseButton(nullptr)
32+
_browseButton(nullptr),
33+
_autogroupCheckbox(nullptr),
34+
_flipCheckbox(nullptr),
35+
_layerChoice(nullptr)
2736
{
2837
_instance = this;
2938
populateWindow();
39+
40+
_mapEventConnection = GlobalMapModule().signal_mapEvent().connect(
41+
sigc::mem_fun(*this, &DecalShooterPanel::onMapEvent)
42+
);
43+
44+
connectToMapRoot();
3045
}
3146

3247
DecalShooterPanel::~DecalShooterPanel()
3348
{
49+
_mapEventConnection.disconnect();
50+
_layersChangedConnection.disconnect();
51+
3452
if (_instance == this)
3553
{
3654
_instance = nullptr;
@@ -57,24 +75,93 @@ double DecalShooterPanel::getDecalOffset() const
5775
return _offsetCtrl ? _offsetCtrl->GetValue() : 0.125;
5876
}
5977

78+
double DecalShooterPanel::getDecalRotation() const
79+
{
80+
return _rotationCtrl ? _rotationCtrl->GetValue() : 0.0;
81+
}
82+
83+
bool DecalShooterPanel::isRandomRotationEnabled() const
84+
{
85+
return _randomRotationCheckbox ? _randomRotationCheckbox->GetValue() : false;
86+
}
87+
88+
bool DecalShooterPanel::isFlipEnabled() const
89+
{
90+
return _flipCheckbox ? _flipCheckbox->GetValue() : false;
91+
}
92+
6093
std::string DecalShooterPanel::getDecalMaterial() const
6194
{
6295
return _materialEntry ? _materialEntry->GetValue().ToStdString() : "textures/common/decal";
6396
}
6497

98+
bool DecalShooterPanel::isAutogroupEnabled() const
99+
{
100+
return _autogroupCheckbox ? _autogroupCheckbox->GetValue() : false;
101+
}
102+
103+
int DecalShooterPanel::getSelectedLayerId() const
104+
{
105+
if (!_layerChoice || _layerChoice->GetSelection() == wxNOT_FOUND)
106+
{
107+
return -1;
108+
}
109+
110+
return static_cast<int>(reinterpret_cast<intptr_t>(_layerChoice->GetClientData(_layerChoice->GetSelection())));
111+
}
112+
113+
void DecalShooterPanel::onDecalCreated(const scene::INodePtr& decalNode)
114+
{
115+
if (!decalNode)
116+
{
117+
return;
118+
}
119+
120+
auto mapRoot = GlobalMapModule().getRoot();
121+
if (!mapRoot)
122+
{
123+
return;
124+
}
125+
126+
// Do the layer assignment
127+
int selectedLayerId = getSelectedLayerId();
128+
if (selectedLayerId >= 0)
129+
{
130+
decalNode->moveToLayer(selectedLayerId);
131+
}
132+
133+
if (isAutogroupEnabled())
134+
{
135+
// Create a new group if we dont have one
136+
if (!_currentSessionGroup)
137+
{
138+
_currentSessionGroup = mapRoot->getSelectionGroupManager().createSelectionGroup();
139+
}
140+
141+
_currentSessionGroup->addNode(decalNode);
142+
}
143+
}
144+
145+
void DecalShooterPanel::resetSessionGroup()
146+
{
147+
_currentSessionGroup.reset();
148+
}
149+
65150
void DecalShooterPanel::onPanelActivated()
66151
{
152+
connectToMapRoot();
67153
}
68154

69155
void DecalShooterPanel::onPanelDeactivated()
70156
{
157+
resetSessionGroup();
71158
}
72159

73160
void DecalShooterPanel::populateWindow()
74161
{
75162
SetSizer(new wxBoxSizer(wxVERTICAL));
76163

77-
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(4, 2, 6, 12);
164+
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(6, 2, 6, 12);
78165
gridSizer->AddGrowableCol(1);
79166

80167
wxStaticText* widthLabel = new wxStaticText(this, wxID_ANY, _("Width:"));
@@ -105,6 +192,26 @@ void DecalShooterPanel::populateWindow()
105192
gridSizer->Add(offsetLabel, 0, wxALIGN_CENTER_VERTICAL);
106193
gridSizer->Add(_offsetCtrl, 1, wxEXPAND);
107194

195+
wxStaticText* rotationLabel = new wxStaticText(this, wxID_ANY, _("Rotation:"));
196+
197+
wxBoxSizer* rotationSizer = new wxBoxSizer(wxHORIZONTAL);
198+
_rotationCtrl = new wxSpinCtrlDouble(this, wxID_ANY);
199+
_rotationCtrl->SetRange(-180.0, 180.0);
200+
_rotationCtrl->SetValue(0.0);
201+
_rotationCtrl->SetIncrement(15.0);
202+
_rotationCtrl->SetDigits(1);
203+
_rotationCtrl->SetToolTip(_("Rotation angle in degrees"));
204+
205+
_randomRotationCheckbox = new wxCheckBox(this, wxID_ANY, _("Random"));
206+
_randomRotationCheckbox->SetToolTip(_("Apply a random rotation for each decal"));
207+
_randomRotationCheckbox->Bind(wxEVT_CHECKBOX, &DecalShooterPanel::onRandomRotationToggled, this);
208+
209+
rotationSizer->Add(_rotationCtrl, 1, wxEXPAND | wxRIGHT, 4);
210+
rotationSizer->Add(_randomRotationCheckbox, 0, wxALIGN_CENTER_VERTICAL);
211+
212+
gridSizer->Add(rotationLabel, 0, wxALIGN_CENTER_VERTICAL);
213+
gridSizer->Add(rotationSizer, 1, wxEXPAND);
214+
108215
wxStaticText* materialLabel = new wxStaticText(this, wxID_ANY, _("Material:"));
109216

110217
wxBoxSizer* materialSizer = new wxBoxSizer(wxHORIZONTAL);
@@ -122,8 +229,30 @@ void DecalShooterPanel::populateWindow()
122229
gridSizer->Add(materialLabel, 0, wxALIGN_CENTER_VERTICAL);
123230
gridSizer->Add(materialSizer, 1, wxEXPAND);
124231

232+
// Layer choice
233+
wxStaticText* layerLabel = new wxStaticText(this, wxID_ANY, _("Layer:"));
234+
_layerChoice = new wxChoice(this, wxID_ANY);
235+
_layerChoice->SetToolTip(_("Assign created decals to this layer"));
236+
populateLayerChoice();
237+
gridSizer->Add(layerLabel, 0, wxALIGN_CENTER_VERTICAL);
238+
gridSizer->Add(_layerChoice, 1, wxEXPAND);
239+
125240
GetSizer()->Add(gridSizer, 0, wxEXPAND | wxALL, 12);
126241

242+
// Checkboxes row
243+
wxBoxSizer* checkboxSizer = new wxBoxSizer(wxHORIZONTAL);
244+
245+
_autogroupCheckbox = new wxCheckBox(this, wxID_ANY, _("Autogroup"));
246+
_autogroupCheckbox->SetToolTip(_("When enabled, all decals placed during this tool session will be grouped together"));
247+
_autogroupCheckbox->Bind(wxEVT_CHECKBOX, &DecalShooterPanel::onAutogroupToggled, this);
248+
checkboxSizer->Add(_autogroupCheckbox, 0, wxRIGHT, 12);
249+
250+
_flipCheckbox = new wxCheckBox(this, wxID_ANY, _("Flip"));
251+
_flipCheckbox->SetToolTip(_("Flip the decal. Useful for decals facing the wrong direction."));
252+
checkboxSizer->Add(_flipCheckbox, 0);
253+
254+
GetSizer()->Add(checkboxSizer, 0, wxLEFT | wxRIGHT | wxBOTTOM, 12);
255+
127256
wxStaticText* helpText = new wxStaticText(this, wxID_ANY,
128257
_("Use Ctrl+Shift+Middle-Click\nin the 3D view to place decals."));
129258
helpText->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
@@ -137,4 +266,91 @@ void DecalShooterPanel::onBrowseMaterial(wxCommandEvent& ev)
137266
chooser->Destroy();
138267
}
139268

269+
void DecalShooterPanel::onAutogroupToggled(wxCommandEvent& ev)
270+
{
271+
if (!_autogroupCheckbox->GetValue())
272+
{
273+
resetSessionGroup();
274+
}
275+
}
276+
277+
void DecalShooterPanel::onRandomRotationToggled(wxCommandEvent& ev)
278+
{
279+
// Disable the rotation text field when randomizer is enabled
280+
if (_rotationCtrl)
281+
{
282+
_rotationCtrl->Enable(!_randomRotationCheckbox->GetValue());
283+
}
284+
}
285+
286+
void DecalShooterPanel::populateLayerChoice()
287+
{
288+
if (!_layerChoice)
289+
{
290+
return;
291+
}
292+
293+
int previousSelection = getSelectedLayerId();
294+
295+
_layerChoice->Clear();
296+
_layerChoice->Append(_("None"), reinterpret_cast<void*>(static_cast<intptr_t>(-1)));
297+
298+
auto mapRoot = GlobalMapModule().getRoot();
299+
if (mapRoot)
300+
{
301+
mapRoot->getLayerManager().foreachLayer([this](int layerId, const std::string& layerName)
302+
{
303+
_layerChoice->Append(layerName, reinterpret_cast<void*>(static_cast<intptr_t>(layerId)));
304+
});
305+
}
306+
307+
if (previousSelection >= 0)
308+
{
309+
for (unsigned int i = 0; i < _layerChoice->GetCount(); ++i)
310+
{
311+
int layerId = static_cast<int>(reinterpret_cast<intptr_t>(_layerChoice->GetClientData(i)));
312+
if (layerId == previousSelection)
313+
{
314+
_layerChoice->SetSelection(i);
315+
return;
316+
}
317+
}
318+
}
319+
320+
_layerChoice->SetSelection(0);
321+
}
322+
323+
void DecalShooterPanel::onLayersChanged()
324+
{
325+
populateLayerChoice();
326+
}
327+
328+
void DecalShooterPanel::connectToMapRoot()
329+
{
330+
_layersChangedConnection.disconnect();
331+
332+
auto mapRoot = GlobalMapModule().getRoot();
333+
if (mapRoot)
334+
{
335+
_layersChangedConnection = mapRoot->getLayerManager().signal_layersChanged().connect(
336+
sigc::mem_fun(*this, &DecalShooterPanel::onLayersChanged)
337+
);
338+
}
339+
340+
populateLayerChoice();
341+
}
342+
343+
void DecalShooterPanel::onMapEvent(IMap::MapEvent ev)
344+
{
345+
if (ev == IMap::MapLoaded)
346+
{
347+
connectToMapRoot();
348+
}
349+
else if (ev == IMap::MapUnloading)
350+
{
351+
_layersChangedConnection.disconnect();
352+
resetSessionGroup();
353+
}
354+
}
355+
140356
} // namespace ui

0 commit comments

Comments
 (0)