Imports the current Win32 source for the pod-racing game 'Red Planet', built on the MUNGA engine and its L4 (Win32/DirectX) platform layer: - MUNGA / MUNGA_L4: cross-platform engine core and Win32 backend - RP / RP_L4: Red Planet game logic and Win32 application - DivLoader, Setup1: asset loader and installer project - lib, MUNGA_L4/openal, MUNGA_L4/sos: third-party audio dependencies Removed stale Subversion metadata and added .gitignore/.gitattributes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1388 lines
30 KiB
C++
1388 lines
30 KiB
C++
class DPLRenderer;
|
|
#include <winsock2.h>
|
|
#include "VGCDivLoader.h"
|
|
|
|
using namespace std;
|
|
|
|
hash_map<string, DivMaterial*> VGCDivLoader::mMaterials;
|
|
hash_map<string, DivTexture*> VGCDivLoader::mTextures;
|
|
hash_map<string, DivRamp*> VGCDivLoader::mRamps;
|
|
hash_map<string, DivObject*> VGCDivLoader::mObjects;
|
|
|
|
VGCDivLoader::VGCDivLoader()
|
|
{
|
|
//Initialize header to defaults
|
|
mHeaderInfo.precision = PRECISION_32BIT;
|
|
mHeaderInfo.scale = 1;
|
|
mHeaderInfo.type = TYPE_GEOMETRY;
|
|
mHeaderInfo.unit = UNIT_INCHES;
|
|
|
|
//Initialize surface props to defaults
|
|
mMasterProps.b_material_type = 2;
|
|
mMasterProps.b_material_name = 0;
|
|
mMasterProps.decal_offset = 0;
|
|
mMasterProps.drawmode = 0;
|
|
mMasterProps.drawmode_width = 1;
|
|
mMasterProps.f_material_type = 2;
|
|
mMasterProps.f_material_name = 0;
|
|
mMasterProps.faceted = 0;
|
|
mMasterProps.locks = 0;
|
|
mMasterProps.name = 0;
|
|
mMasterProps.plane = 0;
|
|
mMasterProps.specialInst = 0;
|
|
mMasterProps.vertex_mask = 0;
|
|
mMasterProps.isImmune = false;
|
|
mMasterProps.drawSpheres = false;
|
|
|
|
if (mTextures.find("NULL") == mTextures.end())
|
|
{
|
|
DivTexture *tex = new DivTexture;
|
|
memset(tex,0,sizeof(DivTexture));
|
|
tex->name = new char[5];
|
|
strcpy_s(tex->name,5,"NULL");
|
|
tex->texBuffer = new TexBuffer;
|
|
tex->texBuffer->height = 1;
|
|
tex->texBuffer->width = 1;
|
|
tex->texBuffer->m_buffer = new RGBA*[1];
|
|
tex->texBuffer->m_buffer[0] = new RGBA;
|
|
tex->texBuffer->m_buffer[0]->r = 255;
|
|
tex->texBuffer->m_buffer[0]->g = 255;
|
|
tex->texBuffer->m_buffer[0]->b = 255;
|
|
tex->texBuffer->m_buffer[0]->a = 255;
|
|
mTextures.insert(std::pair<string,DivTexture*>(string("NULL"),tex));
|
|
}
|
|
}
|
|
|
|
VGCDivLoader::~VGCDivLoader()
|
|
{
|
|
delete[] mMasterProps.b_material_name;
|
|
delete[] mMasterProps.name;
|
|
delete[] mMasterProps.f_material_name;
|
|
}
|
|
|
|
void VGCDivLoader::parse(LPDIRECT3DDEVICE9 device, char *fileName)
|
|
{
|
|
const char BIZ_IDSTR[] = "DIV-BIZ2";
|
|
const char VTX_IDSTR[] = "DIV-VTX2";
|
|
FILE file;
|
|
FILE *mFile = &file;
|
|
if (fopen_s(&mFile, fileName, "rb") != 0)
|
|
return;
|
|
|
|
char id[8];
|
|
|
|
fread(&id, 8, 1, mFile);
|
|
|
|
//Compare first 8 bits to make sure it's a biz2 file
|
|
if (strncmp(id,"DIV-BIZ2",8) != 0)
|
|
{
|
|
fclose(mFile);
|
|
return;
|
|
}
|
|
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int len;
|
|
while(!feof(mFile))
|
|
{
|
|
//Retrieve ID and length of upcoming block
|
|
getBlock(mFile, ident, len);
|
|
|
|
switch(ident)
|
|
{
|
|
case HEADER:
|
|
parseHeader(mFile, len);
|
|
break;
|
|
case TRAILER:
|
|
//Output code here
|
|
fclose(mFile);
|
|
return;
|
|
case TEXTURE:
|
|
//This should be a geometry file
|
|
fseek(mFile,len,SEEK_CUR);
|
|
break;
|
|
case MATERIAL:
|
|
//This should be a geometry file
|
|
fseek(mFile,len,SEEK_CUR);
|
|
break;
|
|
case RAMP:
|
|
//This should be a geometry file
|
|
fseek(mFile,len,SEEK_CUR);
|
|
break;
|
|
case OBJECT:
|
|
parseObject(mFile, len, fileName);
|
|
break;
|
|
default:
|
|
//Unknown! Skip it
|
|
if (!feof(mFile))
|
|
{
|
|
fseek(mFile,len,SEEK_CUR);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(mFile);
|
|
}
|
|
|
|
void VGCDivLoader::parseHeader(FILE *file, unsigned int len)
|
|
{
|
|
//Set identifiable header props
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
switch(ident)
|
|
{
|
|
case HDR_VERSION:
|
|
fread(&mHeaderInfo.verMajor,1,1,file);
|
|
fread(&mHeaderInfo.verMinor,1,1,file);
|
|
break;
|
|
case HDR_DATE:
|
|
fread(&mHeaderInfo.dateDay,1,1,file);
|
|
fread(&mHeaderInfo.dateMon,1,1,file);
|
|
fread(&mHeaderInfo.dateYear,1,1,file);
|
|
break;
|
|
case HDR_TIME:
|
|
fread(&mHeaderInfo.timeHour,1,1,file);
|
|
fread(&mHeaderInfo.timeMin,1,1,file);
|
|
break;
|
|
case HDR_SCALE:
|
|
fread(&mHeaderInfo.scale,floatSize(),1,file);
|
|
break;
|
|
case HDR_PRECISION:
|
|
char precision;
|
|
fread(&precision,1,1,file);
|
|
mHeaderInfo.precision = (FloatPrecision)precision;
|
|
|
|
if (mHeaderInfo.precision == PRECISION_64BIT)
|
|
{
|
|
//Don't believe we have any 64 bit files, so fuck it
|
|
MessageBox(NULL,_T("PANIC 64 BIT"),_T("OH NOES"),MB_OK);
|
|
std::cout << "PANIC: 64 BIT PRECISION." << std::endl;
|
|
std::cin.get();
|
|
exit(1);
|
|
}
|
|
break;
|
|
case HDR_FILETYPE:
|
|
char type;
|
|
fread(&type,1,1,file);
|
|
mHeaderInfo.type = (FileType)type;
|
|
break;
|
|
case HDR_UNIT:
|
|
char unit;
|
|
fread(&unit,1,1,file);
|
|
mHeaderInfo.unit = (Unit)unit;
|
|
break;
|
|
default:
|
|
cout << "UNKNOWN HEADER ENTRY " << ident << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
TexBuffer *VGCDivLoader::LoadVTXFile(char *fileName, DivTexture *tex)
|
|
{
|
|
int len = strlen(fileName) + 5;
|
|
char *fullFileName = new char[len];
|
|
strcpy_s(fullFileName, len, fileName);
|
|
strcat_s(fullFileName, len, ".VTX");
|
|
|
|
FILE *texFile;
|
|
if (fopen_s(&texFile, fullFileName, "rb") != 0)
|
|
{
|
|
delete [] fullFileName;
|
|
return NULL;
|
|
}
|
|
|
|
delete [] fullFileName;
|
|
char id[8];
|
|
fread(id, 1, 8, texFile);
|
|
|
|
if (strncmp(id, "DIV-VTX2", 8) != 0)
|
|
{
|
|
fclose(texFile);
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int blen;
|
|
BIZ_IDENTITIES ident;
|
|
while(!feof(texFile))
|
|
{
|
|
//Retrieve ID and length of upcoming block
|
|
getBlock(texFile, ident, blen);
|
|
|
|
switch(ident)
|
|
{
|
|
case HEADER:
|
|
fseek(texFile, blen, SEEK_CUR);
|
|
break;
|
|
case VTX_TEX:
|
|
tex->texBuffer = new TexBuffer;
|
|
unsigned int nlen;
|
|
while(blen > 0)
|
|
{
|
|
blen -= getBlock(texFile, ident, nlen);
|
|
blen -= nlen;
|
|
|
|
unsigned char pixelWidth;
|
|
unsigned char colorSettings;
|
|
|
|
switch(ident)
|
|
{
|
|
case VTX_COLOR:
|
|
fread(&colorSettings, 1, 1, texFile);
|
|
break;
|
|
case VTX_SIZE:
|
|
fread(&pixelWidth, 1, 1, texFile);
|
|
break;
|
|
case VTX_DIMENSIONS:
|
|
fread(&tex->texBuffer->width, 4, 1, texFile);
|
|
fread(&tex->texBuffer->height, 4, 1, texFile);
|
|
break;
|
|
default:
|
|
if (ident >= 0x68 && ident == 0x68 + colorSettings)
|
|
{
|
|
int dataPoints = 1;
|
|
if (colorSettings & 0x01)
|
|
{
|
|
dataPoints++;
|
|
}
|
|
if (colorSettings & 0x02)
|
|
{
|
|
dataPoints += 2;
|
|
}
|
|
|
|
int sampleSize = 1;
|
|
if (pixelWidth != 0x01)
|
|
{
|
|
MessageBox(NULL, _T("PANIC: UNEXPECTED SAMPLE SIZE FOR TEX"), _T("PROBLEM"), MB_OK);
|
|
}
|
|
|
|
int tLen = nlen / (dataPoints * sampleSize);
|
|
tex->texBuffer->m_buffer = new RGBA*[tex->texBuffer->height];
|
|
|
|
for (int i=0; i < tex->texBuffer->height; i++)
|
|
{
|
|
tex->texBuffer->m_buffer[i] = new RGBA[tex->texBuffer->width];
|
|
|
|
for (int j=0; j< tex->texBuffer->width; j++)
|
|
{
|
|
fread(&tex->texBuffer->m_buffer[i][j].r, 1, 1, texFile);
|
|
|
|
if (colorSettings & 0x02)
|
|
{
|
|
fread(&tex->texBuffer->m_buffer[i][j].g, 1, 1, texFile);
|
|
fread(&tex->texBuffer->m_buffer[i][j].b, 1, 1, texFile);
|
|
} else
|
|
{
|
|
tex->texBuffer->m_buffer[i][j].g = tex->texBuffer->m_buffer[i][i].r;
|
|
tex->texBuffer->m_buffer[i][j].b = tex->texBuffer->m_buffer[i][i].r;
|
|
}
|
|
|
|
if (colorSettings & 0x01)
|
|
{
|
|
fread(&tex->texBuffer->m_buffer[i][j].a, 1, 1, texFile);
|
|
} else
|
|
{
|
|
tex->texBuffer->m_buffer[i][j].a = 255;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
if (ident >= 0x68)
|
|
{
|
|
MessageBox(NULL, _T("BAD TEX INFO"), _T("PROBLEMS"), MB_OK);
|
|
} else
|
|
{
|
|
MessageBox(NULL, _T("UNKNOWN TAG IN VTX"), _T("PROBLEM"), MB_OK);
|
|
}
|
|
fseek(texFile, nlen, SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TRAILER:
|
|
fclose(texFile);
|
|
return tex->texBuffer;
|
|
break;
|
|
default:
|
|
MessageBox(NULL, _T("UNKNOWN VTX INFO"), _T("PROBLEM"), MB_OK);
|
|
fseek(texFile, blen, SEEK_CUR);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(texFile);
|
|
return tex->texBuffer;;
|
|
}
|
|
|
|
unsigned char getBSLData(int channel, int value)
|
|
{
|
|
int shift = (channel + (((channel + 1) % 2) * 2)) * 4;
|
|
int mask = 0xF0 << shift;
|
|
return (unsigned char)((value & mask) >> shift);
|
|
}
|
|
|
|
TexBuffer *VGCDivLoader::LoadBSLFile(char *fileName, char channelIndex, DivTexture *tex)
|
|
{
|
|
int len = strlen(fileName) + 5;
|
|
char *fullFileName = new char[len];
|
|
strcpy_s(fullFileName, len, fileName);
|
|
strcat_s(fullFileName, len, ".BSL");
|
|
|
|
FILE *texFile;
|
|
if (fopen_s(&texFile, fullFileName, "rb") != 0)
|
|
{
|
|
delete [] fullFileName;
|
|
return NULL;
|
|
}
|
|
|
|
char id[8];
|
|
fread(id, 1, 8, texFile);
|
|
|
|
delete [] fullFileName;
|
|
if (strncmp(id, "DIV-BSL2", 8) != 0)
|
|
{
|
|
fclose(texFile);
|
|
return NULL;
|
|
}
|
|
|
|
int width, height, depth, nTextures;
|
|
|
|
fread(&width, 4, 1, texFile);
|
|
fread(&height, 4, 1, texFile);
|
|
fread(&depth, 4, 1, texFile);
|
|
fseek(texFile, 4, SEEK_CUR);
|
|
fread(&nTextures, 4, 1, texFile);
|
|
|
|
int usefulChannel = channelIndex;
|
|
|
|
for (int i=0; i < nTextures; i++)
|
|
{
|
|
int header, channel;
|
|
fread(&header, 4, 1, texFile);
|
|
fread(&channel, 4, 1, texFile);
|
|
|
|
char *chanName = new char[header-4];
|
|
fread(chanName, 1, header - 4, texFile);
|
|
|
|
delete [] chanName;
|
|
}
|
|
|
|
tex->texBuffer = new TexBuffer;
|
|
tex->texBuffer->height = height;
|
|
tex->texBuffer->width = width;
|
|
tex->texBuffer->m_buffer = new RGBA*[height];
|
|
for (int i=0; i <height; i++)
|
|
{
|
|
tex->texBuffer->m_buffer[i] = new RGBA[width];
|
|
for (int j=0; j < width; j++)
|
|
{
|
|
int val;
|
|
fread(&val, 4, 1, texFile);
|
|
if (usefulChannel < 6)
|
|
{
|
|
unsigned char color = getBSLData(usefulChannel,val);
|
|
tex->texBuffer->m_buffer[i][j].a = 255;
|
|
tex->texBuffer->m_buffer[i][j].r = color;
|
|
tex->texBuffer->m_buffer[i][j].g = color;
|
|
tex->texBuffer->m_buffer[i][j].b = color;
|
|
} else
|
|
{
|
|
if (usefulChannel == 8)
|
|
{
|
|
tex->texBuffer->m_buffer[i][j].a = getBSLData(2, val);
|
|
} else
|
|
{
|
|
tex->texBuffer->m_buffer[i][j].a = 255;
|
|
}
|
|
tex->texBuffer->m_buffer[i][j].r = getBSLData(5, val);
|
|
tex->texBuffer->m_buffer[i][j].g = getBSLData(4, val);
|
|
tex->texBuffer->m_buffer[i][j].b = getBSLData(3, val);
|
|
}
|
|
}
|
|
}
|
|
return tex->texBuffer;
|
|
}
|
|
|
|
void VGCDivLoader::parseTexture(FILE *file, unsigned int len)
|
|
{
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
|
|
DivTexture *texture = new DivTexture;
|
|
memset(texture,0,sizeof(DivTexture));
|
|
texture->alpha = 255;
|
|
|
|
char *texMapName = NULL;
|
|
char channelIndex = 0;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
char *fileName; //Name of tex raster file
|
|
char *ran; //Temp place for strings
|
|
|
|
switch(ident)
|
|
{
|
|
case GEN_NAME:
|
|
{
|
|
texture->name = new char[nlen];
|
|
fread(texture->name, 1, nlen, file);
|
|
break;
|
|
}
|
|
case GEN_SCOPE:
|
|
fseek(file,1,SEEK_CUR);
|
|
break;
|
|
case TEX_MAP:
|
|
texMapName = new char[nlen];
|
|
fread(texMapName,1,nlen,file);
|
|
|
|
break;
|
|
case TEX_MINIFY:
|
|
fread(&texture->minify,1,1,file);
|
|
break;
|
|
case TEX_MAGNIFY:
|
|
fread(&texture->magnify,1,1,file);
|
|
break;
|
|
case TEX_ALPHA:
|
|
fread(&texture->alpha,1,1,file);
|
|
break;
|
|
case TEX_WRAP_U:
|
|
fread(&texture->wrap_u,1,1,file);
|
|
break;
|
|
case TEX_WRAP_V:
|
|
fread(&texture->wrap_v, 1, 1, file);
|
|
break;
|
|
case TEX_DETAIL:
|
|
fread(&texture->detail_type, 1, 1, file);
|
|
if (nlen > 1)
|
|
{
|
|
texture->detail_name = new char[nlen - 1];
|
|
fread(texture->detail_name, 1, nlen - 1, file);
|
|
}
|
|
break;
|
|
case TEX_BSL_CHANNEL:
|
|
//This is the channel index being loaded for a BSL file
|
|
fread(&channelIndex, 1, 1, file);
|
|
break;
|
|
case SRF_SPECIAL:
|
|
{
|
|
char *pos;
|
|
//Awesome
|
|
ran = new char[nlen];
|
|
fread(ran,1,nlen,file);
|
|
|
|
if ((pos = strstr(ran,"SCROLL")) != NULL)
|
|
{
|
|
float startu,startv,endu,endv;
|
|
sscanf(pos, "SCROLL %f %f %f %f",&startu, &startv, &endu, &endv);
|
|
texture->doScroll = true;
|
|
texture->scrollU = endu;
|
|
texture->scrollV = endv;
|
|
}
|
|
delete [] ran;
|
|
break;
|
|
}
|
|
default:
|
|
MessageBox(NULL,_T("UNKNOWN TEXTURE ENTRY"),_T("PROBLEM"),MB_OK);
|
|
if (!feof(file))
|
|
{
|
|
fseek(file, nlen, SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Load up the texture buffer
|
|
//Try to load as a BSL first
|
|
if ((texture->texBuffer = LoadBSLFile(texMapName, channelIndex, texture)) == NULL)
|
|
{
|
|
//If that didn't work, do VTX
|
|
texture->texBuffer = LoadVTXFile(texMapName, texture);
|
|
}
|
|
|
|
delete [] texMapName;
|
|
|
|
if (texture->name == NULL)
|
|
{
|
|
delete [] texture->name;
|
|
if (texture->detail_name != NULL)
|
|
{
|
|
delete [] texture->detail_name;
|
|
}
|
|
if (texture->texBuffer != NULL)
|
|
{
|
|
delete texture->texBuffer;
|
|
}
|
|
delete texture;
|
|
fseek(file,len,SEEK_CUR);
|
|
} else
|
|
{
|
|
//Add material
|
|
mTextures.insert(pair<string, DivTexture*>(string(texture->name),texture));
|
|
}
|
|
}
|
|
|
|
bool VGCDivLoader::parseMaterial(FILE *file, unsigned int len, char *filename)
|
|
{
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
|
|
DivMaterial *material = new DivMaterial;
|
|
memset(material,0,sizeof(DivMaterial));
|
|
material->amb_b = 1;
|
|
material->amb_g = 1;
|
|
material->amb_r = 1;
|
|
material->dif_b = 1;
|
|
material->dif_g = 1;
|
|
material->dif_r = 1;
|
|
material->emi_b = 0;
|
|
material->emi_g = 0;
|
|
material->emi_r = 0;
|
|
material->spe_b = 0;
|
|
material->spe_g = 0;
|
|
material->spe_p = 1;
|
|
material->spe_r = 0;
|
|
material->opa_b = 1;
|
|
material->opa_g = 1;
|
|
material->opa_r = 1;
|
|
material->isImmune = false;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident, nlen);
|
|
len -= nlen;
|
|
|
|
char *ran; //Temp place for strings
|
|
|
|
switch(ident)
|
|
{
|
|
case GEN_NAME:
|
|
material->name = new char[nlen];
|
|
fread(material->name, 1, nlen, file);
|
|
break;
|
|
case GEN_SCOPE:
|
|
fseek(file,1,SEEK_CUR);
|
|
break;
|
|
case MAT_TEXTURE:
|
|
fread(&material->tex_type, 1, 1, file);
|
|
if (nlen > 1)
|
|
{
|
|
material->tex_name = new char[nlen - 1];
|
|
fread(material->tex_name, 1, nlen - 1, file);
|
|
}
|
|
|
|
//Precaching textures moves the file offset, so:
|
|
/*loc = ftell(file);
|
|
precacheTexture(material->tex_name,file);
|
|
fseek(file,loc,SEEK_SET);*/
|
|
//We don't precache textures anymore- they should be local, so screw it
|
|
break;
|
|
case MAT_ENVIRONMENT:
|
|
//Anyone know what this is for?
|
|
fread(&material->env_type, 1, 1, file);
|
|
material->env_name = new char[nlen - 1];
|
|
fread(material->env_name, 1, nlen - 1, file);
|
|
break;
|
|
case MAT_AMBIENT:
|
|
fread(&material->amb_r, floatSize(), 1, file);
|
|
fread(&material->amb_g, floatSize(), 1, file);
|
|
fread(&material->amb_b, floatSize(), 1, file);
|
|
break;
|
|
case MAT_DIFFUSE:
|
|
fread(&material->dif_r, floatSize(), 1, file);
|
|
fread(&material->dif_g, floatSize(), 1, file);
|
|
fread(&material->dif_b, floatSize(), 1, file);
|
|
break;
|
|
case MAT_SPECULAR:
|
|
fread(&material->spe_r, floatSize(), 1, file);
|
|
fread(&material->spe_g, floatSize(), 1, file);
|
|
fread(&material->spe_b, floatSize(), 1, file);
|
|
fread(&material->spe_p, floatSize(), 1, file);
|
|
break;
|
|
case MAT_EMISSIVE:
|
|
fread(&material->emi_r, floatSize(), 1, file);
|
|
fread(&material->emi_g, floatSize(), 1, file);
|
|
fread(&material->emi_b, floatSize(), 1, file);
|
|
break;
|
|
case MAT_OPACITY:
|
|
fread(&material->opa_r, floatSize(), 1, file);
|
|
fread(&material->opa_g, floatSize(), 1, file);
|
|
fread(&material->opa_b, floatSize(), 1, file);
|
|
break;
|
|
case MAT_RAMP:
|
|
material->ramp_name = new char[nlen];
|
|
fread(material->ramp_name, 1, nlen, file);
|
|
break;
|
|
case SRF_SPECIAL:
|
|
{
|
|
//Awesome!
|
|
ran = new char[nlen];
|
|
fread(ran,1,nlen,file);
|
|
char *immuneParam = strstr(ran, "IMMUNE");
|
|
if (immuneParam != NULL)
|
|
{
|
|
material->isImmune = true;
|
|
}
|
|
delete [] ran;
|
|
break;
|
|
}
|
|
default:
|
|
MessageBox(NULL,_T("UNKNOWN MATERIAL ENTRY"),_T("PROBLEM"),MB_OK);
|
|
if (!feof(file))
|
|
{
|
|
fseek(file, nlen, SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (material->name == NULL)
|
|
{
|
|
//This material is nameless?
|
|
if (material->env_name)
|
|
{
|
|
delete [] material->env_name;
|
|
}
|
|
|
|
if (material->tex_name)
|
|
{
|
|
delete [] material->tex_name;
|
|
}
|
|
|
|
if (material->ramp_name)
|
|
{
|
|
delete [] material->ramp_name;
|
|
}
|
|
|
|
delete material;
|
|
return false;
|
|
} else
|
|
{
|
|
//If there's no material, set it to null.
|
|
if (material->tex_type != 2)
|
|
{
|
|
if (material->tex_name != NULL)
|
|
{
|
|
delete [] material->tex_name;
|
|
}
|
|
material->tex_type = 2;
|
|
material->tex_name = new char[5];
|
|
strcpy_s(material->tex_name, 5, "NULL");
|
|
}
|
|
//Add material
|
|
string fullMatName = string(filename);
|
|
fullMatName += string(":");
|
|
fullMatName += string(material->name);
|
|
mMaterials.insert(pair<string, DivMaterial*>(fullMatName,material));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void VGCDivLoader::precacheMaterial(char *name)
|
|
{
|
|
FILE *matFile = NULL;
|
|
|
|
//Get material file name
|
|
char *colon = strstr(name, ":");
|
|
int len = colon - name;
|
|
char *fname = new char[(len + 5)];
|
|
char *matFileName = new char[len + 1];
|
|
strncpy(matFileName,name,len);
|
|
matFileName[len] = 0;
|
|
strncpy(fname, name, len);
|
|
fname[len] = 0;
|
|
strcat_s(fname,len + 5, ".BMF");
|
|
|
|
//Get actual material name
|
|
int matNameLen = strlen(name) - len;
|
|
char *mname = new char[matNameLen];
|
|
strcpy_s(mname, matNameLen, colon + 1);
|
|
|
|
if (mMaterials.find(string(name)) != mMaterials.end())
|
|
{
|
|
//Already precached
|
|
return;
|
|
}
|
|
|
|
//Open the BMF file
|
|
int fullDirLen = strlen(fname) + 1;
|
|
char *fulldir = new char[fullDirLen];
|
|
strcpy_s(fulldir, fullDirLen, fname);
|
|
delete [] fname;
|
|
|
|
if (fopen_s(&matFile,fulldir,"rb") != 0)
|
|
{
|
|
MessageBox(NULL,_T("PANIC MAT FILE FAILED TO OPEN"),_T("SHIT"),MB_OK);
|
|
}
|
|
|
|
delete [] fulldir;
|
|
|
|
//Parse the file
|
|
char id[8];
|
|
|
|
fread(&id, 8, 1, matFile);
|
|
|
|
//Compare first 8 bits to make sure it's a biz2 file
|
|
if (strncmp(id,"DIV-BIZ2",8) != 0)
|
|
{
|
|
fclose(matFile);
|
|
MessageBox(NULL, _T("PANIC MAT FILE WAS NOT MAT FILE"),_T("Oh no"),MB_OK);
|
|
}
|
|
|
|
unsigned int blen;
|
|
BIZ_IDENTITIES ident;
|
|
bool done = false;
|
|
while(!feof(matFile))
|
|
{
|
|
//Retrieve ID and length of upcoming block
|
|
getBlock(matFile, ident, blen);
|
|
|
|
switch(ident)
|
|
{
|
|
case MATERIAL:
|
|
//This is the only relevant thing right now
|
|
parseMaterial(matFile, blen,matFileName);
|
|
break;
|
|
case RAMP:
|
|
parseRamp(matFile,blen);
|
|
break;
|
|
case TEXTURE:
|
|
parseTexture(matFile, blen);
|
|
break;
|
|
default:
|
|
if (!feof(matFile))
|
|
{
|
|
fseek(matFile,blen,SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
delete [] mname;
|
|
delete [] matFileName;
|
|
}
|
|
|
|
void VGCDivLoader::parseRamp(FILE *file, unsigned int len)
|
|
{
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
|
|
DivRamp *ramp = new DivRamp;
|
|
memset(ramp, 0, sizeof(DivRamp));
|
|
|
|
char *name;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident, nlen);
|
|
len -= nlen;
|
|
|
|
switch(ident)
|
|
{
|
|
case GEN_NAME:
|
|
name = new char[nlen];
|
|
fread(name, 1, nlen, file);
|
|
|
|
if (mRamps.find(string(name)) != mRamps.end())
|
|
{
|
|
delete [] name;
|
|
delete ramp;
|
|
fseek(file,len,SEEK_CUR);
|
|
return;
|
|
}
|
|
break;
|
|
case GEN_SCOPE:
|
|
if (nlen > 0)
|
|
{
|
|
fseek(file, nlen, SEEK_CUR);
|
|
}
|
|
break;
|
|
case RMP_DATA:
|
|
fread(&ramp->r1, floatSize(), 1, file);
|
|
fread(&ramp->g1, floatSize(), 1, file);
|
|
fread(&ramp->b1, floatSize(), 1, file);
|
|
fread(&ramp->r2, floatSize(), 1, file);
|
|
fread(&ramp->g2, floatSize(), 1, file);
|
|
fread(&ramp->b2, floatSize(), 1, file);
|
|
break;
|
|
default:
|
|
MessageBox(NULL, _T("UNRECOGNIZED RAMP BLOCK"), _T("PROBLEM"), MB_OK);
|
|
if (nlen > 0)
|
|
{
|
|
fseek(file, nlen, SEEK_CUR);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
mRamps[string(name)] = ramp;
|
|
delete [] name;
|
|
}
|
|
|
|
void VGCDivLoader::parseObject(FILE *file, unsigned int len, char *fileName)
|
|
{
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
|
|
DivObject *obj = new DivObject;
|
|
obj->objName = new char[strlen(fileName) + 1];
|
|
strcpy_s(obj->objName, strlen(fileName) + 1, fileName);
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
switch(ident)
|
|
{
|
|
//Set the master set of surface props
|
|
case GEN_NAME:
|
|
case SRF_F_MATERIAL:
|
|
case SRF_B_MATERIAL:
|
|
case SRF_PLANE:
|
|
case SRF_DRAWMODE:
|
|
case SRF_DECAL:
|
|
case SRF_FACETED:
|
|
case SRF_VERTEX:
|
|
case SRF_SPECIAL:
|
|
case SRF_LOCK:
|
|
parseSurface(file, ident,nlen,mMasterProps);
|
|
break;
|
|
case LOD:
|
|
parseLOD(obj, file, nlen);
|
|
break;
|
|
case PATCH:
|
|
parsePatch(obj, file, nlen);
|
|
break;
|
|
}
|
|
}
|
|
|
|
VGCDivLoader::mObjects.insert(std::pair<string, DivObject*>(string(obj->objName), obj));
|
|
|
|
}
|
|
|
|
void VGCDivLoader::parseLOD(DivObject *obj, FILE *file, unsigned int len)
|
|
{
|
|
//LOD's are only relevant if it's the closest LOD level,
|
|
//and the only relevant part are the patches
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
bool parse = false;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
switch(ident)
|
|
{
|
|
case LOD_DISTANCE:
|
|
float in, out;
|
|
fread(&in,floatSize(),1,file);
|
|
fread(&out,floatSize(),1,file);
|
|
|
|
if (in < 5.0f)
|
|
{
|
|
//Don't even bother if this isn't a seriously close-up LOD
|
|
parse = true;
|
|
}
|
|
break;
|
|
case PATCH:
|
|
if (parse)
|
|
{
|
|
//If this is the right LOD, parse patches
|
|
parsePatch(obj, file, nlen);
|
|
} else
|
|
{
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
break;
|
|
default:
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VGCDivLoader::parsePatch(DivObject *obj, FILE *file, unsigned int len)
|
|
{
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
bool parse = false;
|
|
|
|
DivPatch *patch = new DivPatch;
|
|
patch->surface = mMasterProps;
|
|
|
|
int *indices;
|
|
DivVertex *verts;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
int n;
|
|
|
|
switch(ident)
|
|
{
|
|
case GEN_NAME:
|
|
case SRF_F_MATERIAL:
|
|
case SRF_B_MATERIAL:
|
|
case SRF_PLANE:
|
|
case SRF_DRAWMODE:
|
|
case SRF_DECAL:
|
|
case SRF_FACETED:
|
|
case SRF_VERTEX:
|
|
case SRF_SPECIAL:
|
|
case SRF_LOCK:
|
|
//Edit our patch's surface props
|
|
parseSurface(file, ident,nlen,patch->surface);
|
|
break;
|
|
case POLYGON:
|
|
case POLYSTRIP:
|
|
//Polystrip is a tri fan. Incidentally, we're treating
|
|
//>3 sided polys like tri fans
|
|
nlen = 0;
|
|
while (!(ident & 0x80))
|
|
{
|
|
//Poly lists are the only relevant blocks
|
|
if (nlen > 0)
|
|
{
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
getBlock(file, ident,nlen);
|
|
}
|
|
|
|
//Parse list of polys
|
|
verts = parseVList(file, nlen,(unsigned char)(ident & 0x7F),n);
|
|
|
|
indices = new int[n];
|
|
for (int i=0; i<n; i++)
|
|
{
|
|
//Read in vertices to a list of indices
|
|
indices[i] = getVertex(verts[i], obj);
|
|
}
|
|
//addPolygon is what breaks down our list of indices down
|
|
//into multiple tris
|
|
addPolygon(indices,n,patch);
|
|
|
|
delete [] verts;
|
|
delete [] indices;
|
|
|
|
break;
|
|
case TRISTRIP:
|
|
nlen = 0;
|
|
while (!(ident & 0x80))
|
|
{
|
|
if (nlen > 0)
|
|
{
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
getBlock(file, ident,nlen);
|
|
}
|
|
|
|
verts = parseVList(file, nlen,(unsigned char)(ident & 0x7F),n);
|
|
|
|
indices = new int[n];
|
|
for (int i=0; i<n; i++)
|
|
{
|
|
//Read in vertices
|
|
indices[i] = getVertex(verts[i], obj);
|
|
}
|
|
|
|
for (int i=0; i < n-2; i++)
|
|
{
|
|
//For every tri in the strip, add the tri
|
|
addPolygon(indices,3,patch);
|
|
}
|
|
|
|
delete [] verts;
|
|
delete [] indices;
|
|
break;
|
|
case PMESH:
|
|
//PMESHES ARE HARD
|
|
parsePMesh(file, nlen,patch, obj);
|
|
break;
|
|
case SPHERE_LIST:
|
|
{
|
|
unsigned int inlen;
|
|
BIZ_IDENTITIES inident;
|
|
//Skip the width, read the verts
|
|
patch->surface.drawSpheres = true;
|
|
vector<DivVertex> verts;
|
|
|
|
while (nlen > 0)
|
|
{
|
|
nlen -= getBlock(file, inident, inlen);
|
|
nlen -= inlen;
|
|
if (inident == SPHERE)
|
|
{
|
|
for (int i=0; i < (inlen/(floatSize()*4)); i++)
|
|
{
|
|
float x,y,z,radius;
|
|
fread(&x,floatSize(),1,file);
|
|
fread(&y,floatSize(),1,file);
|
|
fread(&z,floatSize(),1,file);
|
|
fread(&radius,floatSize(),1,file);
|
|
DivVertex vert;
|
|
vert.x = x * this->mHeaderInfo.scale;
|
|
vert.y = y * this->mHeaderInfo.scale;
|
|
vert.z = z * this->mHeaderInfo.scale;
|
|
vert.mask = 0;
|
|
verts.insert(verts.end(),vert);
|
|
}
|
|
} else if (inlen > 0)
|
|
{
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
}
|
|
|
|
for (int i=0; i < verts.size(); i++)
|
|
{
|
|
getVertex(verts[i], obj);
|
|
}
|
|
//setupDrawOps(props);
|
|
break;
|
|
}
|
|
case LINE_LIST:
|
|
//Ignore these
|
|
fseek(file,nlen,SEEK_CUR);
|
|
break;
|
|
case TEXT:
|
|
//Ignore these
|
|
fseek(file,nlen,SEEK_CUR);
|
|
break;
|
|
default:
|
|
cout << "UNKNOWN PATCH VAR " << ident << endl;
|
|
}
|
|
}
|
|
|
|
obj->patches.insert(obj->patches.end(), patch);
|
|
|
|
}
|
|
|
|
void VGCDivLoader::parseSurface(FILE *file, BIZ_IDENTITIES ident, unsigned int len, SurfaceProps &props)
|
|
{
|
|
//These are really straightforward- just move the value
|
|
//from the file to the SurfaceProps struct
|
|
switch(ident)
|
|
{
|
|
case GEN_NAME:
|
|
props.name = new char[len];
|
|
fread(props.name,1,len,file);
|
|
break;
|
|
case SRF_F_MATERIAL:
|
|
fread(&props.f_material_type,1,1,file);
|
|
props.faceted |= 0x02;
|
|
if (len > 1)
|
|
{
|
|
props.f_material_name = new char[len-1];
|
|
fread(props.f_material_name,1,len-1,file);
|
|
precacheMaterial(props.f_material_name);
|
|
}
|
|
break;
|
|
case SRF_B_MATERIAL:
|
|
fread(&props.b_material_type,1,1,file);
|
|
props.faceted |= 0x01;
|
|
if (len > 1)
|
|
{
|
|
props.b_material_name = new char[len-1];
|
|
fread(props.b_material_name,1,len-1,file);
|
|
precacheMaterial(props.b_material_name);
|
|
}
|
|
break;
|
|
case SRF_PLANE:
|
|
fread(&props.plane,1,1,file);
|
|
break;
|
|
case SRF_DRAWMODE:
|
|
fread(&props.drawmode,1,1,file);
|
|
fread(&props.drawmode_width,1,1,file);
|
|
break;
|
|
case SRF_DECAL:
|
|
fread(&props.decal_offset,4,1,file);
|
|
break;
|
|
case SRF_FACETED:
|
|
fread(&props.faceted,1,1,file);
|
|
break;
|
|
case SRF_VERTEX:
|
|
fread(&props.vertex_mask,1,1,file);
|
|
break;
|
|
case SRF_SPECIAL:
|
|
props.specialInst = new char[len];
|
|
fread(props.specialInst,1,len,file);
|
|
|
|
break;
|
|
case SRF_LOCK:
|
|
fread(&props.locks,1,1,file);
|
|
break;
|
|
default:
|
|
cout << "UNKNOWN SURFACE VAR " << ident << endl;
|
|
}
|
|
}
|
|
|
|
void VGCDivLoader::parsePMesh(FILE *file, unsigned int len, DivPatch *patch, DivObject *obj)
|
|
{
|
|
//PMeshes are a list of vertices, and a list of vertex indices
|
|
//The pmeshes default to 3 side per poly, but can be set different
|
|
BIZ_IDENTITIES ident;
|
|
unsigned int nlen;
|
|
bool parse = false;
|
|
|
|
std::vector<ConList *> conLists;
|
|
DivVertex *vertList;
|
|
|
|
while(len > 0)
|
|
{
|
|
len -= getBlock(file, ident,nlen);
|
|
len -= nlen;
|
|
|
|
ConList *list;
|
|
|
|
switch(ident)
|
|
{
|
|
case PMESH_CONLIST:
|
|
//old conlists are tris only
|
|
list = new ConList;
|
|
list->sideCount = 3;
|
|
list->numIndices = nlen / 4;
|
|
list->indices = new int[list->numIndices];
|
|
fread(list->indices,4,list->numIndices,file);
|
|
conLists.insert(conLists.begin(),list);
|
|
break;
|
|
case PMESH_CONLIST_NEW:
|
|
//Newer ones specify the sideage of the polys
|
|
list = new ConList;
|
|
fread(&list->sideCount,1,1,file);
|
|
list->numIndices = (nlen-1) / 4;
|
|
list->indices = new int[list->numIndices];
|
|
fread(list->indices,4,list->numIndices,file);
|
|
conLists.insert(conLists.begin(),list);
|
|
break;
|
|
default:
|
|
if (ident & 0x80)
|
|
{
|
|
//Read in verts like normal
|
|
int n;
|
|
vertList = parseVList(file, nlen,(unsigned char)(ident & 0x7F), n);
|
|
} else
|
|
{
|
|
cout << "UNKNOWN PMESH VAR " << ident << endl;
|
|
fseek(file,nlen,SEEK_CUR);
|
|
}
|
|
}
|
|
}
|
|
|
|
while(!conLists.empty())
|
|
{
|
|
//For each connection list, add polygons from the conlist
|
|
//and destroy the connection list
|
|
|
|
ConList *list = *conLists.begin();
|
|
|
|
//Replace local vertex indexes with global ones
|
|
for (int i=0; i < list->numIndices; i++)
|
|
{
|
|
list->indices[i] = getVertex(vertList[list->indices[i]], obj);
|
|
}
|
|
|
|
//Add the individual polygons
|
|
for (int i=0; i < list->numIndices; i += list->sideCount)
|
|
{
|
|
addPolygon(list->indices + i,list->sideCount, patch);
|
|
}
|
|
|
|
delete [] list->indices;
|
|
delete list;
|
|
conLists.erase(conLists.begin());
|
|
}
|
|
delete [] vertList;
|
|
}
|
|
|
|
DivVertex *VGCDivLoader::parseVList(FILE *file, unsigned int len, unsigned char mask, int &n)
|
|
{
|
|
//The passed-in mask is the format of the vertices, and
|
|
//determines the in-file structure
|
|
|
|
//Figure out the size-per-vertex
|
|
int size = floatSize() * 3;
|
|
|
|
if (mask & 0x01)
|
|
{
|
|
//Normals
|
|
size += floatSize() * 3;
|
|
}
|
|
|
|
if (mask & 0x02)
|
|
{
|
|
//RGB
|
|
size += floatSize() * 4;
|
|
} else if (mask & 0x04)
|
|
{
|
|
//Luminance
|
|
size += floatSize() * 2;
|
|
}
|
|
|
|
if (mask & 0x08)
|
|
{
|
|
//2D Tex
|
|
size += floatSize() * 2;
|
|
} else if (mask & 0x10)
|
|
{
|
|
//3D Tex
|
|
size += floatSize() * 3;
|
|
}
|
|
|
|
//Use the size-per-vertex to derive the number of vertices
|
|
n = len / size;
|
|
|
|
DivVertex *buffer = new DivVertex[n];
|
|
|
|
//Read in each vertex
|
|
for (int i=0; i < n; i++)
|
|
{
|
|
buffer[i].mask = mask;
|
|
fread(&buffer[i].x,floatSize(),1,file);
|
|
fread(&buffer[i].y,floatSize(),1,file);
|
|
fread(&buffer[i].z,floatSize(),1,file);
|
|
//buffer[i].z = -buffer[i].z;
|
|
|
|
buffer[i].x *= this->mHeaderInfo.scale;
|
|
buffer[i].y *= this->mHeaderInfo.scale;
|
|
buffer[i].z *= this->mHeaderInfo.scale;
|
|
if (mask & 0x01)
|
|
{
|
|
//Normals
|
|
fread(&buffer[i].Nx,floatSize(),1,file);
|
|
fread(&buffer[i].Ny,floatSize(),1,file);
|
|
fread(&buffer[i].Nz,floatSize(),1,file);
|
|
} else
|
|
{
|
|
buffer[i].Nx = 0;
|
|
buffer[i].Ny = 0;
|
|
buffer[i].Nz = 0;
|
|
}
|
|
|
|
if (mask & 0x02)
|
|
{
|
|
//RGB
|
|
fread(&buffer[i].r,floatSize(),1,file);
|
|
fread(&buffer[i].g,floatSize(),1,file);
|
|
fread(&buffer[i].b,floatSize(),1,file);
|
|
}
|
|
|
|
if (mask & 0x04)
|
|
{
|
|
//Luminance
|
|
fread(&buffer[i].l,floatSize(),1,file);
|
|
}
|
|
|
|
if (mask & 0x06)
|
|
{
|
|
//RGB or Luminance
|
|
fread(&buffer[i].a,floatSize(),1,file);
|
|
}
|
|
|
|
if (mask & 0x18)
|
|
{
|
|
//2D or 3D texture
|
|
fread(&buffer[i].u,floatSize(),1,file);
|
|
//buffer[i].u = 1.0f - buffer[i].u;
|
|
fread(&buffer[i].v,floatSize(),1,file);
|
|
}
|
|
|
|
if (mask & 0x10)
|
|
{
|
|
//3D texture
|
|
fread(&buffer[i].w,floatSize(),1,file);
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
int VGCDivLoader::getBlock(FILE *file, BIZ_IDENTITIES &id, unsigned int &len)
|
|
{
|
|
//BIZ_IDENTTAG is ryan's structure that mimics the in-file structure
|
|
//of the first two bytes of a block
|
|
BIZ_IDENTTAG ident;
|
|
|
|
fread(&ident,sizeof(BIZ_IDENTTAG),1,file);
|
|
|
|
//Get ID value
|
|
int idVal = ident.identity;
|
|
idVal = (ident.configBit)?(idVal+2000):idVal;
|
|
id = (BIZ_IDENTITIES)idVal;
|
|
|
|
//Get File Length
|
|
if (ident.headerSize == 2)
|
|
{
|
|
fread(&len,4,1,file);
|
|
return 6;
|
|
} else if (ident.headerSize == 1)
|
|
{
|
|
unsigned short size;
|
|
fread(&size,2,1,file);
|
|
len = size;
|
|
return 4;
|
|
} else
|
|
{
|
|
unsigned char size;
|
|
fread(&size,1,1,file);
|
|
len = size;
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
int VGCDivLoader::floatSize()
|
|
{
|
|
return 4;
|
|
}
|
|
|
|
int VGCDivLoader::getVertex(DivVertex vert, DivObject *obj)
|
|
{
|
|
//Try and find vertices, if the vertex doesn't exist, add it
|
|
std::vector<DivVertex>::iterator it = obj->vertices.begin();
|
|
int i=0;
|
|
|
|
while(it != obj->vertices.end() && memcmp(&(*it),&vert,sizeof(DivVertex)) != 0) {++it; ++i;}
|
|
|
|
if (it == obj->vertices.end())
|
|
{
|
|
obj->vertices.insert(obj->vertices.end(),vert);
|
|
}
|
|
|
|
//Return the vertex's index in the VGCDivLoader's vertex list
|
|
return i;
|
|
}
|
|
|
|
void VGCDivLoader::addPolygon(int *indices, int numIndices, DivPatch *patch)
|
|
{
|
|
//Break the polygon down into a triangle fan and add it to the tris
|
|
char facets = patch->surface.faceted;
|
|
|
|
for (int i=0; i < numIndices - 2; i++)
|
|
{
|
|
if (facets & 0x02)
|
|
{
|
|
Triangle tri;
|
|
tri.indices[2] = indices[0];
|
|
tri.indices[1] = indices[i+1];
|
|
tri.indices[0] = indices[i+2];
|
|
patch->frontTriangles.insert(patch->frontTriangles.end(),tri);
|
|
}
|
|
|
|
if (facets & 0x01)
|
|
{
|
|
Triangle tri;
|
|
tri.indices[0] = indices[0];
|
|
tri.indices[1] = indices[i+1];
|
|
tri.indices[2] = indices[i+2];
|
|
patch->backTriangles.insert(patch->backTriangles.end(),tri);
|
|
}
|
|
}
|
|
}
|
|
|