/* 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 . */ /* * Implementation of CBrush, a class representing a convex object */ #include "precompiled.h" #include "lib/ogl.h" #include #include "Brush.h" #include "BoundingBoxAligned.h" #include "graphics/Frustum.h" /////////////////////////////////////////////////////////////////////////////// // Convert the given bounds into a brush CBrush::CBrush(const CBoundingBoxAligned& bounds) { m_Vertices.resize(8); for(size_t i = 0; i < 8; ++i) { 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 m_Faces[5] = 4; m_Faces[6] = 5; m_Faces[7] = 7; m_Faces[8] = 6; m_Faces[9] = 4; // Z = max m_Faces[10] = 0; m_Faces[11] = 2; m_Faces[12] = 6; m_Faces[13] = 4; m_Faces[14] = 0; // X = min m_Faces[15] = 1; m_Faces[16] = 3; m_Faces[17] = 7; m_Faces[18] = 5; m_Faces[19] = 1; // X = max m_Faces[20] = 0; m_Faces[21] = 1; m_Faces[22] = 5; m_Faces[23] = 4; m_Faces[24] = 0; // Y = min m_Faces[25] = 2; m_Faces[26] = 3; m_Faces[27] = 7; m_Faces[28] = 6; m_Faces[29] = 2; // Y = max } /////////////////////////////////////////////////////////////////////////////// // Calculate bounds of this brush void CBrush::Bounds(CBoundingBoxAligned& result) const { result.SetEmpty(); for(size_t i = 0; i < m_Vertices.size(); ++i) result += m_Vertices[i]; } /////////////////////////////////////////////////////////////////////////////// // Cut the brush according to a given plane /// 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) }; /// 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; /** * 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; }; /// 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 ovInfo; /// Holds information about newly inserted vertices during a slice operation. std::vector 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 { /** * 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); }; 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 < sliceOp.nvInfo.size(); ++idx) { if ((sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx1 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx2) || (sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx2 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx1)) break; } if (idx >= sliceOp.nvInfo.size()) { // no previously inserted new vertex found on this edge; insert a new one SliceOpNewVertexInfo nvi; CVector3D newPos; // 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.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); } // at this point, 'idx' is the index into nvInfo of the vertex inserted onto the edge if (sliceOp.thisFaceNewVertexIdx != NO_VERTEX) { // 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 (sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 == NO_VERTEX) sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 = idx; else sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx2 = idx; 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 { // 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 sliceOp.nvInfo[idx].resIdx; } void CBrush::Slice(const CPlane& plane, CBrush& result) const { ENSURE(&result != this); SliceOpInfo sliceOp; 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); // Copy vertices that weren't sliced away by the plane to the resulting brush. for(size_t i = 0; i < m_Vertices.size(); ++i) { 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) { // positive side of the plane; not sliced away vtxInfo.resIdx = result.m_Vertices.size(); result.m_Vertices.push_back(vtx); } else { // other side of the plane; sliced away vtxInfo.resIdx = NO_VERTEX; } } // 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 (currentFaceStartIdx == NO_VERTEX) { // starting a new face ENSURE(sliceOp.thisFaceNewVertexIdx == NO_VERTEX); currentFaceStartIdx = m_Faces[i]; resultFaceStartIdx = result.m_Faces.size(); continue; } 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 (sliceOp.ovInfo[prevIdx].resIdx == 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; 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 { // 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; doesn't cross the plane result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx); } else { // 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 we're back at the first vertex of the current face, then we've completed the face if (curIdx == currentFaceStartIdx) { // 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(currentFaceStartIdx == NO_VERTEX); // 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()) { // 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; idx = sliceOp.nvInfo[0].neighbIdx2; // pick arbitrary starting direction sliceOp.nvInfo[0].neighbIdx2 = NO_VERTEX; while(idx != 0) { ENSURE(idx < sliceOp.nvInfo.size()); if (idx >= sliceOp.nvInfo.size()) break; if (sliceOp.nvInfo[idx].neighbIdx1 == prev) { // 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 { // 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(sliceOp.nvInfo[idx].resIdx); // move to next vertex; neighb1 has been normalized to point onward prev = idx; idx = sliceOp.nvInfo[idx].neighbIdx1; sliceOp.nvInfo[prev].neighbIdx1 = NO_VERTEX; // no longer needed, we've moved on } // 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 { ENSURE(&result != this); if (!frustum.GetNumPlanes()) { result = *this; return; } CBrush buf; 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 next = &buf; for(size_t i = 0; i < frustum.GetNumPlanes(); ++i) { prev->Slice(frustum[i], *next); prev = next; if (prev == &buf) next = &result; else next = &buf; } ENSURE(prev == &result); } std::vector CBrush::GetVertices() const { return m_Vertices; } void CBrush::GetFaces(std::vector >& 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 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; } }