mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-14 23:01:26 +00:00
0b28322500
The "A Place of Malice" custom map has such a model which caused a crash (models/z13sp2/temple/pushable_collision_box.ASE), see #382
2331 lines
62 KiB
C++
2331 lines
62 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "idlib/containers/VectorSet.h"
|
|
#include "framework/DemoFile.h"
|
|
#include "renderer/tr_local.h"
|
|
#include "renderer/Model_local.h"
|
|
#include "renderer/Model_ase.h"
|
|
#include "renderer/Model_lwo.h"
|
|
#include "renderer/Model_ma.h"
|
|
#include "renderer/VertexCache.h"
|
|
|
|
#include "renderer/Model.h"
|
|
|
|
idCVar idRenderModelStatic::r_mergeModelSurfaces( "r_mergeModelSurfaces", "1", CVAR_BOOL|CVAR_RENDERER, "combine model surfaces with the same material" );
|
|
idCVar idRenderModelStatic::r_slopVertex( "r_slopVertex", "0.01", CVAR_RENDERER, "merge xyz coordinates this far apart" );
|
|
idCVar idRenderModelStatic::r_slopTexCoord( "r_slopTexCoord", "0.001", CVAR_RENDERER, "merge texture coordinates this far apart" );
|
|
idCVar idRenderModelStatic::r_slopNormal( "r_slopNormal", "0.02", CVAR_RENDERER, "merge normals that dot less than this" );
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::idRenderModelStatic
|
|
================
|
|
*/
|
|
idRenderModelStatic::idRenderModelStatic() {
|
|
name = "<undefined>";
|
|
bounds.Clear();
|
|
lastModifiedFrame = 0;
|
|
lastArchivedFrame = 0;
|
|
overlaysAdded = 0;
|
|
shadowHull = NULL;
|
|
isStaticWorldModel = false;
|
|
defaulted = false;
|
|
purged = false;
|
|
fastLoad = false;
|
|
reloadable = true;
|
|
levelLoadReferenced = false;
|
|
timeStamp = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::~idRenderModelStatic
|
|
================
|
|
*/
|
|
idRenderModelStatic::~idRenderModelStatic() {
|
|
PurgeModel();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelStatic::Print
|
|
==============
|
|
*/
|
|
void idRenderModelStatic::Print() const {
|
|
common->Printf( "%s\n", name.c_str() );
|
|
common->Printf( "Static model.\n" );
|
|
common->Printf( "bounds: (%f %f %f) to (%f %f %f)\n",
|
|
bounds[0][0], bounds[0][1], bounds[0][2],
|
|
bounds[1][0], bounds[1][1], bounds[1][2] );
|
|
|
|
common->Printf( " verts tris material\n" );
|
|
for ( int i = 0 ; i < NumSurfaces() ; i++ ) {
|
|
const modelSurface_t *surf = Surface( i );
|
|
|
|
srfTriangles_t *tri = surf->geometry;
|
|
const idMaterial *material = surf->shader;
|
|
|
|
if ( !tri ) {
|
|
common->Printf( "%2i: %s, NULL surface geometry\n", i, material->GetName() );
|
|
continue;
|
|
}
|
|
|
|
common->Printf( "%2i: %5i %5i %s", i, tri->numVerts, tri->numIndexes / 3, material->GetName() );
|
|
if ( tri->generateNormals ) {
|
|
common->Printf( " (smoothed)\n" );
|
|
} else {
|
|
common->Printf( "\n" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelStatic::Memory
|
|
==============
|
|
*/
|
|
int idRenderModelStatic::Memory() const {
|
|
int totalBytes = 0;
|
|
|
|
totalBytes += sizeof( *this );
|
|
totalBytes += name.DynamicMemoryUsed();
|
|
totalBytes += surfaces.MemoryUsed();
|
|
|
|
if ( shadowHull ) {
|
|
totalBytes += R_TriSurfMemory( shadowHull );
|
|
}
|
|
|
|
for ( int j = 0 ; j < NumSurfaces() ; j++ ) {
|
|
const modelSurface_t *surf = Surface( j );
|
|
if ( !surf->geometry ) {
|
|
continue;
|
|
}
|
|
totalBytes += R_TriSurfMemory( surf->geometry );
|
|
}
|
|
|
|
return totalBytes;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelStatic::List
|
|
==============
|
|
*/
|
|
void idRenderModelStatic::List() const {
|
|
int totalTris = 0;
|
|
int totalVerts = 0;
|
|
int totalBytes = 0;
|
|
|
|
totalBytes = Memory();
|
|
|
|
char closed = 'C';
|
|
for ( int j = 0 ; j < NumSurfaces() ; j++ ) {
|
|
const modelSurface_t *surf = Surface( j );
|
|
if ( !surf->geometry ) {
|
|
continue;
|
|
}
|
|
if ( !surf->geometry->perfectHull ) {
|
|
closed = ' ';
|
|
}
|
|
totalTris += surf->geometry->numIndexes / 3;
|
|
totalVerts += surf->geometry->numVerts;
|
|
}
|
|
common->Printf( "%c%4ik %3i %4i %4i %s", closed, totalBytes/1024, NumSurfaces(), totalVerts, totalTris, Name() );
|
|
|
|
if ( IsDynamicModel() == DM_CACHED ) {
|
|
common->Printf( " (DM_CACHED)" );
|
|
}
|
|
if ( IsDynamicModel() == DM_CONTINUOUS ) {
|
|
common->Printf( " (DM_CONTINUOUS)" );
|
|
}
|
|
if ( defaulted ) {
|
|
common->Printf( " (DEFAULTED)" );
|
|
}
|
|
if ( bounds[0][0] >= bounds[1][0] ) {
|
|
common->Printf( " (EMPTY BOUNDS)" );
|
|
}
|
|
if ( bounds[1][0] - bounds[0][0] > 100000 ) {
|
|
common->Printf( " (HUGE BOUNDS)" );
|
|
}
|
|
|
|
common->Printf( "\n" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsDefaultModel
|
|
================
|
|
*/
|
|
bool idRenderModelStatic::IsDefaultModel() const {
|
|
return defaulted;
|
|
}
|
|
|
|
/*
|
|
================
|
|
AddCubeFace
|
|
================
|
|
*/
|
|
static void AddCubeFace( srfTriangles_t *tri, idVec3 v1, idVec3 v2, idVec3 v3, idVec3 v4 ) {
|
|
tri->verts[tri->numVerts+0].Clear();
|
|
tri->verts[tri->numVerts+0].xyz = v1 * 8;
|
|
tri->verts[tri->numVerts+0].st[0] = 0;
|
|
tri->verts[tri->numVerts+0].st[1] = 0;
|
|
|
|
tri->verts[tri->numVerts+1].Clear();
|
|
tri->verts[tri->numVerts+1].xyz = v2 * 8;
|
|
tri->verts[tri->numVerts+1].st[0] = 1;
|
|
tri->verts[tri->numVerts+1].st[1] = 0;
|
|
|
|
tri->verts[tri->numVerts+2].Clear();
|
|
tri->verts[tri->numVerts+2].xyz = v3 * 8;
|
|
tri->verts[tri->numVerts+2].st[0] = 1;
|
|
tri->verts[tri->numVerts+2].st[1] = 1;
|
|
|
|
tri->verts[tri->numVerts+3].Clear();
|
|
tri->verts[tri->numVerts+3].xyz = v4 * 8;
|
|
tri->verts[tri->numVerts+3].st[0] = 0;
|
|
tri->verts[tri->numVerts+3].st[1] = 1;
|
|
|
|
tri->indexes[tri->numIndexes+0] = tri->numVerts + 0;
|
|
tri->indexes[tri->numIndexes+1] = tri->numVerts + 1;
|
|
tri->indexes[tri->numIndexes+2] = tri->numVerts + 2;
|
|
tri->indexes[tri->numIndexes+3] = tri->numVerts + 0;
|
|
tri->indexes[tri->numIndexes+4] = tri->numVerts + 2;
|
|
tri->indexes[tri->numIndexes+5] = tri->numVerts + 3;
|
|
|
|
tri->numVerts += 4;
|
|
tri->numIndexes += 6;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::MakeDefaultModel
|
|
================
|
|
*/
|
|
void idRenderModelStatic::MakeDefaultModel() {
|
|
|
|
defaulted = true;
|
|
|
|
// throw out any surfaces we already have
|
|
PurgeModel();
|
|
|
|
// create one new surface
|
|
modelSurface_t surf;
|
|
|
|
srfTriangles_t *tri = R_AllocStaticTriSurf();
|
|
|
|
surf.shader = tr.defaultMaterial;
|
|
surf.geometry = tri;
|
|
|
|
R_AllocStaticTriSurfVerts( tri, 24 );
|
|
R_AllocStaticTriSurfIndexes( tri, 36 );
|
|
|
|
AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(1, 1, 1), idVec3(1, -1, 1), idVec3(-1, -1, 1) );
|
|
AddCubeFace( tri, idVec3(-1, 1, -1), idVec3(-1, -1, -1), idVec3(1, -1, -1), idVec3(1, 1, -1) );
|
|
|
|
AddCubeFace( tri, idVec3(1, -1, 1), idVec3(1, 1, 1), idVec3(1, 1, -1), idVec3(1, -1, -1) );
|
|
AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(-1, -1, -1), idVec3(-1, 1, -1), idVec3(-1, 1, 1) );
|
|
|
|
AddCubeFace( tri, idVec3(-1, -1, 1), idVec3(1, -1, 1), idVec3(1, -1, -1), idVec3(-1, -1, -1) );
|
|
AddCubeFace( tri, idVec3(-1, 1, 1), idVec3(-1, 1, -1), idVec3(1, 1, -1), idVec3(1, 1, 1) );
|
|
|
|
tri->generateNormals = true;
|
|
|
|
AddSurface( surf );
|
|
FinishSurfaces();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::PartialInitFromFile
|
|
================
|
|
*/
|
|
void idRenderModelStatic::PartialInitFromFile( const char *fileName ) {
|
|
fastLoad = true;
|
|
InitFromFile( fileName );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::InitFromFile
|
|
================
|
|
*/
|
|
void idRenderModelStatic::InitFromFile( const char *fileName ) {
|
|
bool loaded;
|
|
idStr extension;
|
|
|
|
InitEmpty( fileName );
|
|
|
|
// FIXME: load new .proc map format
|
|
|
|
name.ExtractFileExtension( extension );
|
|
|
|
if ( extension.Icmp( "ase" ) == 0 ) {
|
|
loaded = LoadASE( name );
|
|
reloadable = true;
|
|
} else if ( extension.Icmp( "lwo" ) == 0 ) {
|
|
loaded = LoadLWO( name );
|
|
reloadable = true;
|
|
} else if ( extension.Icmp( "flt" ) == 0 ) {
|
|
loaded = LoadFLT( name );
|
|
reloadable = true;
|
|
} else if ( extension.Icmp( "ma" ) == 0 ) {
|
|
loaded = LoadMA( name );
|
|
reloadable = true;
|
|
} else {
|
|
common->Warning( "idRenderModelStatic::InitFromFile: unknown type for model: \'%s\'", name.c_str() );
|
|
loaded = false;
|
|
}
|
|
|
|
if ( !loaded ) {
|
|
common->Warning( "Couldn't load model: '%s'", name.c_str() );
|
|
MakeDefaultModel();
|
|
return;
|
|
}
|
|
|
|
// it is now available for use
|
|
purged = false;
|
|
|
|
// create the bounds for culling and dynamic surface creation
|
|
FinishSurfaces();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::LoadModel
|
|
================
|
|
*/
|
|
void idRenderModelStatic::LoadModel() {
|
|
PurgeModel();
|
|
InitFromFile( name );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::InitEmpty
|
|
================
|
|
*/
|
|
void idRenderModelStatic::InitEmpty( const char *fileName ) {
|
|
// model names of the form _area* are static parts of the
|
|
// world, and have already been considered for optimized shadows
|
|
// other model names are inline entity models, and need to be
|
|
// shadowed normally
|
|
if ( !idStr::Cmpn( fileName, "_area", 5 ) ) {
|
|
isStaticWorldModel = true;
|
|
} else {
|
|
isStaticWorldModel = false;
|
|
}
|
|
|
|
name = fileName;
|
|
reloadable = false; // if it didn't come from a file, we can't reload it
|
|
PurgeModel();
|
|
purged = false;
|
|
bounds.Zero();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::AddSurface
|
|
================
|
|
*/
|
|
void idRenderModelStatic::AddSurface( modelSurface_t surface ) {
|
|
surfaces.Append( surface );
|
|
if ( surface.geometry ) {
|
|
bounds += surface.geometry->bounds;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::Name
|
|
================
|
|
*/
|
|
const char *idRenderModelStatic::Name() const {
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::Timestamp
|
|
================
|
|
*/
|
|
ID_TIME_T idRenderModelStatic::Timestamp() const {
|
|
return timeStamp;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::NumSurfaces
|
|
================
|
|
*/
|
|
int idRenderModelStatic::NumSurfaces() const {
|
|
return surfaces.Num();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::NumBaseSurfaces
|
|
================
|
|
*/
|
|
int idRenderModelStatic::NumBaseSurfaces() const {
|
|
return surfaces.Num() - overlaysAdded;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::Surface
|
|
================
|
|
*/
|
|
const modelSurface_t *idRenderModelStatic::Surface( int surfaceNum ) const {
|
|
return &surfaces[surfaceNum];
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::AllocSurfaceTriangles
|
|
================
|
|
*/
|
|
srfTriangles_t *idRenderModelStatic::AllocSurfaceTriangles( int numVerts, int numIndexes ) const {
|
|
srfTriangles_t *tri = R_AllocStaticTriSurf();
|
|
R_AllocStaticTriSurfVerts( tri, numVerts );
|
|
R_AllocStaticTriSurfIndexes( tri, numIndexes );
|
|
return tri;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::FreeSurfaceTriangles
|
|
================
|
|
*/
|
|
void idRenderModelStatic::FreeSurfaceTriangles( srfTriangles_t *tris ) const {
|
|
R_FreeStaticTriSurf( tris );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::ShadowHull
|
|
================
|
|
*/
|
|
srfTriangles_t *idRenderModelStatic::ShadowHull() const {
|
|
return shadowHull;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsStaticWorldModel
|
|
================
|
|
*/
|
|
bool idRenderModelStatic::IsStaticWorldModel() const {
|
|
return isStaticWorldModel;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsDynamicModel
|
|
================
|
|
*/
|
|
dynamicModel_t idRenderModelStatic::IsDynamicModel() const {
|
|
// dynamic subclasses will override this
|
|
return DM_STATIC;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsReloadable
|
|
================
|
|
*/
|
|
bool idRenderModelStatic::IsReloadable() const {
|
|
return reloadable;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::Bounds
|
|
================
|
|
*/
|
|
idBounds idRenderModelStatic::Bounds( const struct renderEntity_s *mdef ) const {
|
|
return bounds;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::DepthHack
|
|
================
|
|
*/
|
|
float idRenderModelStatic::DepthHack() const {
|
|
return 0.0f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::InstantiateDynamicModel
|
|
================
|
|
*/
|
|
idRenderModel *idRenderModelStatic::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel ) {
|
|
if ( cachedModel ) {
|
|
delete cachedModel;
|
|
cachedModel = NULL;
|
|
}
|
|
common->Error( "InstantiateDynamicModel called on static model '%s'", name.c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::NumJoints
|
|
================
|
|
*/
|
|
int idRenderModelStatic::NumJoints( void ) const {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::GetJoints
|
|
================
|
|
*/
|
|
const idMD5Joint *idRenderModelStatic::GetJoints( void ) const {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::GetJointHandle
|
|
================
|
|
*/
|
|
jointHandle_t idRenderModelStatic::GetJointHandle( const char *name ) const {
|
|
return INVALID_JOINT;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::GetJointName
|
|
================
|
|
*/
|
|
const char * idRenderModelStatic::GetJointName( jointHandle_t handle ) const {
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::GetDefaultPose
|
|
================
|
|
*/
|
|
const idJointQuat *idRenderModelStatic::GetDefaultPose( void ) const {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::NearestJoint
|
|
================
|
|
*/
|
|
int idRenderModelStatic::NearestJoint( int surfaceNum, int a, int b, int c ) const {
|
|
return INVALID_JOINT;
|
|
}
|
|
|
|
|
|
//=====================================================================
|
|
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::FinishSurfaces
|
|
|
|
The mergeShadows option allows surfaces with different textures to share
|
|
silhouette edges for shadow calculation, instead of leaving shared edges
|
|
hanging.
|
|
|
|
If any of the original shaders have the noSelfShadow flag set, the surfaces
|
|
can't be merged, because they will need to be drawn in different order.
|
|
|
|
If there is only one surface, a separate merged surface won't be generated.
|
|
|
|
A model with multiple surfaces can't later have a skinned shader change the
|
|
state of the noSelfShadow flag.
|
|
|
|
-----------------
|
|
|
|
Creates mirrored copies of two sided surfaces with normal maps, which would
|
|
otherwise light funny.
|
|
|
|
Extends the bounds of deformed surfaces so they don't cull incorrectly at screen edges.
|
|
|
|
================
|
|
*/
|
|
void idRenderModelStatic::FinishSurfaces() {
|
|
int i;
|
|
int totalVerts, totalIndexes;
|
|
|
|
purged = false;
|
|
|
|
// make sure we don't have a huge bounds even if we don't finish everything
|
|
bounds.Zero();
|
|
|
|
if ( surfaces.Num() == 0 ) {
|
|
return;
|
|
}
|
|
|
|
// renderBump doesn't care about most of this
|
|
if ( fastLoad ) {
|
|
bounds.Zero();
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
R_BoundTriSurf( surf->geometry );
|
|
bounds.AddBounds( surf->geometry->bounds );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// cleanup all the final surfaces, but don't create sil edges
|
|
totalVerts = 0;
|
|
totalIndexes = 0;
|
|
|
|
// decide if we are going to merge all the surfaces into one shadower
|
|
int numOriginalSurfaces = surfaces.Num();
|
|
|
|
// make sure there aren't any NULL shaders or geometry
|
|
for ( i = 0 ; i < numOriginalSurfaces ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
if ( surf->geometry == NULL || surf->shader == NULL ) {
|
|
MakeDefaultModel();
|
|
common->Error( "Model %s, surface %i had NULL geometry", name.c_str(), i );
|
|
}
|
|
if ( surf->shader == NULL ) {
|
|
MakeDefaultModel();
|
|
common->Error( "Model %s, surface %i had NULL shader", name.c_str(), i );
|
|
}
|
|
}
|
|
|
|
// duplicate and reverse triangles for two sided bump mapped surfaces
|
|
// note that this won't catch surfaces that have their shaders dynamically
|
|
// changed, and won't work with animated models.
|
|
// It is better to create completely separate surfaces, rather than
|
|
// add vertexes and indexes to the existing surface, because the
|
|
// tangent generation wouldn't like the acute shared edges
|
|
for ( i = 0 ; i < numOriginalSurfaces ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
if ( surf->shader->ShouldCreateBackSides() ) {
|
|
srfTriangles_t *newTri;
|
|
|
|
newTri = R_CopyStaticTriSurf( surf->geometry );
|
|
R_ReverseTriangles( newTri );
|
|
|
|
modelSurface_t newSurf;
|
|
|
|
newSurf.shader = surf->shader;
|
|
newSurf.geometry = newTri;
|
|
|
|
AddSurface( newSurf );
|
|
}
|
|
}
|
|
|
|
// clean the surfaces
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
R_CleanupTriangles( surf->geometry, surf->geometry->generateNormals, true, surf->shader->UseUnsmoothedTangents() );
|
|
if ( surf->shader->SurfaceCastsShadow() ) {
|
|
totalVerts += surf->geometry->numVerts;
|
|
totalIndexes += surf->geometry->numIndexes;
|
|
}
|
|
}
|
|
|
|
// add up the total surface area for development information
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
srfTriangles_t *tri = surf->geometry;
|
|
|
|
for ( int j = 0 ; j < tri->numIndexes ; j += 3 ) {
|
|
float area = idWinding::TriangleArea( tri->verts[tri->indexes[j]].xyz,
|
|
tri->verts[tri->indexes[j+1]].xyz, tri->verts[tri->indexes[j+2]].xyz );
|
|
const_cast<idMaterial *>(surf->shader)->AddToSurfaceArea( area );
|
|
}
|
|
}
|
|
|
|
// calculate the bounds
|
|
if ( surfaces.Num() == 0 ) {
|
|
bounds.Zero();
|
|
} else {
|
|
bounds.Clear();
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
modelSurface_t *surf = &surfaces[i];
|
|
|
|
// if the surface has a deformation, increase the bounds
|
|
// the amount here is somewhat arbitrary, designed to handle
|
|
// autosprites and flares, but could be done better with exact
|
|
// deformation information.
|
|
// Note that this doesn't handle deformations that are skinned in
|
|
// at run time...
|
|
if ( surf->shader->Deform() != DFRM_NONE ) {
|
|
srfTriangles_t *tri = surf->geometry;
|
|
idVec3 mid = ( tri->bounds[1] + tri->bounds[0] ) * 0.5f;
|
|
float radius = ( tri->bounds[0] - mid ).Length();
|
|
radius += 20.0f;
|
|
|
|
tri->bounds[0][0] = mid[0] - radius;
|
|
tri->bounds[0][1] = mid[1] - radius;
|
|
tri->bounds[0][2] = mid[2] - radius;
|
|
|
|
tri->bounds[1][0] = mid[0] + radius;
|
|
tri->bounds[1][1] = mid[1] + radius;
|
|
tri->bounds[1][2] = mid[2] + radius;
|
|
}
|
|
|
|
// add to the model bounds
|
|
bounds.AddBounds( surf->geometry->bounds );
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::ConvertASEToModelSurfaces
|
|
=================
|
|
*/
|
|
typedef struct matchVert_s {
|
|
struct matchVert_s *next;
|
|
int v, tv;
|
|
byte color[4];
|
|
idVec3 normal;
|
|
} matchVert_t;
|
|
|
|
bool idRenderModelStatic::ConvertASEToModelSurfaces( const struct aseModel_s *ase ) {
|
|
aseObject_t * object;
|
|
aseMesh_t * mesh;
|
|
aseMaterial_t * material;
|
|
const idMaterial *im1, *im2;
|
|
srfTriangles_t *tri;
|
|
int objectNum;
|
|
int i, j, k;
|
|
int v, tv;
|
|
int * vRemap;
|
|
int * tvRemap;
|
|
matchVert_t * mvTable; // all of the match verts
|
|
matchVert_t ** mvHash; // points inside mvTable for each xyz index
|
|
matchVert_t * lastmv;
|
|
matchVert_t * mv;
|
|
idVec3 normal;
|
|
float uOffset, vOffset, textureSin, textureCos;
|
|
float uTiling, vTiling;
|
|
int * mergeTo;
|
|
byte * color;
|
|
static byte identityColor[4] = { 255, 255, 255, 255 };
|
|
modelSurface_t surf, *modelSurf;
|
|
|
|
if ( !ase ) {
|
|
return false;
|
|
}
|
|
if ( ase->objects.Num() < 1 ) {
|
|
return false;
|
|
}
|
|
|
|
timeStamp = ase->timeStamp;
|
|
|
|
// the modeling programs can save out multiple surfaces with a common
|
|
// material, but we would like to mege them together where possible
|
|
// meaning that this->NumSurfaces() <= ase->objects.currentElements
|
|
mergeTo = (int *)_alloca( ase->objects.Num() * sizeof( *mergeTo ) );
|
|
surf.geometry = NULL;
|
|
if ( ase->materials.Num() == 0 ) {
|
|
// if we don't have any materials, dump everything into a single surface
|
|
surf.shader = tr.defaultMaterial;
|
|
surf.id = 0;
|
|
this->AddSurface( surf );
|
|
for ( i = 0 ; i < ase->objects.Num() ; i++ ) {
|
|
mergeTo[i] = 0;
|
|
}
|
|
} else if ( !r_mergeModelSurfaces.GetBool() ) {
|
|
// don't merge any
|
|
for ( i = 0 ; i < ase->objects.Num() ; i++ ) {
|
|
mergeTo[i] = i;
|
|
object = ase->objects[i];
|
|
material = ase->materials[object->materialRef];
|
|
surf.shader = declManager->FindMaterial( material->name );
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
} else {
|
|
// search for material matches
|
|
for ( i = 0 ; i < ase->objects.Num() ; i++ ) {
|
|
object = ase->objects[i];
|
|
material = ase->materials[object->materialRef];
|
|
im1 = declManager->FindMaterial( material->name );
|
|
if ( im1->IsDiscrete() ) {
|
|
// flares, autosprites, etc
|
|
j = this->NumSurfaces();
|
|
} else {
|
|
for ( j = 0 ; j < this->NumSurfaces() ; j++ ) {
|
|
modelSurf = &this->surfaces[j];
|
|
im2 = modelSurf->shader;
|
|
if ( im1 == im2 ) {
|
|
// merge this
|
|
mergeTo[i] = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( j == this->NumSurfaces() ) {
|
|
// didn't merge
|
|
mergeTo[i] = j;
|
|
surf.shader = im1;
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
}
|
|
}
|
|
|
|
idVectorSubset<idVec3, 3> vertexSubset;
|
|
idVectorSubset<idVec2, 2> texCoordSubset;
|
|
|
|
// build the surfaces
|
|
for ( objectNum = 0 ; objectNum < ase->objects.Num() ; objectNum++ ) {
|
|
object = ase->objects[objectNum];
|
|
mesh = &object->mesh;
|
|
material = (ase->materials.Num() > object->materialRef) ? ase->materials[object->materialRef] : NULL;
|
|
im1 = declManager->FindMaterial( material ? material->name : NULL );
|
|
|
|
bool normalsParsed = mesh->normalsParsed;
|
|
|
|
// completely ignore any explict normals on surfaces with a renderbump command
|
|
// which will guarantee the best contours and least vertexes.
|
|
const char *rb = im1->GetRenderBump();
|
|
if ( rb && rb[0] ) {
|
|
normalsParsed = false;
|
|
}
|
|
|
|
// It seems like the tools our artists are using often generate
|
|
// verts and texcoords slightly separated that should be merged
|
|
// note that we really should combine the surfaces with common materials
|
|
// before doing this operation, because we can miss a slop combination
|
|
// if they are in different surfaces
|
|
|
|
vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < mesh->numVertexes; j++ ) {
|
|
vRemap[j] = j;
|
|
}
|
|
} else {
|
|
float vertexEpsilon = r_slopVertex.GetFloat();
|
|
float expand = 2 * 32 * vertexEpsilon;
|
|
idVec3 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes );
|
|
mins -= idVec3( expand, expand, expand );
|
|
maxs += idVec3( expand, expand, expand );
|
|
vertexSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < mesh->numVertexes; j++ ) {
|
|
vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon );
|
|
}
|
|
}
|
|
|
|
tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < mesh->numTVertexes; j++ ) {
|
|
tvRemap[j] = j;
|
|
}
|
|
} else {
|
|
float texCoordEpsilon = r_slopTexCoord.GetFloat();
|
|
float expand = 2 * 32 * texCoordEpsilon;
|
|
idVec2 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes );
|
|
mins -= idVec2( expand, expand );
|
|
maxs += idVec2( expand, expand );
|
|
texCoordSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < mesh->numTVertexes; j++ ) {
|
|
tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon );
|
|
}
|
|
}
|
|
|
|
// we need to find out how many unique vertex / texcoord combinations
|
|
// there are, because ASE tracks them separately but we need them unified
|
|
|
|
// the maximum possible number of combined vertexes is the number of indexes
|
|
mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) );
|
|
|
|
// we will have a hash chain based on the xyz values
|
|
mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) );
|
|
|
|
// allocate triangle surface
|
|
tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = 0;
|
|
tri->numIndexes = 0;
|
|
R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 );
|
|
tri->generateNormals = !normalsParsed;
|
|
|
|
// init default normal, color and tex coord index
|
|
normal.Zero();
|
|
color = identityColor;
|
|
tv = 0;
|
|
|
|
// find all the unique combinations
|
|
float normalEpsilon = 1.0f - r_slopNormal.GetFloat();
|
|
for ( j = 0; j < mesh->numFaces; j++ ) {
|
|
for ( k = 0; k < 3; k++ ) {
|
|
v = mesh->faces[j].vertexNum[k];
|
|
|
|
if ( v < 0 || v >= mesh->numVertexes ) {
|
|
common->Error( "ConvertASEToModelSurfaces: bad vertex index in ASE file %s", name.c_str() );
|
|
}
|
|
|
|
// collapse the position if it was slightly offset
|
|
v = vRemap[v];
|
|
|
|
// we may or may not have texcoords to compare
|
|
if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) {
|
|
tv = mesh->faces[j].tVertexNum[k];
|
|
if ( tv < 0 || tv >= mesh->numTVertexes ) {
|
|
common->Error( "ConvertASEToModelSurfaces: bad tex coord index in ASE file %s", name.c_str() );
|
|
}
|
|
// collapse the tex coord if it was slightly offset
|
|
tv = tvRemap[tv];
|
|
}
|
|
|
|
// we may or may not have normals to compare
|
|
if ( normalsParsed ) {
|
|
normal = mesh->faces[j].vertexNormals[k];
|
|
}
|
|
|
|
// we may or may not have colors to compare
|
|
if ( mesh->colorsParsed ) {
|
|
color = mesh->faces[j].vertexColors[k];
|
|
}
|
|
|
|
// find a matching vert
|
|
for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) {
|
|
if ( mv->tv != tv ) {
|
|
continue;
|
|
}
|
|
if ( *(unsigned *)mv->color != *(unsigned *)color ) {
|
|
continue;
|
|
}
|
|
if ( !normalsParsed ) {
|
|
// if we are going to create the normals, just
|
|
// matching texcoords is enough
|
|
break;
|
|
}
|
|
if ( mv->normal * normal > normalEpsilon ) {
|
|
break; // we already have this one
|
|
}
|
|
}
|
|
if ( !mv ) {
|
|
// allocate a new match vert and link to hash chain
|
|
mv = &mvTable[ tri->numVerts ];
|
|
mv->v = v;
|
|
mv->tv = tv;
|
|
mv->normal = normal;
|
|
*(unsigned *)mv->color = *(unsigned *)color;
|
|
mv->next = NULL;
|
|
if ( lastmv ) {
|
|
lastmv->next = mv;
|
|
} else {
|
|
mvHash[v] = mv;
|
|
}
|
|
tri->numVerts++;
|
|
}
|
|
|
|
tri->indexes[tri->numIndexes] = mv - mvTable;
|
|
tri->numIndexes++;
|
|
}
|
|
}
|
|
|
|
// allocate space for the indexes and copy them
|
|
if ( tri->numIndexes > mesh->numFaces * 3 ) {
|
|
common->FatalError( "ConvertASEToModelSurfaces: index miscount in ASE file %s", name.c_str() );
|
|
}
|
|
if ( tri->numVerts > mesh->numFaces * 3 ) {
|
|
common->FatalError( "ConvertASEToModelSurfaces: vertex miscount in ASE file %s", name.c_str() );
|
|
}
|
|
|
|
// an ASE allows the texture coordinates to be scaled, translated, and rotated
|
|
if ( ase->materials.Num() == 0 ) {
|
|
uOffset = vOffset = 0.0f;
|
|
uTiling = vTiling = 1.0f;
|
|
textureSin = 0.0f;
|
|
textureCos = 1.0f;
|
|
} else {
|
|
material = ase->materials[object->materialRef];
|
|
uOffset = -material->uOffset;
|
|
vOffset = material->vOffset;
|
|
uTiling = material->uTiling;
|
|
vTiling = material->vTiling;
|
|
textureSin = idMath::Sin( material->angle );
|
|
textureCos = idMath::Cos( material->angle );
|
|
}
|
|
|
|
// now allocate and generate the combined vertexes
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
for ( j = 0; j < tri->numVerts; j++ ) {
|
|
mv = &mvTable[j];
|
|
tri->verts[ j ].Clear();
|
|
tri->verts[ j ].xyz = mesh->vertexes[ mv->v ];
|
|
tri->verts[ j ].normal = mv->normal;
|
|
*(unsigned *)tri->verts[j].color = *(unsigned *)mv->color;
|
|
if ( mesh->numTVFaces == mesh->numFaces && mesh->numTVertexes != 0 ) {
|
|
const idVec2 &tv = mesh->tvertexes[ mv->tv ];
|
|
float u = tv.x * uTiling + uOffset;
|
|
float v = tv.y * vTiling + vOffset;
|
|
tri->verts[ j ].st[0] = u * textureCos + v * textureSin;
|
|
tri->verts[ j ].st[1] = u * -textureSin + v * textureCos;
|
|
}
|
|
}
|
|
|
|
R_StaticFree( mvTable );
|
|
R_StaticFree( mvHash );
|
|
R_StaticFree( tvRemap );
|
|
R_StaticFree( vRemap );
|
|
|
|
// see if we need to merge with a previous surface of the same material
|
|
modelSurf = &this->surfaces[mergeTo[ objectNum ]];
|
|
srfTriangles_t *mergeTri = modelSurf->geometry;
|
|
if ( !mergeTri ) {
|
|
modelSurf->geometry = tri;
|
|
} else {
|
|
modelSurf->geometry = R_MergeTriangles( mergeTri, tri );
|
|
R_FreeStaticTriSurf( tri );
|
|
R_FreeStaticTriSurf( mergeTri );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::ConvertLWOToModelSurfaces
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::ConvertLWOToModelSurfaces( const struct st_lwObject *lwo ) {
|
|
const idMaterial *im1, *im2;
|
|
srfTriangles_t *tri;
|
|
lwSurface * lwoSurf;
|
|
int numTVertexes;
|
|
int i, j, k;
|
|
int v, tv;
|
|
idVec3 * vList;
|
|
int * vRemap;
|
|
idVec2 * tvList;
|
|
int * tvRemap;
|
|
matchVert_t * mvTable; // all of the match verts
|
|
matchVert_t ** mvHash; // points inside mvTable for each xyz index
|
|
matchVert_t * lastmv;
|
|
matchVert_t * mv;
|
|
idVec3 normal;
|
|
int * mergeTo;
|
|
byte color[4];
|
|
modelSurface_t surf, *modelSurf;
|
|
|
|
if ( !lwo ) {
|
|
return false;
|
|
}
|
|
if ( lwo->surf == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
timeStamp = lwo->timeStamp;
|
|
|
|
// count the number of surfaces
|
|
i = 0;
|
|
for ( lwoSurf = lwo->surf; lwoSurf; lwoSurf = lwoSurf->next ) {
|
|
i++;
|
|
}
|
|
|
|
// the modeling programs can save out multiple surfaces with a common
|
|
// material, but we would like to merge them together where possible
|
|
mergeTo = (int *)_alloca( i * sizeof( mergeTo[0] ) );
|
|
memset( &surf, 0, sizeof( surf ) );
|
|
|
|
if ( !r_mergeModelSurfaces.GetBool() ) {
|
|
// don't merge any
|
|
for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) {
|
|
mergeTo[i] = i;
|
|
surf.shader = declManager->FindMaterial( lwoSurf->name );
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
} else {
|
|
// search for material matches
|
|
for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) {
|
|
im1 = declManager->FindMaterial( lwoSurf->name );
|
|
if ( im1->IsDiscrete() ) {
|
|
// flares, autosprites, etc
|
|
j = this->NumSurfaces();
|
|
} else {
|
|
for ( j = 0 ; j < this->NumSurfaces() ; j++ ) {
|
|
modelSurf = &this->surfaces[j];
|
|
im2 = modelSurf->shader;
|
|
if ( im1 == im2 ) {
|
|
// merge this
|
|
mergeTo[i] = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( j == this->NumSurfaces() ) {
|
|
// didn't merge
|
|
mergeTo[i] = j;
|
|
surf.shader = im1;
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
}
|
|
}
|
|
|
|
idVectorSubset<idVec3, 3> vertexSubset;
|
|
idVectorSubset<idVec2, 2> texCoordSubset;
|
|
|
|
// we only ever use the first layer
|
|
lwLayer *layer = lwo->layer;
|
|
|
|
// vertex positions
|
|
if ( layer->point.count <= 0 ) {
|
|
common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing vertex data", name.c_str() );
|
|
return false;
|
|
}
|
|
|
|
vList = (idVec3 *)R_StaticAlloc( layer->point.count * sizeof( vList[0] ) );
|
|
for ( j = 0; j < layer->point.count; j++ ) {
|
|
vList[j].x = layer->point.pt[j].pos[0];
|
|
vList[j].y = layer->point.pt[j].pos[2];
|
|
vList[j].z = layer->point.pt[j].pos[1];
|
|
}
|
|
|
|
// vertex texture coords
|
|
numTVertexes = 0;
|
|
|
|
if ( layer->nvmaps ) {
|
|
for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) {
|
|
if ( vm->type == LWID_('T','X','U','V') ) {
|
|
numTVertexes += vm->nverts;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( numTVertexes ) {
|
|
tvList = (idVec2 *)Mem_Alloc( numTVertexes * sizeof( tvList[0] ) );
|
|
int offset = 0;
|
|
for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) {
|
|
if ( vm->type == LWID_('T','X','U','V') ) {
|
|
vm->offset = offset;
|
|
for ( k = 0; k < vm->nverts; k++ ) {
|
|
tvList[k + offset].x = vm->val[k][0];
|
|
tvList[k + offset].y = 1.0f - vm->val[k][1]; // invert the t
|
|
}
|
|
offset += vm->nverts;
|
|
}
|
|
}
|
|
} else {
|
|
common->Warning( "ConvertLWOToModelSurfaces: model \'%s\' has bad or missing uv data", name.c_str() );
|
|
numTVertexes = 1;
|
|
tvList = (idVec2 *)Mem_ClearedAlloc( numTVertexes * sizeof( tvList[0] ) );
|
|
}
|
|
|
|
// It seems like the tools our artists are using often generate
|
|
// verts and texcoords slightly separated that should be merged
|
|
// note that we really should combine the surfaces with common materials
|
|
// before doing this operation, because we can miss a slop combination
|
|
// if they are in different surfaces
|
|
|
|
vRemap = (int *)R_StaticAlloc( layer->point.count * sizeof( vRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < layer->point.count; j++ ) {
|
|
vRemap[j] = j;
|
|
}
|
|
} else {
|
|
float vertexEpsilon = r_slopVertex.GetFloat();
|
|
float expand = 2 * 32 * vertexEpsilon;
|
|
idVec3 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, vList, layer->point.count );
|
|
mins -= idVec3( expand, expand, expand );
|
|
maxs += idVec3( expand, expand, expand );
|
|
vertexSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < layer->point.count; j++ ) {
|
|
vRemap[j] = vertexSubset.FindVector( vList, j, vertexEpsilon );
|
|
}
|
|
}
|
|
|
|
tvRemap = (int *)R_StaticAlloc( numTVertexes * sizeof( tvRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < numTVertexes; j++ ) {
|
|
tvRemap[j] = j;
|
|
}
|
|
} else {
|
|
float texCoordEpsilon = r_slopTexCoord.GetFloat();
|
|
float expand = 2 * 32 * texCoordEpsilon;
|
|
idVec2 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, tvList, numTVertexes );
|
|
mins -= idVec2( expand, expand );
|
|
maxs += idVec2( expand, expand );
|
|
texCoordSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < numTVertexes; j++ ) {
|
|
tvRemap[j] = texCoordSubset.FindVector( tvList, j, texCoordEpsilon );
|
|
}
|
|
}
|
|
|
|
// build the surfaces
|
|
for ( lwoSurf = lwo->surf, i = 0; lwoSurf; lwoSurf = lwoSurf->next, i++ ) {
|
|
im1 = declManager->FindMaterial( lwoSurf->name );
|
|
|
|
bool normalsParsed = true;
|
|
|
|
// completely ignore any explict normals on surfaces with a renderbump command
|
|
// which will guarantee the best contours and least vertexes.
|
|
const char *rb = im1->GetRenderBump();
|
|
if ( rb && rb[0] ) {
|
|
normalsParsed = false;
|
|
}
|
|
|
|
// we need to find out how many unique vertex / texcoord combinations there are
|
|
|
|
// the maximum possible number of combined vertexes is the number of indexes
|
|
mvTable = (matchVert_t *)R_ClearedStaticAlloc( layer->polygon.count * 3 * sizeof( mvTable[0] ) );
|
|
|
|
// we will have a hash chain based on the xyz values
|
|
mvHash = (matchVert_t **)R_ClearedStaticAlloc( layer->point.count * sizeof( mvHash[0] ) );
|
|
|
|
// allocate triangle surface
|
|
tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = 0;
|
|
tri->numIndexes = 0;
|
|
R_AllocStaticTriSurfIndexes( tri, layer->polygon.count * 3 );
|
|
tri->generateNormals = !normalsParsed;
|
|
|
|
// find all the unique combinations
|
|
float normalEpsilon;
|
|
if ( fastLoad ) {
|
|
normalEpsilon = 1.0f; // don't merge unless completely exact
|
|
} else {
|
|
normalEpsilon = 1.0f - r_slopNormal.GetFloat();
|
|
}
|
|
for ( j = 0; j < layer->polygon.count; j++ ) {
|
|
lwPolygon *poly = &layer->polygon.pol[j];
|
|
|
|
if ( poly->surf != lwoSurf ) {
|
|
continue;
|
|
}
|
|
|
|
if ( poly->nverts != 3 ) {
|
|
common->Warning( "ConvertLWOToModelSurfaces: model %s has too many verts for a poly! Make sure you triplet it down", name.c_str() );
|
|
continue;
|
|
}
|
|
|
|
for ( k = 0; k < 3; k++ ) {
|
|
|
|
v = vRemap[poly->v[k].index];
|
|
|
|
normal.x = poly->v[k].norm[0];
|
|
normal.y = poly->v[k].norm[2];
|
|
normal.z = poly->v[k].norm[1];
|
|
|
|
// LWO models aren't all that pretty when it comes down to the floating point values they store
|
|
normal.FixDegenerateNormal();
|
|
|
|
tv = 0;
|
|
|
|
color[0] = lwoSurf->color.rgb[0] * 255;
|
|
color[1] = lwoSurf->color.rgb[1] * 255;
|
|
color[2] = lwoSurf->color.rgb[2] * 255;
|
|
color[3] = 255;
|
|
|
|
// first set attributes from the vertex
|
|
lwPoint *pt = &layer->point.pt[poly->v[k].index];
|
|
int nvm;
|
|
for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) {
|
|
lwVMapPt *vm = &pt->vm[nvm];
|
|
|
|
if ( vm->vmap->type == LWID_('T','X','U','V') ) {
|
|
tv = tvRemap[vm->index + vm->vmap->offset];
|
|
}
|
|
if ( vm->vmap->type == LWID_('R','G','B','A') ) {
|
|
for ( int chan = 0; chan < 4; chan++ ) {
|
|
color[chan] = 255 * vm->vmap->val[vm->index][chan];
|
|
}
|
|
}
|
|
}
|
|
|
|
// then override with polygon attributes
|
|
for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) {
|
|
lwVMapPt *vm = &poly->v[k].vm[nvm];
|
|
|
|
if ( vm->vmap->type == LWID_('T','X','U','V') ) {
|
|
tv = tvRemap[vm->index + vm->vmap->offset];
|
|
}
|
|
if ( vm->vmap->type == LWID_('R','G','B','A') ) {
|
|
for ( int chan = 0; chan < 4; chan++ ) {
|
|
color[chan] = 255 * vm->vmap->val[vm->index][chan];
|
|
}
|
|
}
|
|
}
|
|
|
|
// find a matching vert
|
|
for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) {
|
|
if ( mv->tv != tv ) {
|
|
continue;
|
|
}
|
|
if ( *(unsigned *)mv->color != *(unsigned *)color ) {
|
|
continue;
|
|
}
|
|
if ( !normalsParsed ) {
|
|
// if we are going to create the normals, just
|
|
// matching texcoords is enough
|
|
break;
|
|
}
|
|
if ( mv->normal * normal > normalEpsilon ) {
|
|
break; // we already have this one
|
|
}
|
|
}
|
|
if ( !mv ) {
|
|
// allocate a new match vert and link to hash chain
|
|
mv = &mvTable[ tri->numVerts ];
|
|
mv->v = v;
|
|
mv->tv = tv;
|
|
mv->normal = normal;
|
|
*(unsigned *)mv->color = *(unsigned *)color;
|
|
mv->next = NULL;
|
|
if ( lastmv ) {
|
|
lastmv->next = mv;
|
|
} else {
|
|
mvHash[v] = mv;
|
|
}
|
|
tri->numVerts++;
|
|
}
|
|
|
|
tri->indexes[tri->numIndexes] = mv - mvTable;
|
|
tri->numIndexes++;
|
|
}
|
|
}
|
|
|
|
// allocate space for the indexes and copy them
|
|
if ( tri->numIndexes > layer->polygon.count * 3 ) {
|
|
common->FatalError( "ConvertLWOToModelSurfaces: index miscount in LWO file %s", name.c_str() );
|
|
}
|
|
if ( tri->numVerts > layer->polygon.count * 3 ) {
|
|
common->FatalError( "ConvertLWOToModelSurfaces: vertex miscount in LWO file %s", name.c_str() );
|
|
}
|
|
|
|
// now allocate and generate the combined vertexes
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
for ( j = 0; j < tri->numVerts; j++ ) {
|
|
mv = &mvTable[j];
|
|
tri->verts[ j ].Clear();
|
|
tri->verts[ j ].xyz = vList[ mv->v ];
|
|
tri->verts[ j ].st = tvList[ mv->tv ];
|
|
tri->verts[ j ].normal = mv->normal;
|
|
*(unsigned *)tri->verts[j].color = *(unsigned *)mv->color;
|
|
}
|
|
|
|
R_StaticFree( mvTable );
|
|
R_StaticFree( mvHash );
|
|
|
|
// see if we need to merge with a previous surface of the same material
|
|
modelSurf = &this->surfaces[mergeTo[ i ]];
|
|
srfTriangles_t *mergeTri = modelSurf->geometry;
|
|
if ( !mergeTri ) {
|
|
modelSurf->geometry = tri;
|
|
} else {
|
|
modelSurf->geometry = R_MergeTriangles( mergeTri, tri );
|
|
R_FreeStaticTriSurf( tri );
|
|
R_FreeStaticTriSurf( mergeTri );
|
|
}
|
|
}
|
|
|
|
R_StaticFree( tvRemap );
|
|
R_StaticFree( vRemap );
|
|
R_StaticFree( tvList );
|
|
R_StaticFree( vList );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::ConvertLWOToASE
|
|
=================
|
|
*/
|
|
struct aseModel_s *idRenderModelStatic::ConvertLWOToASE( const struct st_lwObject *obj, const char *fileName ) {
|
|
int j, k;
|
|
aseModel_t *ase;
|
|
|
|
if ( !obj ) {
|
|
return NULL;
|
|
}
|
|
|
|
// NOTE: using new operator because aseModel_t contains idList class objects
|
|
ase = new aseModel_t;
|
|
ase->timeStamp = obj->timeStamp;
|
|
ase->objects.Resize( obj->nlayers, obj->nlayers );
|
|
|
|
int materialRef = 0;
|
|
|
|
for ( lwSurface *surf = obj->surf; surf; surf = surf->next ) {
|
|
|
|
aseMaterial_t *mat = (aseMaterial_t *)Mem_ClearedAlloc( sizeof( *mat ) );
|
|
strcpy( mat->name, surf->name );
|
|
mat->uTiling = mat->vTiling = 1;
|
|
mat->angle = mat->uOffset = mat->vOffset = 0;
|
|
ase->materials.Append( mat );
|
|
|
|
lwLayer *layer = obj->layer;
|
|
|
|
aseObject_t *object = (aseObject_t *)Mem_ClearedAlloc( sizeof( *object ) );
|
|
object->materialRef = materialRef++;
|
|
|
|
aseMesh_t *mesh = &object->mesh;
|
|
ase->objects.Append( object );
|
|
|
|
mesh->numFaces = layer->polygon.count;
|
|
mesh->numTVFaces = mesh->numFaces;
|
|
mesh->faces = (aseFace_t *)Mem_Alloc( mesh->numFaces * sizeof( mesh->faces[0] ) );
|
|
|
|
mesh->numVertexes = layer->point.count;
|
|
mesh->vertexes = (idVec3 *)Mem_Alloc( mesh->numVertexes * sizeof( mesh->vertexes[0] ) );
|
|
|
|
// vertex positions
|
|
if ( layer->point.count <= 0 ) {
|
|
common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing vertex data", name.c_str() );
|
|
}
|
|
|
|
for ( j = 0; j < layer->point.count; j++ ) {
|
|
mesh->vertexes[j].x = layer->point.pt[j].pos[0];
|
|
mesh->vertexes[j].y = layer->point.pt[j].pos[2];
|
|
mesh->vertexes[j].z = layer->point.pt[j].pos[1];
|
|
}
|
|
|
|
// vertex texture coords
|
|
mesh->numTVertexes = 0;
|
|
|
|
if ( layer->nvmaps ) {
|
|
for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) {
|
|
if ( vm->type == LWID_('T','X','U','V') ) {
|
|
mesh->numTVertexes += vm->nverts;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mesh->numTVertexes ) {
|
|
mesh->tvertexes = (idVec2 *)Mem_Alloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ) );
|
|
int offset = 0;
|
|
for( lwVMap *vm = layer->vmap; vm; vm = vm->next ) {
|
|
if ( vm->type == LWID_('T','X','U','V') ) {
|
|
vm->offset = offset;
|
|
for ( k = 0; k < vm->nverts; k++ ) {
|
|
mesh->tvertexes[k + offset].x = vm->val[k][0];
|
|
mesh->tvertexes[k + offset].y = 1.0f - vm->val[k][1]; // invert the t
|
|
}
|
|
offset += vm->nverts;
|
|
}
|
|
}
|
|
} else {
|
|
common->Warning( "ConvertLWOToASE: model \'%s\' has bad or missing uv data", fileName );
|
|
mesh->numTVertexes = 1;
|
|
mesh->tvertexes = (idVec2 *)Mem_ClearedAlloc( mesh->numTVertexes * sizeof( mesh->tvertexes[0] ) );
|
|
}
|
|
|
|
mesh->normalsParsed = true;
|
|
mesh->colorsParsed = true; // because we are falling back to the surface color
|
|
|
|
// triangles
|
|
int faceIndex = 0;
|
|
for ( j = 0; j < layer->polygon.count; j++ ) {
|
|
lwPolygon *poly = &layer->polygon.pol[j];
|
|
|
|
if ( poly->surf != surf ) {
|
|
continue;
|
|
}
|
|
|
|
if ( poly->nverts != 3 ) {
|
|
common->Warning( "ConvertLWOToASE: model %s has too many verts for a poly! Make sure you triplet it down", fileName );
|
|
continue;
|
|
}
|
|
|
|
mesh->faces[faceIndex].faceNormal.x = poly->norm[0];
|
|
mesh->faces[faceIndex].faceNormal.y = poly->norm[2];
|
|
mesh->faces[faceIndex].faceNormal.z = poly->norm[1];
|
|
|
|
for ( k = 0; k < 3; k++ ) {
|
|
|
|
mesh->faces[faceIndex].vertexNum[k] = poly->v[k].index;
|
|
|
|
mesh->faces[faceIndex].vertexNormals[k].x = poly->v[k].norm[0];
|
|
mesh->faces[faceIndex].vertexNormals[k].y = poly->v[k].norm[2];
|
|
mesh->faces[faceIndex].vertexNormals[k].z = poly->v[k].norm[1];
|
|
|
|
// complete fallbacks
|
|
mesh->faces[faceIndex].tVertexNum[k] = 0;
|
|
|
|
mesh->faces[faceIndex].vertexColors[k][0] = surf->color.rgb[0] * 255;
|
|
mesh->faces[faceIndex].vertexColors[k][1] = surf->color.rgb[1] * 255;
|
|
mesh->faces[faceIndex].vertexColors[k][2] = surf->color.rgb[2] * 255;
|
|
mesh->faces[faceIndex].vertexColors[k][3] = 255;
|
|
|
|
// first set attributes from the vertex
|
|
lwPoint *pt = &layer->point.pt[poly->v[k].index];
|
|
int nvm;
|
|
for ( nvm = 0; nvm < pt->nvmaps; nvm++ ) {
|
|
lwVMapPt *vm = &pt->vm[nvm];
|
|
|
|
if ( vm->vmap->type == LWID_('T','X','U','V') ) {
|
|
mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset;
|
|
}
|
|
if ( vm->vmap->type == LWID_('R','G','B','A') ) {
|
|
for ( int chan = 0; chan < 4; chan++ ) {
|
|
mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan];
|
|
}
|
|
}
|
|
}
|
|
|
|
// then override with polygon attributes
|
|
for ( nvm = 0; nvm < poly->v[k].nvmaps; nvm++ ) {
|
|
lwVMapPt *vm = &poly->v[k].vm[nvm];
|
|
|
|
if ( vm->vmap->type == LWID_('T','X','U','V') ) {
|
|
mesh->faces[faceIndex].tVertexNum[k] = vm->index + vm->vmap->offset;
|
|
}
|
|
if ( vm->vmap->type == LWID_('R','G','B','A') ) {
|
|
for ( int chan = 0; chan < 4; chan++ ) {
|
|
mesh->faces[faceIndex].vertexColors[k][chan] = 255 * vm->vmap->val[vm->index][chan];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
faceIndex++;
|
|
}
|
|
|
|
mesh->numFaces = faceIndex;
|
|
mesh->numTVFaces = faceIndex;
|
|
|
|
aseFace_t *newFaces = ( aseFace_t* )Mem_Alloc( mesh->numFaces * sizeof ( mesh->faces[0] ) );
|
|
memcpy( newFaces, mesh->faces, sizeof( mesh->faces[0] ) * mesh->numFaces );
|
|
Mem_Free( mesh->faces );
|
|
mesh->faces = newFaces;
|
|
}
|
|
|
|
return ase;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::ConvertMAToModelSurfaces
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::ConvertMAToModelSurfaces (const struct maModel_s *ma ) {
|
|
|
|
maObject_t * object;
|
|
maMesh_t * mesh;
|
|
maMaterial_t * material;
|
|
|
|
const idMaterial *im1, *im2;
|
|
srfTriangles_t *tri;
|
|
int objectNum;
|
|
int i, j, k;
|
|
int v, tv;
|
|
int * vRemap;
|
|
int * tvRemap;
|
|
matchVert_t * mvTable; // all of the match verts
|
|
matchVert_t ** mvHash; // points inside mvTable for each xyz index
|
|
matchVert_t * lastmv;
|
|
matchVert_t * mv;
|
|
idVec3 normal;
|
|
float uOffset, vOffset, textureSin, textureCos;
|
|
float uTiling, vTiling;
|
|
int * mergeTo;
|
|
byte * color;
|
|
static byte identityColor[4] = { 255, 255, 255, 255 };
|
|
modelSurface_t surf, *modelSurf;
|
|
|
|
if ( !ma ) {
|
|
return false;
|
|
}
|
|
if ( ma->objects.Num() < 1 ) {
|
|
return false;
|
|
}
|
|
|
|
timeStamp = ma->timeStamp;
|
|
|
|
// the modeling programs can save out multiple surfaces with a common
|
|
// material, but we would like to mege them together where possible
|
|
// meaning that this->NumSurfaces() <= ma->objects.currentElements
|
|
mergeTo = (int *)_alloca( ma->objects.Num() * sizeof( *mergeTo ) );
|
|
|
|
surf.geometry = NULL;
|
|
if ( ma->materials.Num() == 0 ) {
|
|
// if we don't have any materials, dump everything into a single surface
|
|
surf.shader = tr.defaultMaterial;
|
|
surf.id = 0;
|
|
this->AddSurface( surf );
|
|
for ( i = 0 ; i < ma->objects.Num() ; i++ ) {
|
|
mergeTo[i] = 0;
|
|
}
|
|
} else if ( !r_mergeModelSurfaces.GetBool() ) {
|
|
// don't merge any
|
|
for ( i = 0 ; i < ma->objects.Num() ; i++ ) {
|
|
mergeTo[i] = i;
|
|
object = ma->objects[i];
|
|
if(object->materialRef >= 0) {
|
|
material = ma->materials[object->materialRef];
|
|
surf.shader = declManager->FindMaterial( material->name );
|
|
} else {
|
|
surf.shader = tr.defaultMaterial;
|
|
}
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
} else {
|
|
// search for material matches
|
|
for ( i = 0 ; i < ma->objects.Num() ; i++ ) {
|
|
object = ma->objects[i];
|
|
if(object->materialRef >= 0) {
|
|
material = ma->materials[object->materialRef];
|
|
im1 = declManager->FindMaterial( material->name );
|
|
} else {
|
|
im1 = tr.defaultMaterial;
|
|
}
|
|
if ( im1->IsDiscrete() ) {
|
|
// flares, autosprites, etc
|
|
j = this->NumSurfaces();
|
|
} else {
|
|
for ( j = 0 ; j < this->NumSurfaces() ; j++ ) {
|
|
modelSurf = &this->surfaces[j];
|
|
im2 = modelSurf->shader;
|
|
if ( im1 == im2 ) {
|
|
// merge this
|
|
mergeTo[i] = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( j == this->NumSurfaces() ) {
|
|
// didn't merge
|
|
mergeTo[i] = j;
|
|
surf.shader = im1;
|
|
surf.id = this->NumSurfaces();
|
|
this->AddSurface( surf );
|
|
}
|
|
}
|
|
}
|
|
|
|
idVectorSubset<idVec3, 3> vertexSubset;
|
|
idVectorSubset<idVec2, 2> texCoordSubset;
|
|
|
|
// build the surfaces
|
|
for ( objectNum = 0 ; objectNum < ma->objects.Num() ; objectNum++ ) {
|
|
object = ma->objects[objectNum];
|
|
mesh = &object->mesh;
|
|
if(object->materialRef >= 0) {
|
|
material = ma->materials[object->materialRef];
|
|
im1 = declManager->FindMaterial( material->name );
|
|
} else {
|
|
im1 = tr.defaultMaterial;
|
|
}
|
|
|
|
bool normalsParsed = mesh->normalsParsed;
|
|
|
|
// completely ignore any explict normals on surfaces with a renderbump command
|
|
// which will guarantee the best contours and least vertexes.
|
|
const char *rb = im1->GetRenderBump();
|
|
if ( rb && rb[0] ) {
|
|
normalsParsed = false;
|
|
}
|
|
|
|
// It seems like the tools our artists are using often generate
|
|
// verts and texcoords slightly separated that should be merged
|
|
// note that we really should combine the surfaces with common materials
|
|
// before doing this operation, because we can miss a slop combination
|
|
// if they are in different surfaces
|
|
|
|
vRemap = (int *)R_StaticAlloc( mesh->numVertexes * sizeof( vRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < mesh->numVertexes; j++ ) {
|
|
vRemap[j] = j;
|
|
}
|
|
} else {
|
|
float vertexEpsilon = r_slopVertex.GetFloat();
|
|
float expand = 2 * 32 * vertexEpsilon;
|
|
idVec3 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, mesh->vertexes, mesh->numVertexes );
|
|
mins -= idVec3( expand, expand, expand );
|
|
maxs += idVec3( expand, expand, expand );
|
|
vertexSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < mesh->numVertexes; j++ ) {
|
|
vRemap[j] = vertexSubset.FindVector( mesh->vertexes, j, vertexEpsilon );
|
|
}
|
|
}
|
|
|
|
tvRemap = (int *)R_StaticAlloc( mesh->numTVertexes * sizeof( tvRemap[0] ) );
|
|
|
|
if ( fastLoad ) {
|
|
// renderbump doesn't care about vertex count
|
|
for ( j = 0; j < mesh->numTVertexes; j++ ) {
|
|
tvRemap[j] = j;
|
|
}
|
|
} else {
|
|
float texCoordEpsilon = r_slopTexCoord.GetFloat();
|
|
float expand = 2 * 32 * texCoordEpsilon;
|
|
idVec2 mins, maxs;
|
|
|
|
SIMDProcessor->MinMax( mins, maxs, mesh->tvertexes, mesh->numTVertexes );
|
|
mins -= idVec2( expand, expand );
|
|
maxs += idVec2( expand, expand );
|
|
texCoordSubset.Init( mins, maxs, 32, 1024 );
|
|
for ( j = 0; j < mesh->numTVertexes; j++ ) {
|
|
tvRemap[j] = texCoordSubset.FindVector( mesh->tvertexes, j, texCoordEpsilon );
|
|
}
|
|
}
|
|
|
|
// we need to find out how many unique vertex / texcoord / color combinations
|
|
// there are, because MA tracks them separately but we need them unified
|
|
|
|
// the maximum possible number of combined vertexes is the number of indexes
|
|
mvTable = (matchVert_t *)R_ClearedStaticAlloc( mesh->numFaces * 3 * sizeof( mvTable[0] ) );
|
|
|
|
// we will have a hash chain based on the xyz values
|
|
mvHash = (matchVert_t **)R_ClearedStaticAlloc( mesh->numVertexes * sizeof( mvHash[0] ) );
|
|
|
|
// allocate triangle surface
|
|
tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = 0;
|
|
tri->numIndexes = 0;
|
|
R_AllocStaticTriSurfIndexes( tri, mesh->numFaces * 3 );
|
|
tri->generateNormals = !normalsParsed;
|
|
|
|
// init default normal, color and tex coord index
|
|
normal.Zero();
|
|
color = identityColor;
|
|
tv = 0;
|
|
|
|
// find all the unique combinations
|
|
float normalEpsilon = 1.0f - r_slopNormal.GetFloat();
|
|
for ( j = 0; j < mesh->numFaces; j++ ) {
|
|
for ( k = 0; k < 3; k++ ) {
|
|
v = mesh->faces[j].vertexNum[k];
|
|
|
|
if ( v < 0 || v >= mesh->numVertexes ) {
|
|
common->Error( "ConvertMAToModelSurfaces: bad vertex index in MA file %s", name.c_str() );
|
|
}
|
|
|
|
// collapse the position if it was slightly offset
|
|
v = vRemap[v];
|
|
|
|
// we may or may not have texcoords to compare
|
|
if ( mesh->numTVertexes != 0 ) {
|
|
tv = mesh->faces[j].tVertexNum[k];
|
|
if ( tv < 0 || tv >= mesh->numTVertexes ) {
|
|
common->Error( "ConvertMAToModelSurfaces: bad tex coord index in MA file %s", name.c_str() );
|
|
}
|
|
// collapse the tex coord if it was slightly offset
|
|
tv = tvRemap[tv];
|
|
}
|
|
|
|
// we may or may not have normals to compare
|
|
if ( normalsParsed ) {
|
|
normal = mesh->faces[j].vertexNormals[k];
|
|
}
|
|
|
|
//BSM: Todo: Fix the vertex colors
|
|
// we may or may not have colors to compare
|
|
if ( mesh->faces[j].vertexColors[k] != -1 && mesh->faces[j].vertexColors[k] != -999 ) {
|
|
|
|
color = &mesh->colors[mesh->faces[j].vertexColors[k]*4];
|
|
}
|
|
|
|
// find a matching vert
|
|
for ( lastmv = NULL, mv = mvHash[v]; mv != NULL; lastmv = mv, mv = mv->next ) {
|
|
if ( mv->tv != tv ) {
|
|
continue;
|
|
}
|
|
if ( *(unsigned *)mv->color != *(unsigned *)color ) {
|
|
continue;
|
|
}
|
|
if ( !normalsParsed ) {
|
|
// if we are going to create the normals, just
|
|
// matching texcoords is enough
|
|
break;
|
|
}
|
|
if ( mv->normal * normal > normalEpsilon ) {
|
|
break; // we already have this one
|
|
}
|
|
}
|
|
if ( !mv ) {
|
|
// allocate a new match vert and link to hash chain
|
|
mv = &mvTable[ tri->numVerts ];
|
|
mv->v = v;
|
|
mv->tv = tv;
|
|
mv->normal = normal;
|
|
*(unsigned *)mv->color = *(unsigned *)color;
|
|
mv->next = NULL;
|
|
if ( lastmv ) {
|
|
lastmv->next = mv;
|
|
} else {
|
|
mvHash[v] = mv;
|
|
}
|
|
tri->numVerts++;
|
|
}
|
|
|
|
tri->indexes[tri->numIndexes] = mv - mvTable;
|
|
tri->numIndexes++;
|
|
}
|
|
}
|
|
|
|
// allocate space for the indexes and copy them
|
|
if ( tri->numIndexes > mesh->numFaces * 3 ) {
|
|
common->FatalError( "ConvertMAToModelSurfaces: index miscount in MA file %s", name.c_str() );
|
|
}
|
|
if ( tri->numVerts > mesh->numFaces * 3 ) {
|
|
common->FatalError( "ConvertMAToModelSurfaces: vertex miscount in MA file %s", name.c_str() );
|
|
}
|
|
|
|
// an MA allows the texture coordinates to be scaled, translated, and rotated
|
|
//BSM: Todo: Does Maya support this and if so how
|
|
//if ( ase->materials.Num() == 0 ) {
|
|
uOffset = vOffset = 0.0f;
|
|
uTiling = vTiling = 1.0f;
|
|
textureSin = 0.0f;
|
|
textureCos = 1.0f;
|
|
//} else {
|
|
// material = ase->materials[object->materialRef];
|
|
// uOffset = -material->uOffset;
|
|
// vOffset = material->vOffset;
|
|
// uTiling = material->uTiling;
|
|
// vTiling = material->vTiling;
|
|
// textureSin = idMath::Sin( material->angle );
|
|
// textureCos = idMath::Cos( material->angle );
|
|
//}
|
|
|
|
// now allocate and generate the combined vertexes
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
for ( j = 0; j < tri->numVerts; j++ ) {
|
|
mv = &mvTable[j];
|
|
tri->verts[ j ].Clear();
|
|
tri->verts[ j ].xyz = mesh->vertexes[ mv->v ];
|
|
tri->verts[ j ].normal = mv->normal;
|
|
*(unsigned *)tri->verts[j].color = *(unsigned *)mv->color;
|
|
if ( mesh->numTVertexes != 0 ) {
|
|
const idVec2 &tv = mesh->tvertexes[ mv->tv ];
|
|
float u = tv.x * uTiling + uOffset;
|
|
float v = tv.y * vTiling + vOffset;
|
|
tri->verts[ j ].st[0] = u * textureCos + v * textureSin;
|
|
tri->verts[ j ].st[1] = u * -textureSin + v * textureCos;
|
|
}
|
|
}
|
|
|
|
R_StaticFree( mvTable );
|
|
R_StaticFree( mvHash );
|
|
R_StaticFree( tvRemap );
|
|
R_StaticFree( vRemap );
|
|
|
|
// see if we need to merge with a previous surface of the same material
|
|
modelSurf = &this->surfaces[mergeTo[ objectNum ]];
|
|
srfTriangles_t *mergeTri = modelSurf->geometry;
|
|
if ( !mergeTri ) {
|
|
modelSurf->geometry = tri;
|
|
} else {
|
|
modelSurf->geometry = R_MergeTriangles( mergeTri, tri );
|
|
R_FreeStaticTriSurf( tri );
|
|
R_FreeStaticTriSurf( mergeTri );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::LoadASE
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::LoadASE( const char *fileName ) {
|
|
aseModel_t *ase;
|
|
|
|
ase = ASE_Load( fileName );
|
|
if ( ase == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
ConvertASEToModelSurfaces( ase );
|
|
|
|
ASE_Free( ase );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::LoadLWO
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::LoadLWO( const char *fileName ) {
|
|
unsigned int failID;
|
|
int failPos;
|
|
lwObject *lwo;
|
|
|
|
lwo = lwGetObject( fileName, &failID, &failPos );
|
|
if ( lwo == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
ConvertLWOToModelSurfaces( lwo );
|
|
|
|
lwFreeObject( lwo );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::LoadMA
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::LoadMA( const char *fileName ) {
|
|
maModel_t *ma;
|
|
|
|
ma = MA_Load( fileName );
|
|
if ( ma == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
ConvertMAToModelSurfaces( ma );
|
|
|
|
MA_Free( ma );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::LoadFLT
|
|
|
|
USGS height map data for megaTexture experiments
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::LoadFLT( const char *fileName ) {
|
|
float *data;
|
|
int len;
|
|
|
|
len = fileSystem->ReadFile( fileName, (void **)&data );
|
|
if ( len <= 0 ) {
|
|
return false;
|
|
}
|
|
int size = sqrt( len / 4.0f );
|
|
|
|
// bound the altitudes
|
|
float min = 9999999;
|
|
float max = -9999999;
|
|
for ( int i = 0 ; i < len/4 ; i++ ) {
|
|
data[i] = BigFloat( data[i] );
|
|
if ( data[i] == -9999 ) {
|
|
data[i] = 0; // unscanned areas
|
|
}
|
|
|
|
if ( data[i] < min ) {
|
|
min = data[i];
|
|
}
|
|
if ( data[i] > max ) {
|
|
max = data[i];
|
|
}
|
|
}
|
|
#if 1
|
|
// write out a gray scale height map
|
|
byte *image = (byte *)R_StaticAlloc( len );
|
|
byte *image_p = image;
|
|
for ( int i = 0 ; i < len/4 ; i++ ) {
|
|
float v = ( data[i] - min ) / ( max - min );
|
|
image_p[0] =
|
|
image_p[1] =
|
|
image_p[2] = v * 255;
|
|
image_p[3] = 255;
|
|
image_p += 4;
|
|
}
|
|
idStr tgaName = fileName;
|
|
tgaName.StripFileExtension();
|
|
tgaName += ".tga";
|
|
R_WriteTGA( tgaName.c_str(), image, size, size, false );
|
|
R_StaticFree( image );
|
|
//return false;
|
|
#endif
|
|
|
|
// find the island above sea level
|
|
int minX, maxX, minY, maxY;
|
|
{
|
|
int i;
|
|
for ( minX = 0 ; minX < size ; minX++ ) {
|
|
for ( i = 0 ; i < size ; i++ ) {
|
|
if ( data[i*size + minX] > 1.0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i != size ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( maxX = size-1 ; maxX > 0 ; maxX-- ) {
|
|
for ( i = 0 ; i < size ; i++ ) {
|
|
if ( data[i*size + maxX] > 1.0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i != size ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( minY = 0 ; minY < size ; minY++ ) {
|
|
for ( i = 0 ; i < size ; i++ ) {
|
|
if ( data[minY*size + i] > 1.0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i != size ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for ( maxY = size-1 ; maxY < size ; maxY-- ) {
|
|
for ( i = 0 ; i < size ; i++ ) {
|
|
if ( data[maxY*size + i] > 1.0 ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i != size ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int width = maxX - minX + 1;
|
|
int height = maxY - minY + 1;
|
|
|
|
//width /= 2;
|
|
// allocate triangle surface
|
|
srfTriangles_t *tri = R_AllocStaticTriSurf();
|
|
tri->numVerts = width * height;
|
|
tri->numIndexes = (width-1) * (height-1) * 6;
|
|
|
|
fastLoad = true; // don't do all the sil processing
|
|
|
|
R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
|
|
for ( int i = 0 ; i < height ; i++ ) {
|
|
for ( int j = 0; j < width ; j++ ) {
|
|
int v = i * width + j;
|
|
tri->verts[ v ].Clear();
|
|
tri->verts[ v ].xyz[0] = j * 10; // each sample is 10 meters
|
|
tri->verts[ v ].xyz[1] = -i * 10;
|
|
tri->verts[ v ].xyz[2] = data[(minY+i)*size+minX+j]; // height is in meters
|
|
tri->verts[ v ].st[0] = (float) j / (width-1);
|
|
tri->verts[ v ].st[1] = 1.0 - ( (float) i / (height-1) );
|
|
}
|
|
}
|
|
|
|
for ( int i = 0 ; i < height-1 ; i++ ) {
|
|
for ( int j = 0; j < width-1 ; j++ ) {
|
|
int v = ( i * (width-1) + j ) * 6;
|
|
#if 0
|
|
tri->indexes[ v + 0 ] = i * width + j;
|
|
tri->indexes[ v + 1 ] = (i+1) * width + j;
|
|
tri->indexes[ v + 2 ] = (i+1) * width + j + 1;
|
|
tri->indexes[ v + 3 ] = i * width + j;
|
|
tri->indexes[ v + 4 ] = (i+1) * width + j + 1;
|
|
tri->indexes[ v + 5 ] = i * width + j + 1;
|
|
#else
|
|
tri->indexes[ v + 0 ] = i * width + j;
|
|
tri->indexes[ v + 1 ] = i * width + j + 1;
|
|
tri->indexes[ v + 2 ] = (i+1) * width + j + 1;
|
|
tri->indexes[ v + 3 ] = i * width + j;
|
|
tri->indexes[ v + 4 ] = (i+1) * width + j + 1;
|
|
tri->indexes[ v + 5 ] = (i+1) * width + j;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
fileSystem->FreeFile( data );
|
|
|
|
modelSurface_t surface;
|
|
|
|
surface.geometry = tri;
|
|
surface.id = 0;
|
|
surface.shader = tr.defaultMaterial; // declManager->FindMaterial( "shaderDemos/megaTexture" );
|
|
|
|
this->AddSurface( surface );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::PurgeModel
|
|
================
|
|
*/
|
|
void idRenderModelStatic::PurgeModel() {
|
|
int i;
|
|
modelSurface_t *surf;
|
|
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
surf = &surfaces[i];
|
|
|
|
if ( surf->geometry ) {
|
|
R_FreeStaticTriSurf( surf->geometry );
|
|
}
|
|
}
|
|
surfaces.Clear();
|
|
|
|
purged = true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idRenderModelStatic::FreeVertexCache
|
|
|
|
We are about to restart the vertex cache, so dump everything
|
|
==============
|
|
*/
|
|
void idRenderModelStatic::FreeVertexCache( void ) {
|
|
for ( int j = 0 ; j < surfaces.Num() ; j++ ) {
|
|
srfTriangles_t *tri = surfaces[j].geometry;
|
|
if ( !tri ) {
|
|
continue;
|
|
}
|
|
if ( tri->ambientCache ) {
|
|
vertexCache.Free( tri->ambientCache );
|
|
tri->ambientCache = NULL;
|
|
}
|
|
// static shadows may be present
|
|
if ( tri->shadowCache ) {
|
|
vertexCache.Free( tri->shadowCache );
|
|
tri->shadowCache = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::ReadFromDemoFile
|
|
================
|
|
*/
|
|
void idRenderModelStatic::ReadFromDemoFile( class idDemoFile *f ) {
|
|
PurgeModel();
|
|
|
|
InitEmpty( f->ReadHashString() );
|
|
|
|
int i, j, numSurfaces;
|
|
f->ReadInt( numSurfaces );
|
|
|
|
for ( i = 0 ; i < numSurfaces ; i++ ) {
|
|
modelSurface_t surf;
|
|
|
|
surf.shader = declManager->FindMaterial( f->ReadHashString() );
|
|
|
|
srfTriangles_t *tri = R_AllocStaticTriSurf();
|
|
|
|
f->ReadInt( tri->numIndexes );
|
|
R_AllocStaticTriSurfIndexes( tri, tri->numIndexes );
|
|
for ( j = 0; j < tri->numIndexes; ++j )
|
|
f->ReadInt( (int&)tri->indexes[j] );
|
|
|
|
f->ReadInt( tri->numVerts );
|
|
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
|
|
for ( j = 0; j < tri->numVerts; ++j ) {
|
|
f->ReadVec3( tri->verts[j].xyz );
|
|
f->ReadVec2( tri->verts[j].st );
|
|
f->ReadVec3( tri->verts[j].normal );
|
|
f->ReadVec3( tri->verts[j].tangents[0] );
|
|
f->ReadVec3( tri->verts[j].tangents[1] );
|
|
f->ReadUnsignedChar( tri->verts[j].color[0] );
|
|
f->ReadUnsignedChar( tri->verts[j].color[1] );
|
|
f->ReadUnsignedChar( tri->verts[j].color[2] );
|
|
f->ReadUnsignedChar( tri->verts[j].color[3] );
|
|
}
|
|
|
|
surf.geometry = tri;
|
|
|
|
this->AddSurface( surf );
|
|
}
|
|
this->FinishSurfaces();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::WriteToDemoFile
|
|
================
|
|
*/
|
|
void idRenderModelStatic::WriteToDemoFile( class idDemoFile *f ) {
|
|
int data[1];
|
|
|
|
// note that it has been updated
|
|
lastArchivedFrame = tr.frameCount;
|
|
|
|
data[0] = DC_DEFINE_MODEL;
|
|
f->WriteInt( data[0] );
|
|
f->WriteHashString( this->Name() );
|
|
|
|
int i, j, iData = surfaces.Num();
|
|
f->WriteInt( iData );
|
|
|
|
for ( i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
f->WriteHashString( surf->shader->GetName() );
|
|
|
|
srfTriangles_t *tri = surf->geometry;
|
|
f->WriteInt( tri->numIndexes );
|
|
for ( j = 0; j < tri->numIndexes; ++j )
|
|
f->WriteInt( (int&)tri->indexes[j] );
|
|
f->WriteInt( tri->numVerts );
|
|
for ( j = 0; j < tri->numVerts; ++j ) {
|
|
f->WriteVec3( tri->verts[j].xyz );
|
|
f->WriteVec2( tri->verts[j].st );
|
|
f->WriteVec3( tri->verts[j].normal );
|
|
f->WriteVec3( tri->verts[j].tangents[0] );
|
|
f->WriteVec3( tri->verts[j].tangents[1] );
|
|
f->WriteUnsignedChar( tri->verts[j].color[0] );
|
|
f->WriteUnsignedChar( tri->verts[j].color[1] );
|
|
f->WriteUnsignedChar( tri->verts[j].color[2] );
|
|
f->WriteUnsignedChar( tri->verts[j].color[3] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsLoaded
|
|
================
|
|
*/
|
|
bool idRenderModelStatic::IsLoaded( void ) const {
|
|
return !purged;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::SetLevelLoadReferenced
|
|
================
|
|
*/
|
|
void idRenderModelStatic::SetLevelLoadReferenced( bool referenced ) {
|
|
levelLoadReferenced = referenced;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idRenderModelStatic::IsLevelLoadReferenced
|
|
================
|
|
*/
|
|
bool idRenderModelStatic::IsLevelLoadReferenced( void ) {
|
|
return levelLoadReferenced;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::TouchData
|
|
=================
|
|
*/
|
|
void idRenderModelStatic::TouchData( void ) {
|
|
for ( int i = 0 ; i < surfaces.Num() ; i++ ) {
|
|
const modelSurface_t *surf = &surfaces[i];
|
|
|
|
// re-find the material to make sure it gets added to the
|
|
// level keep list
|
|
declManager->FindMaterial( surf->shader->GetName() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::DeleteSurfaceWithId
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::DeleteSurfaceWithId( int id ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < surfaces.Num(); i++ ) {
|
|
if ( surfaces[i].id == id ) {
|
|
R_FreeStaticTriSurf( surfaces[i].geometry );
|
|
surfaces.RemoveIndex( i );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::DeleteSurfacesWithNegativeId
|
|
=================
|
|
*/
|
|
void idRenderModelStatic::DeleteSurfacesWithNegativeId( void ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < surfaces.Num(); i++ ) {
|
|
if ( surfaces[i].id < 0 ) {
|
|
R_FreeStaticTriSurf( surfaces[i].geometry );
|
|
surfaces.RemoveIndex( i );
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idRenderModelStatic::FindSurfaceWithId
|
|
=================
|
|
*/
|
|
bool idRenderModelStatic::FindSurfaceWithId( int id, int &surfaceNum ) {
|
|
int i;
|
|
|
|
for ( i = 0; i < surfaces.Num(); i++ ) {
|
|
if ( surfaces[i].id == id ) {
|
|
surfaceNum = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|