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