Skip to content

Commit 54abb54

Browse files
authored
Merge pull request #50 from klaussilveira/feat/decals
Added decal shooter tool
2 parents f656e8c + cef68c6 commit 54abb54

12 files changed

Lines changed: 630 additions & 0 deletions

include/ui/iusercontrol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ struct UserControl
8282
constexpr static const char* MapMergePanel = "MapMergePanel";
8383
constexpr static const char* AasVisualisationPanel = "AasVisualisationPanel";
8484
constexpr static const char* OrthoBackgroundPanel = "OrthoBackgroundPanel";
85+
constexpr static const char* DecalShooter = "DecalShooter";
8586
};
8687

8788
}

install/input.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<tool name="DragSelectionMouseToolFaceOnly" button="LMB" modifiers="SHIFT+CONTROL" />
3232
<tool name="CycleSelectionMouseTool" button="LMB" modifiers="ALT+SHIFT" />
3333
<tool name="CycleSelectionMouseToolFaceOnly" button="LMB" modifiers="SHIFT+CONTROL+ALT" />
34+
<tool name="DecalShooterTool" button="MMB" modifiers="CONTROL+SHIFT" />
3435
</mouseToolMapping>
3536
<!-- ID 2 = textool -->
3637
<mouseToolMapping name="TextureTool" id="2">

radiant/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ add_executable(darkradiant
22
camera/CameraSettings.cpp
33
camera/CamWnd.cpp
44
camera/CameraWndManager.cpp
5+
camera/tools/DecalShooterTool.cpp
6+
camera/tools/FaceIntersectionFinder.cpp
57
clipboard/ClipboardModule.cpp
68
eventmanager/Accelerator.cpp
79
eventmanager/EventManager.cpp
@@ -84,6 +86,7 @@ add_executable(darkradiant
8486
ui/layers/LayerControlPanel.cpp
8587
ui/layers/LayerOrthoContextMenuItem.cpp
8688
ui/lightinspector/LightInspector.cpp
89+
ui/decalshooter/DecalShooterPanel.cpp
8790
ui/LongRunningOperationHandler.cpp
8891
ui/mainframe/AuiManager.cpp
8992
ui/mainframe/AuiFloatingFrame.cpp

radiant/camera/CameraWndManager.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "tools/JumpToObjectTool.h"
1717
#include "tools/FreeMoveTool.h"
1818
#include "tools/PanViewTool.h"
19+
#include "tools/DecalShooterTool.h"
1920

2021
#include <functional>
2122

@@ -504,6 +505,7 @@ void CameraWndManager::initialiseModule(const IApplicationContext& ctx)
504505
toolGroup.registerMouseTool(std::make_shared<PasteShaderToBrushTool>());
505506
toolGroup.registerMouseTool(std::make_shared<PasteShaderNameTool>());
506507
toolGroup.registerMouseTool(std::make_shared<JumpToObjectTool>());
508+
toolGroup.registerMouseTool(std::make_shared<DecalShooterTool>());
507509

508510
GlobalUserInterface().registerControl(std::make_shared<CameraControl>(*this));
509511

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#include "DecalShooterTool.h"
2+
3+
#include "i18n.h"
4+
#include "iundo.h"
5+
#include "imap.h"
6+
#include "ipatch.h"
7+
#include "iscenegraph.h"
8+
#include "iselection.h"
9+
10+
#include "scenelib.h"
11+
#include "CameraMouseToolEvent.h"
12+
#include "FaceIntersectionFinder.h"
13+
#include "ui/decalshooter/DecalShooterPanel.h"
14+
15+
#include <cmath>
16+
17+
namespace ui
18+
{
19+
20+
// Static members
21+
std::string DecalShooterTool::_name = "DecalShooterTool";
22+
std::string DecalShooterTool::_displayName;
23+
24+
const std::string& DecalShooterTool::NAME()
25+
{
26+
return _name;
27+
}
28+
29+
const std::string& DecalShooterTool::getName()
30+
{
31+
return _name;
32+
}
33+
34+
const std::string& DecalShooterTool::getDisplayName()
35+
{
36+
if (_displayName.empty())
37+
{
38+
_displayName = _("Place Decal");
39+
}
40+
return _displayName;
41+
}
42+
43+
MouseTool::Result DecalShooterTool::onMouseDown(Event& ev)
44+
{
45+
try
46+
{
47+
CameraMouseToolEvent& camEvent = dynamic_cast<CameraMouseToolEvent&>(ev);
48+
49+
SelectionTestPtr selectionTest = camEvent.getView().createSelectionTestForPoint(
50+
camEvent.getDevicePosition()
51+
);
52+
53+
const Matrix4& viewProjection = selectionTest->getVolume().GetViewProjection();
54+
55+
FaceIntersectionFinder finder(*selectionTest, viewProjection);
56+
GlobalSceneGraph().root()->traverse(finder);
57+
58+
const FaceIntersection& intersection = finder.getResult();
59+
60+
if (intersection.valid)
61+
{
62+
DecalShooterPanel* panel = DecalShooterPanel::getInstance();
63+
64+
double width = panel ? panel->getDecalWidth() : 128.0;
65+
double height = panel ? panel->getDecalHeight() : 128.0;
66+
double offset = panel ? panel->getDecalOffset() : 0.125;
67+
std::string material = panel ? panel->getDecalMaterial() : "textures/common/decal";
68+
69+
createDecalAtFace(
70+
intersection.point,
71+
intersection.normal,
72+
width,
73+
height,
74+
offset,
75+
material
76+
);
77+
}
78+
79+
return Result::Finished;
80+
}
81+
catch (std::bad_cast&)
82+
{
83+
}
84+
85+
return Result::Ignored;
86+
}
87+
88+
MouseTool::Result DecalShooterTool::onMouseMove(Event& ev)
89+
{
90+
return Result::Ignored;
91+
}
92+
93+
MouseTool::Result DecalShooterTool::onMouseUp(Event& ev)
94+
{
95+
return Result::Finished;
96+
}
97+
98+
void DecalShooterTool::createDecalAtFace(
99+
const Vector3& intersectionPoint,
100+
const Vector3& normal,
101+
double width,
102+
double height,
103+
double offset,
104+
const std::string& material)
105+
{
106+
UndoableCommand cmd("PlaceDecal");
107+
108+
scene::INodePtr patchNode = GlobalPatchModule().createPatch(patch::PatchDefType::Def3);
109+
110+
if (!patchNode)
111+
{
112+
return;
113+
}
114+
115+
IPatchNodePtr patchNodePtr = std::dynamic_pointer_cast<IPatchNode>(patchNode);
116+
if (!patchNodePtr)
117+
{
118+
return;
119+
}
120+
121+
IPatch& patch = patchNodePtr->getPatch();
122+
123+
patch.setDims(3, 3);
124+
patch.setFixedSubdivisions(true, Subdivisions(1, 1));
125+
126+
// Build orthonormal basis for the decal plane
127+
Vector3 up(0, 0, 1);
128+
if (std::abs(normal.dot(up)) > 0.9)
129+
{
130+
up = Vector3(0, 1, 0);
131+
}
132+
133+
Vector3 tangent = normal.cross(up).getNormalised();
134+
Vector3 bitangent = tangent.cross(normal).getNormalised();
135+
136+
Vector3 center = intersectionPoint + normal * offset;
137+
138+
double halfWidth = width * 0.5;
139+
double halfHeight = height * 0.5;
140+
141+
Vector3 points[4];
142+
points[0] = center - tangent * halfWidth + bitangent * halfHeight;
143+
points[1] = center + tangent * halfWidth + bitangent * halfHeight;
144+
points[2] = center + tangent * halfWidth - bitangent * halfHeight;
145+
points[3] = center - tangent * halfWidth - bitangent * halfHeight;
146+
147+
// Set up 3x3 control point grid
148+
patch.ctrlAt(0, 0).vertex = points[0];
149+
patch.ctrlAt(2, 0).vertex = points[1];
150+
patch.ctrlAt(1, 0).vertex = (patch.ctrlAt(0, 0).vertex + patch.ctrlAt(2, 0).vertex) / 2;
151+
152+
patch.ctrlAt(0, 1).vertex = (points[0] + points[3]) / 2;
153+
patch.ctrlAt(2, 1).vertex = (points[1] + points[2]) / 2;
154+
patch.ctrlAt(1, 1).vertex = (patch.ctrlAt(0, 1).vertex + patch.ctrlAt(2, 1).vertex) / 2;
155+
156+
patch.ctrlAt(0, 2).vertex = points[3];
157+
patch.ctrlAt(2, 2).vertex = points[2];
158+
patch.ctrlAt(1, 2).vertex = (patch.ctrlAt(0, 2).vertex + patch.ctrlAt(2, 2).vertex) / 2;
159+
160+
patch.setShader(material);
161+
patch.fitTexture(1, 1);
162+
patch.controlPointsChanged();
163+
164+
scene::INodePtr worldspawn = GlobalMapModule().findOrInsertWorldspawn();
165+
if (worldspawn)
166+
{
167+
worldspawn->addChildNode(patchNode);
168+
}
169+
170+
GlobalSelectionSystem().setSelectedAll(false);
171+
Node_setSelected(patchNode, true);
172+
}
173+
174+
} // namespace ui
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#pragma once
2+
3+
#include "imousetool.h"
4+
#include "math/Vector3.h"
5+
#include <string>
6+
7+
namespace ui
8+
{
9+
10+
/**
11+
* Camera mouse tool that places decal patches on brush faces.
12+
*/
13+
class DecalShooterTool :
14+
public MouseTool
15+
{
16+
private:
17+
static std::string _name;
18+
static std::string _displayName;
19+
20+
public:
21+
const std::string& getName() override;
22+
const std::string& getDisplayName() override;
23+
24+
Result onMouseDown(Event& ev) override;
25+
Result onMouseMove(Event& ev) override;
26+
Result onMouseUp(Event& ev) override;
27+
28+
static const std::string& NAME();
29+
30+
private:
31+
void createDecalAtFace(
32+
const Vector3& intersectionPoint,
33+
const Vector3& normal,
34+
double width,
35+
double height,
36+
double offset,
37+
const std::string& material
38+
);
39+
};
40+
41+
} // namespace ui
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#include "FaceIntersectionFinder.h"
2+
3+
#include "ifilter.h"
4+
#include "ibrush.h"
5+
#include "scenelib.h"
6+
#include "math/Vector4.h"
7+
8+
#include <cmath>
9+
10+
namespace ui
11+
{
12+
13+
FaceIntersectionFinder::FaceIntersectionFinder(SelectionTest& test, const Matrix4& viewProjection) :
14+
_selectionTest(test)
15+
{
16+
computeWorldRay(viewProjection, _worldRayOrigin, _worldRayDirection);
17+
}
18+
19+
void FaceIntersectionFinder::computeWorldRay(const Matrix4& viewProjection,
20+
Vector3& outOrigin, Vector3& outDirection)
21+
{
22+
Matrix4 invViewProj = viewProjection.getFullInverse();
23+
24+
// Use getProjected() for proper perspective divide (not getVector3())
25+
Vector4 nearClip = invViewProj.transform(Vector4(0, 0, -1, 1));
26+
Vector4 farClip = invViewProj.transform(Vector4(0, 0, 1, 1));
27+
28+
outOrigin = nearClip.getProjected();
29+
outDirection = (farClip.getProjected() - outOrigin).getNormalised();
30+
}
31+
32+
bool FaceIntersectionFinder::pre(const scene::INodePtr& node)
33+
{
34+
if (!node->visible())
35+
{
36+
return false;
37+
}
38+
39+
if (Node_isEntity(node))
40+
{
41+
return scene::hasChildPrimitives(node);
42+
}
43+
44+
IBrush* brush = Node_getIBrush(node);
45+
46+
if (brush != nullptr)
47+
{
48+
_selectionTest.BeginMesh(node->localToWorld());
49+
50+
for (std::size_t i = 0; i < brush->getNumFaces(); ++i)
51+
{
52+
const IFace& face = brush->getFace(i);
53+
54+
if (!GlobalFilterSystem().isVisible(FilterType::TEXTURE, face.getShader()))
55+
{
56+
continue;
57+
}
58+
59+
const IWinding& winding = face.getWinding();
60+
61+
if (winding.empty())
62+
{
63+
continue;
64+
}
65+
66+
SelectionIntersection intersection;
67+
_selectionTest.TestPolygon(
68+
VertexPointer(&winding.front().vertex, sizeof(WindingVertex)),
69+
winding.size(),
70+
intersection
71+
);
72+
73+
if (intersection.isValid() && intersection.isCloserThan(_bestIntersection))
74+
{
75+
_bestIntersection = intersection;
76+
_hasResult = true;
77+
_bestPlane = face.getPlane3();
78+
_bestNode = node;
79+
}
80+
}
81+
}
82+
83+
return true;
84+
}
85+
86+
FaceIntersection FaceIntersectionFinder::getResult() const
87+
{
88+
FaceIntersection result;
89+
90+
if (!_hasResult)
91+
{
92+
return result;
93+
}
94+
95+
const Matrix4& localToWorld = _bestNode->localToWorld();
96+
97+
// Transform plane to world space
98+
Vector3 localNormal = _bestPlane.normal();
99+
Vector3 localPointOnPlane = localNormal * _bestPlane.dist();
100+
Vector3 worldPointOnPlane = localToWorld.transformPoint(localPointOnPlane);
101+
Vector3 worldNormal = localToWorld.transformDirection(localNormal).getNormalised();
102+
double worldDist = worldNormal.dot(worldPointOnPlane);
103+
104+
// Ray-plane intersection
105+
double denom = worldNormal.dot(_worldRayDirection);
106+
Vector3 worldIntersection;
107+
108+
if (std::abs(denom) > 0.0001)
109+
{
110+
double t = (worldDist - worldNormal.dot(_worldRayOrigin)) / denom;
111+
worldIntersection = _worldRayOrigin + _worldRayDirection * t;
112+
}
113+
else
114+
{
115+
worldIntersection = worldPointOnPlane;
116+
}
117+
118+
result.valid = true;
119+
result.point = worldIntersection;
120+
result.normal = worldNormal;
121+
result.node = _bestNode;
122+
123+
return result;
124+
}
125+
126+
} // namespace ui

0 commit comments

Comments
 (0)