/* ----------------------------------------------------------------------------- This source file is part of OGRE (Object-oriented Graphics Rendering Engine) For the latest info, see http://www.ogre3d.org/ Copyright (c) 2000-2006 Torus Knot Software Ltd Also see acknowledgements in Readme.html This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, or go to http://www.gnu.org/copyleft/lesser.txt. You may alternatively use this source under the terms of a specific version of the OGRE Unrestricted License provided you have obtained such a license from Torus Knot Software Ltd. ----------------------------------------------------------------------------- */ #include "OgreStableHeaders.h" #include "OgrePatchSurface.h" #include "OgreMeshManager.h" #include "OgreMesh.h" #include "OgreSubMesh.h" #include "OgreException.h" #include "OgreHardwareBufferManager.h" #include "OgreHardwareVertexBuffer.h" #include "OgreHardwareIndexBuffer.h" #define LEVEL_WIDTH(lvl) ((1 << (lvl+1)) + 1) namespace Ogre { // TODO: make this deal with specular colours and more than 2 texture coords //----------------------------------------------------------------------- PatchSurface::PatchSurface() { mType = PST_BEZIER; } //----------------------------------------------------------------------- PatchSurface::~PatchSurface() { } //----------------------------------------------------------------------- void PatchSurface::defineSurface(void* controlPointBuffer, VertexDeclaration *declaration, size_t width, size_t height, PatchSurfaceType pType, size_t uMaxSubdivisionLevel, size_t vMaxSubdivisionLevel, VisibleSide visibleSide) { if (height == 0 || width == 0) return; // Do nothing - garbage mType = pType; mCtlWidth = width; mCtlHeight = height; mCtlCount = width * height; mControlPointBuffer = controlPointBuffer; mDeclaration = declaration; // Copy positions into Vector3 vector mVecCtlPoints.clear(); const VertexElement* elem = declaration->findElementBySemantic(VES_POSITION); size_t vertSize = declaration->getVertexSize(0); const unsigned char *pVert = static_cast(controlPointBuffer); float* pFloat; for (size_t i = 0; i < mCtlCount; ++i) { elem->baseVertexPointerToElement((void*)pVert, &pFloat); mVecCtlPoints.push_back(Vector3(pFloat[0], pFloat[1], pFloat[2])); pVert += vertSize; } mVSide = visibleSide; // Determine max level // Initialise to 100% detail mSubdivisionFactor = 1.0f; if (uMaxSubdivisionLevel == AUTO_LEVEL) { mULevel = mMaxULevel = getAutoULevel(); } else { mULevel = mMaxULevel = uMaxSubdivisionLevel; } if (vMaxSubdivisionLevel == AUTO_LEVEL) { mVLevel = mMaxVLevel = getAutoVLevel(); } else { mVLevel = mMaxVLevel = vMaxSubdivisionLevel; } // Derive mesh width / height mMeshWidth = (LEVEL_WIDTH(mMaxULevel)-1) * ((mCtlWidth-1)/2) + 1; mMeshHeight = (LEVEL_WIDTH(mMaxVLevel)-1) * ((mCtlHeight-1)/2) + 1; // Calculate number of required vertices / indexes at max resolution mRequiredVertexCount = mMeshWidth * mMeshHeight; int iterations = (mVSide == VS_BOTH)? 2 : 1; mRequiredIndexCount = (mMeshWidth-1) * (mMeshHeight-1) * 2 * iterations * 3; // Calculate bounds based on control points std::vector::const_iterator ctli; Vector3 min, max; Real maxSqRadius; bool first = true; for (ctli = mVecCtlPoints.begin(); ctli != mVecCtlPoints.end(); ++ctli) { if (first) { min = max = *ctli; maxSqRadius = ctli->squaredLength(); first = false; } else { min.makeFloor(*ctli); max.makeCeil(*ctli); maxSqRadius = std::max(ctli->squaredLength(), maxSqRadius); } } mAABB.setExtents(min, max); mBoundingSphere = Math::Sqrt(maxSqRadius); } //----------------------------------------------------------------------- const AxisAlignedBox& PatchSurface::getBounds(void) const { return mAABB; } //----------------------------------------------------------------------- Real PatchSurface::getBoundingSphereRadius(void) const { return mBoundingSphere; } //----------------------------------------------------------------------- size_t PatchSurface::getRequiredVertexCount(void) const { return mRequiredVertexCount; } //----------------------------------------------------------------------- size_t PatchSurface::getRequiredIndexCount(void) const { return mRequiredIndexCount; } //----------------------------------------------------------------------- void PatchSurface::build(HardwareVertexBufferSharedPtr destVertexBuffer, size_t vertexStart, HardwareIndexBufferSharedPtr destIndexBuffer, size_t indexStart) { if (mVecCtlPoints.empty()) return; mVertexBuffer = destVertexBuffer; mVertexOffset = vertexStart; mIndexBuffer = destIndexBuffer; mIndexOffset = indexStart; // Lock just the region we are interested in void* lockedBuffer = mVertexBuffer->lock( mVertexOffset * mDeclaration->getVertexSize(0), mRequiredVertexCount * mDeclaration->getVertexSize(0), HardwareBuffer::HBL_NO_OVERWRITE); distributeControlPoints(lockedBuffer); // Subdivide the curve to the MAX :) // Do u direction first, so need to step over v levels not done yet size_t vStep = 1 << mMaxVLevel; size_t uStep = 1 << mMaxULevel; size_t v, u; for (v = 0; v < mMeshHeight; v += vStep) { // subdivide this row in u subdivideCurve(lockedBuffer, v*mMeshWidth, uStep, mMeshWidth / uStep, mULevel); } // Now subdivide in v direction, this time all the u direction points are there so no step for (u = 0; u < mMeshWidth; ++u) { subdivideCurve(lockedBuffer, u, vStep*mMeshWidth, mMeshHeight / vStep, mVLevel); } mVertexBuffer->unlock(); // Make triangles from mesh at this current level of detail makeTriangles(); } //----------------------------------------------------------------------- size_t PatchSurface::getAutoULevel(bool forMax) { // determine levels // Derived from work by Bart Sekura in Rogl Vector3 a,b,c; size_t u,v; bool found=false; // Find u level for(v = 0; v < mCtlHeight; v++) { for(u = 0; u < mCtlWidth-1; u += 2) { a = mVecCtlPoints[v * mCtlWidth + u]; b = mVecCtlPoints[v * mCtlWidth + u+1]; c = mVecCtlPoints[v * mCtlWidth + u+2]; if(a!=c) { found=true; break; } } if(found) break; } if(!found) { OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Can't find suitable control points for determining U subdivision level", "PatchSurface::getAutoULevel"); } return findLevel(a,b,c); } //----------------------------------------------------------------------- size_t PatchSurface::getAutoVLevel(bool forMax) { Vector3 a,b,c; size_t u,v; bool found=false; for(u = 0; u < mCtlWidth; u++) { for(v = 0; v < mCtlHeight-1; v += 2) { a = mVecCtlPoints[v * mCtlWidth + u]; b = mVecCtlPoints[(v+1) * mCtlWidth + u]; c = mVecCtlPoints[(v+2) * mCtlWidth + u]; if(a!=c) { found=true; break; } } if(found) break; } if(!found) { OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, "Can't find suitable control points for determining V subdivision level", "PatchSurface::getAutoVLevel"); } return findLevel(a,b,c); } //----------------------------------------------------------------------- void PatchSurface::setSubdivisionFactor(Real factor) { assert(factor >= 0.0f && factor <= 1.0f); mSubdivisionFactor = factor; mULevel = static_cast(factor * mMaxULevel); mVLevel = static_cast(factor * mMaxVLevel); makeTriangles(); } //----------------------------------------------------------------------- Real PatchSurface::getSubdivisionFactor(void) const { return mSubdivisionFactor; } //----------------------------------------------------------------------- size_t PatchSurface::getCurrentIndexCount(void) const { return mCurrIndexCount; } //----------------------------------------------------------------------- size_t PatchSurface::findLevel(Vector3& a, Vector3& b, Vector3& c) { // Derived from work by Bart Sekura in rogl // Apart from I think I fixed a bug - see below // I also commented the code, the only thing wrong with rogl is almost no comments!! const size_t max_levels = 5; const float subdiv = 10; size_t level; float test=subdiv*subdiv; Vector3 s,t,d; for(level=0; levelsharedVertexData = new VertexData(); // Copy all vertex parameters mMesh->sharedVertexData->vertexStart = 0; // Vertex count will be set on build() because it depends on current level // NB clone the declaration because Mesh's VertexData will destroy it mMesh->sharedVertexData->vertexDeclaration = mDeclaration->clone(); // Create buffer (only a single buffer) // Allocate enough buffer memory for maximum subdivision, not current subdivision HardwareVertexBufferSharedPtr vbuf = HardwareBufferManager::getSingleton(). createVertexBuffer( mDeclaration->getVertexSize(0), mMaxMeshHeight * mMaxMeshWidth, // maximum size HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // dynamic for changing level // Set binding mMesh->sharedVertexData->vertexBufferBinding->setBinding(0, vbuf); SubMesh* sm = mMesh->createSubMesh(); // Allocate enough index data for max subdivision sm->indexData->indexStart = 0; // Index count will be set on build() unsigned short iterations = (mVSide == VS_BOTH ? 2 : 1); sm->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer( HardwareIndexBuffer::IT_16BIT, (mMaxMeshWidth-1) * (mMaxMeshHeight-1) * 2 * iterations * 3, HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); mMesh->load(); // Derive bounds from control points, cannot stray outside that Vector3 min, max; Real maxSquaredRadius; bool first = true; std::vector::iterator i, iend; iend = mVecCtlPoints.end(); for (i = mVecCtlPoints.begin(); i != iend; ++i) { if (first) { min = max = *i; maxSquaredRadius = i->squaredLength(); } else { min.makeFloor(*i); max.makeCeil(*i); maxSquaredRadius = std::max(maxSquaredRadius, i->squaredLength()); } } mMesh->_setBounds(AxisAlignedBox(min, max)); mMesh->_setBoundingSphereRadius(Math::Sqrt(maxSquaredRadius)); } */ //----------------------------------------------------------------------- void PatchSurface::distributeControlPoints(void* lockedBuffer) { // Insert original control points into expanded mesh size_t uStep = 1 << mULevel; size_t vStep = 1 << mVLevel; void* pSrc = mControlPointBuffer; size_t vertexSize = mDeclaration->getVertexSize(0); float *pSrcReal, *pDestReal; RGBA *pSrcRGBA, *pDestRGBA; void* pDest; const VertexElement* elemPos = mDeclaration->findElementBySemantic(VES_POSITION); const VertexElement* elemNorm = mDeclaration->findElementBySemantic(VES_NORMAL); const VertexElement* elemTex0 = mDeclaration->findElementBySemantic(VES_TEXTURE_COORDINATES, 0); const VertexElement* elemTex1 = mDeclaration->findElementBySemantic(VES_TEXTURE_COORDINATES, 1); const VertexElement* elemDiffuse = mDeclaration->findElementBySemantic(VES_DIFFUSE); for (size_t v = 0; v < mMeshHeight; v += vStep) { // set dest by v from base pDest = static_cast( static_cast(lockedBuffer) + (vertexSize * mMeshWidth * v)); for (size_t u = 0; u < mMeshWidth; u += uStep) { // Copy Position elemPos->baseVertexPointerToElement(pSrc, &pSrcReal); elemPos->baseVertexPointerToElement(pDest, &pDestReal); *pDestReal++ = *pSrcReal++; *pDestReal++ = *pSrcReal++; *pDestReal++ = *pSrcReal++; // Copy Normals if (elemNorm) { elemNorm->baseVertexPointerToElement(pSrc, &pSrcReal); elemNorm->baseVertexPointerToElement(pDest, &pDestReal); *pDestReal++ = *pSrcReal++; *pDestReal++ = *pSrcReal++; *pDestReal++ = *pSrcReal++; } // Copy Diffuse if (elemDiffuse) { elemDiffuse->baseVertexPointerToElement(pSrc, &pSrcRGBA); elemDiffuse->baseVertexPointerToElement(pDest, &pDestRGBA); *pDestRGBA++ = *pSrcRGBA++; } // Copy texture coords if (elemTex0) { elemTex0->baseVertexPointerToElement(pSrc, &pSrcReal); elemTex0->baseVertexPointerToElement(pDest, &pDestReal); for (size_t dim = 0; dim < VertexElement::getTypeCount(elemTex0->getType()); ++dim) *pDestReal++ = *pSrcReal++; } if (elemTex1) { elemTex1->baseVertexPointerToElement(pSrc, &pSrcReal); elemTex1->baseVertexPointerToElement(pDest, &pDestReal); for (size_t dim = 0; dim < VertexElement::getTypeCount(elemTex1->getType()); ++dim) *pDestReal++ = *pSrcReal++; } // Increment source by one vertex pSrc = static_cast( static_cast(pSrc) + vertexSize); // Increment dest by 1 vertex * uStep pDest = static_cast( static_cast(pDest) + (vertexSize * uStep)); } // u } // v } //----------------------------------------------------------------------- void PatchSurface::subdivideCurve(void* lockedBuffer, size_t startIdx, size_t stepSize, size_t numSteps, size_t iterations) { // Subdivides a curve within a sparsely populated buffer (gaps are already there to be interpolated into) size_t leftIdx, rightIdx, destIdx, halfStep, maxIdx; bool firstSegment; maxIdx = startIdx + (numSteps * stepSize); size_t step = stepSize; while(iterations--) { halfStep = step / 2; leftIdx = startIdx; destIdx = leftIdx + halfStep; rightIdx = leftIdx + step; firstSegment = true; while (leftIdx < maxIdx) { // Interpolate interpolateVertexData(lockedBuffer, leftIdx, rightIdx, destIdx); // If 2nd or more segment, interpolate current left between current and last mid points if (!firstSegment) { interpolateVertexData(lockedBuffer, leftIdx - halfStep, leftIdx + halfStep, leftIdx); } // Next segment leftIdx = rightIdx; destIdx = leftIdx + halfStep; rightIdx = leftIdx + step; firstSegment = false; } step = halfStep; } } //----------------------------------------------------------------------- void PatchSurface::makeTriangles(void) { // Our vertex buffer is subdivided to the highest level, we need to generate tris // which step over the vertices we don't need for this level of detail. // Calculate steps int vStep = 1 << (mMaxVLevel - mVLevel); int uStep = 1 << (mMaxULevel - mULevel); size_t currWidth = (LEVEL_WIDTH(mULevel)-1) * ((mCtlWidth-1)/2) + 1; size_t currHeight = (LEVEL_WIDTH(mVLevel)-1) * ((mCtlHeight-1)/2) + 1; bool use32bitindexes = (mIndexBuffer->getType() == HardwareIndexBuffer::IT_32BIT); // The mesh is built, just make a list of indexes to spit out the triangles int vInc, uInc; size_t vCount, uCount, v, u, iterations; if (mVSide == VS_BOTH) { iterations = 2; vInc = vStep; v = 0; // Start with front } else { iterations = 1; if (mVSide == VS_FRONT) { vInc = vStep; v = 0; } else { vInc = -vStep; v = mMeshHeight - 1; } } // Calc num indexes mCurrIndexCount = (currWidth - 1) * (currHeight - 1) * 6 * iterations; size_t v1, v2, v3; // Lock just the section of the buffer we need unsigned short* p16; unsigned int* p32; if (use32bitindexes) { p32 = static_cast( mIndexBuffer->lock( mIndexOffset * sizeof(unsigned int), mRequiredIndexCount * sizeof(unsigned int), HardwareBuffer::HBL_NO_OVERWRITE)); } else { p16 = static_cast( mIndexBuffer->lock( mIndexOffset * sizeof(unsigned short), mRequiredIndexCount * sizeof(unsigned short), HardwareBuffer::HBL_NO_OVERWRITE)); } while (iterations--) { // Make tris in a zigzag pattern (compatible with strips) u = 0; uInc = uStep; // Start with moving +u vCount = currHeight - 1; while (vCount--) { uCount = currWidth - 1; while (uCount--) { // First Tri in cell // ----------------- v1 = ((v + vInc) * mMeshWidth) + u; v2 = (v * mMeshWidth) + u; v3 = ((v + vInc) * mMeshWidth) + (u + uInc); // Output indexes if (use32bitindexes) { *p32++ = static_cast(v1); *p32++ = static_cast(v2); *p32++ = static_cast(v3); } else { *p16++ = static_cast(v1); *p16++ = static_cast(v2); *p16++ = static_cast(v3); } // Second Tri in cell // ------------------ v1 = ((v + vInc) * mMeshWidth) + (u + uInc); v2 = (v * mMeshWidth) + u; v3 = (v * mMeshWidth) + (u + uInc); // Output indexes if (use32bitindexes) { *p32++ = static_cast(v1); *p32++ = static_cast(v2); *p32++ = static_cast(v3); } else { *p16++ = static_cast(v1); *p16++ = static_cast(v2); *p16++ = static_cast(v3); } // Next column u += uInc; } // Next row v += vInc; u = 0; } // Reverse vInc for double sided v = mMeshHeight - 1; vInc = -vInc; } mIndexBuffer->unlock(); } //----------------------------------------------------------------------- void PatchSurface::interpolateVertexData(void* lockedBuffer, size_t leftIdx, size_t rightIdx, size_t destIdx) { size_t vertexSize = mDeclaration->getVertexSize(0); const VertexElement* elemPos = mDeclaration->findElementBySemantic(VES_POSITION); const VertexElement* elemNorm = mDeclaration->findElementBySemantic(VES_NORMAL); const VertexElement* elemDiffuse = mDeclaration->findElementBySemantic(VES_DIFFUSE); const VertexElement* elemTex0 = mDeclaration->findElementBySemantic(VES_TEXTURE_COORDINATES, 0); const VertexElement* elemTex1 = mDeclaration->findElementBySemantic(VES_TEXTURE_COORDINATES, 1); float *pDestReal, *pLeftReal, *pRightReal; unsigned char *pDestChar, *pLeftChar, *pRightChar; unsigned char *pDest, *pLeft, *pRight; // Set up pointers & interpolate pDest = static_cast(lockedBuffer) + (vertexSize * destIdx); pLeft = static_cast(lockedBuffer) + (vertexSize * leftIdx); pRight = static_cast(lockedBuffer) + (vertexSize * rightIdx); // Position elemPos->baseVertexPointerToElement(pDest, &pDestReal); elemPos->baseVertexPointerToElement(pLeft, &pLeftReal); elemPos->baseVertexPointerToElement(pRight, &pRightReal); *pDestReal++ = (*pLeftReal++ + *pRightReal++) * 0.5; *pDestReal++ = (*pLeftReal++ + *pRightReal++) * 0.5; *pDestReal++ = (*pLeftReal++ + *pRightReal++) * 0.5; if (elemNorm) { elemNorm->baseVertexPointerToElement(pDest, &pDestReal); elemNorm->baseVertexPointerToElement(pLeft, &pLeftReal); elemNorm->baseVertexPointerToElement(pRight, &pRightReal); Vector3 norm; norm.x = (*pLeftReal++ + *pRightReal++) * 0.5; norm.y = (*pLeftReal++ + *pRightReal++) * 0.5; norm.z = (*pLeftReal++ + *pRightReal++) * 0.5; norm.normalise(); *pDestReal++ = norm.x; *pDestReal++ = norm.y; *pDestReal++ = norm.z; } if (elemDiffuse) { // Blend each byte individually elemDiffuse->baseVertexPointerToElement(pDest, &pDestChar); elemDiffuse->baseVertexPointerToElement(pLeft, &pLeftChar); elemDiffuse->baseVertexPointerToElement(pRight, &pRightChar); // 4 bytes to RGBA *pDestChar++ = static_cast(((*pLeftChar++) + (*pRightChar++)) * 0.5); *pDestChar++ = static_cast(((*pLeftChar++) + (*pRightChar++)) * 0.5); *pDestChar++ = static_cast(((*pLeftChar++) + (*pRightChar++)) * 0.5); *pDestChar++ = static_cast(((*pLeftChar++) + (*pRightChar++)) * 0.5); } if (elemTex0) { elemTex0->baseVertexPointerToElement(pDest, &pDestReal); elemTex0->baseVertexPointerToElement(pLeft, &pLeftReal); elemTex0->baseVertexPointerToElement(pRight, &pRightReal); for (size_t dim = 0; dim < VertexElement::getTypeCount(elemTex0->getType()); ++dim) *pDestReal++ = ((*pLeftReal++) + (*pRightReal++)) * 0.5; } if (elemTex1) { elemTex1->baseVertexPointerToElement(pDest, &pDestReal); elemTex1->baseVertexPointerToElement(pLeft, &pLeftReal); elemTex1->baseVertexPointerToElement(pRight, &pRightReal); for (size_t dim = 0; dim < VertexElement::getTypeCount(elemTex1->getType()); ++dim) *pDestReal++ = ((*pLeftReal++) + (*pRightReal++)) * 0.5; } } }