3388 lines
150 KiB
C++
3388 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);
|
||
|
}
|
||
|
|