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