Prevent AABB/frustum intersections from blowing up on empty input bounds. Fixes #1027.
This was SVN commit r11441.
This commit is contained in:
parent
7ff99cc218
commit
305723f9a7
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
/* Copyright (C) 2012 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -217,6 +217,12 @@ void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOrie
|
||||
// Intersect with the given frustum in a conservative manner
|
||||
void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum)
|
||||
{
|
||||
// if this bound is empty, then the result must be empty (we should not attempt to intersect with
|
||||
// a brush, may cause crashes due to the numeric representation of empty bounds -- see
|
||||
// http://trac.wildfiregames.com/ticket/1027)
|
||||
if (IsEmpty())
|
||||
return;
|
||||
|
||||
CBrush brush(*this);
|
||||
CBrush buf;
|
||||
|
||||
|
@ -126,6 +126,7 @@ public:
|
||||
*
|
||||
* @note While not in the spirit of this function's purpose, a no-op would be a correct
|
||||
* implementation of this function.
|
||||
* @note If this bound is empty, the result is the empty bound.
|
||||
*
|
||||
* @param frustum the frustum to intersect with
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2012 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -38,11 +38,13 @@ CBrush::CBrush(const CBoundingBoxAligned& bounds)
|
||||
|
||||
for(size_t i = 0; i < 8; ++i)
|
||||
{
|
||||
m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0];
|
||||
m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1];
|
||||
m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2];
|
||||
m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0]; // X
|
||||
m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1]; // Y
|
||||
m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2]; // Z
|
||||
}
|
||||
|
||||
// construct cube face indices, 5 vertex indices per face (start vertex included twice)
|
||||
|
||||
m_Faces.resize(30);
|
||||
|
||||
m_Faces[0] = 0; m_Faces[1] = 1; m_Faces[2] = 3; m_Faces[3] = 2; m_Faces[4] = 0; // Z = min
|
||||
@ -69,208 +71,278 @@ void CBrush::Bounds(CBoundingBoxAligned& result) const
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Cut the brush according to a given plane
|
||||
struct SliceVertexInfo {
|
||||
float d; // distance
|
||||
size_t res; // index in result brush (or no_vertex if cut away)
|
||||
|
||||
/// Holds information about what happens to a single vertex in a brush during a slicing operation.
|
||||
struct SliceOpVertexInfo
|
||||
{
|
||||
float planeDist; ///< Signed distance from this vertex to the slicing plane.
|
||||
size_t resIdx; ///< Index of this vertex in the resulting brush (or NO_VERTEX if cut away)
|
||||
};
|
||||
|
||||
struct NewVertexInfo {
|
||||
size_t v1, v2; // adjacent vertices in original brush
|
||||
size_t res; // index in result brush
|
||||
/// Holds information about a newly introduced vertex on an edge in a brush as the result of a slicing operation.
|
||||
struct SliceOpNewVertexInfo
|
||||
{
|
||||
/// Indices of adjacent edge vertices in original brush
|
||||
size_t edgeIdx1, edgeIdx2;
|
||||
/// Index of newly introduced vertex in resulting brush
|
||||
size_t resIdx;
|
||||
|
||||
size_t neighb1, neighb2; // index into newv
|
||||
/**
|
||||
* Index into SliceOpInfo.nvInfo; hold the indices of this new vertex's direct neighbours in the slicing plane face,
|
||||
* with no consistent winding direction around the face for either field (e.g., the neighb1 of X can point back to
|
||||
* X with either its neighb1 or neighb2).
|
||||
*/
|
||||
size_t neighbIdx1, neighbIdx2;
|
||||
};
|
||||
|
||||
struct SliceInfo {
|
||||
std::vector<SliceVertexInfo> v;
|
||||
std::vector<NewVertexInfo> newv;
|
||||
size_t thisFaceNewVertex; // index into newv
|
||||
const CBrush* original;
|
||||
/// Holds support information during a CBrush/CPlane slicing operation.
|
||||
struct SliceOpInfo
|
||||
{
|
||||
CBrush* result;
|
||||
const CBrush* original;
|
||||
|
||||
/**
|
||||
* Holds information about what happens to each vertex in the original brush after the slice operation.
|
||||
* Same size as m_Vertices of the brush getting sliced.
|
||||
*/
|
||||
std::vector<SliceOpVertexInfo> ovInfo;
|
||||
|
||||
/// Holds information about newly inserted vertices during a slice operation.
|
||||
std::vector<SliceOpNewVertexInfo> nvInfo;
|
||||
|
||||
/**
|
||||
* Indices into nvInfo; during the execution of the slicing algorithm, holds the previously inserted new vertex on
|
||||
* one of the edges of the face that's currently being evaluated for slice points, or NO_VERTEX if no such vertex
|
||||
* exists.
|
||||
*/
|
||||
size_t thisFaceNewVertexIdx;
|
||||
};
|
||||
|
||||
struct CBrush::Helper
|
||||
{
|
||||
static size_t SliceNewVertex(SliceInfo& si, size_t v1, size_t v2);
|
||||
/**
|
||||
* Creates a new vertex between the given two vertices (indexed into the original brush).
|
||||
* Returns the index of the new vertex in the resulting brush.
|
||||
*/
|
||||
static size_t SliceNewVertex(SliceOpInfo& sliceInfo, size_t v1, size_t v2);
|
||||
};
|
||||
|
||||
// create a new vertex between the given two vertices (index into original brush)
|
||||
// returns the index of the new vertex in the resulting brush
|
||||
size_t CBrush::Helper::SliceNewVertex(SliceInfo& si, size_t v1, size_t v2)
|
||||
size_t CBrush::Helper::SliceNewVertex(SliceOpInfo& sliceOp, size_t edgeIdx1, size_t edgeIdx2)
|
||||
{
|
||||
// check if a new vertex has already been inserted on this edge
|
||||
size_t idx;
|
||||
|
||||
for(idx = 0; idx < si.newv.size(); ++idx)
|
||||
for(idx = 0; idx < sliceOp.nvInfo.size(); ++idx)
|
||||
{
|
||||
if ((si.newv[idx].v1 == v1 && si.newv[idx].v2 == v2) ||
|
||||
(si.newv[idx].v1 == v2 && si.newv[idx].v2 == v1))
|
||||
if ((sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx1 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx2) ||
|
||||
(sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx2 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx1))
|
||||
break;
|
||||
}
|
||||
|
||||
if (idx >= si.newv.size())
|
||||
if (idx >= sliceOp.nvInfo.size())
|
||||
{
|
||||
NewVertexInfo nvi;
|
||||
CVector3D newpos;
|
||||
float inv = 1.0 / (si.v[v1].d - si.v[v2].d);
|
||||
// no previously inserted new vertex found on this edge; insert a new one
|
||||
SliceOpNewVertexInfo nvi;
|
||||
CVector3D newPos;
|
||||
|
||||
newpos = si.original->m_Vertices[v2]*(si.v[v1].d*inv) +
|
||||
si.original->m_Vertices[v1]*(-si.v[v2].d*inv);
|
||||
// interpolate between the two vertices based on their distance from the plane
|
||||
float inv = 1.0 / (sliceOp.ovInfo[edgeIdx1].planeDist - sliceOp.ovInfo[edgeIdx2].planeDist);
|
||||
newPos = sliceOp.original->m_Vertices[edgeIdx2] * ( sliceOp.ovInfo[edgeIdx1].planeDist * inv) +
|
||||
sliceOp.original->m_Vertices[edgeIdx1] * (-sliceOp.ovInfo[edgeIdx2].planeDist * inv);
|
||||
|
||||
nvi.v1 = v1;
|
||||
nvi.v2 = v2;
|
||||
nvi.res = si.result->m_Vertices.size();
|
||||
nvi.neighb1 = no_vertex;
|
||||
nvi.neighb2 = no_vertex;
|
||||
si.result->m_Vertices.push_back(newpos);
|
||||
si.newv.push_back(nvi);
|
||||
nvi.edgeIdx1 = edgeIdx1;
|
||||
nvi.edgeIdx2 = edgeIdx2;
|
||||
nvi.resIdx = sliceOp.result->m_Vertices.size();
|
||||
nvi.neighbIdx1 = NO_VERTEX;
|
||||
nvi.neighbIdx2 = NO_VERTEX;
|
||||
|
||||
sliceOp.result->m_Vertices.push_back(newPos);
|
||||
sliceOp.nvInfo.push_back(nvi);
|
||||
}
|
||||
|
||||
if (si.thisFaceNewVertex != no_vertex)
|
||||
// at this point, 'idx' is the index into nvInfo of the vertex inserted onto the edge
|
||||
|
||||
if (sliceOp.thisFaceNewVertexIdx != NO_VERTEX)
|
||||
{
|
||||
if (si.newv[si.thisFaceNewVertex].neighb1 == no_vertex)
|
||||
si.newv[si.thisFaceNewVertex].neighb1 = idx;
|
||||
else
|
||||
si.newv[si.thisFaceNewVertex].neighb2 = idx;
|
||||
// a vertex has been previously inserted onto another edge of this face; link them together as neighbours
|
||||
// (using whichever one of the neighbIdx1 or -2 links is still available)
|
||||
|
||||
if (si.newv[idx].neighb1 == no_vertex)
|
||||
si.newv[idx].neighb1 = si.thisFaceNewVertex;
|
||||
if (sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 == NO_VERTEX)
|
||||
sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 = idx;
|
||||
else
|
||||
si.newv[idx].neighb2 = si.thisFaceNewVertex;
|
||||
sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx2 = idx;
|
||||
|
||||
si.thisFaceNewVertex = no_vertex;
|
||||
if (sliceOp.nvInfo[idx].neighbIdx1 == NO_VERTEX)
|
||||
sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.thisFaceNewVertexIdx;
|
||||
else
|
||||
sliceOp.nvInfo[idx].neighbIdx2 = sliceOp.thisFaceNewVertexIdx;
|
||||
|
||||
// a plane should slice a face only in two locations, so reset for the next face
|
||||
sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
si.thisFaceNewVertex = idx;
|
||||
// store the index of the inserted vertex on this edge, so that we can retrieve it when the plane slices
|
||||
// this face again in another edge
|
||||
sliceOp.thisFaceNewVertexIdx = idx;
|
||||
}
|
||||
|
||||
return si.newv[idx].res;
|
||||
return sliceOp.nvInfo[idx].resIdx;
|
||||
}
|
||||
|
||||
void CBrush::Slice(const CPlane& plane, CBrush& result) const
|
||||
{
|
||||
ENSURE(&result != this);
|
||||
|
||||
SliceInfo si;
|
||||
SliceOpInfo sliceOp;
|
||||
|
||||
si.original = this;
|
||||
si.result = &result;
|
||||
si.thisFaceNewVertex = no_vertex;
|
||||
si.newv.reserve(m_Vertices.size() / 2);
|
||||
sliceOp.original = this;
|
||||
sliceOp.result = &result;
|
||||
sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
|
||||
sliceOp.ovInfo.resize(m_Vertices.size());
|
||||
sliceOp.nvInfo.reserve(m_Vertices.size() / 2);
|
||||
|
||||
result.m_Vertices.resize(0); // clear any left-overs
|
||||
result.m_Faces.resize(0);
|
||||
result.m_Vertices.reserve(m_Vertices.size() + 2);
|
||||
result.m_Faces.reserve(m_Faces.size() + 5);
|
||||
|
||||
// Classify and copy vertices
|
||||
si.v.resize(m_Vertices.size());
|
||||
|
||||
// Copy vertices that weren't sliced away by the plane to the resulting brush.
|
||||
for(size_t i = 0; i < m_Vertices.size(); ++i)
|
||||
{
|
||||
si.v[i].d = plane.DistanceToPlane(m_Vertices[i]);
|
||||
if (si.v[i].d >= 0.0)
|
||||
const CVector3D& vtx = m_Vertices[i]; // current vertex
|
||||
SliceOpVertexInfo& vtxInfo = sliceOp.ovInfo[i]; // slicing operation info about current vertex
|
||||
|
||||
vtxInfo.planeDist = plane.DistanceToPlane(vtx);
|
||||
if (vtxInfo.planeDist >= 0.0)
|
||||
{
|
||||
si.v[i].res = result.m_Vertices.size();
|
||||
result.m_Vertices.push_back(m_Vertices[i]);
|
||||
// positive side of the plane; not sliced away
|
||||
vtxInfo.resIdx = result.m_Vertices.size();
|
||||
result.m_Vertices.push_back(vtx);
|
||||
}
|
||||
else
|
||||
{
|
||||
si.v[i].res = no_vertex;
|
||||
// other side of the plane; sliced away
|
||||
vtxInfo.resIdx = NO_VERTEX;
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer faces
|
||||
size_t firstInFace = no_vertex; // in original brush
|
||||
size_t startInResultFaceArray = ~0u;
|
||||
// Transfer faces. (Recall how faces are specified; see CBrush::m_Faces). The idea is to examine each face separately,
|
||||
// and see where its edges cross the slicing plane (meaning that exactly one of the vertices of that edge was cut away).
|
||||
// On those edges, new vertices are introduced where the edge intersects the plane, and the resulting brush's m_Faces
|
||||
// array is updated to refer to the newly inserted vertices instead of the original one that got cut away.
|
||||
|
||||
size_t currentFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the original brush
|
||||
size_t resultFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the resulting brush
|
||||
|
||||
for(size_t i = 0; i < m_Faces.size(); ++i)
|
||||
{
|
||||
if (firstInFace == no_vertex)
|
||||
if (currentFaceStartIdx == NO_VERTEX)
|
||||
{
|
||||
ENSURE(si.thisFaceNewVertex == no_vertex);
|
||||
// starting a new face
|
||||
ENSURE(sliceOp.thisFaceNewVertexIdx == NO_VERTEX);
|
||||
|
||||
firstInFace = m_Faces[i];
|
||||
startInResultFaceArray = result.m_Faces.size();
|
||||
currentFaceStartIdx = m_Faces[i];
|
||||
resultFaceStartIdx = result.m_Faces.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t prev = m_Faces[i-1];
|
||||
size_t cur = m_Faces[i];
|
||||
size_t prevIdx = m_Faces[i-1]; // index of previous vertex in this face list
|
||||
size_t curIdx = m_Faces[i]; // index of current vertex in this face list
|
||||
|
||||
if (si.v[prev].res == no_vertex)
|
||||
if (sliceOp.ovInfo[prevIdx].resIdx == NO_VERTEX)
|
||||
{
|
||||
if (si.v[cur].res != no_vertex)
|
||||
// previous face vertex got sliced away by the plane; see if the edge (prev,current) crosses the slicing plane
|
||||
if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
|
||||
{
|
||||
// re-entering the front side of the plane
|
||||
result.m_Faces.push_back(Helper::SliceNewVertex(si, prev, cur));
|
||||
result.m_Faces.push_back(si.v[cur].res);
|
||||
// re-entering the front side of the plane; insert vertex on intersection of plane and (prev,current) edge
|
||||
result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
|
||||
result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (si.v[cur].res != no_vertex)
|
||||
// previous face vertex didn't get sliced away; see if the edge (prev,current) crosses the slicing plane
|
||||
if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
|
||||
{
|
||||
// perfectly normal edge
|
||||
result.m_Faces.push_back(si.v[cur].res);
|
||||
// perfectly normal edge; doesn't cross the plane
|
||||
result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// leaving the front side of the plane
|
||||
result.m_Faces.push_back(Helper::SliceNewVertex(si, prev, cur));
|
||||
// leaving the front side of the plane; insert vertex on intersection of plane and edge (prev, current)
|
||||
result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
|
||||
}
|
||||
}
|
||||
|
||||
if (cur == firstInFace)
|
||||
// if we're back at the first vertex of the current face, then we've completed the face
|
||||
if (curIdx == currentFaceStartIdx)
|
||||
{
|
||||
if (result.m_Faces.size() > startInResultFaceArray)
|
||||
result.m_Faces.push_back(result.m_Faces[startInResultFaceArray]);
|
||||
firstInFace = no_vertex; // start a new face
|
||||
// close the index loop
|
||||
if (result.m_Faces.size() > resultFaceStartIdx)
|
||||
result.m_Faces.push_back(result.m_Faces[resultFaceStartIdx]);
|
||||
|
||||
currentFaceStartIdx = NO_VERTEX; // start a new face
|
||||
}
|
||||
}
|
||||
|
||||
ENSURE(firstInFace == no_vertex);
|
||||
ENSURE(currentFaceStartIdx == NO_VERTEX);
|
||||
|
||||
// Create the face that lies in the slicing plane
|
||||
if (si.newv.size())
|
||||
// Create the face that lies in the slicing plane. Remember, all the intersections of the slicing plane with face
|
||||
// edges of the brush have been stored in sliceOp.nvInfo by the SliceNewVertex function, and refer to their direct
|
||||
// neighbours in the slicing plane face using the neighbIdx1 and neighbIdx2 fields (in no consistent winding order).
|
||||
|
||||
if (sliceOp.nvInfo.size())
|
||||
{
|
||||
size_t prev = 0;
|
||||
// push the starting vertex
|
||||
result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
|
||||
|
||||
// At this point, there is no consistent winding order in the neighbX fields, so at each vertex we need to figure
|
||||
// out whether neighb1 or neighb2 points 'onwards' along the face, according to an initially chosen winding direction.
|
||||
// (or, equivalently, which one points back to the one we were just at). At each vertex, we then set neighb1 to be the
|
||||
// one to point onwards, deleting any pointers which we no longer need to complete the trace.
|
||||
|
||||
size_t idx;
|
||||
size_t prev = 0;
|
||||
|
||||
result.m_Faces.push_back(si.newv[0].res);
|
||||
idx = si.newv[0].neighb2;
|
||||
si.newv[0].neighb2 = no_vertex;
|
||||
idx = sliceOp.nvInfo[0].neighbIdx2; // pick arbitrary starting direction
|
||||
sliceOp.nvInfo[0].neighbIdx2 = NO_VERTEX;
|
||||
|
||||
while(idx != 0)
|
||||
{
|
||||
ENSURE(idx < si.newv.size());
|
||||
if (idx >= si.newv.size())
|
||||
ENSURE(idx < sliceOp.nvInfo.size());
|
||||
if (idx >= sliceOp.nvInfo.size())
|
||||
break;
|
||||
|
||||
if (si.newv[idx].neighb1 == prev)
|
||||
if (sliceOp.nvInfo[idx].neighbIdx1 == prev)
|
||||
{
|
||||
si.newv[idx].neighb1 = si.newv[idx].neighb2;
|
||||
si.newv[idx].neighb2 = no_vertex;
|
||||
// neighb1 is pointing the wrong way; we want to normalize it to point onwards in the direction
|
||||
// we initially chose, so swap it with neighb2 and delete neighb2 (no longer needed)
|
||||
sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.nvInfo[idx].neighbIdx2;
|
||||
sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
ENSURE(si.newv[idx].neighb2 == prev);
|
||||
|
||||
si.newv[idx].neighb2 = no_vertex;
|
||||
// neighb1 isn't pointing to the previous vertex, so neighb2 must be (otherwise a pair of vertices failed to
|
||||
// get paired properly during face/plane slicing).
|
||||
ENSURE(sliceOp.nvInfo[idx].neighbIdx2 == prev);
|
||||
sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
|
||||
}
|
||||
|
||||
result.m_Faces.push_back(si.newv[idx].res);
|
||||
result.m_Faces.push_back(sliceOp.nvInfo[idx].resIdx);
|
||||
|
||||
// move to next vertex; neighb1 has been normalized to point onward
|
||||
prev = idx;
|
||||
idx = si.newv[idx].neighb1;
|
||||
si.newv[prev].neighb1 = no_vertex;
|
||||
idx = sliceOp.nvInfo[idx].neighbIdx1;
|
||||
sliceOp.nvInfo[prev].neighbIdx1 = NO_VERTEX; // no longer needed, we've moved on
|
||||
}
|
||||
|
||||
result.m_Faces.push_back(si.newv[0].res);
|
||||
// push starting vertex again to close the shape
|
||||
result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Intersect with frustum by repeated slicing
|
||||
void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
|
||||
@ -287,6 +359,9 @@ void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
|
||||
const CBrush* prev = this;
|
||||
CBrush* next;
|
||||
|
||||
// Repeatedly slice this brush with each plane of the frustum, alternating between 'result' and 'buf' to
|
||||
// save intermediate results. Set up the starting brush so that the final version always ends up in 'result'.
|
||||
|
||||
if (frustum.GetNumPlanes() & 1)
|
||||
next = &result;
|
||||
else
|
||||
@ -304,3 +379,38 @@ void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
|
||||
|
||||
ENSURE(prev == &result);
|
||||
}
|
||||
std::vector<CVector3D> CBrush::GetVertices() const
|
||||
{
|
||||
return m_Vertices;
|
||||
}
|
||||
|
||||
void CBrush::GetFaces(std::vector<std::vector<size_t> >& out) const
|
||||
{
|
||||
// split the back-to-back faces into separate face vectors, so that they're in a
|
||||
// user-friendlier format than the back-to-back vertex index array
|
||||
// i.e. split 'x--xy------yz----z' into 'x--x', 'y-------y', 'z---z'
|
||||
|
||||
size_t faceStartIdx = 0;
|
||||
while (faceStartIdx < m_Faces.size())
|
||||
{
|
||||
// start new face
|
||||
std::vector<size_t> singleFace;
|
||||
singleFace.push_back(m_Faces[faceStartIdx]);
|
||||
|
||||
// step over all the values in the face until we hit the starting value again (which closes the face)
|
||||
size_t j = faceStartIdx + 1;
|
||||
while (j < m_Faces.size() && m_Faces[j] != m_Faces[faceStartIdx])
|
||||
{
|
||||
singleFace.push_back(m_Faces[j]);
|
||||
j++;
|
||||
}
|
||||
|
||||
// each face must be closed by the same value that started it
|
||||
ENSURE(m_Faces[faceStartIdx] == m_Faces[j]);
|
||||
|
||||
singleFace.push_back(m_Faces[j]);
|
||||
out.push_back(singleFace);
|
||||
|
||||
faceStartIdx = j + 1;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2012 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -34,6 +34,8 @@ class CPlane;
|
||||
*/
|
||||
class CBrush
|
||||
{
|
||||
friend class TestBrush;
|
||||
|
||||
public:
|
||||
CBrush() { }
|
||||
|
||||
@ -59,8 +61,9 @@ public:
|
||||
void Bounds(CBoundingBoxAligned& result) const;
|
||||
|
||||
/**
|
||||
* Slice: Cut the object along the given plane, resulting in a smaller (or even empty)
|
||||
* brush representing the part of the object that lies in front of the plane.
|
||||
* Slice: Cut the object along the given plane, resulting in a smaller (or even empty) brush representing
|
||||
* the part of the object that lies in front of the plane (as defined by the positive direction of its
|
||||
* normal vector).
|
||||
*
|
||||
* @param plane the slicing plane
|
||||
* @param result the resulting brush is stored here
|
||||
@ -76,12 +79,34 @@ public:
|
||||
void Intersect(const CFrustum& frustum, CBrush& result) const;
|
||||
|
||||
private:
|
||||
static const size_t no_vertex = ~0u;
|
||||
|
||||
/**
|
||||
* Returns a copy of the vertices in this brush. Intended for testing purposes; you should not need to use
|
||||
* this method directly.
|
||||
*/
|
||||
std::vector<CVector3D> GetVertices() const;
|
||||
|
||||
/**
|
||||
* Writes a vector of the faces in this brush to @p out. Each face is itself a vector, listing the vertex indices
|
||||
* that make up the face, starting and ending with the same index. Intended for testing purposes; you should not
|
||||
* need to use this method directly.
|
||||
*/
|
||||
void GetFaces(std::vector<std::vector<size_t> >& out) const;
|
||||
|
||||
private:
|
||||
static const size_t NO_VERTEX = ~0u;
|
||||
|
||||
typedef std::vector<CVector3D> Vertices;
|
||||
typedef std::vector<size_t> FaceIndices;
|
||||
|
||||
/// Collection of unique vertices that make up this shape.
|
||||
Vertices m_Vertices;
|
||||
|
||||
/**
|
||||
* Holds the face definitions of this brush. Each face is a sequence of indices into m_Vertices that starts and ends with
|
||||
* the same vertex index, completing a loop through all the vertices that make up the face. This vector holds all the face
|
||||
* sequences back-to-back, thus looking something like 'x---xy--------yz--z' in the general case.
|
||||
*/
|
||||
FaceIndices m_Faces;
|
||||
|
||||
struct Helper;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2009 Wildfire Games.
|
||||
/* Copyright (C) 2012 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -18,11 +18,12 @@
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "maths/BoundingBoxAligned.h"
|
||||
#include "maths/BoundingBoxOriented.h"
|
||||
|
||||
class TestBound : public CxxTest::TestSuite
|
||||
{
|
||||
public:
|
||||
void test_empty()
|
||||
void test_empty_aabb()
|
||||
{
|
||||
CBoundingBoxAligned bound;
|
||||
TS_ASSERT(bound.IsEmpty());
|
||||
@ -32,6 +33,19 @@ public:
|
||||
TS_ASSERT(bound.IsEmpty());
|
||||
}
|
||||
|
||||
void test_empty_obb()
|
||||
{
|
||||
CBoundingBoxOriented bound;
|
||||
TS_ASSERT(bound.IsEmpty());
|
||||
bound.m_Basis[0] = CVector3D(1,0,0);
|
||||
bound.m_Basis[1] = CVector3D(0,1,0);
|
||||
bound.m_Basis[2] = CVector3D(0,0,1);
|
||||
bound.m_HalfSizes = CVector3D(1,2,3);
|
||||
TS_ASSERT(!bound.IsEmpty());
|
||||
bound.SetEmpty();
|
||||
TS_ASSERT(bound.IsEmpty());
|
||||
}
|
||||
|
||||
void test_extend_vector()
|
||||
{
|
||||
CBoundingBoxAligned bound;
|
||||
|
183
source/maths/tests/test_Brush.h
Normal file
183
source/maths/tests/test_Brush.h
Normal file
@ -0,0 +1,183 @@
|
||||
/* Copyright (C) 2012 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lib/self_test.h"
|
||||
|
||||
#include "maths/Brush.h"
|
||||
#include "maths/BoundingBoxAligned.h"
|
||||
#include "graphics/Frustum.h"
|
||||
|
||||
class TestBrush : public CxxTest::TestSuite
|
||||
{
|
||||
public:
|
||||
void setUp()
|
||||
{
|
||||
CxxTest::setAbortTestOnFail(true);
|
||||
}
|
||||
|
||||
void tearDown()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void test_slice_empty_brush()
|
||||
{
|
||||
// verifies that the result of slicing an empty bound with a plane yields an empty bound
|
||||
CBrush brush;
|
||||
CPlane plane(CVector4D(0, 0, -1, 0.5f)); // can be anything, really
|
||||
|
||||
CBrush result;
|
||||
brush.Slice(plane, result);
|
||||
TS_ASSERT(brush.IsEmpty());
|
||||
}
|
||||
|
||||
void test_slice_plane_simple()
|
||||
{
|
||||
// slice a 1x1x1 cube vertically down the middle at z = 0.5, with the plane normal pointing towards the negative
|
||||
// end of the Z axis (i.e., anything with Z lower than 0.5 is considered 'in front of' the plane and is kept)
|
||||
CPlane plane(CVector4D(0, 0, -1, 0.5f));
|
||||
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
|
||||
|
||||
CBrush result;
|
||||
brush.Slice(plane, result);
|
||||
|
||||
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
|
||||
TS_ASSERT_EQUALS(8, result.GetVertices().size());
|
||||
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
|
||||
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
|
||||
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 0.5f)); // right-bottom-back
|
||||
size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 0.5f)); // etc.
|
||||
size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
|
||||
size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
|
||||
size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 0.5f));
|
||||
size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 0.5f));
|
||||
|
||||
// verify that the brush contains the six expected planes (one of which is the slicing plane)
|
||||
VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
|
||||
VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
|
||||
VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
|
||||
VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
|
||||
VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
|
||||
VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
|
||||
}
|
||||
|
||||
void test_slice_plane_behind_brush()
|
||||
{
|
||||
// slice the (0,0,0) to (1,1,1) cube by the plane z = 1.5, with the plane normal pointing towards the negative
|
||||
// end of the Z axis (i.e. the entire cube is 'in front of' the plane and should be kept)
|
||||
CPlane plane(CVector4D(0, 0, -1, 1.5f));
|
||||
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
|
||||
|
||||
CBrush result;
|
||||
brush.Slice(plane, result);
|
||||
|
||||
// verify that the resulting brush consists of exactly our 8 expected, unique vertices
|
||||
TS_ASSERT_EQUALS(8, result.GetVertices().size());
|
||||
size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
|
||||
size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
|
||||
size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 1)); // right-bottom-back
|
||||
size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 1)); // etc.
|
||||
size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
|
||||
size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
|
||||
size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 1));
|
||||
size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 1));
|
||||
|
||||
// verify that the brush contains the six expected planes (one of which is the slicing plane)
|
||||
VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
|
||||
VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
|
||||
VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
|
||||
VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
|
||||
VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
|
||||
VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
|
||||
}
|
||||
|
||||
void test_slice_plane_in_front_of_brush()
|
||||
{
|
||||
// slices the (0,0,0) to (1,1,1) cube by the plane z = -0.5, with the plane normal pointing towards the negative
|
||||
// end of the Z axis (i.e. the entire cube is 'behind' the plane and should be cut away)
|
||||
CPlane plane(CVector4D(0, 0, -1, -0.5f));
|
||||
CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
|
||||
|
||||
CBrush result;
|
||||
brush.Slice(plane, result);
|
||||
|
||||
TS_ASSERT_EQUALS(0, result.GetVertices().size());
|
||||
|
||||
std::vector<std::vector<size_t> > faces;
|
||||
result.GetFaces(faces);
|
||||
|
||||
TS_ASSERT_EQUALS(0, faces.size());
|
||||
}
|
||||
|
||||
private:
|
||||
size_t GetUniqueVertexIndex(const CBrush& brush, const CVector3D& vertex, float eps = 1e-6f)
|
||||
{
|
||||
std::vector<CVector3D> vertices = brush.GetVertices();
|
||||
|
||||
for (size_t i = 0; i < vertices.size(); ++i)
|
||||
{
|
||||
const CVector3D& v = vertices[i];
|
||||
if (fabs(v.X - vertex.X) < eps
|
||||
&& fabs(v.Y - vertex.Y) < eps
|
||||
&& fabs(v.Z - vertex.Z) < eps)
|
||||
return i;
|
||||
}
|
||||
|
||||
TS_FAIL("Vertex not found in brush");
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
void VerifyFacePresent(const CBrush& brush, int count, ...)
|
||||
{
|
||||
std::vector<size_t> face;
|
||||
|
||||
va_list args;
|
||||
va_start(args, count);
|
||||
for (int x = 0; x < count; ++x)
|
||||
face.push_back(va_arg(args, size_t));
|
||||
va_end(args);
|
||||
|
||||
if (face.size() == 0)
|
||||
return;
|
||||
|
||||
std::vector<std::vector<size_t> > faces;
|
||||
brush.GetFaces(faces);
|
||||
|
||||
// the brush is free to use any starting vertex along the face, and to use any winding order, so have 'face'
|
||||
// cycle through various starting values and see if any of them (or their reverse) matches one found in the brush.
|
||||
|
||||
for (size_t c = 0; c < face.size() - 1; ++c)
|
||||
{
|
||||
std::vector<std::vector<size_t> >::iterator it1 = std::find(faces.begin(), faces.end(), face);
|
||||
if (it1 != faces.end())
|
||||
return;
|
||||
|
||||
// no match, try the reverse
|
||||
std::vector<size_t> faceReverse = face;
|
||||
std::reverse(faceReverse.begin(), faceReverse.end());
|
||||
std::vector<std::vector<size_t> >::iterator it2 = std::find(faces.begin(), faces.end(), faceReverse);
|
||||
if (it2 != faces.end())
|
||||
return;
|
||||
|
||||
// no match, cycle it
|
||||
face.erase(face.begin());
|
||||
face.push_back(face[0]);
|
||||
}
|
||||
|
||||
TS_FAIL("Face not found in brush");
|
||||
}
|
||||
};
|
@ -224,11 +224,19 @@ void ShadowMap::AddShadowedBound(const CBoundingBoxAligned& bounds)
|
||||
void ShadowMapInternals::CalcShadowMatrices()
|
||||
{
|
||||
CRenderer& renderer = g_Renderer;
|
||||
|
||||
float minZ = ShadowBound[0].Z;
|
||||
|
||||
ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
|
||||
|
||||
// ShadowBound might have been empty to begin with, producing an empty result
|
||||
if (ShadowBound.IsEmpty())
|
||||
{
|
||||
// no-op
|
||||
LightProjection.SetIdentity();
|
||||
TextureMatrix = LightTransform;
|
||||
return;
|
||||
}
|
||||
|
||||
// round off the shadow boundaries to sane increments to help reduce swim effect
|
||||
float boundInc = 16.0f;
|
||||
ShadowBound[0].X = floor(ShadowBound[0].X / boundInc) * boundInc;
|
||||
@ -270,14 +278,14 @@ void ShadowMapInternals::CalcShadowMatrices()
|
||||
LightProjection._34 = shift.Z * scale.Z + renderer.m_ShadowZBias;
|
||||
LightProjection._44 = 1.0;
|
||||
|
||||
|
||||
// Calculate texture matrix by creating the clip space to texture coordinate matrix
|
||||
// and then concatenating all matrices that have been calculated so far
|
||||
CMatrix3D lightToTex;
|
||||
|
||||
float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
|
||||
float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
|
||||
float texscalez = scale.Z * 0.5f;
|
||||
|
||||
CMatrix3D lightToTex;
|
||||
lightToTex.SetZero();
|
||||
lightToTex._11 = texscalex;
|
||||
lightToTex._14 = (offsetX - ShadowBound[0].X) * texscalex;
|
||||
|
Loading…
Reference in New Issue
Block a user