openclonk/src/object/C4Shape.cpp

575 lines
16 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Basic classes for rectangles and vertex outlines */
#include <C4Include.h>
#include <C4Shape.h>
#include <C4Physics.h>
#include <C4Material.h>
#include <C4Landscape.h>
#include <C4Record.h>
bool C4Shape::AddVertex(int32_t iX, int32_t iY)
{
if (VtxNum>=C4D_MaxVertex) return false;
VtxX[VtxNum]=iX; VtxY[VtxNum]=iY;
VtxNum++;
return true;
}
void C4Shape::Default()
{
ZeroMem(this,sizeof (C4Shape));
AttachMat=MNone;
ContactDensity=C4M_Solid;
}
C4Shape::C4Shape()
{
Default();
}
void C4Shape::Clear()
{
ZeroMem(this, sizeof (C4Shape));
}
void C4Shape::Rotate(C4Real Angle, bool bUpdateVertices)
{
C4RCRotVtx rc;
int32_t i = 0;
if (Config.General.DebugRec)
{
rc.x=x; rc.y=y; rc.wdt=Wdt; rc.hgt=Hgt; rc.r=Angle;
for (; i<4; ++i)
{ rc.VtxX[i]=VtxX[i]; rc.VtxY[i]=VtxY[i]; }
AddDbgRec(RCT_RotVtx1, &rc, sizeof(rc));
}
int32_t cnt,nvtx,nvty,nwdt,nhgt;
C4Real mtx[4];
// Calculate rotation matrix
mtx[0] = Cos(Angle); mtx[1] = -Sin(Angle);
mtx[2] = -mtx[1]; mtx[3] = mtx[0];
if (bUpdateVertices)
{
// Rotate vertices
for (cnt = 0; cnt < VtxNum; cnt++)
{
nvtx = fixtoi(mtx[0] * VtxX[cnt] + mtx[1] * VtxY[cnt]);
nvty = fixtoi(mtx[2] * VtxX[cnt] + mtx[3] * VtxY[cnt]);
VtxX[cnt] = nvtx; VtxY[cnt] = nvty;
}
}
// Enlarge Rect
nvtx = fixtoi(mtx[0] * x + mtx[1] * y);
nvty = fixtoi(mtx[2] * x + mtx[3] * y);
if (mtx[0] > 0)
{
if (mtx[1] > 0)
{
nwdt = fixtoi(mtx[0] * Wdt + mtx[1] * Hgt);
nhgt = fixtoi(mtx[1] * Wdt + mtx[0] * Hgt);
x = nvtx;
y = nvty - fixtoi(mtx[1] * Wdt);
}
else
{
nwdt = fixtoi(mtx[0] * Wdt - mtx[1] * Hgt);
nhgt = fixtoi(- mtx[1] * Wdt + mtx[0] * Hgt);
x = nvtx + fixtoi(mtx[1] * Hgt);
y = nvty;
}
}
else
{
if (mtx[1] > 0)
{
nwdt = fixtoi(- mtx[0] * Wdt + mtx[1] * Hgt);
nhgt = fixtoi(mtx[1] * Wdt - mtx[0] * Hgt);
x = nvtx + fixtoi(mtx[0] * Wdt);
y = nvty - nhgt;
}
else
{
nwdt = fixtoi(- mtx[0] * Wdt - mtx[1] * Hgt);
nhgt = fixtoi(- mtx[1] * Wdt - mtx[0] * Hgt);
x = nvtx - nwdt;
y = nvty + fixtoi(mtx[0] * Hgt);
}
}
Wdt = nwdt;
Hgt = nhgt;
if (Config.General.DebugRec)
{
rc.x=x; rc.y=y; rc.wdt=Wdt; rc.hgt=Hgt;
for (i=0; i<4; ++i)
{ rc.VtxX[i]=VtxX[i]; rc.VtxY[i]=VtxY[i]; }
AddDbgRec(RCT_RotVtx2, &rc, sizeof(rc));
}
}
void C4Shape::Stretch(int32_t iCon, bool bUpdateVertices)
{
int32_t cnt;
x=x*iCon/FullCon;
y=y*iCon/FullCon;
Wdt=Wdt*iCon/FullCon;
Hgt=Hgt*iCon/FullCon;
FireTop=FireTop*iCon/FullCon;
if (bUpdateVertices)
for (cnt=0; cnt<VtxNum; cnt++)
{
VtxX[cnt]=VtxX[cnt]*iCon/FullCon;
VtxY[cnt]=VtxY[cnt]*iCon/FullCon;
}
}
void C4Shape::Jolt(int32_t iCon, bool bUpdateVertices)
{
int32_t cnt;
y=y*iCon/FullCon;
Hgt=Hgt*iCon/FullCon;
FireTop=FireTop*iCon/FullCon;
if (bUpdateVertices)
for (cnt=0; cnt<VtxNum; cnt++)
VtxY[cnt]=VtxY[cnt]*iCon/FullCon;
}
void C4Shape::GetVertexOutline(C4Rect &rRect)
{
int32_t cnt;
rRect.x=rRect.y=rRect.Wdt=rRect.Hgt=0;
for (cnt=0; cnt<VtxNum; cnt++)
{
// Extend left
if (VtxX[cnt]<rRect.x)
{
rRect.Wdt+=rRect.x-VtxX[cnt];
rRect.x=VtxX[cnt];
}
// Extend right
else if (VtxX[cnt]>rRect.x+rRect.Wdt)
{ rRect.Wdt=VtxX[cnt]-rRect.x; }
// Extend up
if (VtxY[cnt]<rRect.y)
{
rRect.Hgt+=rRect.y-VtxY[cnt];
rRect.y=VtxY[cnt];
}
// Extend down
else if (VtxY[cnt]>rRect.y+rRect.Hgt)
{ rRect.Hgt=VtxY[cnt]-rRect.y; }
}
rRect.Hgt+=rRect.y-y;
rRect.y=y;
}
inline bool C4Shape::CheckTouchableMaterial(int32_t x, int32_t y, int32_t vtx_i, int32_t ydir, const C4DensityProvider &rDensityProvider) {
return rDensityProvider.GetDensity(x,y) >= ContactDensity &&
((ydir > 0 && (CNAT_CollideHalfVehicle & VtxCNAT[vtx_i])) || !IsMCHalfVehicle(GBackPix(x,y)));
}
// Adjust given position to one pixel before contact
// at vertices matching CNAT request.
bool C4Shape::Attach(int32_t &cx, int32_t &cy, BYTE cnat_pos)
{
// reset attached material
AttachMat=MNone;
int xcd = 0;
int ycd = 0;
// determine attachment direction
switch (cnat_pos & (~CNAT_Flags))
{
case CNAT_Top: ycd=-1; break;
case CNAT_Bottom: ycd=+1; break;
case CNAT_Left: xcd=-1; break;
case CNAT_Right: xcd=+1; break;
default: return false;
}
int testx = cx;
int testy = cy;
bool increase_distance = true;
bool any_contact = false;
// Find the nearest position that has at least one vertex adjacent to dense material
// and no vertices in dense materials
while (Abs(testx - cx) < AttachRange && Abs(testy - cy) < AttachRange)
{
bool found = false;
for (int i = 0; i < VtxNum; ++i)
{
if (VtxCNAT[i] & cnat_pos)
{
// get new vertex pos
int32_t ax = testx + VtxX[i], ay = testy + VtxY[i];
if (CheckTouchableMaterial(ax, ay, i))
{
found = false;
break;
}
// can attach here?
if (CheckTouchableMaterial(ax + xcd, ay + ycd, i, ycd))
{
found = true;
any_contact = true;
// store attachment material
AttachMat = GBackMat(ax + xcd, ay + ycd);
// store absolute attachment position
iAttachX = ax + xcd; iAttachY = ay + ycd;
iAttachVtx = i;
}
}
}
if (found)
{
cx = testx;
cy = testy;
return true;
}
// Try positions in order of distance from the origin,
// and alternating the direction
testx = cx - (testx - cx);
testy = cy - (testy - cy);
if (increase_distance)
{
testx += xcd;
testy += ycd;
}
increase_distance = !increase_distance;
}
return any_contact;
}
bool C4Shape::LineConnect(int32_t tx, int32_t ty, int32_t cvtx, int32_t ld, int32_t oldx, int32_t oldy)
{
if (VtxNum<2) return false;
// No modification
if ((VtxX[cvtx]==tx) && (VtxY[cvtx]==ty)) return true;
// Check new path
int32_t ix,iy;
if (PathFree(tx,ty,VtxX[cvtx+ld],VtxY[cvtx+ld],&ix,&iy))
{
// Okay, set vertex
VtxX[cvtx]=tx; VtxY[cvtx]=ty;
return true;
}
else
{
// Intersected, find bend vertex
bool found = false;
int32_t cix;
int32_t ciy;
for (int irange = 4; irange <= 12; irange += 4)
for (cix = ix - irange / 2; cix <= ix + irange; cix += irange)
for (ciy = iy - irange / 2; ciy <= iy + irange; ciy += irange)
{
if (PathFree(cix,ciy,tx,ty) && PathFree(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
{
found = true;
goto out;
}
}
out:
if (!found)
{
// try bending directly at path the line took
// allow going through vehicle in this case to allow lines through castles and elevator shafts
cix = oldx;
ciy = oldy;
if (!PathFreeIgnoreVehicle(cix,ciy,tx,ty) || !PathFreeIgnoreVehicle(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
if (!PathFreeIgnoreVehicle(cix,ciy,tx,ty) || !PathFreeIgnoreVehicle(cix,ciy,VtxX[cvtx+ld],VtxY[cvtx+ld]))
return false; // Found no bend vertex
}
// Insert bend vertex
if (ld>0)
{
if (!InsertVertex(cvtx+1,cix,ciy)) return false;
}
else
{
if (!InsertVertex(cvtx,cix,ciy)) return false;
cvtx++;
}
// Okay, set vertex
VtxX[cvtx]=tx; VtxY[cvtx]=ty;
return true;
}
return false;
}
bool C4Shape::InsertVertex(int32_t iPos, int32_t tx, int32_t ty)
{
if (VtxNum+1>C4D_MaxVertex) return false;
// Insert vertex before iPos
for (int32_t cnt=VtxNum; cnt>iPos; cnt--)
{ VtxX[cnt]=VtxX[cnt-1]; VtxY[cnt]=VtxY[cnt-1]; }
VtxX[iPos]=tx; VtxY[iPos]=ty;
VtxNum++;
return true;
}
bool C4Shape::RemoveVertex(int32_t iPos)
{
if (!Inside<int32_t>(iPos,0,VtxNum-1)) return false;
for (int32_t cnt=iPos; cnt+1<VtxNum; cnt++)
{ VtxX[cnt]=VtxX[cnt+1]; VtxY[cnt]=VtxY[cnt+1]; }
VtxNum--;
return true;
}
bool C4Shape::CheckContact(int32_t cx, int32_t cy)
{
// Check all vertices at given object position.
// Return true on any contact.
for (int32_t cvtx=0; cvtx<VtxNum; cvtx++)
if (!(VtxCNAT[cvtx] & CNAT_NoCollision))
if (CheckTouchableMaterial(cx+VtxX[cvtx],cy+VtxY[cvtx], cvtx))
return true;
return false;
}
bool C4Shape::ContactCheck(int32_t cx, int32_t cy, uint32_t *border_hack_contacts, bool collide_halfvehic)
{
// Check all vertices at given object position.
// Set ContactCNAT and ContactCount.
// Set VtxContactCNAT and VtxContactMat.
// Return true on any contact.
ContactCNAT=CNAT_None;
ContactCount=0;
for (int32_t cvtx=0; cvtx<VtxNum; cvtx++)
// Ignore vertex if collision has been flagged out
if (!(VtxCNAT[cvtx] & CNAT_NoCollision))
{
VtxContactCNAT[cvtx]=CNAT_None;
int32_t x = cx+VtxX[cvtx];
int32_t y = cy+VtxY[cvtx];
VtxContactMat[cvtx]=GBackMat(x,y);
if (CheckTouchableMaterial(x, y, cvtx, collide_halfvehic? 1:0))
{
ContactCNAT |= VtxCNAT[cvtx];
VtxContactCNAT[cvtx]|=CNAT_Center;
ContactCount++;
// Vertex center contact, now check top,bottom,left,right
if (CheckTouchableMaterial(x,y-1,cvtx))
VtxContactCNAT[cvtx]|=CNAT_Top;
if (CheckTouchableMaterial(x,y+1,cvtx))
VtxContactCNAT[cvtx]|=CNAT_Bottom;
if (CheckTouchableMaterial(x-1,y,cvtx))
VtxContactCNAT[cvtx]|=CNAT_Left;
if (CheckTouchableMaterial(x+1,y,cvtx))
VtxContactCNAT[cvtx]|=CNAT_Right;
}
if (border_hack_contacts)
{
if (x == 0 && CheckTouchableMaterial(x-1, y, cvtx)) *border_hack_contacts |= CNAT_Left;
else if (x == ::Landscape.Width && CheckTouchableMaterial(x+1, y, cvtx)) *border_hack_contacts |= CNAT_Right;
}
}
return !!ContactCount;
}
bool C4Shape::CheckScaleToWalk(int x, int y)
{
for (int32_t i = 0; i < VtxNum; i++)
{
if (VtxCNAT[i] & CNAT_NoCollision)
continue;
if (VtxCNAT[i] & CNAT_Bottom)
{
// no ground under the feet?
if (CheckTouchableMaterial(x + VtxX[i], y + VtxY[i] + 1, i))
return false;
}
else
{
// can climb with hands?
if (CheckTouchableMaterial(x + VtxX[i] - 1, y + VtxY[i], i))
return false;
if (CheckTouchableMaterial(x + VtxX[i] + 1, y + VtxY[i], i))
return false;
}
}
return true;
}
int32_t C4Shape::GetVertexX(int32_t iVertex)
{
if (!Inside<int32_t>(iVertex,0,VtxNum-1)) return 0;
return VtxX[iVertex];
}
int32_t C4Shape::GetVertexY(int32_t iVertex)
{
if (!Inside<int32_t>(iVertex,0,VtxNum-1)) return 0;
return VtxY[iVertex];
}
void C4Shape::CopyFrom(C4Shape rFrom, bool bCpyVertices, bool fCopyVerticesFromSelf)
{
if (bCpyVertices)
{
// truncate / copy vertex count
VtxNum = (fCopyVerticesFromSelf ? Min<int32_t>(VtxNum, C4D_VertexCpyPos) : rFrom.VtxNum);
// restore vertices from back of own buffer (retaining count)
int32_t iCopyPos = (fCopyVerticesFromSelf ? C4D_VertexCpyPos : 0);
C4Shape &rVtxFrom = (fCopyVerticesFromSelf ? *this : rFrom);
memcpy(VtxX, rVtxFrom.VtxX+iCopyPos, VtxNum*sizeof(*VtxX));
memcpy(VtxY, rVtxFrom.VtxY+iCopyPos, VtxNum*sizeof(*VtxY));
memcpy(VtxCNAT, rVtxFrom.VtxCNAT+iCopyPos, VtxNum*sizeof(*VtxCNAT));
memcpy(VtxFriction, rVtxFrom.VtxFriction+iCopyPos, VtxNum*sizeof(*VtxFriction));
memcpy(VtxContactCNAT, rVtxFrom.VtxContactCNAT+iCopyPos, VtxNum*sizeof(*VtxContactCNAT));
memcpy(VtxContactMat, rVtxFrom.VtxContactMat+iCopyPos, VtxNum*sizeof(*VtxContactMat));
// continue: copies other members
}
*((C4Rect *) this) = rFrom;
AttachMat=rFrom.AttachMat;
ContactCNAT=rFrom.ContactCNAT;
ContactCount=rFrom.ContactCount;
FireTop=rFrom.FireTop;
}
int32_t C4Shape::GetBottomVertex()
{
// return bottom-most vertex
int32_t iMax = -1;
for (int32_t i = 0; i < VtxNum; i++)
if (VtxCNAT[i] & CNAT_Bottom)
if (iMax == -1 || VtxY[i] < VtxY[iMax])
iMax = i;
return iMax;
}
int C4Shape::GetBottom()
{
int b = INT_MIN;
for (int32_t i = 0; i < VtxNum; i++)
if (~VtxCNAT[i] & CNAT_NoCollision)
if (VtxY[i] > b)
b = VtxY[i];
if (b == INT_MIN)
return y + Hgt;
return b;
}
C4DensityProvider DefaultDensityProvider;
int32_t C4DensityProvider::GetDensity(int32_t x, int32_t y) const
{
// default density provider checks the landscape
return GBackDensity(x,y);
}
int32_t C4Shape::GetVertexContact(int32_t iVtx, DWORD dwCheckMask, int32_t tx, int32_t ty, const C4DensityProvider &rDensityProvider)
{
// default check mask
if (!dwCheckMask) dwCheckMask = VtxCNAT[iVtx];
// check vertex positions (vtx num not range-checked!)
tx += VtxX[iVtx]; ty += VtxY[iVtx];
int32_t iContact = 0;
// check all directions for solid mat
if (~VtxCNAT[iVtx] & CNAT_NoCollision)
{
if (dwCheckMask & CNAT_Center) if (CheckTouchableMaterial(tx, ty , iVtx, 0, rDensityProvider)) iContact |= CNAT_Center;
if (dwCheckMask & CNAT_Left) if (CheckTouchableMaterial(tx-1, ty, iVtx, 0, rDensityProvider)) iContact |= CNAT_Left;
if (dwCheckMask & CNAT_Right) if (CheckTouchableMaterial(tx+1, ty, iVtx, 0, rDensityProvider)) iContact |= CNAT_Right;
if (dwCheckMask & CNAT_Top) if (CheckTouchableMaterial(tx, ty-1, iVtx, 0, rDensityProvider)) iContact |= CNAT_Top;
if (dwCheckMask & CNAT_Bottom) if (CheckTouchableMaterial(tx, ty+1, iVtx, 1, rDensityProvider)) iContact |= CNAT_Bottom;
}
// return resulting bitmask
return iContact;
}
void C4Shape::CreateOwnOriginalCopy(C4Shape &rFrom)
{
// copy vertices from original buffer, including count
VtxNum = Min<int32_t>(rFrom.VtxNum, C4D_VertexCpyPos);
memcpy(VtxX+C4D_VertexCpyPos, rFrom.VtxX, VtxNum*sizeof(*VtxX));
memcpy(VtxY+C4D_VertexCpyPos, rFrom.VtxY, VtxNum*sizeof(*VtxY));
memcpy(VtxCNAT+C4D_VertexCpyPos, rFrom.VtxCNAT, VtxNum*sizeof(*VtxCNAT));
memcpy(VtxFriction+C4D_VertexCpyPos, rFrom.VtxFriction, VtxNum*sizeof(*VtxFriction));
memcpy(VtxContactCNAT+C4D_VertexCpyPos, rFrom.VtxContactCNAT, VtxNum*sizeof(*VtxContactCNAT));
memcpy(VtxContactMat+C4D_VertexCpyPos, rFrom.VtxContactMat, VtxNum*sizeof(*VtxContactMat));
}
void C4Shape::CompileFunc(StdCompiler *pComp, const C4Shape *default_shape)
{
const StdBitfieldEntry<int32_t> ContactDirections[] =
{
{ "CNAT_None", CNAT_None },
{ "CNAT_Left", CNAT_Left },
{ "CNAT_Right", CNAT_Right },
{ "CNAT_Top", CNAT_Top },
{ "CNAT_Bottom", CNAT_Bottom },
{ "CNAT_Center", CNAT_Center },
{ "CNAT_MultiAttach", CNAT_MultiAttach },
{ "CNAT_NoCollision", CNAT_NoCollision },
{ "CNAT_CollideHalfVehicle", CNAT_CollideHalfVehicle },
{ NULL, 0 }
};
// a default shape is given in object compilation context only
bool fRuntime = !!default_shape;
C4Shape default_def_shape;
if (!default_shape) default_shape = &default_def_shape;
// Note: Compiled directly into "Object" and "DefCore"-categories, so beware of name clashes
// (see C4Object::CompileFunc and C4Def::CompileFunc)
pComp->Value(mkNamingAdapt( Wdt, "Width", default_shape->Wdt));
pComp->Value(mkNamingAdapt( Hgt, "Height", default_shape->Hgt));
pComp->Value(mkNamingAdapt( mkArrayAdaptDefArr(&x,2,&default_shape->x), "Offset", &default_shape->x));
pComp->Value(mkNamingAdapt( VtxNum, "Vertices", default_shape->VtxNum));
pComp->Value(mkNamingAdapt( mkArrayAdaptDMA(VtxX, default_shape->VtxX), "VertexX", default_shape->VtxX));
pComp->Value(mkNamingAdapt( mkArrayAdaptDMA(VtxY, default_shape->VtxY), "VertexY", default_shape->VtxY));
pComp->Value(mkNamingAdapt( mkArrayAdaptDMAM(VtxCNAT, default_shape->VtxCNAT, [&](decltype(*VtxCNAT) &elem){ return mkBitfieldAdapt<int32_t>(elem, ContactDirections); }), "VertexCNAT", default_shape->VtxCNAT));
pComp->Value(mkNamingAdapt( mkArrayAdaptDMA(VtxFriction, default_shape->VtxFriction), "VertexFriction", default_shape->VtxFriction));
pComp->Value(mkNamingAdapt( ContactDensity, "ContactDensity", default_shape->ContactDensity));
pComp->Value(mkNamingAdapt( FireTop, "FireTop", default_shape->FireTop));
if (fRuntime)
{
pComp->Value(mkNamingAdapt( iAttachX, "AttachX", 0 ));
pComp->Value(mkNamingAdapt( iAttachY, "AttachY", 0 ));
pComp->Value(mkNamingAdapt( iAttachVtx, "AttachVtx", 0 ));
}
}