I never thought lightmapping could be so exciting! I can give you the overview on how the light mapper works, but I have also included the main C++ source code to the core routine which prepares and light maps the scene, which should give the C++ users out there a glimpse into how I go about choosing what to lightmap, and to scare the pants off the non C++ users who can thank their lucky stars someone else is dealing with the code.
Essentially, as seen in the attached image, which was taken from my Asset Importer tool, that you have three floating meshes, each one holds a version of the crate at different polygon counts. You will also notice the smoothing group on the lower polygon variants could do with some improvement if you use the AssImp tool and load the SupplyDropV1.x into it. You then have what I call limbs (coders can call them frames), with a parent limb acting as the root and then three child limbs from this parent, each one referencing one of the meshes. It is the limb name (frame name) which holds the all important text to identify whether the limb is a regular model or whether it's a special LOD model. A special LOD model names each limb to identify which referenced mesh is high polygon, medium polygon or low polygon. A great example of a working LOD model would be the attached AssImp-CombatBuilding.png image which shows the three limbs named as LOD_0, LOD_1 and LOD_2. When the light mapper scans all the models in the scene, it will find the limb name LOD_0 and prefer it over the other two as it will always attempt to find the highest polygon mesh to lightmap (see code below).
I suspect the SupplyDropV1.X model has a few issues, such as not having a limb named LOD_0 which might cause it to use ALL THREE meshes in the lightmap process and produce too many shadows and artefacts. I also suspect the normals are wrong in the mesh too given the improvements demonstrated in the thread above. Alas, I simply don't have the time to thoroughly test every model that comes through, and if they look good, with the right material and collision, they normally go on to the public. If I did this for the thousands of models we've got through so far, I doubt I would have had time to write even a single line of code. I am however happy to get reports on suspicious models and refer those to the original artist so they can make corrections, and it looks like the SupplyDrop model will be getting this treatment. if you find any more, please do pass them onto me and I can get the assets fixed and included in the next build. I am also very happy to receive fixed versions of models and FPE files, as that makes even more sense to me as it can go out all the sooner.
Here is the current light mapper baking code for future reference:
void lm_process ( void )
{
// Start the lightmap Timer ( )
t.starttimer=Timer();
// remove any LM objects which no longer match entityelement data
lm_removeold ( );
// Prepare terrain objects for scene
timestampactivity(0,"LIGHTMAPPER: Create Glass Terrain Objects");
lm_createglassterrainobjects ( );
// User prompt
t.tdisableLMprogressreading=1;
for ( t.n = 0 ; t.n<= 1; t.n++ )
{
t.tonscreenprompt_s="Preparing to Lightmap";
if ( t.hardwareinfoglobals.noterrain == 0 ) terrain_update ( );
lm_onscreenprompt() ; Sync ( );
}
t.tdisableLMprogressreading=0;
// use terrain objects to chart progress through overlord loop
t.overlordmaxiterations=0;
t.overlordradius=1024;
for ( t.overlordz = 0 ; t.overlordz <= 51200 ; t.overlordz+= t.overlordradius )
{
for ( t.overlordx = 0 ; t.overlordx <= 51200 ; t.overlordx+= t.overlordradius )
{
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
if ( ObjectPositionX(t.tlmobj2) >= t.overlordx && ObjectPositionX(t.tlmobj2)<t.overlordx+t.overlordradius )
{
if ( ObjectPositionZ(t.tlmobj2) >= t.overlordz && ObjectPositionZ(t.tlmobj2)<t.overlordz+t.overlordradius )
{
++t.overlordmaxiterations;
}
}
}
}
}
}
t.overlordindex=0;
// Overruling sliced up terrain and LM objects, we lightmap by quadrant
// to ensure system memory is conserved into managable grids of work
t.lightmappedobjnumbersofar=g.lightmappedterrainoffsetfinish+1;
t.storelasttexturefilenumber=0;
Dim ( t.overlordprocessed,g.glmsceneentitymax );
for ( t.e = 1 ; t.e<= g.glmsceneentitymax; t.e++ )
{
t.overlordprocessed[t.e]=0;
t.lmsceneobj[t.e].includerotandscale=0;
t.lmsceneobj[t.e].reverseframes=0;
}
// Start BIG OVERLORD LOOP
Dim ( t.objsaveexclude,g.lightmappedobjectoffsetlast-g.lightmappedobjectoffset );
for ( t.overlordz = 0 ; t.overlordz<= 51200 ; t.overlordz+= t.overlordradius )
{
for ( t.overlordx = 0 ; t.overlordx<= 51200 ; t.overlordx+= t.overlordradius )
{
// Work out terrain iterations (as can eat huge amounts of system memory)
t.tterrainobjectscount=0;
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
HideObject ( t.tlmobj2 );
if ( ObjectPositionX(t.tlmobj2) >= t.overlordx && ObjectPositionX(t.tlmobj2)<t.overlordx+t.overlordradius )
{
if ( ObjectPositionZ(t.tlmobj2) >= t.overlordz && ObjectPositionZ(t.tlmobj2)<t.overlordz+t.overlordradius )
{
ShowObject ( t.tlmobj2 );
++t.tterrainobjectscount;
}
}
}
}
if ( t.tterrainobjectscount>0 ) ++t.overlordindex;
t.tcurrententityindex=0;
// mark entities we can process in this grid section
t.tcountoverlordmarks=0;
for ( t.e = 1 ; t.e<= g.glmsceneentitymax; t.e++ )
{
t.tentid=t.entityelement[t.e].bankindex;
t.tvalidshadereffect = 0 ; if ( strcmp ( Lower(t.entityprofile[t.tentid].effect_s.Get()) , "effectbank\\reloaded\\entity_basic.fx" ) == 0 ) t.tvalidshadereffect = 1;
t.tokay=0;
if ( t.entityelement[t.e].obj>0 && t.entityelement[t.e].staticflag == 1 && t.entityprofile[t.tentid].ismarker == 0 && t.tvalidshadereffect == 1 ) t.tokay = 1;
if ( t.tokay == 1 )
{
if ( t.overlordprocessed[t.e] == 0 )
{
t.tdx_f=t.entityelement[t.e].x-(t.overlordx+(t.overlordradius/2));
t.tdz_f=t.entityelement[t.e].z-(t.overlordz+(t.overlordradius/2));
t.tdd_f=Sqrt(abs(t.tdx_f*t.tdx_f)+abs(t.tdz_f*t.tdz_f));
if ( t.tdd_f<((t.overlordradius+0.0)*1.1) )
{
t.overlordprocessed[t.e]=1;
++t.tcountoverlordmarks;
}
}
}
}
// Convert all objects to be lightmapped to standard static FVF
if ( t.tcountoverlordmarks>0 || t.tterrainobjectscount>0 )
{
t.strwork = ""; t.strwork = t.strwork + "LIGHTMAPPER: Overlord Grid Work. "+Str(t.tcountoverlordmarks)+" Objects and "+Str(t.tterrainobjectscount)+" Terrain at "+Str(t.overlordx/t.overlordradius)+" t.x "+Str(t.overlordz/t.overlordradius);
timestampactivity(0, t.strwork.Get() );
t.tlmobj=t.lightmappedobjnumbersofar;
t.tlmobjstartedfrom=t.tlmobj;
for ( t.e = 1 ; t.e<= g.glmsceneentitymax; t.e++ )
{
t.tentid=t.entityelement[t.e].bankindex;
t.tobj=t.entityelement[t.e].obj;
if ( t.tobj>0 && t.overlordprocessed[t.e] == 1 )
{
t.ttsourceobj=g.entitybankoffset+t.tentid;
t.tvalidshadereffect = 0 ; if ( strcmp ( Lower(t.entityprofile[t.tentid].effect_s.Get()) , "effectbank\\reloaded\\entity_basic.fx" ) == 0 ) t.tvalidshadereffect = 1;
if ( t.entityelement[t.e].staticflag == 1 && t.entityprofile[t.tentid].ismarker == 0 && t.tvalidshadereffect == 1 )
{
// hide instance
HideObject ( t.tobj );
//Dave Performance
///SetIgnoreObject ( t.obj , true ); = this is lightmap generation, NO EFFECT ON PERFORMANCE!!
if ( t.lmsceneobj[t.e].lmvalid == 0 )
{
// create one or more LM objects
t.lmsceneobj[t.e].startobj=t.tlmobj;
t.lmsceneobj[t.e].reverseframes=t.entityprofile[t.tentid].reverseframes;
// create new object ready for lightmapping
t.tmultimatcount=GetMultiMaterialCount(t.ttsourceobj);
if ( t.tmultimatcount == 0 )
{
t.mastertlmobj=t.tlmobj;
}
else
{
t.mastertlmobj=t.tlmobj+t.tmultimatcount+1;
}
if ( ObjectExist(t.mastertlmobj) == 1 ) DeleteObject ( t.mastertlmobj );
if ( t.tlmobj>g.lightmappedobjectoffsetfinish ) g.lightmappedobjectoffsetfinish = t.tlmobj;
if ( t.tlmobj >= t.lightmappedobjnumbersofar ) t.lightmappedobjnumbersofar = t.tlmobj+1;
CloneObject ( t.mastertlmobj,t.ttsourceobj,0 );
// strip out LOD1 and LOD2 meshes
t.tmultimatflag=GetMultiMaterialCount(t.mastertlmobj);
PerformCheckListForLimbs ( t.mastertlmobj );
t.tbestlod=-1;
LPSTR pSimplygonBlenderStyle = NULL;
for ( t.c = ChecklistQuantity() ; t.c >= 1 ; t.c+= -1 )
{
t.tname_s=Lower(ChecklistString(t.c));
if ( t.tname_s == "lod_0" && (t.tbestlod == -1 || t.tbestlod>0) ) t.tbestlod = 0;
if ( t.tname_s == "lod_1" && (t.tbestlod == -1 || t.tbestlod>1) ) t.tbestlod = 1;
if ( t.tname_s == "lod_2" && (t.tbestlod == -1) ) t.tbestlod = 2;
// additional LODs to choose from (Simplygon->Blender)
if ( strlen(t.tname_s.Get()) > 5 )
{
LPSTR pLODPart = t.tname_s.Get() + strlen(t.tname_s.Get()) - 5;
if ( stricmp ( pLODPart, "_LOD1" )==NULL && (t.tbestlod == -1 || t.tbestlod>1) ) { t.tbestlod = 1; pSimplygonBlenderStyle = Lower(ChecklistString(t.c)); }
if ( stricmp ( pLODPart, "_LOD2" )==NULL && (t.tbestlod == -1) ) { t.tbestlod = 2; pSimplygonBlenderStyle = Lower(ChecklistString(t.c)); }
}
}
if ( pSimplygonBlenderStyle!=NULL )
{
// uses _LOD1 and _LOD2 markers but _LOD0 not specified so find first mesh that is not LOD1/2
// to be used as the BEST LOD mesh (the highest polygon one)
char pTestWith[256];
strcpy ( pTestWith, pSimplygonBlenderStyle );
pTestWith[strlen(pSimplygonBlenderStyle)-5] = 0;
for ( t.c = ChecklistQuantity() ; t.c >= 1 ; t.c+= -1 )
{
t.tname_s=Lower(ChecklistString(t.c));
if ( stricmp ( t.tname_s.Get(), pTestWith )==NULL )
{
t.tbestlod = 0;
}
}
}
for ( t.c = ChecklistQuantity() ; t.c >= 1 ; t.c+= -1 )
{
t.tname_s=Lower(ChecklistString(t.c));
t.tokay=0;
if ( t.tbestlod >= 0 )
{
if ( t.tbestlod == 0 && (t.tname_s == "lod_1" || t.tname_s == "lod_2") ) t.tokay = 1;
if ( t.tbestlod == 1 && (t.tname_s == "lod_2") ) t.tokay = 1;
// additional LODs to choose from (Simplygon->Blender)
if ( strlen(t.tname_s.Get()) > 5 )
{
LPSTR pLODPart = t.tname_s.Get() + strlen(t.tname_s.Get()) - 5;
if ( t.tbestlod == 0 && (stricmp ( pLODPart, "_LOD1" )==NULL || stricmp ( pLODPart, "_LOD2" )==NULL) ) t.tokay = 1;
if ( t.tbestlod == 1 && (stricmp ( pLODPart, "_LOD2" )==NULL) ) t.tokay = 1;
}
}
if ( t.tokay == 1 )
{
RemoveLimb ( t.mastertlmobj,t.c-1 );
}
else
{
// seems X file load to can leave garbage (artist leftover) in matCombined
// so restore this to identity matrices (fixes wrong scaling of trees in foliage pack)
if ( t.entityprofile[t.tentid].resetlimbmatrix == 1 )
{
OffsetLimb ( t.mastertlmobj,t.c-1,0,0,0,0 );
}
}
}
if ( t.entityprofile[t.tentid].fixnewy != 0 )
{
RotateObject ( t.mastertlmobj,0,t.entityprofile[t.tentid].fixnewy,0 );
FixObjectPivot ( t.mastertlmobj );
}
SetObjectEffect ( t.mastertlmobj,0 );
CloneMeshToNewFormat ( t.mastertlmobj,530,1 );
// is this entity a multimaterial model, create more LM objects (and reduce parent too at end)
if ( t.tmultimatflag == 0 )
{
// 110115 - only for non multimaterial models
t.storetlmobj=t.tlmobj ; t.tlmobj=t.mastertlmobj;
lm_preplmobj () ; t.tlmobj=t.storetlmobj;
// leave to move to next one
SetObjectWireframe ( t.tlmobj,1 );
++t.tlmobj;
}
else
{
// now need to create small LM objects from master
PerformCheckListForLimbs ( t.mastertlmobj );
t.tmultimatcount=ChecklistQuantity();
for ( t.mpass = 1 ; t.mpass<= t.tmultimatcount-1; t.mpass++ )
{
if ( GetMeshExist(g.meshlightmapwork) == 1 ) DeleteMesh ( g.meshlightmapwork );
MakeMeshFromLimb ( g.meshlightmapwork,t.mastertlmobj,t.mpass );
if ( GetMeshExist(g.meshlightmapwork) == 1 )
{
if ( ObjectExist(t.tlmobj) == 1 ) DeleteObject ( t.tlmobj );
if ( t.tlmobj>g.lightmappedobjectoffsetfinish ) g.lightmappedobjectoffsetfinish = t.tlmobj;
if ( t.tlmobj >= t.lightmappedobjnumbersofar ) t.lightmappedobjnumbersofar = t.tlmobj+1;
MakeObject ( t.tlmobj,g.meshlightmapwork,-1 );
SetObjectEffect ( t.tlmobj,0 );
lm_preplmobj ( );
++t.tlmobj;
}
}
if ( GetMeshExist(g.meshlightmapwork) == 1 ) DeleteMesh ( g.meshlightmapwork );
MakeMeshFromLimb ( g.meshlightmapwork,t.mastertlmobj,0 );
if ( GetMeshExist(g.meshlightmapwork) == 1 )
{
if ( ObjectExist(t.tlmobj) == 1 ) DeleteObject ( t.tlmobj );
if ( t.tlmobj>g.lightmappedobjectoffsetfinish ) g.lightmappedobjectoffsetfinish = t.tlmobj;
if ( t.tlmobj >= t.lightmappedobjnumbersofar ) t.lightmappedobjnumbersofar = t.tlmobj+1;
MakeObject ( t.tlmobj,g.meshlightmapwork,-1 );
SetObjectEffect ( t.tlmobj,0 );
lm_preplmobj ( );
SetObjectWireframe ( t.tlmobj,1 );
++t.tlmobj;
}
DeleteObject ( t.mastertlmobj );
}
t.lmsceneobj[t.e].finishobj=t.tlmobj-1;
// trigger this one to be lightmapped (next step)
t.lmsceneobj[t.e].lmvalid=-2;
}
}
}
}
// Now we have formatted lightmap objects ready, let's glue the ones
// with same textures together, and in close proximity, thus reducing
// the individual draw calls when we come to render them
t.tconsolidatelocallightmapobjectspolylimit=3000;
timestampactivity(0,cstr(cstr("LIGHTMAPPER: Consolidate static objects (")+cstr(t.tconsolidatelocallightmapobjectspolylimit)+cstr(" poly limit)")).Get());
t.tcountconsolidations=0;
t.tconsolidatelocallightmapobjects=1;
if ( t.tconsolidatelocallightmapobjects == 1 )
{
t.tlobjmax=t.lightmappedobjnumbersofar-t.tlmobjstartedfrom;
if ( t.tlobjmax<1 ) t.tlobjmax = 1;
t.tlobjmax=t.tlobjmax*2;
Dim ( t.gtlobjused,t.tlobjmax );
for ( t.tlmobjindex = 1 ; t.tlmobjindex <= t.tlobjmax ; t.tlmobjindex++ ) t.gtlobjused[t.tlmobjindex]=0 ;
for ( t.e = 1 ; t.e<= g.glmsceneentitymax; t.e++ )
{
if ( t.lmsceneobj[t.e].lmvalid == -2 && t.lmsceneobj[t.e].startobj>0 && t.overlordprocessed[t.e] == 1 )
{
// untouched LM object, use this as the base to add similar local objects to get particulars of this LM objects
for ( t.tlmobj = t.lmsceneobj[t.e].startobj; t.tlmobj <= t.lmsceneobj[t.e].finishobj; t.tlmobj++ )
{
if ( ObjectExist(t.tlmobj) == 1 )
{
if ( 1 )
{
++t.tcountconsolidations;
t.tmasterx_f=ObjectPositionX(t.tlmobj);
t.tmastery_f=ObjectPositionY(t.tlmobj);
t.tmasterz_f=ObjectPositionZ(t.tlmobj);
// detect if object contains GLASS, if so, do NOT consolidate
t.tskipconsolidation=0;
PerformCheckListForLimbs ( t.tlmobj );
for ( t.tlmi = 0 ; t.tlmi<= ChecklistQuantity()-1; t.tlmi++ )
{
t.tname_s=Lower(ChecklistString(1+t.tlmi));
if ( t.tname_s == "glass" )
{
t.tskipconsolidation=1;
}
}
// 240315 - also skip consolidation if cumilative polygons will overload shader mesh maker (max 65535 vertices)
if ( GetObjectPolygonCount(t.tlmobj)>21840 )
{
t.tskipconsolidation=2;
}
// remove any rotation from the master object and move to root limb
if ( GetObjectPolygonCount(t.tlmobj) == 0 || t.tskipconsolidation != 0 )
{
// parent object has NO polygons, so skip this entirely!
}
else
{
// if have polys, MUST convert each object
t.tcreatedmasterrecepticle=0;
// mark as used (also master object to glue stuff to)
t.tlmobjindex=t.tlmobj-t.tlmobjstartedfrom;
t.gtlobjused[t.tlmobjindex]=-1;
// object exists, glue stuff to it
if ( GetMeshExist(g.meshlightmapwork) == 1 ) DeleteMesh ( g.meshlightmapwork );
MakeMeshFromObject ( g.meshlightmapwork,t.tlmobj );
if ( GetMeshExist(g.meshlightmapwork) == 1 )
{
// go through all other 'untouched' objects
t.tlastpolycount=0;
for ( t.ee = 1; t.ee <= g.glmsceneentitymax; t.ee++ ) // 040116 - changed t.e to to 1 to ensure maximum opportunity to batch anything in range
{
// 040116 - added t.e != t.ee to ensure first mesh not added twice!
t.ttlmobjstart=t.lmsceneobj[t.ee].startobj;
if ( t.e != t.ee && t.lmsceneobj[t.ee].lmvalid == -2 && t.ttlmobjstart>0 && t.overlordprocessed[t.ee] == 1 )
{
// this object is available for gluing
t.ttlmobjfinish=t.lmsceneobj[t.ee].finishobj;
for ( t.ttlmobj = t.ttlmobjstart ; t.ttlmobj<= t.ttlmobjfinish; t.ttlmobj++ )
{
if ( ObjectExist(t.ttlmobj) == 1 )
{
t.ttlmobjindex=t.ttlmobj-t.tlmobjstartedfrom;
if ( t.gtlobjused[t.ttlmobjindex] == 0 )
{
// 040115 - find first limb which HAS a mesh (thus a texture) - a two frame mesh with the first being an empty root will FAIL this test!
int iLimbThatHasMeshTexture = 0;
sObject* pThisObj = GetObjectData(t.tlmobj);
if ( pThisObj )
{
for ( int iF=0; iF<pThisObj->iFrameCount; iF++ )
{
if ( pThisObj->ppFrameList[iF]->pMesh )
{
iLimbThatHasMeshTexture = iF;
break;
}
}
}
int iOtherLimbThatHasMeshTexture = 0;
sObject* pOtherObj = GetObjectData(t.ttlmobj);
if ( pOtherObj )
{
for ( int iF=0; iF<pOtherObj->iFrameCount; iF++ )
{
if ( pOtherObj->ppFrameList[iF]->pMesh )
{
iOtherLimbThatHasMeshTexture = iF;
break;
}
}
}
if ( GetLimbTexturePtr(t.tlmobj,iLimbThatHasMeshTexture) == GetLimbTexturePtr(t.ttlmobj,iOtherLimbThatHasMeshTexture) && GetLimbTexturePtr(t.tlmobj,iLimbThatHasMeshTexture)>0 )
{
t.tdx_f=t.entityelement[t.e].x-t.entityelement[t.ee].x;
t.tdz_f=t.entityelement[t.e].z-t.entityelement[t.ee].z;
t.tdd_f=Sqrt(abs(t.tdx_f*t.tdx_f)+abs(t.tdz_f*t.tdz_f));
if ( t.tdd_f<1000 )
{
// this object close enough and of same texture index, add this object to master object
t.tnomorespace=0;
if ( GetObjectVertexCount(t.tlmobj)+GetObjectVertexCount(t.ttlmobj) >= 65530 ) t.tnomorespace = 1;
if ( t.tnomorespace == 0 && t.tlastpolycount+GetObjectPolygonCount(t.ttlmobj) <= t.tconsolidatelocallightmapobjectspolylimit )
{
// only when KNOW we are doing this, create the new master mesh for consolidation
if ( t.tcreatedmasterrecepticle == 0 )
{
// start a new master mesh object
DeleteObject ( t.tlmobj );
t.tlmobjdest=t.tlmobj;
MakeObject ( t.tlmobjdest,g.meshlightmapwork,-1 );
PositionObject ( t.tlmobjdest,t.tmasterx_f,t.tmastery_f,t.tmasterz_f );
t.tcreatedmasterrecepticle=1;
}
// and within polygon count max limit
if ( GetMeshExist(g.meshlightmapwork) == 1 ) DeleteMesh ( g.meshlightmapwork );
MakeMeshFromObject ( g.meshlightmapwork,t.ttlmobj );
// add this mesh to the master object
PerformCheckListForLimbs ( t.tlmobjdest );
AddLimb ( t.tlmobjdest,ChecklistQuantity(),g.meshlightmapwork );
t.tox_f=ObjectPositionX(t.ttlmobj)-t.tmasterx_f;
t.toy_f=ObjectPositionY(t.ttlmobj)-t.tmastery_f;
t.toz_f=ObjectPositionZ(t.ttlmobj)-t.tmasterz_f;
OffsetLimb ( t.tlmobjdest,ChecklistQuantity(),t.tox_f,t.toy_f,t.toz_f );
// now create a super-mesh which includes both master and new object
DeleteMesh ( g.meshlightmapwork );
MakeMeshFromObject ( g.meshlightmapwork,t.tlmobjdest );
// and create a new master object which combines both
DeleteObject ( t.tlmobjdest );
MakeObject ( t.tlmobjdest,g.meshlightmapwork,-1 );
PositionObject ( t.tlmobjdest,t.tmasterx_f,t.tmastery_f,t.tmasterz_f );
SetObjectMask ( t.tlmobjdest, 1 );
if ( t.entityelement[t.e].staticflag == 1 )
{
if ( t.entityprofile[t.entityelement[t.e].bankindex].canseethrough == 1 )
{
SetObjectCollisionProperty ( t.tlmobjdest,1 );
}
}
if ( t.entityprofile[t.entityelement[t.e].bankindex].ischaracter == 0 )
{
if ( t.entityprofile[t.entityelement[t.e].bankindex].collisionmode == 11 )
{
SetObjectCollisionProperty ( t.tlmobjdest,1 );
}
}
// delete the doner object and work mesh
if ( GetMeshExist(g.meshlightmapwork) == 1 ) DeleteMesh ( g.meshlightmapwork );
CloneObject ( t.tlmobjdest,t.tlmobj,101 );
DeleteObject ( t.ttlmobj );
// ensure deleted doner object info saved (so real ent can be hidden/deleted later)
// record size of this object polygons
t.tlastpolycount=GetObjectPolygonCount(t.tlmobj);
// add THIS object to the master
t.gtlobjused[t.ttlmobjindex]=t.ee;
// also ensure this object does NOT get saved
t.objsaveexclude[t.ttlmobj-g.lightmappedobjectoffset]=1;
}
else
{
// polygon count reached, skip rest for this batch
t.ttlmobj=t.ttlmobjfinish+1 ; t.ee=g.glmsceneentitymax+1;
}
}
}
}
}
}
}
}
}
if ( t.tcreatedmasterrecepticle == 0 )
{
// must take LM and bake in rotation and scale, even if not batched
// but only if not a multimesh otherwise two meshes get glued and one tex used
// and only if not made up of meshes with DIFFERENT textures
t.tfirsttexname_s="" ; t.tonedifferent=0;
PerformCheckListForLimbs ( t.tlmobj );
for ( t.c = 1 ; t.c<= ChecklistQuantity(); t.c++ )
{
t.ttexname_s=LimbTextureName(t.tlmobj,t.c-1);
if ( t.tfirsttexname_s == "" )
{
if ( t.ttexname_s != "" ) t.tfirsttexname_s = t.ttexname_s;
}
else
{
if ( t.ttexname_s != t.tfirsttexname_s ) t.tonedifferent = 1;
}
}
if ( t.tonedifferent == 0 )
{
// can consolidate into one mesh and one texture
DeleteObject ( t.tlmobj );
MakeObject ( t.tlmobj,g.meshlightmapwork,-1 );
PositionObject ( t.tlmobj,t.tmasterx_f,t.tmastery_f,t.tmasterz_f );
}
else
{
// if not touched at all, ensure properties restored
t.lmsceneobj[t.e].includerotandscale=1;
lm_preplmobj ( );
}
}
}
if ( t.tskipconsolidation == 2 )
{
if ( t.lmsceneobj[t.e].includerotandscale == 0 )
{
t.lmsceneobj[t.e].includerotandscale=1;
lm_preplmobj ( );
}
}
}
}
}
}
}
// only when ALL objects consolidated, mark entities we should hide (as they have been consolidated)
for ( t.ttlmobjindex = 1 ; t.ttlmobjindex<= t.tlobjmax; t.ttlmobjindex++ )
{
t.ee=t.gtlobjused[t.ttlmobjindex];
if ( t.ee>0 )
{
t.lmsceneobj[t.ee].startobj=abs(t.lmsceneobj[t.ee].startobj)*-1;
}
}
UnDim ( t.gtlobjused );
}
t.strwork = ""; t.strwork = t.strwork + "LIGHTMAPPER: Completed Consolidation with "+Str(t.tcountconsolidations)+" new objects";
timestampactivity(0, t.strwork.Get() );
}
// Go through a series of grouped entities
t.markslicesindex=0 ; t.tcurrententitygroup=1;
while ( t.tcurrententityindex <= g.glmsceneentitymax && (t.tcountoverlordmarks>0 || t.tterrainobjectscount>0) )
{
// Group title for this batch
t.strwork = ""; t.strwork = t.strwork + "LIGHTMAPPER: Group "+Str(t.tcurrententitygroup)+" at entity t.index "+Str(t.tcurrententityindex);
timestampactivity(0, t.strwork.Get() );
// Prepare all entity lightmapped objects for scene (objs start at end of terrain object set)
if ( t.tcurrententityindex <= 0 )
{
// signifies doing terrain objects first
t.strwork = "" ; t.strwork = t.strwork + "LIGHTMAPPER: Prepare Terrain For Pre-Baking ("+Str(t.tterrainobjectscount)+" segments)";
timestampactivity(0, t.strwork.Get() );
}
else
{
// signifies doing all objects in slices
t.strwork = ""; t.strwork = t.strwork + "LIGHTMAPPER: Prepare Objects For Pre-Baking ("+Str(t.tcurrententityindex)+" to "+Str(g.glmsceneentitymax)+")";
timestampactivity(0, t.strwork.Get() );
t.tnextentityindex=-1;
t.tobjectstobelitcount=0;
for ( t.e = t.tcurrententityindex ; t.e<= g.glmsceneentitymax; t.e++ )
{
t.tobj=t.entityelement[t.e].obj;
if ( t.tobj>0 && t.overlordprocessed[t.e] == 1 )
{
t.tentid=t.entityelement[t.e].bankindex;
t.tvalidshadereffect = 0 ; if ( strcmp ( Lower(t.entityprofile[t.tentid].effect_s.Get()) , "effectbank\\reloaded\\entity_basic.fx" ) == 0 ) t.tvalidshadereffect = 1;
if ( t.entityelement[t.e].staticflag == 1 && t.tvalidshadereffect == 1 )
{
// hide instance
if ( t.lmsceneobj[t.e].lmvalid == -2 )
{
// ready for next step in process
t.lmsceneobj[t.e].lmvalid=2;
++t.tobjectstobelitcount;
// copy entityelement info to this LM obj
t.lmsceneobj[t.e].bankindex=t.entityelement[t.e].bankindex;
t.lmsceneobj[t.e].x=t.entityelement[t.e].x;
t.lmsceneobj[t.e].y=t.entityelement[t.e].y;
t.lmsceneobj[t.e].z=t.entityelement[t.e].z;
t.lmsceneobj[t.e].rx=t.entityelement[t.e].rx;
t.lmsceneobj[t.e].ry=t.entityelement[t.e].ry;
t.lmsceneobj[t.e].rz=t.entityelement[t.e].rz;
t.lmsceneobj[t.e].sx=t.entityelement[t.e].scalex;
t.lmsceneobj[t.e].sy=t.entityelement[t.e].scaley;
t.lmsceneobj[t.e].sz=t.entityelement[t.e].scalez;
}
}
}
}
if ( t.tnextentityindex == -1 )
{
t.strwork = "" ; t.strwork = t.strwork + "LIGHTMAPPER: Working on all "+Str(t.tobjectstobelitcount)+" objects";
timestampactivity(0, t.strwork.Get() );
}
else
{
t.strwork = "" ; t.strwork = t.strwork + "LIGHTMAPPER: Slicing work at "+Str(t.tnextentityindex-1);
timestampactivity(0, t.strwork.Get() );
}
if ( t.tnextentityindex == -1 ) t.tnextentityindex = g.glmsceneentitymax+1;
}
// First scan to see if any ACTUAL lightmapping would take place (collision objects needed)
timestampactivity(0,"LIGHTMAPPER: Scan for relevant collision objects");
t.colobjworkcount=0;
Dim ( t.colobjworke,g.entityelementlist );
for ( t.e = 1 ; t.e<= g.entityelementlist; t.e++ )
{
t.tobj=t.entityelement[t.e].obj;
if ( t.tobj>0 )
{
t.tentid=t.entityelement[t.e].bankindex;
t.tvalidshadereffect = 0 ; if ( strcmp ( Lower(t.entityprofile[t.tentid].effect_s.Get()) , "effectbank\\reloaded\\entity_basic.fx" ) == 0 ) t.tvalidshadereffect = 1;
if ( t.entityelement[t.e].staticflag == 1 && t.entityprofile[t.tentid].ismarker == 0 && t.tvalidshadereffect == 1 )
{
// only choose those near enough to cast a possible shadow on current target
t.tokay=0;
if ( t.tcurrententityindex <= 0 )
{
// shadowing terrain
if ( t.tterrainobjectscount>0 )
{
t.tradwithheight=t.lmterrainshadrad+(ObjectSizeY(t.tobj,1)*2);
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
if ( ObjectPositionX(t.tlmobj2) >= t.overlordx && ObjectPositionX(t.tlmobj2)<t.overlordx+t.overlordradius )
{
if ( ObjectPositionZ(t.tlmobj2) >= t.overlordz && ObjectPositionZ(t.tlmobj2)<t.overlordz+t.overlordradius )
{
if ( t.entityelement[t.e].x >= ObjectPositionX(t.tlmobj2)-t.tradwithheight && t.entityelement[t.e].x<ObjectPositionX(t.tlmobj2)+t.tradwithheight )
{
if ( t.entityelement[t.e].z >= ObjectPositionZ(t.tlmobj2)-t.tradwithheight && t.entityelement[t.e].z<ObjectPositionZ(t.tlmobj2)+t.tradwithheight )
{
t.tokay=1;
}
}
}
}
}
}
}
}
else
{
// shadowing LM objects
t.tdx_f=t.entityelement[t.e].x-(t.overlordx+(t.overlordradius/2));
t.tdz_f=t.entityelement[t.e].z-(t.overlordz+(t.overlordradius/2));
t.tdd_f=Sqrt(abs(t.tdx_f*t.tdx_f)+abs(t.tdz_f*t.tdz_f));
if ( t.tdd_f<((t.overlordradius+0.0)*1.5) )
{
t.tokay=1;
}
}
if ( t.tokay == 1 )
{
if ( ObjectExist(t.tobj) == 1 )
{
++t.colobjworkcount ; t.colobjworke[t.colobjworkcount]=t.e;
}
}
}
}
}
// If nothing to lightmap, or nothing casting a shadow, can skip (unless terrain which needs light)
if ( t.tcurrententityindex <= 0 || (t.colobjworkcount>0 && (t.tcurrententityindex>0 && t.tobjectstobelitcount>0)) )
{
// Prepare lightmapping stage
t.quality_f=g.fLightmappingQuality;
t.blurlevel_f=g.fLightmappingBlurLevel;
if ( t.tcurrententityindex <= 0 )
{
t.texturesize=g.iLightmappingSizeTerrain;
}
else
{
t.texturesize=g.iLightmappingSizeEntity;
}
t.lightingthreads=-1;
if ( g.lmlightmapnowmode == 1 || g.lmlightmapnowmode == 2 || g.lmlightmapnowmode == 3 )
{
// quick lightmapping
t.ambientocclusion=0;
t.ambientocclusiondistance_f=0.0;
}
if ( g.lmlightmapnowmode == 4 )
{
// full lightmapping with ambient occlusion
t.ambientocclusion=10;
t.ambientocclusiondistance_f=5.0;
}
LMStart ( );
if ( PathExist(t.lightmapper.lmpath_s.Get()) == 0 ) MakeDirectory ( t.lightmapper.lmpath_s.Get() );
LMSetMode ( 0 );
LMSetLightMapFileFormat ( 1 );
LMSetLightMapFolder ( t.lightmapper.lmpath_s.Get() );
LMSetLightMapStartNumber ( t.storelasttexturefilenumber );
// include all static lights from scene
if ( g.lmlightmapnowmode == 2 || g.lmlightmapnowmode == 3 || g.lmlightmapnowmode == 4 )
{
for ( t.l = 1 ; t.l<= g.infinilightmax; t.l++ )
{
if ( t.infinilight[t.l].used == 1 && t.infinilight[t.l].type == 1 )
{
t.tr_f=t.infinilight[t.l].colrgb.r/255.0;
t.tg_f=t.infinilight[t.l].colrgb.g/255.0;
t.tb_f=t.infinilight[t.l].colrgb.b/255.0;
t.tr_f=t.tr_f-0.0 ; t.tr_f=t.tr_f*2;
t.tg_f=t.tg_f-0.0 ; t.tg_f=t.tg_f*2;
t.tb_f=t.tb_f-0.0 ; t.tb_f=t.tb_f*2;
t.trad_f=t.infinilight[t.l].range;
t.tatten_f=16.0/(t.trad_f*t.trad_f);
LMAddCustomPointLight ( t.infinilight[t.l].x,t.infinilight[t.l].y,t.infinilight[t.l].z,t.trad_f,t.trad_f,t.tatten_f,t.tr_f,t.tg_f,t.tb_f );
}
}
}
LMAddDirectionalLight ( -0.25f,-0.5f,-0.25f,1.05f,1.05f,1.05f );
LMSetAmbientLight ( 0.25f,0.25f,0.25f );
if ( t.ambientocclusion>0 )
{
LMSetAmbientOcclusionOn ( t.ambientocclusion,t.ambientocclusiondistance_f,1 );
}
// Go through all entities and create collision data
// use actual entity instances to generate collision data (strips to LOD0 inside command)
t.strwork = ""; t.strwork = t.strwork + "LIGHTMAPPER: Create "+Str(t.colobjworkcount)+" Collision Objects";
timestampactivity(0, t.strwork.Get() );
for ( t.colobjworkindex = 1 ; t.colobjworkindex<= t.colobjworkcount; t.colobjworkindex++ )
{
t.e=t.colobjworke[t.colobjworkindex];
t.tobj=t.entityelement[t.e].obj;
t.tentid=t.entityelement[t.e].bankindex;
if ( t.entityprofile[t.tentid].castshadow != -1 )
{
if ( t.entityprofile[t.tentid].transparency>0 )
{
if ( g.lmlightmapnowmode == 2 || g.lmlightmapnowmode == 3 || g.lmlightmapnowmode == 4 )
{
LMAddTransparentCollisionObject ( t.tobj,1 );
}
else
{
LMAddCollisionObject ( t.tobj );
}
}
else
{
LMAddCollisionObject ( t.tobj );
}
}
}
UnDim ( t.colobjworke );
if ( t.tcurrententityindex>0 )
{
// add terrain as colliders for objects (so ambient occlusion on objects can happen)
timestampactivity(0,"LIGHTMAPPER: Create Terrain Ray Collision Data");
if ( g.lightmappedterrainoffset != -1 )
{
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
if ( GetVisible(t.tlmobj2) == 1 )
{
LMAddCollisionObject ( t.tlmobj2 );
}
}
}
}
}
LMBuildCollisionData ( );
// Add objects to be lightmapped
if ( t.tcurrententityindex <= 0 )
{
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
if ( GetVisible(t.tlmobj2) == 1 )
{
LMAddShadedLightMapObject ( t.tlmobj2,0,-1 );
}
}
}
}
else
{
// Add entity objects for lightmapping
timestampactivity(0,"LIGHTMAPPER: Add Objects To Be Lightmapped");
for ( t.e = t.tcurrententityindex ; t.e<= t.tnextentityindex-1; t.e++ )
{
if ( t.overlordprocessed[t.e] == 1 )
{
if ( t.lmsceneobj[t.e].lmvalid == 2 )
{
t.tobjstart=abs(t.lmsceneobj[t.e].startobj);
for ( t.tlmobj = t.tobjstart ; t.tlmobj<= t.lmsceneobj[t.e].finishobj; t.tlmobj++ )
{
if ( ObjectExist(t.tlmobj) == 1 )
{
// 011215 - solve facet issue by rewelding normals together (anything less than 50 degrees is blended for curved surfaces)
if ( g.fLightmappingSmoothAngle > 0 )
SetObjectSmoothing ( t.tlmobj, g.fLightmappingSmoothAngle );
// then add to be lightmapped
LMAddShadedLightMapObject ( t.tlmobj,1 );
}
}
}
}
}
}
// Calculate all lightmaps
timestampactivity(0,"LIGHTMAPPER: Calculate Lightmaps");
if ( t.lightingthreads == 0 )
{
// single threading now uses time sliced commands
LMBuildLightMapsStart ( t.texturesize,t.quality_f,t.blurlevel_f );
while ( LMBuildLightMapsCycle() == 0 )
{
SleepNow ( 50 );
t.tonscreenprompt_s = ""; t.tonscreenprompt_s=t.tonscreenprompt_s+LMGetStatus()+" (hold ESCAPE to cancel)";
if ( t.hardwareinfoglobals.noterrain == 0 ) terrain_update ( );
lm_onscreenprompt ( );
lm_focuscameraonoverlordxz ( );
Sync ( );
}
}
else
{
LMBuildLightMapsThread ( t.texturesize,t.quality_f,t.blurlevel_f,t.lightingthreads ) ;
while ( LMGetComplete() == 0 )
{
SleepNow ( 50 );
t.tonscreenprompt_s = "" ; t.tonscreenprompt_s=t.tonscreenprompt_s+LMGetStatus()+" (hold ESCAPE to cancel)";
if ( t.hardwareinfoglobals.noterrain == 0 ) terrain_update ( );
lm_onscreenprompt ( );
lm_focuscameraonoverlordxz ( );
Sync ( );
}
}
// If ESCAPE key press detected, must cancel lightmapping step
t.tskiplightmapcompletion=0;
if ( EscapeKey() == 1 )
{
t.tdisableLMprogressreading=1;
while ( EscapeKey() == 1 )
{
t.tonscreenprompt_s="Lightmapping Aborted";
if ( t.hardwareinfoglobals.noterrain == 0 ) terrain_update ( );
lm_onscreenprompt( ) ; Sync ( );
}
lm_emptylightmapfolder ( );
t.tdisableLMprogressreading=0;
t.tskiplightmapcompletion=1;
}
// Finalise and save lightmaps
if ( t.tskiplightmapcompletion == 1 )
{
timestampactivity(0,"LIGHTMAPPER: Lightmapping aborted.");
LMReset ( );
lm_deleteall ( );
return;
}
else
{
timestampactivity(0,"LIGHTMAPPER: Finalise And Save Lightmap Textures");
t.tdisableLMprogressreading=2;
for ( t.n = 0 ; t.n<= 1; t.n++ )
{
if ( t.tcurrententityindex <= 0 )
{
t.tonscreenprompt_s="Saving Terrain Lightmaps";
}
else
{
t.tonscreenprompt_s="Saving Entity Lightmaps";
}
if ( t.hardwareinfoglobals.noterrain == 0 ) terrain_update ( );
lm_onscreenprompt() ; Sync ( );
}
t.tdisableLMprogressreading=0;
LMCompleteLightMaps ( );
timestampactivity(0,"LIGHTMAPPER: Lightmap bake completed");
t.storelasttexturefilenumber=LMGetLightMapLastNumber();
}
// Reset lightmapper system
timestampactivity(0,"LIGHTMAPPER: Reset Lightmapper");
LMReset ( );
// Skipped lm process
}
// Finished light map, now save it out
if ( t.tcurrententityindex <= 0 )
{
// done terrain objects (only when tcurrententityindex>0), THEN do the entity objects
++t.tcurrententitygroup;
t.tnextentityindex=t.tcurrententityindex+1;
}
else
{
for ( t.e = t.tcurrententityindex ; t.e<= t.tnextentityindex-1; t.e++ )
{
if ( t.lmsceneobj[t.e].lmvalid == 2 )
{
t.lmsceneobj[t.e].needsaving=1;
t.lmsceneobj[t.e].lmvalid=3;
}
}
}
// Loop to group lightmapping into a series of memory saving batches
t.tcurrententityindex=t.tnextentityindex;
}
if ( t.tcountoverlordmarks>0 || t.tterrainobjectscount>0 )
{
timestampactivity(0,"LIGHTMAPPER:");
t.tterrainobjectscount=0;
t.tcountoverlordmarks=0;
}
// Finally mark ones we baked as used, and restore the 'collision radius ones'
for ( t.e = 1 ; t.e<= g.entityelementlist; t.e++ )
{
if ( t.overlordprocessed[t.e] == 1 ) t.overlordprocessed[t.e] = 3;
if ( t.overlordprocessed[t.e] == 2 ) t.overlordprocessed[t.e] = 0;
if ( t.overlordprocessed[t.e] == 4 ) t.overlordprocessed[t.e] = 3;
}
// We step through each grid in the entire world (keeps workload groups small)
}
}
timestampactivity(0,"LIGHTMAPPER: Finished overlord loop");
UnDim ( t.overlordprocessed );
// only if processed anything for this grid
if ( 1 )
{
// Raise terrain light map objects
t.liftshadowstositontopofterrain_f=0.0;
for ( t.tlmobj2 = g.lightmappedterrainoffset ; t.tlmobj2<= g.lightmappedterrainoffsetfinish; t.tlmobj2++ )
{
if ( ObjectExist(t.tlmobj2) == 1 )
{
PositionObject ( t.tlmobj2,ObjectPositionX(t.tlmobj2),t.liftshadowstositontopofterrain_f,ObjectPositionZ(t.tlmobj2) );
SetObjectTransparency ( t.tlmobj2, 6 );
lm_zbias ( );
}
}
// Prepare shader for light map objects
timestampactivity(0,"LIGHTMAPPER: Apply Static Shaders");
lm_applystaticshader ( );
// Save lightmapped scene objects
timestampactivity(0,"LIGHTMAPPER: Save Lightmap Files");
lm_savescene ( );
// Finally show results of lightmapping (hide static entities)
timestampactivity(0,"LIGHTMAPPER: Show Lightmap Results");
lm_showall ( );
}
// free usage array when done
UnDim ( t.objsaveexclude );
// End lightmapper Timer ( )
t.endtimer = Timer();
}
You can email me directly at lee@thegamecreators.com if you have specific models in the default assets (or DLC) you feel should be fixed, and please indicate the exact nature of the issue with the model/entity, thanks. For models on the online asset store, you can contact the authors of those models directly who will be happy to make the corrections for you (just like coders, artists are always looking to improve their skillsets).
PC SPECS: Windows 8.1 Pro 64-bit, Intel Core i7-5930K (PASSMARK:13645), NVIDIA Geforce GTX 980 GPU (PASSMARK:9762) , 32GB RAM