Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 839fab41a5
Fetching contributors…

Cannot retrieve contributors at this time

4741 lines (3873 sloc) 118.286 kb
// This file is distributed under a BSD license. See LICENSE.txt for details.
#include "engine.hpp"
#include "genmesh.hpp"
#include "genminmesh.hpp"
#include "genmaterial.hpp"
#include "genscene.hpp"
#include "geneffect.hpp"
#include "genoverlay.hpp"
#include "kkriegergame.hpp"
#include "_start.hpp"
#include "rtmanager.hpp"
#include <xmmintrin.h>
#include <malloc.h>
#include "_gui.hpp"
#if sINTRO
#define _aligned_malloc(n,a) (::operator new(n))
#define _aligned_free(n) (::operator delete(n))
#endif
#if sPROFILE
#include "_util.hpp"
sMAKEZONE(PortalVis ,"PortalVis" ,0xff00ff00);
sMAKEZONE(PortalJob ,"PortalJob" ,0xff70b040);
sMAKEZONE(ShadowJob ,"ShadowJob" ,0xff800000);
sMAKEZONE(UpdatePlanes,"UpdatePlanes" ,0xff902020);
sMAKEZONE(BuildJobs ,"BuildJobs" ,0xff5f2297);
sMAKEZONE(SortJobs ,"SortJobs" ,0xffe4ab3b);
sMAKEZONE(RenderJobs ,"RenderJobs" ,0xff7ade53);
sMAKEZONE(InstancePrg ,"InstancePrg" ,0xff3e9024);
sMAKEZONE(EvalBones ,"EvalBones" ,0xffef23ff);
sMAKEZONE(VertCopy ,"VertCopy" ,0xff2090ee);
sMAKEZONE(VCopyStencil,"VCopyStencil" ,0xff18406e);
#endif
/****************************************************************************/
// Fixed limits, increase as required
#ifdef KKRIEGER
#define MAXLIGHT 4 // number of *active* lights in scene
#else
#define MAXLIGHT 16 // this affects render pass sorting!!
#endif
#define MAXBATCH 16 // number of batches per job
#define MAXSVCACHE 8 // sv caches/job.
#define MAXENGMEM 0x60000 // memory used internally by engine
// Feature toggles for intro
#define BONES !sINTRO
#define THICKLINES 1
#define SHADOWS !sINTRO
/****************************************************************************/
// helper functions
static sInt ConvertRGB(sF32 value)
{
return sRange<sInt>(sFtol(value * 255.0f),255,0);
}
static sInt ConvertXYZ(sF32 value)
{
return sRange<sInt>(sFtol((value + 1.0f) * 127.5f),255,0);
}
static sU32 PackColor(const sVector *color)
{
return (ConvertRGB(color->x) << 16) | (ConvertRGB(color->y) << 8)
| (ConvertRGB(color->z) << 0) | (ConvertRGB(color->w) << 24);
}
static sU32 PackVector(const sVector *vector)
{
return (ConvertXYZ(vector->x) << 16) | (ConvertXYZ(vector->y) << 8)
| (ConvertXYZ(vector->z) << 0) | (ConvertXYZ(vector->w) << 24);
}
static void UnpackVector(sU32 packed,sVector &unpacked)
{
unpacked.x = ((packed >> 16) & 0xff) / 127.5f - 1.0f;
unpacked.y = ((packed >> 8) & 0xff) / 127.5f - 1.0f;
unpacked.z = ((packed >> 0) & 0xff) / 127.5f - 1.0f;
unpacked.w = ((packed >> 24) & 0xff) / 127.5f - 1.0f;
}
/****************************************************************************/
struct EngMesh::SilEdge // silhouette extraction edge
{
sInt Vert[2]; // vertex index
sInt Face[2]; // face (plane) indices
};
/****************************************************************************/
struct EngMesh::SVCache
{
sVector LightPos;
sF32 LightRange;
sInt LightId;
sInt *IndexBuffer; // index buffer for sil edges
sInt SilIndices; // # of silhouette indices
sInt CapIndices; // # of cap indices
void Init();
void Exit();
};
void EngMesh::SVCache::Init()
{
LightId = 0;
IndexBuffer = 0;
SilIndices = 0;
CapIndices = 0;
}
void EngMesh::SVCache::Exit()
{
delete[] IndexBuffer;
}
/****************************************************************************/
struct EngMesh::Job // painting is done in jobs
{
sInt MtrlId; // material id for this job
sInt Program; // (finalizer) program
sInt Geometry; // handle of geometry object
sInt VertexCount; // # of vertices
sInt IndexCount; // # of indices in buffer
sInt EdgeCount; // # of (silhouette) edges
sInt PlaneCount; // # of planes
sInt PartCount; // # of convex parts
sInt *VertexBuffer; // vertices (refs into main VB)
sInt *IndexBuffer; // indices.
sInt *FaceMap; // for SV caps
SilEdge *Edges; // for silhouette extraction
sVector *Planes; // for silhouette extraction
sInt *FacePartEnd; // to cull groups of faces
sInt *EdgePartEnd; // to cull groups of edges
sInt AnimType; // ENGANIM_*
sInt AnimMatrix; // for "rigid" type
sAABox BBox; // bounding box
EngMesh::SVCache SVCache[MAXSVCACHE]; // shadow volume caches.
void Init();
void Alloc(sInt vc,sInt ic,sInt ec,sInt fmc,sInt pc=0);
void Exit();
void OptimizeIndices(); // optimize for cache coherence
void ReIndexVertices(); // orders vertices sequentially
void UpdatePlanes(sVector *target,const sVertexTSpace3Big *verts);
EngMesh::SVCache *GetSVCache(sInt lightId);
};
void EngMesh::Job::Init()
{
MtrlId = 0;
Program = MPP_OFF;
Geometry = sINVALID;
VertexCount = 0;
IndexCount = 0;
EdgeCount = 0;
PlaneCount = 0;
PartCount = 0;
VertexBuffer = 0;
IndexBuffer = 0;
FaceMap = 0;
Edges = 0;
Planes = 0;
FacePartEnd = 0;
EdgePartEnd = 0;
AnimType = 0;
AnimMatrix = -1;
for(sInt i=0;i<MAXSVCACHE;i++)
SVCache[i].Init();
}
void EngMesh::Job::Alloc(sInt vc,sInt ic,sInt ec,sInt fmc,sInt pc)
{
sVERIFY(Geometry == sINVALID);
VertexCount = vc;
IndexCount = ic;
EdgeCount = ec;
PartCount = pc;
VertexBuffer = new sInt[vc];
IndexBuffer = new sInt[ic];
FaceMap = new sInt[fmc];
Edges = new EngMesh::SilEdge[ec];
Planes = 0;
FacePartEnd = new sInt[pc];
EdgePartEnd = new sInt[pc];
}
void EngMesh::Job::Exit()
{
if(Geometry != sINVALID)
sSystem->GeoRem(Geometry);
delete[] VertexBuffer;
delete[] IndexBuffer;
delete[] FaceMap;
delete[] Edges;
delete[] Planes;
delete[] FacePartEnd;
delete[] EdgePartEnd;
for(sInt i=0;i<MAXSVCACHE;i++)
SVCache[i].Exit();
}
/****************************************************************************/
struct VCacheVert
{
sInt CachePos; // its position in the cache (-1 if not in)
sInt Score; // its score (higher=better)
sInt TrisLeft; // # of not-yet-used tris
sInt *TriList; // list of triangle indices
sInt OpenPos; // position in "open vertex" list
};
struct VCacheTri
{
sInt Score; // current score (-1 if already done)
sInt Inds[3]; // vertex indices
};
static void DumpCacheEfficiency(const sInt *inds,sInt indCount)
{
static const sInt cacheSize = 24;
static const sBool isFIFO = sTRUE;
sInt misses = 0;
sInt cache[cacheSize+1];
sInt wrPos = 0;
if(!indCount)
return;
// initialize cache (we simulate a FIFO here)
for(sInt i=0;i<cacheSize;i++)
cache[i] = -1;
// simulate
for(sInt i=0;i<indCount;i++)
{
sInt ind = inds[i];
cache[cacheSize] = ind;
// find in cache
sInt cachePos;
for(cachePos=0;cache[cachePos] != inds[i];cachePos++);
misses += cachePos == cacheSize;
if(isFIFO)
{
cache[wrPos] = ind;
if(++wrPos == cacheSize)
wrPos = 0;
}
else
{
// move to front
for(sInt j=cachePos;j>0;j--)
cache[j] = cache[j-1];
cache[0] = ind;
}
}
// print results
sF32 ACMR = misses * 3.0f / indCount;
sDPrintF("ACMR: %d.%03d (fifo: %s)\n",sInt(ACMR),sInt(ACMR*1000+0.5f)%1000,isFIFO ? "yes" : "no");
}
void EngMesh::Job::OptimizeIndices()
{
if(!IndexCount)
return;
// alloc+initialize vertices
VCacheVert *verts = new VCacheVert[VertexCount];
for(sInt i=0;i<VertexCount;i++)
{
verts[i].CachePos = -1;
verts[i].Score = 0;
verts[i].TrisLeft = 0;
verts[i].TriList = 0;
verts[i].OpenPos = -1;
}
// prepare triangles
sInt nTris = IndexCount/3;
VCacheTri *tris = new VCacheTri[nTris];
sInt *indPtr = IndexBuffer;
for(sInt i=0;i<nTris;i++)
{
tris[i].Score = 0;
for(sInt j=0;j<3;j++)
{
sInt ind = *indPtr++;
tris[i].Inds[j] = ind;
verts[ind].TrisLeft++;
}
}
// alloc space for vert->tri indices
sInt *vertTriInd = new sInt[nTris*3];
sInt *vertTriPtr = vertTriInd;
for(sInt i=0;i<VertexCount;i++)
{
verts[i].TriList = vertTriPtr;
vertTriPtr += verts[i].TrisLeft;
verts[i].TrisLeft = 0;
}
// make vert->tri tables
for(sInt i=0;i<nTris;i++)
{
for(sInt j=0;j<3;j++)
{
sInt ind = tris[i].Inds[j];
verts[ind].TriList[verts[ind].TrisLeft] = i;
verts[ind].TrisLeft++;
}
}
// open vertices
sInt *openVerts = new sInt[VertexCount];
sInt openCount = 0;
// the cache
static const sInt cacheSize = 32;
static const sInt maxValence = 15;
sInt cache[cacheSize+3];
sInt pos2Score[cacheSize];
sInt val2Score[maxValence+1];
for(sInt i=0;i<cacheSize+3;i++)
cache[i] = -1;
for(sInt i=0;i<cacheSize;i++)
{
sF32 score = (i<3) ? 0.75f : sFPow(1.0f - (i-3)/(cacheSize-3),1.5f);
pos2Score[i] = score * 65536.0f + 0.5f;
}
val2Score[0] = 0;
for(sInt i=1;i<16;i++)
{
sF32 score = 2.0f * sFInvSqrt(i);
val2Score[i] = score * 65536.0f + 0.5f;
}
// outer loop: find triangle to start with
indPtr = IndexBuffer;
sInt seedPos = 0;
while(1)
{
sInt seedScore = -1;
sInt seedTri = -1;
// if there are open vertices, search them for the seed triangle
// which maximum score.
for(sInt i=0;i<openCount;i++)
{
VCacheVert *vert = &verts[openVerts[i]];
for(sInt j=0;j<vert->TrisLeft;j++)
{
sInt triInd = vert->TriList[j];
VCacheTri *tri = &tris[triInd];
if(tri->Score > seedScore)
{
seedScore = tri->Score;
seedTri = triInd;
}
}
}
// if we haven't found a seed triangle yet, there are no open
// vertices and we can pick any triangle
if(seedTri == -1)
{
while(seedPos < nTris && tris[seedPos].Score<0)
seedPos++;
if(seedPos == nTris) // no triangle left, we're done!
break;
seedTri = seedPos;
}
// the main loop.
sInt bestTriInd = seedTri;
while(bestTriInd != -1)
{
VCacheTri *bestTri = &tris[bestTriInd];
// mark this triangle as used, remove it from the "remaining tris"
// list of the vertices it uses, and add it to the index buffer.
bestTri->Score = -1;
for(sInt j=0;j<3;j++)
{
sInt vertInd = bestTri->Inds[j];
*indPtr++ = vertInd;
VCacheVert *vert = &verts[vertInd];
// find this triangles' entry
sInt k = 0;
while(vert->TriList[k] != bestTriInd)
{
sVERIFY(k < vert->TrisLeft);
k++;
}
// swap it to the end and decrement # of tris left
if(--vert->TrisLeft)
sSwap(vert->TriList[k],vert->TriList[vert->TrisLeft]);
else if(vert->OpenPos >= 0)
sSwap(openVerts[vert->OpenPos],openVerts[--openCount]);
}
// update cache status
cache[cacheSize] = cache[cacheSize+1] = cache[cacheSize+2] = -1;
for(sInt j=0;j<3;j++)
{
sInt ind = bestTri->Inds[j];
cache[cacheSize+2] = ind;
// find vertex index
sInt pos;
for(pos=0;cache[pos]!=ind;pos++);
// move to front
for(sInt k=pos;k>0;k--)
cache[k] = cache[k-1];
cache[0] = ind;
// remove sentinel if it wasn't used
if(pos!=cacheSize+2)
cache[cacheSize+2] = -1;
}
// update vertex scores
for(sInt i=0;i<cacheSize+3;i++)
{
sInt vertInd = cache[i];
if(vertInd == -1)
continue;
VCacheVert *vert = &verts[vertInd];
vert->Score = val2Score[sMin(vert->TrisLeft,maxValence)];
if(i < cacheSize)
{
vert->CachePos = i;
vert->Score += pos2Score[i];
}
else
vert->CachePos = -1;
// also add to open vertices list if the vertex is indeed open
if(vert->OpenPos<0 && vert->TrisLeft)
{
vert->OpenPos = openCount;
openVerts[openCount++] = vertInd;
}
}
// update triangle scores, find new best triangle
sInt bestTriScore = -1;
bestTriInd = -1;
for(sInt i=0;i<cacheSize;i++)
{
if(cache[i] == -1)
continue;
const VCacheVert *vert = &verts[cache[i]];
for(sInt j=0;j<vert->TrisLeft;j++)
{
sInt triInd = vert->TriList[j];
VCacheTri *tri = &tris[triInd];
sVERIFY(tri->Score != -1);
sInt score = 0;
for(sInt k=0;k<3;k++)
score += verts[tri->Inds[k]].Score;
tri->Score = score;
if(score > bestTriScore)
{
bestTriScore = score;
bestTriInd = triInd;
}
}
}
}
}
// cleanup
delete[] verts;
delete[] tris;
delete[] vertTriInd;
delete[] openVerts;
}
void EngMesh::Job::ReIndexVertices()
{
sInt *vertOrder = new sInt[VertexCount];
sInt *vertPos = new sInt[VertexCount];
// prepare tables
sCopyMem(vertOrder,VertexBuffer,sizeof(sInt) * VertexCount);
for(sInt i=0;i<VertexCount;i++)
vertPos[i] = -1;
// just go through indices in order, adding vertices as we go
sInt outCount = 0;
for(sInt i=0;i<IndexCount;i++)
{
sInt v = IndexBuffer[i];
sInt remap = vertPos[v];
if(remap == -1) // not yet seen
{
vertPos[v] = remap = outCount;
VertexBuffer[outCount++] = vertOrder[v];
}
IndexBuffer[i] = remap;
}
// add not yet used vertices at the back
for(sInt i=0;i<VertexCount;i++)
{
if(vertPos[i] == -1)
VertexBuffer[outCount++] = vertOrder[i];
}
delete[] vertOrder;
delete[] vertPos;
}
/****************************************************************************/
void EngMesh::Job::UpdatePlanes(sVector *target,const sVertexTSpace3Big *verts)
{
sZONE(UpdatePlanes);
sInt lastPlane = 0;
// this is somewhat inner-loopish, so you may consider optimizing
// it... the two levels of indirection may suck.
sVector *plane = target;
plane->Init(0,0,0,0);
plane++;
for(sInt f=0;f<IndexCount/3;f++)
{
if(FaceMap[f] != lastPlane)
{
sInt i = f*3;
const sVertexTSpace3Big *v0 = &verts[VertexBuffer[IndexBuffer[i+0] >> 1]];
const sVertexTSpace3Big *v1 = &verts[VertexBuffer[IndexBuffer[i+1] >> 1]];
const sVertexTSpace3Big *v2 = &verts[VertexBuffer[IndexBuffer[i+2] >> 1]];
// plane vectors
sF32 d1x = v1->x - v0->x;
sF32 d1y = v1->y - v0->y;
sF32 d1z = v1->z - v0->z;
sF32 d2x = v2->x - v0->x;
sF32 d2y = v2->y - v0->y;
sF32 d2z = v2->z - v0->z;
// normal
sF32 nx = d1y * d2z - d1z * d2y;
sF32 ny = d1z * d2x - d1x * d2z;
sF32 nz = d1x * d2y - d1y * d2x;
// don't need to normalize this, it's only used for sign tests
/*// normalize normal
sF32 len = nx*nx + ny*ny + nz*nz;
if(len)
{
len = sFInvSqrt(len);
nx *= len;
ny *= len;
nz *= len;
}*/
// store plane
plane->x = nx;
plane->y = ny;
plane->z = nz;
plane->w = -(v0->x * nx + v0->y * ny + v0->z * nz);
plane++;
lastPlane = FaceMap[f];
}
}
}
EngMesh::SVCache *EngMesh::Job::GetSVCache(sInt lightId)
{
// TODO: maybe implement somewhat better logic here
for(sInt i=0;i<MAXSVCACHE-1;i++)
if(!SVCache[i].LightId || SVCache[i].LightId == lightId)
return &SVCache[i];
return &SVCache[MAXSVCACHE-1];
}
/****************************************************************************/
sU8 EngMesh::SFaceIn[65536];
sU8 EngMesh::SPartSkip[0x4000];
EngMesh::EngMesh()
{
Vert = 0;
BoneInfos = 0;
CompletelyRigid = sFALSE;
PartCount = 0;
PartBBs = 0;
Preloaded = sFALSE;
Mtrl.Init();
Jobs.Init();
Instances = 0;
InstanceCount = InstanceAlloc = 0;
Animation = 0;
#if !sPLAYER
PrepareWireMaterials();
#endif
#if sINTRO
sSetMem(SPartSkip,1,sizeof(SPartSkip));
#endif
}
EngMesh::~EngMesh()
{
for(sInt i=1;i<Mtrl.Count;i++)
Mtrl[i].Material->Release();
for(sInt i=0;i<Jobs.Count;i++)
Jobs[i].Exit();
_aligned_free(Vert);
if(!CompletelyRigid)
delete[] BoneInfos;
else
delete[] MatrixInds;
delete[] PartBBs;
Mtrl.Exit();
Jobs.Exit();
if(Instances)
_aligned_free(Instances);
sRelease(Animation);
#if !sPLAYER
ReleaseWireMaterials();
#endif
}
void EngMesh::Copy(KObject *o)
{
// there's absolutely no valid reason for EngMeshes to be copied, so don't!
sVERIFYFALSE;
}
/****************************************************************************/
sInt EngMesh::GetSize()
{
sInt size = 0;
if(Vert) size += sizeof(sVertexTSpace3Big) * VertCount;
if(!CompletelyRigid)
{
if(BoneInfos) size += sizeof(BoneInfo) * VertCount;
}
else
{
if(MatrixInds) size += sizeof(sU16) * VertCount;
}
if(PartBBs) size += sizeof(sF32) * 6 * PartCount;
for(sInt i=0;i<Jobs.Count;i++)
{
Job *job = &Jobs[i];
if(job->VertexBuffer) size += sizeof(sInt) * job->VertexCount;
if(job->IndexBuffer) size += sizeof(sInt) * job->IndexCount;
if(job->FaceMap) size += sizeof(sInt) * (job->IndexCount / 3);
if(job->Edges) size += sizeof(SilEdge) * job->EdgeCount;
if(job->Planes) size += sizeof(sVector) * job->PlaneCount;
}
return size;
}
/****************************************************************************/
#if sLINK_FATMESH
// Converts a GenMesh to a list of jobs and a vertex buffer.
void EngMesh::FromGenMesh(GenMesh *mesh)
{
mesh->NeedAllNormals();
CalcPartBoundingBoxes(mesh);
FillVertexBuffer(mesh);
Mtrl.Copy(mesh->Mtrl);
for(sInt i=1;i<Mtrl.Count;i++)
Mtrl[i].Material->AddRef();
PrepareJobs(mesh);
}
/****************************************************************************/
// Same as above, but for wireframe mode
void EngMesh::FromGenMeshWire(GenMesh *mesh,sInt wireFlags,sU32 wireMask)
{
sAABox bbox;
delete[] PartBBs;
mesh->NeedAllNormals();
mesh->CalcBBox(bbox); // calculate mesh bounding box
PartCount = 1;
PartBBs = new sF32[6];
PartBBs[0] = (bbox.Max.x + bbox.Min.x) * 0.5f;
PartBBs[1] = (bbox.Max.x - bbox.Min.x) * 0.5f;
PartBBs[2] = (bbox.Max.y + bbox.Min.y) * 0.5f;
PartBBs[3] = (bbox.Max.y - bbox.Min.y) * 0.5f;
PartBBs[4] = (bbox.Max.z + bbox.Min.z) * 0.5f;
PartBBs[5] = (bbox.Max.z - bbox.Min.z) * 0.5f;
FillVertexBuffer(mesh);
Mtrl.Init();
GenMeshMtrl *nullMtrl = Mtrl.Add();
nullMtrl->Material = 0;
nullMtrl->Pass = 0;
if(wireFlags & EWF_EDGES)
AddWireEdgeJob(mesh,AddMaterial(MtrlWire[0],0),(wireMask >> 0) & 0xff);
if(wireFlags & EWF_FACES)
AddWireFaceJob(mesh,AddMaterial(MtrlWire[1],0),(wireMask >> 8) & 0xff,0);
if(wireFlags & EWF_VERTS)
AddWireVertJob(mesh,AddMaterial(MtrlWire[4],0),(wireMask >> 16) & 0xff);
if(wireFlags & EWF_HIDDEN)
AddWireFaceJob(mesh,AddMaterial(MtrlWire[2],2),0,1);
if(wireFlags & EWF_COLLISION)
AddWireCollisionJob(mesh,AddMaterial(MtrlWire[3],4));
}
#endif
/****************************************************************************/
// Converts a GenMinMesh to a list of jobs and a vertex buffer.
#if sLINK_MINMESH
void EngMesh::FromGenMinMesh(GenMinMesh *mesh)
{
mesh->CalcNormals();
mesh->CalcAdjacency();
CompletelyRigid = mesh->CompletelyRigid;
FillVertexBuffer(mesh);
PrepareJobs(mesh);
sRelease(Animation);
Animation = mesh->Animation;
if(Animation)
Animation->AddRef();
}
#endif
/****************************************************************************/
// Same as above, but for wireframe mode
#if sLINK_MINMESH
#if !sINTRO
void EngMesh::FromGenMinMeshWire(GenMinMesh *mesh,sInt wireFlags,sU32 wireMask)
{
mesh->CalcNormals();
mesh->CalcAdjacency();
FillVertexBuffer(mesh);
Mtrl.Init();
GenMeshMtrl *nullMtrl = Mtrl.Add();
nullMtrl->Material = 0;
nullMtrl->Pass = 0;
if(wireFlags & EWF_EDGES)
AddWireEdgeJob(mesh,AddMaterial(MtrlWire[0],0),(wireMask >> 0) & 0xff);
if(wireFlags & EWF_FACES)
AddWireFaceJob(mesh,AddMaterial(MtrlWire[1],0),(wireMask >> 8) & 0xff,0);
if(wireFlags & EWF_VERTS)
AddWireVertJob(mesh,AddMaterial(MtrlWire[4],0),(wireMask >> 16) & 0xff);
if(wireFlags & EWF_HIDDEN)
AddWireFaceJob(mesh,AddMaterial(MtrlWire[2],2),0,1);
sRelease(Animation);
Animation = mesh->Animation;
if(Animation)
Animation->AddRef();
}
#endif
#endif
/****************************************************************************/
// Full bone innerloop (FPU variant)
static void FullBoneInner(sVertexTSpace3Big *out,const sVertexTSpace3Big *in,EngMesh::BoneInfo *inInfo,sMatrix *matrices,sInt vc)
{
for(int i=0;i<vc;i++)
{
sMatrix m;
m.i.x = 0.0f; m.i.y = 0.0f; m.i.z = 0.0f;
m.j.x = 0.0f; m.j.y = 0.0f; m.j.z = 0.0f;
m.k.x = 0.0f; m.k.y = 0.0f; m.k.z = 0.0f;
m.l.x = 0.0f; m.l.y = 0.0f; m.l.z = 0.0f;
// sum weighted matrices
for(int j=0;j<4;j++)
{
sF32 w = inInfo->Weight[j];
if(!w)
break;
const sMatrix *mat = matrices + inInfo->Matrix[j];
m.i.x += w*mat->i.x;
m.i.y += w*mat->i.y;
m.i.z += w*mat->i.z;
m.j.x += w*mat->j.x;
m.j.y += w*mat->j.y;
m.j.z += w*mat->j.z;
m.k.x += w*mat->k.x;
m.k.y += w*mat->k.y;
m.k.z += w*mat->k.z;
m.l.x += w*mat->l.x;
m.l.y += w*mat->l.y;
m.l.z += w*mat->l.z;
}
// transform
out->x = m.i.x * in->x + m.j.x * in->y + m.k.x * in->z + m.l.x;
out->y = m.i.y * in->x + m.j.y * in->y + m.k.y * in->z + m.l.y;
out->z = m.i.z * in->x + m.j.z * in->y + m.k.z * in->z + m.l.z;
out->nx = m.i.x * in->nx + m.j.x * in->ny + m.k.x * in->nz;
out->ny = m.i.y * in->nx + m.j.y * in->ny + m.k.y * in->nz;
out->nz = m.i.z * in->nx + m.j.z * in->ny + m.k.z * in->nz;
out->sx = m.i.x * in->sx + m.j.x * in->sy + m.k.x * in->sz;
out->sy = m.i.y * in->sx + m.j.y * in->sy + m.k.y * in->sz;
out->sz = m.i.z * in->sx + m.j.z * in->sy + m.k.z * in->sz;
// the vertex shader does renormalization and orthogonalization now
// copy rest
out->c = in->c;
out->u = in->u;
out->v = in->v;
out++;
in++;
inInfo++;
}
}
static void RigidInnerSSE_asm(sVertexTSpace3Big *outp,const sVertexTSpace3Big *inp,sU16 *matrixInds,sMatrix *matrices,sInt vc)
{
static const sU32 __declspec(align(16)) mask[4] = { ~0,~0,~0,0 };
if(!vc)
return;
__asm
{
mov edi, [outp];
mov esi, [inp];
mov ebx, [matrixInds];
mov edx, [matrices];
mov ecx, [vc];
align 16;
vertloop:
movzx eax, word ptr [ebx];
shl eax, 6;
add eax, edx;
prefetcht0 [esi+144];
prefetcht0 [eax+64];
movaps xmm4, [esi+ 0];
movaps xmm0, [eax+ 0];
movaps xmm1, [eax+16];
movaps xmm2, [eax+32];
movaps xmm3, [eax+48];
// pos
movaps xmm5, xmm4;
movaps xmm6, xmm4;
movaps xmm7, xmm4;
andps xmm3, [mask];
shufps xmm4, xmm4, 000h;
shufps xmm5, xmm5, 055h;
shufps xmm6, xmm6, 0aah;
shufps xmm7, xmm7, 0ffh;
mulps xmm4, xmm0;
mulps xmm5, xmm1;
mulps xmm6, xmm2;
addps xmm3, xmm4; // xmm3=pos_half
// normal
movaps xmm4, [esi+16];
addps xmm5, xmm6; // xmm5=pos_other_half
movaps xmm6, xmm4;
addps xmm3, xmm5; // xmm3=pos
movaps xmm5, xmm4;
shufps xmm4, xmm4, 000h;
mulps xmm7, xmm0;
shufps xmm6, xmm6, 055h;
mulps xmm4, xmm1;
mulps xmm6, xmm2;
addps xmm4, xmm7;
movaps xmm7, xmm5;
shufps xmm5, xmm5, 0aah;
add edi, 48;
shufps xmm7, xmm7, 0ffh;
addps xmm4, xmm6; // xmm4=normal
// tangent
movaps xmm6, [esi+32];
mulps xmm0, xmm5;
mulps xmm1, xmm7;
add esi, 48;
movaps xmm5, xmm6;
shufps xmm6, xmm6, 000h;
addps xmm0, xmm1;
add ebx, 2;
mulps xmm2, xmm6; // xmm0+xmm2=tangent
movaps xmm1, xmm4;
shufps xmm4, xmm4, 03fh;
addps xmm0, xmm2; // xmm0=tangent
shufps xmm1, xmm1, 0f9h;
addps xmm3, xmm4; // xmm3=first 4 final
movlhps xmm1, xmm0; // xmm1=mid 4 final
movhlps xmm0, xmm0;
dec ecx;
movntps [edi-48], xmm3;
movntps [edi-32], xmm1;
movss xmm5, xmm0; // xmm5=last 4 final
movntps [edi-16], xmm5;
jnz vertloop;
}
}
#pragma warning (push)
#pragma warning (disable: 4731) // frame pointer modified by asm code
static void FullBoneInnerSSE_asm(sVertexTSpace3Big *outp,const sVertexTSpace3Big *inp,EngMesh::BoneInfo *inInfo,sMatrix *matrices,sInt vc)
{
if(!vc)
return;
__asm
{
mov edi, [outp];
mov esi, [inp];
mov ebx, [inInfo];
mov edx, [matrices];
push ebp;
mov ebp, [vc];
add ebx, 4;
align 16;
vertloop:
movzx eax, word ptr [ebx+12];
movss xmm4, [ebx-4];
shl eax, 6;
shufps xmm4, xmm4, 0c0h; // (w,w,w,0)
add eax, edx;
mov ecx, 6;
push ebx;
prefetcht0 [ebx+32];
prefetcht0 [esi+96];
movaps xmm0, [eax+ 0];
movaps xmm1, [eax+16];
movaps xmm2, [eax+32];
movaps xmm3, [eax+48];
mulps xmm0, xmm4;
mulps xmm1, xmm4;
mulps xmm2, xmm4;
mulps xmm3, xmm4;
weightmats:
mov eax, [ebx];
test eax, eax;
jz matsdone;
movzx eax, word ptr [ebx+ecx+8];
movss xmm4, [ebx];
shl eax, 6;
shufps xmm4, xmm4, 0c0h; // (w,w,w,0)
add eax, edx;
movaps xmm5, xmm4;
movaps xmm6, xmm4;
movaps xmm7, xmm4;
add ebx, 4;
mulps xmm4, [eax+ 0];
mulps xmm5, [eax+16];
mulps xmm6, [eax+32];
mulps xmm7, [eax+48];
sub ecx, 2;
addps xmm0, xmm4;
addps xmm1, xmm5;
addps xmm2, xmm6;
addps xmm3, xmm7;
jnz weightmats;
matsdone:
pop ebx;
// pos
movaps xmm4, [esi+ 0];
movaps xmm5, xmm4;
movaps xmm6, xmm4;
movaps xmm7, xmm4;
shufps xmm4, xmm4, 000h; // in.px
shufps xmm5, xmm5, 055h; // in.py
shufps xmm6, xmm6, 0aah; // in.pz
shufps xmm7, xmm7, 0ffh; // in.nx
mulps xmm4, xmm0; // pi
mulps xmm5, xmm1; // pj
mulps xmm6, xmm2; // pk
addps xmm3, xmm4; // xmm3=pl+pi
// normal
movaps xmm4, [esi+16];
addps xmm5, xmm6; // xmm5=pj+pk
movaps xmm6, xmm4;
addps xmm3, xmm5; // xmm3=pos
movaps xmm5, xmm4;
shufps xmm4, xmm4, 000h; // in.ny
mulps xmm7, xmm0; // ni
shufps xmm6, xmm6, 055h; // in.nz
mulps xmm4, xmm1; // nj
mulps xmm6, xmm2; // nk
addps xmm4, xmm7; // xmm4=nj+ni
movaps xmm7, xmm5;
shufps xmm5, xmm5, 0aah; // in.sx
add edi, 48;
addps xmm4, xmm6; // xmm4=normal
movaps xmm6, [esi+32];
shufps xmm7, xmm7, 0ffh; // in.sy
// tangent
mulps xmm0, xmm5; // ti
shufps xmm4, xmm4, 039h; // (normal.y,normal.z,0,normal.x)
mulps xmm1, xmm7; // tj
movaps xmm5, xmm6;
shufps xmm6, xmm6, 000h; // in.sz
addps xmm0, xmm1; // ti+tj
shufps xmm1, xmm4, 0efh; // (0,0,0,normal.x)
mulps xmm2, xmm6; // tk
addps xmm3, xmm1; // xmm3=first 4 final
addps xmm0, xmm2; // xmm0=tangent
movntps [edi-48], xmm3;
movlhps xmm4, xmm0; // xmm4=mid 4 final
add ebx, 24;
movntps [edi-32], xmm4;
movhlps xmm0, xmm0;
add esi, 48;
movss xmm5, xmm0; // xmm5=last 4 final
dec ebp;
movntps [edi-16], xmm5;
jnz vertloop;
pop ebp;
}
}
#pragma warning (pop)
// Do the bone transforms
void *EngMesh::EvalBones(sMatrix *matrices,sGrowableMemStack &alloc)
{
#if BONES
sZONE(EvalBones);
if(!BoneInfos) // && !MatrixInds
return Vert;
sVertexTSpace3Big *vert = alloc.Alloc<sVertexTSpace3Big>(VertCount);
sVertexTSpace3Big *out = vert;
const sVertexTSpace3Big *in = Vert;
BoneInfo *inInfo = BoneInfos;
if(CompletelyRigid)
{
/*static sU64 totalClocks = 0;
static sU32 totalVerts = 0;
totalVerts += VertCount;
__asm
{
rdtsc;
sub dword ptr [totalClocks+0], eax;
sbb dword ptr [totalClocks+4], edx;
}
for(sInt i=0;i<VertCount;i++)
{
const sMatrix *m = matrices + inInfo->Matrix[0];
out->x = m->i.x * in->x + m->j.x * in->y + m->k.x * in->z + m->l.x;
out->y = m->i.y * in->x + m->j.y * in->y + m->k.y * in->z + m->l.y;
out->z = m->i.z * in->x + m->j.z * in->y + m->k.z * in->z + m->l.z;
out->nx = m->i.x * in->nx + m->j.x * in->ny + m->k.x * in->nz;
out->ny = m->i.y * in->nx + m->j.y * in->ny + m->k.y * in->nz;
out->nz = m->i.z * in->nx + m->j.z * in->ny + m->k.z * in->nz;
out->sx = m->i.x * in->sx + m->j.x * in->sy + m->k.x * in->sz;
out->sy = m->i.y * in->sx + m->j.y * in->sy + m->k.y * in->sz;
out->sz = m->i.z * in->sx + m->j.z * in->sy + m->k.z * in->sz;
// copy rest
out->c = in->c;
out->u = in->u;
out->v = in->v;
out++;
in++;
inInfo++;
}
__asm
{
rdtsc;
add dword ptr [totalClocks+0], eax;
adc dword ptr [totalClocks+4], edx;
}
if(totalVerts > 100000)
{
sF64 avgTime = 1.0f * totalClocks / totalVerts;
sDPrintF("rigid avg. clocks/vertex: %.2f\n",avgTime);
totalVerts = 0;
totalClocks = 0;
}*/
RigidInnerSSE_asm(out,in,MatrixInds,matrices,VertCount);
}
else
{
//FullBoneInner(out,in,inInfo,matrices,VertCount);
FullBoneInnerSSE_asm(out,in,inInfo,matrices,VertCount);
}
return vert;
#else
return Vert;
#endif
}
/****************************************************************************/
static sVector SPlaneBuffer[262144];
// Instancing inner loop
static void InstancingInner(sVertexTSpace3Big *vp,const sVertexTSpace3Big *vert,const sInt *vind,sInt vc,const sMatrix *mat,sInt inst)
{
while(inst--)
{
for(sInt i=0;i<vc;i++)
{
const sVertexTSpace3Big *s = &vert[vind[i]];
vp->x = s->x*mat->i.x + s->y*mat->j.x + s->z*mat->k.x + mat->l.x;
vp->y = s->x*mat->i.y + s->y*mat->j.y + s->z*mat->k.y + mat->l.y;
vp->z = s->x*mat->i.z + s->y*mat->j.z + s->z*mat->k.z + mat->l.z;
vp->nx = s->nx*mat->i.x + s->ny*mat->j.x + s->nz*mat->k.x;
vp->ny = s->nx*mat->i.y + s->ny*mat->j.y + s->nz*mat->k.y;
vp->nz = s->nx*mat->i.z + s->ny*mat->j.z + s->nz*mat->k.z;
vp->sx = s->sx*mat->i.x + s->sy*mat->j.x + s->sz*mat->k.x;
vp->sy = s->sx*mat->i.y + s->sy*mat->j.y + s->sz*mat->k.y;
vp->sz = s->sx*mat->i.z + s->sy*mat->j.z + s->sz*mat->k.z;
vp->c = s->c;
vp->u = s->u;
vp->v = s->v;
vp++;
}
mat++;
}
}
static void VertCopyInner(sVertexTSpace3Big *vp,const sVertexTSpace3Big *vert,sInt *vind,sInt vc)
{
sZONE(VertCopy);
#if !sINTRO
__asm
{
mov edi, [vp];
mov esi, [vind];
mov ebx, [vert];
mov ecx, [vc];
and ecx, not 1;
jz vert_copy_tail;
shl ecx, 2;
add esi, ecx;
neg ecx;
vert_copy_main:
mov eax, [esi+ecx];
mov edx, [esi+ecx+4];
shl eax, 4;
shl edx, 4;
lea eax, [eax*2+eax];
lea edx, [edx*2+edx];
add eax, ebx;
add edx, ebx;
prefetcht0 [eax+48*4];
prefetcht0 [edx+48*4];
movq mm0, [eax+ 0];
movq mm1, [eax+ 8];
movq mm2, [eax+16];
movq mm3, [eax+24];
movntq [edi+ 0], mm0;
movntq [edi+ 8], mm1;
movntq [edi+16], mm2;
movntq [edi+24], mm3;
movq mm0, [eax+32];
movq mm1, [eax+40];
movq mm2, [edx+ 0];
movq mm3, [edx+ 8];
movntq [edi+32], mm0;
movntq [edi+40], mm1;
movntq [edi+48], mm2;
movntq [edi+56], mm3;
movq mm0, [edx+16];
movq mm1, [edx+24];
movq mm2, [edx+32];
movq mm3, [edx+40];
movntq [edi+64], mm0;
movntq [edi+72], mm1;
movntq [edi+80], mm2;
movntq [edi+88], mm3;
add edi, 96;
add ecx, 8;
jnz vert_copy_main;
vert_copy_tail:
mov ecx, [vc];
and ecx, 1;
jz vert_copy_end;
vert_copy_tail_lp:
mov eax, [esi];
shl eax, 4;
lea eax, [eax*2+eax];
add eax, ebx;
movq mm0, [eax+ 0];
movq mm1, [eax+ 8];
movq mm2, [eax+16];
movq mm3, [eax+24];
movq mm4, [eax+32];
movq mm5, [eax+40];
movntq [edi+ 0], mm0;
movntq [edi+ 8], mm1;
movntq [edi+16], mm2;
movntq [edi+24], mm3;
movntq [edi+32], mm4;
movntq [edi+40], mm5;
add esi, 4;
add edi, 48;
dec ecx;
jnz vert_copy_tail_lp;
vert_copy_end:
emms;
}
#else
for(sInt i=0;i<vc;i++)
vp[i] = vert[vind[i]];
#endif
}
static void VertCopyStencilInner(sVertexXYZW *vp,const sVertexTSpace3Big *vert,sInt *vind,sInt vc)
{
static const sU64 andmask = 0x00000000ffffffff;
static const sU64 ormask = 0x3f80000000000000;
sZONE(VCopyStencil);
#if !sINTRO
__asm
{
movq mm6, [ormask];
movq mm7, [andmask];
mov edi, [vp];
mov esi, [vind];
mov ebx, [vert];
mov ecx, [vc];
and ecx, not 1;
jz vert_copy_stencil_tail;
shl ecx, 2;
add esi, ecx;
neg ecx;
vert_copy_stencil_main:
mov eax, [esi+ecx];
mov edx, [esi+ecx+4];
shl eax, 4;
shl edx, 4;
lea eax, [eax*2+eax];
lea edx, [edx*2+edx];
add eax, ebx;
add edx, ebx;
movq mm0, [eax+ 0];
movq mm1, [eax+ 8];
movq mm2, [edx+ 0];
movq mm3, [edx+ 8];
movq mm4, mm6;
movq mm5, mm6;
pand mm1, mm7;
pand mm3, mm7;
por mm4, mm1;
por mm5, mm3;
movntq [edi+ 0], mm0;
movntq [edi+ 8], mm4;
movntq [edi+16], mm0;
movntq [edi+24], mm1;
movntq [edi+32], mm2;
movntq [edi+40], mm5;
movntq [edi+48], mm2;
movntq [edi+56], mm3;
add edi, 64;
add ecx, 8;
jnz vert_copy_stencil_main;
vert_copy_stencil_tail:
mov ecx, [vc];
and ecx, 1;
jz vert_copy_stencil_end;
vert_copy_stencil_tail_lp:
mov eax, [esi];
shl eax, 4;
lea eax, [eax*2+eax];
add eax, ebx;
movq mm0, [eax+ 0];
movq mm1, [eax+ 8];
movq mm2, mm6;
pand mm1, mm7;
por mm2, mm1;
movntq [edi+ 0], mm0;
movntq [edi+ 8], mm2;
movntq [edi+16], mm0;
movntq [edi+24], mm1;
add esi, 4;
add edi, 32;
dec ecx;
jnz vert_copy_stencil_tail_lp;
vert_copy_stencil_end:
emms;
}
#else
for(sInt i=0;i<vc;i++)
{
const sVertexTSpace3Big *src = &vert[vind[i]];
vp[0].x = src->x;
vp[0].y = src->y;
vp[0].z = src->z;
vp[0].w = 1.0f;
vp[1].x = src->x;
vp[1].y = src->y;
vp[1].z = src->z;
vp[1].w = 0.0f;
vp += 2;
}
#endif
}
// Paints the given job
void EngMesh::PaintJob(sInt jobId,GenMaterialPass *pass,const EngPaintInfo &paintInfo)
{
sVERIFY(jobId < Jobs.Count);
Job *job = &Jobs[jobId];
// skip empty jobs
if(!job->VertexCount)
return;
//sZONE(PaintJob);
// preparation
sInt program = job->Program;
#if SHADOWS
SVCache *svCache;
sBool svUpdate;
if(program == MPP_SHADOW)
{
svCache = job->GetSVCache(paintInfo.LightId);
if(BoneInfos ||
!paintInfo.LightId || paintInfo.LightId != svCache->LightId
|| paintInfo.LightPos != svCache->LightPos
|| paintInfo.LightRange != svCache->LightRange)
svUpdate = sTRUE;
else
svUpdate = sFALSE;
svUpdate = sTRUE;
if(svUpdate)
{
sVector *planes;
if(paintInfo.BoneData)
{
job->UpdatePlanes(SPlaneBuffer,(sVertexTSpace3Big *) paintInfo.BoneData);
planes = SPlaneBuffer;
}
else
planes = job->Planes;
UpdateShadowCacheJob(job,planes,svCache,paintInfo);
svCache->LightPos = paintInfo.LightPos;
svCache->LightRange = paintInfo.LightRange;
svCache->LightId = paintInfo.LightId;
}
}
#endif
// choose right source vertices
sVertexTSpace3Big *vert = Vert;
if(paintInfo.BoneData)
vert = (sVertexTSpace3Big *) paintInfo.BoneData;
sInt handle = job->Geometry;
sBool bigInd = job->VertexCount >= (program == MPP_SHADOW ? 32768 : 65536);
if(program==MPP_INSTANCES) bigInd = 1;
sInt bigFlag = bigInd ? sGEO_IND32B : 0;
// create geobuffers if necessary
if(handle == sINVALID)
{
switch(program)
{
case MPP_STATIC:
if(!BoneInfos)
handle = sSystem->GeoAdd(sFVF_TSPACE3BIG,sGEO_TRI|sGEO_STATIC|bigFlag);
else
handle = sSystem->GeoAdd(sFVF_TSPACE3BIG,sGEO_TRI|sGEO_DYNVB|sGEO_STATIB|bigFlag);
break;
#if SHADOWS
case MPP_SHADOW:
if(!BoneInfos)
handle = sSystem->GeoAdd(sFVF_XYZW,sGEO_TRI|sGEO_STATVB|sGEO_DYNIB|bigFlag);
else
handle = sSystem->GeoAdd(sFVF_XYZW,sGEO_TRI|sGEO_DYNVB|sGEO_DYNIB|bigFlag);
break;
#endif
case MPP_SPRITES:
handle = sSystem->GeoAdd(sFVF_STANDARD,sGEO_QUAD|sGEO_DYNAMIC);
break;
#if THICKLINES
case MPP_THICKLINES:
handle = sSystem->GeoAdd(sFVF_STANDARD,sGEO_QUAD|sGEO_DYNAMIC);
break;
#endif
case MPP_INSTANCES:
handle = sSystem->GeoAdd(sFVF_TSPACE3BIG,sGEO_TRI|sGEO_DYNAMIC|bigFlag);
break;
case MPP_INSTANCES_SH:
handle = sSystem->GeoAdd(sFVF_TSPACE3BIG,sGEO_TRI|sGEO_STATVB|sGEO_DYNIB|bigFlag);
break;
#if !sPLAYER
case MPP_WIRELINES:
if(!BoneInfos)
handle = sSystem->GeoAdd(sFVF_COMPACT,sGEO_LINE|sGEO_STATIC);
else
handle = sSystem->GeoAdd(sFVF_COMPACT,sGEO_LINE|sGEO_DYNAMIC);
break;
case MPP_WIREVERTEX:
handle = sSystem->GeoAdd(sFVF_COMPACT,sGEO_QUAD|sGEO_DYNAMIC);
break;
#endif
default:
sVERIFYFALSE;
}
job->Geometry = handle;
}
// try to draw
sInt update = sSystem->GeoDraw(handle);
// do we need to update first?
if(update) // yes we do
{
switch(program)
{
case MPP_STATIC:
{
sVertexTSpace3Big *vp;
sU16 *ip;
sInt *vind = job->VertexBuffer;
sInt vc = job->VertexCount;
sInt ic = job->IndexCount;
// update geometry
sSystem->GeoBegin(handle,vc,ic,(sF32 **)&vp,(void **)&ip,update);
// copy vertices
if(update & sGEO_VERTEX)
VertCopyInner(vp,vert,vind,vc);
// copy indices
if(update & sGEO_INDEX)
{
if(bigInd)
sCopyMem(ip,job->IndexBuffer,ic*4);
else
for(sInt i=0;i<ic;i++)
ip[i] = job->IndexBuffer[i];
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
// ich hoffe das geht so nicht schief - aber sonst uploadet er
// die vertices mehrfach, wenn sie in mehreren passes gebraucht
// werden => sehr blöde idee (und lahm) -ryg
/*if(BoneInfos)
sSystem->GeoFlush(handle,sGEO_VERTEX);*/
}
break;
#if SHADOWS
case MPP_SHADOW:
{
// draw from cache: get counts
sInt vc = job->VertexCount;
sInt ic = svCache->SilIndices;
if(paintInfo.StencilFlags & sMBF_STENCILZFAIL)
ic += svCache->CapIndices;
// vertices
if(update & sGEO_VERTEX)
{
sVertexXYZW *vp;
sU16 *ip;
sSystem->GeoBegin(handle,vc*2,ic ? 0 : 3,(sF32 **)&vp,(void **)&ip,ic ? (update & sGEO_VERTEX) : update);
sInt *vind = job->VertexBuffer;
VertCopyStencilInner(vp,vert,vind,vc);
sSystem->GeoEnd(handle);
sSystem->GeoFlush(handle,sGEO_INDEX);
}
// update and draw
if(ic)
{
sU16 *ip;
// indices
sSystem->GeoBegin(handle,0,ic,0,(void **)&ip,update & sGEO_INDEX);
if(bigInd)
sCopyMem(ip,svCache->IndexBuffer,ic*4);
else
{
for(sInt i=0;i<ic;i++)
ip[i] = svCache->IndexBuffer[i];
}
sSystem->GeoEnd(handle);
// draw, if possible with two-sided stencil
if(sSystem->GpuMask & sGPU_TWOSIDESTENCIL)
{
sMaterial11::SetShadowStates(sMBF_STENCILINDE|sMBF_DOUBLESIDED|paintInfo.StencilFlags);
sSystem->GeoDraw(handle);
}
else
{
sMaterial11::SetShadowStates(sMBF_STENCILINC|paintInfo.StencilFlags);
sSystem->GeoDraw(handle);
sMaterial11::SetShadowStates(sMBF_STENCILDEC|sMBF_INVERTCULL|paintInfo.StencilFlags);
sSystem->GeoDraw(handle);
}
// flush indices
sSystem->GeoFlush(handle,/*BoneInfos ? sGEO_VERTEX|sGEO_INDEX : */sGEO_INDEX);
#if 0 && !sINTRO // DEBUG PAINT CODE
Engine->DebugPaintStart(sFALSE);
// show non-culled faces
sInt *vind = job->VertexBuffer;
sInt firstInd = svCache->SilIndices;
sInt count = job->IndexCount/3;
for(sInt i=0;i<count;i++)
{
if(1 || SFaceIn[job->FaceMap[i]])
{
sVector v0,v1,v2;
sU32 color = SFaceIn[job->FaceMap[i]] ? 0xff800000 : 0xff808000;
sInt i0 = job->IndexBuffer[i*3+0];
sInt i1 = job->IndexBuffer[i*3+1];
sInt i2 = job->IndexBuffer[i*3+2];
sVertexTSpace3Big *p0 = &vert[vind[i0]];
sVertexTSpace3Big *p1 = &vert[vind[i1]];
sVertexTSpace3Big *p2 = &vert[vind[i2]];
v0.Init(p0->x,p0->y,p0->z);
v1.Init(p1->x,p1->y,p1->z);
v2.Init(p2->x,p2->y,p2->z);
//Engine->DebugPaintTri(v0,v1,v2,0xff803c00);
Engine->DebugPaintTri(v0,v1,v2,color);
}
}
// show silhouette edges
for(sInt i=0;i<svCache->SilIndices;i+=6)
{
sInt i0 = svCache->IndexBuffer[i+0];
sInt i1 = svCache->IndexBuffer[i+1];
sVertexTSpace3Big *v0 = &vert[vind[i0]];
sVertexTSpace3Big *v1 = &vert[vind[i1]];
Engine->DebugPaintLine(v0->x,v0->y,v0->z,v1->x,v1->y,v1->z,0xffff0000);
}
#endif
}
}
break;
#endif
case MPP_SPRITES:
{
sVertexStandard *vp;
sInt *vind = job->VertexBuffer;
sInt count = job->VertexCount;
sF32 scale = pass->Size;
sF32 aspect = pass->Aspect;
// get transform
sMatrix mat;
sSystem->GetTransform(sGT_MODELVIEW,mat);
mat.Trans3();
// calc local axes
sVector vx,vy;
vx.Scale3(mat.i,scale);
vy.Scale3(mat.j,scale);
// update geometry
while(count)
{
sInt vc = sMin(count,0x1000);
sSystem->GeoBegin(handle,vc*4,0,(sF32 **)&vp,0);
// fill vertex buffer
for(sInt i=0;i<vc;i++)
{
sVertexTSpace3Big *vsrc = &vert[vind[i]];
// gen verts
vp[0].x = vsrc->x - vx.x + vy.x;
vp[0].y = vsrc->y - vx.y + vy.y;
vp[0].z = vsrc->z - vx.z + vy.z;
vp[0].nx = 0.0f;
vp[0].ny = 0.0f;
vp[0].nz = 1.0f;
vp[0].u = 0.0f;
vp[0].v = 0.0f;
vp[1].x = vsrc->x + vx.x + vy.x;
vp[1].y = vsrc->y + vx.y + vy.y;
vp[1].z = vsrc->z + vx.z + vy.z;
vp[1].nx = 0.0f;
vp[1].ny = 0.0f;
vp[1].nz = 1.0f;
vp[1].u = 1.0f;
vp[1].v = 0.0f;
vp[2].x = vsrc->x + vx.x - vy.x;
vp[2].y = vsrc->y + vx.y - vy.y;
vp[2].z = vsrc->z + vx.z - vy.z;
vp[2].nx = 0.0f;
vp[2].ny = 0.0f;
vp[2].nz = 1.0f;
vp[2].u = 1.0f;
vp[2].v = 1.0f;
vp[3].x = vsrc->x - vx.x - vy.x;
vp[3].y = vsrc->y - vx.y - vy.y;
vp[3].z = vsrc->z - vx.z - vy.z;
vp[3].nx = 0.0f;
vp[3].ny = 0.0f;
vp[3].nz = 1.0f;
vp[3].u = 0.0f;
vp[3].v = 1.0f;
vp += 4;
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
sSystem->GeoFlush(handle);
// update counters
count -= vc;
vind += vc;
}
}
break;
#if THICKLINES
case MPP_THICKLINES:
{
sVertexStandard *vp;
sInt *vind = job->VertexBuffer;
sInt count = job->VertexCount;
sF32 s0 = pass->Size * pass->Aspect;
sF32 s1 = pass->Size / pass->Aspect;
// get transform
sMatrix mat;
sSystem->GetTransform(sGT_MODELVIEW,mat);
// update geometry
while(count)
{
sInt vc = sMin(count,0x2000);
sSystem->GeoBegin(handle,vc*2,0,(sF32 **)&vp,0);
// fill vertex buffer
for(sInt i=0;i<vc;i+=2)
{
sVertexTSpace3Big *v0 = &vert[vind[i+0]];
sVertexTSpace3Big *v1 = &vert[vind[i+1]];
// calc transformed positions and scales
sVector p0,p1;
p0.Rotate34(mat,(sVector &)v0->x);
p1.Rotate34(mat,(sVector &)v1->x);
sF32 ip0z,ip1z;
ip0z = 1.0f / sMax(p0.z,0.01f);
ip1z = 1.0f / sMax(p1.z,0.01f);
// calc projected direction and normalize it
sF32 pdx,pdy,ilen;
pdx = p0.x * ip0z - p1.x * ip1z;
pdy = p0.y * ip0z - p1.y * ip1z;
ilen = sFInvSqrt(pdx*pdx + pdy*pdy);
pdx *= ilen;
pdy *= ilen;
// calc local axes
sVector vx,vy;
vx.x = mat.i.y * pdx * s0 - mat.i.x * pdy * s0;
vx.y = mat.j.y * pdx * s0 - mat.j.x * pdy * s0;
vx.z = mat.k.y * pdx * s0 - mat.k.x * pdy * s0;
vy.x = -mat.i.x * pdx * s1 - mat.i.y * pdy * s1;
vy.y = -mat.j.x * pdx * s1 - mat.j.y * pdy * s1;
vy.z = -mat.k.x * pdx * s1 - mat.k.y * pdy * s1;
// gen verts
vp[0].x = v1->x - vx.x + vy.x;
vp[0].y = v1->y - vx.y + vy.y;
vp[0].z = v1->z - vx.z + vy.z;
vp[0].nx = 0.0f;
vp[0].ny = 0.0f;
vp[0].nz = 1.0f;
vp[0].u = 0.0f;
vp[0].v = 0.0f;
vp[1].x = v1->x + vx.x + vy.x;
vp[1].y = v1->y + vx.y + vy.y;
vp[1].z = v1->z + vx.z + vy.z;
vp[1].nx = 0.0f;
vp[1].ny = 0.0f;
vp[1].nz = 1.0f;
vp[1].u = 1.0f;
vp[1].v = 0.0f;
vp[2].x = v0->x + vx.x - vy.x;
vp[2].y = v0->y + vx.y - vy.y;
vp[2].z = v0->z + vx.z - vy.z;
vp[2].nx = 0.0f;
vp[2].ny = 0.0f;
vp[2].nz = 1.0f;
vp[2].u = 1.0f;
vp[2].v = 1.0f;
vp[3].x = v0->x - vx.x - vy.x;
vp[3].y = v0->y - vx.y - vy.y;
vp[3].z = v0->z - vx.z - vy.z;
vp[3].nx = 0.0f;
vp[3].ny = 0.0f;
vp[3].nz = 1.0f;
vp[3].u = 0.0f;
vp[3].v = 1.0f;
vp += 4;
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
sSystem->GeoFlush(handle);
// update counters
count -= vc;
vind += vc;
}
}
break;
#endif
case MPP_INSTANCES: // this is inefficient, but it won't tear the engine apart.
if(InstanceCount > 0)
{
sZONE(InstancePrg);
sVertexTSpace3Big *vp;
sU16 *ip;
sInt *vind = job->VertexBuffer;
sInt vc = job->VertexCount;
sInt ic = job->IndexCount;
sInt inst = InstanceCount;
const sInt maxinst = sMin<sInt>(MAX_DYNVBSIZE/sizeof(*vp)/vc,MAX_DYNIBSIZE/2/ic);
if(inst>maxinst) inst = maxinst;
// update geometry
sSystem->GeoBegin(handle,vc*inst,ic*inst,(sF32 **)&vp,(void **)&ip,update);
// copy vertices
if(update & sGEO_VERTEX)
InstancingInner(vp,vert,vind,vc,Instances,inst);
// copy indices
if(update & sGEO_INDEX)
{
sInt io = 0;
sU32 *ip32 = (sU32 *) ip;
for(sInt j=0;j<inst;j++)
{
if(bigInd)
{
for(sInt i=0;i<ic;i++)
ip32[i] = job->IndexBuffer[i]+io;
ip32 += ic;
}
else
{
for(sInt i=0;i<ic;i++)
ip[i] = job->IndexBuffer[i]+io;
ip += ic;
}
io += vc;
}
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
}
break;
case MPP_INSTANCES_SH: // maybe this is a better idea!
if(InstanceCount > 0)
{
static const sInt numCopies = 76;
sVertexTSpace3Big *vp;
sU16 *ip;
sInt *vind = job->VertexBuffer;
sInt vc = job->VertexCount;
sInt ic = job->IndexCount;
// update geometry
sSystem->GeoBegin(handle,vc*numCopies,ic*numCopies,(sF32 **)&vp,(void **)&ip,update);
// copy vertices
if(update & sGEO_VERTEX)
{
for(sInt copy=0;copy<numCopies;copy++)
{
for(sInt i=0;i<vc;i++)
{
*vp = vert[vind[i]];
vp->c = (vp->c & 0xffffff) | ((copy*3)<<24);
vp++;
}
}
}
// copy indices
if(update & sGEO_INDEX)
{
sInt offset = 0;
for(sInt copy=0;copy<numCopies;copy++)
{
if(bigInd)
{
for(sInt i=0;i<ic;i++)
((sU32 *) ip)[i] = job->IndexBuffer[i] + offset;
ip += 2*ic; // 2* because of sU16 ip
}
else
{
for(sInt i=0;i<ic;i++)
ip[i] = job->IndexBuffer[i] + offset;
ip += ic;
}
offset += vc;
}
}
// draw
sSystem->GeoEnd(handle);
// now, PAINT! (paint paint paint paint...)
const sMatrix *instanceMat = Instances;
for(sInt inst=0;inst<InstanceCount;inst+=numCopies)
{
sVector para[numCopies*3];
sInt count = sMin(InstanceCount-inst,numCopies);
// extract the constants
sVector *paraOut = para;
for(sInt i=0;i<count;i++)
{
paraOut->x = instanceMat->i.x;
paraOut->y = instanceMat->j.x;
paraOut->z = instanceMat->k.x;
paraOut->w = instanceMat->l.x;
paraOut++;
paraOut->x = instanceMat->i.y;
paraOut->y = instanceMat->j.y;
paraOut->z = instanceMat->k.y;
paraOut->w = instanceMat->l.y;
paraOut++;
paraOut->x = instanceMat->i.z;
paraOut->y = instanceMat->j.z;
paraOut->z = instanceMat->k.z;
paraOut->w = instanceMat->l.z;
paraOut++;
instanceMat++;
}
// set the constants and go!
sSystem->MtrlSetVSConstants(27,para,count*3);
sSystem->GeoDraw(handle,ic*count);
}
sSystem->GeoFlush(handle,sGEO_INDEX); // this is a hack.
}
break;
#if !sPLAYER
case MPP_WIRELINES:
{
sVertexCompact *vp;
sInt *vind = job->VertexBuffer;
sInt vc = job->VertexCount;
sF32 extrude = pass->Size;
// update geometry
sSystem->GeoBegin(handle,vc,0,(sF32 **)&vp,0);
// fill vertex buffer
for(sInt i=0;i<vc;i++)
{
sVertexTSpace3Big *vsrc = &vert[vind[i]];
vp[i].x = vsrc->x + vsrc->nx * extrude;
vp[i].y = vsrc->y + vsrc->ny * extrude;
vp[i].z = vsrc->z + vsrc->nz * extrude;
vp[i].c0 = 0;
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
}
break;
case MPP_WIREVERTEX:
{
sVertexCompact *vp;
sInt *vind = job->VertexBuffer;
sInt count = job->VertexCount;
sF32 scale = pass->Size;
sF32 extrude = pass->Aspect;
// get transform
sMatrix mat;
sSystem->GetTransform(sGT_MODELVIEW,mat);
mat.Trans4();
while(count)
{
sInt vc = sMin(count,0x3f00);
// update geometry
sSystem->GeoBegin(handle,vc*4,0,(sF32 **)&vp,0);
// fill vertex buffer
for(sInt i=0;i<vc;i++)
{
sVertexTSpace3Big *vsrc = &vert[vind[i]];
sF32 projZ = vsrc->x*mat.k.x + vsrc->y*mat.k.y + vsrc->z*mat.k.z + mat.k.w;
sF32 size = (sFAbs(projZ) + 0.001f) * scale;
sVector vx,vy,n;
// extrude
n.x = vsrc->nx * extrude;
n.y = vsrc->ny * extrude;
n.z = vsrc->nz * extrude;
// calc local axes
vx.Scale3(mat.i,size);
vy.Scale3(mat.j,size);
// gen verts
vp[0].x = vsrc->x - vx.x + vy.x + n.x;
vp[0].y = vsrc->y - vx.y + vy.y + n.y;
vp[0].z = vsrc->z - vx.z + vy.z + n.z;
vp[0].c0 = vsrc->c;
vp[1].x = vsrc->x + vx.x + vy.x + n.x;
vp[1].y = vsrc->y + vx.y + vy.y + n.y;
vp[1].z = vsrc->z + vx.z + vy.z + n.z;
vp[1].c0 = vsrc->c;
vp[2].x = vsrc->x + vx.x - vy.x + n.x;
vp[2].y = vsrc->y + vx.y - vy.y + n.y;
vp[2].z = vsrc->z + vx.z - vy.z + n.z;
vp[2].c0 = vsrc->c;
vp[3].x = vsrc->x - vx.x - vy.x + n.x;
vp[3].y = vsrc->y - vx.y - vy.y + n.y;
vp[3].z = vsrc->z - vx.z - vy.z + n.z;
vp[3].c0 = vsrc->c;
vp += 4;
}
// draw
sSystem->GeoEnd(handle);
sSystem->GeoDraw(handle);
sSystem->GeoFlush(handle);
// update counters
count -= vc;
vind += vc;
}
}
break;
#endif
default:
sVERIFYFALSE;
}
}
}
/****************************************************************************/
// Create vertex/index buffers for the mesh and free Vertex array if it's
// no longer required.
void EngMesh::Preload()
{
if(Preloaded)
return;
sMaterialEnv env;
env.Init();
sViewport vp;
vp.Init();
vp.Window.Init(0,0,1,1);
sSystem->SetViewport(vp);
sBool mayDeleteVert = BoneInfos ? sFALSE : sTRUE;
EngPaintInfo paintInfo;
paintInfo.LightId = 0;
paintInfo.LightPos.Init(0,0,0,0);
paintInfo.LightRange = 1;
paintInfo.StencilFlags = 0;
paintInfo.BoneData = 0;
// paint everything to preload
for(sInt i=1;i<Mtrl.Count;i++)
{
GenMaterial *mtrl = Mtrl[i].Material;
for(sInt j=0;j<mtrl->Passes.Count;j++)
{
GenMaterialPass *pass = &mtrl->Passes[j];
if(pass->Program != MPP_STATIC && pass->Program != MPP_SHADOW)
mayDeleteVert = sFALSE;
else
{
pass->Mtrl->Set(env);
PaintJob(Mtrl[i].JobIds[j],pass,paintInfo);
}
}
}
// now, we can delete data that has become unnecessary if the mesh
// is static
if(!BoneInfos)
{
for(sInt i=0;i<Jobs.Count;i++)
{
switch(Jobs[i].Program)
{
case MPP_STATIC:
sDeleteArray(Jobs[i].VertexBuffer);
sDeleteArray(Jobs[i].IndexBuffer);
break;
case MPP_SHADOW:
sDeleteArray(Jobs[i].VertexBuffer);
break;
}
}
}
// we can also delete vertex data if it's not needed anymore
if(mayDeleteVert)
{
_aligned_free(Vert);
Vert = 0;
}
Preloaded = sTRUE;
}
/****************************************************************************/
sMatrix *EngMesh::GetInstanceMatrices(sInt count)
{
InstanceCount = count;
if(InstanceAlloc < count)
{
InstanceAlloc = count;
_aligned_free(Instances);
Instances = (sMatrix *) _aligned_malloc(count * sizeof(sMatrix),16);
}
return Instances;
}
void EngMesh::UpdateInstanceCount(sInt count)
{
sVERIFY(count <= InstanceCount);
InstanceCount = count;
}
/****************************************************************************/
void EngMesh::SetWireColors(sU32 *colors)
{
if(WireInit)
{
((sSimpleMaterial *) MtrlWire[0]->Passes[0].Mtrl)->Color = colors[EWC_WIRELOW];
((sSimpleMaterial *) MtrlWire[0]->Passes[1].Mtrl)->Color = colors[EWC_WIREHIGH];
((sSimpleMaterial *) MtrlWire[1]->Passes[0].Mtrl)->Color = colors[EWC_FACE];
((sSimpleMaterial *) MtrlWire[2]->Passes[0].Mtrl)->Color = colors[EWC_HIDDEN];
((sSimpleMaterial *) MtrlWire[3]->Passes[0].Mtrl)->Color = colors[EWC_COLADD];
((sSimpleMaterial *) MtrlWire[3]->Passes[1].Mtrl)->Color = colors[EWC_COLSUB];
((sSimpleMaterial *) MtrlWire[3]->Passes[2].Mtrl)->Color = colors[EWC_COLZONE];
((sSimpleMaterial *) MtrlWire[4]->Passes[0].Mtrl)->Color = colors[EWC_VERTEX];
}
}
/****************************************************************************/
sInt EngMesh::AddMaterial(GenMaterial *genMtrl,sInt pass)
{
// save id, create new material
sInt id = Mtrl.Count;
GenMeshMtrl *mtrl = Mtrl.Add();
// fill values and return
mtrl->Material = genMtrl;
mtrl->Material->AddRef();
mtrl->Pass = pass;
return id;
}
sInt EngMesh::AddJob(sInt mtrlId,sInt program)
{
// save id, create new job
sInt id = Jobs.Count;
Job *job = Jobs.Add();
// fill out job and return
job->Init();
job->MtrlId = mtrlId;
job->Program = program;
return id;
}
/****************************************************************************/
#if sLINK_FATMESH
// Convert a meshes' vertices into a compact vertex list.
void EngMesh::FillVertexBuffer(GenMesh *mesh)
{
_aligned_free(Vert);
//delete[] Vert;
VertCount = mesh->Vert.Count;
Vert = (sVertexTSpace3Big *) _aligned_malloc(mesh->Vert.Count * sizeof(sVertexTSpace3Big),16);
//Vert = new sVertexTSpace3Big[mesh->Vert.Count];
sVertexTSpace3Big *outVert = Vert;
sVector *srcVert = mesh->VertBuf;
sInt vuv = mesh->VertMap(sGMI_UV0);
sInt vnr = mesh->VertMap(sGMI_NORMAL);
sInt vtn = mesh->VertMap(sGMI_TANGENT);
sInt vco = mesh->VertMap(sGMI_COLOR0);
for(sInt i=0;i<mesh->Vert.Count;i++)
{
outVert->x = srcVert[0].x;
outVert->y = srcVert[0].y;
outVert->z = srcVert[0].z;
outVert->nx = srcVert[vnr].x;
outVert->ny = srcVert[vnr].y;
outVert->nz = srcVert[vnr].z;
outVert->sx = srcVert[vtn].x;
outVert->sy = srcVert[vtn].y;
outVert->sz = srcVert[vtn].z;
outVert->c = PackColor (srcVert + vco);
outVert->u = srcVert[vuv].x;
outVert->v = srcVert[vuv].y;
srcVert += mesh->VertSize();
outVert++;
}
}
// Compute bounding boxes for all parts in the mesh
void EngMesh::CalcPartBoundingBoxes(GenMesh *mesh)
{
delete[] PartBBs;
PartCount = mesh->Parts.Count;
PartBBs = new sF32[PartCount * 6];
sF32 *outPtr = PartBBs;
for(sInt i=0;i<PartCount;i++)
{
sInt end = (i == PartCount-1) ? mesh->Face.Count : mesh->Parts[i+1];
sVector min,max;
min.Init( 1e+20f, 1e+20f, 1e+20f,1.0f);
max.Init(-1e+20f,-1e+20f,-1e+20f,1.0f);
// find min/max values for the current part
for(sInt j=mesh->Parts[i];j<end;j++)
{
sInt e,ee;
e = ee = mesh->Face[j].Edge;
do
{
const sVector &v = mesh->VertPos(mesh->GetVertId(e));
min.x = sMin(min.x,v.x);
min.y = sMin(min.y,v.y);
min.z = sMin(min.z,v.z);
max.x = sMax(max.x,v.x);
max.y = sMax(max.y,v.y);
max.z = sMax(max.z,v.z);
e = mesh->NextFaceEdge(e);
}
while(e != ee);
}
// output box
for(sInt j=0;j<3;j++)
{
*outPtr++ = (max[j] + min[j]) * 0.5f;
*outPtr++ = (max[j] - min[j]) * 0.5f;
}
}
}
// Calculates a bounding box for the given material id
void EngMesh::CalcBoundingBox(GenMesh *mesh,sInt mtrlId,sAABox &box)
{
sVector min,max;
min.Init( 1e+20f, 1e+20f, 1e+20f,1.0f);
max.Init(-1e+20f,-1e+20f,-1e+20f,1.0f);
// find extents for this material
for(sInt i=0;i<mesh->Face.Count;i++)
{
sInt faceMtrl = mesh->Face[i].Material;
if(!faceMtrl || mtrlId != -1 && faceMtrl != mtrlId)
continue;
sInt e,ee;
e = ee = mesh->Face[i].Edge;
do
{
const sVector &v = mesh->VertPos(mesh->GetVertId(e));
min.x = sMin(min.x,v.x);
min.y = sMin(min.y,v.y);
min.z = sMin(min.z,v.z);
max.x = sMax(max.x,v.x);
max.y = sMax(max.y,v.y);
max.z = sMax(max.z,v.z);
e = mesh->NextFaceEdge(e);
}
while(e != ee);
}
box.Min = min;
box.Max = max;
}
// Prepares all materials for the given mesh
void EngMesh::PrepareJobs(GenMesh *mesh)
{
sBool firstShadowJob = sTRUE;
sBool *hasShadow = new sBool[Mtrl.Count];
hasShadow[0] = sFALSE;
for(sInt i=1;i<Mtrl.Count;i++)
{
GenMaterial *mtrl = Mtrl[i].Material;
hasShadow[i] = sFALSE;
for(sInt j=0;j<mtrl->Passes.Count;j++)
if(mtrl->Passes[j].Program == MPP_SHADOW)
hasShadow[i] = sTRUE;
}
for(sInt i=1;i<Mtrl.Count;i++)
{
GenMaterial *mtrl = Mtrl[i].Material;
// Reuse prepared jobs if possible
sInt jobIds[MPP_MAX];
for(sInt j=0;j<MPP_MAX;j++)
jobIds[j] = -1;
for(sInt j=0;j<mtrl->Passes.Count;j++)
{
GenMaterialPass *pass = &mtrl->Passes[j];
sInt program = pass->Program;
sInt jobId = jobIds[program];
if(jobId == -1) // we haven't seen this program yet
{
jobId = AddJob(i,program);
jobIds[program] = jobId;
PrepareJob(&Jobs[jobId],mesh,firstShadowJob,hasShadow);
}
Mtrl[i].JobIds[j] = jobId;
}
}
delete[] hasShadow;
}
// Prepares a single mesh job
void EngMesh::PrepareJob(Job *job,GenMesh *mesh,sBool &firstShadowJob,const sBool *hasShadow)
{
// check whether we understand the program used
sInt program = job->Program;
sBool isShadow = program == MPP_SHADOW;
if(isShadow && !firstShadowJob)
return;
// compute bounding box
CalcBoundingBox(mesh,isShadow ? -1 : job->MtrlId,job->BBox);
switch(program)
{
case MPP_SPRITES:
PrepareSpriteJob(job,mesh);
return;
#if THICKLINES
case MPP_THICKLINES:
PrepareThickLinesJob(job,mesh);
return;
#endif
}
// count faces, edges and vertices
sInt faceCount,triCount,edgeCount,vertCount;
mesh->All2Sel(0,MAS_EDGE|MAS_FACE|MAS_VERT);
faceCount = 0;
triCount = 0;
for(sInt i=0;i<mesh->Face.Count;i++)
{
mesh->Face[i].Temp = faceCount;
sInt mtrl = mesh->Face[i].Material;
if((isShadow && mesh->Face[i].Used && hasShadow[mtrl]) || (!isShadow && mtrl == job->MtrlId))
//if(mesh->Face[i].Material == job->MtrlId && (!isShadow || mesh->Face[i].Used))
{
mesh->Face[i].Select = 1;
faceCount++;
sInt e,ee,ec;
e = ee = mesh->Face[i].Edge;
ec = 0;
do
{
ec++;
e = mesh->NextFaceEdge(e);
}
while(e != ee);
triCount += ec - 2;
}
}
edgeCount = 0;
for(sInt i=0;i<mesh->Edge.Count;i++)
{
if(mesh->GetFace(i*2+0)->Select || mesh->GetFace(i*2+1)->Select)
{
mesh->Edge[i].Temp[0] = edgeCount;
edgeCount++;
mesh->GetVert(i*2+0)->Select = 1;
mesh->GetVert(i*2+1)->Select = 1;
}
}
vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
{
mesh->Vert[i].Temp = vertCount;
vertCount += mesh->Vert[i].Select;
}
// build them buffers!
job->Alloc(vertCount,triCount * 3,isShadow ? edgeCount : 0,
isShadow ? triCount : 0,mesh->Parts.Count);
#if SHADOWS
// prepare face planes if necessary
if(isShadow)
{
job->PlaneCount = faceCount+1;
job->Planes = new sVector[faceCount+1];
job->Planes[0].Init(0,0,0,0);
for(sInt i=0;i<mesh->Face.Count;i++)
if(mesh->Face[i].Select)
mesh->CalcFacePlane(job->Planes[mesh->Face[i].Temp+1],i);
}
#endif
// generate vertex buffer
for(sInt i=0;i<mesh->Vert.Count;i++)
{
if(mesh->Vert[i].Select)
job->VertexBuffer[mesh->Vert[i].Temp] = i;
}
// generate index+edge buffers
sInt curPart = 0;
faceCount = 0;
triCount = 0;
edgeCount = 0;
vertCount = 0;
for(sInt faceInd=0;faceInd<mesh->Face.Count;faceInd++)
{
// update current part
while(curPart < mesh->Parts.Count-1 && faceInd >= mesh->Parts[curPart+1])
{
job->FacePartEnd[curPart] = faceCount;
job->EdgePartEnd[curPart] = edgeCount;
curPart++;
}
GenMeshFace *curFace = &mesh->Face[faceInd];
if(!curFace->Select)
continue;
// go round face, write edges and triangulate
sInt v0 = -1,v1 = -1,v2 = -1;
sInt count = 0,e,ee;
e = ee = curFace->Edge;
do
{
// vertex
count++;
v1 = v2;
v2 = mesh->GetVert(e)->Temp;
if(v0 == -1)
v0 = v2;
if(count >= 3) // write a face
{
job->IndexBuffer[triCount*3+0] = v0;
job->IndexBuffer[triCount*3+1] = v1;
job->IndexBuffer[triCount*3+2] = v2;
if(isShadow)
{
job->IndexBuffer[triCount*3+0] *= 2;
job->IndexBuffer[triCount*3+1] *= 2;
job->IndexBuffer[triCount*3+2] *= 2;
job->FaceMap[triCount] = faceCount +1;
}
triCount++;
v1 = v2;
}
#if SHADOWS
// edge
if(isShadow && !mesh->GetEdge(e)->Select)
{
mesh->GetEdge(e)->Select = 1;
SilEdge *edge = &job->Edges[edgeCount++];
edge->Vert[0] = mesh->GetVert(e)->Temp * 2;
edge->Vert[1] = mesh->GetVert(mesh->NextFaceEdge(e))->Temp * 2;
edge->Face[0] = mesh->GetFace(e)->Temp + 1;
edge->Face[1] = mesh->GetFace(e^1)->Temp + 1;
}
#endif
e = mesh->NextFaceEdge(e);
}
while(e != ee);
faceCount++;
}
sVERIFY(triCount*3 == job->IndexCount);
if(isShadow)
firstShadowJob = sFALSE;
if(program == MPP_STATIC || program == MPP_INSTANCES || program == MPP_INSTANCES_SH)
{
#if !sINTRO
/*job->OptimizeIndices();
job->ReIndexVertices();*/
#endif
}
}
// Prepares a sprite job.
void EngMesh::PrepareSpriteJob(Job *job,GenMesh *mesh)
{
mesh->All2Sel(1,MAS_FACE);
mesh->Face2Vert();
// count vertices
sInt vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
if(mesh->Vert[i].First == i && mesh->Vert[i].Select)
vertCount++;
// create batches
job->Alloc(vertCount,0,0,0,0);
vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
if(mesh->Vert[i].First == i && mesh->Vert[i].Select)
job->VertexBuffer[vertCount++] = i;
}
// Prepares a thick lines job.
void EngMesh::PrepareThickLinesJob(Job *job,GenMesh *mesh)
{
sInt edgeCount = 0;
// select interesting edges and count them
for(sInt i=0;i<mesh->Edge.Count;i++)
{
mesh->Edge[i].Select = mesh->GetFace(i*2)->Material == job->MtrlId
|| mesh->GetFace(i*2+1)->Material == job->MtrlId;
edgeCount += mesh->Edge[i].Select;
}
// build them vertex buffers
job->Alloc(edgeCount*2,0,0,0,0);
edgeCount = 0;
for(sInt i=0;i<mesh->Edge.Count;i++)
{
GenMeshEdge *curEdge = &mesh->Edge[i];
if(curEdge->Select)
{
job->VertexBuffer[edgeCount*2+0] = curEdge->Vert[0];
job->VertexBuffer[edgeCount*2+1] = curEdge->Vert[1];
edgeCount++;
}
}
}
/****************************************************************************/
void EngMesh::AddWireEdgeJob(GenMesh *mesh,sInt mtrl,sU32 mask)
{
sInt count[2];
// count selected edges
count[0] = 0;
count[1] = 0;
for(sInt i=0;i<mesh->Edge.Count;i++)
{
if(mesh->GetFace(i*2)->Material || mesh->GetFace(i*2+1)->Material)
{
count[0]++;
if(mesh->Edge[i].Mask & mask)
count[1]++;
}
}
count[0] -= count[1];
// for the selected and unselected case...
for(sInt sel=0;sel<2;sel++)
{
// add the actual job
sInt jobId = AddJob(mtrl,MPP_WIRELINES);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[sel] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// fill out the batches
job->Alloc(count[sel]*2,0,0,0,0);
sInt counter = 0;
for(sInt edgeInd=0;edgeInd<mesh->Edge.Count;edgeInd++)
{
if(mesh->GetFace(edgeInd*2)->Material || mesh->GetFace(edgeInd*2+1)->Material)
{
GenMeshEdge *edge = &mesh->Edge[edgeInd];
if(((edge->Mask & mask) != 0) == sel)
{
job->VertexBuffer[counter*2+0] = edge->Vert[0];
job->VertexBuffer[counter*2+1] = edge->Vert[1];
counter++;
}
}
}
sVERIFY(counter == count[sel]);
}
}
void EngMesh::AddWireFaceJob(GenMesh *mesh,sInt mtrl,sU32 mask,sU32 compare)
{
// add job
sInt jobId = AddJob(mtrl,MPP_STATIC);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[0] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count vertices+triangles
sInt triCount = 0,vertCount = 0;
mesh->All2Sel(0,MAS_VERT|MAS_FACE);
for(sInt faceInd = 0;faceInd < mesh->Face.Count;faceInd++)
{
GenMeshFace *curFace = &mesh->Face[faceInd];
if(curFace->Material && ((curFace->Mask & mask) != compare))
{
sInt e,ee,count;
curFace->Select = 1;
count = 0;
e = ee = curFace->Edge;
do
{
count++;
if(!mesh->GetVert(e)->Select)
{
mesh->GetVert(e)->Select = 1;
vertCount++;
}
e = mesh->NextFaceEdge(e);
}
while(e != ee);
triCount += count - 2;
}
}
// alloc job, create vertex buffer
job->Alloc(vertCount,triCount*3,0,0,0);
vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
{
if(mesh->Vert[i].Select)
{
mesh->Vert[i].Temp = vertCount;
job->VertexBuffer[vertCount++] = i;
}
}
sVERIFY(vertCount == job->VertexCount);
// create index buffer
triCount = 0;
for(sInt faceInd=0;faceInd<mesh->Face.Count;faceInd++)
{
GenMeshFace *curFace = &mesh->Face[faceInd];
if(!curFace->Select)
continue;
sInt v0,v1,v2 = -1;
sInt e,ee,count;
e = ee = curFace->Edge;
count = 0;
do
{
count++;
v1 = v2;
v2 = mesh->GetVert(e)->Temp;
if(count == 1)
v0 = v2;
else if(count >= 3)
{
job->IndexBuffer[triCount*3+0] = v0;
job->IndexBuffer[triCount*3+1] = v1;
job->IndexBuffer[triCount*3+2] = v2;
triCount++;
}
e = mesh->NextFaceEdge(e);
}
while(e != ee);
}
sVERIFY(triCount * 3 == job->IndexCount);
}
void EngMesh::AddWireVertJob(GenMesh *mesh,sInt mtrl,sU32 mask)
{
// add job
sInt jobId = AddJob(mtrl,MPP_WIREVERTEX);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[0] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count vertices
sInt vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
if(mesh->Vert[i].Mask & mask)
vertCount++;
// create job
job->Alloc(vertCount,0,0,0,0);
vertCount = 0;
for(sInt i=0;i<mesh->Vert.Count;i++)
if(mesh->Vert[i].Mask & mask)
job->VertexBuffer[vertCount++] = i;
}
void EngMesh::AddWireCollisionJob(GenMesh *mesh,sInt mtrl)
{
static const sU8 cubeTable[6*4] =
{
0,2,3,1, 2,6,7,3, 3,7,5,1, 4,0,1,5, 2,0,4,6, 7,6,4,5
};
for(sInt type=0;type<3;type++) // for each collision type
{
// add job
sInt jobId = AddJob(mtrl,MPP_STATIC);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[type] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count collision cubes
sInt collCubes = 0;
for(sInt i=0;i<mesh->Coll.Count;i++)
if((mesh->Coll[i].Mode & 3) == type)
collCubes++;
// create job
job->Alloc(collCubes * 8,collCubes * 36,0,0,0);
// create vertex and index buffer
for(sInt collInd=0;collInd<mesh->Coll.Count;collInd++)
{
GenMeshColl *curColl = &mesh->Coll[collInd];
if((curColl->Mode & 3) != type)
continue;
sInt basev = collInd*8;
// vertices
for(sInt j=0;j<8;j++)
job->VertexBuffer[basev+j] = curColl->Vert[j];
// indices
for(sInt j=0;j<6;j++)
{
sInt basei = collInd*36 + j*6;
job->IndexBuffer[basei+0] = basev + cubeTable[j*4+0];
job->IndexBuffer[basei+1] = basev + cubeTable[j*4+1];
job->IndexBuffer[basei+2] = basev + cubeTable[j*4+2];
job->IndexBuffer[basei+3] = basev + cubeTable[j*4+0];
job->IndexBuffer[basei+4] = basev + cubeTable[j*4+2];
job->IndexBuffer[basei+5] = basev + cubeTable[j*4+3];
}
}
}
}
#endif
/****************************************************************************/
#if sLINK_MINMESH
void EngMesh::FillVertexBuffer(GenMinMesh *mesh)
{
_aligned_free(Vert);
//delete[] Vert;
VertCount = mesh->Vertices.Count;
//Vert = new sVertexTSpace3Big[mesh->Vertices.Count];
Vert = (sVertexTSpace3Big *) _aligned_malloc(mesh->Vertices.Count*sizeof(sVertexTSpace3Big),16);
sVertexTSpace3Big *outVert = Vert;
GenMinVert *inVert = &mesh->Vertices[0];
for(sInt i=0;i<mesh->Vertices.Count;i++)
{
outVert->x = inVert->Pos.x;
outVert->y = inVert->Pos.y;
outVert->z = inVert->Pos.z;
outVert->nx = inVert->Normal.x;
outVert->ny = inVert->Normal.y;
outVert->nz = inVert->Normal.z;
outVert->sx = inVert->Tangent.x;
outVert->sy = inVert->Tangent.y;
outVert->sz = inVert->Tangent.z;
outVert->c = inVert->Color;
outVert->u = inVert->UV[0][0];
outVert->v = inVert->UV[0][1];
outVert++;
inVert++;
}
// find out whether we need bones
sBool needBones = sFALSE;
for(sInt i=1;i<mesh->Clusters.Count;i++)
if(mesh->Clusters[i].AnimType == 2)
needBones = sTRUE;
// create bone structures
if(needBones)
{
if(!CompletelyRigid)
{
BoneInfos = new BoneInfo[mesh->Vertices.Count];
BoneInfo *outInfo = BoneInfos;
inVert = &mesh->Vertices[0];
for(sInt i=0;i<mesh->Vertices.Count;i++)
{
sInt boneCount = inVert->BoneCount;
for(sInt j=0;j<4;j++)
{
outInfo->Weight[j] = inVert->Weights[j];
outInfo->Matrix[j] = inVert->Matrix[j];
}
// (insertion) sort bones by weight
for(sInt j=1;j<boneCount;j++)
{
sF32 w = outInfo->Weight[j];
sU16 m = outInfo->Matrix[j];
sInt k = j;
while(k && sFAbs(outInfo->Weight[k-1]) < sFAbs(w))
{
outInfo->Weight[k] = outInfo->Weight[k-1];
outInfo->Matrix[k] = outInfo->Matrix[k-1];
k--;
}
outInfo->Weight[k] = w;
outInfo->Matrix[k] = m;
}
// remove weights that don't have a significant effect
sF32 wSum = 1.0f;
static const sF32 thresh = 1.0f/64.0f;
while(boneCount && sFAbs(outInfo->Weight[boneCount-1]) < thresh)
wSum -= outInfo->Weight[--boneCount];
for(sInt i=0;i<boneCount;i++)
outInfo->Weight[i] /= wSum;
for(sInt i=boneCount;i<4;i++)
{
outInfo->Weight[i] = 0;
outInfo->Matrix[i] = 0;
}
outInfo++;
inVert++;
}
}
else // CompletelyRigid is set
{
MatrixInds = new sU16[mesh->Vertices.Count];
for(sInt i=0;i<mesh->Vertices.Count;i++)
MatrixInds[i] = mesh->Vertices[i].Matrix[0];
}
}
}
void EngMesh::CalcBoundingBox(GenMinMesh *mesh,sInt clusterId,sAABox &box)
{
sVector min,max;
min.Init( 1e+20f, 1e+20f, 1e+20f,1.0f);
max.Init(-1e+20f,-1e+20f,-1e+20f,1.0f);
// find extents of this cluster
for(sInt i=0;i<mesh->Faces.Count;i++)
{
GenMinFace *face = &mesh->Faces[i];
sInt faceCluster = face->Cluster;
if(!faceCluster || clusterId != -1 && faceCluster != clusterId)
continue;
for(sInt j=0;j<face->Count;j++)
{
const GenMinVert *v = &mesh->Vertices[face->Vertices[j]];
min.x = sMin(min.x,v->Pos.x);
min.y = sMin(min.y,v->Pos.y);
min.z = sMin(min.z,v->Pos.z);
max.x = sMax(max.x,v->Pos.x);
max.y = sMax(max.y,v->Pos.y);
max.z = sMax(max.z,v->Pos.z);
}
}
box.Min = min;
box.Max = max;
}
void EngMesh::PrepareJobs(GenMinMesh *mesh)
{
sBool firstShadowJob = sTRUE;
sBool *hasShadow = new sBool[mesh->Clusters.Count];
Mtrl.Resize(mesh->Clusters.Count);
hasShadow[0] = sFALSE;
for(sInt i=1;i<mesh->Clusters.Count;i++)
{
GenMaterial *mtrl = mesh->Clusters[i].Mtrl;
hasShadow[i] = sFALSE;
for(sInt j=0;j<mtrl->Passes.Count;j++)
if(mtrl->Passes[j].Program == MPP_SHADOW)
hasShadow[i] = sTRUE;
}
for(sInt i=0;i<mesh->Clusters.Count;i++)
{
GenMinCluster *cluster = &mesh->Clusters[i];
GenMeshMtrl *outMtrl = &Mtrl[i];
GenMaterial *mtrl = cluster->Mtrl;
outMtrl->Pass = cluster->RenderPass;
outMtrl->Material = mtrl;
if(!i) // first material is null material
continue;
mtrl->AddRef();
// Reuse jobs if possible
sInt jobIds[MPP_MAX];
for(sInt j=0;j<MPP_MAX;j++)
jobIds[j] = -1;
for(sInt j=0;j<mtrl->Passes.Count;j++)
{
GenMaterialPass *pass = &mtrl->Passes[j];
sInt program = pass->Program;
sInt jobId = jobIds[program];
if(jobId == -1) // we haven't seen this program yet
{
jobId = AddJob(i,program);
jobIds[program] = jobId;
Jobs[jobId].AnimType = cluster->AnimType;
Jobs[jobId].AnimMatrix = cluster->AnimMatrix;
PrepareJob(&Jobs[jobId],mesh,firstShadowJob,hasShadow);
}
outMtrl->JobIds[j] = jobId;
}
}
delete[] hasShadow;
}
void EngMesh::PrepareJob(Job *job,GenMinMesh *mesh,sBool &firstShadowJob,const sBool *hasShadow)
{
// prepare program, calc bounding box
sInt program = job->Program;
sBool isShadow = program == MPP_SHADOW;
if(isShadow && !firstShadowJob)
return;
CalcBoundingBox(mesh,isShadow ? -1 : job->MtrlId,job->BBox);
switch(program)
{
case MPP_STATIC:
case MPP_SHADOW:
case MPP_INSTANCES:
case MPP_INSTANCES_SH:
break;
case MPP_SPRITES:
PrepareSpriteJob(job,mesh);
return;
#if THICKLINES
case MPP_THICKLINES:
PrepareThickLinesJob(job,mesh);
#endif
return;
default:
return;
}
// count faces, edges and vertices
sInt faceCount,triCount,edgeCount,vertCount;
sInt *vertMap = new sInt[mesh->Vertices.Count];
sInt *facePlaneMap = new sInt[mesh->Faces.Count];
memset(vertMap,0xff,mesh->Vertices.Count * sizeof(sInt));
memset(facePlaneMap,0,mesh->Faces.Count * sizeof(sInt));
faceCount = 0;
triCount = 0;
edgeCount = 0;
vertCount = 0;
for(sInt i=0;i<mesh->Faces.Count;i++)
{
GenMinFace *curFace = &mesh->Faces[i];
curFace->Temp = 0;
if(curFace->Count < 3)
continue;
if((isShadow && !(curFace->Flags & 1) && hasShadow[curFace->Cluster]) || (!isShadow && curFace->Cluster == job->MtrlId))
{
curFace->Temp = 1;
facePlaneMap[i] = ++faceCount;
triCount += curFace->Count - 2;
for(sInt j=0;j<curFace->Count;j++)
{
sInt v = curFace->Vertices[j];
sInt adj = curFace->Adjacent[j];
if(vertMap[v] == -1)
vertMap[v] = vertCount++;
if(i < (adj >> 3))
//if((adj >> 3) < i)
edgeCount++;
}
}
}
// build the buffers
job->Alloc(vertCount,triCount * 3,isShadow ? edgeCount : 0,
isShadow ? triCount : 0,1);
// prepare face planes if necessary
#if SHADOWS
if(isShadow)
{
job->PlaneCount = faceCount+1;
job->Planes = new sVector[faceCount+1];
job->Planes[0].Init(0,0,0,0);
for(sInt i=0;i<mesh->Faces.Count;i++)
{
sInt planeInd = facePlaneMap[i];
if(planeInd)
{
GenMinFace *face = &mesh->Faces[i];
GenMinVector *pt = &mesh->Vertices[face->Vertices[0]].Pos;
sVector *plane = &job->Planes[planeInd];
plane->x = face->Normal.x;
plane->y = face->Normal.y;
plane->z = face->Normal.z;
plane->w = -face->Normal.Dot(*pt);
}
}
}
#endif
// generate vertex buffer
for(sInt i=0;i<mesh->Vertices.Count;i++)
{
sInt map = vertMap[i];
if(map != -1)
job->VertexBuffer[map] = i;
}
// generate index+edge buffers
faceCount = 0;
triCount = 0;
edgeCount = 0;
vertCount = 0;
for(sInt faceInd=0;faceInd<mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
if(!curFace->Temp)
continue;
sInt v0,v1,v2 = -1;
for(sInt i=0;i<curFace->Count;i++)
{
// vertex+face
v1 = v2;
v2 = vertMap[curFace->Vertices[i]];
if(i == 0)
v0 = v2;
else if(i >= 2)
{
job->IndexBuffer[triCount*3+0] = v0;
job->IndexBuffer[triCount*3+1] = v1;
job->IndexBuffer[triCount*3+2] = v2;
if(isShadow)
{
job->IndexBuffer[triCount*3+0] *= 2;
job->IndexBuffer[triCount*3+1] *= 2;
job->IndexBuffer[triCount*3+2] *= 2;
job->FaceMap[triCount] = faceCount + 1;
}
triCount++;
}
#if SHADOWS
// edge
sInt adjFace = curFace->Adjacent[i] >> 3;
if(isShadow && faceInd < adjFace)
{
sInt ni = i == curFace->Count-1 ? 0 : i+1;
SilEdge *edge = &job->Edges[edgeCount++];
edge->Vert[0] = v2 * 2;
edge->Vert[1] = vertMap[curFace->Vertices[ni]] * 2;
edge->Face[0] = facePlaneMap[faceInd];
edge->Face[1] = adjFace != -1 ? facePlaneMap[adjFace] : 0;
}
#endif
}
faceCount++;
}
delete[] facePlaneMap;
delete[] vertMap;
if(isShadow)
firstShadowJob = sFALSE;
#if !sINTRO
if(program == MPP_STATIC || program == MPP_INSTANCES || program == MPP_INSTANCES_SH)
{
job->OptimizeIndices();
job->ReIndexVertices();
}
#endif
}
void EngMesh::PrepareSpriteJob(Job *job,GenMinMesh *mesh)
{
sInt *merge = mesh->CalcMergeVerts();
sInt vertCount = 0;
for(sInt i=0;i<mesh->Vertices.Count;i++)
if(merge[i] == i)
vertCount++;
job->Alloc(vertCount,0,0,0,0);
vertCount = 0;
for(sInt i=0;i<mesh->Vertices.Count;i++)
if(merge[i] == i)
job->VertexBuffer[vertCount++] = i;
}
void EngMesh::PrepareThickLinesJob(Job *job,GenMinMesh *mesh)
{
sInt edgeCount = 0;
// count interesting edges
for(sInt faceInd=0;faceInd<mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
for(sInt j=0;j<curFace->Count;j++)
{
sInt adjFace = curFace->Adjacent[j] >> 3;
if(adjFace < faceInd)
{
if(curFace->Cluster == job->MtrlId ||
adjFace != -1 && mesh->Faces[adjFace].Cluster == job->MtrlId)
edgeCount++;
}
}
}
// build vertex buffer
job->Alloc(edgeCount*2,0,0,0,0);
edgeCount = 0;
for(sInt faceInd=0;faceInd<mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
sInt count = curFace->Count;
for(sInt j=0;j<count;j++)
{
sInt adjFace = curFace->Adjacent[j] >> 3;
if(adjFace < faceInd)
{
if(curFace->Cluster == job->MtrlId ||
adjFace != -1 && mesh->Faces[adjFace].Cluster == job->MtrlId)
{
job->VertexBuffer[edgeCount*2+0] = curFace->Vertices[j];
job->VertexBuffer[edgeCount*2+1] = curFace->Vertices[(j+1) % count];
edgeCount++;
}
}
}
}
}
#endif
/****************************************************************************/
#if sLINK_MINMESH
void EngMesh::AddWireEdgeJob(GenMinMesh *mesh,sInt mtrl,sU32 mask)
{
// add the job
sInt jobId = AddJob(mtrl,MPP_WIRELINES);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[0] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count edges
sInt edgeCount = 0;
for(sInt faceInd=0;faceInd < mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
if(!curFace->Cluster || curFace->Count <= 2)
continue;
for(sInt i=0;i<curFace->Count;i++)
if((curFace->Adjacent[i] >> 3) < faceInd)
edgeCount++;
}
// fill the vertex buffer
job->Alloc(edgeCount*2,0,0,0,0);
edgeCount = 0;
for(sInt faceInd=0;faceInd < mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
if(!curFace->Cluster || curFace->Count <= 2)
continue;
sInt v0, v1 = curFace->Vertices[curFace->Count-1];
sInt f0, f1 = curFace->Adjacent[curFace->Count-1] >> 3;
for(sInt i=0;i<curFace->Count;i++)
{
v0 = v1;
v1 = curFace->Vertices[i];
f0 = f1;
f1 = curFace->Adjacent[i] >> 3;
// only add each edge in one direction
if(f0 < faceInd)
{
job->VertexBuffer[edgeCount*2+0] = v0;
job->VertexBuffer[edgeCount*2+1] = v1;
edgeCount++;
}
}
}
sVERIFY(edgeCount * 2 == job->VertexCount);
// the "selected" job is unused for this mesh type
Mtrl[mtrl].JobIds[1] = AddJob(mtrl,MPP_WIRELINES);
}
void EngMesh::AddWireFaceJob(GenMinMesh *mesh,sInt mtrl,sU32 mask,sU32 compare)
{
// add job
sInt jobId = AddJob(mtrl,MPP_STATIC);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[0] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count vertices and triangles
sInt vertCount=0,triCount=0;
sInt *vertRemap = new sInt[mesh->Vertices.Count];
memset(vertRemap,0xff,mesh->Vertices.Count*sizeof(sInt));
for(sInt faceInd=0;faceInd<mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
if(curFace->Cluster && curFace->Count >= 2 && ((curFace->Select & mask) != compare))
{
triCount += curFace->Count - 2;
for(sInt i=0;i<curFace->Count;i++)
{
sInt v = curFace->Vertices[i];
if(vertRemap[v] == -1)
vertRemap[v] = vertCount++;
}
}
}
// create job and vertexbuffer
job->Alloc(vertCount,triCount * 3,0,0,0);
for(sInt i=0;i<mesh->Vertices.Count;i++)
if(vertRemap[i] != -1)
job->VertexBuffer[vertRemap[i]] = i;
// create index buffer
triCount = 0;
for(sInt faceInd=0;faceInd<mesh->Faces.Count;faceInd++)
{
GenMinFace *curFace = &mesh->Faces[faceInd];
if(curFace->Cluster && curFace->Count >= 2 && ((curFace->Select & mask) != compare))
{
sInt v0 = vertRemap[curFace->Vertices[0]];
sInt v2 = vertRemap[curFace->Vertices[1]];
sInt v1;
for(sInt j=2;j<curFace->Count;j++)
{
v1 = v2;
v2 = vertRemap[curFace->Vertices[j]];
job->IndexBuffer[triCount*3+0] = v0;
job->IndexBuffer[triCount*3+1] = v1;
job->IndexBuffer[triCount*3+2] = v2;
triCount++;
}
}
}
sVERIFY(triCount * 3 == job->IndexCount);
delete[] vertRemap;
}
void EngMesh::AddWireVertJob(GenMinMesh *mesh,sInt mtrl,sU32 mask)
{
// add job
sInt jobId = AddJob(mtrl,MPP_WIREVERTEX);
Job *job = &Jobs[jobId];
Mtrl[mtrl].JobIds[0] = jobId;
CalcBoundingBox(mesh,-1,job->BBox);
// count selected vertices
sInt vertCount = 0;
for(sInt i=0;i<mesh->Vertices.Count;i++)
if(mesh->Vertices[i].Select)
vertCount++;
// create job
job->Alloc(vertCount,0,0,0,0);
vertCount = 0;
for(sInt i=0;i<mesh->Vertices.Count;i++)
if(mesh->Vertices[i].Select)
job->VertexBuffer[vertCount++] = i;
}
#endif
/****************************************************************************/
void EngMesh::UpdateShadowCacheJob(Job *job,sVector *planes,SVCache *svCache,const EngPaintInfo &info)
{
sZONE(ShadowJob);
sVERIFY(job->PlaneCount <= sizeof(SFaceIn) / sizeof(SFaceIn[0]));
sVERIFY(PartCount < sizeof(SPartSkip) / sizeof(SPartSkip[0]));
// light front/back side determination
for(sInt i=0;i<job->PlaneCount;i++)
{
sVector *vec = &planes[i];
sF32 d = vec->x * info.LightPos.x + vec->y * info.LightPos.y + vec->z * info.LightPos.z + vec->w;
SFaceIn[i] = d >= 0;
}
#if 0 && !sINTRO
// part culling
sF32 rr = info.LightRange * info.LightRange;
sF32 *pbb = PartBBs;
for(sInt i=0;i<PartCount;i++,pbb+=6)
{
sF32 d = 0.0f, dt;
// sphere/box intersection calculation
dt = sFAbs(info.LightPos.x - pbb[0]) - pbb[1];
if(dt > 0.0f)
d += dt * dt;
dt = sFAbs(info.LightPos.y - pbb[2]) - pbb[3];
if(dt > 0.0f)
d += dt * dt;
dt = sFAbs(info.LightPos.z - pbb[4]) - pbb[5];
if(dt > 0.0f)
d += dt * dt;
SPartSkip[i] = d > rr;
}
#endif
sInt ec = job->EdgeCount;
SilEdge *edges = job->Edges;
// get index pointer
if(!svCache->IndexBuffer)
svCache->IndexBuffer = new sInt[ec*6 + job->IndexCount];
sInt *ip = svCache->IndexBuffer;
// find silhouette edges
sInt silInds = 0;
for(sInt i=0;i<ec;i++)
{
SilEdge *edge = &edges[i];
if(SFaceIn[edge->Face[0]] != SFaceIn[edge->Face[1]]) // found one
{
sInt k = SFaceIn[edge->Face[0]];
sInt i0 = edge->Vert[k^1];
sInt i1 = edge->Vert[k];
ip[0] = i0 + 0;
ip[1] = i1 + 0;
ip[2] = i1 + 1;
ip[3] = i0 + 0;
ip[4] = i1 + 1;
ip[5] = i0 + 1;
ip += 6;
silInds += 6;
}
}
// process caps
sInt capInds = job->IndexCount;
sInt *inds = job->IndexBuffer;
sInt *indEnd = inds + capInds;
sInt *face = job->FaceMap;
while(inds < indEnd)
{
sInt j = SFaceIn[*face++];
ip[0] = inds[0] + j;
ip[1] = inds[1] + j;
ip[2] = inds[2] + j;
ip += 3;
inds += 3;
}
// write back
svCache->SilIndices = silInds;
svCache->CapIndices = capInds;
}
/****************************************************************************/
sInt EngMesh::WireInit = 0;
GenMaterial *EngMesh::MtrlWire[5];
void EngMesh::PrepareWireMaterials()
{
if(WireInit++ == 0)
{
sMaterial *mi;
// Wireframe edges
MtrlWire[0] = new GenMaterial;
// unselected edge
mi = new sSimpleMaterial(sINVALID,sMBF_ZON,1,0x404040);
MtrlWire[0]->AddPass(mi,ENGU_OTHER,MPP_WIRELINES,0,1.0f / 512.0f);
// selected edge
mi = new sSimpleMaterial(sINVALID,sMBF_ZON,1,0xffffff);
MtrlWire[0]->AddPass(mi,ENGU_OTHER,MPP_WIRELINES,0,1.0f / 512.0f);
// Selected faces
MtrlWire[1] = new GenMaterial;
mi = new sSimpleMaterial(sINVALID,sMBF_ZON|sMBF_DOUBLESIDED,1,0x706050);
MtrlWire[1]->AddPass(mi,ENGU_OTHER,MPP_STATIC);
// Hidden-line faces
MtrlWire[2] = new GenMaterial;
mi = new sSimpleMaterial(sINVALID,sMBF_ZREAD|sMBF_DOUBLESIDED|sMBF_BLENDALPHA|sMBF_ZBIASBACK,1,0x60101010);
MtrlWire[2]->AddPass(mi,ENGU_OTHER,MPP_STATIC);
// Collision
MtrlWire[3] = new GenMaterial;
// add volume
mi = new sSimpleMaterial(sINVALID,sMBF_ZOFF|sMBF_DOUBLESIDED|sMBF_BLENDADD,1,0x00001800);
MtrlWire[3]->AddPass(mi,ENGU_OTHER,MPP_STATIC);
// sub volume
mi = new sSimpleMaterial(sINVALID,sMBF_ZOFF|sMBF_DOUBLESIDED|sMBF_BLENDADD,1,0x00180000);
MtrlWire[3]->AddPass(mi,ENGU_OTHER,MPP_STATIC);
// zone volume
mi = new sSimpleMaterial(sINVALID,sMBF_ZOFF|sMBF_DOUBLESIDED|sMBF_BLENDADD,1,0x00000018);
MtrlWire[3]->AddPass(mi,ENGU_OTHER,MPP_STATIC);
// Vertices
MtrlWire[4] = new GenMaterial;
mi = new sSimpleMaterial(sINVALID,sMBF_ZON|sMBF_ZBIASFORE,1,0x00707070);
MtrlWire[4]->AddPass(mi,ENGU_OTHER,MPP_WIREVERTEX,0,0.01f,1.0f / 512.0f);
}
}
void EngMesh::ReleaseWireMaterials()
{
if(--WireInit == 0)
{
for(sInt i=0;i<5;i++)
MtrlWire[i]->Release();
}
}
/****************************************************************************/
struct Engine_::MeshJob
{
MeshJob *Next; // linked list
EngMesh *Mesh; // mesh to use
sF32 Time; // for bone animation
sInt PassAdjust;
sMatrix *Matrix; // world matrix
};
struct Engine_::EffectJob
{
EffectJob *Next; // linked list
KOp *Op; // effect op
sInt PassAdjust;
sMatrix Matrix;
sInt VarStart; // Index of first animation parameter
sInt VarCount; // # of animation parameters
sVector *Animation; // Animation parameters
};
struct Engine_::PaintJob // =32 bytes (this should be small!)
{
sU32 SortKey; // sorting key
sInt JobId; // mesh job id (-1 for effects)
EngLight *Light; // light to use (0 for effects)
union
{
struct
{
EngMesh *Mesh; // mesh to paint
sMatrix *Matrix; // matrix to use
GenMaterialPass *Pass; // material pass to paint
sInt Flags; // paint flags (varying uses)
void *BoneData; // transformed vertices w/ bones
};
struct
{
EffectJob *EffJob; // effect job to use (everything in there)
};
};
};
struct Engine_::PortalJob
{
PortalJob *Next; // linked list
GenScene *Sectors[3]; // "left", "right", inbetween
sInt Cost; // 0<=Cost<=256
sVector PCube[8]; // "portalcube"
};
/****************************************************************************/
Engine_::Engine_()
{
#if sPROFILE
sREGZONE(PortalVis);
sREGZONE(PortalJob);
sREGZONE(ShadowJob);
sREGZONE(UpdatePlanes);
sREGZONE(BuildJobs);
sREGZONE(SortJobs);
sREGZONE(RenderJobs);
sREGZONE(InstancePrg);
sREGZONE(EvalBones);
sREGZONE(VertCopy);
sREGZONE(VCopyStencil);
#endif
LightJobs = new EngLight[MAXLIGHT];
Mem.Init(MAXENGMEM);
BigMem.Init(MAXENGMEM);
// Allocate "current render" target
#if !sINTRO
RenderTargetManager->Add(0x00000000,0,0);
#endif
UsageMask = ~0U;
sMaterialEnv env;
env.Init();
SetViewProject(env);
PaintJobs.Init();
PaintJobs2.Init();
StartFrame();
#if !sINTRO
GeoLine = sSystem->GeoAdd(sFVF_COMPACT,sGEO_LINE);
GeoTri = sSystem->GeoAdd(sFVF_COMPACT,sGEO_TRI);
MtrlLines = new sSimpleMaterial(sINVALID,sMBF_ZON|sMBF_DOUBLESIDED/*|sMBF_BLENDADD*/,0,0);
#endif
}
Engine_::~Engine_()
{
PaintJobs.Exit();
PaintJobs2.Exit();
#if !sINTRO
sRelease(MtrlLines);
sSystem->GeoRem(GeoLine);
sSystem->GeoRem(GeoTri);
#endif
delete[] LightJobs;
Mem.Exit();
BigMem.Exit();
}
void Engine_::SetViewProject(const sMaterialEnv &env)
{
sFRect rect;
Env = env;
View = env.CameraSpace;
View.TransR();
env.MakeProjectionMatrix(Project);
ViewProject.Mul4(View,Project);
rect.Init(-1,-1,1,1);
Frustum.FromViewProject(ViewProject,rect);
Frustum.Normalize();
}
void Engine_::ApplyViewProject()
{
sSystem->SetViewProject(&Env);
}
void Engine_::StartFrame()
{
LightJobCount = 0;
sSetMem(Inserts,0,sizeof(Inserts));
sSetMem(NeedCurrentRender,0,sizeof(NeedCurrentRender));
PaintJobs.Count = 0;
PaintJobs2.Count = 0;
MeshJobs = 0;
EffectJobs = 0;
PortalJobs = 0;
SectorJobs = 0;
AmbientLight = 0;
WeaponLightSet = sFALSE;
CurrentSectorPaint = sTRUE;
Mem.Flush();
BigMem.Flush();
}
/****************************************************************************/
void Engine_::SetUsageMask(sU32 mask)
{
UsageMask = mask;
}
/****************************************************************************/
void Engine_::AddPaintJob(EngMesh *mesh,const sMatrix &matrix,sF32 time,sInt passAdjust)
{
MeshJob *job = Mem.Alloc<MeshJob>();
job->Next = MeshJobs;
job->Mesh = mesh;
job->Time = time;
job->PassAdjust = passAdjust;
job->Matrix = AddMatrix(matrix);
MeshJobs = job;
}
void Engine_::AddPaintJob(KOp *op,const sMatrix &matrix,KEnvironment *kenv,sInt passAdjust)
{
GenEffect *effect = (GenEffect *) op->Cache;
sVERIFY(op && effect && effect->ClassId == KC_EFFECT);
EffectJob *job = Mem.Alloc<EffectJob>();
job->Next = EffectJobs;
job->Op = op;
job->PassAdjust = passAdjust;
job->Matrix = matrix;
job->VarStart = op->VarStart;
job->VarCount = op->VarCount;
job->Animation = Mem.Alloc<sVector>(job->VarCount);
sCopyMem(job->Animation,&kenv->Var[job->VarStart],job->VarCount*sizeof(sVector));
EffectJobs = job;
}
void Engine_::AddLightJob(const EngLight &light)
{
// Start with a quick sphere-frustum rejection test
for(sInt i=0;i<Frustum.NPlanes;i++)
{
if(Frustum.Planes[i].Distance(light.Position) < -light.Range) // completely out
return;
}
// Lousy light importance heuristic.
sF32 d = light.Position.Distance(Env.CameraSpace.l);
sF32 importance = light.Range / sMax(d,0.1f);
// Light distance fadeout, if wanted
#ifdef KKRIEGER
sF32 fade = sRange((45.0f - d) / 10.0f,1.0f,0.0f);
#else
sF32 fade = 1.0f;
#endif
if(fade > 0.0 && light.Amplify >= 1.0f/255.0f) // light visible
{
#if !sINTRO
if(light.Flags & EL_WEAPON)
{
sInt oldTime = (WeaponLightSet && WeaponLight.Event) ? WeaponLight.Event->Start : 0;
sInt newTime = light.Event ? light.Event->Start : 0;
if(!WeaponLightSet || newTime > oldTime)
{
WeaponLight = light;
WeaponLightSet = sTRUE;
}
}
else
InsertLightJob(light,importance,fade);
#else
InsertLightJob(light,importance,fade);
#endif
}
}
void Engine_::AddAmbientLight(sU32 color)
{
sInt newR = sRange<sInt>(((AmbientLight >> 16) & 0xff) + ((color >> 16) & 0xff),255,0);
sInt newG = sRange<sInt>(((AmbientLight >> 8) & 0xff) + ((color >> 8) & 0xff),255,0);
sInt newB = sRange<sInt>(((AmbientLight >> 0) & 0xff) + ((color >> 0) & 0xff),255,0);
AmbientLight = (newR << 16) | (newG << 8) | newB;
}
void Engine_::AddPortalJob(GenScene *sectors[],const sAABox &portalBox,const sMatrix &matrix,sInt cost)
{
// Allocate and link in portaljob
PortalJob *job = Mem.Alloc<PortalJob>();
job->Next = PortalJobs;
PortalJobs = job;
// Fill in fields
for(sInt i=0;i<3;i++)
job->Sectors[i] = sectors[i];
job->Cost = cost;
// Calculate portalbox vertices
for(sInt i=0;i<8;i++)
{
sVector v;
v.x = (i & 1) ? portalBox.Max.x : portalBox.Min.x;
v.y = (i & 2) ? portalBox.Max.y : portalBox.Min.y;
v.z = (i & 4) ? portalBox.Max.z : portalBox.Min.z;
v.w = 1.0f;
job->PCube[i].Rotate34(matrix,v);
}
}
void Engine_::AddSectorJob(GenScene *sector,KOp *sectorData,const sMatrix &matrix)
{
// Insert job
sector->Next = SectorJobs;
sector->Sector = sectorData;
sector->SectorMatrix = matrix;
sector->SectorPaint = sFALSE;
sector->SectorVisited = sFALSE;
sector->PortalBox.Init(1e+15f,1e+15f,-1e+15f,-1e+15f);
SectorJobs = sector;
}
/****************************************************************************/
void Engine_::ProcessPortals(KEnvironment *kenv,KKriegerCell *observerCell)
{
#if !sINTRO // no portal culling in intros
sFRect unitBox;
unitBox.Init(-1,-1,1,1);
// process sector visibility
if(!observerCell) // just paint everything
{
for(GenScene *job=SectorJobs;job;job=job->Next)
{
job->SectorPaint = sTRUE;
job->PortalBox = unitBox;
}
}
else
{
sZONE(PortalVis);
// process visiblity recursively
PortalVisR(observerCell->Scene,0,unitBox);
// also tag all inbetween sectors for visible portals
for(PortalJob *job=PortalJobs;job;job=job->Next)
{
if(job->Sectors[2] && (job->Sectors[0]->SectorPaint || job->Sectors[1]->SectorPaint))
{
job->Sectors[2]->SectorPaint = sTRUE;
job->Sectors[2]->PortalBox = unitBox;
}
}
}
// exec phase
sZONE(PortalJob);
for(GenScene *job=SectorJobs;job;job=job->Next)
{
if(job->SectorPaint)
{
const sFRect &box = job->PortalBox;
job->SectorPaint = sFALSE; // paint sectors exactly once
CurrentSectorPaint = box.x1 > box.x0 && box.y1 > box.y0;
Frustum.FromViewProject(ViewProject,box);
Frustum.Normalize();
kenv->ExecStack.Push(job->SectorMatrix);
job->Sector->ExecWithNewMem(kenv,&job->Sector->SceneMemLink);
kenv->ExecStack.Pop();
}
}
// restore frustum and sector paint flags
CurrentSectorPaint = sTRUE;
Frustum.FromViewProject(ViewProject,unitBox);
Frustum.Normalize();
#endif
}
/****************************************************************************/
void Engine_::Paint(KEnvironment *kenv,sBool specular)
{
// Insert weapon light into list of light jobs, if necessary.
if(WeaponLightSet)
InsertLightJob(WeaponLight,LightJobs[0].Importance + 1.0f,1.0f);
#if sPROFILE
// Official beginning of gpu frame ;)
sPerfMon->Marker(0);
#endif
sU32 oldUsageMask = UsageMask;
// remove unwanted passes via usage mask
if(GenOverlayManager->EnableShadows >= 2)
UsageMask &= ~(1 << ENGU_LIGHT);
if(GenOverlayManager->EnableShadows >= 1)
UsageMask &= ~(1 << ENGU_SHADOW);
// FIRE! (damn, this routine is getting shorter and shorter)
BuildPaintJobs();
SortPaintJobs();
ApplyViewProject();
RenderPaintJobs(kenv);
UsageMask = oldUsageMask;
}
void Engine_::PaintSimple(KEnvironment *kenv)
{
// we only want "other" (i.e. single pass) materials
sU32 oldUsageMask = UsageMask;
UsageMask = 1 << ENGU_OTHER;
BuildPaintJobs();
SortPaintJobs();
ApplyViewProject();
RenderPaintJobs(kenv);
UsageMask = oldUsageMask;
}
/****************************************************************************/
#if !sINTRO
// Paint bounding boxes of all objects (for debugging)
void Engine_::DebugPaintBoundingBoxes(sU32 color)
{
DebugPaintStart();
/*// iterate through list of paintjobs drawing their bboxes
for(PaintJob *job=PaintJobs;job;job=job->Next)
DebugPaintAABox(job->BBox,color);*/
}
// Prepare debug painting (set up material)
void Engine_::DebugPaintStart(sBool world)
{
if(world)
Env.ModelSpace.Init();
MtrlLines->Set(Env);
}
// Paint one AABB
void Engine_::DebugPaintAABox(const sAABox &box,sU32 color)
{
static sU8 cubeEdge[] =
{
0,1, 1,5, 5,4, 4,0, 2,3, 3,7, 7,6, 6,2, 0,2, 1,3, 5,7, 4,6
};
sVertexCompact *vp;
sSystem->GeoBegin(GeoLine,24,0,(sF32 **)&vp,0);
for(sInt i=0;i<24;i++)
{
sInt v = cubeEdge[i];
vp->x = (v & 1) ? box.Max.x : box.Min.x;
vp->y = (v & 2) ? box.Max.y : box.Min.y;
vp->z = (v & 4) ? box.Max.z : box.Min.z;
vp->c0 = color;
vp++;
}
sSystem->GeoEnd(GeoLine);
sSystem->GeoDraw(GeoLine);
}
// Paint a rect
void Engine_::DebugPaintRect(const sFRect &rect,sU32 color)
{
static sU8 edgeTable[] = { 0,1, 1,3, 3,2, 2,0 };
sVertexCompact *vp;
sMatrix mp;
mp = Env.CameraSpace;
//mp.Trans3();
sF32 near = Env.NearClip * 1.1f;
sF32 sx = 1.0f / Env.ZoomX;
sF32 sy = 1.0f / Env.ZoomY;
sSystem->GeoBegin(GeoLine,8,0,(sF32 **)&vp,0);
for(sInt i=0;i<8;i++)
{
sVector v;
sInt n = edgeTable[i];
v = mp.l;
v.AddScale3(mp.i,near * sx * ((n & 1) ? rect.x1 : rect.x0));
v.AddScale3(mp.j,near * sy * ((n & 2) ? rect.y1 : rect.y0));
v.AddScale3(mp.k,near);
vp->x = v.x;
vp->y = v.y;
vp->z = v.z;
vp->c0 = color;
vp++;
}
sSystem->GeoEnd(GeoLine);
sSystem->GeoDraw(GeoLine);
}
// Paint one line
void Engine_::DebugPaintLine(sF32 x0,sF32 y0,sF32 z0,sF32 x1,sF32 y1,sF32 z1,sU32 color)
{
sVertexCompact *vp;
sSystem->GeoBegin(GeoLine,2,0,(sF32 **)&vp,0);
vp->x = x0;
vp->y = y0;
vp->z = z0;
vp->c0 = color;
vp++;
vp->x = x1;
vp->y = y1;
vp->z = z1;
vp->c0 = color;
vp++;
sSystem->GeoEnd(GeoLine);
sSystem->GeoDraw(GeoLine);
}
// Paint one triangle
void Engine_::DebugPaintTri(const sVector &v0,const sVector &v1,const sVector &v2,sU32 color)
{
sVertexCompact *vp;
sSystem->GeoBegin(GeoTri,3,0,(sF32 **)&vp,0);
vp->x = v0.x;
vp->y = v0.y;