1#include "quad_knife.h"
2
3#include "editor.h"
4#include "editor_actions.h"
5
6void CQuadKnife::CState::Reset()
7{
8 m_Active = false;
9 m_Count = 0;
10 m_SelectedQuadIndex = -1;
11 std::fill(first: std::begin(arr&: m_aPoints), last: std::end(arr&: m_aPoints), value: vec2(0.0f, 0.0f));
12}
13
14bool CQuadKnife::IsActive() const
15{
16 return Map()->m_QuadKnifeState.m_Active;
17}
18
19void CQuadKnife::Activate(int SelectedQuad)
20{
21 Map()->m_QuadKnifeState.m_Active = true;
22 Map()->m_QuadKnifeState.m_Count = 0;
23 Map()->m_QuadKnifeState.m_SelectedQuadIndex = SelectedQuad;
24}
25
26void CQuadKnife::Deactivate()
27{
28 Map()->m_QuadKnifeState.Reset();
29}
30
31static float TriangleArea(vec2 A, vec2 B, vec2 C)
32{
33 return absolute(a: ((B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y)) * 0.5f);
34}
35
36static bool IsInTriangle(vec2 Point, vec2 A, vec2 B, vec2 C)
37{
38 // Normalize to increase precision
39 vec2 Min(minimum(a: A.x, b: B.x, c: C.x), minimum(a: A.y, b: B.y, c: C.y));
40 vec2 Max(maximum(a: A.x, b: B.x, c: C.x), maximum(a: A.y, b: B.y, c: C.y));
41 vec2 Size(Max.x - Min.x, Max.y - Min.y);
42
43 if(Size.x < 0.0000001f || Size.y < 0.0000001f)
44 return false;
45
46 vec2 Normal(1.f / Size.x, 1.f / Size.y);
47
48 A = (A - Min) * Normal;
49 B = (B - Min) * Normal;
50 C = (C - Min) * Normal;
51 Point = (Point - Min) * Normal;
52
53 float Area = TriangleArea(A, B, C);
54 return Area > 0.f && absolute(a: TriangleArea(A: Point, B: A, C: B) + TriangleArea(A: Point, B, C) + TriangleArea(A: Point, B: C, C: A) - Area) < 0.000001f;
55}
56
57void CQuadKnife::DoSlice()
58{
59 if(Editor()->m_Dialog != DIALOG_NONE || Editor()->Ui()->IsPopupOpen())
60 {
61 return;
62 }
63
64 int QuadIndex = Map()->m_vSelectedQuads[Map()->m_QuadKnifeState.m_SelectedQuadIndex];
65 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
66 CQuad *pQuad = &pLayer->m_vQuads[QuadIndex];
67
68 const bool IgnoreGrid = Editor()->Input()->AltIsPressed();
69 float SnapRadius = 4.f * Editor()->MapView()->MouseWorldScale();
70
71 vec2 Mouse = Editor()->MapView()->MouseWorldPos();
72 vec2 Point = Mouse;
73
74 vec2 v[4] = {
75 vec2(fx2f(v: pQuad->m_aPoints[0].x), fx2f(v: pQuad->m_aPoints[0].y)),
76 vec2(fx2f(v: pQuad->m_aPoints[1].x), fx2f(v: pQuad->m_aPoints[1].y)),
77 vec2(fx2f(v: pQuad->m_aPoints[3].x), fx2f(v: pQuad->m_aPoints[3].y)),
78 vec2(fx2f(v: pQuad->m_aPoints[2].x), fx2f(v: pQuad->m_aPoints[2].y))};
79
80 str_copy(dst&: Editor()->m_aTooltip, src: "Left click inside the quad to select an area to slice. Hold alt to ignore grid. Right click to leave knife mode.");
81
82 if(Editor()->Ui()->MouseButtonClicked(Index: 1))
83 {
84 Deactivate();
85 return;
86 }
87
88 // Handle snapping
89 if(Editor()->MapView()->MapGrid()->IsEnabled() && !IgnoreGrid)
90 {
91 float CellSize = Editor()->MapView()->MapGrid()->GridLineDistance();
92 vec2 OnGrid = Mouse;
93 Editor()->MapView()->MapGrid()->SnapToGrid(Position&: OnGrid);
94
95 if(IsInTriangle(Point: OnGrid, A: v[0], B: v[1], C: v[2]) || IsInTriangle(Point: OnGrid, A: v[0], B: v[3], C: v[2]))
96 Point = OnGrid;
97 else
98 {
99 float MinDistance = -1.f;
100
101 for(int i = 0; i < 4; i++)
102 {
103 int j = (i + 1) % 4;
104 vec2 Min(minimum(a: v[i].x, b: v[j].x), minimum(a: v[i].y, b: v[j].y));
105 vec2 Max(maximum(a: v[i].x, b: v[j].x), maximum(a: v[i].y, b: v[j].y));
106
107 if(in_range(a: OnGrid.y, lower: Min.y, upper: Max.y) && Max.y - Min.y > 0.0000001f)
108 {
109 vec2 OnEdge(v[i].x + (OnGrid.y - v[i].y) / (v[j].y - v[i].y) * (v[j].x - v[i].x), OnGrid.y);
110 float Distance = absolute(a: OnGrid.x - OnEdge.x);
111
112 if(Distance < CellSize && (Distance < MinDistance || MinDistance < 0.f))
113 {
114 MinDistance = Distance;
115 Point = OnEdge;
116 }
117 }
118
119 if(in_range(a: OnGrid.x, lower: Min.x, upper: Max.x) && Max.x - Min.x > 0.0000001f)
120 {
121 vec2 OnEdge(OnGrid.x, v[i].y + (OnGrid.x - v[i].x) / (v[j].x - v[i].x) * (v[j].y - v[i].y));
122 float Distance = absolute(a: OnGrid.y - OnEdge.y);
123
124 if(Distance < CellSize && (Distance < MinDistance || MinDistance < 0.f))
125 {
126 MinDistance = Distance;
127 Point = OnEdge;
128 }
129 }
130 }
131 }
132 }
133 else
134 {
135 float MinDistance = -1.f;
136
137 // Try snapping to corners
138 for(const auto &x : v)
139 {
140 float Distance = distance(a: Mouse, b: x);
141
142 if(Distance <= SnapRadius && (Distance < MinDistance || MinDistance < 0.f))
143 {
144 MinDistance = Distance;
145 Point = x;
146 }
147 }
148
149 if(MinDistance < 0.f)
150 {
151 // Try snapping to edges
152 for(int i = 0; i < 4; i++)
153 {
154 int j = (i + 1) % 4;
155 vec2 s(v[j] - v[i]);
156
157 float t = ((Mouse.x - v[i].x) * s.x + (Mouse.y - v[i].y) * s.y) / (s.x * s.x + s.y * s.y);
158
159 if(in_range(a: t, lower: 0.f, upper: 1.f))
160 {
161 vec2 OnEdge = vec2((v[i].x + t * s.x), (v[i].y + t * s.y));
162 float Distance = distance(a: Mouse, b: OnEdge);
163
164 if(Distance <= SnapRadius && (Distance < MinDistance || MinDistance < 0.f))
165 {
166 MinDistance = Distance;
167 Point = OnEdge;
168 }
169 }
170 }
171 }
172 }
173
174 bool ValidPosition = IsInTriangle(Point, A: v[0], B: v[1], C: v[2]) || IsInTriangle(Point, A: v[0], B: v[3], C: v[2]);
175
176 if(Editor()->Ui()->MouseButtonClicked(Index: 0) && ValidPosition)
177 {
178 Map()->m_QuadKnifeState.m_aPoints[Map()->m_QuadKnifeState.m_Count] = Point;
179 Map()->m_QuadKnifeState.m_Count++;
180 }
181
182 if(Map()->m_QuadKnifeState.m_Count == 4)
183 {
184 auto &aPoints = Map()->m_QuadKnifeState.m_aPoints;
185 if(IsInTriangle(Point: aPoints[3], A: aPoints[0], B: aPoints[1], C: aPoints[2]) ||
186 IsInTriangle(Point: aPoints[1], A: aPoints[0], B: aPoints[2], C: aPoints[3]))
187 {
188 // Fix concave order
189 std::swap(a&: aPoints[0], b&: aPoints[3]);
190 std::swap(a&: aPoints[1], b&: aPoints[2]);
191 }
192
193 std::swap(a&: aPoints[2], b&: aPoints[3]);
194
195 CQuad *pResult = pLayer->NewQuad(x: 64, y: 64, Width: 64, Height: 64);
196 pQuad = &pLayer->m_vQuads[QuadIndex];
197
198 for(int i = 0; i < 4; i++)
199 {
200 int t = IsInTriangle(Point: aPoints[i], A: v[0], B: v[3], C: v[2]) ? 2 : 1;
201
202 vec2 A = vec2(fx2f(v: pQuad->m_aPoints[0].x), fx2f(v: pQuad->m_aPoints[0].y));
203 vec2 B = vec2(fx2f(v: pQuad->m_aPoints[3].x), fx2f(v: pQuad->m_aPoints[3].y));
204 vec2 C = vec2(fx2f(v: pQuad->m_aPoints[t].x), fx2f(v: pQuad->m_aPoints[t].y));
205
206 float TriArea = TriangleArea(A, B, C);
207 float WeightA = TriangleArea(A: aPoints[i], B, C) / TriArea;
208 float WeightB = TriangleArea(A: aPoints[i], B: C, C: A) / TriArea;
209 float WeightC = TriangleArea(A: aPoints[i], B: A, C: B) / TriArea;
210
211 pResult->m_aColors[i].r = (int)std::round(x: pQuad->m_aColors[0].r * WeightA + pQuad->m_aColors[3].r * WeightB + pQuad->m_aColors[t].r * WeightC);
212 pResult->m_aColors[i].g = (int)std::round(x: pQuad->m_aColors[0].g * WeightA + pQuad->m_aColors[3].g * WeightB + pQuad->m_aColors[t].g * WeightC);
213 pResult->m_aColors[i].b = (int)std::round(x: pQuad->m_aColors[0].b * WeightA + pQuad->m_aColors[3].b * WeightB + pQuad->m_aColors[t].b * WeightC);
214 pResult->m_aColors[i].a = (int)std::round(x: pQuad->m_aColors[0].a * WeightA + pQuad->m_aColors[3].a * WeightB + pQuad->m_aColors[t].a * WeightC);
215
216 pResult->m_aTexcoords[i].x = (int)std::round(x: pQuad->m_aTexcoords[0].x * WeightA + pQuad->m_aTexcoords[3].x * WeightB + pQuad->m_aTexcoords[t].x * WeightC);
217 pResult->m_aTexcoords[i].y = (int)std::round(x: pQuad->m_aTexcoords[0].y * WeightA + pQuad->m_aTexcoords[3].y * WeightB + pQuad->m_aTexcoords[t].y * WeightC);
218
219 pResult->m_aPoints[i].x = f2fx(v: aPoints[i].x);
220 pResult->m_aPoints[i].y = f2fx(v: aPoints[i].y);
221 }
222
223 pResult->m_aPoints[4].x = ((pResult->m_aPoints[0].x + pResult->m_aPoints[3].x) / 2 + (pResult->m_aPoints[1].x + pResult->m_aPoints[2].x) / 2) / 2;
224 pResult->m_aPoints[4].y = ((pResult->m_aPoints[0].y + pResult->m_aPoints[3].y) / 2 + (pResult->m_aPoints[1].y + pResult->m_aPoints[2].y) / 2) / 2;
225
226 Map()->m_QuadKnifeState.m_Count = 0;
227 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionNewQuad>(args: Map(), args&: Map()->m_SelectedGroup, args&: Map()->m_vSelectedLayers[0]));
228 }
229
230 // Render
231 Graphics()->TextureClear();
232 Graphics()->LinesBegin();
233
234 IGraphics::CLineItem aEdges[] = {
235 IGraphics::CLineItem(v[0], v[1]),
236 IGraphics::CLineItem(v[1], v[2]),
237 IGraphics::CLineItem(v[2], v[3]),
238 IGraphics::CLineItem(v[3], v[0])};
239
240 Graphics()->SetColor(r: 1.f, g: 0.5f, b: 0.f, a: 1.f);
241 Graphics()->LinesDraw(pArray: aEdges, Num: std::size(aEdges));
242
243 IGraphics::CLineItem aLines[4];
244 int LineCount = maximum(a: Map()->m_QuadKnifeState.m_Count - 1, b: 0);
245
246 for(int i = 0; i < LineCount; i++)
247 aLines[i] = IGraphics::CLineItem(Map()->m_QuadKnifeState.m_aPoints[i], Map()->m_QuadKnifeState.m_aPoints[i + 1]);
248
249 Graphics()->SetColor(r: 1.f, g: 1.f, b: 1.f, a: 1.f);
250 Graphics()->LinesDraw(pArray: aLines, Num: LineCount);
251
252 if(ValidPosition)
253 {
254 if(Map()->m_QuadKnifeState.m_Count > 0)
255 {
256 IGraphics::CLineItem LineCurrent(Point, Map()->m_QuadKnifeState.m_aPoints[Map()->m_QuadKnifeState.m_Count - 1]);
257 Graphics()->LinesDraw(pArray: &LineCurrent, Num: 1);
258 }
259
260 if(Map()->m_QuadKnifeState.m_Count == 3)
261 {
262 IGraphics::CLineItem LineClose(Point, Map()->m_QuadKnifeState.m_aPoints[0]);
263 Graphics()->LinesDraw(pArray: &LineClose, Num: 1);
264 }
265 }
266
267 Graphics()->LinesEnd();
268 Graphics()->QuadsBegin();
269
270 IGraphics::CQuadItem aMarkers[4];
271
272 for(int i = 0; i < Map()->m_QuadKnifeState.m_Count; i++)
273 aMarkers[i] = IGraphics::CQuadItem(Map()->m_QuadKnifeState.m_aPoints[i].x, Map()->m_QuadKnifeState.m_aPoints[i].y, 5.f * Editor()->MapView()->MouseWorldScale(), 5.f * Editor()->MapView()->MouseWorldScale());
274
275 Graphics()->SetColor(r: 0.f, g: 0.f, b: 1.f, a: 1.f);
276 Graphics()->QuadsDraw(pArray: aMarkers, Num: Map()->m_QuadKnifeState.m_Count);
277
278 if(ValidPosition)
279 {
280 IGraphics::CQuadItem MarkerCurrent(Point.x, Point.y, 5.f * Editor()->MapView()->MouseWorldScale(), 5.f * Editor()->MapView()->MouseWorldScale());
281 Graphics()->QuadsDraw(pArray: &MarkerCurrent, Num: 1);
282 }
283
284 Graphics()->QuadsEnd();
285}
286