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