// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" // #include "nel/3d/packed_zone.h" #include "nel/3d/driver.h" #include "nel/3d/material.h" // #include "nel/misc/matrix.h" #include "nel/misc/polygon.h" #include "nel/misc/path.h" #include "nel/misc/grid_traversal.h" #include "nel/misc/bit_mem_stream.h" // #include #include // using namespace NLMISC; namespace NL3D { #define PACKED_COL_RANGE 4095 class CVectorPacker { public: struct CFormat { uint Rel0NumBits; // 2 for - 2 .. + 1 range uint Rel1NumBits; // 4 for - 8 .. + 7 range uint Rel2NumBits; // 8 for - 128 .. + 127 range uint AbsNumBits; // num bits for absolute value }; void serialPackedVector16(std::vector &v, NLMISC::IStream &f, const CFormat &format); private: // format is stored as 2 bits at each entry enum TFormat { Rel0 = 0, // - 2 .. + 1 range Rel1 = 1, // - 8 .. + 7 range Rel2 = 2, // - 128 .. + 127 range AbsOrRLE = 3 // full precision absolute value }; // std::vector _LastDeltas; uint32 _LastTag; uint _ReadIndex; uint _Count[4]; // statistics uint _Repeated[4]; // statistics CFormat _Format; private: void bufferizeDelta(uint32 tag, sint32 delta, CBitMemStream &bits); void flush(CBitMemStream &bits); void writeSimpleValue(CBitMemStream &bits, sint32 delta); void readSimpleValue(uint32 tag, std::vector &v, CBitMemStream &bits); static sint32 getMax(uint numBits) { return (uint32) ((1 << (numBits - 1)) - 1); } static sint32 getMin(uint numBits) { return (uint32) (0xfffffffe << (numBits - 2)); } static void extendSign(uint numBits, uint32 &value) { if (value & (1 << (numBits - 1))) { value |= (uint32) (0xfffffffe << (numBits - 1)); } } }; // ******************************************************************************************** void CVectorPacker::writeSimpleValue(CBitMemStream &bits, sint32 delta) { uint32 croppedDelta = (uint32) delta; switch(_LastTag) { case Rel0: { bits.serial(croppedDelta, _Format.Rel0NumBits); } break; case Rel1: { bits.serial(croppedDelta, _Format.Rel1NumBits); } break; case Rel2: { bits.serial(croppedDelta, _Format.Rel2NumBits); } break; case AbsOrRLE: { // not a delta but a value ... uint32 value = (uint16) delta; bits.serial(value, _Format.AbsNumBits); } break; default: nlassert(0); break; } if (_LastTag == AbsOrRLE) { //nlwarning("writing simple value %d (tag = %d)", (int) (uint16) delta, (int) _LastTag); } else { //nlwarning("writing simple delta %d (tag = %d)", (int) delta, (int) _LastTag); } ++ _Count[_LastTag]; } // ******************************************************************************************** void CVectorPacker::readSimpleValue(uint32 tag, std::vector &v, CBitMemStream &bits) { uint32 croppedDelta; switch(tag) { case Rel0: bits.serial(croppedDelta, _Format.Rel0NumBits); extendSign(_Format.Rel0NumBits, croppedDelta); v[_ReadIndex] = (uint16) (v[_ReadIndex - 1] + (sint16) croppedDelta); break; case Rel1: bits.serial(croppedDelta, _Format.Rel1NumBits); extendSign(_Format.Rel1NumBits, croppedDelta); v[_ReadIndex] = (uint16) (v[_ReadIndex - 1] + (sint16) croppedDelta); break; case Rel2: bits.serial(croppedDelta, _Format.Rel2NumBits); extendSign(_Format.Rel2NumBits, croppedDelta); v[_ReadIndex] = (uint16) (v[_ReadIndex - 1] + (sint16) croppedDelta); break; default: nlassert(0); break; } //nlwarning("reading simple delta %d (tag = %d)", (int) (v[_ReadIndex] - v[_ReadIndex - 1]), (int) tag); ++ _ReadIndex; } // ******************************************************************************************** void CVectorPacker::flush(CBitMemStream &bits) { if (_LastDeltas.empty()) return; if (_LastDeltas.size() > 4) { // put a 'repeat tag' uint32 repeatTag = AbsOrRLE; bits.serial(repeatTag, 2); uint32 isRLE = 1; bits.serial(isRLE, 1); bits.serial(_LastTag, 2); nlassert(_LastDeltas.size() <= 255); uint8 length = (uint8)_LastDeltas.size(); //nlwarning("begin RLE, length = %d, tag = %d", (int) length, (int) repeatTag); bits.serial(length); for(uint k = 0; k < _LastDeltas.size(); ++k) { writeSimpleValue(bits, _LastDeltas[k]); } _LastDeltas.clear(); ++_Repeated[_LastTag]; } else { // flush separate values for(uint k = 0; k < _LastDeltas.size(); ++k) { //nlwarning("write single value tag %d", (int) _LastTag); bits.serial(_LastTag, 2); if (_LastTag == (uint32) AbsOrRLE) { uint32 isRLE = 0; bits.serial(isRLE, 1); } writeSimpleValue(bits, _LastDeltas[k]); } _LastDeltas.clear(); } } // ******************************************************************************************** void CVectorPacker::bufferizeDelta(uint32 tag, sint32 delta, CBitMemStream &bits) { if (tag != _LastTag || _LastDeltas.size() == 255) { flush(bits); _LastTag = tag; } _LastDeltas.push_back(delta); } // ******************************************************************************************** void CVectorPacker::serialPackedVector16(std::vector &v,NLMISC::IStream &f, const CFormat &format) { _Format = format; if (f.isReading()) { CBitMemStream bits(true); std::vector datas; f.serialCont(datas); bits.fill(&datas[0], (uint32)datas.size()); uint32 numValues = 0; bits.serial(numValues); v.resize(numValues); _ReadIndex = 0; while (_ReadIndex != numValues) { if (_ReadIndex == 0) { uint32 value; bits.serial(value, _Format.AbsNumBits); v[0] = (uint16) value; //nlwarning("v[0] = %d", (int) v[0]); ++ _ReadIndex; } else { uint32 tag; bits.serial(tag, 2); //nlwarning("read tag %d", (int) tag); switch(tag) { case Rel0: case Rel1: case Rel2: readSimpleValue(tag, v, bits); break; case AbsOrRLE: { // serial one more bit to see if there's RLE here uint32 isRLE = 0; bits.serial(isRLE, 1); if (isRLE) { uint32 repeatTag; bits.serial(repeatTag, 2); uint8 length = 0; bits.serial(length); //nlwarning("begin RLE, length = %d", (int) length); for(uint l = 0; l < length; ++l) { switch(repeatTag) { case Rel0: case Rel1: case Rel2: readSimpleValue(repeatTag, v, bits); break; case AbsOrRLE: uint32 value; bits.serial(value, _Format.AbsNumBits); v[_ReadIndex] = (uint16) value; //nlwarning("reading abs 16 value : %d", (int) v[_ReadIndex]); ++ _ReadIndex; break; default: throw NLMISC::EInvalidDataStream(); break; } } } else { // absolute value uint32 value = 0; bits.serial(value, _Format.AbsNumBits); v[_ReadIndex] = (uint16) value; //nlwarning("reading abs 16 value : %d", (int) v[_ReadIndex]); ++ _ReadIndex; } } break; } } } } else { _Count[Rel0] = 0; _Count[Rel1] = 0; _Count[Rel2] = 0; _Count[AbsOrRLE] = 0; _Repeated[Rel0] = 0; _Repeated[Rel1] = 0; _Repeated[Rel2] = 0; _Repeated[AbsOrRLE] = 0; // CBitMemStream bits(false); uint32 numValues = (uint32)v.size(); bits.serial(numValues); _LastTag = std::numeric_limits::max(); _LastDeltas.clear(); for(uint k = 0; k < v.size(); ++k) { if (k == 0) { uint32 value = v[0]; bits.serial(value, _Format.AbsNumBits); //nlwarning("v[0] = %d", (int) v[0]); } else { sint32 delta = v[k] - v[k - 1]; if (delta >= getMin(_Format.Rel0NumBits) && delta <= getMax(_Format.Rel0NumBits)) { bufferizeDelta(Rel0, delta, bits); } else if (delta >= getMin(_Format.Rel1NumBits) && delta <= getMax(_Format.Rel1NumBits)) { bufferizeDelta(Rel1, delta, bits); } else if (delta >= getMin(_Format.Rel2NumBits) && delta <= getMax(_Format.Rel2NumBits)) { bufferizeDelta(Rel2, delta, bits); } else { bufferizeDelta(AbsOrRLE, v[k], bits); // absolute value here ... } } } flush(bits); std::vector datas(bits.buffer(), bits.buffer() + bits.length()); f.serialCont(datas); /* if (_Count[Rel0] != 0 || _Count[Rel1] != 0 || _Count[Rel2] != 0 || _Count[AbsOrRLE] != 0) { nlwarning("count0 = %d", _Count[Rel0]); nlwarning("count1 = %d", _Count[Rel1]); nlwarning("count2 = %d", _Count[Rel2]); nlwarning("countAbs = %d", _Count[AbsOrRLE]); nlwarning("*"); nlwarning("repeat 0 = %d", _Repeated[Rel0]); nlwarning("repeat 1 = %d", _Repeated[Rel1]); nlwarning("repeat 2 = %d", _Repeated[Rel2]); nlwarning("repeat Abs = %d", _Repeated[AbsOrRLE]); nlwarning("*"); } */ } } // helper function : serialize a uint16 vector void serialPackedVector16(std::vector &v, NLMISC::IStream &f) { CVectorPacker packer; CVectorPacker::CFormat format; format.Rel0NumBits = 2; format.Rel1NumBits = 4; format.Rel2NumBits = 8; format.AbsNumBits = 16; packer.serialPackedVector16(v, f, format); /* if (!v.empty()) { nlwarning("*"); uint num = std::min((uint) v.size(), (uint) 1000); for(uint k = 0; k < num; ++k) { nlwarning("[%d] = %d", (int) k, (int) v[k]); } nlwarning("*"); } */ } void serialPackedVector12(std::vector &v, NLMISC::IStream &f) { CVectorPacker packer; CVectorPacker::CFormat format; format.Rel0NumBits = 2; format.Rel1NumBits = 6; format.Rel2NumBits = 9; format.AbsNumBits = 12; packer.serialPackedVector16(v, f, format); /* if (!v.empty()) { nlwarning("*"); uint num = std::min((uint) v.size(), (uint) 1000); for(uint k = 0; k < num; ++k) { nlwarning("[%d] = %d", (int) k, (int) v[k]); } nlwarning("*"); } */ } // some function to ease writing of some primitives into a vertex buffer static inline void pushVBLine2D(NLMISC::CVector *&dest, const NLMISC::CVector &v0, const NLMISC::CVector &v1) { dest->x = v0.x; dest->y = v0.y; dest->z = -0.5f; ++ dest; dest->x = v1.x; dest->y = v1.y; dest->z = -0.5f; ++ dest; dest->x = v0.x; dest->y = v0.y; dest->z = -0.5f; ++ dest; } static inline void pushVBTri2D(NLMISC::CVector *&dest, const NLMISC::CTriangle &tri) { dest->x = tri.V0.x; dest->y = tri.V0.y; dest->z = -0.5f; ++ dest; dest->x = tri.V1.x; dest->y = tri.V1.y; dest->z = -0.5f; ++ dest; dest->x = tri.V2.x; dest->y = tri.V2.y; dest->z = -0.5f; ++ dest; } static inline void pushVBQuad2D(NLMISC::CVector *&dest, const NLMISC::CQuad &quad) { dest->x = quad.V0.x; dest->y = quad.V0.y; dest->z = -0.5f; ++ dest; dest->x = quad.V1.x; dest->y = quad.V1.y; dest->z = -0.5f; ++ dest; dest->x = quad.V2.x; dest->y = quad.V2.y; dest->z = -0.5f; ++ dest; dest->x = quad.V3.x; dest->y = quad.V3.y; dest->z = -0.5f; ++ dest; } static inline void pushVBQuad(NLMISC::CVector *&dest, const NLMISC::CQuad &quad) { *dest++ = quad.V0; *dest++ = quad.V1; *dest++ = quad.V2; *dest++ = quad.V3; } // compute rasters union. static void computeRastersUnion(const CPolygon2D::TRasterVect &inRaster0, CPolygon2D::TRasterVect &inRaster1, sint minY0, sint minY1, CPolygon2D::TRasterVect &outRaster, sint &finalMinY) { if (inRaster0.empty()) { if (inRaster1.empty()) { outRaster.empty(); finalMinY = -1; return; } outRaster = inRaster1; finalMinY = minY1; return; } else if (inRaster1.empty()) { outRaster = inRaster0; finalMinY = minY0; return; } nlassert(&outRaster != &inRaster0); nlassert(&outRaster != &inRaster1); finalMinY = std::min(minY0, minY1); sint maxY = std::max(minY0 + (sint) inRaster0.size(), minY1 + (sint) inRaster1.size()); outRaster.resize(maxY - finalMinY); for(sint y = 0; y < (sint) outRaster.size(); ++y) { outRaster[y].first = INT_MAX; outRaster[y].second = INT_MIN; sint raster0Y = y + finalMinY - minY0; if (raster0Y >= 0 && raster0Y < (sint) inRaster0.size()) { //if (inRaster0[raster0Y].second >= inRaster0[raster0Y].first) { outRaster[y].first = std::min(outRaster[y].first, inRaster0[raster0Y].first); outRaster[y].second = std::max(outRaster[y].second, inRaster0[raster0Y].second); } } sint raster1Y = y + finalMinY - minY1; if (raster1Y >= 0 && raster1Y < (sint) inRaster1.size()) { //if (inRaster1[raster1Y].second >= inRaster1[raster1Y].first) { outRaster[y].first = std::min(outRaster[y].first, inRaster1[raster1Y].first); outRaster[y].second = std::max(outRaster[y].second, inRaster1[raster1Y].second); } } } } static void addQuadToSilhouette(const CVector &v0, const CVector &v1, const CVector &v2, CVector &v3, CPolygon2D::TRasterVect &sil, sint &minY, float cellSize) { static CPolygon2D quad; quad.Vertices.resize(4); quad.Vertices[0] = v0 / cellSize; quad.Vertices[1] = v1 / cellSize; quad.Vertices[2] = v2 / cellSize; quad.Vertices[3] = v3 / cellSize ; // static CPolygon2D::TRasterVect newQuad; static CPolygon2D::TRasterVect result; sint newMinY = -1; sint resultMinY; quad.computeOuterBorders(newQuad, newMinY); computeRastersUnion(sil, newQuad, minY, newMinY, result, resultMinY); minY = resultMinY; sil.swap(result); } uint16 CPackedZone16::UndefIndex = 0xffff; // *************************************************************************************** CPackedZone16::CPackedZone16() { CellSize = 0.f; _Origin = CVector::Null; } // *************************************************************************************** void CPackedZone32::unpackTri(const CPackedTri &src, CVector dest[3]) const { // TODO: add 'multiply-add' operator dest[0].set(Verts[src.V0].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V0].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V0].Z * _PackedLocalToWorld.z + _Origin.z); dest[1].set(Verts[src.V1].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V1].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V1].Z * _PackedLocalToWorld.z + _Origin.z); dest[2].set(Verts[src.V2].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V2].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V2].Z * _PackedLocalToWorld.z + _Origin.z); } uint32 CPackedZone32::UndefIndex = 0xffffffff; // *************************************************************************************** CPackedZone32::CPackedZone32() { CellSize = 0; _Origin = CVector::Null; } // *************************************************************************************** void CPackedZone32::serial(NLMISC::IStream &f) throw (NLMISC::EStream) { f.serialVersion(0); f.serial(Box); f.serialCont(Verts); f.serialCont(Tris); f.serialCont(TriLists); f.serial(Grid); f.serial(CellSize); f.serial(_Origin); f.serial(_WorldToLocal); f.serial(ZoneX); f.serial(ZoneY); if (f.isReading()) { _PackedLocalToWorld.set(1.f / (_WorldToLocal.x * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.y * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.z * (float) PACKED_COL_RANGE)); } } // used by CPackedZone::build struct CZoneInstance { const CShapeInfo *SI; CMatrix WorldMat; }; // *************************************************************************************** void CPackedZone32::build(std::vector &leaves, float cellSize, std::vector igs, const TShapeCache &shapeCache, const NLMISC::CAABBox &baseZoneBBox, sint32 zoneX, sint32 zoneY ) { nlassert(cellSize > 0); Verts.clear(); Tris.clear(); TriLists.clear(); Grid.clear(); if (leaves.empty()) return; for(uint k = 0; k < leaves.size(); ++k) { if (k == 0) { Box.setMinMax(leaves[k]->VBase->EndPos, leaves[k]->VBase->EndPos); Box.extend(leaves[k]->VLeft->EndPos); Box.extend(leaves[k]->VRight->EndPos); } else { Box.extend(leaves[k]->VBase->EndPos); Box.extend(leaves[k]->VLeft->EndPos); Box.extend(leaves[k]->VRight->EndPos); } } CAABBox landBBox = Box; landBBox.extend(baseZoneBBox.getMin()); landBBox.extend(baseZoneBBox.getMax()); // list of instances that contribute to that zone std::vector instances; // extends with instances that intersect the land bbox with respect to x/y for(uint k = 0; k < igs.size(); ++k) { if (!igs[k]) continue; for(uint l = 0; l < igs[k]->getNumInstance(); ++l) { CMatrix instanceMatrix; igs[k]->getInstanceMatrix(l, instanceMatrix); if (NLMISC::toLower(NLMISC::CFile::getExtension(igs[k]->getShapeName(l))) == "pacs_prim") continue; std::string stdShapeName = standardizeShapeName(igs[k]->getShapeName(l)); TShapeCache::const_iterator it = shapeCache.find(stdShapeName); if (it != shapeCache.end()) { CAABBox xformBBox = CAABBox::transformAABBox(instanceMatrix, it->second.LocalBBox); if (xformBBox.getMin().x < landBBox.getMax().x && xformBBox.getMin().y < landBBox.getMax().y && xformBBox.getMax().y >= landBBox.getMin().y && xformBBox.getMax().x >= landBBox.getMin().x) { Box.extend(xformBBox.getMin()); Box.extend(xformBBox.getMax()); CZoneInstance zi; zi.SI = &(it->second); zi.WorldMat = instanceMatrix; instances.push_back(zi); } } } } // /*float delta = 5.f; Box.extend(Box.getMin() + CVector(- delta, - delta, - delta)); Box.extend(Box.getMax() + CVector(delta, delta, delta));*/ // CVector cornerMin = Box.getMin(); CVector cornerMax = Box.getMax(); // uint width = (uint) ceilf((cornerMax.x - cornerMin.x) / cellSize); uint height = (uint) ceilf((cornerMax.y - cornerMin.y) / cellSize); float depth = cornerMax.z - cornerMin.z; if (width == 0 || height == 0) return; Grid.init(width, height, UndefIndex); // TVertexGrid vertexGrid; // grid for fast sharing of vertices TTriListGrid triListGrid; // grid for list of tris per grid cell (before packing in a single vector) vertexGrid.init(width, height); triListGrid.init(width, height); CellSize = cellSize; // _Origin = cornerMin; _WorldToLocal.x = 1.f / (width * cellSize); _WorldToLocal.y = 1.f / (height * cellSize); _WorldToLocal.z = depth != 0 ? 1.f / depth : 0.f; // for(uint k = 0; k < leaves.size(); ++k) { CTriangle tri; tri.V0 = leaves[k]->VBase->EndPos; tri.V1 = leaves[k]->VLeft->EndPos; tri.V2 = leaves[k]->VRight->EndPos; addTri(tri, vertexGrid, triListGrid); } // add each ig for(uint k = 0; k < instances.size(); ++k) { addInstance(*instances[k].SI, instances[k].WorldMat, vertexGrid, triListGrid); } // pack tri lists for (uint y = 0; y < height; ++y) { for (uint x = 0; x < width; ++x) { if (!triListGrid(x, y).empty()) { Grid(x, y) = (uint32)TriLists.size(); std::copy(triListGrid(x, y).begin(), triListGrid(x, y).end(), std::back_inserter(TriLists)); TriLists.push_back(UndefIndex); // mark the end of the list } } } // ZoneX = zoneX; ZoneX = zoneY; // _PackedLocalToWorld.set(1.f / (_WorldToLocal.x * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.y * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.z * (float) PACKED_COL_RANGE)); // reorder vertices by distance for better compression : start with first vertex then found closest vertex // /* std::vector triRemapping(Verts.size()); std::vector vertRemapping(Verts.size()); std::vector todo(Verts.size()); for(uint k = 0; k < Verts.size(); ++k) { todo[k] = k; } CPackedVertex lastPos; for(uint k = 0; k < Verts.size(); ++k) { // find vertex with closest dist uint best; if (k == 0) { best = 0; } else { uint32 bestDist = INT_MAX; for(uint l = 0; l < todo.size(); ++l) { uint32 dist = (uint32) abs((sint32) Verts[todo[l]].X - (sint32) lastPos.X) + (uint32) abs((sint32) Verts[todo[l]].Y - (sint32) lastPos.Y) + (uint32) abs((sint32) Verts[todo[l]].Z - (sint32) lastPos.Z); if (dist < bestDist) { bestDist = dist; best = l; } } } lastPos = Verts[todo[best]]; vertRemapping[k] = todo[best]; triRemapping[todo[best]] = k; todo[best] = todo[todo.size() - 1]; todo.pop_back(); } // remap all tris for(uint k = 0; k < Tris.size(); ++k) { Tris[k].V0 = triRemapping[Tris[k].V0]; Tris[k].V1 = triRemapping[Tris[k].V1]; Tris[k].V2 = triRemapping[Tris[k].V2]; } // reorder vertices std::vector remappedVerts(Verts.size()); for(uint k = 0; k < Verts.size(); ++k) { remappedVerts[k] = Verts[vertRemapping[k]]; } Verts.swap(remappedVerts); ///////////////////////////////////////////////////////////////////// // reorder tris triRemapping.resize(Tris.size()); std::vector triListRemapping(Tris.size()); // todo.resize(Tris.size()); for(uint k = 0; k < Tris.size(); ++k) { todo[k] = k; } uint32 lastIndex = 0; for(uint k = 0; k < Tris.size(); ++k) { uint32 best = 0; uint32 bestScore = INT_MAX; for(uint l = 0; l < todo.size(); ++l) { uint32 score = abs(Tris[todo[l]].V0 - lastIndex) + abs(Tris[todo[l]].V1 - Tris[todo[l]].V0) + abs(Tris[todo[l]].V2 - Tris[todo[l]].V1); if (score < bestScore) { bestScore = score; best = l; } } lastIndex = Tris[todo[best]].V2; triRemapping[k] = todo[best]; triListRemapping[todo[best]] = k; todo[best] = todo[todo.size() - 1]; todo.pop_back(); } // remap tri lists for(uint k = 0; k < TriLists.size(); ++k) { if (TriLists[k] != std::numeric_limits::max()) { TriLists[k] = triListRemapping[TriLists[k]]; } } // reorder tris std::vector remappedTris(Tris.size()); for(uint k = 0; k < Tris.size(); ++k) { remappedTris[k] = Tris[triRemapping[k]]; } Tris.swap(remappedTris); */ // reorder tri lists for better compression std::vector::iterator firstIt = TriLists.begin(); std::vector::iterator currIt = firstIt; std::vector::iterator lastIt = TriLists.end(); while (currIt != lastIt) { if (*currIt == UndefIndex) { std::sort(firstIt, currIt); ++ currIt; if (currIt == lastIt) break; firstIt = currIt; } else { ++ currIt; } } // /*int vertsSize = sizeof(CPackedVertex) * Verts.size(); int trisSize = sizeof(CPackedTri) * Tris.size(); int triListSize = sizeof(uint32) * TriLists.size(); int gridSize = sizeof(uint32) * Grid.getWidth() * Grid.getHeight(); */ /*printf("Verts Size = %d\n", vertsSize); printf("Tris Size = %d\n", trisSize); printf("Tri List Size = %d\n", triListSize); printf("Grid size = %d\n", gridSize); printf("Total = %d\n", vertsSize + trisSize + triListSize + gridSize);*/ } // *************************************************************************************** void CPackedZone32::addTri(const CTriangle &tri, TVertexGrid &vertexGrid, TTriListGrid &triListGrid) { CPackedTri pt; pt.V0 = allocVertex(tri.V0, vertexGrid); pt.V1 = allocVertex(tri.V1, vertexGrid); pt.V2 = allocVertex(tri.V2, vertexGrid); // static CPolygon2D::TRasterVect rasters; static CPolygon2D polyTri; sint minY; polyTri.Vertices.resize(3); // polyTri.Vertices[0].set((tri.V0.x - _Origin.x) / CellSize, (tri.V0.y - _Origin.y) / CellSize); polyTri.Vertices[1].set((tri.V1.x - _Origin.x) / CellSize, (tri.V1.y - _Origin.y) / CellSize); polyTri.Vertices[2].set((tri.V2.x - _Origin.x) / CellSize, (tri.V2.y - _Origin.y) / CellSize); // polyTri.computeOuterBorders(rasters, minY); if (rasters.empty()) return; Tris.push_back(pt); // for (sint y = 0; y < (sint) rasters.size(); ++y) { sint gridY = y + minY; if (gridY < 0) continue; if (gridY >= (sint) triListGrid.getHeight()) break; for (sint x = rasters[y].first; x <= rasters[y].second; ++x) { if (x < 0) continue; if (x >= (sint) triListGrid.getWidth()) break; triListGrid(x, gridY).push_back((uint32)Tris.size() - 1); } } } // *************************************************************************************** uint32 CPackedZone32::allocVertex(const CVector &src, TVertexGrid &vertexGrid) { CVector local((src.x - _Origin.x) * _WorldToLocal.x, (src.y - _Origin.y) * _WorldToLocal.y, (src.z - _Origin.z) * _WorldToLocal.z); sint x = (sint) (local.x * vertexGrid.getWidth()); sint y = (sint) (local.y * vertexGrid.getHeight()); if (x == (sint) vertexGrid.getWidth()) x = (sint) vertexGrid.getWidth() - 1; if (y == (sint) vertexGrid.getHeight()) y = (sint) vertexGrid.getHeight() - 1; // CPackedVertex pv; sint32 ix = (sint32) (local.x * PACKED_COL_RANGE); sint32 iy = (sint32) (local.y * PACKED_COL_RANGE); sint32 iz = (sint32) (local.z * PACKED_COL_RANGE); clamp(ix, 0, PACKED_COL_RANGE); clamp(iy, 0, PACKED_COL_RANGE); clamp(iz, 0, PACKED_COL_RANGE); pv.X = (uint16) ix; pv.Y = (uint16) iy; pv.Z = (uint16) iz; // std::list &vertList = vertexGrid(x, y); for(std::list::iterator it = vertexGrid(x, y).begin(); it != vertexGrid(x, y).end(); ++it) { if (Verts[*it] == pv) return *it; // exists already } // create a new vertex Verts.push_back(pv); vertList.push_front((uint32)Verts.size() - 1); return (uint32)Verts.size() - 1; } // *************************************************************************************** void CPackedZone32::render(CVertexBuffer &vb, IDriver &drv, CMaterial &material, CMaterial &wiredMaterial, const CMatrix &camMat, uint batchSize, const CVector localFrustCorners[8]) { if (Tris.empty()) return; IDriver::TPolygonMode oldPolygonMode = drv.getPolygonMode(); // CVector frustCorners[8]; for(uint k = 0; k < sizeofarray(frustCorners); ++k) { frustCorners[k] = camMat * localFrustCorners[k]; frustCorners[k].x -= _Origin.x; frustCorners[k].y -= _Origin.y; } // project frustum on x/y plane to see where to test polys sint minY = INT_MAX; static CPolygon2D::TRasterVect silhouette; silhouette.clear(); addQuadToSilhouette(frustCorners[0], frustCorners[1], frustCorners[2], frustCorners[3], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[1], frustCorners[5], frustCorners[6], frustCorners[2], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[4], frustCorners[5], frustCorners[6], frustCorners[7], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[0], frustCorners[4], frustCorners[7], frustCorners[3], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[0], frustCorners[1], frustCorners[5], frustCorners[4], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[3], frustCorners[7], frustCorners[6], frustCorners[2], silhouette, minY, CellSize); // { CVertexBufferReadWrite vba; vb.setNumVertices(batchSize * 3); vb.lock(vba); CVector *dest = vba.getVertexCoordPointer(0); const CVector *endDest = dest + batchSize * 3; for(sint y = 0; y < (sint) silhouette.size(); ++y) { sint gridY = y + minY; if (gridY < 0) continue; if (gridY >= (sint) Grid.getHeight()) continue; sint minX = silhouette[y].first; sint maxX = silhouette[y].second; for (sint x = minX; x <= maxX; ++x) { if (x < 0) continue; if (x >= (sint) Grid.getWidth()) break; uint32 triRefIndex = Grid(x, gridY); if (triRefIndex == UndefIndex) continue; for (;;) { uint32 triIndex = TriLists[triRefIndex]; if (triIndex == UndefIndex) break; // end of list unpackTri(Tris[triIndex], dest); dest += 3; if (dest == endDest) { // flush batch vba.unlock(); drv.setPolygonMode(IDriver::Filled); drv.activeVertexBuffer(vb); drv.renderRawTriangles(material, 0, batchSize); drv.setPolygonMode(IDriver::Line); //drv.renderRawTriangles(wiredMaterial, 0, batchSize); // reclaim a new batch vb.setNumVertices(batchSize * 3); vb.lock(vba); dest = vba.getVertexCoordPointer(0); endDest = dest + batchSize * 3; } ++ triRefIndex; } } } vba.unlock(); uint numRemainingTris = batchSize - (uint)((endDest - dest) / 3); if (numRemainingTris) { drv.setPolygonMode(IDriver::Filled); drv.activeVertexBuffer(vb); drv.renderRawTriangles(material, 0, numRemainingTris); drv.setPolygonMode(IDriver::Line); //drv.renderRawTriangles(wiredMaterial, 0, numRemainingTris); } } drv.setPolygonMode(oldPolygonMode); } // *************************************************************************************** void CPackedZone32::addInstance(const CShapeInfo &si, const NLMISC::CMatrix &matrix, TVertexGrid &vertexGrid, TTriListGrid &triListGrid) { for(uint k = 0; k < si.Tris.size(); ++k) { CTriangle worldTri; si.Tris[k].applyMatrix(matrix, worldTri); addTri(worldTri, vertexGrid, triListGrid); } } // *************************************************************************************** CSmartPtr CPackedZone32::buildPackedZone16() { if (Verts.size() > 65535) return NULL; if (Tris.size() > 65534) return NULL; // NB : not 65534 here because -1 is used to mark the end of a list if (TriLists.size() > 65534) return NULL; // can convert CSmartPtr dest = new CPackedZone16; dest->Box = Box; dest->Verts = Verts; dest->Tris.resize(Tris.size()); for(uint k = 0; k < Tris.size(); ++k) { dest->Tris[k].V0 = (uint16) Tris[k].V0; dest->Tris[k].V1 = (uint16) Tris[k].V1; dest->Tris[k].V2 = (uint16) Tris[k].V2; } dest->TriLists.resize(TriLists.size()); for(uint k = 0; k < TriLists.size(); ++k) { dest->TriLists[k] = (uint16) TriLists[k]; } dest->CellSize = CellSize; dest->Grid.init(Grid.getWidth(), Grid.getHeight()); for (sint y = 0; y < (sint) Grid.getHeight(); ++y) { for (sint x = 0; x < (sint) Grid.getWidth(); ++x) { dest->Grid(x, y) = (uint16) Grid(x, y); } } dest->_Origin = _Origin; dest->_WorldToLocal = _WorldToLocal; dest->ZoneX = ZoneX; dest->ZoneY = ZoneY; dest->_PackedLocalToWorld.set(1.f / (dest->_WorldToLocal.x * (float) PACKED_COL_RANGE), 1.f / (dest->_WorldToLocal.y * (float) PACKED_COL_RANGE), 1.f / (dest->_WorldToLocal.z * (float) PACKED_COL_RANGE)); return dest; } // *************************************************************************************** void CPackedZone16::serial(NLMISC::IStream &f) throw (NLMISC::EStream) { f.serialVersion(0); f.serial(Box); std::vector datas; if (f.isReading()) { // vertices serialPackedVector12(datas, f); Verts.resize(datas.size() / 3); for(uint k = 0; k < Verts.size(); ++k) { Verts[k].X = datas[k]; Verts[k].Y = datas[Verts.size() + k]; Verts[k].Z = datas[2 * Verts.size() + k]; } // triangles serialPackedVector16(datas, f); Tris.resize(datas.size() / 3); for(uint k = 0; k < Tris.size(); ++k) { Tris[k].V0 = datas[3 * k]; Tris[k].V1 = datas[3 * k + 1]; Tris[k].V2 = datas[3 * k + 2]; } // tris list serialPackedVector16(TriLists, f); // grid uint16 width = (uint16) Grid.getWidth(); uint16 height = (uint16) Grid.getHeight(); f.serial(width, height); Grid.init(width, height); serialPackedVector16(datas, f); if ((uint) datas.size() != (uint) width * (uint) height) { throw NLMISC::EInvalidDataStream(); } std::copy(datas.begin(), datas.end(), Grid.begin()); } else { // vertices datas.resize(Verts.size() * 3); for(uint k = 0; k < Verts.size(); ++k) { datas[k] = Verts[k].X; datas[Verts.size() + k ] = Verts[k].Y; datas[2 * Verts.size() + k] = Verts[k].Z; } //nlwarning("serial verts"); serialPackedVector12(datas, f); // triangles datas.resize(Tris.size() * 3); for(uint k = 0; k < Tris.size(); ++k) { datas[3 * k] = Tris[k].V0; datas[3 * k + 1] = Tris[k].V1; datas[3 * k + 2] = Tris[k].V2; } //nlwarning("serial tris"); serialPackedVector16(datas, f); // tris list //nlwarning("serial tri lists"); serialPackedVector16(TriLists, f); // grid uint16 width = (uint16) Grid.getWidth(); uint16 height = (uint16) Grid.getHeight(); f.serial(width, height); datas.resize((uint) width * (uint) height); std::copy(Grid.begin(), Grid.end(), datas.begin()); //nlwarning("serial grid"); serialPackedVector16(datas, f); } f.serial(CellSize); f.serial(_Origin); f.serial(_WorldToLocal); f.serial(ZoneX); f.serial(ZoneY); if (f.isReading()) { _PackedLocalToWorld.set(1.f / (_WorldToLocal.x * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.y * (float) PACKED_COL_RANGE), 1.f / (_WorldToLocal.z * (float) PACKED_COL_RANGE)); } } // *************************************************************************************** void CPackedZone16::render(CVertexBuffer &vb, IDriver &drv, CMaterial &material, CMaterial &wiredMaterial, const CMatrix &camMat, uint batchSize, const CVector localFrustCorners[8]) { if (Tris.empty()) return; IDriver::TPolygonMode oldPolygonMode = drv.getPolygonMode(); CVector frustCorners[8]; for(uint k = 0; k < sizeofarray(frustCorners); ++k) { frustCorners[k] = camMat * localFrustCorners[k]; frustCorners[k].x -= _Origin.x; frustCorners[k].y -= _Origin.y; } // project frustum on x/y plane to see where to test polys sint minY = INT_MAX; static CPolygon2D::TRasterVect silhouette; silhouette.clear(); addQuadToSilhouette(frustCorners[0], frustCorners[1], frustCorners[2], frustCorners[3], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[1], frustCorners[5], frustCorners[6], frustCorners[2], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[4], frustCorners[5], frustCorners[6], frustCorners[7], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[0], frustCorners[4], frustCorners[7], frustCorners[3], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[0], frustCorners[1], frustCorners[5], frustCorners[4], silhouette, minY, CellSize); addQuadToSilhouette(frustCorners[3], frustCorners[7], frustCorners[6], frustCorners[2], silhouette, minY, CellSize); // { CVertexBufferReadWrite vba; vb.setNumVertices(batchSize * 3); vb.lock(vba); CVector *dest = vba.getVertexCoordPointer(0); const CVector *endDest = dest + batchSize * 3; for(sint y = 0; y < (sint) silhouette.size(); ++y) { sint gridY = y + minY; if (gridY < 0) continue; if (gridY >= (sint) Grid.getHeight()) continue; sint minX = silhouette[y].first; sint maxX = silhouette[y].second; for (sint x = minX; x <= maxX; ++x) { if (x < 0) continue; if (x >= (sint) Grid.getWidth()) break; uint32 triRefIndex = Grid(x, gridY); if (triRefIndex == UndefIndex) continue; for (;;) { uint16 triIndex = TriLists[triRefIndex]; if (triIndex == UndefIndex) break; // end of list unpackTri(Tris[triIndex], dest); dest += 3; if (dest == endDest) { // flush batch vba.unlock(); drv.setPolygonMode(IDriver::Filled); drv.activeVertexBuffer(vb); drv.renderRawTriangles(material, 0, batchSize); drv.setPolygonMode(IDriver::Line); //drv.renderRawTriangles(wiredMaterial, 0, batchSize); // reclaim a new batch vb.setNumVertices(batchSize * 3); vb.lock(vba); dest = vba.getVertexCoordPointer(0); endDest = dest + batchSize * 3; } ++ triRefIndex; } } } vba.unlock(); uint numRemainingTris = batchSize - (uint)((endDest - dest) / 3); if (numRemainingTris) { drv.setPolygonMode(IDriver::Filled); drv.activeVertexBuffer(vb); drv.renderRawTriangles(material, 0, numRemainingTris); drv.setPolygonMode(IDriver::Line); //drv.renderRawTriangles(wiredMaterial, 0, numRemainingTris); } } drv.setPolygonMode(oldPolygonMode); } // *************************************************************************************** void CPackedZone16::unpackTri(const CPackedTri16 &src, CVector dest[3]) const { // yes this is ugly code duplication of CPackedZone16::unpackTri but this code is temporary anyway... // TODO: add 'multiply-add' operator dest[0].set(Verts[src.V0].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V0].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V0].Z * _PackedLocalToWorld.z + _Origin.z); dest[1].set(Verts[src.V1].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V1].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V1].Z * _PackedLocalToWorld.z + _Origin.z); dest[2].set(Verts[src.V2].X * _PackedLocalToWorld.x + _Origin.x, Verts[src.V2].Y * _PackedLocalToWorld.y + _Origin.y, Verts[src.V2].Z * _PackedLocalToWorld.z + _Origin.z); } // raytrace code, common to CPackedZone32 & CPackedZone16 template bool raytrace(T &packedZone, const NLMISC::CVector &start, const NLMISC::CVector &end, NLMISC::CVector &inter, std::vector *testedTriangles = NULL, NLMISC::CVector *normal = NULL) { if (packedZone.Grid.empty()) return false; CVector2f start2f((start.x - packedZone.Box.getMin().x) / packedZone.CellSize, (start.y - packedZone.Box.getMin().y) / packedZone.CellSize); CVector2f dir2f((end.x - start.x) / packedZone.CellSize, (end.y - start.y) / packedZone.CellSize); sint x, y; CGridTraversal::startTraverse(start2f, x, y); do { if (x < 0) continue; if (x >= (sint) packedZone.Grid.getWidth()) continue; if (y < 0) continue; if (y >= (sint) packedZone.Grid.getHeight()) continue; typename T::TIndexType triListIndex = packedZone.Grid(x, y); if (triListIndex != T::UndefIndex) { CTriangle tri; CPlane triPlane; float bestInterDist = FLT_MAX; CVector bestNormal(0.f, 0.f, 0.f); CVector currInter; do { packedZone.unpackTri(packedZone.Tris[packedZone.TriLists[triListIndex]], &tri.V0); if (testedTriangles) { testedTriangles->push_back(tri); } triPlane.make(tri.V0, tri.V1, tri.V2); if (tri.intersect(start, end, currInter, triPlane)) { float dist = (currInter - start).norm(); if (dist < bestInterDist) { bestInterDist = dist; inter = currInter; bestNormal.set(triPlane.a, triPlane.b, triPlane.c); } } ++ triListIndex; } while (packedZone.TriLists[triListIndex] != T::UndefIndex); if (bestInterDist != FLT_MAX) { if (normal) { *normal = bestNormal.normed(); } return true; } } } while(CGridTraversal::traverse(start2f, dir2f, x, y)); return false; } // *************************************************************************************** bool CPackedZone32::raytrace(const NLMISC::CVector &start, const NLMISC::CVector &end, NLMISC::CVector &inter, std::vector *testedTriangles /*= NULL*/, NLMISC::CVector *normal /*= NULL*/) const { return NL3D::raytrace(*this, start, end, inter, testedTriangles, normal); } // *************************************************************************************** bool CPackedZone16::raytrace(const NLMISC::CVector &start, const NLMISC::CVector &end, NLMISC::CVector &inter, std::vector *testedTriangles /*= NULL*/, NLMISC::CVector *normal /*= NULL*/) const { return NL3D::raytrace(*this, start, end, inter, testedTriangles, normal); } // *************************************************************************************** void CPackedZone16::appendSelection(const NLMISC::CPolygon2D &poly, std::vector &selectedTriangles) const { // compute covered zones NLMISC::CPolygon2D localPoly = poly; for (uint k = 0; k < localPoly.Vertices.size(); ++k) { localPoly.Vertices[k].x = (localPoly.Vertices[k].x - Box.getMin().x) / CellSize; localPoly.Vertices[k].y = (localPoly.Vertices[k].y - Box.getMin().y) / CellSize; } NLMISC::CPolygon2D::TRasterVect borders; sint minY; localPoly.computeOuterBorders(borders, minY); CTriangle newTri; // std::vector done(Tris.size(), false); // avoid double insertion // for (sint y = minY; y < (sint) (minY + borders.size()); ++y) { if (y < 0 || y >= (sint) Grid.getHeight()) continue; for (sint x = borders[y - minY].first; x <= borders[y - minY].second; ++x) { if (x < 0 || x >= (sint) Grid.getWidth()) continue; { if (Grid(x, y) != UndefIndex) { uint16 currTriIndex = Grid(x, y); while (TriLists[currTriIndex] != UndefIndex) { if (!done[TriLists[currTriIndex]]) { unpackTri(Tris[TriLists[currTriIndex]], &newTri.V0); selectedTriangles.push_back(newTri); done[TriLists[currTriIndex]] = true; } ++ currTriIndex; } } } } } } // *************************************************************************************** void CPackedZone32::appendSelection(const NLMISC::CPolygon2D &poly, std::vector &selectedTriangles) const { // TODO nico : factorize with CPackedZone16::appendSelection selectedTriangles.clear(); // compute covered zones NLMISC::CPolygon2D localPoly = poly; for (uint k = 0; k < localPoly.Vertices.size(); ++k) { localPoly.Vertices[k].x = (localPoly.Vertices[k].x - Box.getMin().x) / CellSize; localPoly.Vertices[k].y = (localPoly.Vertices[k].y - Box.getMin().y) / CellSize; } NLMISC::CPolygon2D::TRasterVect borders; sint minY; localPoly.computeOuterBorders(borders, minY); CTriangle newTri; // std::vector done(Tris.size(), false); // avoid double insertion // for (sint y = minY; y < (sint) (minY + borders.size()); ++y) { if (y < 0 || y >= (sint) Grid.getHeight()) continue; for (sint x = borders[y - minY].first; x <= borders[y - minY].second; ++x) { if (x < 0 || x >= (sint) Grid.getWidth()) continue; { if (Grid(x, y) != UndefIndex) { uint32 currTriIndex = Grid(x, y); while (TriLists[currTriIndex] != UndefIndex) { if (!done[TriLists[currTriIndex]]) { unpackTri(Tris[TriLists[currTriIndex]], &newTri.V0); selectedTriangles.push_back(newTri); done[TriLists[currTriIndex]] = true; } ++ currTriIndex; } } } } } } } // NL3D