/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program 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 3 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsWindows.h" #include #include "Max.h" #include "stdmat.h" #include "bmmlib.h" #include "iparamb2.h" #include "meshdlib.h" #include "hsTypes.h" #include #include #include "../MaxMain/plMaxNode.h" #include "../MaxComponent/plComponent.h" #include "../MaxComponent/plLightGrpComponent.h" #include "../MaxComponent/plSoftVolumeComponent.h" #include "plClusterUtil.h" #include "../plDrawable/plClusterGroup.h" #include "../plDrawable/plCluster.h" #include "../plDrawable/plSpanTemplate.h" #include "../plDrawable/plSpanInstance.h" #include "../plDrawable/plGeometrySpan.h" #include "../plSurface/hsGMaterial.h" #include "../plSurface/plLayer.h" #include "../plScene/plVisRegion.h" #include "../plGLight/plLightInfo.h" #include "plMeshConverter.h" #include "hsVertexShader.h" #include "plLightMapGen.h" #include "hsResMgr.h" #include "../pnKeyedObject/plUoid.h" #include "../pnMessage/plNodeRefMsg.h" #include "plTweak.h" plConst(int) kDefMinFaces(200); plConst(int) kDefMaxFaces(1000); plConst(hsScalar) kDefMinSize(50.f); plClusterUtil::plClusterUtil() : fGroup(nil), fTemplNode(nil), fTemplate(nil), fMinFaces(kDefMinFaces), fMaxFaces(kDefMaxFaces), fMinSize(kDefMinSize), fIdx(0) { } plClusterUtil::~plClusterUtil() { } plClusterGroup* plClusterUtil::CreateGroup(plMaxNode* templNode, const char* name) { plClusterGroup* retVal = TRACKED_NEW plClusterGroup; char buff[256]; sprintf(buff, "%s_%s_%d", name, templNode->GetName(), fIdx++); hsgResMgr::ResMgr()->NewKey(buff, retVal, templNode->GetLocation(), templNode->GetLoadMask()); plKey sceneNode = templNode->GetRoomKey(); retVal->SetSceneNode(sceneNode); plNodeRefMsg* refMsg = TRACKED_NEW plNodeRefMsg(sceneNode, plRefMsg::kOnCreate, -1, plNodeRefMsg::kGeneric); hsgResMgr::ResMgr()->AddViaNotify(retVal->GetKey(), refMsg, plRefFlags::kActiveRef); return retVal; } plClusterGroup* plClusterUtil::SetupGroup(plClusterGroup *group, plMaxNode* templNode, plSpanTemplateB* templ) { fTemplNode = templNode; fGroup = group; fTemplate = templ; fGroup->fTemplate = templ; fGroup->ISendToSelf(plClusterGroup::kRefMaterial, templ->fMaterial); fGroup->fRenderLevel = templ->fRenderLevel; fMinInsts = fMinFaces / templ->NumTris(); fMaxInsts = fMaxFaces / templ->NumTris(); if( fMinInsts < 1 ) fMinInsts = 1; if( fMaxInsts <= fMinInsts ) fMaxInsts = fMinInsts+1; // STUB // Finish setting up the group here (lights, visregions, LOD), extracting all info // from the template node. ISetupGroupFromTemplate(templNode); return fGroup; } void plClusterUtil::ISetupGroupFromTemplate(plMaxNode* templ) { plLightGrpComponent* liGrp = plLightGrpComponent::GetComp(templ); if( liGrp ) { const hsTArray& lights = liGrp->GetLightInfos(); int i; for( i = 0; i < lights.GetCount(); i++ ) { fGroup->ISendToSelf(plClusterGroup::kRefLight, lights[i]); } } if( templ->HasFade() ) { hsScalar maxDist = 0; hsScalar minDist = 0; Box3 fade = templ->GetFade(); const hsScalar kMaxMaxDist = 1.e10f; if( fade.Min()[2] < 0 ) { minDist = fade.Min()[0]; maxDist = kMaxMaxDist; } if( fade.Max()[2] > 0 ) maxDist = fade.Max()[0]; if( maxDist > minDist ) { fGroup->fLOD.Set(minDist, maxDist); } } hsTArray regions; plVisRegionComponent::CollectRegions(templ, regions); plEffVisSetComponent::CollectRegions(templ, regions); if( regions.GetCount() ) { int i; for( i = 0; i < regions.GetCount(); i++ ) { fGroup->ISendToSelf(plClusterGroup::kRefRegion, regions[i]); } } } class sortData { public: UInt16 fIdx0; UInt16 fIdx1; UInt16 fIdx2; hsScalar fDist; sortData() {} sortData(UInt16 idx0, UInt16 idx1, UInt16 idx2, hsScalar dist) : fIdx0(idx0), fIdx1(idx1), fIdx2(idx2), fDist(dist) { } bool operator<(const sortData& ot) const { return fDist < ot.fDist; } bool operator>(const sortData& ot) const { return fDist > ot.fDist; } bool operator==(const sortData& ot) const { return fDist == ot.fDist; } bool operator!=(const sortData& ot) const { return fDist != ot.fDist; } }; void plClusterUtil::ISortTemplate(plSpanTemplateB* templ) const { UInt16* indexData = templ->fIndices; const int numTris = templ->NumTris(); typedef std::vector sortVec; sortVec vec; vec.resize(numTris); sortVec::iterator iter; for( iter = vec.begin(); iter != vec.end(); iter++ ) { iter->fIdx0 = indexData[0]; iter->fIdx1 = indexData[1]; iter->fIdx2 = indexData[2]; hsPoint3 pos; pos = *templ->Position(indexData[0]); float dist0 = pos.fX * pos.fX + pos.fY * pos.fY; pos = *templ->Position(indexData[1]); float dist1 = pos.fX * pos.fX + pos.fY * pos.fY; pos = *templ->Position(indexData[2]); float dist2 = pos.fX * pos.fX + pos.fY * pos.fY; iter->fDist = dist0 > dist1 ? (dist0 > dist2 ? dist0 : dist2) : (dist1 > dist2 ? dist1 : dist2); indexData += 3; } std::sort(vec.begin(), vec.end(), std::less()); indexData = templ->fIndices; for( iter = vec.begin(); iter != vec.end(); iter++ ) { indexData[0] = iter->fIdx0; indexData[1] = iter->fIdx1; indexData[2] = iter->fIdx2; indexData += 3; } } void plClusterUtil::ITemplateFromGeo(plSpanTemplateB* templ, plGeometrySpan* geo) { UInt16 format = plSpanTemplate::MakeFormat( true, // hasColor geo->GetNumUVs(), // UVW count geo->fFormat & plGeometrySpan::kSkinIndices, // hasWgtIdx (geo->fFormat & plGeometrySpan::kSkinWeightMask) >> 4, // NumWeights true, // hasNorm true // hasPos; ); UInt32 numVerts = geo->fNumVerts; UInt32 numTris = geo->fNumIndices / 3; // Alloc it. templ->Alloc(format, numVerts, numTris); templ->AllocColors(); UInt32 numPos = templ->NumPos(); UInt32 numNorm = templ->NumNorm(); UInt32 numUVWs = templ->NumUVWs(); UInt32 numWeights = templ->NumWeights(); UInt32 numColor = templ->NumColor(); UInt32 numColor2 = templ->NumColor2(); UInt32 numWgtIdx = templ->NumWgtIdx(); // Fill in the data. memcpy(templ->fIndices, geo->fIndexData, templ->IndexSize()); int i; for( i = 0; i < templ->NumVerts(); i++ ) { float wgt[4]; UInt32 wgtIdx; geo->ExtractInitColor(i, templ->MultColor(i), templ->AddColor(i)); hsColorRGBA color; geo->ExtractVertex(i, templ->Position(i), templ->Normal(i), &color); if( templ->NumColor() ) *templ->Color(i) = color.ToARGB32(); if( templ->NumColor2() ) *templ->Color2(i) = 0; int k; for( k = 0; k < templ->NumUVWs(); k++ ) { geo->ExtractUv(i, k, templ->UVWs(i, k)); } if( templ->NumWeights() ) { geo->ExtractWeights(i, wgt, &wgtIdx); int j; for( j = 0; j < templ->NumWeights(); j++ ) *templ->Weight(i, j) = wgt[j]; if( templ->NumWgtIdx() ) *templ->WgtIdx(i) = wgtIdx; } } // Compute the local bounds. templ->ComputeBounds(); ISortTemplate(templ); } plSpanTemplateB* plClusterUtil::IAddTemplate(plMaxNode* templNode, plGeometrySpan* geo) { // Shade our mesh. // STUB // Create our blank template plSpanTemplateB* templ = TRACKED_NEW plSpanTemplateB(templNode); templ->fRenderLevel = templNode->GetRenderLevel(!templNode->GetNoDeferDraw()); ITemplateFromGeo(templ, geo); return templ; } void plClusterUtil::IAddTemplates(plMaxNode* templNode, plSpanTemplTab& templs) { // STUB // Get the Mesh // Figure the format and total number of verts. // If we're lazy or pressed for time we could use MeshConverter. // But we'd probably spend more time undoing MeshConverter hacks than if we start // from scratch. // But, here we go descending cheerully into hell. // At least with this interface we can bail and do it right later without to much // bloodshed. hsTArray spanArray; if( !plMeshConverter::Instance().CreateSpans(templNode, spanArray, false) ) return; plLightMapGen::Instance().Open(::GetCOREInterface(), ::GetCOREInterface()->GetTime(), false); hsVertexShader::Instance().Open(); hsVertexShader::Instance().ShadeNode(templNode, templNode->GetLocalToWorld44(), templNode->GetWorldToLocal44(), spanArray); plLightMapGen::Instance().Close(); hsVertexShader::Instance().Close(); int i; for( i = 0; i < spanArray.GetCount(); i++ ) { plSpanTemplateB* templ = IAddTemplate(templNode, spanArray[i]); templs.Append(1, &templ); templ->fMaterial = spanArray[i]->fMaterial; delete spanArray[i]; } } Box3 plClusterUtil::IBound(const plL2WTab& src) const { Box3 box; int i; for( i = 0; i < src.Count(); i++ ) { box += src[i].GetTrans(); } return box; } Point3 plClusterUtil::ILength(const plL2WTab& src) const { Box3 box = IBound(src); return box.Max() - box.Min(); } int plClusterUtil::ISelectAxis(const plL2WTab& src) const { Box3 box = IBound(src); Point3 del = box.Max() - box.Min(); if( del.x > del.y ) { if( del.x > del.z ) return 0; else return 2; } if( del.y > del.z ) return 1; return 2; } static int sortAxis = 0; static int cmp(const void *elem1, const void *elem2) { Matrix3* m1 = (Matrix3*) elem1; Matrix3* m2 = (Matrix3*) elem2; float d1 = m1->GetTrans()[sortAxis]; float d2 = m2->GetTrans()[sortAxis]; if( d1 < d2 ) return -1; else if( d1 > d2 ) return 1; return 0; } hsBool plClusterUtil::ISplitCluster(plSpanTemplateB* templ, plL2WTab& src, plL2WTab& lo, plL2WTab& hi) { // Tried this, seems to work pretty well, but a more even grid is probably wiser at // this point. #if 0 // MAX_SEP if( src.Count() <= fMinInsts) return false; // Pick an axis sortAxis = ISelectAxis(src); if( src.Count() < fMaxInsts) { Point3 len = ILength(src); if( len[sortAxis] < fMinSize ) return false; } // Sort by that axis src.Sort(cmp); // Find the biggest gap float maxDist = 0; int pivot = 0; int i; for( i = 1; i < src.Count(); i++ ) { float dist = src[i].GetTrans()[sortAxis] - src[i-1].GetTrans()[sortAxis]; if( dist > maxDist ) { maxDist = dist; pivot = i; } } hsAssert((pivot > 0) && (pivot < src.Count()), "Invalid pivot found"); // Put everyone above it in hi, below it in lo lo.Append(pivot, src.Addr(0)); hi.Append(src.Count()-pivot, src.Addr(pivot)); #else // MAX_SEP if( src.Count() <= fMinInsts ) return false; // Pick an axis sortAxis = ISelectAxis(src); if( src.Count() < fMaxInsts) { Point3 len = ILength(src); if( len[sortAxis] < fMinSize ) return false; } // Sort by that axis src.Sort(cmp); int pivot = src.Count() >> 1; lo.Append(pivot, src.Addr(0)); hi.Append(src.Count()-pivot, src.Addr(pivot)); #endif // MAX_SEP return true; } void plClusterUtil::IFindClustersRecur(plSpanTemplateB* templ, plL2WTab& src, plL2WTabTab& dst) { plL2WTab lo; plL2WTab hi; if( ISplitCluster(templ, src, lo, hi) ) { // Keep going IFindClustersRecur(templ, lo, dst); IFindClustersRecur(templ, hi, dst); } else { plL2WTab* tab = TRACKED_NEW plL2WTab(src); dst.Append(1, &tab); } } void plClusterUtil::IFreeClustersRecur(plL2WTabTab& dst) const { int i; for( i = 0; i < dst.Count(); i++ ) delete dst[i]; } inline hsScalar inlGetAlpha(UInt32* color) { return hsScalar(*color >> 24) / 255.99f; } plSpanEncoding plClusterUtil::ISelectEncoding(plPoint3TabTab& delPosTab, plColorTabTab& colorsTab) { hsBool hasColor = false; hsBool hasAlpha = false; hsScalar maxLenSq = 0; hsScalar maxX = 0; hsScalar maxY = 0; hsScalar maxZ = 0; int i; for( i = 0; i < delPosTab.Count(); i++ ) { int j; if( delPosTab[i] ) { plPoint3Tab& delPos = *delPosTab[i]; for( j = 0; j < delPos.Count(); j++ ) { hsScalar lenSq = delPos[j].MagnitudeSquared(); if( lenSq > maxLenSq ) maxLenSq = lenSq; hsScalar d = fabs(delPos[j].fX); if( d > maxX ) maxX = d; d = fabs(delPos[j].fY); if( d > maxY ) maxY = d; d = fabs(delPos[j].fZ); if( d > maxZ ) maxZ = d; } } if( colorsTab[i] ) { plColorTab& color = *colorsTab[i]; for( j = 0; j < color.Count(); j++ ) { UInt32 col = color[j]; if( (col & 0x00ffffff) != 0x00ffffff ) hasColor = true; if( (col & 0xff000000) != 0xff000000 ) hasAlpha = true; } } } UInt32 code = 0; hsScalar posScale = 1.f; if( hasColor && hasAlpha ) code |= plSpanEncoding::kColAI88; else if( hasColor ) code |= plSpanEncoding::kColI8; else if( hasAlpha ) code |= plSpanEncoding::kColA8; plConst(hsScalar) kPosQuantum(0.5 / 12.f); // 1/2 inch. hsScalar maxLen = hsSquareRoot(maxLenSq); if( maxLen > kPosQuantum ) { if( (maxX < kPosQuantum) && (maxY < kPosQuantum) ) { code |= plSpanEncoding::kPos008; posScale = maxLen / 255.9f; } else if( (maxLen / 255.9f) < kPosQuantum ) { code |= plSpanEncoding::kPos888; posScale = maxLen / 255.9f; } else if( (maxLen / hsScalar(1 << 10)) < kPosQuantum ) { code |= plSpanEncoding::kPos101010; posScale = maxLen / hsScalar(1 << 10); } else { code |= plSpanEncoding::kPos161616; posScale = maxLen / hsScalar(1 << 16); } } return plSpanEncoding(code, posScale); } static int CompTemplates(const void *elem1, const void *elem2) { plSpanTemplateB* templA = *((plSpanTemplateB**)elem1); plSpanTemplateB* templB = *((plSpanTemplateB**)elem2); hsScalar hA = templA->GetLocalBounds().GetMaxs().fZ; hsScalar hB = templB->GetLocalBounds().GetMaxs().fZ; if( hA < hB ) return -1; if( hA > hB ) return 1; return 0; } void plClusterUtil::ISortTemplates(plSpanTemplTab& templs) const { templs.Sort(CompTemplates); float maxZ = -1.e33f; int i; for( i = 1; i < templs.Count(); i++ ) { templs[i]->fRenderLevel.Set(templs[i-1]->fRenderLevel.Level() + 1); } } plSpanTemplTab plClusterUtil::MakeTemplates(INode* templNode) { plSpanTemplTab templs; IAddTemplates((plMaxNode*)templNode, templs); ISortTemplates(templs); return templs; } void plClusterUtil::AddClusters(plL2WTab& insts, plDeformVert* def, plShadeVert* shade) { plPoint3TabTab delPos; plColorTabTab colors; plL2WTabTab clusters; IFindClustersRecur(fTemplate, insts, clusters); int j; for( j = 0; j < clusters.Count(); j++ ) { // Create a plCluster to hold them all. plCluster* cluster = fGroup->IAddCluster(); // Get the delPositions and colors for all the instances IAllocPosAndColor(fTemplate, *clusters[j], delPos, colors); IDelPosAndColor(fTemplate, *clusters[j], def, shade, delPos, colors); // Look through the results and pick out a proper encoding plSpanEncoding code = ISelectEncoding(delPos, colors); cluster->SetEncoding(code); // Now create, encode and add all the insts to the cluster. IAddInstsToCluster(cluster, fTemplate, *clusters[j], delPos, colors); IFreePosAndColor(delPos, colors); } IFreeClustersRecur(clusters); } void plClusterUtil::IAddInstsToCluster(plCluster* cluster, plSpanTemplateB* templ, const plL2WTab& insts, plPoint3TabTab& delPos, plColorTabTab& colors) { int i; for( i = 0; i < insts.Count(); i++ ) { plSpanInstance* span = TRACKED_NEW plSpanInstance; span->Alloc(cluster->GetEncoding(), templ->NumVerts()); span->SetLocalToWorld(plMaxNodeBase::Matrix3ToMatrix44(insts[i])); span->Encode(cluster->GetEncoding(), templ->NumVerts(), delPos[i] ? delPos[i]->Addr(0) : nil, colors[i] ? colors[i]->Addr(0) : nil); cluster->IAddInst(span); } } void plClusterUtil::IAllocPosAndColor(plSpanTemplateB* templ, const plL2WTab& insts, plPoint3TabTab& delPos, plColorTabTab& colors) { delPos.SetCount(insts.Count()); colors.SetCount(insts.Count()); const int numVerts = templ->NumVerts(); int i; for( i = 0; i < insts.Count(); i++ ) { delPos[i] = nil; colors[i] = nil; } } void plClusterUtil::IFreePosAndColor(plPoint3TabTab& delPos, plColorTabTab& colors) const { int i; for( i = 0; i < delPos.Count(); i++ ) delete delPos[i]; for( i = 0; i < colors.Count(); i++ ) delete colors[i]; } void plClusterUtil::IDelPosAndColor(plSpanTemplateB* templ, const plL2WTab& insts, plDeformVert* def, plShadeVert* shade, plPoint3TabTab& delPos, plColorTabTab& colors) { hsBool doDef = def != nil; hsBool doCol = shade != nil; // For each inst int i; for( i = 0; i < insts.Count(); i++ ) { hsBounds3Ext wBnd = templ->GetLocalBounds(); hsMatrix44 l2w = plMaxNodeBase::Matrix3ToMatrix44(insts[i]); hsMatrix44 w2l; l2w.GetInverse(&w2l); hsMatrix44 w2lT; w2l.GetTranspose(&w2lT); wBnd.Transform(&l2w); if( doDef ) { def->Begin(templ->GetSrcNode(), wBnd); delPos[i] = TRACKED_NEW plPoint3Tab; delPos[i]->SetCount(templ->NumVerts()); int j; for( j = 0; j < templ->NumVerts(); j++ ) { hsPoint3 p = l2w * *templ->Position(j); plPoint3Tab& dp = *delPos[i]; dp[j] = def->GetDel(p); dp[j] = w2l * dp[j]; } def->End(); } // Make the stored colors the actual output UInt32. // templ has the mult and add colors, apply them here. if( doCol ) { shade->Begin(templ->GetSrcNode(), wBnd); colors[i] = TRACKED_NEW plColorTab; colors[i]->SetCount(templ->NumVerts()); int j; for( j = 0; j < templ->NumVerts(); j++ ) { hsPoint3 pos = *templ->Position(j); pos += (*delPos[i])[j]; pos = l2w * pos; hsVector3 norm = *templ->Normal(j); norm = w2lT * norm; Color rgb = shade->GetShade(pos, norm); rgb *= Color(templ->MultColor(j)->r, templ->MultColor(j)->g, templ->MultColor(j)->b); rgb += Color(templ->AddColor(j)->r, templ->AddColor(j)->g, templ->AddColor(j)->b); (*colors[i])[j] = hsColorRGBA().Set(rgb.r, rgb.g, rgb.b, templ->MultColor(j)->a).ToARGB32(); } shade->End(); } } }