/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program 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.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
/***********************************************************************************************
*** Confidential - Westwood Studios ***
***********************************************************************************************
* *
* Project Name : LightMap *
* *
* $Archive:: /Commando/Code/Tool $*
* *
* $Author:: Ian_l $*
* *
* $Modtime:: 6/06/01 5:40p $*
* *
* $Revision:: 35 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifndef _LIGHTMAPPACKER_H
#define _LIGHTMAPPACKER_H
// Includes.
#include "ProceduralTexture.h"
#include "StringBuilder.h"
#include "Triangle.h"
#include "vector.h"
#include "vector2.h"
#include "matrix3.h"
#include "listnode.h"
#include "Targa.h"
#include "w3d_file.h"
#include
#include
// Defines.
#define UNPACKED_TEXEL_BYTE_COUNT 4 // No. of bytes for internal unpacked texel format (ARGB).
class ColorSurface : public srColorSurface
{
public:
ColorSurface (srPixelConvert::e_surfaceType pixelformat, short width, short height)
: srColorSurface (pixelformat, width, height)
{}
};
class TrueColorTarga : public Targa
{
public:
// Public functions.
TrueColorTarga();
TrueColorTarga (unsigned width, unsigned height, unsigned pixeldepth);
TrueColorTarga (unsigned width, unsigned height, unsigned pixeldepth, const W3dRGBStruct &clearcolor);
void Reformat (unsigned width, unsigned height, unsigned pixeldepth);
void Reformat (unsigned width, unsigned height, unsigned pixeldepth, const W3dRGBStruct &clearcolor);
unsigned Width() const {return (Header.Width);}
unsigned Height() const {return (Header.Height);}
unsigned Pixel_Depth() const {return (Header.PixelDepth);}
void Scale (unsigned width, unsigned height) {Scale (*this, width, height);}
void Scale (TrueColorTarga &destination) {Scale (destination, destination.Width(), destination.Height());}
void Scale (float filtererror) {Scale (*this, filtererror);}
void Transpose() {Transpose (*this);}
void Pad (unsigned padwidth, unsigned padheight, const W3dRGBStruct &padcolor, DynamicVectorClass &triangleptrs)
{
Pad (*this, padwidth, padheight, padcolor, triangleptrs);
}
char *Load (const char *pathname);
char *Save (const char *pathname);
void Blit (TrueColorTarga &destination, unsigned x, unsigned y);
void Scale (TrueColorTarga &destination, unsigned width, unsigned height);
int Compare (TrueColorTarga &comparison, float epsilon);
int Compare (TrueColorTarga &comparison, unsigned x, unsigned y, float epsilon);
bool Fill (const W3dRGBStruct &fillcolor);
void Add (TrueColorTarga &targa);
void Rasterize (TrueColorTarga &destination, const W3dRGBStruct &fillcolor, unsigned vertexcount, const W3dRGBStruct *vertexcolors, Vector2 *uvs);
bool Get_Color (const Vector2 &t, W3dRGBStruct &color) const;
bool Set_Color (unsigned x, unsigned y, const W3dRGBStruct &color);
protected:
enum TransposeEnum {
UNTRANSPOSED,
TRANSPOSED,
TRANSPOSE_COUNT
};
struct UnpackedTexelStruct {
UnpackedTexelStruct () {}
UnpackedTexelStruct (unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
Set (r, g, b, a);
}
void Set (unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
Byte [0] = a;
Byte [1] = r;
Byte [2] = g;
Byte [3] = b;
}
unsigned char Alpha() const {return (Byte [0]);}
unsigned char Red() const {return (Byte [1]);}
unsigned char Green() const {return (Byte [2]);}
unsigned char Blue() const {return (Byte [3]);}
// Equality operator.
bool operator == (const UnpackedTexelStruct &t) {
ASSERT (sizeof (unsigned long) == sizeof (Byte));
return (*((unsigned long*) Byte) == *((unsigned long*) t.Byte));
}
// Inequality operator.
bool operator != (const UnpackedTexelStruct &t) {
return (!(*this == t));
}
// Addition operator.
UnpackedTexelStruct operator += (UnpackedTexelStruct t)
{
Byte [0] = MIN (((unsigned) Byte [0]) + ((unsigned) t.Byte [0]), (unsigned) UCHAR_MAX);
Byte [1] = MIN (((unsigned) Byte [1]) + ((unsigned) t.Byte [1]), (unsigned) UCHAR_MAX);
Byte [2] = MIN (((unsigned) Byte [2]) + ((unsigned) t.Byte [2]), (unsigned) UCHAR_MAX);
Byte [3] = MIN (((unsigned) Byte [3]) + ((unsigned) t.Byte [3]), (unsigned) UCHAR_MAX);
return (*this);
}
// Public data.
unsigned char Byte [UNPACKED_TEXEL_BYTE_COUNT];
};
struct PointStruct {
// Equality operator.
bool operator == (const PointStruct &p) {
return ((X == p.X) && (Y == p.Y));
}
// Inequality operator.
bool operator != (const PointStruct &p) {
return (!(*this == p));
}
// Public data.
int X;
int Y;
};
void Clear (const UnpackedTexelStruct &cleartexel);
private:
// Inlines.
unsigned char *Get_Texel (int x, int y) const;
UnpackedTexelStruct *Unpack_Texel (unsigned char *packedtexelptr, unsigned packedbytespertexel, UnpackedTexelStruct &unpackedtexel) const;
void Pack_Texel (const UnpackedTexelStruct &unpackedtexel, unsigned char *packedtexelptr, unsigned packedbytespertexel);
// Static functions.
static srPixelConvert::e_surfaceType Pixel_Format (unsigned pixeldepth);
void Scale (TrueColorTarga &destination, float filtererror);
void Transpose (TrueColorTarga &destination);
bool Fill_Four_Connected (unsigned x, unsigned y, const UnpackedTexelStruct &filltexel, DynamicVectorClass &fourconnectedarray);
void Pad (TrueColorTarga &destination, unsigned verticalpadthickness, unsigned horizontalpadthickness, const W3dRGBStruct &padcolor, DynamicVectorClass &triangleptrs);
};
class Page : public TrueColorTarga
{
public:
// Public functions.
Page (unsigned bitdepth, const W3dRGBStruct &clearcolor);
~Page ();
bool Pack (TrueColorTarga &targa, float epsilon, DynamicVectorClass &triangleptrs);
float Packing_Efficiency() const
{
return (((float) UsedTexelCount) / (((unsigned) Width()) * ((unsigned) Height())));
}
float Replica_Efficiency() const
{
return (((float) ReplicaTexelCount) / (((unsigned) Width()) * ((unsigned) Height())));
}
private:
class Region : public GenericNode {
public:
// Public functions.
Region () {}
Region (const Region ®ion)
{
Set (region.X0, region.Y0, region.X1, region.Y1);
}
unsigned Width () {return (X1 - X0 + 1);}
unsigned Height() {return (Y1 - Y0 + 1);}
void Set (unsigned x0, unsigned y0, unsigned x1, unsigned y1)
{
X0 = x0; Y0 = y0; X1 = x1; Y1 = y1;
}
bool Accomodates (unsigned width, unsigned height, Region &targetregion);
bool Intersects (const Region &targetregion, unsigned &subregioncount, Region **subregions);
// Public data.
unsigned X0, Y0;
unsigned X1, Y1;
};
// Private functions.
bool Replica_Region (TrueColorTarga &targa, float epsilon, Region &replicaregion);
bool Lowest_Cost_Region (TrueColorTarga &targa, unsigned &lowestcost, Region &lowestcostregion);
void Insert_Region (const Region &insertionregion);
bool Contains (const Region &testregion);
// Statistics functions.
void Reset_Statistics();
// Private data.
GenericList VacantRegionList;
GenericList UsedRegionList;
// Statistics counters.
unsigned AssetCount;
unsigned UsedTexelCount;
unsigned ReplicaTexelCount;
};
class TargaLoader {
public:
// Public functions.
TargaLoader (float samplerate, W3dRGBStruct fillcolor);
~TargaLoader();
TrueColorTarga *Load (const Triangle &triangle);
private:
struct TargaCacheStruct {
TargaCacheStruct()
{
Ptr = NULL;
}
~TargaCacheStruct()
{
if (Ptr != NULL) delete Ptr;
}
// Equality operator.
bool operator == (const TargaCacheStruct &t) {
return ((ID == t.ID) && (Ptr == t.Ptr));
}
// Inequality operator.
bool operator != (const TargaCacheStruct &t) {
return (!(*this == t));
}
// Data.
unsigned ID;
TrueColorTarga *Ptr;
};
// Private functions.
TargaLoader() {}
unsigned Index (unsigned id)
{
return (id % ((unsigned) Cache.Length()));
}
// Private data.
VectorClass Cache; // A simple array of targacache objects.
DynamicVectorClass CleanupList; // List of targas that have been displaced from the cache but cannot
// yet be deleted because they may still be referenced.
float SampleRate; // No. of texels per unit length.
W3dRGBStruct FillColor; // Color to replace during fill operations.
};
class TrianglePacker {
public:
TrianglePacker (const PackingTriangle *principaltriangleptr, const DynamicVectorClass &adjtriangles, unsigned groupid, unsigned edgeblendthickness, float samplerate, W3dRGBStruct fillcolor);
DynamicVectorClass &Principal_Triangles() {return (PrincipalTriangles);}
// Query functions.
unsigned Width() const {return (UpperBound.U - LowerBound.U);}
unsigned Height() const {return (UpperBound.V - LowerBound.V);}
// Operations.
TrianglePacker *Merge (const TrianglePacker &trianglepacker);
void Rasterize (TargaLoader &targaloader, ProceduralTexture *proceduraltexture, TrueColorTarga &rasterizedtarga);
// Statistics.
unsigned Adjacent_Face_Count() const
{
return (AdjacentTriangles [ADJACENT_PROJECTION_COMMON].Count() +
AdjacentTriangles [ADJACENT_PROJECTION_VALID].Count() +
AdjacentTriangles [ADJACENT_PROJECTION_NONE].Count());
}
unsigned Blended_Face_Count() const
{
return (AdjacentTriangles [ADJACENT_PROJECTION_COMMON].Count() +
AdjacentTriangles [ADJACENT_PROJECTION_VALID].Count());
}
float Edge_Blend_Area() const;
private:
enum ProjectionEnum {
PROJECTION_Y_POSITIVE,
PROJECTION_Y_NEGATIVE,
PROJECTION_Z_POSITIVE,
PROJECTION_Z_NEGATIVE,
PROJECTION_X_POSITIVE,
PROJECTION_X_NEGATIVE,
PROJECTION_COUNT
};
enum AdjacentProjectionEnum {
ADJACENT_PROJECTION_COMMON, // Adjacent triangles that use the same projection as this object.
ADJACENT_PROJECTION_VALID, // Adjacent triangles that map to a different projection but have non-zero area if object's projection is used.
ADJACENT_PROJECTION_NONE, // Adjacent triangles that cannot be projected using object's projection.
ADJACENT_PROJECTION_COUNT
};
class ProjectionTriangle {
public:
// Public functions.
ProjectionTriangle() {}
ProjectionTriangle (const Vector3 *points, const Vector2 *sourceuvs, const TrueColorTarga *sourcetargaptr, const Vector2 *projectionuvs);
// Public data.
Vector3 Points [Triangle::VERTICES_COUNT];
Vector2 SourceUVs [Triangle::VERTICES_COUNT];
TrueColorTarga *SourceTargaPtr;
Vector2 ProjectionUVs [Triangle::VERTICES_COUNT];
};
class SampleSurface {
public:
// Public functions.
SampleSurface (unsigned width, unsigned height, ProceduralTexture *blendtexture = NULL);
bool Sample (const Vector2 &samplepoint, const ProjectionTriangle &projectiontriangle, unsigned priority);
bool Sample (float alpha, float beta, const ProjectionTriangle &projectiontriangle, unsigned priority);
bool Get_Color (unsigned x, unsigned y, W3dRGBStruct &color)
{
SampleStruct *sampleptr;
ASSERT ((x < Width) && (y < Height));
sampleptr = Surface + (y * Width + x);
if (sampleptr->Count > 0) {
float oocount;
unsigned r, g, b;
oocount = 1.0f / sampleptr->Count;
r = sampleptr->Red * oocount;
g = sampleptr->Green * oocount;
b = sampleptr->Blue * oocount;
color.Set ((uint8) MIN (r, UCHAR_MAX), (uint8) MIN (g, UCHAR_MAX), (uint8) MIN (b, UCHAR_MAX));
return (true);
} else {
return (false);
}
}
unsigned Priority (unsigned x, unsigned y)
{
SampleStruct *sampleptr;
ASSERT ((x < Width) && (y < Height));
sampleptr = Surface + (y * Width + x);
return (sampleptr->Priority);
}
float Sampling_Ratio() {return (((float) SampledTexelCount) / ((float) (Width * Height)));}
private:
struct SampleStruct {
unsigned Red;
unsigned Green;
unsigned Blue;
unsigned Count;
unsigned Priority;
};
SampleSurface() {}
unsigned Width;
unsigned Height;
unsigned SampledTexelCount;
SampleStruct *Surface;
ProceduralTexture *BlendTexture;
};
// Private functions.
TrianglePacker() {}
TrianglePacker (const TrianglePacker &trianglepacker); // Copy constructor.
void Set_Bounds();
bool Can_Project (const Vector3 &normal);
Vector2 Project (const Vector3 &point) const;
// void Get_Projection_Triangle (const Triangle &triangle, ProjectionTriangleStruct &projectiontriangle);
bool Sample (const Vector2 &samplepoint, const ProjectionTriangle &projectiontriangle, W3dRGBStruct &color);
static ProjectionEnum Get_Projection (const Vector3 &normal);
// Private data.
ProjectionEnum Projection; // Texture projection to be used for rasterization.
unsigned GroupID;
unsigned EdgeBlendThickness;
float SampleRate; // No. of texels per unit length.
W3dRGBStruct FillColor; // Color to replace during fill operations.
DynamicVectorClass PrincipalTriangles; // Set of triangles that will be rasterized in their entirety (ie. not edge blended).
DynamicVectorClass AdjacentTriangles [ADJACENT_PROJECTION_COUNT]; // Set of triangles that will be edge-blended.
Vector2 LowerBound; // Lower bound of all principal triangle points projected onto the texel plane.
Vector2 UpperBound; // Upper bound of all principal triangle points projected onto the texel plane.
};
class LightmapPacker {
public:
enum ThicknessEnum {
EDGE_BLEND_THICKNESS = 2 // No. of texels to pad around every packed map.
// NOTE: The edge blend thickness is designed to prevent
// bleeding between adjacent maps under mip-mapping. The
// no, of allowable mip-maps is dependant upon this
// thickness value.
};
enum StatisticsEnum {
STATISTICS_PAGE_FORMAT,
STATISTICS_LIGHTMAPS_PROCESSED,
STATISTICS_ADJACENT_FACE_BLEND_PERCENTAGE,
STATISTICS_EDGE_BLEND_EFFICIENCY,
STATISTICS_SCALING_EFFICIENCY,
STATISTICS_PAGES_CREATED,
STATISTICS_PACKING_EFFICIENCY,
STATISTICS_CULLING_EFFICIENCY,
STATISTICS_TEXTURE_SWAP_EFFICIENCY,
STATISTICS_OVERSIZE_LIGHTMAPS,
STATISTICS_COUNT,
STATISTICS_STRING_SIZE = 32 // Size of statistic string in bytes.
};
LightmapPacker();
Finish();
~LightmapPacker();
void Submit (PackingTriangle *principaltriangleptr, const DynamicVectorClass &adjtriangles);
TrianglePacker *Merge (TrianglePacker *trianglepackerptr);
void Pack (ProceduralTexture *proceduraltexture = NULL);
void Pack (TrueColorTarga &targa, DynamicVectorClass &triangleptrs);
static const char *Lightmap_Pathname (unsigned pageindex);
static const char *Asset_Directory();
static const char *Asset_Directory (const char *filename);
static void Delete_Assets();
static void Copy_Assets (const char *pathname);
static const char *Get_Statistic (unsigned index) {ASSERT (index < STATISTICS_COUNT); return (_Statistics [index]);}
private:
// Statistics functions.
void Reset_Statistics();
void Update_Statistics (const Page &page);
void Update_Statistics (const TrianglePacker &trianglepacker);
void Collate_Statistics();
W3dRGBStruct FillColor; // Color used to pad unused texels in lightmaps.
float ScaleFactor; // Global lightmap scaling factor (0.0...1.0).
float FilterError; // Maximum allowed variance between a color component of original
// lightmap and that of packed lightmap, expressed as a fraction
// of color component's total range ie. a setting of 0.0 will
// allow no variance (packed lightmap must match original) and
// a setting of 1.0 allows complete variance.
float SampleRate; // Sample rate used by rasterizers (such as Edge_Blend()) in texels per unit length.
unsigned PageBitDepth; // Bit depth of page texels.
int CurrentPageIndex; // Current page (-1 indicates no page exists).
DynamicVectorClass TrianglePackerPtrs;
DynamicVectorClass PagePtrs;
TextureNameNode *PlaceholderTextureNameNodePtr;
// Statistics counters.
unsigned FaceCount;
unsigned LightmapCount;
unsigned AdjacentFaceCount;
unsigned BlendedFaceCount;
double EdgeBlendAreaSum;
__int64 UnscaledTexelCount;
__int64 ScaledTexelCount;
double PackingEfficiencySum;
double ReplicaEfficiencySum;
unsigned TextureSwapCount;
unsigned OversizeCount;
unsigned AllFillColorCount;
static unsigned _BasePageIndex;
static char _Statistics [STATISTICS_COUNT][STATISTICS_STRING_SIZE];
};
/***********************************************************************************************
* TrueColorTarga::Get_Color -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/27/00 IML : Created. *
*=============================================================================================*/
inline bool TrueColorTarga::Get_Color (const Vector2 &t, W3dRGBStruct &color) const
{
int x, y;
unsigned char *texelptr;
UnpackedTexelStruct unpackedtexel;
x = t.U * Width();
y = t.V * Height();
texelptr = Get_Texel (x, y);
if (texelptr == NULL) return (false);
Unpack_Texel (texelptr, TGA_BytesPerPixel (Pixel_Depth()), unpackedtexel);
color.Set (unpackedtexel.Red(), unpackedtexel.Green(), unpackedtexel.Blue());
return (true);
}
/***********************************************************************************************
* TrueColorTarga::Set_Color -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/27/00 IML : Created. *
*=============================================================================================*/
inline bool TrueColorTarga::Set_Color (unsigned x, unsigned y, const W3dRGBStruct &color)
{
UnpackedTexelStruct unpackedtexel (color.R, color.G, color.B, 0);
unsigned char *texelptr;
texelptr = Get_Texel (x, y);
if (texelptr != NULL) {
Pack_Texel (unpackedtexel, texelptr, TGA_BytesPerPixel (Pixel_Depth()));
return (true);
} else {
return (false);
}
}
/***********************************************************************************************
* TrueColorTarga::Get_Texel -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
inline unsigned char *TrueColorTarga::Get_Texel (int x, int y) const
{
// Check for (x, y) out of range.
if ((x < 0) || (x >= ((int) Width ()))) return (NULL);
if ((y < 0) || (y >= ((int) Height()))) return (NULL);
return ((unsigned char*) GetImage()) + (((Width() * y) + x) * TGA_BytesPerPixel (Pixel_Depth()));
}
/***********************************************************************************************
* TrueColorTarga::Unpack_Texel -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
inline TrueColorTarga::UnpackedTexelStruct *TrueColorTarga::Unpack_Texel (unsigned char *packedtexelptr, unsigned packedbytespertexel, UnpackedTexelStruct &unpackedtexel) const
{
switch (packedbytespertexel) {
case 4:
// Packed format same as unpacked format.
ASSERT (sizeof (UnpackedTexelStruct) == 4);
unpackedtexel = *((UnpackedTexelStruct*) packedtexelptr);
break;
case 3:
// Unpack to ordering ARGB. Set Alpha to zero.
unpackedtexel.Byte [0] = 0; // Alpha
unpackedtexel.Byte [1] = *(packedtexelptr + 0); // Red
unpackedtexel.Byte [2] = *(packedtexelptr + 1); // Green
unpackedtexel.Byte [3] = *(packedtexelptr + 2); // Blue
break;
case 2:
{
static unsigned char _alpha [2] = {0x00, 0xff};
unsigned short packedtexel = *((unsigned short*) packedtexelptr);
// Unpack to ordering ARGB. Bit replicate Alpha.
unpackedtexel.Byte [0] = _alpha [packedtexel >> 15]; // Alpha
unpackedtexel.Byte [1] = ((packedtexel & 0x7c00) >> 7) | 0x3; // Red
unpackedtexel.Byte [2] = ((packedtexel & 0x03e0) >> 2) | 0x3; // Green
unpackedtexel.Byte [3] = ((packedtexel & 0x001f) << 3) | 0x3; // Blue
}
break;
default:
// Unrecognized byte count.
ASSERT (0);
break;
}
return (&unpackedtexel);
}
/***********************************************************************************************
* TrueColorTarga::Pack_Texel -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
inline void TrueColorTarga::Pack_Texel (const UnpackedTexelStruct &unpackedtexel, unsigned char *packedtexelptr, unsigned packedbytespertexel)
{
switch (packedbytespertexel) {
case 4:
*(packedtexelptr + 0) = unpackedtexel.Byte [0];
*(packedtexelptr + 1) = unpackedtexel.Byte [1];
*(packedtexelptr + 2) = unpackedtexel.Byte [2];
*(packedtexelptr + 3) = unpackedtexel.Byte [3];
break;
case 3:
// Pack to ordering RGB. Lose Alpha.
*(packedtexelptr + 0) = unpackedtexel.Byte [1];
*(packedtexelptr + 1) = unpackedtexel.Byte [2];
*(packedtexelptr + 2) = unpackedtexel.Byte [3];
break;
case 2:
{
unsigned a, r, g, b;
// Pack to ordering ARGB. Round Alpha, Red, Green, Blue.
a = unpackedtexel.Byte [0] >> 7;
r = MIN (0x1f, (unpackedtexel.Byte [1] >> 3) + ((unpackedtexel.Byte [1] & 0x4) >> 2));
g = MIN (0x1f, (unpackedtexel.Byte [2] >> 3) + ((unpackedtexel.Byte [2] & 0x4) >> 2));
b = MIN (0x1f, (unpackedtexel.Byte [3] >> 3) + ((unpackedtexel.Byte [3] & 0x4) >> 2));
*((unsigned short*) packedtexelptr) = ((a << 15) | (r << 10) | (g << 5) | b);
}
break;
default:
// Unrecognized byte count.
ASSERT (0);
break;
}
}
#endif // LIGHTMAPPACKER_H