| 1 | #include "quad_knife.h" |
| 2 | |
| 3 | #include "editor.h" |
| 4 | #include "editor_actions.h" |
| 5 | |
| 6 | bool CQuadKnife::IsActive() const |
| 7 | { |
| 8 | return Map()->m_QuadKnifeState.m_Active; |
| 9 | } |
| 10 | |
| 11 | void 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 | |
| 18 | void 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 | |
| 28 | static 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 | |
| 33 | static 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 | |
| 54 | void 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 | |