khanat-code-old/code/nel/src/3d/packed_zone.cpp

1431 lines
43 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// 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 <http://www.gnu.org/licenses/>.
#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 <limits>
#include <iterator>
//
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<uint16> &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<sint32> _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<uint16> &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<uint16> &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<uint16> &v,NLMISC::IStream &f, const CFormat &format)
{
_Format = format;
if (f.isReading())
{
CBitMemStream bits(true);
std::vector<uint8> 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<uint32>::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<uint8> 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<uint16> &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<uint16> &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<const CTessFace*> &leaves,
float cellSize,
std::vector<CInstanceGroup *> 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<CZoneInstance> 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<uint32> triRemapping(Verts.size());
std::vector<uint32> vertRemapping(Verts.size());
std::vector<uint32> 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<CPackedVertex> 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<uint32> 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<uint32>::max())
{
TriLists[k] = triListRemapping[TriLists[k]];
}
}
// reorder tris
std::vector<CPackedTri> 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<uint32>::iterator firstIt = TriLists.begin();
std::vector<uint32>::iterator currIt = firstIt;
std::vector<uint32>::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<uint32> &vertList = vertexGrid(x, y);
for(std::list<uint32>::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<CPackedZone16> 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<CPackedZone16> 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<uint16> 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 <class T> bool raytrace(T &packedZone, const NLMISC::CVector &start, const NLMISC::CVector &end, NLMISC::CVector &inter, std::vector<CTriangle> *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<CTriangle> *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<CTriangle> *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<NLMISC::CTriangle> &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<bool> 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<NLMISC::CTriangle> &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<bool> 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