1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "layer_quads.h"
4
5#include "image.h"
6
7#include <game/editor/editor.h>
8#include <game/editor/editor_actions.h>
9
10#include <limits>
11
12CLayerQuads::CLayerQuads(CEditorMap *pMap) :
13 CLayer(pMap, LAYERTYPE_QUADS)
14{
15 m_aName[0] = '\0';
16 m_Image = -1;
17}
18
19CLayerQuads::CLayerQuads(const CLayerQuads &Other) :
20 CLayer(Other)
21{
22 m_Image = Other.m_Image;
23 m_vQuads = Other.m_vQuads;
24}
25
26CLayerQuads::~CLayerQuads() = default;
27
28void CLayerQuads::Render(const CEditorMap *pRenderMap)
29{
30 if(m_Image >= 0 && (size_t)m_Image < pRenderMap->m_vpImages.size())
31 {
32 Graphics()->TextureSet(Texture: pRenderMap->m_vpImages[m_Image]->m_Texture);
33 }
34 else
35 {
36 Graphics()->TextureClear();
37 }
38
39 Graphics()->BlendNone();
40 Editor()->RenderMap()->ForceRenderQuads(pQuads: m_vQuads.data(), NumQuads: m_vQuads.size(), Flags: LAYERRENDERFLAG_OPAQUE, pEnvEval: &pRenderMap->m_EnvelopeEvaluator);
41 Graphics()->BlendNormal();
42 Editor()->RenderMap()->ForceRenderQuads(pQuads: m_vQuads.data(), NumQuads: m_vQuads.size(), Flags: LAYERRENDERFLAG_TRANSPARENT, pEnvEval: &pRenderMap->m_EnvelopeEvaluator);
43}
44
45CQuad *CLayerQuads::NewQuad(int x, int y, int Width, int Height)
46{
47 Map()->OnModify();
48
49 m_vQuads.emplace_back();
50 CQuad *pQuad = &m_vQuads[m_vQuads.size() - 1];
51
52 pQuad->m_PosEnv = -1;
53 pQuad->m_ColorEnv = -1;
54 pQuad->m_PosEnvOffset = 0;
55 pQuad->m_ColorEnvOffset = 0;
56
57 Width /= 2;
58 Height /= 2;
59 pQuad->m_aPoints[0].x = i2fx(v: x - Width);
60 pQuad->m_aPoints[0].y = i2fx(v: y - Height);
61 pQuad->m_aPoints[1].x = i2fx(v: x + Width);
62 pQuad->m_aPoints[1].y = i2fx(v: y - Height);
63 pQuad->m_aPoints[2].x = i2fx(v: x - Width);
64 pQuad->m_aPoints[2].y = i2fx(v: y + Height);
65 pQuad->m_aPoints[3].x = i2fx(v: x + Width);
66 pQuad->m_aPoints[3].y = i2fx(v: y + Height);
67
68 pQuad->m_aPoints[4].x = i2fx(v: x); // pivot
69 pQuad->m_aPoints[4].y = i2fx(v: y);
70
71 pQuad->m_aTexcoords[0].x = i2fx(v: 0);
72 pQuad->m_aTexcoords[0].y = i2fx(v: 0);
73
74 pQuad->m_aTexcoords[1].x = i2fx(v: 1);
75 pQuad->m_aTexcoords[1].y = i2fx(v: 0);
76
77 pQuad->m_aTexcoords[2].x = i2fx(v: 0);
78 pQuad->m_aTexcoords[2].y = i2fx(v: 1);
79
80 pQuad->m_aTexcoords[3].x = i2fx(v: 1);
81 pQuad->m_aTexcoords[3].y = i2fx(v: 1);
82
83 std::fill(first: std::begin(arr&: pQuad->m_aColors), last: std::end(arr&: pQuad->m_aColors), value: CColor{255, 255, 255, 255});
84
85 return pQuad;
86}
87
88void CLayerQuads::BrushSelecting(CUIRect Rect)
89{
90 Rect.DrawOutline(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
91}
92
93int CLayerQuads::BrushGrab(CLayerGroup *pBrush, CUIRect Rect)
94{
95 // create new layers
96 std::shared_ptr<CLayerQuads> pGrabbed = std::make_shared<CLayerQuads>(args: pBrush->Map());
97 pGrabbed->m_Image = m_Image;
98 pBrush->AddLayer(pLayer: pGrabbed);
99
100 for(const auto &Quad : m_vQuads)
101 {
102 float PointX = fx2f(v: Quad.m_aPoints[4].x);
103 float PointY = fx2f(v: Quad.m_aPoints[4].y);
104
105 if(PointX > Rect.x && PointX < Rect.x + Rect.w && PointY > Rect.y && PointY < Rect.y + Rect.h)
106 {
107 CQuad NewQuad = Quad;
108 for(auto &Point : NewQuad.m_aPoints)
109 {
110 Point.x -= f2fx(v: Rect.x);
111 Point.y -= f2fx(v: Rect.y);
112 }
113
114 pGrabbed->m_vQuads.push_back(x: NewQuad);
115 }
116 }
117
118 return pGrabbed->m_vQuads.empty() ? 0 : 1;
119}
120
121void CLayerQuads::BrushPlace(CLayer *pBrush, vec2 WorldPos)
122{
123 if(m_Readonly)
124 return;
125
126 CLayerQuads *pQuadLayer = static_cast<CLayerQuads *>(pBrush);
127 std::vector<CQuad> vAddedQuads;
128 for(const auto &Quad : pQuadLayer->m_vQuads)
129 {
130 CQuad NewQuad = Quad;
131 for(auto &Point : NewQuad.m_aPoints)
132 {
133 Point.x += f2fx(v: WorldPos.x);
134 Point.y += f2fx(v: WorldPos.y);
135 }
136
137 m_vQuads.push_back(x: NewQuad);
138 vAddedQuads.push_back(x: NewQuad);
139 }
140 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionQuadPlace>(args: Map(), args&: Map()->m_SelectedGroup, args&: Map()->m_vSelectedLayers[0], args&: vAddedQuads));
141 Map()->OnModify();
142}
143
144void CLayerQuads::BrushFlipX()
145{
146 // calculate bounding box
147 int LeftBound = std::numeric_limits<int>::max();
148 int RightBound = std::numeric_limits<int>::min();
149 for(auto &Quad : m_vQuads)
150 {
151 for(int PointId = 0; PointId < 4; ++PointId)
152 {
153 LeftBound = std::min(a: Quad.m_aPoints[PointId].x, b: LeftBound);
154 RightBound = std::max(a: Quad.m_aPoints[PointId].x, b: RightBound);
155 }
156 }
157
158 // flip box
159 for(auto &Quad : m_vQuads)
160 {
161 for(auto &Point : Quad.m_aPoints)
162 {
163 Point.x = RightBound - (Point.x - LeftBound);
164 }
165 }
166 Map()->OnModify();
167}
168
169void CLayerQuads::BrushFlipY()
170{
171 // calculate bounding box
172 int TopBound = std::numeric_limits<int>::max();
173 int BottomBound = std::numeric_limits<int>::min();
174 for(auto &Quad : m_vQuads)
175 {
176 for(int PointId = 0; PointId < 4; ++PointId)
177 {
178 TopBound = std::min(a: Quad.m_aPoints[PointId].y, b: TopBound);
179 BottomBound = std::max(a: Quad.m_aPoints[PointId].y, b: BottomBound);
180 }
181 }
182
183 // flip box
184 for(auto &Quad : m_vQuads)
185 {
186 for(auto &Point : Quad.m_aPoints)
187 {
188 Point.y = BottomBound - (Point.y - TopBound);
189 }
190 }
191 Map()->OnModify();
192}
193
194static void Rotate(vec2 *pCenter, vec2 *pPoint, float Rotation)
195{
196 float x = pPoint->x - pCenter->x;
197 float y = pPoint->y - pCenter->y;
198 pPoint->x = x * std::cos(x: Rotation) - y * std::sin(x: Rotation) + pCenter->x;
199 pPoint->y = x * std::sin(x: Rotation) + y * std::cos(x: Rotation) + pCenter->y;
200}
201
202void CLayerQuads::BrushRotate(float Amount)
203{
204 vec2 Center;
205 GetSize(pWidth: &Center.x, pHeight: &Center.y);
206 Center.x /= 2;
207 Center.y /= 2;
208
209 for(auto &Quad : m_vQuads)
210 {
211 for(auto &Point : Quad.m_aPoints)
212 {
213 vec2 Pos(fx2f(v: Point.x), fx2f(v: Point.y));
214 Rotate(pCenter: &Center, pPoint: &Pos, Rotation: Amount);
215 Point.x = f2fx(v: Pos.x);
216 Point.y = f2fx(v: Pos.y);
217 }
218 }
219}
220
221void CLayerQuads::GetSize(float *pWidth, float *pHeight)
222{
223 *pWidth = 0;
224 *pHeight = 0;
225
226 for(const auto &Quad : m_vQuads)
227 {
228 for(const auto &Point : Quad.m_aPoints)
229 {
230 *pWidth = std::max(a: *pWidth, b: fx2f(v: Point.x));
231 *pHeight = std::max(a: *pHeight, b: fx2f(v: Point.y));
232 }
233 }
234}
235
236CUi::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox)
237{
238 CProperty aProps[] = {
239 {"Image", m_Image, PROPTYPE_IMAGE, -1, 0},
240 {nullptr},
241 };
242
243 static int s_aIds[(int)ELayerQuadsProp::NUM_PROPS] = {0};
244 int NewVal = 0;
245 auto [State, Prop] = Editor()->DoPropertiesWithState<ELayerQuadsProp>(pToolbox: pToolBox, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
246 if(Prop != ELayerQuadsProp::NONE && (State == EEditState::END || State == EEditState::ONE_GO))
247 {
248 Map()->OnModify();
249 }
250
251 Map()->m_LayerQuadPropTracker.Begin(pObject: this, Prop, State);
252
253 if(Prop == ELayerQuadsProp::IMAGE)
254 {
255 if(NewVal >= 0)
256 m_Image = NewVal % Map()->m_vpImages.size();
257 else
258 m_Image = -1;
259 }
260
261 Map()->m_LayerQuadPropTracker.End(Prop, State);
262
263 return CUi::POPUP_KEEP_OPEN;
264}
265
266bool CLayerQuads::IsEnvelopeUsed(int EnvelopeIndex) const
267{
268 return std::any_of(first: m_vQuads.begin(), last: m_vQuads.end(), pred: [&](const auto &Quad) {
269 return Quad.m_PosEnv == EnvelopeIndex || Quad.m_ColorEnv == EnvelopeIndex;
270 });
271}
272
273bool CLayerQuads::IsImageUsed(int ImageIndex) const
274{
275 return m_Image == ImageIndex;
276}
277
278void CLayerQuads::ModifyImageIndex(const FIndexModifyFunction &IndexModifyFunction)
279{
280 IndexModifyFunction(&m_Image);
281}
282
283void CLayerQuads::ModifyEnvelopeIndex(const FIndexModifyFunction &IndexModifyFunction)
284{
285 for(auto &Quad : m_vQuads)
286 {
287 IndexModifyFunction(&Quad.m_PosEnv);
288 IndexModifyFunction(&Quad.m_ColorEnv);
289 }
290}
291
292std::shared_ptr<CLayer> CLayerQuads::Duplicate() const
293{
294 return std::make_shared<CLayerQuads>(args: *this);
295}
296
297int CLayerQuads::SwapQuads(int Index0, int Index1)
298{
299 if(Index0 < 0 || Index0 >= (int)m_vQuads.size())
300 return Index0;
301 if(Index1 < 0 || Index1 >= (int)m_vQuads.size())
302 return Index0;
303 if(Index0 == Index1)
304 return Index0;
305 Map()->OnModify();
306 std::swap(a&: m_vQuads[Index0], b&: m_vQuads[Index1]);
307 return Index1;
308}
309
310const char *CLayerQuads::TypeName() const
311{
312 return "quads";
313}
314