1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2003, Sven2
6 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18/* Solid areas of objects, put into the landscape */
19
20#include <C4Include.h>
21#include <C4SolidMask.h>
22
23#include <C4Object.h>
24#include <C4Wrappers.h>
25
26void C4SolidMask::Put(bool fCauseInstability, C4TargetRect *pClipRect, bool fRestoreAttachment)
27{
28 // If not put, put mask to background,
29 // storing background pixels in cSolidMask.
30
31 // No mask
32 if (!pSolidMask || !pSolidMaskMatBuff) { iAttachingObjectsCount = 0; return; }
33 // Contained
34 if (pForObject->Contained) { iAttachingObjectsCount = 0; return; }
35 // Mask is put
36 if (fCauseInstability) CheckConsistency();
37
38 bool RegularPut;
39 if (!pClipRect)
40 {
41 // Regular Put: Update MaskPutRect and MaskPutRotation
42 MaskPutRotation = pForObject->r;
43 pClipRect = &MaskPutRect;
44 RegularPut = true;
45 }
46 else
47 {
48 // Reput by C4SolidMask::Remove
49 // Don't change MaskPutRotation or MaskPutRect
50 // Intersect ClipRect with the MaskPutRect
51 if (!pClipRect->ClipBy(rClip&: MaskPutRect)) return;
52 RegularPut = false;
53 }
54 // Lock mask surface
55 int iPitch = pForObject->SolidMask.Wdt;
56 int xcnt, ycnt, iTx, iTy;
57 // Put mask pixels
58 uint8_t byPixel;
59 // not rotated?
60 if (!MaskPutRotation)
61 {
62 // calc put rect
63 if (RegularPut)
64 {
65 int ox, oy;
66 ox = pForObject->x + pForObject->Def->Shape.x + pForObject->SolidMask.tx;
67 oy = pForObject->y + pForObject->Def->Shape.y + pForObject->SolidMask.ty;
68 MaskPutRect.x = ox;
69 if (MaskPutRect.x < 0) { MaskPutRect.tx = -MaskPutRect.x; MaskPutRect.x = 0; }
70 else MaskPutRect.tx = 0;
71 MaskPutRect.y = oy;
72 if (MaskPutRect.y < 0) { MaskPutRect.ty = -MaskPutRect.y; MaskPutRect.y = 0; }
73 else MaskPutRect.ty = 0;
74 MaskPutRect.Wdt = std::min<int32_t>(a: ox + pForObject->SolidMask.Wdt, GBackWdt) - MaskPutRect.x;
75 MaskPutRect.Hgt = std::min<int32_t>(a: oy + pForObject->SolidMask.Hgt, GBackHgt) - MaskPutRect.y;
76 }
77 // fill rect with mask
78 for (ycnt = 0; ycnt < pClipRect->Hgt; ++ycnt)
79 {
80 uint8_t *pPix = pSolidMask + (ycnt + pClipRect->ty) * pForObject->SolidMask.Wdt + pClipRect->tx;
81 for (xcnt = 0; xcnt < pClipRect->Wdt; ++xcnt, ++pPix)
82 {
83 if (*pPix)
84 {
85 // solid mask present here
86 // calc position in landscape
87 iTx = pClipRect->x + xcnt; iTy = pClipRect->y + ycnt;
88 // is background mat to be stored? always do this in the given rect
89 if (!MaskPut)
90 {
91 // get background pixel
92 byPixel = GBackPix(x: iTx, y: iTy);
93 // store it. If MCVehic, also store in initial put, but won't be used in restore
94 // do not overwrite current value in re-put issued by SolidMask-remover
95 if (byPixel != MCVehic || RegularPut)
96 pSolidMaskMatBuff[(ycnt + pClipRect->ty) * MatBuffPitch + xcnt + pClipRect->tx] = byPixel;
97 }
98 // and set mask
99 _SBackPix(x: iTx, y: iTy, npix: MCVehic);
100 }
101 else
102 // no SolidMask: mark buffer as unused here
103 if (!MaskPut)
104 pSolidMaskMatBuff[(ycnt + pClipRect->ty) * MatBuffPitch + xcnt + pClipRect->tx] = MCVehic;
105 }
106 }
107 }
108 else
109 {
110 // calc matrix for given rotation
111 C4Fixed Ma1 = Cos(fAngle: itofix(x: -MaskPutRotation)), Ma2 = -Sin(fAngle: itofix(x: -MaskPutRotation)),
112 Mb1 = Sin(fAngle: itofix(x: -MaskPutRotation)), Mb2 = Cos(fAngle: itofix(x: -MaskPutRotation));
113 // get upper-left corner of landscape copy rect
114 int centerx = pForObject->Def->Shape.x + pForObject->SolidMask.tx + pForObject->SolidMask.Wdt / 2;
115 int centery = pForObject->Def->Shape.y + pForObject->SolidMask.ty + pForObject->SolidMask.Hgt / 2;
116 int xstart = pForObject->x + fixtoi(x: Ma1 * itofix(x: centerx) - Ma2 * itofix(x: centery)) - MatBuffPitch / 2;
117 int ystart = pForObject->y + fixtoi(x: -Mb1 * itofix(x: centerx) + Mb2 * itofix(x: centery)) - MatBuffPitch / 2;
118 // store put rect
119 if (RegularPut)
120 {
121 MaskPutRect.x = xstart;
122 if (MaskPutRect.x < 0) { MaskPutRect.tx = -MaskPutRect.x; MaskPutRect.Wdt = MaskPutRect.x; MaskPutRect.x = 0; }
123 else { MaskPutRect.tx = 0; MaskPutRect.Wdt = 0; }
124 MaskPutRect.y = ystart;
125 if (MaskPutRect.y < 0) { MaskPutRect.ty = -MaskPutRect.y; MaskPutRect.Hgt = MaskPutRect.y; MaskPutRect.y = 0; }
126 else { MaskPutRect.ty = 0; MaskPutRect.Hgt = 0; }
127 MaskPutRect.Wdt = std::min<int32_t>(a: xstart + MatBuffPitch, GBackWdt) - MaskPutRect.x;
128 MaskPutRect.Hgt = std::min<int32_t>(a: ystart + MatBuffPitch, GBackHgt) - MaskPutRect.y;
129 }
130 // go through clipping rect
131 const C4Fixed y0 = itofix(x: pClipRect->ty - MatBuffPitch / 2);
132 const C4Fixed x0 = itofix(x: pClipRect->tx - MatBuffPitch / 2);
133 iTy = pClipRect->y;
134 int w = pForObject->SolidMask.Wdt;
135 int h = pForObject->SolidMask.Hgt;
136 C4Fixed ya = y0 * Ma2;
137 C4Fixed yb = y0 * Mb2;
138 for (ycnt = 0; ycnt < pClipRect->Hgt; ycnt++)
139 {
140 iTx = pClipRect->x;
141 int i = (ycnt + pClipRect->ty) * MatBuffPitch + pClipRect->tx;
142 C4Fixed xa = x0 * Ma1;
143 C4Fixed xb = x0 * Mb1;
144 for (xcnt = 0; xcnt < pClipRect->Wdt; xcnt++)
145 {
146 // calc position in solidmask buffer
147 int iMx = fixtoi(x: xa + ya) + w / 2;
148 int iMy = fixtoi(x: xb + yb) + h / 2;
149 // in bounds? and solidmask?
150 if (iMx >= 0 && iMy >= 0 && iMx < w && iMy < h && pSolidMask[iMy * iPitch + iMx])
151 {
152 // is background mat to be stored?
153 if (!MaskPut)
154 {
155 // get background pixel
156 byPixel = _GBackPix(x: iTx, y: iTy);
157 // store it. If MCVehic, also store in initial put, but won't be used in restore
158 // do not overwrite current value in re-put issued by SolidMask-remover
159 if (byPixel != MCVehic || RegularPut)
160 pSolidMaskMatBuff[i + xcnt] = byPixel;
161 }
162 // set mask pix
163 _SBackPix(x: iTx, y: iTy, npix: MCVehic);
164 }
165 else if (!MaskPut)
166 // mark pix as unused in buf
167 pSolidMaskMatBuff[i + xcnt] = MCVehic;
168 xa += Ma1; xb += Mb1;
169 ++iTx;
170 }
171 ya += Ma2; yb += Mb2;
172 ++iTy;
173 }
174 }
175 // Store mask put status
176 MaskPut = true;
177 // restore attached object positions if moved
178 if (fRestoreAttachment && iAttachingObjectsCount)
179 {
180 int32_t dx = pForObject->x - MaskRemovalX;
181 int32_t dy = pForObject->y - MaskRemovalY;
182 if (dx | dy)
183 for (int i = 0; i < iAttachingObjectsCount; ++i)
184 {
185 C4Object *pObj = ppAttachingObjects[i];
186 if (pObj->IsMoveableBySolidMask())
187 if (!pObj->Shape.ContactCheck(cx: pObj->x + dx, cy: pObj->y + dy))
188 if (pObj->iLastAttachMovementFrame != Game.FrameCounter)
189 {
190 pObj->iLastAttachMovementFrame = Game.FrameCounter;
191 pObj->MovePosition(dx, dy);
192 }
193 }
194 iAttachingObjectsCount = 0;
195 }
196
197 if (fCauseInstability) CheckConsistency();
198}
199
200int32_t C4SolidMask::DensityProvider::GetDensity(int32_t x, int32_t y) const
201{
202 // outside SolidMask: free
203 x -= rSolidMaskData.MaskPutRect.x;
204 y -= rSolidMaskData.MaskPutRect.y;
205 if (!Inside<int32_t>(ival: x, lbound: 0, rbound: rSolidMaskData.MaskPutRect.Wdt - 1)
206 || !Inside<int32_t>(ival: y, lbound: 0, rbound: rSolidMaskData.MaskPutRect.Hgt - 1))
207 return 0;
208 // check put mask. Easy for unrotated
209 uint8_t *pPix;
210 if (!rSolidMaskData.MaskPutRotation)
211 {
212 pPix = rSolidMaskData.pSolidMask + (y + rSolidMaskData.MaskPutRect.ty) * rSolidMaskData.pForObject->SolidMask.Wdt + rSolidMaskData.MaskPutRect.tx + x;
213 if (*pPix == 0xff)
214 return C4M_Solid;
215 else
216 return 0;
217 }
218 else
219 {
220 // Using put-buffer for rotated masks
221 // for SolidMask-pixels not put because there was another SolidMask already, this will not return solid
222 pPix = rSolidMaskData.pSolidMaskMatBuff + (y + rSolidMaskData.MaskPutRect.ty) * rSolidMaskData.MatBuffPitch + rSolidMaskData.MaskPutRect.tx + x;
223 if (*pPix == MCVehic)
224 return 0;
225 else
226 return C4M_Solid;
227 }
228}
229
230void C4SolidMask::Remove(bool fCauseInstability, bool fBackupAttachment)
231{
232 // If put, restore background pixels from buffer
233
234 // Not put
235 if (!MaskPut || !pSolidMask || !pSolidMaskMatBuff) return;
236
237 CheckConsistency();
238
239 // reput background pixels
240 for (int ycnt = 0; ycnt < MaskPutRect.Hgt; ++ycnt)
241 {
242 uint8_t *pPix = pSolidMaskMatBuff + (ycnt + MaskPutRect.ty) * MatBuffPitch + MaskPutRect.tx;
243 for (int xcnt = 0; xcnt < MaskPutRect.Wdt; ++xcnt, ++pPix)
244 // only if mask was used here
245 if (*pPix != MCVehic)
246 {
247 // calc position in landscape
248 int iTx = MaskPutRect.x + xcnt; int iTy = MaskPutRect.y + ycnt;
249 // restore pixel here
250 // The pPix-check ensures that only pixels that hads been overwritten by this SolidMask are restored
251 // Non-SolidMask-pixels should not happen here, because all relevant landscape change routines should
252 // temp remove SolidMasks before
253 assert(_GBackPix(iTx, iTy) == MCVehic);
254 _SBackPixIfMask(x: iTx, y: iTy, npix: *pPix, nMask: MCVehic);
255 // Instability
256 if (fCauseInstability)
257 Game.Landscape.CheckInstabilityRange(tx: iTx, ty: iTy);
258 }
259 }
260 // Mask not put flag
261 MaskPut = false;
262 // update surrounding masks in that range
263 C4TargetRect ClipRect;
264 for (C4SolidMask *pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
265 if (pSolid->MaskPut) if (pSolid->MaskPutRect.Overlap(rTarget&: MaskPutRect))
266 {
267 // set clipping rect for all calls, since they may modify it
268 ClipRect.Set(iX: MaskPutRect.x, iY: MaskPutRect.y, iWdt: MaskPutRect.Wdt, iHgt: MaskPutRect.Hgt, iTX: 0, iTY: 0);
269 // doubled solidmask-pixels have just been removed in the clipped area!
270 pSolid->MaskPut = false;
271 // re-put the solidmask
272 pSolid->Put(fCauseInstability: false, pClipRect: &ClipRect, fRestoreAttachment: false);
273 }
274
275 // backup attachment if desired: Backup old pos and all objects that attach to or lie on the SolidMask
276 if (fBackupAttachment)
277 {
278 MaskRemovalX = pForObject->x;
279 MaskRemovalY = pForObject->y;
280 iAttachingObjectsCount = 0;
281 C4LArea SolidArea(&Game.Objects.Sectors, MaskPutRect.x - 1, MaskPutRect.y - 1, MaskPutRect.Wdt + 2, MaskPutRect.Hgt + 2);
282 C4LSector *pSct; C4Object *pObj;
283 for (C4ObjectList *pLst = SolidArea.FirstObjectShapes(ppSct: &pSct); pLst; pLst = SolidArea.NextObjectShapes(pPrev: pLst, ppSct: &pSct))
284 for (C4ObjectLink *clnk = pLst->First; clnk; clnk = clnk->Next)
285 if ((pObj = clnk->Obj) && pObj != pForObject && pObj->IsMoveableBySolidMask() && !pObj->Shape.CheckContact(cx: pObj->x, cy: pObj->y))
286 {
287 // check for any contact to own SolidMask - attach-directions, bottom - "stuck" (CNAT_Center) is ignored, because that causes problems with things being stuck in basements :(
288 int iVtx = 0;
289 for (; iVtx < pObj->Shape.VtxNum; ++iVtx)
290 if (pObj->Shape.GetVertexContact(iVtx, dwCheckMask: pObj->Action.t_attach | CNAT_Bottom, tx: pObj->x, ty: pObj->y, rDensityProvider: DensityProvider(pForObject, *this)))
291 if (pObj->Shape.GetVertexContact(iVtx, dwCheckMask: pObj->Action.t_attach | CNAT_Bottom, tx: pObj->x, ty: pObj->y, rDensityProvider: DensityProvider(pForObject, *this)))
292 break;
293 if (iVtx == pObj->Shape.VtxNum) continue; // no contact
294 // contact: Add object to list
295 if (iAttachingObjectsCapacity == iAttachingObjectsCount)
296 {
297 iAttachingObjectsCapacity += 4;
298 C4Object **ppNewAttachingObjects = new C4Object *[iAttachingObjectsCapacity];
299 if (iAttachingObjectsCount) memcpy(dest: ppNewAttachingObjects, src: ppAttachingObjects, n: sizeof(C4Object *) * iAttachingObjectsCount);
300 delete[] ppAttachingObjects;
301 ppAttachingObjects = ppNewAttachingObjects;
302 }
303 ppAttachingObjects[iAttachingObjectsCount++] = pObj;
304 }
305 }
306
307 CheckConsistency();
308}
309
310void C4SolidMask::Clear()
311{
312 // free mask+mat-buffer
313 delete[] pSolidMask; pSolidMask = nullptr;
314 delete[] pSolidMaskMatBuff; pSolidMaskMatBuff = nullptr;
315 // safety: mask cannot be removed now
316 MaskPut = false;
317 // clear attaching objects
318 delete[] ppAttachingObjects; ppAttachingObjects = nullptr;
319 iAttachingObjectsCount = iAttachingObjectsCapacity = 0;
320}
321
322void C4SolidMask::RemoveTemporary(C4Rect where)
323{
324 if (!MaskPut || !pSolidMask || !pSolidMaskMatBuff) return;
325 where.Intersect(r2: MaskPutRect);
326 // reput background pixels
327 for (int y = where.y; y < where.y + where.Hgt; ++y)
328 {
329 for (int x = where.x; x < where.x + where.Wdt; ++x)
330 {
331 uint8_t *pPix = pSolidMaskMatBuff + (y - MaskPutRect.y + MaskPutRect.ty) * MatBuffPitch + x - MaskPutRect.x + MaskPutRect.tx;
332 // only if mask was used here
333 if (*pPix != MCVehic)
334 {
335 // restore
336 assert(GBackPix(x, y) == MCVehic);
337 _SBackPix(x, y, npix: *pPix);
338 }
339 }
340 }
341}
342
343void C4SolidMask::PutTemporary(C4Rect where)
344{
345 if (!MaskPut || !pSolidMask || !pSolidMaskMatBuff) return;
346 where.Intersect(r2: MaskPutRect);
347 // reput vehicle pixels
348 for (int y = where.y; y < where.y + where.Hgt; ++y)
349 {
350 for (int x = where.x; x < where.x + where.Wdt; ++x)
351 {
352 uint8_t *pPix = pSolidMaskMatBuff + (y - MaskPutRect.y + MaskPutRect.ty) * MatBuffPitch + x - MaskPutRect.x + MaskPutRect.tx;
353 // only if mask was used here
354 if (*pPix != MCVehic)
355 {
356 // put
357 assert(GBackPix(x, y) == *pPix);
358 _SBackPix(x, y, npix: MCVehic);
359 }
360 }
361 }
362}
363
364void C4SolidMask::Repair(C4Rect where)
365{
366 if (!MaskPut || !pSolidMask || !pSolidMaskMatBuff) return;
367 where.Intersect(r2: MaskPutRect);
368 // reput vehicle pixels
369 for (int y = where.y; y < where.y + where.Hgt; ++y)
370 {
371 for (int x = where.x; x < where.x + where.Wdt; ++x)
372 {
373 uint8_t *pPix = pSolidMaskMatBuff + (y - MaskPutRect.y + MaskPutRect.ty) * MatBuffPitch + x - MaskPutRect.x + MaskPutRect.tx;
374 // only if mask was used here
375 if (*pPix != MCVehic)
376 {
377 // record changed landscape in MatBuff
378 *pPix = GBackPix(x, y);
379 // put
380 _SBackPix(x, y, npix: MCVehic);
381 }
382 }
383 }
384}
385
386C4SolidMask::C4SolidMask(C4Object *pForObject) : pForObject(pForObject)
387{
388 // zero fields
389 MaskPut = false;
390 MaskPutRotation = 0;
391 MaskRemovalX = MaskRemovalY = 0;
392 ppAttachingObjects = nullptr;
393 iAttachingObjectsCount = iAttachingObjectsCapacity = 0;
394 // Update linked list
395 Next = nullptr;
396 Prev = Last;
397 Last = this;
398 if (Prev) Prev->Next = this;
399 else First = this;
400 // copy solid mask from bitmap
401 int iNeededBufSize = pForObject->SolidMask.Wdt * pForObject->SolidMask.Hgt;
402 pSolidMask = new uint8_t[iNeededBufSize];
403 C4Surface *sfcBitmap = pForObject->GetGraphics()->GetBitmap();
404 if (!sfcBitmap->Lock()) return;
405
406 int xcnt, ycnt;
407 for (ycnt = 0; ycnt < pForObject->SolidMask.Hgt; ycnt++)
408 for (xcnt = 0; xcnt < pForObject->SolidMask.Wdt; xcnt++)
409 {
410 // Solid mask target x/y is relative to def bitmap top-left, not object center.
411 pSolidMask[xcnt + ycnt * pForObject->SolidMask.Wdt] = sfcBitmap->IsPixTransparent(iX: pForObject->SolidMask.x + xcnt, iY: pForObject->SolidMask.y + ycnt) ? 0x00 : 0xff;
412 }
413 // create mat buff to store the material replaced by the solid mask
414 // the upper left corner is here the [objpos]+rot([shapexy]+[targetxy]+[realWH]/2)-maxWH/2
415 MatBuffPitch = static_cast<int>(sqrt(x: double(pForObject->SolidMask.Wdt * pForObject->SolidMask.Wdt + pForObject->SolidMask.Hgt * pForObject->SolidMask.Hgt))) + 1;
416 pSolidMaskMatBuff = new uint8_t[MatBuffPitch * MatBuffPitch]{};
417 sfcBitmap->Unlock(noUpload: true);
418}
419
420C4SolidMask::~C4SolidMask()
421{
422 // Update linked list
423 if (Next) Next->Prev = Prev;
424 if (Prev) Prev->Next = Next;
425 if (First == this) First = Next;
426 if (Last == this) Last = Prev;
427 // clear fields
428 Clear();
429}
430
431C4SolidMask *C4SolidMask::First = nullptr;
432C4SolidMask *C4SolidMask::Last = nullptr;
433
434#ifdef SOLIDMASK_DEBUG
435
436bool C4SolidMask::CheckConsistency()
437{
438 C4Rect SolidMaskRect(0, 0, GBackWdt, GBackHgt);
439 C4SolidMask *pSolid;
440 for (pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
441 {
442 pSolid->RemoveTemporary(SolidMaskRect);
443 }
444 assert(!Game.Landscape.MatCount[MVehic]);
445 // Restore Solidmasks
446 for (pSolid = C4SolidMask::First; pSolid; pSolid = pSolid->Next)
447 {
448 pSolid->PutTemporary(SolidMaskRect);
449 }
450 return true;
451}
452
453#endif
454