This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/LightMap/LightMapPacker.cpp

3387 lines
150 KiB
C++

/*
** 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 <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** Confidential - Westwood Studios ***
***********************************************************************************************
* *
* Project Name : LightMap *
* *
* $Archive:: /Commando/Code/Tool $*
* *
* $Author:: Ian_l $*
* *
* $Modtime:: 7/09/01 4:30p $*
* *
* $Revision:: 50 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// Includes.
#include "StdAfx.h"
#include "LightMap.h"
#include "LightmapPacker.h"
#include "OptionsDialog.h"
#include "StringBuilder.h"
#include "Targa.h"
#include "TextureNameNode.h"
#include "srFilter.hpp"
#include <direct.h>
#include <io.h>
#include <stdlib.h>
#include <winbase.h>
// Defines.
#define TEMPORARY_DIRECTORY_NAME "~L" // Name of temporary directory used to store packed lightmaps.
#define PAGE_WIDTH 256 // Width & height of page in texels.
#define PAGE_HEIGHT 256 // NOTE: Elected to use the biggest texture size that is
// supported by the gamut of PC 3D graphics hardware in order
// to maximize no. of lightmaps packed per page and therefore
// minimize no. of texture swaps per rendered polygon.
#define PACKING_BYTES (1024 * 1024) // No. of page bytes to consider for packing the current lightmap.
#define ASSET_EXTENSION ".tga" // Extension (and file type) of lightmap assets.
// Static data.
unsigned LightmapPacker::_BasePageIndex = 0;
char LightmapPacker::_Statistics [LightmapPacker::STATISTICS_COUNT][LightmapPacker::STATISTICS_STRING_SIZE] = {
"No data available",
"No data available",
"No data available",
"No data available",
"No data available",
"No data available",
"No data available",
"No data available",
"No data available",
"No data available"
};
/***********************************************************************************************
* LightmapPacker::LightmapPacker -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
LightmapPacker::LightmapPacker()
{
char pathname [_MAX_PATH];
OptionsDialog options;
// Initialize.
FillColor = options.Get_Fill_Color();
FilterError = pow (options.Get_Filter_Error() * 4.919e-3l, 2.5l); // Map s.t. 50th percentile -> 0.03% RGB error.
SampleRate = options.Get_Sample_Rate();
PageBitDepth = options.Get_Bit_Depth();
CurrentPageIndex = -1;
Reset_Statistics();
// Recreate the asset directory.
strcpy (pathname, theApp.Working_Path());
strcat (pathname, Asset_Directory());
// Can the asset directory can be successfully created?
if (_mkdir (pathname) == 0) {
_BasePageIndex = 0;
}
// Create the placeholder texture name node.
strcpy (pathname, theApp.Working_Path());
strcat (pathname, "Placeholder.tga");
PlaceholderTextureNameNodePtr = new TextureNameNode (pathname);
ASSERT (PlaceholderTextureNameNodePtr != NULL);
}
/***********************************************************************************************
* LightmapPacker::Finish -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
LightmapPacker::Finish()
{
// For each page...
for (int pageindex = 0; pageindex < PagePtrs.Count(); pageindex++) {
char pagepathname [_MAX_PATH];
char *errormessage;
// Pad fill color.
PagePtrs [pageindex]->Fill (FillColor);
// Update statistics about the page.
Update_Statistics (*PagePtrs [pageindex]);
// Save the page. Report any error.
strcpy (pagepathname, theApp.Working_Path());
strcat (pagepathname, Lightmap_Pathname (pageindex));
errormessage = PagePtrs [pageindex]->Save (pagepathname);
if (errormessage != NULL) throw (errormessage);
}
_BasePageIndex += PagePtrs.Count();
Collate_Statistics();
}
/***********************************************************************************************
* LightmapPacker::~LightmapPacker -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: Do not throw any exceptions in this (and any other) destructor because this *
* destructor can itself be called during the exception as part of its 'clean-up'. *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
LightmapPacker::~LightmapPacker()
{
// For each page...
for (int pageindex = 0; pageindex < PagePtrs.Count(); pageindex++) {
delete PagePtrs [pageindex];
}
delete PlaceholderTextureNameNodePtr;
}
/***********************************************************************************************
* LightmapPacker::Submit -- Submit a triangular region of a lightmap for packing. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Submit (PackingTriangle *principaltriangleptr, const DynamicVectorClass <Triangle> &adjtriangles)
{
const unsigned normalgroupid = 0;
const unsigned placeholdergroupid = 1;
TrianglePacker *trianglepackerptr, *mergedpackerptr;
ASSERT (principaltriangleptr != NULL);
// Is there an associated texture?
if (principaltriangleptr->TextureNameNodePtr == NULL) {
static const float _placeholderuvs [Triangle::VERTICES_COUNT][2] =
{
{0.0f, 0.0f},
{1.0f, 0.0f},
{0.0f, 1.0f},
};
DynamicVectorClass <Triangle> noadjtriangles;
// Substitute a placeholder texture.
principaltriangleptr->TextureNameNodePtr = PlaceholderTextureNameNodePtr;
for (unsigned v = 0; v < Triangle::VERTICES_COUNT; v++) {
principaltriangleptr->Vertices [v].UV.X = _placeholderuvs [v][0];
principaltriangleptr->Vertices [v].UV.Y = _placeholderuvs [v][1];
}
// Create a triangle packer from the modified principal triangle with no adjacent triangles.
// NOTE: Use a 'placeholder group' ID to indicate to the triangle packer that it should not be merged with non-placeholder triangles.
trianglepackerptr = new TrianglePacker (principaltriangleptr, noadjtriangles, placeholdergroupid, EDGE_BLEND_THICKNESS, SampleRate, FillColor);
ASSERT (trianglepackerptr != NULL);
} else {
// Create a triangle packer from the principal and adjacent triangles.
trianglepackerptr = new TrianglePacker (principaltriangleptr, adjtriangles, normalgroupid, EDGE_BLEND_THICKNESS, SampleRate, FillColor);
ASSERT (trianglepackerptr != NULL);
}
while (trianglepackerptr != NULL) {
mergedpackerptr = trianglepackerptr;
trianglepackerptr = Merge (mergedpackerptr);
}
TrianglePackerPtrs.Add (mergedpackerptr);
// Statistics: each submission contains one triangular principal face.
FaceCount++;
}
/***********************************************************************************************
* LightmapPacker::Merge -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
TrianglePacker *LightmapPacker::Merge (TrianglePacker *trianglepackerptr)
{
TrianglePacker *resultantpackerptr;
// See if the triangle packer can be merged with an existing triangle packer.
resultantpackerptr = NULL;
for (int t = 0; t < TrianglePackerPtrs.Count(); t++) {
TrianglePacker *mergedpackerptr;
mergedpackerptr = TrianglePackerPtrs [t]->Merge (*trianglepackerptr);
if (mergedpackerptr != NULL) {
// Does the merged packer fit within the maximum page size?
if ((mergedpackerptr->Width() <= PAGE_WIDTH) && (mergedpackerptr->Height() <= PAGE_HEIGHT)) {
unsigned mergedarea, unmergedarea;
// Is the merged packer smaller in area than the sum of the two unmerged packers?
mergedarea = mergedpackerptr->Width() * mergedpackerptr->Height();
unmergedarea = (trianglepackerptr->Width() * trianglepackerptr->Height()) + (TrianglePackerPtrs [t]->Width() * TrianglePackerPtrs [t]->Height());
if (mergedarea <= unmergedarea) {
bool success;
// Replace the two unmerged packers with the merged packer.
delete trianglepackerptr;
delete TrianglePackerPtrs [t];
success = TrianglePackerPtrs.Delete (t);
ASSERT (success);
resultantpackerptr = mergedpackerptr;
break;
}
}
}
}
return (resultantpackerptr);
}
/***********************************************************************************************
* LightmapPacker::Pack -- Pack all the triangular regions submitted thus far. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/18/00 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Pack (ProceduralTexture *proceduraltexture)
{
TargaLoader targaloader (SampleRate, FillColor);
// For each triangle packer...
for (int t = 0; t < TrianglePackerPtrs.Count(); t++) {
const unsigned rescalethreshold = 16; // When snapping to even dimension, 'round down' if above this size -
// otherwise 'round up'.
TrueColorTarga rasterizedtarga;
unsigned unscaledwidth, unscaledheight;
unsigned clampedwidth, clampedheight;
unsigned padwidth, padheight;
// Rasterize the triangle packer.
TrianglePackerPtrs [t]->Rasterize (targaloader, proceduraltexture, rasterizedtarga);
// Scale targa according to user-specified filter error.
unscaledwidth = rasterizedtarga.Width();
unscaledheight = rasterizedtarga.Height();
rasterizedtarga.Scale (FilterError);
// Scale targa to maximum page size (taking into account edge thickness).
ASSERT (PAGE_WIDTH > (EDGE_BLEND_THICKNESS * 2));
ASSERT (PAGE_HEIGHT > (EDGE_BLEND_THICKNESS * 2));
clampedwidth = MIN (PAGE_WIDTH - (EDGE_BLEND_THICKNESS * 2), rasterizedtarga.Width());
clampedheight = MIN (PAGE_HEIGHT - (EDGE_BLEND_THICKNESS * 2), rasterizedtarga.Height());
if ((clampedwidth < rasterizedtarga.Width()) || (clampedheight < rasterizedtarga.Height())) {
// Statistics: update no. of oversize targas.
OversizeCount++;
}
// Snap to even dimensions.
// NOTE: This will reduce texel bleeding as a side effect of mip-mapping or rendering at reduced resolutions.
ASSERT (rescalethreshold < PAGE_WIDTH);
ASSERT (rescalethreshold < PAGE_HEIGHT);
if (clampedwidth > rescalethreshold) {
clampedwidth -= (clampedwidth & 0x1);
} else {
clampedwidth += (clampedwidth & 0x1);
}
if (clampedheight > rescalethreshold) {
clampedheight -= (clampedheight & 0x1);
} else {
clampedheight += (clampedheight & 0x1);
}
if (clampedwidth != rasterizedtarga.Width() || clampedheight != rasterizedtarga.Height()) {
rasterizedtarga.Scale (clampedwidth, clampedheight);
}
// Add pad texels to compensate for edge texels that may have been lost due to filtered scaling and scaling to clamp to maximum page size.
// NOTE: Pad to a multiple of 2 to maintain 'even' dimensions.
padwidth = 2 * ceilf (EDGE_BLEND_THICKNESS * (1.0f - (((float) rasterizedtarga.Width()) / ((float) unscaledwidth))));
padheight = 2 * ceilf (EDGE_BLEND_THICKNESS * (1.0f - (((float) rasterizedtarga.Height()) / ((float) unscaledheight))));
rasterizedtarga.Pad (padwidth, padheight, FillColor, TrianglePackerPtrs [t]->Principal_Triangles());
// Pack the targa into a lightmap page.
Pack (rasterizedtarga, TrianglePackerPtrs [t]->Principal_Triangles());
// Statistics: add targa's area before and after scaling.
// NOTE: Although this is not desirable, it is possible for scaling to increase the targa's area.
UnscaledTexelCount += unscaledwidth * unscaledheight;
ScaledTexelCount += rasterizedtarga.Width() * rasterizedtarga.Height();
// Update statistics about the triangle packer.
Update_Statistics (*TrianglePackerPtrs [t]);
// Clean-up.
delete TrianglePackerPtrs [t];
}
// Reset.
TrianglePackerPtrs.Clear();
}
/***********************************************************************************************
* TargaLoader::TargaLoader -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/30/00 IML : Created. *
*=============================================================================================*/
TargaLoader::TargaLoader (float samplerate, W3dRGBStruct fillcolor)
{
const unsigned cachesize = 1024; // Maximum no. of entries in cache.
const unsigned cleanuplistsize = 256; // Initial size of clean-up list.
SampleRate = samplerate;
FillColor = fillcolor;
// Size the cache and the clean-up list.
Cache.Resize (cachesize);
CleanupList.Set_Growth_Step (cleanuplistsize);
}
/***********************************************************************************************
* TargaLoader::~TargaLoader -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/30/00 IML : Created. *
*=============================================================================================*/
TargaLoader::~TargaLoader()
{
// Delete the targas in the clean-up list.
for (int c = 0; c < CleanupList.Count(); c++) {
ASSERT (CleanupList [c] != NULL);
delete CleanupList [c];
}
}
/***********************************************************************************************
* TargaLoader::Load -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/08/00 IML : Created. *
*=============================================================================================*/
TrueColorTarga *TargaLoader::Load (const Triangle &triangle)
{
TrueColorTarga *targaptr;
unsigned index;
index = Index (triangle.TextureID);
// Is there a cache miss?
if ((Cache [index].Ptr == NULL) || (Cache [index].ID != triangle.TextureID)) {
char *errormessage;
TextureNameNode *pathnamenodeptr;
Vector2 t;
Vector3 p;
float length, localsamplerate, scalefactor;
// There must be a texture pathname.
pathnamenodeptr = triangle.TextureNameNodePtr;
ASSERT (pathnamenodeptr != NULL);
targaptr = new TrueColorTarga;
ASSERT (targaptr != NULL);
// Read the first targa asset file. Report any error.
errormessage = targaptr->Load (pathnamenodeptr->TextureName);
if (errormessage != NULL) throw (errormessage);
// Replace fill texel with padding. If the targa is all fill color substitute the replacement targa.
targaptr->Fill (FillColor);
// Read all remaining targa asset files and add them to the first.
pathnamenodeptr = pathnamenodeptr->Next;
while (pathnamenodeptr != NULL) {
TrueColorTarga additivetarga;
errormessage = additivetarga.Load (pathnamenodeptr->TextureName);
if (errormessage != NULL) throw (errormessage);
// Replace fill texel with padding.
additivetarga.Fill (FillColor);
targaptr->Add (additivetarga);
// Advance to next targa asset.
pathnamenodeptr = pathnamenodeptr->Next;
}
// Calculate the sample rate of the targa (no. of texels per unit length).
// NOTE: A typical length unit is metres (it does not matter which it is).
t = triangle.Vertices [1].UV - triangle.Vertices [0].UV;
t.Scale (targaptr->Width(), targaptr->Height());
p = triangle.Vertices [1].Point - triangle.Vertices [0].Point;
length = p.Length();
// Degenerate triangles are not allowed!
ASSERT (length > 0.0f);
localsamplerate = t.Length() / length;
// In the event that the targa is sampled, maximize sampling accuracy by scaling
// to match the global sample rate.
// NOTE: There is no advantage to scaling-up.
scalefactor = SampleRate / localsamplerate;
if (scalefactor < 1.0f) {
targaptr->Scale (targaptr->Width() * scalefactor, targaptr->Height() * scalefactor);
}
// Add the existing targa to the clean-up list for removal later (in the destructor).
// NOTE: It cannot be deleted yet because another object may be referencing it.
if (Cache [index].Ptr != NULL) {
CleanupList.Add (Cache [index].Ptr);
}
// Update the cache entry.
Cache [index].ID = triangle.TextureID;
Cache [index].Ptr = targaptr;
} else {
// Cache hit.
targaptr = Cache [index].Ptr;
}
return (targaptr);
}
/***********************************************************************************************
* LightmapPacker::Pack -- Pack a targa into a lightmap page. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/19/00 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Pack (TrueColorTarga &targa, DynamicVectorClass <PackingTriangle*> &triangleptrs)
{
const float packingerror = FilterError * 0.5f;
bool packed;
// Attempt to pack the lightmap in the current page.
if (CurrentPageIndex != -1) {
packed = PagePtrs [CurrentPageIndex]->Pack (targa, packingerror, triangleptrs);
} else {
packed = false;
}
if (!packed) {
const unsigned packingwindowsize = PACKING_BYTES / ((PAGE_WIDTH * PAGE_HEIGHT * PageBitDepth) / CHAR_BIT);
int firstpage, lastpage;
// Iterate over all existing pages (except the current page) that are in the packing window.
firstpage = PagePtrs.Count() - 1;
lastpage = MAX (firstpage - ((int) packingwindowsize) + 1, 0);
for (int pageindex = firstpage; pageindex >= lastpage; pageindex--) {
if (pageindex != CurrentPageIndex) {
packed = PagePtrs [pageindex]->Pack (targa, packingerror, triangleptrs);
if (packed) {
CurrentPageIndex = pageindex;
break;
}
}
}
// If still not packed then create a new page.
if (!packed) {
Page *pageptr;
pageptr = new Page (PageBitDepth, FillColor);
ASSERT (pageptr != NULL);
PagePtrs.Add (pageptr);
packed = pageptr->Pack (targa, packingerror, triangleptrs);
// The first lightmap packed into a page must always succeed.
ASSERT (packed);
CurrentPageIndex = PagePtrs.Count() - 1;
}
// The current page index has changed so this will incur a texture swap.
TextureSwapCount++;
}
LightmapCount++;
// Store the page index in the triangles.
for (int t = 0; t < triangleptrs.Count(); t++) {
triangleptrs [t]->PackedTextureID = CurrentPageIndex;
}
}
/***********************************************************************************************
* LightmapPacker::Lightmap_Pathname -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
const char *LightmapPacker::Lightmap_Pathname (unsigned pageindex)
{
static char _pathname [_MAX_PATH];
char filename [_MAX_FNAME];
// Build pathname of lightmap based on lightmap's index.
strcpy (_pathname, Asset_Directory());
_ultoa (_BasePageIndex + pageindex, filename, 10);
strcat (filename, ASSET_EXTENSION);
strcat (_pathname, filename);
return (_pathname);
}
/***********************************************************************************************
* LightmapPacker::Asset_Directory -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/15/00 IML : Created. *
*=============================================================================================*/
const char *LightmapPacker::Asset_Directory()
{
static bool _called = false;
static char _assetdirectory [_MAX_PATH];
// Optimization: only required to build asset directory once because it is invariant over
// lifetime of application.
if (!_called) {
strcpy (_assetdirectory, TEMPORARY_DIRECTORY_NAME);
strcat (_assetdirectory, theApp.Instance_Name());
strcat (_assetdirectory, "\\");
_called = true;
}
return (_assetdirectory);
}
/***********************************************************************************************
* LightmapPacker::Asset_Directory -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
const char *LightmapPacker::Asset_Directory (const char *filename)
{
static char _pathname [_MAX_PATH];
strcpy (_pathname, filename);
strcat (_pathname, "+\\");
return (_pathname);
}
/***********************************************************************************************
* LightmapPacker::Delete_Assets -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Delete_Assets()
{
char pathname [_MAX_PATH];
long handle;
_finddata_t fileinfo;
// Attempt to delete every file in the asset directory.
strcpy (pathname, theApp.Working_Path());
strcat (pathname, Asset_Directory());
strcat (pathname, "*");
strcat (pathname, ASSET_EXTENSION);
handle = _findfirst (pathname, &fileinfo);
if (handle != -1) {
do {
strcpy (pathname, theApp.Working_Path());
strcat (pathname, Asset_Directory());
strcat (pathname, fileinfo.name);
DeleteFile (pathname);
} while (_findnext (handle, &fileinfo) == 0);
}
// Clean-up.
_findclose (handle);
// Attempt to remove the asset directory.
strcpy (pathname, theApp.Working_Path());
strcat (pathname, Asset_Directory());
_rmdir (pathname);
}
/***********************************************************************************************
* LightmapPacker::Copy_Assets -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Copy_Assets (const char *pathname)
{
char savedrivename [_MAX_DRIVE];
char savedirectoryname [_MAX_DIR];
char savefilename [_MAX_FNAME];
char loadpathname [_MAX_PATH];
char savepath [_MAX_PATH];
char savepathname [_MAX_PATH];
long handle;
_finddata_t fileinfo;
// Attempt to copy every file in the load directory to the save directory.
strcpy (loadpathname, theApp.Working_Path());
strcat (loadpathname, Asset_Directory());
strcat (loadpathname, "*");
strcat (loadpathname, ASSET_EXTENSION);
handle = _findfirst (loadpathname, &fileinfo);
if (handle != -1) {
// Now we know that there are assets so create the save directory.
_splitpath (pathname, savedrivename, savedirectoryname, savefilename, NULL);
strcpy (savepath, savedrivename);
strcat (savepath, savedirectoryname);
strcat (savepath, Asset_Directory (savefilename));
_mkdir (savepath);
do {
strcpy (loadpathname, theApp.Working_Path());
strcat (loadpathname, Asset_Directory());
strcat (loadpathname, fileinfo.name);
strcpy (savepathname, savepath);
strcat (savepathname, fileinfo.name);
CopyFile (loadpathname, savepathname, FALSE);
} while (_findnext (handle, &fileinfo) == 0);
}
// Clean-up.
_findclose (handle);
}
/***********************************************************************************************
* LightmapPacker::Reset_Statistics -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Reset_Statistics()
{
FaceCount = 0;
LightmapCount = 0;
AdjacentFaceCount = 0;
BlendedFaceCount = 0;
EdgeBlendAreaSum = 0.0;
UnscaledTexelCount = 0;
ScaledTexelCount = 0;
ReplicaEfficiencySum = 0.0;
PackingEfficiencySum = 0.0;
TextureSwapCount = 0;
OversizeCount = 0;
AllFillColorCount = 0;
}
/***********************************************************************************************
* LightmapPacker::Update_Statistics -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Update_Statistics (const Page &page)
{
PackingEfficiencySum += page.Packing_Efficiency();
ReplicaEfficiencySum += page.Replica_Efficiency();
}
/***********************************************************************************************
* LightmapPacker::Update_Statistics -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/31/00 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Update_Statistics (const TrianglePacker &trianglepacker)
{
AdjacentFaceCount += trianglepacker.Adjacent_Face_Count();
BlendedFaceCount += trianglepacker.Blended_Face_Count();
EdgeBlendAreaSum += trianglepacker.Edge_Blend_Area();
}
/***********************************************************************************************
* LightmapPacker::Collate_Statistics -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
void LightmapPacker::Collate_Statistics()
{
unsigned pagecount;
float adjacentfaceblendfraction, edgeblendefficiency, scalingefficiency, packingefficiency, cullingefficiency, swappingefficiency;
// Build statistics string.
adjacentfaceblendfraction = (AdjacentFaceCount > 0) ? ((float) BlendedFaceCount) / ((float) AdjacentFaceCount) : 0.0f;
edgeblendefficiency = (UnscaledTexelCount > 0) ? 1.0f - (EdgeBlendAreaSum / UnscaledTexelCount) : 0.0f;
scalingefficiency = (UnscaledTexelCount > 0) ? 1.0f - (((float) ScaledTexelCount) / ((float) UnscaledTexelCount)) : 0.0f;
pagecount = PagePtrs.Count();
packingefficiency = (pagecount > 0) ? PackingEfficiencySum / pagecount : 0.0f;
cullingefficiency = (pagecount > 0) ? ReplicaEfficiencySum / pagecount : 0.0f;
swappingefficiency = (FaceCount > 0) ? 1.0f - (((float)TextureSwapCount) / FaceCount) : 0.0f;
_snprintf (_Statistics [STATISTICS_PAGE_FORMAT], sizeof (_Statistics [STATISTICS_PAGE_FORMAT]) - 1, "%d x %d x %dbpp", PAGE_WIDTH, PAGE_HEIGHT, PageBitDepth);
_snprintf (_Statistics [STATISTICS_LIGHTMAPS_PROCESSED], sizeof (_Statistics [STATISTICS_LIGHTMAPS_PROCESSED]) - 1, "%d", LightmapCount);
_snprintf (_Statistics [STATISTICS_ADJACENT_FACE_BLEND_PERCENTAGE], sizeof (_Statistics [STATISTICS_ADJACENT_FACE_BLEND_PERCENTAGE]) - 1, "%.1f%%", adjacentfaceblendfraction * 100.0f);
_snprintf (_Statistics [STATISTICS_EDGE_BLEND_EFFICIENCY], sizeof (_Statistics [STATISTICS_EDGE_BLEND_EFFICIENCY]) - 1, "%.1f%%", edgeblendefficiency * 100.0f);
_snprintf (_Statistics [STATISTICS_SCALING_EFFICIENCY], sizeof (_Statistics [STATISTICS_SCALING_EFFICIENCY]) - 1, "%.1f%%", scalingefficiency * 100.0f);
_snprintf (_Statistics [STATISTICS_PAGES_CREATED], sizeof (_Statistics [STATISTICS_PAGES_CREATED]) - 1, "%d", pagecount);
_snprintf (_Statistics [STATISTICS_PACKING_EFFICIENCY], sizeof (_Statistics [STATISTICS_PACKING_EFFICIENCY]) - 1, "%.1f%%", packingefficiency * 100.0f);
_snprintf (_Statistics [STATISTICS_CULLING_EFFICIENCY], sizeof (_Statistics [STATISTICS_CULLING_EFFICIENCY]) - 1, "%.1f%%", cullingefficiency * 100.0f);
_snprintf (_Statistics [STATISTICS_TEXTURE_SWAP_EFFICIENCY], sizeof (_Statistics [STATISTICS_TEXTURE_SWAP_EFFICIENCY]) - 1, "%.1f%%", swappingefficiency * 100.0f);
_snprintf (_Statistics [STATISTICS_OVERSIZE_LIGHTMAPS], sizeof (_Statistics [STATISTICS_TEXTURE_SWAP_EFFICIENCY]) - 1, "%d", OversizeCount);
}
/***********************************************************************************************
* TrianglePacker::TrianglePacker -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
TrianglePacker::TrianglePacker (const PackingTriangle *principaltriangleptr, const DynamicVectorClass <Triangle> &adjtriangles, unsigned groupid, unsigned edgeblendthickness, float samplerate, W3dRGBStruct fillcolor)
{
Projection = Get_Projection (principaltriangleptr->Normal);
GroupID = groupid;
EdgeBlendThickness = edgeblendthickness;
SampleRate = samplerate;
FillColor = fillcolor;
// Add the principal triangle.
PrincipalTriangles.Add ((PackingTriangle*) principaltriangleptr);
// Add the adjacent triangles, sorting them into the three groups COMMON, VALID & NONE.
for (int a = 0; a < adjtriangles.Count(); a++) {
if (Get_Projection (adjtriangles [a].Normal) == Projection) {
AdjacentTriangles [ADJACENT_PROJECTION_COMMON].Add (adjtriangles [a]);
} else {
if (Can_Project (adjtriangles [a].Normal)) {
AdjacentTriangles [ADJACENT_PROJECTION_VALID].Add (adjtriangles [a]);
} else {
AdjacentTriangles [ADJACENT_PROJECTION_NONE].Add (adjtriangles [a]);
}
}
}
// The triangle sets have been modifed - therefore set bounds.
Set_Bounds();
}
/***********************************************************************************************
* TrianglePacker::TrianglePacker -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
TrianglePacker::TrianglePacker (const TrianglePacker &trianglepacker)
{
int t, a;
Projection = trianglepacker.Projection;
GroupID = trianglepacker.GroupID;
EdgeBlendThickness = trianglepacker.EdgeBlendThickness;
SampleRate = trianglepacker.SampleRate;
FillColor = trianglepacker.FillColor;
LowerBound = trianglepacker.LowerBound;
UpperBound = trianglepacker.UpperBound;
// Copy the principal triangles.
for (t = 0; t < trianglepacker.PrincipalTriangles.Count(); t++) {
PrincipalTriangles.Add (trianglepacker.PrincipalTriangles [t]);
}
// Copy the adjacent triangles.
for (a = 0; a < ADJACENT_PROJECTION_COUNT; a++) {
for (t = 0; t < trianglepacker.AdjacentTriangles [a].Count(); t++) {
AdjacentTriangles [a].Add (trianglepacker.AdjacentTriangles [a] [t]);
}
}
}
/***********************************************************************************************
* TrianglePacker::Reset -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
void TrianglePacker::Set_Bounds()
{
const Vector2 edge (EdgeBlendThickness, EdgeBlendThickness);
Vector2 lowerbound (+FLT_MAX, +FLT_MAX);
Vector2 upperbound (-FLT_MAX, -FLT_MAX);
// Calculate the smallest bounding rectangle that will contain the projected principal triangles.
for (int t = 0; t < PrincipalTriangles.Count(); t++) {
for (int v = 0; v < Triangle::VERTICES_COUNT; v++) {
Vector2 uv;
uv = Project (PrincipalTriangles [t]->Vertices [v].Point);
lowerbound.Update_Min (uv);
upperbound.Update_Max (uv);
}
}
// Increase the bounds by the edge thickness to account for edge blending and snap to integer coordinates.
lowerbound -= edge;
upperbound += edge;
LowerBound.Set (floorf (lowerbound.U), floorf (lowerbound.V));
UpperBound.Set (ceilf (upperbound.U), ceilf (upperbound.V));
}
/***********************************************************************************************
* TrianglePacker::Edge_Blend_Area -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
float TrianglePacker::Edge_Blend_Area() const
{
float principalareasum;
unsigned width, height;
principalareasum = 0.0f;
for (int t = 0; t < PrincipalTriangles.Count(); t++) {
Vector2 o, a, b;
o = Project (PrincipalTriangles [t]->Vertices [0].Point);
a = Project (PrincipalTriangles [t]->Vertices [1].Point) - o;
b = Project (PrincipalTriangles [t]->Vertices [2].Point) - o;
principalareasum += 0.5f * WWMath::Fabs (a.U * b.V - b.U * a.V);
}
width = Width();
height = Height();
ASSERT ((width * height) >= principalareasum);
return ((width * height) - principalareasum);
}
/***********************************************************************************************
* TrianglePacker::Merge -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 07/28/00 IML : Created. *
*=============================================================================================*/
TrianglePacker *TrianglePacker::Merge (const TrianglePacker &trianglepacker)
{
TrianglePacker *mergedpackerptr;
mergedpackerptr = NULL;
// Do both triangle packers have the same projection, group ID, edge blend thickness, sample rate, and fill color?
// If not, then they are not compatible packers and cannot be merged.
if ((Projection == trianglepacker.Projection) &&
(GroupID == trianglepacker.GroupID) &&
(EdgeBlendThickness == trianglepacker.EdgeBlendThickness) &&
(SampleRate == trianglepacker.SampleRate) &&
(FillColor == trianglepacker.FillColor)) {
// For each principal triangle in triangle packer...
for (int p = 0; p < trianglepacker.PrincipalTriangles.Count(); p++) {
// For each principal triangle in this...
for (int q = 0; q < PrincipalTriangles.Count(); q++) {
// Does one principal triangle abut the other?
if (trianglepacker.PrincipalTriangles [p]->Abuts (*PrincipalTriangles [q])) {
// Copy this.
mergedpackerptr = new TrianglePacker (*this);
ASSERT (mergedpackerptr != NULL);
break;
}
}
if (mergedpackerptr != NULL) break;
}
if (mergedpackerptr != NULL) {
// For each principal triangle in triangle packer...
for (int p = 0; p < trianglepacker.PrincipalTriangles.Count(); p++) {
// Add it to the merged packer.
mergedpackerptr->PrincipalTriangles.Add (trianglepacker.PrincipalTriangles [p]);
}
// Merge the adjacent triangles (do not allow duplicates).
for (int a = 0; a < ADJACENT_PROJECTION_COUNT; a++) {
for (int t = 0; t < trianglepacker.AdjacentTriangles [a].Count(); t++) {
const Triangle *triangleptr;
bool foundadjacent;
triangleptr = &(trianglepacker.AdjacentTriangles [a][t]);
foundadjacent = false;
for (int m = 0; m < AdjacentTriangles [a].Count(); m++) {
if (triangleptr->Is_Equivalent ((AdjacentTriangles [a]) [m])) {
foundadjacent = true;
break;
}
}
if (!foundadjacent) {
mergedpackerptr->AdjacentTriangles [a].Add ((trianglepacker.AdjacentTriangles [a]) [t]);
}
}
}
// The triangle sets have been modifed - therefore set bounds.
mergedpackerptr->Set_Bounds();
}
}
return (mergedpackerptr);
}
/***********************************************************************************************
* TrianglePacker::Get_Projection -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/19/00 IML : Created. *
*=============================================================================================*/
TrianglePacker::ProjectionEnum TrianglePacker::Get_Projection (const Vector3 &normal)
{
float x, y, z;
x = WWMath::Fabs (normal.X);
y = WWMath::Fabs (normal.Y);
z = WWMath::Fabs (normal.Z);
if (y >= x) {
if (y >= z) {
if (normal.Y >= 0.0f) {
return (PROJECTION_Y_POSITIVE);
} else {
return (PROJECTION_Y_NEGATIVE);
}
} else {
if (normal.Z >= 0.0f) {
return (PROJECTION_Z_POSITIVE);
} else {
return (PROJECTION_Z_NEGATIVE);
}
}
} else {
if (x >= z) {
if (normal.X >= 0.0f) {
return (PROJECTION_X_POSITIVE);
} else {
return (PROJECTION_X_NEGATIVE);
}
} else {
if (normal.Z >= 0.0f) {
return (PROJECTION_Z_POSITIVE);
} else {
return (PROJECTION_Z_NEGATIVE);
}
}
}
}
/***********************************************************************************************
* TrianglePacker::Can_Project -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/19/00 IML : Created. *
*=============================================================================================*/
bool TrianglePacker::Can_Project (const Vector3 &normal)
{
static const float _projectionnormals [PROJECTION_COUNT][3] =
{{ 0.0f, 1.0f, 0.0f},
{ 0.0f, -1.0f, 0.0f},
{ 0.0f, 0.0f, 1.0f},
{ 0.0f, 0.0f, -1.0f},
{ 1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f}
};
const float maxprojectionangle = (80.0f * WWMATH_PI) / 180.0f; // Max. allowed angle between triangle and projection plane.
Vector3 projectionnormal;
float angle;
projectionnormal.Set (_projectionnormals [Projection][0], _projectionnormals [Projection][1], _projectionnormals [Projection][2]);
angle = acosf (Vector3::Dot_Product (normal, projectionnormal));
return (angle < maxprojectionangle);
}
/***********************************************************************************************
* TrianglePacker::Project -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/19/00 IML : Created. *
*=============================================================================================*/
Vector2 TrianglePacker::Project (const Vector3 &point) const
{
Vector2 t;
switch (Projection) {
case PROJECTION_Y_POSITIVE:
case PROJECTION_Y_NEGATIVE:
t.Set (point.X, point.Z);
break;
case PROJECTION_Z_POSITIVE:
case PROJECTION_Z_NEGATIVE:
t.Set (point.X, point.Y);
break;
case PROJECTION_X_POSITIVE:
case PROJECTION_X_NEGATIVE:
t.Set (point.Y, point.Z);
break;
}
t *= SampleRate;
return (t);
}
/***********************************************************************************************
* TrianglePacker::Rasterize -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/08/00 IML : Created. *
*=============================================================================================*/
void TrianglePacker::Rasterize (TargaLoader &targaloader, ProceduralTexture *proceduraltexture, TrueColorTarga &rasterizedtarga)
{
const unsigned pixeldepth = 24;
const unsigned sqrtforwardsamplespertexel = 4;
const unsigned sqrtbackwardsamplespertexel = 4;
const unsigned principalpriority = 2;
const unsigned adjacentpriority = 1;
// There must be at least one principal triangle to rasterize.
ASSERT (PrincipalTriangles.Count() >= 1);
unsigned width, height;
SampleSurface *forwardsurfaceptr, *backwardsurfaceptr;
int t;
unsigned projectiontrianglecount;
float right, top;
ProjectionTriangle *projectiontriangles;
Vector3 points [Triangle::VERTICES_COUNT];
Vector2 sourceuvs [Triangle::VERTICES_COUNT];
Vector2 projectionuvs [Triangle::VERTICES_COUNT];
TrueColorTarga *sourcetargaptr;
float oosqrtsamplespertexel;
Vector2 samplepoint;
int sampledt;
unsigned x, y, u, v;
float oowidth, ooheight;
width = Width();
height = Height();
// Allocate a sample surface to store destination->source (backward) samples.
backwardsurfaceptr = new SampleSurface (width, height, proceduraltexture);
ASSERT (backwardsurfaceptr != NULL);
// Allocate enough projection triangles to cover the principal triangles and those triangles that can be projected.
projectiontriangles = new ProjectionTriangle [PrincipalTriangles.Count() + AdjacentTriangles [ADJACENT_PROJECTION_COMMON].Count() + AdjacentTriangles [ADJACENT_PROJECTION_VALID].Count()];
ASSERT (projectiontriangles != NULL);
// For each principal triangle...
projectiontrianglecount = PrincipalTriangles.Count();
for (t = 0; t < (int) projectiontrianglecount; t++) {
for (int v = 0; v < Triangle::VERTICES_COUNT; v++) {
points [v] = PrincipalTriangles [t]->Vertices [v].Point;
sourceuvs [v] = PrincipalTriangles [t]->Vertices [v].UV;
projectionuvs [v] = Project (PrincipalTriangles [t]->Vertices [v].Point) - LowerBound;
}
sourcetargaptr = targaloader.Load (*PrincipalTriangles [t]);
projectiontriangles [t] = ProjectionTriangle (points, sourceuvs, sourcetargaptr, projectionuvs);
}
// For each adjacent triangle that is common or valid...
right = width;
top = height;
for (int a = ADJACENT_PROJECTION_COMMON; a <= ADJACENT_PROJECTION_VALID; a++) {
for (t = 0; t < AdjacentTriangles [a].Count(); t++) {
unsigned outcode, outcodeu, outcodev;
// Test the triangle for trivial rejection.
outcode = 0xf;
for (int v = 0; v < Triangle::VERTICES_COUNT; v++) {
projectionuvs [v] = Project (AdjacentTriangles [a] [t].Vertices [v].Point) - LowerBound;
outcodeu = (projectionuvs [v].U < 0.0f) | ((projectionuvs [v].U >= right) << 1);
outcodev = (projectionuvs [v].V < 0.0f) | ((projectionuvs [v].V >= top) << 1);
outcode &= (outcodeu | (outcodev << 2));
}
// Should the triangle be accepted (ie. not trivially rejected)?
if (outcode == 0) {
for (int v = 0; v < Triangle::VERTICES_COUNT; v++) {
points [v] = AdjacentTriangles [a] [t].Vertices [v].Point;
sourceuvs [v] = AdjacentTriangles [a] [t].Vertices [v].UV;
}
sourcetargaptr = targaloader.Load (AdjacentTriangles [a] [t]);
projectiontriangles [projectiontrianglecount] = ProjectionTriangle (points, sourceuvs, sourcetargaptr, projectionuvs);
projectiontrianglecount++;
}
}
}
// Sample the projection triangles into the backward surface.
oosqrtsamplespertexel = 1.0f / sqrtbackwardsamplespertexel;
sampledt = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
for (v = 0; v < sqrtbackwardsamplespertexel; v++) {
samplepoint.V = y + (v * oosqrtsamplespertexel);
for (u = 0; u < sqrtbackwardsamplespertexel; u++) {
samplepoint.U = x + (u * oosqrtsamplespertexel);
if (sampledt < PrincipalTriangles.Count()) {
if (backwardsurfaceptr->Sample (samplepoint, projectiontriangles [sampledt], principalpriority)) goto nextsample;
}
for (t = 0; t < PrincipalTriangles.Count(); t++) {
if (t != sampledt) {
if (backwardsurfaceptr->Sample (samplepoint, projectiontriangles [t], principalpriority)) {
sampledt = t;
goto nextsample;
}
}
}
if ((sampledt >= PrincipalTriangles.Count()) && (sampledt < (int) projectiontrianglecount)) {
if (backwardsurfaceptr->Sample (samplepoint, projectiontriangles [sampledt], adjacentpriority)) goto nextsample;
}
for (t = PrincipalTriangles.Count(); t < (int) projectiontrianglecount; t++) {
if (t != sampledt) {
if (backwardsurfaceptr->Sample (samplepoint, projectiontriangles [t], adjacentpriority)) {
sampledt = t;
goto nextsample;
}
}
}
nextsample:;
}
}
}
}
// If less than 50% of the texels were sampled in the backward rasterization prepare a forward rasterization.
forwardsurfaceptr = NULL;
if (backwardsurfaceptr->Sampling_Ratio() < 0.5f) {
// Allocate a sample surface to store source->destination (forward) samples.
forwardsurfaceptr = new SampleSurface (width, height, proceduraltexture);
ASSERT (forwardsurfaceptr != NULL);
for (t = 0; t < PrincipalTriangles.Count(); t++) {
Vector2 d, d0, d1, d2;
float projectionarea, maxlength, pseudoarea;
unsigned sqrtsamplecount;
float oosqrtsamplecount;
unsigned a, b;
float alpha, beta;
// Calculate the sample rate based on the triangle's projected area.
// NOTE: If the area is small relative to its longest edge use the edge length instead.
d = projectionuvs [2] - projectionuvs [0];
d0 = projectionuvs [1] - projectionuvs [0];
d1 = projectionuvs [2] - projectionuvs [1];
d2 = projectionuvs [0] - projectionuvs [2];
projectionarea = 0.5f * WWMath::Fabs (d.U * d0.V - d0.U * d.V);
maxlength = (float) WWMath::Sqrt (MAX (MAX (d0.Length2(), d1.Length2()), d2.Length2()));
pseudoarea = MAX (projectionarea, maxlength);
sqrtsamplecount = ceilf (WWMath::Sqrt (sqrtforwardsamplespertexel * sqrtforwardsamplespertexel * pseudoarea));
// Sample the projection triangle into the forward surface.
oosqrtsamplecount = 1.0f / sqrtsamplecount;
for (b = 0; b < sqrtsamplecount; b++) {
beta = b * oosqrtsamplecount;
for (a = 0; a < sqrtsamplecount; a++) {
alpha = a * oosqrtsamplecount;
if (alpha + beta <= 1.0f) {
forwardsurfaceptr->Sample (alpha, beta, projectiontriangles [t], principalpriority);
}
}
}
}
}
// Resize the targa which holds the resulting rasterization.
rasterizedtarga.Reformat (width, height, pixeldepth);
// Iterate over the backward (and possibly forward) surface and translate it into the rasterized targa.
for (y = 0; y < height; y++) {
unsigned miny, maxy, minx, maxx;
bool inrangeofprincipal;
unsigned a, b;
miny = MAX ((int) 0, ((int) y) - ((int) EdgeBlendThickness));
maxy = MIN (height - 1, y + EdgeBlendThickness);
for (x = 0; x < width; x++) {
W3dRGBStruct color;
minx = MAX ((int) 0, ((int) x) - ((int) EdgeBlendThickness));
maxx = MIN (width - 1, x + EdgeBlendThickness);
// Find out if this texel is within edge blend texels of a texel that has a 'principal sample'.
// NOTE: If so then this texel can potentially be read and therefore must contain valid data.
// Otherwise fill color will suffice.
inrangeofprincipal = false;
for (b = miny; b <= maxy; b++) {
for (a = minx; a <= maxx; a++) {
if (backwardsurfaceptr->Priority (a, b) >= principalpriority) {
inrangeofprincipal = true;
break;
}
if (forwardsurfaceptr != NULL) {
if (forwardsurfaceptr->Priority (a, b) >= principalpriority) {
inrangeofprincipal = true;
break;
}
}
}
if (inrangeofprincipal) break;
}
// Is texel within range of 'principal texel'?
if (inrangeofprincipal) {
// Does the backward surface's texel have a sample?
if (backwardsurfaceptr->Get_Color (x, y, color)) {
rasterizedtarga.Set_Color (x, y, color);
} else {
if (forwardsurfaceptr != NULL) {
if (forwardsurfaceptr->Get_Color (x, y, color)) {
rasterizedtarga.Set_Color (x, y, color);
} else {
rasterizedtarga.Set_Color (x, y, FillColor);
}
} else {
rasterizedtarga.Set_Color (x, y, FillColor);
}
}
} else {
rasterizedtarga.Set_Color (x, y, FillColor);
}
}
}
// Pad any fill color texels.
rasterizedtarga.Fill (FillColor);
// Set the packed UV's of each principal triangle to normalized projection UV's.
oowidth = 1.0f / width;
ooheight = 1.0f / height;
for (t = 0; t < PrincipalTriangles.Count(); t++) {
for (unsigned v = 0; v < Triangle::VERTICES_COUNT; v++) {
Vector2 uv;
uv = Project (PrincipalTriangles [t]->Vertices [v].Point) - LowerBound;
uv.Scale (oowidth, ooheight);
PrincipalTriangles [t]->PackedUVs [v] = uv;
}
}
// Clean-up.
if (forwardsurfaceptr != NULL) delete forwardsurfaceptr;
delete [] projectiontriangles;
delete backwardsurfaceptr;
}
/***********************************************************************************************
* ProjectionTriangle::ProjectionTriangle -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/08/00 IML : Created. *
*=============================================================================================*/
TrianglePacker::ProjectionTriangle::ProjectionTriangle (const Vector3 *points, const Vector2 *sourceuvs, const TrueColorTarga *sourcetargaptr, const Vector2 *projectionuvs)
{
Points [0] = points [0];
Points [1] = points [1] - points [0];
Points [2] = points [2] - points [0];
SourceUVs [0] = sourceuvs [0];
SourceUVs [1] = sourceuvs [1] - sourceuvs [0];
SourceUVs [2] = sourceuvs [2] - sourceuvs [0];
ProjectionUVs [0] = projectionuvs [0];
ProjectionUVs [1] = projectionuvs [1] - projectionuvs [0];
ProjectionUVs [2] = projectionuvs [2] - projectionuvs [0];
SourceTargaPtr = (TrueColorTarga*) sourcetargaptr;
}
/***********************************************************************************************
* TrueColorTarga::TrueColorTarga -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
TrueColorTarga::TrueColorTarga()
: Targa()
{
const unsigned width = 1;
const unsigned height = 1;
const unsigned pixeldepth = 24;
// Check that width, height and pixel depth will fit into the Targa internal storage types.
ASSERT (width <= SHRT_MAX);
ASSERT (height <= SHRT_MAX);
ASSERT (pixeldepth <= SCHAR_MAX);
mFlags = TGAF_IMAGE;
Header.ImageType = TGA_TRUECOLOR;
Reformat (width, height, pixeldepth);
}
/***********************************************************************************************
* TrueColorTarga::TrueColorTarga -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
TrueColorTarga::TrueColorTarga (unsigned width, unsigned height, unsigned pixeldepth)
: Targa()
{
// Check that width, height and pixel depth will fit into the Targa internal storage types.
ASSERT (width <= SHRT_MAX);
ASSERT (height <= SHRT_MAX);
ASSERT (pixeldepth <= SCHAR_MAX);
mFlags = TGAF_IMAGE;
Header.ImageType = TGA_TRUECOLOR;
Reformat (width, height, pixeldepth);
}
/***********************************************************************************************
* TrueColorTarga::TrueColorTarga -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
TrueColorTarga::TrueColorTarga (unsigned width, unsigned height, unsigned pixeldepth, const W3dRGBStruct &clearcolor)
: Targa()
{
// Check that width, height and pixel depth will fit into the Targa internal storage types.
ASSERT (width <= SHRT_MAX);
ASSERT (height <= SHRT_MAX);
ASSERT (pixeldepth <= SCHAR_MAX);
mFlags = TGAF_IMAGE;
Header.ImageType = TGA_TRUECOLOR;
Reformat (width, height, pixeldepth, clearcolor);
}
/***********************************************************************************************
* TrueColorTarga::Reformat -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Reformat (unsigned width, unsigned height, unsigned pixeldepth)
{
size_t size;
Header.Width = width;
Header.Height = height;
Header.PixelDepth = pixeldepth;
size = width * height * TGA_BytesPerPixel (pixeldepth);
if (size > 0) {
// NOTE: Use malloc() or realloc() here because destructor will use free().
mImage = (char*) realloc (mImage, size);
ASSERT (mImage != NULL);
}
}
/***********************************************************************************************
* TrueColorTarga::Reformat -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Reformat (unsigned width, unsigned height, unsigned pixeldepth, const W3dRGBStruct &clearcolor)
{
UnpackedTexelStruct cleartexel (clearcolor.R, clearcolor.G, clearcolor.B, 0);
Reformat (width, height, pixeldepth);
Clear (cleartexel);
}
/***********************************************************************************************
* TrueColorTarga::Pixel_Format -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
srPixelConvert::e_surfaceType TrueColorTarga::Pixel_Format (unsigned pixeldepth)
{
// Map a pixel depth (in bits) to a Targa supported pixel format.
switch (pixeldepth) {
case 16:
return (srPixelConvert::ARGB1555);
case 24:
return (srPixelConvert::RGB888);
case 32:
return (srPixelConvert::ARGB8888);
default:
ASSERT (FALSE);
break;
}
ASSERT (FALSE);
return (srPixelConvert::e_surfaceType (0));
}
/***********************************************************************************************
* TrueColorTarga::Clear -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Clear (const TrueColorTarga::UnpackedTexelStruct &cleartexel)
{
const unsigned bytespertexel = TGA_BytesPerPixel (Pixel_Depth());
unsigned char *texelptr;
for (unsigned y = 0; y < Height(); y++) {
texelptr = ((unsigned char*) GetImage()) + (Width() * y * bytespertexel);
for (unsigned x = 0; x < Width(); x++) {
Pack_Texel (cleartexel, texelptr, bytespertexel);
texelptr += bytespertexel;
}
}
}
/***********************************************************************************************
* TrueColorTarga::Load -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
char *TrueColorTarga::Load (const char *pathname)
{
const char *loaderrormessage = "The image file ""%s"" cannot be loaded (load error: %d).\
Please ensure that the image file exists and is in TGA (Truevision TARGA) format.";
const char *formaterrormessage = "The image file ""%s"" is not in a suitable TGA format.\
Please ensure that the image file is in true color format (16, 24 or 32 bit).";
static char _messagebuffer [256];
long error;
bool valid;
StringBuilder errormessage (_messagebuffer, sizeof (_messagebuffer));
error = Targa::Load (pathname, TGAF_IMAGE);
if (error != 0) {
errormessage.Copy (loaderrormessage, pathname, error);
return (errormessage.String());
}
// Check for a specific subset of the available .TGA texel formats which are suitable for
// true color targas (ie. true color, not paletted, not monochrome).
valid = (Header.ImageType == TGA_TRUECOLOR) || (Header.ImageType == TGA_TRUECOLOR_ENCODED);
valid &= (Header.PixelDepth == 16) || (Header.PixelDepth == 24) || (Header.PixelDepth == 32);
if (!valid) {
errormessage.Copy (formaterrormessage, pathname);
return (errormessage.String());
}
// No error message.
return (NULL);
}
/***********************************************************************************************
* TrueColorTarga::Save -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 8/18/99 IML : Created. *
*=============================================================================================*/
char *TrueColorTarga::Save (const char *pathname)
{
const char *saveerrormessage = "Cannot save the image file ""%s"" (save error: %d).";
static char _messagebuffer [256];
long error;
StringBuilder errormessage (_messagebuffer, sizeof (_messagebuffer));
error = Targa::Save (pathname, TGAF_IMAGE);
if (error != 0) {
errormessage.Copy (saveerrormessage, pathname, error);
return (errormessage.String());
}
// No error message.
return (NULL);
}
/***********************************************************************************************
* TrueColorTarga::Blit -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Blit (TrueColorTarga &destination, unsigned x, unsigned y)
{
unsigned sourcebytespertexel, destbytespertexel;
unsigned maxc, maxr;
unsigned char *sourceptr, *destptr;
sourcebytespertexel = TGA_BytesPerPixel (Pixel_Depth());
destbytespertexel = TGA_BytesPerPixel (destination.Pixel_Depth());
// Currently, a true color targa cannot blit to itself.
ASSERT (this != &destination);
ASSERT (GetImage() != NULL);
ASSERT (destination.GetImage() != NULL);
// Sanity checks.
if ((x >= destination.Width()) || (y >= destination.Height())) return;
// Clip to destination.
if (x + Width() <= destination.Width()) {
maxc = Width();
} else {
maxc = destination.Width() - x;
}
if (y + Height() <= destination.Height()) {
maxr = Height();
} else {
maxr = destination.Height() - y;
}
// For each row...
for (unsigned r = 0; r < maxr; r++) {
sourceptr = ((unsigned char*) GetImage()) + (Width() * r * sourcebytespertexel);
destptr = ((unsigned char*) destination.GetImage()) + (((destination.Width() * (y + r)) + x) * destbytespertexel);
// For each column...
for (unsigned c = 0; c < maxc; c++) {
UnpackedTexelStruct unpackedtexel;
Pack_Texel (*Unpack_Texel (sourceptr, sourcebytespertexel, unpackedtexel), destptr, destbytespertexel);
// Advance to next texel.
sourceptr += sourcebytespertexel;
destptr += destbytespertexel;
}
}
}
/***********************************************************************************************
* TrueColorTarga::Scale -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Scale (TrueColorTarga &destination, unsigned width, unsigned height)
{
size_t size, newsize;
ColorSurface surface (Pixel_Format (Pixel_Depth()), Width(), Height());
SRBOOL success;
// Image data must exist.
ASSERT (GetImage() != NULL);
// Currently, source and destination pixel depths must match.
ASSERT (Pixel_Depth() == destination.Pixel_Depth());
// Clamp width, height.
if (width < 1) width = 1;
if (height < 1) height = 1;
// Define highest quality filter.
surface.setFilter (&srBSplineFilter);
// Enable clamping.
surface.setHClampMode (true);
surface.setVClampMode (true);
// Copy the targa source texel data to the Surrender surface.
size = Width() * Height() * TGA_BytesPerPixel (Pixel_Depth());
memcpy (surface.getDataPtr(), GetImage(), size);
// Scale.
success = surface.rescale (width, height);
ASSERT (success);
// Resize the destination to match that of the rescaled Surrender surface.
destination.Reformat (width, height, destination.Pixel_Depth());
// Copy the rescaled Surrender surface to the targa destination.
newsize = width * height * TGA_BytesPerPixel (destination.Pixel_Depth());
memcpy (destination.GetImage(), surface.getDataPtr(), newsize);
}
/***********************************************************************************************
* TrueColorTarga::Scale -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/11/99 IML : Created. *
*=============================================================================================*/
#define AVERAGE(a, b) \
(((a) + (b)) / 2) + (((a) + (b)) % 2)
#define BINARY_CHOP \
if (result == -1) { \
maxdimension = dimension; \
dimension = AVERAGE (dimension, mindimension); \
if (maxdimension == dimension) break; \
} else { \
if (result == 1) { \
mindimension = dimension; \
dimension = AVERAGE (dimension, maxdimension); \
if (mindimension == dimension) break; \
} else { \
break; \
} \
} \
void TrueColorTarga::Scale (TrueColorTarga &destination, float error)
{
const unsigned attemptcount = 8;
unsigned attempt, maxdimension, mindimension, dimension;
int result;
unsigned width, height;
TrueColorTarga *scaledtargaptr;
// Calculate width.
maxdimension = Width();
mindimension = 1;
dimension = AVERAGE (maxdimension, mindimension);
for (attempt = 0; attempt < attemptcount; attempt++) {
scaledtargaptr = new TrueColorTarga (dimension, Height(), Pixel_Depth());
ASSERT (scaledtargaptr != NULL);
Scale (*scaledtargaptr);
result = Compare (*scaledtargaptr, error);
delete scaledtargaptr;
BINARY_CHOP;
}
width = dimension;
// Calculate height.
maxdimension = Height();
mindimension = 1;
dimension = AVERAGE (maxdimension, mindimension);
for (attempt = 0; attempt < attemptcount; attempt++) {
scaledtargaptr = new TrueColorTarga (Width(), dimension, Pixel_Depth());
ASSERT (scaledtargaptr != NULL);
Scale (*scaledtargaptr);
result = Compare (*scaledtargaptr, error);
delete scaledtargaptr;
BINARY_CHOP;
}
height = dimension;
// Scale targa.
Scale (destination, width, height);
}
#undef AVERAGE
#undef BINARY_CHOP
/***********************************************************************************************
* TrueColorTarga::Transpose -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/07/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Transpose (TrueColorTarga &destination)
{
unsigned bytespertexel;
size_t size;
unsigned sourcestride;
unsigned char *sourceptr, *destinationptr, *stagingbuffer;
// Image data must exist.
ASSERT (GetImage() != NULL);
// Currently, source and destination pixel depths must match.
ASSERT (Pixel_Depth() == destination.Pixel_Depth());
bytespertexel = TGA_BytesPerPixel (Pixel_Depth());
size = Width() * Height() * bytespertexel;
stagingbuffer = new unsigned char [size];
ASSERT (stagingbuffer != NULL);
// Write transposed image data to staging buffer.
sourcestride = Width() * bytespertexel;
destinationptr = stagingbuffer;
for (unsigned y = 0; y < Width(); y++) {
sourceptr = ((unsigned char*) GetImage()) + (y * bytespertexel);
for (unsigned x = 0; x < Height(); x++) {
for (unsigned b = 0; b < bytespertexel; b++) {
*destinationptr++ = *(sourceptr + b);
}
sourceptr += sourcestride;
}
}
// Resize the destination to match that of the transposed source.
destination.Reformat (Height(), Width(), destination.Pixel_Depth());
// Copy transposed image data to destination.
memcpy (destination.GetImage(), stagingbuffer, size);
// Clean-up.
delete [] stagingbuffer;
}
/***********************************************************************************************
* TrueColorTarga::Compare -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/11/99 IML : Created. *
*=============================================================================================*/
int TrueColorTarga::Compare (TrueColorTarga &comparison, float epsilon)
{
unsigned bytespertexel, comparisonbytespertexel;
int epsilondelta;
float xscale, yscale;
int result;
unsigned char *imageptr;
ASSERT ((epsilon >= 0.0f) && (epsilon <= 1.0f));
bytespertexel = (size_t) TGA_BytesPerPixel (Pixel_Depth());
comparisonbytespertexel = (size_t) TGA_BytesPerPixel (comparison.Pixel_Depth());
epsilondelta = MAX (0, epsilon * 255.0f);
result = -1;
// Calculate destination:source scaling factors.
xscale = ((float) comparison.Width()) / Width();
yscale = ((float) comparison.Height()) / Height();
// For each texel in this targa measure the delta for the corresponding texel in the comparison targa.
imageptr = (unsigned char*) GetImage();
for (unsigned y = 0; y < Height(); y++) {
unsigned ys;
// NOTE: Position sample point at center of texel.
ys = MIN ((unsigned) ((y + 0.5f) * yscale), (unsigned) comparison.Height() - 1);
for (unsigned x = 0; x < Width(); x++) {
unsigned xs;
unsigned char *comparisonimageptr;
int delta;
UnpackedTexelStruct texel, comparisontexel;
// NOTE: Position sample point at center of texel.
xs = MIN ((unsigned) ((x + 0.5f) * xscale), (unsigned) comparison.Width() - 1);
comparisonimageptr = ((unsigned char*) comparison.GetImage()) + (((ys * comparison.Width()) + xs) * comparisonbytespertexel);
Unpack_Texel (imageptr, bytespertexel, texel);
Unpack_Texel (comparisonimageptr, comparisonbytespertexel, comparisontexel);
delta = abs (((int) texel.Byte [0]) - ((int) comparisontexel.Byte [0]));
for (unsigned b = 1; b < sizeof (texel.Byte); b++) {
delta = MAX (delta, abs (((int) texel.Byte [b]) - ((int) comparisontexel.Byte [b])));
}
if (delta > epsilondelta) {
// Return result.
return (1);
}
if (delta == epsilondelta) {
result = 0;
}
// Advance.
imageptr += bytespertexel;
}
}
// Return result.
return (result);
}
/***********************************************************************************************
* TrueColorTarga::Compare -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/11/99 IML : Created. *
*=============================================================================================*/
int TrueColorTarga::Compare (TrueColorTarga &comparison, unsigned x, unsigned y, float epsilon)
{
unsigned bytespertexel, comparisonbytespertexel;
int epsilondelta;
int result;
unsigned char *imageptr, *comparisonptr;
ASSERT ((epsilon >= 0.0f) && (epsilon <= 1.0f));
bytespertexel = (size_t) TGA_BytesPerPixel (Pixel_Depth());
comparisonbytespertexel = (size_t) TGA_BytesPerPixel (comparison.Pixel_Depth());
epsilondelta = MAX (0, epsilon * 255.0f);
result = -1;
// This targa must be contained by comparison targa.
if (x + Width() >= comparison.Width()) return (1);
if (y + Height() >= comparison.Height()) return (1);
// For each row...
for (unsigned r = 0; r < Height(); r++) {
imageptr = ((unsigned char*) GetImage()) + (Width() * r * bytespertexel);
comparisonptr = ((unsigned char*) comparison.GetImage()) + (((comparison.Width() * (y + r)) + x) * comparisonbytespertexel);
// For each column...
for (unsigned c = 0; c < Width(); c++) {
UnpackedTexelStruct texel, comparisontexel;
int delta;
Unpack_Texel (imageptr, bytespertexel, texel);
Unpack_Texel (comparisonptr, comparisonbytespertexel, comparisontexel);
delta = abs (((int) texel.Byte [0]) - ((int) comparisontexel.Byte [0]));
for (unsigned b = 1; b < sizeof (texel.Byte); b++) {
delta = MAX (delta, abs (((int) texel.Byte [b]) - ((int) comparisontexel.Byte [b])));
}
if (delta > epsilondelta) {
// Return result.
return (1);
}
if (delta == epsilondelta) {
result = 0;
}
// Advance.
imageptr += bytespertexel;
comparisonptr += comparisonbytespertexel;
}
}
// Return result.
return (result);
}
/***********************************************************************************************
* TrueColorTarga::Fill -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
bool TrueColorTarga::Fill (const W3dRGBStruct &fillcolor)
{
const unsigned growthstep = 2048;
const UnpackedTexelStruct filltexel (fillcolor.R, fillcolor.G, fillcolor.B, 0);
DynamicVectorClass <PointStruct> *fourconnectedarrays;
unsigned index;
bool allfillcolor;
// Allocate dynamic arrays and initialize.
fourconnectedarrays = new DynamicVectorClass <PointStruct> [2];
ASSERT (fourconnectedarrays != NULL);
fourconnectedarrays [0].Set_Growth_Step (growthstep);
fourconnectedarrays [1].Set_Growth_Step (growthstep);
// Step thru all texels and pad all existing four-connected texels, and add new four-connected texel coordinates to dynamic array.
index = 0;
allfillcolor = true;
for (unsigned y = 0; y < Height(); y++) {
for (unsigned x = 0; x < Width(); x++) {
allfillcolor &= Fill_Four_Connected (x, y, filltexel, fourconnectedarrays [index]);
}
}
if (!allfillcolor) {
// While there are still unpadded texels...
while (fourconnectedarrays [index].Count() > 0) {
// For each four connected texel...
for (int e = 0; e < fourconnectedarrays [index].Count(); e++) {
Fill_Four_Connected ((fourconnectedarrays [index])[e].X, (fourconnectedarrays [index])[e].Y, filltexel, fourconnectedarrays [index ^ 1]);
}
fourconnectedarrays [index].Clear();
// Toggle dynamic array in which to place new four-connected texel coordinates.
index ^= 1;
}
}
// Clean-up.
delete [] fourconnectedarrays;
return (allfillcolor);
}
/***********************************************************************************************
* TrueColorTarga::Fill_Four_Connected -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
bool TrueColorTarga::Fill_Four_Connected (unsigned x, unsigned y, const UnpackedTexelStruct &filltexel, DynamicVectorClass <PointStruct> &fourconnectedarray)
{
const unsigned fourconnectedcount = 4;
const unsigned bytespertexel = TGA_BytesPerPixel (Pixel_Depth());
static const int offsetx [fourconnectedcount] = {-1, +1, 0, 0};
static const int offsety [fourconnectedcount] = { 0, 0, -1, +1};
UnpackedTexelStruct unpackedtexel;
unsigned char *texelptr;
// Is texel (x, y) the fill color (ie. not already filled)?
texelptr = Get_Texel (x, y);
ASSERT (texelptr != NULL);
Unpack_Texel (texelptr, bytespertexel, unpackedtexel);
if (unpackedtexel == filltexel) {
unsigned adj;
bool fourconnected [fourconnectedcount];
unsigned fillcount, r, g, b;
// Initialize.
fillcount = 0;
r = g = b = 0;
// Fill the texel by averaging its non-fill color four-connected neighbors.
for (adj = 0; adj < fourconnectedcount; adj++) {
int adjx, adjy;
unsigned char *adjtexelptr;
fourconnected [adj] = false;
adjx = ((int) x) + offsetx [adj];
adjy = ((int) y) + offsety [adj];
adjtexelptr = Get_Texel (adjx, adjy);
if (adjtexelptr != NULL) {
Unpack_Texel (adjtexelptr, bytespertexel, unpackedtexel);
if (unpackedtexel == filltexel) {
fourconnected [adj] = true;
} else {
r += unpackedtexel.Red();
g += unpackedtexel.Green();
b += unpackedtexel.Blue();
fillcount++;
}
}
}
if (fillcount > 0) {
float oofillcount;
// Fill texel (x, y) with average of neighboring non-fill colors.
oofillcount = 1.0f / fillcount;
r = MIN (r * oofillcount, UCHAR_MAX);
g = MIN (g * oofillcount, UCHAR_MAX);
b = MIN (b * oofillcount, UCHAR_MAX);
UnpackedTexelStruct texel (r, g, b, 0);
Pack_Texel (texel, texelptr, bytespertexel);
// Add the coordinates of any neighboring fill colors.
for (adj = 0; adj < fourconnectedcount; adj++) {
if (fourconnected [adj]) {
PointStruct p;
p.X = ((int) x) + offsetx [adj];
p.Y = ((int) y) + offsety [adj];
fourconnectedarray.Add (p);
}
}
}
return (true);
} else {
return (false);
}
}
/***********************************************************************************************
* TrueColorTarga::Add -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Add (TrueColorTarga &targa)
{
unsigned width, height;
unsigned thisbytespertexel, targabytespertexel;
// Scale both targas to maximum width & height of both.
width = MAX (Width(), targa.Width());
height = MAX (Height(), targa.Height());
Scale (width, height);
targa.Scale (width, height);
// Step thru both targas and add the pixel data. Store result in this targa.
// For each row...
thisbytespertexel = TGA_BytesPerPixel (Pixel_Depth());
targabytespertexel = TGA_BytesPerPixel (targa.Pixel_Depth());
for (unsigned r = 0; r < height; r++) {
unsigned char *thisptr, *targaptr;
thisptr = ((unsigned char*) GetImage()) + (width * r * thisbytespertexel);
targaptr = ((unsigned char*) targa.GetImage()) + (width * r * targabytespertexel);
// For each column...
for (unsigned c = 0; c < width; c++) {
UnpackedTexelStruct thisunpackedtexel, targaunpackedtexel;
Unpack_Texel (thisptr, thisbytespertexel, thisunpackedtexel);
Unpack_Texel (targaptr, targabytespertexel, targaunpackedtexel);
thisunpackedtexel += targaunpackedtexel;
Pack_Texel (thisunpackedtexel, thisptr, thisbytespertexel);
// Advance to next texel.
thisptr += thisbytespertexel;
targaptr += targabytespertexel;
}
}
}
/***********************************************************************************************
* TrueColorTarga::Rasterize -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/22/00 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Rasterize (TrueColorTarga &destination, const W3dRGBStruct &fillcolor, unsigned vertexcount, const W3dRGBStruct *vertexcolors, Vector2 *vertexuvs)
{
const unsigned edgecount = 3;
const static float _vertexuvs [edgecount][2] =
{
{1.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 1.0f}
};
const UnpackedTexelStruct filltexel (fillcolor.R, fillcolor.G, fillcolor.B, 0);
const unsigned sqrtsamplecountminusone = 3;
const float oosqrtsamplecountminusone = 1.0f / sqrtsamplecountminusone;
const unsigned mindimension = 3;
Vector2 edges [edgecount];
float longestlength2, r, w, h;
unsigned longestedge;
Vector2 i, j;
float oowidthminustwo, ooheightminustwo;
unsigned bytespertexel;
unsigned char *destptr;
W3dRGBStruct colors [edgecount];
float oowidth, ooheight;
unsigned n;
// UV's must define a triangle.
ASSERT (vertexcount == edgecount);
// Define edges of source triangle.
edges [0] = vertexuvs [1] - vertexuvs [0];
edges [1] = vertexuvs [2] - vertexuvs [1];
edges [2] = vertexuvs [0] - vertexuvs [2];
// The destination triangle is a right-angled triangle that occupies one half of the
// destination targa. Size the destination targa usinh the following rules:
// (a) length of longest edge equals length of targa's diagonal (hypotenuse of destination triangle)
// (b) ratio of lengths of remaining two edges of source triangle equals ratio of dimensions of targa.
// Identify the longest edge.
longestlength2 = 0.0f;
for (unsigned e = 0; e < edgecount; e++) {
float length2 = edges [e].Length2();
if (length2 > longestlength2) {
longestedge = e;
longestlength2 = length2;
}
}
// Calculate width and height of destination targa.
r = edges [(longestedge + 1) % edgecount].Length() / edges [(longestedge + 2) % edgecount].Length();
w = WWMath::Sqrt (longestlength2 / (1.0f + r));
h = w / r;
// Resize the destination targa.
destination.Reformat (MAX (mindimension, (unsigned) (w * Width())), MAX (mindimension, (unsigned) (h * Height())), Pixel_Depth());
// Set up a coordinate system to define the source triangle in terms of the destination triangle.
i = -edges [(longestedge + 1) % edgecount];
j = edges [(longestedge + 2) % edgecount];
// Define colors that correspond to each vertex;
for (n = 0; n < vertexcount; n++) {
if (vertexcolors != NULL) {
colors [n] = vertexcolors [(n + longestedge) % edgecount];
} else {
colors [n] = fillcolor;
}
}
// Iterate over the destination triangle, sampling the source triangle at regular intervals.
// Also insert a one pixel wide filtered edge comprising interpolated vertex colors.
// For each destination texel...
bytespertexel = TGA_BytesPerPixel (Pixel_Depth());
oowidthminustwo = 1.0f / (destination.Width() - 2);
ooheightminustwo = 1.0f / (destination.Height() - 2);
destptr = (unsigned char*) destination.GetImage();
for (unsigned v = 0; v < destination.Height(); v++) {
for (unsigned u = 0; u < destination.Width(); u++) {
unsigned validsamplecount;
float r, g, b;
float fu, fv;
float oovalidsamplecount;
UnpackedTexelStruct averagetexel;
float clamped;
// For given destination texel subsample the source triangle.
validsamplecount = 0;
r = g = b = 0.0f;
for (unsigned sv = 0; sv <= sqrtsamplecountminusone; sv++) {
fv = (v + (sv * oosqrtsamplecountminusone) - 1.0f) * ooheightminustwo;
for (unsigned su = 0; su <= sqrtsamplecountminusone; su++) {
fu = (u + (su * oosqrtsamplecountminusone) - 1.0f) * oowidthminustwo;
// Which region does (fu, fv) fall in?
clamped = false;
if (fu < 0.0f) {
fu = 0.0f;
clamped = true;
} else {
if (fu > 1.0f) {
fu = 1.0f;
clamped = true;
}
}
if (fv < 0.0f) {
fv = 0.0f;
clamped = true;
} else {
if (fv > 1.0f) {
fv = 1.0f;
clamped = true;
}
}
if ((fu + fv) >= 1.0f) {
float ood = 1.0f / (fu + fv);
fu = fu * ood;
fv = fv * ood;
clamped = true;
}
if (!clamped) {
Vector2 p;
unsigned x, y;
UnpackedTexelStruct unpackedtexel;
unsigned char *sourceptr;
// Calculate source coordinates.
p = vertexuvs [(longestedge + 2) % 3] + (fu * i) + (fv * j);
x = (unsigned) MIN (MAX ((int) (p.X * Width()), 0), ((int) Width()) - 1);
y = (unsigned) MIN (MAX ((int) (p.Y * Height()), 0), ((int) Height()) - 1);
// Sample the source.
sourceptr = ((unsigned char*) GetImage()) + (((Width() * y) + x) * bytespertexel);
Unpack_Texel (sourceptr, bytespertexel, unpackedtexel);
r += unpackedtexel.Red();
g += unpackedtexel.Green();
b += unpackedtexel.Blue();
} else {
// Calculate source color.
r += 0.5f * ((colors [1].R * fu) + (colors [2].R * (1.0f - fu)) + (colors [2].R * (1.0f - fv)) + (colors [0].R * fv));
g += 0.5f * ((colors [1].G * fu) + (colors [2].G * (1.0f - fu)) + (colors [2].G * (1.0f - fv)) + (colors [0].G * fv));
b += 0.5f * ((colors [1].B * fu) + (colors [2].B * (1.0f - fu)) + (colors [2].B * (1.0f - fv)) + (colors [0].B * fv));
}
validsamplecount++;
}
}
// One or more samples must have been taken.
ASSERT (validsamplecount > 0);
// Destination texel = average of samples.
oovalidsamplecount = 1.0f / validsamplecount;
averagetexel.Set (r * oovalidsamplecount, g * oovalidsamplecount, b * oovalidsamplecount, 0);
Pack_Texel (averagetexel, destptr, bytespertexel);
// Advance.
destptr += bytespertexel;
}
}
// Replace source UV's with destination coordinates for caller.
oowidth = 1.0f / destination.Width();
ooheight = 1.0f / destination.Height();
for (n = 0; n < vertexcount; n++) {
vertexuvs [n].Set (_vertexuvs [((edgecount - 1 - longestedge) + n) % edgecount][0], _vertexuvs [((edgecount - 1 - longestedge) + n) % edgecount][1]);
// Scale and translate UV's to accomodate one texel wide filtered edge.
vertexuvs [n].U *= (destination.Width() - 2) * oowidth;
vertexuvs [n].V *= (destination.Height() - 2) * ooheight;
vertexuvs [n].U += oowidth;
vertexuvs [n].V += ooheight;
}
}
/***********************************************************************************************
* TrueColorTarga::Pad -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/22/00 IML : Created. *
*=============================================================================================*/
void TrueColorTarga::Pad (TrueColorTarga &destination, unsigned padwidth, unsigned padheight, const W3dRGBStruct &padcolor, DynamicVectorClass <PackingTriangle*> &triangleptrs)
{
unsigned w, h;
float oobw, oobh;
UnpackedTexelStruct padtexel (padcolor.R, padcolor.G, padcolor.B, 0);
Matrix3 uvmatrix;
// Optimization: was a non-zero pad thickness specified?
if ((padwidth > 0) || (padheight > 0)) {
// Round pad width and height to even no. to ensure that there is an equal amount of padding on each side.
padwidth += (padwidth & 0x1);
padheight += (padheight & 0x1);
// Calculate scaling and translation factors.
w = Width();
h = Height();
oobw = 1.0f / (w + padwidth);
oobh = 1.0f / (h + padheight);
if (this == &destination) {
TrueColorTarga targacopy (w, h, Pixel_Depth());
Blit (targacopy, 0, 0);
// Resize to match that of the padded targa.
Reformat (w + padwidth, h + padheight, Pixel_Depth());
// Clear to pad color.
Clear (padtexel);
// Blit copy to this targa. Offset by pad thickness.
targacopy.Blit (*this, padwidth >> 1, padheight >> 1);
} else {
// Resize the destination to match that of the padded targa.
destination.Reformat (w + padwidth, h + padheight, Pixel_Depth());
// Clear destination to pad color.
destination.Clear (padtexel);
// Blit this to destination. Offset by pad thickness.
Blit (destination, padwidth >> 1, padheight >> 1);
}
// Pad pad color.
destination.Fill (padcolor);
// Calculate scaling and translation matrix.
uvmatrix [0].Set (w * oobw, 0.0f, 0.0f);
uvmatrix [1].Set (0.0f, h * oobh, 0.0f);
uvmatrix [2].Set ((padwidth >> 1) * oobw, (padheight >> 1) * oobh, 1.0f);
// Transpose transform matrix.
// NOTE: This matrix is set up for post multiplication (ie. V' = M X V, matrix M, vector V).
uvmatrix = uvmatrix.Transpose();
// Apply transform to all triangles...
for (int t = 0; t < triangleptrs.Count(); t++) {
for (unsigned v = 0; v < Triangle::VERTICES_COUNT; v++) {
Vector3 uv;
// NOTE: Create a homogeneous vector (3-components) from the UV vector so that it can be transformed by the matrix.
uv.Set (triangleptrs [t]->PackedUVs [v].X, triangleptrs [t]->PackedUVs [v].Y, 1.0f);
uv = uvmatrix * uv;
triangleptrs [t]->PackedUVs [v].Set (uv.X, uv.Y);
}
}
}
}
/***********************************************************************************************
* TrianglePacker::SampleSurface::SampleSurface -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/12/00 IML : Created. *
*=============================================================================================*/
TrianglePacker::SampleSurface::SampleSurface (unsigned width, unsigned height, ProceduralTexture *blendtexture)
{
SampleStruct *sampleptr;
Width = width;
Height = height;
SampledTexelCount = 0;
Surface = new SampleStruct [width * height];
ASSERT (Surface != NULL);
// Initialize surface.
sampleptr = Surface;
for (unsigned s = 0; s < width * height; s++) {
sampleptr->Red = 0;
sampleptr->Green = 0;
sampleptr->Blue = 0;
sampleptr->Count = 0;
sampleptr->Priority = 0;
sampleptr++;
}
BlendTexture = blendtexture;
}
/***********************************************************************************************
* TrianglePacker::SampleSurface::Sample -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/12/00 IML : Created. *
*=============================================================================================*/
bool TrianglePacker::SampleSurface::Sample (const Vector2 &samplepoint, const ProjectionTriangle &projectiontriangle, unsigned priority)
{
const float epsilon = 0.0001f;
int u, v;
Vector2 t0, t1, t2;
float alpha, beta;
u = floorf (samplepoint.U);
v = floorf (samplepoint.V);
// NOTE: Sample point must be in range.
if ((u < 0) || (u >= (int) Width)) return (false);
if ((v < 0) || (v >= (int) Height)) return (false);
// Use the baricentric method to determine if the sample point lies inside
// the triangle (see Graphics Gems I p390).
t0 = samplepoint - projectiontriangle.ProjectionUVs [0];
t1 = projectiontriangle.ProjectionUVs [1];
t2 = projectiontriangle.ProjectionUVs [2];
if (WWMath::Fabs (t1.U) <= epsilon) {
beta = t0.U / t2.U;
if (beta <= 0.0f || beta >= 1.0f) return (false);
alpha = (t0.V - (beta * t2.V)) / t1.V;
} else {
beta = (t0.V * t1.U - t0.U * t1.V) / (t2.V * t1.U - t2.U * t1.V);
if (beta <= 0.0f || beta >= 1.0f) return (false);
alpha = (t0.U - beta * t2.U) / t1.U;
}
// Does the projection triangle contain the sample point?
if ((alpha > 0.0f) && ((alpha + beta) < 1.0f)) {
Vector2 uv;
W3dRGBStruct color;
SampleStruct *sampleptr;
// Use the baricentric coordinates (alpha, beta) to calculate the texture coordinates.
uv = (alpha * projectiontriangle.SourceUVs [1]) + (beta * projectiontriangle.SourceUVs [2]) + projectiontriangle.SourceUVs [0];
if (!projectiontriangle.SourceTargaPtr->Get_Color (uv, color)) return (false);
// Should the texel color be blended with a procedural texture?
if (BlendTexture != NULL) {
Vector3 p;
float v;
p = (alpha * projectiontriangle.Points [1]) + (beta * projectiontriangle.Points [2]) + projectiontriangle.Points [0];
v = BlendTexture->Value (p);
color.Set ((uint8) (color.R * v), (uint8) (color.G * v), (uint8) (color.B * v));
}
// Add the sample.
sampleptr = Surface + (v * Width + u);
sampleptr->Red += color.R;
sampleptr->Green += color.G;
sampleptr->Blue += color.B;
if (sampleptr->Count == 0) SampledTexelCount++;
sampleptr->Count++;
// Update priority.
if (priority > sampleptr->Priority) sampleptr->Priority = priority;
return (true);
} else {
return (false);
}
}
/***********************************************************************************************
* TrianglePacker::SampleSurface::Sample -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 06/12/00 IML : Created. *
*=============================================================================================*/
bool TrianglePacker::SampleSurface::Sample (float alpha, float beta, const ProjectionTriangle &projectiontriangle, unsigned priority)
{
Vector2 samplepoint;
int u, v;
Vector2 uv;
W3dRGBStruct color;
SampleStruct *sampleptr;
// Use the baricentric coordinates (alpha, beta) to calculate the sample point.
samplepoint = (alpha * projectiontriangle.ProjectionUVs [1]) + (beta * projectiontriangle.ProjectionUVs [2]) + projectiontriangle.ProjectionUVs [0];
u = floorf (samplepoint.U);
v = floorf (samplepoint.V);
// NOTE: Sample point must be in range.
if ((u < 0) || (u >= (int) Width)) return (false);
if ((v < 0) || (v >= (int) Height)) return (false);
// Use the baricentric coordinates (alpha, beta) to calculate the texture coordinates.
uv = (alpha * projectiontriangle.SourceUVs [1]) + (beta * projectiontriangle.SourceUVs [2]) + projectiontriangle.SourceUVs [0];
if (!projectiontriangle.SourceTargaPtr->Get_Color (uv, color)) return (false);
// Should the texel color be blended with a procedural texture?
if (BlendTexture != NULL) {
Vector3 p;
float v;
p = (alpha * projectiontriangle.Points [1]) + (beta * projectiontriangle.Points [2]) + projectiontriangle.Points [0];
v = BlendTexture->Value (p);
color.Set ((uint8) (color.R * v), (uint8) (color.G * v), (uint8) (color.B * v));
}
// Add the sample.
sampleptr = Surface + (v * Width + u);
sampleptr->Red += color.R;
sampleptr->Green += color.G;
sampleptr->Blue += color.B;
if (sampleptr->Count == 0) SampledTexelCount++;
sampleptr->Count++;
// Update priority.
if (priority > sampleptr->Priority) sampleptr->Priority = priority;
return (true);
}
/***********************************************************************************************
* Page::Page -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
Page::Page (unsigned bitdepth, const W3dRGBStruct &clearcolor)
: TrueColorTarga (PAGE_WIDTH, PAGE_HEIGHT, bitdepth)
{
const UnpackedTexelStruct cleartexel (clearcolor.R, clearcolor.G, clearcolor.B, 0);
Region *regionptr;
// Clear this page to a recognizable color.
Clear (cleartexel);
// Create a single region for the entire page.
regionptr = new Region;
ASSERT (regionptr != NULL);
regionptr->Set (0, 0, PAGE_WIDTH - 1, PAGE_HEIGHT - 1);
VacantRegionList.Add_Head (regionptr);
Reset_Statistics();
}
/***********************************************************************************************
* Page::Page -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
Page::~Page()
{
// Delete the used region list.
while (UsedRegionList.First_Valid()) {
delete UsedRegionList.First_Valid();
}
// Delete the region list.
while (VacantRegionList.First_Valid()) {
delete VacantRegionList.First_Valid();
}
}
/***********************************************************************************************
* Page::Reset_Statisitcs -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
void Page::Reset_Statistics()
{
AssetCount = 0;
UsedTexelCount = 0;
ReplicaTexelCount = 0;
}
/***********************************************************************************************
* Page::Pack -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 05/19/00 IML : Created. *
*=============================================================================================*/
bool Page::Pack (TrueColorTarga &targa, float epsilon, DynamicVectorClass <PackingTriangle*> &triangleptrs)
{
TrueColorTarga *targaptr [TRANSPOSE_COUNT];
bool replica, packed;
TransposeEnum transposed;
unsigned cost;
Region targetregion;
// Targa must fit on this page.
ASSERT (targa.Width() <= Width());
ASSERT (targa.Height() <= Height());
targaptr [UNTRANSPOSED] = &targa;
// Create a transposed version of the targa.
targaptr [TRANSPOSED] = new TrueColorTarga (targaptr [UNTRANSPOSED]->Width(), targaptr [UNTRANSPOSED]->Height(), targaptr [UNTRANSPOSED]->Pixel_Depth());
ASSERT (targaptr [TRANSPOSED] != NULL);
targaptr [UNTRANSPOSED]->Blit (*targaptr [TRANSPOSED], 0, 0);
targaptr [TRANSPOSED]->Transpose();
// Attempt to find a replica targa on this page.
transposed = (targaptr [UNTRANSPOSED]->Width() >= targaptr [UNTRANSPOSED]->Height()) ? UNTRANSPOSED : TRANSPOSED;
replica = Replica_Region (*targaptr [transposed], epsilon, targetregion);
if (!replica) {
transposed = (transposed == UNTRANSPOSED) ? TRANSPOSED : UNTRANSPOSED;
replica = Replica_Region (*targaptr [transposed], epsilon, targetregion);
}
if (replica) {
// Update statistics.
ReplicaTexelCount += targaptr [transposed]->Width() * targaptr [transposed]->Height();
} else {
// Attempt to pack targa with width >= height first.
transposed = (targaptr [UNTRANSPOSED]->Width() >= targaptr [UNTRANSPOSED]->Height()) ? UNTRANSPOSED : TRANSPOSED;
// Can the targa be packed?
packed = Lowest_Cost_Region (*targaptr [transposed], cost, targetregion);
// If not packed then transpose the targa and attempt to pack it again.
if (!packed) {
transposed = (transposed == UNTRANSPOSED) ? TRANSPOSED : UNTRANSPOSED;
packed = Lowest_Cost_Region (*targaptr [transposed], cost, targetregion);
}
if (packed) {
// Region must be aligned on an even boundary. This will reduce texel bleeding as a result of mip-mapping.
ASSERT ((targetregion.X0 & 0x1) == 0x0);
ASSERT ((targetregion.Y0 & 0x1) == 0x0);
ASSERT ((targetregion.X1 & 0x1) == 0x1);
ASSERT ((targetregion.Y1 & 0x1) == 0x1);
Insert_Region (targetregion);
// Blit the targa onto this page.
targaptr [transposed]->Blit (*this, targetregion.X0, targetregion.Y0);
// Update statistics.
UsedTexelCount += targaptr [transposed]->Width() * targaptr [transposed]->Height();
}
}
// Transform the UV's to reflect the packing.
if (replica || packed) {
float su, sv; // Scaling factors of the targa within this page.
float tu, tv; // Translation of the targa within this page.
Matrix3 uvmatrix;
// Calculate scaling and translation factors.
su = ((float) (targaptr [UNTRANSPOSED]->Width())) / Width();
sv = ((float) (targaptr [UNTRANSPOSED]->Height())) / Height();
tu = ((float) targetregion.X0) / Width();
tv = ((float) targetregion.Y0) / Height();
// Was the targa transposed?
if (transposed == TRANSPOSED) {
// Define scaling/rotation vectors in transform matrix.
uvmatrix [0].Set (0.0f, su , 0.0f);
uvmatrix [1].Set (sv , 0.0f, 0.0f);
} else {
// Define scaling/rotation vectors in transform matrix.
uvmatrix [0].Set (su , 0.0f, 0.0f);
uvmatrix [1].Set (0.0f, sv , 0.0f);
}
// Define translation vector in transform matrix.
uvmatrix [2].Set (tu, tv, 1.0f);
// Transpose transform matrix.
// NOTE: Thus matrix is set up for post multiplication (ie. V' = M X V, matrix M, vector V).
uvmatrix = uvmatrix.Transpose();
// Iterate over all triangles and transform their packed UV's.
for (int t = 0; t < triangleptrs.Count(); t++) {
for (unsigned v = 0; v < Triangle::VERTICES_COUNT; v++) {
Vector3 uv;
// NOTE: Create a homogeneous vector (3-components) from the UV vector so that it can be transformed by the matrix.
uv.Set (triangleptrs [t]->PackedUVs [v].X, triangleptrs [t]->PackedUVs [v].Y, 1.0f);
uv = uvmatrix * uv;
triangleptrs [t]->PackedUVs [v].Set (uv.X, uv.Y);
}
}
// Update statistics.
AssetCount++;
}
// Clean-up.
delete targaptr [TRANSPOSED];
// Return success.
return (replica || packed);
}
/***********************************************************************************************
* Page::Replica_Region -- *
* * *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 11/17/99 IML : Created. *
*=============================================================================================*/
bool Page::Replica_Region (TrueColorTarga &targa, float epsilon, Region &replicaregion)
{
bool replica;
Region *regionptr;
unsigned regionwidth, regionheight;
// Iterate over all used regions to see if one matches the targa to within tolerance.
replica = false;
regionptr = (Region*) UsedRegionList.First_Valid();
while (regionptr != NULL) {
regionwidth = regionptr->X1 - regionptr->X0 + 1;
regionheight = regionptr->Y1 - regionptr->Y0 + 1;
// If regions are the same size...
if ((regionwidth == targa.Width()) && (regionheight == targa.Height())) {
// If regions are deemed equal to within epsilon...
if (targa.Compare (*this, regionptr->X0, regionptr->Y0, epsilon) <= 0) {
replica = true;
replicaregion = *regionptr;
break;
}
}
// Next region.
regionptr = (Region*) regionptr->Next_Valid();
}
return (replica);
}
/***********************************************************************************************
* Page::Lowest_Cost_Region -- *
* * *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/8/99 IML : Created. *
*=============================================================================================*/
bool Page::Lowest_Cost_Region (TrueColorTarga &targa, unsigned &lowestcost, Region &lowestcostregion)
{
unsigned width, height;
bool packed;
unsigned mintotalsubregioncount;
Region *regionptr;
// Find a vacant region in the page that will incur the lowest cost if it contains the targa asset.
width = targa.Width();
height = targa.Height();
packed = false;
mintotalsubregioncount = UINT_MAX;
regionptr = (Region*) VacantRegionList.First_Valid();
while (regionptr != NULL) {
Region candidateregion;
// Does the asset fit inside the region?
if (regionptr->Accomodates (width, height, candidateregion)) {
unsigned totalsubregioncount, subregioncount;
Region *fragmentregionptr, *subregions;
fragmentregionptr = (Region*) VacantRegionList.First_Valid();
totalsubregioncount = 0;
while (fragmentregionptr != NULL) {
// Calculate the fragmentation count for the fragment region.
if (fragmentregionptr->Intersects (candidateregion, subregioncount, &subregions)) {
totalsubregioncount += subregioncount;
}
// Next region.
fragmentregionptr = (Region*) fragmentregionptr->Next_Valid();
}
// Is this the minimum total subregion count?
if (totalsubregioncount < mintotalsubregioncount) {
packed = true;
lowestcostregion = candidateregion;
mintotalsubregioncount = totalsubregioncount;
}
}
// Next region.
regionptr = (Region*) regionptr->Next_Valid();
}
lowestcost = mintotalsubregioncount;
// Can the asset be packed on this page?
return (packed);
}
/***********************************************************************************************
* Page::Insert_Region -- *
* * *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 10/8/99 IML : Created. *
*=============================================================================================*/
void Page::Insert_Region (const Region &insertionregion)
{
Region *regionptr;
// First, add the insertion region to the used region list.
regionptr = new Region (insertionregion);
ASSERT (regionptr != NULL);
UsedRegionList.Add_Head (regionptr);
// Walk down the vacant list. If any region intersects the target region, fragment it into
// 0 to 4 sub-regions, depending on the relative location of the clipped region.
regionptr = (Region*) VacantRegionList.First_Valid();
while (regionptr != NULL) {
unsigned subregioncount;
Region *subregions, *removalptr;
// If there is an intersection between the region and target region...
if (regionptr->Intersects (insertionregion, subregioncount, &subregions)) {
// Remove the original region, advance to next region.
removalptr = regionptr;
regionptr = (Region*) regionptr->Next_Valid();
delete removalptr;
// Add the subregions to the head of the list.
// NOTE: Adding to the head of the list will ensure that they will not get re-processed by the current 'list walker'.
for (int s = ((int) subregioncount) - 1; s >= 0; s--) {
// Only add the subregion if it is not contained by another region ie. avoid redundancy.
if (!Contains (subregions [s])) {
Region *newregionptr;
newregionptr = new Region (subregions [s]);
ASSERT (newregionptr != NULL);
VacantRegionList.Add_Head (newregionptr);
}
}
} else {
// Advance to next region.
regionptr = (Region*) regionptr->Next_Valid();
}
}
}
/***********************************************************************************************
* Page::Region::Accomodates -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
bool Page::Region::Accomodates (unsigned width, unsigned height, Region &targetregion)
{
if ((width <= Width()) && (height <= Height())) {
// Locate the target region in the top-left corner of this region.
targetregion.Set (X0, Y0, X0 + width - 1, Y0 + height - 1);
return (true);
} else {
return (false);
}
}
/***********************************************************************************************
* Page::Region::Intersects -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
bool Page::Region::Intersects (const Region &targetregion, unsigned &subregioncount, Region **subregions)
{
const unsigned maxsubregioncount = 4; // Maximum no. of fragmented subregions.
static Region _subregions [maxsubregioncount];
unsigned s;
// First, test the target region for trivial rejection against the left, right, top and bottom
// sides of this region respectively.
if (targetregion.X1 < X0) return (false);
if (targetregion.X0 > X1) return (false);
if (targetregion.Y1 < Y0) return (false);
if (targetregion.Y0 > Y1) return (false);
// Now it is known that the target region intersects this region in some way.
// Count and generate the subregions between the target region and this region.
s = 0;
if (targetregion.X0 > X0) {
_subregions [s].Set (X0, Y0, targetregion.X0 - 1, Y1);
s++;
}
if (targetregion.X1 < X1) {
_subregions [s].Set (targetregion.X1 + 1, Y0, X1, Y1);
s++;
}
if (targetregion.Y0 > Y0) {
_subregions [s].Set (X0, Y0, X1, targetregion.Y0 - 1);
s++;
}
if (targetregion.Y1 < Y1) {
_subregions [s].Set (X0, targetregion.Y1 + 1, X1, Y1);
s++;
}
subregioncount = s;
*subregions = _subregions;
return (true);
}
/***********************************************************************************************
* Page::Region::Contains -- *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 9/27/99 IML : Created. *
*=============================================================================================*/
bool Page::Contains (const Region &testregion)
{
Region *regionptr;
// Step down the vacant list until a region is found that contains the test region or end of list.
regionptr = (Region*) VacantRegionList.First_Valid();
while (regionptr != NULL) {
if ((testregion.X0 >= regionptr->X0) && (testregion.X1 <= regionptr->X1) && (testregion.Y0 >= regionptr->Y0) && (testregion.Y1 <= regionptr->Y1)) {
// Test region is contained by a region in the list.
return (true);
}
regionptr = (Region*) regionptr->Next_Valid();
}
// Test region is not contained by a region in the list.
return (false);
}