1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Basic class for vertex outlines */
18
19#include <C4Include.h>
20#include <C4Shape.h>
21
22#include <C4Physics.h>
23#include <C4Material.h>
24#include <C4Wrappers.h>
25
26bool C4Shape::AddVertex(int32_t iX, int32_t iY)
27{
28 if (VtxNum >= C4D_MaxVertex) return false;
29 VtxX[VtxNum] = iX; VtxY[VtxNum] = iY;
30 VtxNum++;
31 return true;
32}
33
34C4Shape::C4Shape() : ContactDensity{C4M_Solid}, AttachMat{MNone} {}
35
36void C4Shape::Default()
37{
38 *this = {};
39}
40
41void C4Shape::Rotate(int32_t iAngle, bool bUpdateVertices)
42{
43#ifdef DEBUGREC
44 C4RCRotVtx rc;
45 rc.x = x; rc.y = y; rc.wdt = Wdt; rc.hgt = Hgt; rc.r = iAngle;
46 int32_t i = 0;
47 for (; i < 4; ++i)
48 {
49 rc.VtxX[i] = VtxX[i]; rc.VtxY[i] = VtxY[i];
50 }
51 AddDbgRec(RCT_RotVtx1, &rc, sizeof(rc));
52#endif
53 int32_t cnt, nvtx, nvty, rdia;
54
55 C4Fixed mtx[4];
56 C4Fixed fAngle = itofix(x: iAngle);
57
58 if (bUpdateVertices)
59 {
60 // Calculate rotation matrix
61 mtx[0] = Cos(fAngle); mtx[1] = -Sin(fAngle);
62 mtx[2] = -mtx[1]; mtx[3] = mtx[0];
63 // Rotate vertices
64 for (cnt = 0; cnt < VtxNum; cnt++)
65 {
66 nvtx = fixtoi(x: mtx[0] * VtxX[cnt] + mtx[1] * VtxY[cnt]);
67 nvty = fixtoi(x: mtx[2] * VtxX[cnt] + mtx[3] * VtxY[cnt]);
68 VtxX[cnt] = nvtx; VtxY[cnt] = nvty;
69 }
70
71 /* This is freaking nuts. I used the int32_t* to shortcut the
72 two int32_t arrays Shape.Vtx_[]. Without modifications to
73 this code, after rotation the x-values of vertex 2 and 4
74 are screwed to that of vertex 0. Direct use of the array
75 variables instead of the pointers helped. Later in
76 development, again without modification to this code, the
77 same error occured again. I moved back to pointer array
78 shortcut and it worked again. ?!
79
80 The error occurs after the C4DefCore structure has
81 changed. It must have something to do with struct
82 member alignment. But why does pointer usage vs. array
83 index make a difference?
84 */
85 }
86
87 // Enlarge Rect
88 rdia = static_cast<int32_t>(sqrt(x: double(x * x + y * y))) + 2;
89 x = -rdia;
90 y = -rdia;
91 Wdt = 2 * rdia;
92 Hgt = 2 * rdia;
93#ifdef DEBUGREC
94 rc.x = x; rc.y = y; rc.wdt = Wdt; rc.hgt = Hgt;
95 for (i = 0; i < 4; ++i)
96 {
97 rc.VtxX[i] = VtxX[i]; rc.VtxY[i] = VtxY[i];
98 }
99 AddDbgRec(RCT_RotVtx2, &rc, sizeof(rc));
100#endif
101}
102
103void C4Shape::Stretch(int32_t iPercent, bool bUpdateVertices)
104{
105 int32_t cnt;
106 x = x * iPercent / 100;
107 y = y * iPercent / 100;
108 Wdt = Wdt * iPercent / 100;
109 Hgt = Hgt * iPercent / 100;
110 FireTop = FireTop * iPercent / 100;
111 if (bUpdateVertices)
112 for (cnt = 0; cnt < VtxNum; cnt++)
113 {
114 VtxX[cnt] = VtxX[cnt] * iPercent / 100;
115 VtxY[cnt] = VtxY[cnt] * iPercent / 100;
116 }
117}
118
119void C4Shape::Jolt(int32_t iPercent, bool bUpdateVertices)
120{
121 int32_t cnt;
122 y = y * iPercent / 100;
123 Hgt = Hgt * iPercent / 100;
124 FireTop = FireTop * iPercent / 100;
125 if (bUpdateVertices)
126 for (cnt = 0; cnt < VtxNum; cnt++)
127 VtxY[cnt] = VtxY[cnt] * iPercent / 100;
128}
129
130void C4Shape::GetVertexOutline(C4Rect &rRect)
131{
132 int32_t cnt;
133 rRect.x = rRect.y = rRect.Wdt = rRect.Hgt = 0;
134 for (cnt = 0; cnt < VtxNum; cnt++)
135 {
136 // Extend left
137 if (VtxX[cnt] < rRect.x)
138 {
139 rRect.Wdt += rRect.x - VtxX[cnt];
140 rRect.x = VtxX[cnt];
141 }
142 // Extend right
143 else if (VtxX[cnt] > rRect.x + rRect.Wdt)
144 {
145 rRect.Wdt = VtxX[cnt] - rRect.x;
146 }
147
148 // Extend up
149 if (VtxY[cnt] < rRect.y)
150 {
151 rRect.Hgt += rRect.y - VtxY[cnt];
152 rRect.y = VtxY[cnt];
153 }
154 // Extend down
155 else if (VtxY[cnt] > rRect.y + rRect.Hgt)
156 {
157 rRect.Hgt = VtxY[cnt] - rRect.y;
158 }
159 }
160
161 rRect.Hgt += rRect.y - y;
162 rRect.y = y;
163}
164
165bool C4Shape::Attach(int32_t &cx, int32_t &cy, uint8_t cnat_pos)
166{
167 // Adjust given position to one pixel before contact
168 // at vertices matching CNAT request.
169
170 bool fAttached = false;
171
172 int32_t vtx, xcnt, ycnt, xcrng, ycrng, xcd, ycd;
173 int32_t motion_x = 0; uint8_t cpix;
174
175 // reset attached material
176 AttachMat = MNone;
177
178 // New attachment behaviour in CE:
179 // Before, attachment was done by searching through all vertices,
180 // and doing attachment to any vertex with a matching CNAT.
181 // While this worked well for normal Clonk attachment, it caused nonsense
182 // behaviour if multiple vertices matched the same CNAT. In effect, attachment
183 // was then done to the last vertex only, usually stucking the object sooner
184 // or later.
185 // For instance, the scaling procedure of regular Clonks uses two CNAT_Left-
186 // vertices (shoulder+belly), which "block" each other in situations like
187 // scaling up battlements of towers. That way, the 2px-overhang of the
188 // battlement is sufficient for keeping out scaling Clonks. The drawback is
189 // that sometimes Clonks get stuck scaling in very sharp edges or single
190 // floating material pixels; occuring quite often in Caverace, or maps where
191 // you blast Granite and many single pixels remain.
192 //
193 // Until a better solution for designing battlements is found, the old-style
194 // behaviour will be used for Clonks. Both code variants should behave equally
195 // for objects with only one matching vertex to cnat_pos.
196 if (!(cnat_pos & CNAT_MultiAttach))
197 {
198 // old-style attachment
199 for (vtx = 0; vtx < VtxNum; vtx++)
200 if (VtxCNAT[vtx] & cnat_pos)
201 {
202 xcd = ycd = 0;
203 switch (cnat_pos & (~CNAT_Flags))
204 {
205 case CNAT_Top: ycd = -1; break;
206 case CNAT_Bottom: ycd = +1; break;
207 case CNAT_Left: xcd = -1; break;
208 case CNAT_Right: xcd = +1; break;
209 }
210 xcrng = AttachRange * xcd * (-1); ycrng = AttachRange * ycd * (-1);
211 for (xcnt = xcrng, ycnt = ycrng; (xcnt != -xcrng) || (ycnt != -ycrng); xcnt += xcd, ycnt += ycd)
212 {
213 int32_t ax = cx + VtxX[vtx] + xcnt + xcd, ay = cy + VtxY[vtx] + ycnt + ycd;
214 if (GBackDensity(x: ax, y: ay) >= ContactDensity && ax >= 0 && ax < GBackWdt)
215 {
216 cpix = GBackPix(x: ax, y: ay);
217 AttachMat = PixCol2Mat(pixc: cpix);
218 iAttachX = ax; iAttachY = ay;
219 iAttachVtx = vtx;
220 cx += xcnt; cy += ycnt;
221 fAttached = 1;
222 break;
223 }
224 }
225 }
226 }
227 else // CNAT_MultiAttach
228 {
229 // new-style attachment
230 // determine attachment direction
231 xcd = ycd = 0;
232 switch (cnat_pos & (~CNAT_Flags))
233 {
234 case CNAT_Top: ycd = -1; break;
235 case CNAT_Bottom: ycd = +1; break;
236 case CNAT_Left: xcd = -1; break;
237 case CNAT_Right: xcd = +1; break;
238 }
239 // check within attachment range
240 xcrng = AttachRange * xcd * (-1); ycrng = AttachRange * ycd * (-1);
241 for (xcnt = xcrng, ycnt = ycrng; (xcnt != -xcrng) || (ycnt != -ycrng); xcnt += xcd, ycnt += ycd)
242 // check all vertices with matching CNAT
243 for (vtx = 0; vtx < VtxNum; vtx++)
244 if (VtxCNAT[vtx] & cnat_pos)
245 {
246 // get new vertex pos
247 int32_t ax = cx + VtxX[vtx] + xcnt + xcd, ay = cy + VtxY[vtx] + ycnt + ycd;
248 // can attach here?
249 cpix = GBackPix(x: ax, y: ay);
250 if (MatDensity(mat: PixCol2Mat(pixc: cpix)) >= ContactDensity && ax >= 0 && ax < GBackWdt)
251 {
252 // store attachment material
253 AttachMat = PixCol2Mat(pixc: cpix);
254 // store absolute attachment position
255 iAttachX = ax; iAttachY = ay;
256 iAttachVtx = vtx;
257 // move position here
258 cx += xcnt; cy += ycnt;
259 // mark attachment
260 fAttached = 1;
261 // break both looops
262 xcnt = -xcrng - xcd; ycnt = -ycrng - ycd;
263 break;
264 }
265 }
266 }
267 // both attachments: apply motion done by SolidMasks
268 if (motion_x) cx += BoundBy<int32_t>(bval: motion_x, lbound: -1, rbound: 1);
269
270 return fAttached;
271}
272
273bool C4Shape::LineConnect(int32_t tx, int32_t ty, int32_t cvtx, int32_t ld, int32_t oldx, int32_t oldy)
274{
275 if (VtxNum < 2) return false;
276
277 // No modification
278 if ((VtxX[cvtx] == tx) && (VtxY[cvtx] == ty)) return true;
279
280 // Check new path
281 int32_t ix, iy;
282 if (PathFree(x1: tx, y1: ty, x2: VtxX[cvtx + ld], y2: VtxY[cvtx + ld], ix: &ix, iy: &iy))
283 {
284 // Okay, set vertex
285 VtxX[cvtx] = tx; VtxY[cvtx] = ty;
286 return true;
287 }
288 else
289 {
290 // Intersected, find bend vertex
291 bool found = false;
292 int32_t cix;
293 int32_t ciy;
294 for (int irange = 4; irange <= 12; irange += 4)
295 for (cix = ix - irange / 2; cix <= ix + irange; cix += irange)
296 for (ciy = iy - irange / 2; ciy <= iy + irange; ciy += irange)
297 {
298 if (PathFree(x1: cix, y1: ciy, x2: tx, y2: ty) && PathFree(x1: cix, y1: ciy, x2: VtxX[cvtx + ld], y2: VtxY[cvtx + ld]))
299 {
300 found = true;
301 goto out;
302 }
303 }
304 out:
305 if (!found)
306 {
307 // try bending directly at path the line took
308 // allow going through vehicle in this case to allow lines through castles and elevator shafts
309 cix = oldx;
310 ciy = oldy;
311 if (!PathFreeIgnoreVehicle(x1: cix, y1: ciy, x2: tx, y2: ty) || !PathFreeIgnoreVehicle(x1: cix, y1: ciy, x2: VtxX[cvtx + ld], y2: VtxY[cvtx + ld]))
312 if (!PathFreeIgnoreVehicle(x1: cix, y1: ciy, x2: tx, y2: ty) || !PathFreeIgnoreVehicle(x1: cix, y1: ciy, x2: VtxX[cvtx + ld], y2: VtxY[cvtx + ld]))
313 return false; // Found no bend vertex
314 }
315 // Insert bend vertex
316 if (ld > 0)
317 {
318 if (!InsertVertex(iPos: cvtx + 1, tx: cix, ty: ciy)) return false;
319 }
320 else
321 {
322 if (!InsertVertex(iPos: cvtx, tx: cix, ty: ciy)) return false;
323 cvtx++;
324 }
325 // Okay, set vertex
326 VtxX[cvtx] = tx; VtxY[cvtx] = ty;
327 return true;
328 }
329
330 return false;
331}
332
333bool C4Shape::InsertVertex(int32_t iPos, int32_t tx, int32_t ty)
334{
335 if (VtxNum + 1 > C4D_MaxVertex) return false;
336 // Insert vertex before iPos
337 for (int32_t cnt = VtxNum; cnt > iPos; cnt--)
338 {
339 VtxX[cnt] = VtxX[cnt - 1]; VtxY[cnt] = VtxY[cnt - 1];
340 }
341 VtxX[iPos] = tx; VtxY[iPos] = ty;
342 VtxNum++;
343 return true;
344}
345
346bool C4Shape::RemoveVertex(int32_t iPos)
347{
348 if (!Inside<int32_t>(ival: iPos, lbound: 0, rbound: VtxNum - 1)) return false;
349 for (int32_t cnt = iPos; cnt + 1 < VtxNum; cnt++)
350 {
351 VtxX[cnt] = VtxX[cnt + 1]; VtxY[cnt] = VtxY[cnt + 1];
352 }
353 VtxNum--;
354 return true;
355}
356
357bool C4Shape::CheckContact(int32_t cx, int32_t cy)
358{
359 // Check all vertices at given object position.
360 // Return true on any contact.
361
362 for (int32_t cvtx = 0; cvtx < VtxNum; cvtx++)
363 if (!(VtxCNAT[cvtx] & CNAT_NoCollision))
364 if (GBackDensity(x: cx + VtxX[cvtx], y: cy + VtxY[cvtx]) >= ContactDensity)
365 return true;
366
367 return false;
368}
369
370bool C4Shape::ContactCheck(int32_t cx, int32_t cy)
371{
372 // Check all vertices at given object position.
373 // Set ContactCNAT and ContactCount.
374 // Set VtxContactCNAT and VtxContactMat.
375 // Return true on any contact.
376
377 ContactCNAT = CNAT_None;
378 ContactCount = 0;
379
380 for (int32_t cvtx = 0; cvtx < VtxNum; cvtx++)
381
382 // Ignore vertex if collision has been flagged out
383 if (!(VtxCNAT[cvtx] & CNAT_NoCollision))
384
385 {
386 VtxContactCNAT[cvtx] = CNAT_None;
387 VtxContactMat[cvtx] = GBackMat(x: cx + VtxX[cvtx], y: cy + VtxY[cvtx]);
388
389 if (GBackDensity(x: cx + VtxX[cvtx], y: cy + VtxY[cvtx]) >= ContactDensity)
390 {
391 ContactCNAT |= VtxCNAT[cvtx];
392 VtxContactCNAT[cvtx] |= CNAT_Center;
393 ContactCount++;
394 // Vertex center contact, now check top,bottom,left,right
395 if (GBackDensity(x: cx + VtxX[cvtx], y: cy + VtxY[cvtx] - 1) >= ContactDensity)
396 VtxContactCNAT[cvtx] |= CNAT_Top;
397 if (GBackDensity(x: cx + VtxX[cvtx], y: cy + VtxY[cvtx] + 1) >= ContactDensity)
398 VtxContactCNAT[cvtx] |= CNAT_Bottom;
399 if (GBackDensity(x: cx + VtxX[cvtx] - 1, y: cy + VtxY[cvtx]) >= ContactDensity)
400 VtxContactCNAT[cvtx] |= CNAT_Left;
401 if (GBackDensity(x: cx + VtxX[cvtx] + 1, y: cy + VtxY[cvtx]) >= ContactDensity)
402 VtxContactCNAT[cvtx] |= CNAT_Right;
403 }
404 }
405
406 return ContactCount;
407}
408
409int32_t C4Shape::GetVertexX(int32_t iVertex)
410{
411 if (!Inside<int32_t>(ival: iVertex, lbound: 0, rbound: VtxNum - 1)) return 0;
412 return VtxX[iVertex];
413}
414
415int32_t C4Shape::GetVertexY(int32_t iVertex)
416{
417 if (!Inside<int32_t>(ival: iVertex, lbound: 0, rbound: VtxNum - 1)) return 0;
418 return VtxY[iVertex];
419}
420
421void C4Shape::CopyFrom(C4Shape rFrom, bool bCpyVertices, bool fCopyVerticesFromSelf)
422{
423 if (bCpyVertices)
424 {
425 // truncate / copy vertex count
426 VtxNum = (fCopyVerticesFromSelf ? std::min<int32_t>(a: VtxNum, C4D_VertexCpyPos) : rFrom.VtxNum);
427 // restore vertices from back of own buffer (retaining count)
428 int32_t iCopyPos = (fCopyVerticesFromSelf ? C4D_VertexCpyPos : 0);
429 C4Shape &rVtxFrom = (fCopyVerticesFromSelf ? *this : rFrom);
430 memcpy(dest: VtxX, src: rVtxFrom.VtxX + iCopyPos, n: VtxNum * sizeof(*VtxX));
431 memcpy(dest: VtxY, src: rVtxFrom.VtxY + iCopyPos, n: VtxNum * sizeof(*VtxY));
432 memcpy(dest: VtxCNAT, src: rVtxFrom.VtxCNAT + iCopyPos, n: VtxNum * sizeof(*VtxCNAT));
433 memcpy(dest: VtxFriction, src: rVtxFrom.VtxFriction + iCopyPos, n: VtxNum * sizeof(*VtxFriction));
434 memcpy(dest: VtxContactCNAT, src: rVtxFrom.VtxContactCNAT + iCopyPos, n: VtxNum * sizeof(*VtxContactCNAT));
435 memcpy(dest: VtxContactMat, src: rVtxFrom.VtxContactMat + iCopyPos, n: VtxNum * sizeof(*VtxContactMat));
436 // continue: copies other members
437 }
438 *static_cast<C4Rect *>(this) = rFrom;
439 AttachMat = rFrom.AttachMat;
440 ContactCNAT = rFrom.ContactCNAT;
441 ContactCount = rFrom.ContactCount;
442 FireTop = rFrom.FireTop;
443}
444
445int32_t C4Shape::GetBottomVertex()
446{
447 // return bottom-most vertex
448 int32_t iMax = -1;
449 for (int32_t i = 0; i < VtxNum; i++)
450 if (VtxCNAT[i] & CNAT_Bottom)
451 if (iMax == -1 || VtxY[i] < VtxY[iMax])
452 iMax = i;
453 return iMax;
454}
455
456C4DensityProvider DefaultDensityProvider;
457
458int32_t C4DensityProvider::GetDensity(int32_t x, int32_t y) const
459{
460 // default density provider checks the landscape
461 return GBackDensity(x, y);
462}
463
464int32_t C4Shape::GetVertexContact(int32_t iVtx, uint32_t dwCheckMask, int32_t tx, int32_t ty, const C4DensityProvider &rDensityProvider)
465{
466 // default check mask
467 if (!dwCheckMask) dwCheckMask = VtxCNAT[iVtx];
468 // check vertex positions (vtx num not range-checked!)
469 tx += VtxX[iVtx]; ty += VtxY[iVtx];
470 int32_t iContact = 0;
471 // check all directions for solid mat
472 if (~VtxCNAT[iVtx] & CNAT_NoCollision)
473 {
474 if (dwCheckMask & CNAT_Center) if (rDensityProvider.GetDensity(x: tx, y: ty) >= ContactDensity) iContact |= CNAT_Center;
475 if (dwCheckMask & CNAT_Left) if (rDensityProvider.GetDensity(x: tx - 1, y: ty) >= ContactDensity) iContact |= CNAT_Left;
476 if (dwCheckMask & CNAT_Right) if (rDensityProvider.GetDensity(x: tx + 1, y: ty) >= ContactDensity) iContact |= CNAT_Right;
477 if (dwCheckMask & CNAT_Top) if (rDensityProvider.GetDensity(x: tx, y: ty - 1) >= ContactDensity) iContact |= CNAT_Top;
478 if (dwCheckMask & CNAT_Bottom) if (rDensityProvider.GetDensity(x: tx, y: ty + 1) >= ContactDensity) iContact |= CNAT_Bottom;
479 }
480 // return resulting bitmask
481 return iContact;
482}
483
484void C4Shape::CreateOwnOriginalCopy(C4Shape &rFrom)
485{
486 // copy vertices from original buffer, including count
487 VtxNum = std::min<int32_t>(a: rFrom.VtxNum, C4D_VertexCpyPos);
488 memcpy(dest: VtxX + C4D_VertexCpyPos, src: rFrom.VtxX, n: VtxNum * sizeof(*VtxX));
489 memcpy(dest: VtxY + C4D_VertexCpyPos, src: rFrom.VtxY, n: VtxNum * sizeof(*VtxY));
490 memcpy(dest: VtxCNAT + C4D_VertexCpyPos, src: rFrom.VtxCNAT, n: VtxNum * sizeof(*VtxCNAT));
491 memcpy(dest: VtxFriction + C4D_VertexCpyPos, src: rFrom.VtxFriction, n: VtxNum * sizeof(*VtxFriction));
492 memcpy(dest: VtxContactCNAT + C4D_VertexCpyPos, src: rFrom.VtxContactCNAT, n: VtxNum * sizeof(*VtxContactCNAT));
493 memcpy(dest: VtxContactMat + C4D_VertexCpyPos, src: rFrom.VtxContactMat, n: VtxNum * sizeof(*VtxContactMat));
494}
495
496void C4Shape::CompileFunc(StdCompiler *pComp, bool fRuntime)
497{
498 // Note: Compiled directly into "Object" and "DefCore"-categories, so beware of name clashes
499 // (see C4Object::CompileFunc and C4DefCore::CompileFunc)
500 pComp->Value(rStruct: mkNamingAdapt(rValue&: Wdt, szName: "Width", rDefault: 0));
501 pComp->Value(rStruct: mkNamingAdapt(rValue&: Hgt, szName: "Height", rDefault: 0));
502 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdaptS(array: &x, size: 2, default_: 0), szName: "Offset"));
503 pComp->Value(rStruct: mkNamingAdapt(rValue&: VtxNum, szName: "Vertices", rDefault: 0));
504 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: VtxX, default_: 0), szName: "VertexX"));
505 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: VtxY, default_: 0), szName: "VertexY"));
506 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: VtxCNAT, default_: 0), szName: "VertexCNAT"));
507 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: VtxFriction, default_: 0), szName: "VertexFriction"));
508 pComp->Value(rStruct: mkNamingAdapt(rValue&: ContactDensity, szName: "ContactDensity", rDefault: C4M_Solid));
509 pComp->Value(rStruct: mkNamingAdapt(rValue&: FireTop, szName: "FireTop", rDefault: 0));
510 if (fRuntime)
511 {
512 pComp->Value(rStruct: mkNamingAdapt(rValue&: iAttachX, szName: "AttachX", rDefault: 0));
513 pComp->Value(rStruct: mkNamingAdapt(rValue&: iAttachY, szName: "AttachY", rDefault: 0));
514 pComp->Value(rStruct: mkNamingAdapt(rValue&: iAttachVtx, szName: "AttachVtx", rDefault: 0));
515 }
516}
517