mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-26 14:01:15 +00:00
Merge branch 'master' of https://github.com/rheit/zdoom
This commit is contained in:
commit
f391216ab9
34 changed files with 1564 additions and 312 deletions
|
@ -212,6 +212,8 @@ Note: All <bool> fields default to false unless mentioned otherwise.
|
|||
leakiness = <int>; // Probability of leaking through radiation suit (0 = never, 256 = always), default = 0.
|
||||
damageterraineffect = <bool>; // Will spawn a terrain splash when damage is inflicted. Default = false.
|
||||
damagehazard = <bool>; // Changes damage model to Strife's delayed damage for the given sector. Default = false.
|
||||
floorterrain = <string>; // Sets the terrain for the sector's floor. Default = 'use the flat texture's terrain definition.'
|
||||
ceilingterrain = <string>; // Sets the terrain for the sector's ceiling. Default = 'use the flat texture's terrain definition.'
|
||||
|
||||
|
||||
* Note about dropactors
|
||||
|
|
|
@ -1059,6 +1059,8 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE
|
|||
wi_stuff.cpp
|
||||
zstrformat.cpp
|
||||
zstring.cpp
|
||||
GuillotineBinPack.cpp
|
||||
SkylineBinPack.cpp
|
||||
g_doom/a_doommisc.cpp
|
||||
g_heretic/a_hereticmisc.cpp
|
||||
g_hexen/a_hexenmisc.cpp
|
||||
|
|
633
src/GuillotineBinPack.cpp
Normal file
633
src/GuillotineBinPack.cpp
Normal file
|
@ -0,0 +1,633 @@
|
|||
/** @file GuillotineBinPack.cpp
|
||||
@author Jukka Jylänki
|
||||
|
||||
@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
|
||||
|
||||
This work is released to Public Domain, do whatever you want with it.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include "templates.h"
|
||||
#include "GuillotineBinPack.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
GuillotineBinPack::GuillotineBinPack()
|
||||
:binWidth(0),
|
||||
binHeight(0)
|
||||
{
|
||||
}
|
||||
|
||||
GuillotineBinPack::GuillotineBinPack(int width, int height)
|
||||
{
|
||||
Init(width, height);
|
||||
}
|
||||
|
||||
void GuillotineBinPack::Init(int width, int height)
|
||||
{
|
||||
binWidth = width;
|
||||
binHeight = height;
|
||||
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Clear();
|
||||
#endif
|
||||
|
||||
// Clear any memory of previously packed rectangles.
|
||||
usedRectangles.Clear();
|
||||
|
||||
// We start with a single big free rectangle that spans the whole bin.
|
||||
Rect n;
|
||||
n.x = 0;
|
||||
n.y = 0;
|
||||
n.width = width;
|
||||
n.height = height;
|
||||
|
||||
freeRectangles.Clear();
|
||||
freeRectangles.Push(n);
|
||||
}
|
||||
|
||||
void GuillotineBinPack::Insert(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge,
|
||||
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
|
||||
{
|
||||
dst.Clear();
|
||||
|
||||
// Remember variables about the best packing choice we have made so far during the iteration process.
|
||||
int bestFreeRect = 0;
|
||||
int bestRect = 0;
|
||||
bool bestFlipped = false;
|
||||
|
||||
// Pack rectangles one at a time until we have cleared the rects array of all rectangles.
|
||||
// rects will get destroyed in the process.
|
||||
while(rects.Size() > 0)
|
||||
{
|
||||
// Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better.
|
||||
int bestScore = INT_MAX;
|
||||
|
||||
for(unsigned i = 0; i < freeRectangles.Size(); ++i)
|
||||
{
|
||||
for(unsigned j = 0; j < rects.Size(); ++j)
|
||||
{
|
||||
// If this rectangle is a perfect match, we pick it instantly.
|
||||
if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height)
|
||||
{
|
||||
bestFreeRect = i;
|
||||
bestRect = j;
|
||||
bestFlipped = false;
|
||||
bestScore = INT_MIN;
|
||||
i = freeRectangles.Size(); // Force a jump out of the outer loop as well - we got an instant fit.
|
||||
break;
|
||||
}
|
||||
// If flipping this rectangle is a perfect match, pick that then.
|
||||
else if (rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height)
|
||||
{
|
||||
bestFreeRect = i;
|
||||
bestRect = j;
|
||||
bestFlipped = true;
|
||||
bestScore = INT_MIN;
|
||||
i = freeRectangles.Size(); // Force a jump out of the outer loop as well - we got an instant fit.
|
||||
break;
|
||||
}
|
||||
// Try if we can fit the rectangle upright.
|
||||
else if (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height)
|
||||
{
|
||||
int score = ScoreByHeuristic(rects[j].width, rects[j].height, freeRectangles[i], rectChoice);
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestFreeRect = i;
|
||||
bestRect = j;
|
||||
bestFlipped = false;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
// If not, then perhaps flipping sideways will make it fit?
|
||||
else if (rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height)
|
||||
{
|
||||
int score = ScoreByHeuristic(rects[j].height, rects[j].width, freeRectangles[i], rectChoice);
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestFreeRect = i;
|
||||
bestRect = j;
|
||||
bestFlipped = true;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't manage to find any rectangle to pack, abort.
|
||||
if (bestScore == INT_MAX)
|
||||
return;
|
||||
|
||||
// Otherwise, we're good to go and do the actual packing.
|
||||
Rect newNode;
|
||||
newNode.x = freeRectangles[bestFreeRect].x;
|
||||
newNode.y = freeRectangles[bestFreeRect].y;
|
||||
newNode.width = rects[bestRect].width;
|
||||
newNode.height = rects[bestRect].height;
|
||||
|
||||
if (bestFlipped)
|
||||
std::swap(newNode.width, newNode.height);
|
||||
|
||||
// Remove the free space we lost in the bin.
|
||||
SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod);
|
||||
freeRectangles.Delete(bestFreeRect);
|
||||
|
||||
// Remove the rectangle we just packed from the input list.
|
||||
rects.Delete(bestRect);
|
||||
|
||||
// Perform a Rectangle Merge step if desired.
|
||||
if (merge)
|
||||
MergeFreeList();
|
||||
|
||||
// Remember the new used rectangle.
|
||||
usedRectangles.Push(newNode);
|
||||
|
||||
// Check that we're really producing correct packings here.
|
||||
assert(disjointRects.Add(newNode) == true);
|
||||
}
|
||||
}
|
||||
|
||||
/// @return True if r fits inside freeRect (possibly rotated).
|
||||
bool Fits(const RectSize &r, const Rect &freeRect)
|
||||
{
|
||||
return (r.width <= freeRect.width && r.height <= freeRect.height) ||
|
||||
(r.height <= freeRect.width && r.width <= freeRect.height);
|
||||
}
|
||||
|
||||
/// @return True if r fits perfectly inside freeRect, i.e. the leftover area is 0.
|
||||
bool FitsPerfectly(const RectSize &r, const Rect &freeRect)
|
||||
{
|
||||
return (r.width == freeRect.width && r.height == freeRect.height) ||
|
||||
(r.height == freeRect.width && r.width == freeRect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
// A helper function for GUILLOTINE-MAXFITTING. Counts how many rectangles fit into the given rectangle
|
||||
// after it has been split.
|
||||
void CountNumFitting(const Rect &freeRect, int width, int height, const TArray<RectSize> &rects,
|
||||
int usedRectIndex, bool splitHorizontal, int &score1, int &score2)
|
||||
{
|
||||
const int w = freeRect.width - width;
|
||||
const int h = freeRect.height - height;
|
||||
|
||||
Rect bottom;
|
||||
bottom.x = freeRect.x;
|
||||
bottom.y = freeRect.y + height;
|
||||
bottom.height = h;
|
||||
|
||||
Rect right;
|
||||
right.x = freeRect.x + width;
|
||||
right.y = freeRect.y;
|
||||
right.width = w;
|
||||
|
||||
if (splitHorizontal)
|
||||
{
|
||||
bottom.width = freeRect.width;
|
||||
right.height = height;
|
||||
}
|
||||
else // Split vertically
|
||||
{
|
||||
bottom.width = width;
|
||||
right.height = freeRect.height;
|
||||
}
|
||||
|
||||
int fitBottom = 0;
|
||||
int fitRight = 0;
|
||||
for(size_t i = 0; i < rects.size(); ++i)
|
||||
if (i != usedRectIndex)
|
||||
{
|
||||
if (FitsPerfectly(rects[i], bottom))
|
||||
fitBottom |= 0x10000000;
|
||||
if (FitsPerfectly(rects[i], right))
|
||||
fitRight |= 0x10000000;
|
||||
|
||||
if (Fits(rects[i], bottom))
|
||||
++fitBottom;
|
||||
if (Fits(rects[i], right))
|
||||
++fitRight;
|
||||
}
|
||||
|
||||
score1 = min(fitBottom, fitRight);
|
||||
score2 = max(fitBottom, fitRight);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
|
||||
void GuillotineBinPack::InsertMaxFitting(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge,
|
||||
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
|
||||
{
|
||||
dst.clear();
|
||||
int bestRect = 0;
|
||||
bool bestFlipped = false;
|
||||
bool bestSplitHorizontal = false;
|
||||
|
||||
// Pick rectangles one at a time and pack the one that leaves the most choices still open.
|
||||
while(rects.size() > 0 && freeRectangles.size() > 0)
|
||||
{
|
||||
int bestScore1 = -1;
|
||||
int bestScore2 = -1;
|
||||
|
||||
///\todo Different sort predicates.
|
||||
clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), CompareRectShortSide);
|
||||
|
||||
Rect &freeRect = freeRectangles[0];
|
||||
|
||||
for(size_t j = 0; j < rects.size(); ++j)
|
||||
{
|
||||
int score1;
|
||||
int score2;
|
||||
|
||||
if (rects[j].width == freeRect.width && rects[j].height == freeRect.height)
|
||||
{
|
||||
bestRect = j;
|
||||
bestFlipped = false;
|
||||
bestScore1 = bestScore2 = std::numeric_limits<int>::max();
|
||||
break;
|
||||
}
|
||||
else if (rects[j].width <= freeRect.width && rects[j].height <= freeRect.height)
|
||||
{
|
||||
CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, false, score1, score2);
|
||||
|
||||
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
|
||||
{
|
||||
bestRect = j;
|
||||
bestScore1 = score1;
|
||||
bestScore2 = score2;
|
||||
bestFlipped = false;
|
||||
bestSplitHorizontal = false;
|
||||
}
|
||||
|
||||
CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, true, score1, score2);
|
||||
|
||||
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
|
||||
{
|
||||
bestRect = j;
|
||||
bestScore1 = score1;
|
||||
bestScore2 = score2;
|
||||
bestFlipped = false;
|
||||
bestSplitHorizontal = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rects[j].height == freeRect.width && rects[j].width == freeRect.height)
|
||||
{
|
||||
bestRect = j;
|
||||
bestFlipped = true;
|
||||
bestScore1 = bestScore2 = std::numeric_limits<int>::max();
|
||||
break;
|
||||
}
|
||||
else if (rects[j].height <= freeRect.width && rects[j].width <= freeRect.height)
|
||||
{
|
||||
CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, false, score1, score2);
|
||||
|
||||
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
|
||||
{
|
||||
bestRect = j;
|
||||
bestScore1 = score1;
|
||||
bestScore2 = score2;
|
||||
bestFlipped = true;
|
||||
bestSplitHorizontal = false;
|
||||
}
|
||||
|
||||
CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, true, score1, score2);
|
||||
|
||||
if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
|
||||
{
|
||||
bestRect = j;
|
||||
bestScore1 = score1;
|
||||
bestScore2 = score2;
|
||||
bestFlipped = true;
|
||||
bestSplitHorizontal = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestScore1 >= 0)
|
||||
{
|
||||
Rect newNode;
|
||||
newNode.x = freeRect.x;
|
||||
newNode.y = freeRect.y;
|
||||
newNode.width = rects[bestRect].width;
|
||||
newNode.height = rects[bestRect].height;
|
||||
if (bestFlipped)
|
||||
std::swap(newNode.width, newNode.height);
|
||||
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
SplitFreeRectAlongAxis(freeRect, newNode, bestSplitHorizontal);
|
||||
|
||||
rects.erase(rects.begin() + bestRect);
|
||||
|
||||
if (merge)
|
||||
MergeFreeList();
|
||||
|
||||
usedRectangles.push_back(newNode);
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Add(newNode);
|
||||
#endif
|
||||
}
|
||||
|
||||
freeRectangles.erase(freeRectangles.begin());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice,
|
||||
GuillotineSplitHeuristic splitMethod)
|
||||
{
|
||||
// Find where to put the new rectangle.
|
||||
int freeNodeIndex = 0;
|
||||
Rect newRect = FindPositionForNewNode(width, height, rectChoice, &freeNodeIndex);
|
||||
|
||||
// Abort if we didn't have enough space in the bin.
|
||||
if (newRect.height == 0)
|
||||
return newRect;
|
||||
|
||||
// Remove the space that was just consumed by the new rectangle.
|
||||
SplitFreeRectByHeuristic(freeRectangles[freeNodeIndex], newRect, splitMethod);
|
||||
freeRectangles.Delete(freeNodeIndex);
|
||||
|
||||
// Perform a Rectangle Merge step if desired.
|
||||
if (merge)
|
||||
MergeFreeList();
|
||||
|
||||
// Remember the new used rectangle.
|
||||
usedRectangles.Push(newRect);
|
||||
|
||||
// Check that we're really producing correct packings here.
|
||||
assert(disjointRects.Add(newRect) == true);
|
||||
|
||||
return newRect;
|
||||
}
|
||||
|
||||
/// Computes the ratio of used surface area to the total bin area.
|
||||
float GuillotineBinPack::Occupancy() const
|
||||
{
|
||||
///\todo The occupancy rate could be cached/tracked incrementally instead
|
||||
/// of looping through the list of packed rectangles here.
|
||||
unsigned long usedSurfaceArea = 0;
|
||||
for(unsigned i = 0; i < usedRectangles.Size(); ++i)
|
||||
usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
|
||||
|
||||
return (float)usedSurfaceArea / (binWidth * binHeight);
|
||||
}
|
||||
|
||||
/// Returns the heuristic score value for placing a rectangle of size width*height into freeRect. Does not try to rotate.
|
||||
int GuillotineBinPack::ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice)
|
||||
{
|
||||
switch(rectChoice)
|
||||
{
|
||||
case RectBestAreaFit: return ScoreBestAreaFit(width, height, freeRect);
|
||||
case RectBestShortSideFit: return ScoreBestShortSideFit(width, height, freeRect);
|
||||
case RectBestLongSideFit: return ScoreBestLongSideFit(width, height, freeRect);
|
||||
case RectWorstAreaFit: return ScoreWorstAreaFit(width, height, freeRect);
|
||||
case RectWorstShortSideFit: return ScoreWorstShortSideFit(width, height, freeRect);
|
||||
case RectWorstLongSideFit: return ScoreWorstLongSideFit(width, height, freeRect);
|
||||
default: assert(false); return INT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreBestAreaFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
return freeRect.width * freeRect.height - width * height;
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreBestShortSideFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
int leftoverHoriz = abs(freeRect.width - width);
|
||||
int leftoverVert = abs(freeRect.height - height);
|
||||
int leftover = MIN(leftoverHoriz, leftoverVert);
|
||||
return leftover;
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreBestLongSideFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
int leftoverHoriz = abs(freeRect.width - width);
|
||||
int leftoverVert = abs(freeRect.height - height);
|
||||
int leftover = MAX(leftoverHoriz, leftoverVert);
|
||||
return leftover;
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreWorstAreaFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
return -ScoreBestAreaFit(width, height, freeRect);
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreWorstShortSideFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
return -ScoreBestShortSideFit(width, height, freeRect);
|
||||
}
|
||||
|
||||
int GuillotineBinPack::ScoreWorstLongSideFit(int width, int height, const Rect &freeRect)
|
||||
{
|
||||
return -ScoreBestLongSideFit(width, height, freeRect);
|
||||
}
|
||||
|
||||
Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex)
|
||||
{
|
||||
Rect bestNode;
|
||||
memset(&bestNode, 0, sizeof(Rect));
|
||||
|
||||
int bestScore = INT_MAX;
|
||||
|
||||
/// Try each free rectangle to find the best one for placement.
|
||||
for(unsigned i = 0; i < freeRectangles.Size(); ++i)
|
||||
{
|
||||
// If this is a perfect fit upright, choose it immediately.
|
||||
if (width == freeRectangles[i].width && height == freeRectangles[i].height)
|
||||
{
|
||||
bestNode.x = freeRectangles[i].x;
|
||||
bestNode.y = freeRectangles[i].y;
|
||||
bestNode.width = width;
|
||||
bestNode.height = height;
|
||||
bestScore = INT_MIN;
|
||||
*nodeIndex = i;
|
||||
assert(disjointRects.Disjoint(bestNode));
|
||||
break;
|
||||
}
|
||||
// If this is a perfect fit sideways, choose it.
|
||||
/* else if (height == freeRectangles[i].width && width == freeRectangles[i].height)
|
||||
{
|
||||
bestNode.x = freeRectangles[i].x;
|
||||
bestNode.y = freeRectangles[i].y;
|
||||
bestNode.width = height;
|
||||
bestNode.height = width;
|
||||
bestScore = INT_MIN;
|
||||
*nodeIndex = i;
|
||||
assert(disjointRects.Disjoint(bestNode));
|
||||
break;
|
||||
}
|
||||
*/ // Does the rectangle fit upright?
|
||||
else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height)
|
||||
{
|
||||
int score = ScoreByHeuristic(width, height, freeRectangles[i], rectChoice);
|
||||
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestNode.x = freeRectangles[i].x;
|
||||
bestNode.y = freeRectangles[i].y;
|
||||
bestNode.width = width;
|
||||
bestNode.height = height;
|
||||
bestScore = score;
|
||||
*nodeIndex = i;
|
||||
assert(disjointRects.Disjoint(bestNode));
|
||||
}
|
||||
}
|
||||
// Does the rectangle fit sideways?
|
||||
/* else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height)
|
||||
{
|
||||
int score = ScoreByHeuristic(height, width, freeRectangles[i], rectChoice);
|
||||
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestNode.x = freeRectangles[i].x;
|
||||
bestNode.y = freeRectangles[i].y;
|
||||
bestNode.width = height;
|
||||
bestNode.height = width;
|
||||
bestScore = score;
|
||||
*nodeIndex = i;
|
||||
assert(disjointRects.Disjoint(bestNode));
|
||||
}
|
||||
}
|
||||
*/ }
|
||||
return bestNode;
|
||||
}
|
||||
|
||||
void GuillotineBinPack::SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method)
|
||||
{
|
||||
// Compute the lengths of the leftover area.
|
||||
const int w = freeRect.width - placedRect.width;
|
||||
const int h = freeRect.height - placedRect.height;
|
||||
|
||||
// Placing placedRect into freeRect results in an L-shaped free area, which must be split into
|
||||
// two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line.
|
||||
// We have two choices: horizontal or vertical.
|
||||
|
||||
// Use the given heuristic to decide which choice to make.
|
||||
|
||||
bool splitHorizontal;
|
||||
switch(method)
|
||||
{
|
||||
case SplitShorterLeftoverAxis:
|
||||
// Split along the shorter leftover axis.
|
||||
splitHorizontal = (w <= h);
|
||||
break;
|
||||
case SplitLongerLeftoverAxis:
|
||||
// Split along the longer leftover axis.
|
||||
splitHorizontal = (w > h);
|
||||
break;
|
||||
case SplitMinimizeArea:
|
||||
// Maximize the larger area == minimize the smaller area.
|
||||
// Tries to make the single bigger rectangle.
|
||||
splitHorizontal = (placedRect.width * h > w * placedRect.height);
|
||||
break;
|
||||
case SplitMaximizeArea:
|
||||
// Maximize the smaller area == minimize the larger area.
|
||||
// Tries to make the rectangles more even-sized.
|
||||
splitHorizontal = (placedRect.width * h <= w * placedRect.height);
|
||||
break;
|
||||
case SplitShorterAxis:
|
||||
// Split along the shorter total axis.
|
||||
splitHorizontal = (freeRect.width <= freeRect.height);
|
||||
break;
|
||||
case SplitLongerAxis:
|
||||
// Split along the longer total axis.
|
||||
splitHorizontal = (freeRect.width > freeRect.height);
|
||||
break;
|
||||
default:
|
||||
splitHorizontal = true;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Perform the actual split.
|
||||
SplitFreeRectAlongAxis(freeRect, placedRect, splitHorizontal);
|
||||
}
|
||||
|
||||
/// This function will add the two generated rectangles into the freeRectangles array. The caller is expected to
|
||||
/// remove the original rectangle from the freeRectangles array after that.
|
||||
void GuillotineBinPack::SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal)
|
||||
{
|
||||
// Form the two new rectangles.
|
||||
Rect bottom;
|
||||
bottom.x = freeRect.x;
|
||||
bottom.y = freeRect.y + placedRect.height;
|
||||
bottom.height = freeRect.height - placedRect.height;
|
||||
|
||||
Rect right;
|
||||
right.x = freeRect.x + placedRect.width;
|
||||
right.y = freeRect.y;
|
||||
right.width = freeRect.width - placedRect.width;
|
||||
|
||||
if (splitHorizontal)
|
||||
{
|
||||
bottom.width = freeRect.width;
|
||||
right.height = placedRect.height;
|
||||
}
|
||||
else // Split vertically
|
||||
{
|
||||
bottom.width = placedRect.width;
|
||||
right.height = freeRect.height;
|
||||
}
|
||||
|
||||
// Add the new rectangles into the free rectangle pool if they weren't degenerate.
|
||||
if (bottom.width > 0 && bottom.height > 0)
|
||||
freeRectangles.Push(bottom);
|
||||
if (right.width > 0 && right.height > 0)
|
||||
freeRectangles.Push(right);
|
||||
|
||||
assert(disjointRects.Disjoint(bottom));
|
||||
assert(disjointRects.Disjoint(right));
|
||||
}
|
||||
|
||||
void GuillotineBinPack::MergeFreeList()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
DisjointRectCollection test;
|
||||
for(unsigned i = 0; i < freeRectangles.Size(); ++i)
|
||||
assert(test.Add(freeRectangles[i]) == true);
|
||||
#endif
|
||||
|
||||
// Do a Theta(n^2) loop to see if any pair of free rectangles could me merged into one.
|
||||
// Note that we miss any opportunities to merge three rectangles into one. (should call this function again to detect that)
|
||||
for(unsigned i = 0; i < freeRectangles.Size(); ++i)
|
||||
for(unsigned j = i+1; j < freeRectangles.Size(); ++j)
|
||||
{
|
||||
if (freeRectangles[i].width == freeRectangles[j].width && freeRectangles[i].x == freeRectangles[j].x)
|
||||
{
|
||||
if (freeRectangles[i].y == freeRectangles[j].y + freeRectangles[j].height)
|
||||
{
|
||||
freeRectangles[i].y -= freeRectangles[j].height;
|
||||
freeRectangles[i].height += freeRectangles[j].height;
|
||||
freeRectangles.Delete(j);
|
||||
--j;
|
||||
}
|
||||
else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y)
|
||||
{
|
||||
freeRectangles[i].height += freeRectangles[j].height;
|
||||
freeRectangles.Delete(j);
|
||||
--j;
|
||||
}
|
||||
}
|
||||
else if (freeRectangles[i].height == freeRectangles[j].height && freeRectangles[i].y == freeRectangles[j].y)
|
||||
{
|
||||
if (freeRectangles[i].x == freeRectangles[j].x + freeRectangles[j].width)
|
||||
{
|
||||
freeRectangles[i].x -= freeRectangles[j].width;
|
||||
freeRectangles[i].width += freeRectangles[j].width;
|
||||
freeRectangles.Delete(j);
|
||||
--j;
|
||||
}
|
||||
else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x)
|
||||
{
|
||||
freeRectangles[i].width += freeRectangles[j].width;
|
||||
freeRectangles.Delete(j);
|
||||
--j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
test.Clear();
|
||||
for(unsigned i = 0; i < freeRectangles.Size(); ++i)
|
||||
assert(test.Add(freeRectangles[i]) == true);
|
||||
#endif
|
||||
}
|
135
src/GuillotineBinPack.h
Normal file
135
src/GuillotineBinPack.h
Normal file
|
@ -0,0 +1,135 @@
|
|||
/** @file GuillotineBinPack.h
|
||||
@author Jukka Jylänki
|
||||
|
||||
@brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
|
||||
|
||||
This work is released to Public Domain, do whatever you want with it.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "tarray.h"
|
||||
|
||||
#include "Rect.h"
|
||||
|
||||
/** GuillotineBinPack implements different variants of bin packer algorithms that use the GUILLOTINE data structure
|
||||
to keep track of the free space of the bin where rectangles may be placed. */
|
||||
class GuillotineBinPack
|
||||
{
|
||||
public:
|
||||
/// The initial bin size will be (0,0). Call Init to set the bin size.
|
||||
GuillotineBinPack();
|
||||
|
||||
/// Initializes a new bin of the given size.
|
||||
GuillotineBinPack(int width, int height);
|
||||
|
||||
/// (Re)initializes the packer to an empty bin of width x height units. Call whenever
|
||||
/// you need to restart with a new bin.
|
||||
void Init(int width, int height);
|
||||
|
||||
/// Specifies the different choice heuristics that can be used when deciding which of the free subrectangles
|
||||
/// to place the to-be-packed rectangle into.
|
||||
enum FreeRectChoiceHeuristic
|
||||
{
|
||||
RectBestAreaFit, ///< -BAF
|
||||
RectBestShortSideFit, ///< -BSSF
|
||||
RectBestLongSideFit, ///< -BLSF
|
||||
RectWorstAreaFit, ///< -WAF
|
||||
RectWorstShortSideFit, ///< -WSSF
|
||||
RectWorstLongSideFit ///< -WLSF
|
||||
};
|
||||
|
||||
/// Specifies the different choice heuristics that can be used when the packer needs to decide whether to
|
||||
/// subdivide the remaining free space in horizontal or vertical direction.
|
||||
enum GuillotineSplitHeuristic
|
||||
{
|
||||
SplitShorterLeftoverAxis, ///< -SLAS
|
||||
SplitLongerLeftoverAxis, ///< -LLAS
|
||||
SplitMinimizeArea, ///< -MINAS, Try to make a single big rectangle at the expense of making the other small.
|
||||
SplitMaximizeArea, ///< -MAXAS, Try to make both remaining rectangles as even-sized as possible.
|
||||
SplitShorterAxis, ///< -SAS
|
||||
SplitLongerAxis ///< -LAS
|
||||
};
|
||||
|
||||
/// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned
|
||||
/// struct will have the width and height values swapped.
|
||||
/// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure
|
||||
/// tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up
|
||||
/// some extra time.
|
||||
/// @param rectChoice The free rectangle choice heuristic rule to use.
|
||||
/// @param splitMethod The free rectangle split heuristic rule to use.
|
||||
Rect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
|
||||
|
||||
/// Inserts a list of rectangles into the bin.
|
||||
/// @param rects The list of rectangles to add. This list will be destroyed in the packing process.
|
||||
/// @param dst The outputted list of rectangles. Note that the indices will not correspond to the input indices.
|
||||
/// @param merge If true, performs Rectangle Merge operations during the packing process.
|
||||
/// @param rectChoice The free rectangle choice heuristic rule to use.
|
||||
/// @param splitMethod The free rectangle split heuristic rule to use.
|
||||
void Insert(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge,
|
||||
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
|
||||
|
||||
// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
|
||||
// void InsertMaxFitting(TArray<RectSize> &rects, TArray<Rect> &dst, bool merge,
|
||||
// FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod);
|
||||
|
||||
/// Computes the ratio of used/total surface area. 0.00 means no space is yet used, 1.00 means the whole bin is used.
|
||||
float Occupancy() const;
|
||||
|
||||
/// Returns the internal list of disjoint rectangles that track the free area of the bin. You may alter this vector
|
||||
/// any way desired, as long as the end result still is a list of disjoint rectangles.
|
||||
TArray<Rect> &GetFreeRectangles() { return freeRectangles; }
|
||||
|
||||
/// Returns the list of packed rectangles. You may alter this vector at will, for example, you can move a Rect from
|
||||
/// this list to the Free Rectangles list to free up space on-the-fly, but notice that this causes fragmentation.
|
||||
TArray<Rect> &GetUsedRectangles() { return usedRectangles; }
|
||||
|
||||
/// Performs a Rectangle Merge operation. This procedure looks for adjacent free rectangles and merges them if they
|
||||
/// can be represented with a single rectangle. Takes up Theta(|freeRectangles|^2) time.
|
||||
void MergeFreeList();
|
||||
|
||||
#ifdef _DEBUG
|
||||
void DelDisjoint(const Rect &r) { disjointRects.Del(r); }
|
||||
#endif
|
||||
|
||||
private:
|
||||
int binWidth;
|
||||
int binHeight;
|
||||
|
||||
/// Stores a list of all the rectangles that we have packed so far. This is used only to compute the Occupancy ratio,
|
||||
/// so if you want to have the packer consume less memory, this can be removed.
|
||||
TArray<Rect> usedRectangles;
|
||||
|
||||
/// Stores a list of rectangles that represents the free area of the bin. This rectangles in this list are disjoint.
|
||||
TArray<Rect> freeRectangles;
|
||||
|
||||
#ifdef _DEBUG
|
||||
/// Used to track that the packer produces proper packings.
|
||||
DisjointRectCollection disjointRects;
|
||||
#endif
|
||||
|
||||
/// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into.
|
||||
/// Running time is Theta(|freeRectangles|).
|
||||
/// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new
|
||||
/// rect was placed.
|
||||
/// @return A Rect structure that represents the placement of the new rect into the best free rectangle.
|
||||
Rect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex);
|
||||
|
||||
static int ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice);
|
||||
// The following functions compute (penalty) score values if a rect of the given size was placed into the
|
||||
// given free rectangle. In these score values, smaller is better.
|
||||
|
||||
static int ScoreBestAreaFit(int width, int height, const Rect &freeRect);
|
||||
static int ScoreBestShortSideFit(int width, int height, const Rect &freeRect);
|
||||
static int ScoreBestLongSideFit(int width, int height, const Rect &freeRect);
|
||||
|
||||
static int ScoreWorstAreaFit(int width, int height, const Rect &freeRect);
|
||||
static int ScoreWorstShortSideFit(int width, int height, const Rect &freeRect);
|
||||
static int ScoreWorstLongSideFit(int width, int height, const Rect &freeRect);
|
||||
|
||||
/// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it.
|
||||
/// Determines the split axis by using the given heuristic.
|
||||
void SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method);
|
||||
|
||||
/// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis.
|
||||
void SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal);
|
||||
};
|
94
src/Rect.h
Normal file
94
src/Rect.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/** @file Rect.h
|
||||
@author Jukka Jylänki
|
||||
|
||||
This work is released to Public Domain, do whatever you want with it.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
struct RectSize
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct Rect
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
/// Performs a lexicographic compare on (rect short side, rect long side).
|
||||
/// @return -1 if the smaller side of a is shorter than the smaller side of b, 1 if the other way around.
|
||||
/// If they are equal, the larger side length is used as a tie-breaker.
|
||||
/// If the rectangles are of same size, returns 0.
|
||||
int CompareRectShortSide(const Rect &a, const Rect &b);
|
||||
|
||||
/// Performs a lexicographic compare on (x, y, width, height).
|
||||
int NodeSortCmp(const Rect &a, const Rect &b);
|
||||
|
||||
/// Returns true if a is contained in b.
|
||||
bool IsContainedIn(const Rect &a, const Rect &b);
|
||||
|
||||
#ifdef _DEBUG
|
||||
class DisjointRectCollection
|
||||
{
|
||||
public:
|
||||
TArray<Rect> rects;
|
||||
|
||||
bool Add(const Rect &r)
|
||||
{
|
||||
// Degenerate rectangles are ignored.
|
||||
if (r.width == 0 || r.height == 0)
|
||||
return true;
|
||||
|
||||
if (!Disjoint(r))
|
||||
return false;
|
||||
rects.Push(r);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Del(const Rect &r)
|
||||
{
|
||||
for(unsigned i = 0; i < rects.Size(); ++i)
|
||||
{
|
||||
if(r.x == rects[i].x && r.y == rects[i].y && r.width == rects[i].width && r.height == rects[i].height)
|
||||
{
|
||||
rects.Delete(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
rects.Clear();
|
||||
}
|
||||
|
||||
bool Disjoint(const Rect &r) const
|
||||
{
|
||||
// Degenerate rectangles are ignored.
|
||||
if (r.width == 0 || r.height == 0)
|
||||
return true;
|
||||
|
||||
for(unsigned i = 0; i < rects.Size(); ++i)
|
||||
if (!Disjoint(rects[i], r))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool Disjoint(const Rect &a, const Rect &b)
|
||||
{
|
||||
if (a.x + a.width <= b.x ||
|
||||
b.x + b.width <= a.x ||
|
||||
a.y + a.height <= b.y ||
|
||||
b.y + b.height <= a.y)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif
|
330
src/SkylineBinPack.cpp
Normal file
330
src/SkylineBinPack.cpp
Normal file
|
@ -0,0 +1,330 @@
|
|||
/** @file SkylineBinPack.cpp
|
||||
@author Jukka Jylänki
|
||||
|
||||
@brief Implements different bin packer algorithms that use the SKYLINE data structure.
|
||||
|
||||
This work is released to Public Domain, do whatever you want with it.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include "templates.h"
|
||||
|
||||
#include "SkylineBinPack.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
SkylineBinPack::SkylineBinPack()
|
||||
:binWidth(0),
|
||||
binHeight(0)
|
||||
{
|
||||
}
|
||||
|
||||
SkylineBinPack::SkylineBinPack(int width, int height, bool useWasteMap)
|
||||
{
|
||||
Init(width, height, useWasteMap);
|
||||
}
|
||||
|
||||
void SkylineBinPack::Init(int width, int height, bool useWasteMap_)
|
||||
{
|
||||
binWidth = width;
|
||||
binHeight = height;
|
||||
|
||||
useWasteMap = useWasteMap_;
|
||||
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Clear();
|
||||
#endif
|
||||
|
||||
usedSurfaceArea = 0;
|
||||
skyLine.Clear();
|
||||
SkylineNode node;
|
||||
node.x = 0;
|
||||
node.y = 0;
|
||||
node.width = binWidth;
|
||||
skyLine.Push(node);
|
||||
|
||||
if (useWasteMap)
|
||||
{
|
||||
wasteMap.Init(width, height);
|
||||
wasteMap.GetFreeRectangles().Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SkylineBinPack::Insert(TArray<RectSize> &rects, TArray<Rect> &dst)
|
||||
{
|
||||
dst.Clear();
|
||||
|
||||
while(rects.Size() > 0)
|
||||
{
|
||||
Rect bestNode;
|
||||
int bestScore1 = INT_MAX;
|
||||
int bestScore2 = INT_MAX;
|
||||
int bestSkylineIndex = -1;
|
||||
int bestRectIndex = -1;
|
||||
for(unsigned i = 0; i < rects.Size(); ++i)
|
||||
{
|
||||
Rect newNode;
|
||||
int score1;
|
||||
int score2;
|
||||
int index;
|
||||
newNode = FindPositionForNewNodeMinWaste(rects[i].width, rects[i].height, score2, score1, index);
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
if (newNode.height != 0)
|
||||
{
|
||||
if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
|
||||
{
|
||||
bestNode = newNode;
|
||||
bestScore1 = score1;
|
||||
bestScore2 = score2;
|
||||
bestSkylineIndex = index;
|
||||
bestRectIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestRectIndex == -1)
|
||||
return;
|
||||
|
||||
// Perform the actual packing.
|
||||
assert(disjointRects.Disjoint(bestNode));
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Add(bestNode);
|
||||
#endif
|
||||
AddSkylineLevel(bestSkylineIndex, bestNode);
|
||||
usedSurfaceArea += rects[bestRectIndex].width * rects[bestRectIndex].height;
|
||||
rects.Delete(bestRectIndex);
|
||||
dst.Push(bestNode);
|
||||
}
|
||||
}
|
||||
|
||||
Rect SkylineBinPack::Insert(int width, int height)
|
||||
{
|
||||
// First try to pack this rectangle into the waste map, if it fits.
|
||||
Rect node = wasteMap.Insert(width, height, true, GuillotineBinPack::RectBestShortSideFit,
|
||||
GuillotineBinPack::SplitMaximizeArea);
|
||||
assert(disjointRects.Disjoint(node));
|
||||
|
||||
if (node.height != 0)
|
||||
{
|
||||
Rect newNode;
|
||||
newNode.x = node.x;
|
||||
newNode.y = node.y;
|
||||
newNode.width = node.width;
|
||||
newNode.height = node.height;
|
||||
usedSurfaceArea += width * height;
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Add(newNode);
|
||||
#endif
|
||||
return newNode;
|
||||
}
|
||||
|
||||
return InsertMinWaste(width, height);
|
||||
}
|
||||
|
||||
bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y) const
|
||||
{
|
||||
int x = skyLine[skylineNodeIndex].x;
|
||||
if (x + width > binWidth)
|
||||
return false;
|
||||
int widthLeft = width;
|
||||
int i = skylineNodeIndex;
|
||||
y = skyLine[skylineNodeIndex].y;
|
||||
while(widthLeft > 0)
|
||||
{
|
||||
y = MAX(y, skyLine[i].y);
|
||||
if (y + height > binHeight)
|
||||
return false;
|
||||
widthLeft -= skyLine[i].width;
|
||||
++i;
|
||||
assert(i < (int)skyLine.Size() || widthLeft <= 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int SkylineBinPack::ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const
|
||||
{
|
||||
int wastedArea = 0;
|
||||
const int rectLeft = skyLine[skylineNodeIndex].x;
|
||||
const int rectRight = rectLeft + width;
|
||||
for(; skylineNodeIndex < (int)skyLine.Size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex)
|
||||
{
|
||||
if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft)
|
||||
break;
|
||||
|
||||
int leftSide = skyLine[skylineNodeIndex].x;
|
||||
int rightSide = MIN(rectRight, leftSide + skyLine[skylineNodeIndex].width);
|
||||
assert(y >= skyLine[skylineNodeIndex].y);
|
||||
wastedArea += (rightSide - leftSide) * (y - skyLine[skylineNodeIndex].y);
|
||||
}
|
||||
return wastedArea;
|
||||
}
|
||||
|
||||
bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const
|
||||
{
|
||||
bool fits = RectangleFits(skylineNodeIndex, width, height, y);
|
||||
if (fits)
|
||||
wastedArea = ComputeWastedArea(skylineNodeIndex, width, height, y);
|
||||
|
||||
return fits;
|
||||
}
|
||||
|
||||
void SkylineBinPack::AddWasteMapArea(int skylineNodeIndex, int width, int height, int y)
|
||||
{
|
||||
int wastedArea = 0;
|
||||
const int rectLeft = skyLine[skylineNodeIndex].x;
|
||||
const int rectRight = rectLeft + width;
|
||||
for(; skylineNodeIndex < (int)skyLine.Size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex)
|
||||
{
|
||||
if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft)
|
||||
break;
|
||||
|
||||
int leftSide = skyLine[skylineNodeIndex].x;
|
||||
int rightSide = MIN(rectRight, leftSide + skyLine[skylineNodeIndex].width);
|
||||
assert(y >= skyLine[skylineNodeIndex].y);
|
||||
|
||||
Rect waste;
|
||||
waste.x = leftSide;
|
||||
waste.y = skyLine[skylineNodeIndex].y;
|
||||
waste.width = rightSide - leftSide;
|
||||
waste.height = y - skyLine[skylineNodeIndex].y;
|
||||
|
||||
assert(disjointRects.Disjoint(waste));
|
||||
wasteMap.GetFreeRectangles().Push(waste);
|
||||
}
|
||||
}
|
||||
|
||||
void SkylineBinPack::AddWaste(const Rect &waste)
|
||||
{
|
||||
wasteMap.GetFreeRectangles().Push(waste);
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Del(waste);
|
||||
wasteMap.DelDisjoint(waste);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SkylineBinPack::AddSkylineLevel(int skylineNodeIndex, const Rect &rect)
|
||||
{
|
||||
// First track all wasted areas and mark them into the waste map if we're using one.
|
||||
if (useWasteMap)
|
||||
AddWasteMapArea(skylineNodeIndex, rect.width, rect.height, rect.y);
|
||||
|
||||
SkylineNode newNode;
|
||||
newNode.x = rect.x;
|
||||
newNode.y = rect.y + rect.height;
|
||||
newNode.width = rect.width;
|
||||
skyLine.Insert(skylineNodeIndex, newNode);
|
||||
|
||||
assert(newNode.x + newNode.width <= binWidth);
|
||||
assert(newNode.y <= binHeight);
|
||||
|
||||
for(unsigned i = skylineNodeIndex+1; i < skyLine.Size(); ++i)
|
||||
{
|
||||
assert(skyLine[i-1].x <= skyLine[i].x);
|
||||
|
||||
if (skyLine[i].x < skyLine[i-1].x + skyLine[i-1].width)
|
||||
{
|
||||
int shrink = skyLine[i-1].x + skyLine[i-1].width - skyLine[i].x;
|
||||
|
||||
skyLine[i].x += shrink;
|
||||
skyLine[i].width -= shrink;
|
||||
|
||||
if (skyLine[i].width <= 0)
|
||||
{
|
||||
skyLine.Delete(i);
|
||||
--i;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
MergeSkylines();
|
||||
}
|
||||
|
||||
void SkylineBinPack::MergeSkylines()
|
||||
{
|
||||
for(unsigned i = 0; i < skyLine.Size()-1; ++i)
|
||||
if (skyLine[i].y == skyLine[i+1].y)
|
||||
{
|
||||
skyLine[i].width += skyLine[i+1].width;
|
||||
skyLine.Delete(i+1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
Rect SkylineBinPack::InsertMinWaste(int width, int height)
|
||||
{
|
||||
int bestHeight;
|
||||
int bestWastedArea;
|
||||
int bestIndex;
|
||||
Rect newNode = FindPositionForNewNodeMinWaste(width, height, bestHeight, bestWastedArea, bestIndex);
|
||||
|
||||
if (bestIndex != -1)
|
||||
{
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
// Perform the actual packing.
|
||||
AddSkylineLevel(bestIndex, newNode);
|
||||
|
||||
usedSurfaceArea += width * height;
|
||||
#ifdef _DEBUG
|
||||
disjointRects.Add(newNode);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
memset(&newNode, 0, sizeof(newNode));
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
Rect SkylineBinPack::FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const
|
||||
{
|
||||
bestHeight = INT_MAX;
|
||||
bestWastedArea = INT_MAX;
|
||||
bestIndex = -1;
|
||||
Rect newNode;
|
||||
memset(&newNode, 0, sizeof(newNode));
|
||||
for(unsigned i = 0; i < skyLine.Size(); ++i)
|
||||
{
|
||||
int y;
|
||||
int wastedArea;
|
||||
|
||||
if (RectangleFits(i, width, height, y, wastedArea))
|
||||
{
|
||||
if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + height < bestHeight))
|
||||
{
|
||||
bestHeight = y + height;
|
||||
bestWastedArea = wastedArea;
|
||||
bestIndex = i;
|
||||
newNode.x = skyLine[i].x;
|
||||
newNode.y = y;
|
||||
newNode.width = width;
|
||||
newNode.height = height;
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
}
|
||||
}
|
||||
/* if (RectangleFits(i, height, width, y, wastedArea))
|
||||
{
|
||||
if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + width < bestHeight))
|
||||
{
|
||||
bestHeight = y + width;
|
||||
bestWastedArea = wastedArea;
|
||||
bestIndex = i;
|
||||
newNode.x = skyLine[i].x;
|
||||
newNode.y = y;
|
||||
newNode.width = height;
|
||||
newNode.height = width;
|
||||
assert(disjointRects.Disjoint(newNode));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/// Computes the ratio of used surface area.
|
||||
float SkylineBinPack::Occupancy() const
|
||||
{
|
||||
return (float)usedSurfaceArea / (binWidth * binHeight);
|
||||
}
|
88
src/SkylineBinPack.h
Normal file
88
src/SkylineBinPack.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/** @file SkylineBinPack.h
|
||||
@author Jukka Jylänki
|
||||
|
||||
@brief Implements different bin packer algorithms that use the SKYLINE data structure.
|
||||
|
||||
This work is released to Public Domain, do whatever you want with it.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "tarray.h"
|
||||
|
||||
#include "Rect.h"
|
||||
#include "GuillotineBinPack.h"
|
||||
|
||||
/** Implements bin packing algorithms that use the SKYLINE data structure to store the bin contents. Uses
|
||||
GuillotineBinPack as the waste map. */
|
||||
class SkylineBinPack
|
||||
{
|
||||
public:
|
||||
/// Instantiates a bin of size (0,0). Call Init to create a new bin.
|
||||
SkylineBinPack();
|
||||
|
||||
/// Instantiates a bin of the given size.
|
||||
SkylineBinPack(int binWidth, int binHeight, bool useWasteMap);
|
||||
|
||||
/// (Re)initializes the packer to an empty bin of width x height units. Call whenever
|
||||
/// you need to restart with a new bin.
|
||||
void Init(int binWidth, int binHeight, bool useWasteMap);
|
||||
|
||||
/// Inserts the given list of rectangles in an offline/batch mode, possibly rotated.
|
||||
/// @param rects The list of rectangles to insert. This vector will be destroyed in the process.
|
||||
/// @param dst [out] This list will contain the packed rectangles. The indices will not correspond to that of rects.
|
||||
/// @param method The rectangle placement rule to use when packing.
|
||||
void Insert(TArray<RectSize> &rects, TArray<Rect> &dst);
|
||||
|
||||
/// Inserts a single rectangle into the bin, possibly rotated.
|
||||
Rect Insert(int width, int height);
|
||||
|
||||
/// Adds a rectangle to the waste list. It must have been previously returned by
|
||||
/// Insert or the results are undefined.
|
||||
void AddWaste(const Rect &rect);
|
||||
|
||||
/// Computes the ratio of used surface area to the total bin area.
|
||||
float Occupancy() const;
|
||||
|
||||
private:
|
||||
int binWidth;
|
||||
int binHeight;
|
||||
|
||||
#ifdef _DEBUG
|
||||
DisjointRectCollection disjointRects;
|
||||
#endif
|
||||
|
||||
/// Represents a single level (a horizontal line) of the skyline/horizon/envelope.
|
||||
struct SkylineNode
|
||||
{
|
||||
/// The starting x-coordinate (leftmost).
|
||||
int x;
|
||||
|
||||
/// The y-coordinate of the skyline level line.
|
||||
int y;
|
||||
|
||||
/// The line width. The ending coordinate (inclusive) will be x+width-1.
|
||||
int width;
|
||||
};
|
||||
|
||||
TArray<SkylineNode> skyLine;
|
||||
|
||||
unsigned long usedSurfaceArea;
|
||||
|
||||
/// If true, we use the GuillotineBinPack structure to recover wasted areas into a waste map.
|
||||
bool useWasteMap;
|
||||
GuillotineBinPack wasteMap;
|
||||
|
||||
Rect InsertMinWaste(int width, int height);
|
||||
Rect FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const;
|
||||
|
||||
bool RectangleFits(int skylineNodeIndex, int width, int height, int &y) const;
|
||||
bool RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const;
|
||||
int ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const;
|
||||
|
||||
void AddWasteMapArea(int skylineNodeIndex, int width, int height, int y);
|
||||
|
||||
void AddSkylineLevel(int skylineNodeIndex, const Rect &rect);
|
||||
|
||||
/// Merges all skyline nodes that are at the same level.
|
||||
void MergeSkylines();
|
||||
};
|
|
@ -896,6 +896,7 @@ public:
|
|||
|
||||
struct sector_t *floorsector;
|
||||
FTextureID floorpic; // contacted sec floorpic
|
||||
int floorterrain;
|
||||
struct sector_t *ceilingsector;
|
||||
FTextureID ceilingpic; // contacted sec ceilingpic
|
||||
fixed_t radius, height; // for movement checking
|
||||
|
|
|
@ -423,8 +423,8 @@ void FCajunMaster::RemoveAllBots (bool fromlist)
|
|||
}
|
||||
}
|
||||
}
|
||||
FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, players[i].mo, true, i, true);
|
||||
ClearPlayer (i, !fromlist);
|
||||
FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1713,6 +1713,8 @@ void G_DoPlayerPop(int playernum)
|
|||
|
||||
// [RH] Make the player disappear
|
||||
FBehavior::StaticStopMyScripts(players[playernum].mo);
|
||||
// [RH] Let the scripts know the player left
|
||||
FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, players[playernum].mo, true, playernum, true);
|
||||
if (players[playernum].mo != NULL)
|
||||
{
|
||||
P_DisconnectEffect(players[playernum].mo);
|
||||
|
@ -1726,8 +1728,6 @@ void G_DoPlayerPop(int playernum)
|
|||
players[playernum].mo = NULL;
|
||||
players[playernum].camera = NULL;
|
||||
}
|
||||
// [RH] Let the scripts know the player left
|
||||
FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, NULL, true, playernum);
|
||||
}
|
||||
|
||||
void G_ScreenShot (char *filename)
|
||||
|
|
|
@ -1232,6 +1232,7 @@ void G_FinishTravel ()
|
|||
pawn->dropoffz = pawndup->dropoffz;
|
||||
pawn->floorsector = pawndup->floorsector;
|
||||
pawn->floorpic = pawndup->floorpic;
|
||||
pawn->floorterrain = pawndup->floorterrain;
|
||||
pawn->ceilingsector = pawndup->ceilingsector;
|
||||
pawn->ceilingpic = pawndup->ceilingpic;
|
||||
pawn->floorclip = pawndup->floorclip;
|
||||
|
|
|
@ -512,6 +512,8 @@ xx(damageinterval)
|
|||
xx(leakiness)
|
||||
xx(damageterraineffect)
|
||||
xx(damagehazard)
|
||||
xx(floorterrain)
|
||||
xx(ceilingterrain)
|
||||
|
||||
// USDF keywords
|
||||
xx(Amount)
|
||||
|
|
|
@ -349,7 +349,7 @@ void P_PlayerOnSpecial3DFloor(player_t* player)
|
|||
|
||||
// Apply flat specials (using the ceiling!)
|
||||
P_PlayerOnSpecialFlat(
|
||||
player, TerrainTypes[rover->model->GetTexture(rover->flags & FF_INVERTSECTOR? sector_t::floor : sector_t::ceiling)]);
|
||||
player, rover->model->GetTerrain(rover->top.isceiling));
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -769,6 +769,7 @@ void P_LineOpening_XFloors (FLineOpening &open, AActor * thing, const line_t *li
|
|||
linedef->frontsector->floorplane.ZatPoint(x, y),
|
||||
linedef->backsector->floorplane.ZatPoint(x, y) };
|
||||
FTextureID highestfloorpic;
|
||||
int highestfloorterrain = -1;
|
||||
FTextureID lowestceilingpic;
|
||||
|
||||
highestfloorpic.SetInvalid();
|
||||
|
@ -799,6 +800,7 @@ void P_LineOpening_XFloors (FLineOpening &open, AActor * thing, const line_t *li
|
|||
{
|
||||
highestfloor = ff_top;
|
||||
highestfloorpic = *rover->top.texture;
|
||||
highestfloorterrain = rover->model->GetTerrain(rover->top.isceiling);
|
||||
}
|
||||
if(ff_top > lowestfloor[j] && ff_top <= thing->z + thing->MaxStepHeight) lowestfloor[j] = ff_top;
|
||||
}
|
||||
|
@ -808,6 +810,7 @@ void P_LineOpening_XFloors (FLineOpening &open, AActor * thing, const line_t *li
|
|||
{
|
||||
open.bottom = highestfloor;
|
||||
open.floorpic = highestfloorpic;
|
||||
open.floorterrain = highestfloorterrain;
|
||||
}
|
||||
|
||||
if(lowestceiling < open.top)
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
|
||||
#include "templates.h"
|
||||
#include "p_local.h"
|
||||
#include "p_terrain.h"
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
@ -290,6 +291,8 @@ bool P_LineOpening_3dMidtex(AActor *thing, const line_t *linedef, FLineOpening &
|
|||
open.bottom = tt;
|
||||
open.abovemidtex = true;
|
||||
open.floorpic = linedef->sidedef[0]->GetTexture(side_t::mid);
|
||||
open.floorterrain = TerrainTypes[open.floorpic];
|
||||
|
||||
}
|
||||
// returns true if it touches the midtexture
|
||||
return (abs(thing->z - tt) <= thing->MaxStepHeight);
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#include "actorptrselect.h"
|
||||
#include "farchive.h"
|
||||
#include "decallib.h"
|
||||
#include "p_terrain.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "g_shared/a_pickups.h"
|
||||
|
@ -4459,6 +4460,7 @@ enum EACSFunctions
|
|||
ACSF_Warp, // 92
|
||||
ACSF_GetMaxInventory,
|
||||
ACSF_SetSectorDamage,
|
||||
ACSF_SetSectorTerrain,
|
||||
|
||||
/* Zandronum's - these must be skipped when we reach 99!
|
||||
-100:ResetMap(0),
|
||||
|
@ -5968,6 +5970,18 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound)
|
|||
}
|
||||
break;
|
||||
|
||||
case ACSF_SetSectorTerrain:
|
||||
if (argCount >= 3)
|
||||
{
|
||||
int terrain = P_FindTerrain(FBehavior::StaticLookupString(args[2]));
|
||||
FSectorTagIterator it(args[0]);
|
||||
int s;
|
||||
while ((s = it.Next()) >= 0)
|
||||
{
|
||||
sectors[s].terrainnum[args[1]] = terrain;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -443,6 +443,7 @@ static void LoadSectors (sectortype *bsec)
|
|||
sec->movefactor = ORIG_FRICTION_FACTOR;
|
||||
sec->ColorMap = map;
|
||||
sec->ZoneNumber = 0xFFFF;
|
||||
sec->terrainnum[sector_t::ceiling] = sec->terrainnum[sector_t::floor] = -1;
|
||||
|
||||
if (bsec->floorstat & 4)
|
||||
{
|
||||
|
|
|
@ -293,6 +293,7 @@ struct FLineOpening
|
|||
sector_t *topsec;
|
||||
FTextureID ceilingpic;
|
||||
FTextureID floorpic;
|
||||
int floorterrain;
|
||||
bool touchmidtex;
|
||||
bool abovemidtex;
|
||||
};
|
||||
|
@ -408,6 +409,7 @@ struct FCheckPosition
|
|||
fixed_t ceilingz;
|
||||
fixed_t dropoffz;
|
||||
FTextureID floorpic;
|
||||
int floorterrain;
|
||||
sector_t *floorsector;
|
||||
FTextureID ceilingpic;
|
||||
sector_t *ceilingsector;
|
||||
|
|
|
@ -242,6 +242,7 @@ void P_GetFloorCeilingZ(FCheckPosition &tmf, int flags)
|
|||
tmf.floorz = tmf.dropoffz = sec->floorplane.ZatPoint(tmf.x, tmf.y);
|
||||
tmf.ceilingz = sec->ceilingplane.ZatPoint(tmf.x, tmf.y);
|
||||
tmf.floorpic = sec->GetTexture(sector_t::floor);
|
||||
tmf.floorterrain = sec->GetTerrain(sector_t::floor);
|
||||
tmf.ceilingpic = sec->GetTexture(sector_t::ceiling);
|
||||
}
|
||||
else
|
||||
|
@ -264,6 +265,7 @@ void P_GetFloorCeilingZ(FCheckPosition &tmf, int flags)
|
|||
{
|
||||
tmf.dropoffz = tmf.floorz = ff_top;
|
||||
tmf.floorpic = *rover->top.texture;
|
||||
tmf.floorterrain = rover->model->GetTerrain(rover->top.isceiling);
|
||||
}
|
||||
}
|
||||
if (ff_bottom <= tmf.ceilingz && ff_bottom > tmf.z + tmf.thing->height)
|
||||
|
@ -304,6 +306,7 @@ void P_FindFloorCeiling(AActor *actor, int flags)
|
|||
tmf.floorz = tmf.dropoffz = actor->floorz;
|
||||
tmf.ceilingz = actor->ceilingz;
|
||||
tmf.floorpic = actor->floorpic;
|
||||
tmf.floorterrain = actor->floorterrain;
|
||||
tmf.ceilingpic = actor->ceilingpic;
|
||||
P_GetFloorCeilingZ(tmf, flags);
|
||||
}
|
||||
|
@ -311,6 +314,7 @@ void P_FindFloorCeiling(AActor *actor, int flags)
|
|||
actor->dropoffz = tmf.dropoffz;
|
||||
actor->ceilingz = tmf.ceilingz;
|
||||
actor->floorpic = tmf.floorpic;
|
||||
actor->floorterrain = tmf.floorterrain;
|
||||
actor->floorsector = tmf.floorsector;
|
||||
actor->ceilingpic = tmf.ceilingpic;
|
||||
actor->ceilingsector = tmf.ceilingsector;
|
||||
|
@ -337,6 +341,7 @@ void P_FindFloorCeiling(AActor *actor, int flags)
|
|||
actor->dropoffz = tmf.dropoffz;
|
||||
actor->ceilingz = tmf.ceilingz;
|
||||
actor->floorpic = tmf.floorpic;
|
||||
actor->floorterrain = tmf.floorterrain;
|
||||
actor->floorsector = tmf.floorsector;
|
||||
actor->ceilingpic = tmf.ceilingpic;
|
||||
actor->ceilingsector = tmf.ceilingsector;
|
||||
|
@ -348,6 +353,7 @@ void P_FindFloorCeiling(AActor *actor, int flags)
|
|||
if (actor->Sector != NULL)
|
||||
{
|
||||
actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
|
||||
actor->floorterrain = actor->Sector->GetTerrain(sector_t::floor);
|
||||
actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
|
||||
}
|
||||
}
|
||||
|
@ -455,6 +461,7 @@ bool P_TeleportMove(AActor *thing, fixed_t x, fixed_t y, fixed_t z, bool telefra
|
|||
thing->ceilingz = tmf.ceilingz;
|
||||
thing->floorsector = tmf.floorsector;
|
||||
thing->floorpic = tmf.floorpic;
|
||||
thing->floorterrain = tmf.floorterrain;
|
||||
thing->ceilingsector = tmf.ceilingsector;
|
||||
thing->ceilingpic = tmf.ceilingpic;
|
||||
thing->dropoffz = tmf.dropoffz; // killough 11/98
|
||||
|
@ -529,16 +536,18 @@ void P_PlayerStartStomp(AActor *actor)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
inline fixed_t secfriction(const sector_t *sec)
|
||||
inline fixed_t secfriction(const sector_t *sec, int plane = sector_t::floor)
|
||||
{
|
||||
fixed_t friction = Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].Friction;
|
||||
return friction != 0 ? friction : sec->friction;
|
||||
if (sec->Flags & SECF_FRICTION) return sec->friction;
|
||||
fixed_t friction = Terrains[sec->GetTerrain(plane)].Friction;
|
||||
return friction != 0 ? friction : ORIG_FRICTION;
|
||||
}
|
||||
|
||||
inline fixed_t secmovefac(const sector_t *sec)
|
||||
inline fixed_t secmovefac(const sector_t *sec, int plane = sector_t::floor)
|
||||
{
|
||||
fixed_t movefactor = Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].MoveFactor;
|
||||
return movefactor != 0 ? movefactor : sec->movefactor;
|
||||
if (sec->Flags & SECF_FRICTION) return sec->friction;
|
||||
fixed_t movefactor = Terrains[sec->GetTerrain(plane)].MoveFactor;
|
||||
return movefactor != 0 ? movefactor : ORIG_FRICTION_FACTOR;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
@ -584,11 +593,11 @@ int P_GetFriction(const AActor *mo, int *frictionfactor)
|
|||
mo->z < rover->bottom.plane->ZatPoint(mo->x, mo->y))
|
||||
continue;
|
||||
|
||||
newfriction = secfriction(rover->model);
|
||||
newfriction = secfriction(rover->model, rover->top.isceiling);
|
||||
if (newfriction < friction || friction == ORIG_FRICTION)
|
||||
{
|
||||
friction = newfriction;
|
||||
movefactor = secmovefac(rover->model) >> 1;
|
||||
movefactor = secmovefac(rover->model, rover->top.isceiling) >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -622,16 +631,16 @@ int P_GetFriction(const AActor *mo, int *frictionfactor)
|
|||
else
|
||||
continue;
|
||||
|
||||
newfriction = secfriction(rover->model);
|
||||
newfriction = secfriction(rover->model, rover->top.isceiling);
|
||||
if (newfriction < friction || friction == ORIG_FRICTION)
|
||||
{
|
||||
friction = newfriction;
|
||||
movefactor = secmovefac(rover->model);
|
||||
movefactor = secmovefac(rover->model, rover->top.isceiling);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(sec->Flags & SECF_FRICTION) &&
|
||||
Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].Friction == 0)
|
||||
Terrains[sec->GetTerrain(sector_t::floor)].Friction == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -889,6 +898,7 @@ bool PIT_CheckLine(line_t *ld, const FBoundingBox &box, FCheckPosition &tm)
|
|||
tm.floorz = open.bottom;
|
||||
tm.floorsector = open.bottomsec;
|
||||
tm.floorpic = open.floorpic;
|
||||
tm.floorterrain = open.floorterrain;
|
||||
tm.touchmidtex = open.touchmidtex;
|
||||
tm.abovemidtex = open.abovemidtex;
|
||||
tm.thing->BlockingLine = ld;
|
||||
|
@ -1434,6 +1444,7 @@ bool P_CheckPosition(AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm, bo
|
|||
tm.floorz = tm.dropoffz = newsec->floorplane.ZatPoint(x, y);
|
||||
tm.ceilingz = newsec->ceilingplane.ZatPoint(x, y);
|
||||
tm.floorpic = newsec->GetTexture(sector_t::floor);
|
||||
tm.floorterrain = newsec->GetTerrain(sector_t::floor);
|
||||
tm.floorsector = newsec;
|
||||
tm.ceilingpic = newsec->GetTexture(sector_t::ceiling);
|
||||
tm.ceilingsector = newsec;
|
||||
|
@ -1466,6 +1477,7 @@ bool P_CheckPosition(AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm, bo
|
|||
{
|
||||
tm.floorz = tm.dropoffz = ff_top;
|
||||
tm.floorpic = *rover->top.texture;
|
||||
tm.floorterrain = rover->model->GetTerrain(rover->top.isceiling);
|
||||
}
|
||||
if (ff_bottom < tm.ceilingz && abs(delta1) >= abs(delta2))
|
||||
{
|
||||
|
@ -2089,6 +2101,7 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y,
|
|||
thing->ceilingz = tm.ceilingz;
|
||||
thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs
|
||||
thing->floorpic = tm.floorpic;
|
||||
thing->floorterrain = tm.floorterrain;
|
||||
thing->floorsector = tm.floorsector;
|
||||
thing->ceilingpic = tm.ceilingpic;
|
||||
thing->ceilingsector = tm.ceilingsector;
|
||||
|
@ -4949,6 +4962,7 @@ bool P_AdjustFloorCeil(AActor *thing, FChangePosition *cpos)
|
|||
thing->ceilingz = tm.ceilingz;
|
||||
thing->dropoffz = tm.dropoffz; // killough 11/98: remember dropoffs
|
||||
thing->floorpic = tm.floorpic;
|
||||
thing->floorterrain = tm.floorterrain;
|
||||
thing->floorsector = tm.floorsector;
|
||||
thing->ceilingpic = tm.ceilingpic;
|
||||
thing->ceilingsector = tm.ceilingsector;
|
||||
|
|
|
@ -194,6 +194,7 @@ void P_LineOpening (FLineOpening &open, AActor *actor, const line_t *linedef,
|
|||
open.bottom = ff;
|
||||
open.bottomsec = front;
|
||||
open.floorpic = front->GetTexture(sector_t::floor);
|
||||
open.floorterrain = front->GetTerrain(sector_t::floor);
|
||||
open.lowfloor = bf;
|
||||
}
|
||||
else
|
||||
|
@ -201,6 +202,7 @@ void P_LineOpening (FLineOpening &open, AActor *actor, const line_t *linedef,
|
|||
open.bottom = bf;
|
||||
open.bottomsec = back;
|
||||
open.floorpic = back->GetTexture(sector_t::floor);
|
||||
open.floorterrain = back->GetTerrain(sector_t::floor);
|
||||
open.lowfloor = ff;
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +213,7 @@ void P_LineOpening (FLineOpening &open, AActor *actor, const line_t *linedef,
|
|||
open.top = FIXED_MAX;
|
||||
open.bottomsec = NULL;
|
||||
open.floorpic.SetInvalid();
|
||||
open.floorterrain = -1;
|
||||
open.bottom = FIXED_MIN;
|
||||
open.lowfloor = FIXED_MAX;
|
||||
}
|
||||
|
|
|
@ -191,6 +191,10 @@ void AActor::Serialize (FArchive &arc)
|
|||
<< tics
|
||||
<< state
|
||||
<< Damage;
|
||||
if (SaveVersion >= 4530)
|
||||
{
|
||||
P_SerializeTerrain(arc, floorterrain);
|
||||
}
|
||||
if (SaveVersion >= 3227)
|
||||
{
|
||||
arc << projectileKickback;
|
||||
|
@ -4035,6 +4039,7 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t
|
|||
{
|
||||
actor->floorsector = actor->Sector;
|
||||
actor->floorpic = actor->floorsector->GetTexture(sector_t::floor);
|
||||
actor->floorterrain = actor->floorsector->GetTerrain(sector_t::floor);
|
||||
actor->ceilingsector = actor->Sector;
|
||||
actor->ceilingpic = actor->ceilingsector->GetTexture(sector_t::ceiling);
|
||||
}
|
||||
|
@ -4046,6 +4051,7 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t
|
|||
else
|
||||
{
|
||||
actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
|
||||
actor->floorterrain = actor->Sector->GetTerrain(sector_t::floor);
|
||||
actor->floorsector = actor->Sector;
|
||||
actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
|
||||
actor->ceilingsector = actor->Sector;
|
||||
|
@ -4339,7 +4345,7 @@ void AActor::AdjustFloorClip ()
|
|||
sector_t *hsec = m->m_sector->GetHeightSec();
|
||||
if (hsec == NULL && m->m_sector->floorplane.ZatPoint (x, y) == z)
|
||||
{
|
||||
fixed_t clip = Terrains[TerrainTypes[m->m_sector->GetTexture(sector_t::floor)]].FootClip;
|
||||
fixed_t clip = Terrains[m->m_sector->GetTerrain(sector_t::floor)].FootClip;
|
||||
if (clip < shallowestclip)
|
||||
{
|
||||
shallowestclip = clip;
|
||||
|
@ -5303,13 +5309,13 @@ void P_RipperBlood (AActor *mo, AActor *bleeder)
|
|||
|
||||
int P_GetThingFloorType (AActor *thing)
|
||||
{
|
||||
if (thing->floorpic.isValid())
|
||||
if (thing->floorterrain >= 0)
|
||||
{
|
||||
return TerrainTypes[thing->floorpic];
|
||||
return thing->floorterrain;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TerrainTypes[thing->Sector->GetTexture(sector_t::floor)];
|
||||
return thing->Sector->GetTerrain(sector_t::floor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5362,6 +5368,7 @@ bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z
|
|||
}
|
||||
#endif
|
||||
|
||||
// 'force' means, we want this sector's terrain, no matter what.
|
||||
if (!force)
|
||||
{
|
||||
for (unsigned int i = 0; i<sec->e->XFloor.ffloors.Size(); i++)
|
||||
|
@ -5373,7 +5380,7 @@ bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z
|
|||
{
|
||||
if (rover->flags & (FF_SOLID | FF_SWIMMABLE))
|
||||
{
|
||||
terrainnum = TerrainTypes[*rover->top.texture];
|
||||
terrainnum = rover->model->GetTerrain(rover->top.isceiling);
|
||||
goto foundone;
|
||||
}
|
||||
}
|
||||
|
@ -5384,11 +5391,11 @@ bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z
|
|||
hsec = sec->GetHeightSec();
|
||||
if (force || hsec == NULL || !(hsec->MoreFlags & SECF_CLIPFAKEPLANES))
|
||||
{
|
||||
terrainnum = TerrainTypes[sec->GetTexture(sector_t::floor)];
|
||||
terrainnum = sec->GetTerrain(sector_t::floor);
|
||||
}
|
||||
else
|
||||
{
|
||||
terrainnum = TerrainTypes[hsec->GetTexture(sector_t::floor)];
|
||||
terrainnum = hsec->GetTerrain(sector_t::floor);
|
||||
}
|
||||
foundone:
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "farchive.h"
|
||||
#include "p_lnspec.h"
|
||||
#include "p_acs.h"
|
||||
#include "p_terrain.h"
|
||||
|
||||
static void CopyPlayer (player_t *dst, player_t *src, const char *name);
|
||||
static void ReadOnePlayer (FArchive &arc, bool skipload);
|
||||
|
@ -369,6 +370,11 @@ void P_SerializeWorld (FArchive &arc)
|
|||
<< sec->heightsec
|
||||
<< sec->bottommap << sec->midmap << sec->topmap
|
||||
<< sec->gravity;
|
||||
if (SaveVersion >= 4530)
|
||||
{
|
||||
P_SerializeTerrain(arc, sec->terrainnum[0]);
|
||||
P_SerializeTerrain(arc, sec->terrainnum[1]);
|
||||
}
|
||||
if (SaveVersion >= 4529)
|
||||
{
|
||||
arc << sec->damageamount;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "doomstat.h"
|
||||
#include "g_level.h"
|
||||
#include "nodebuild.h"
|
||||
#include "p_terrain.h"
|
||||
#include "po_man.h"
|
||||
#include "farchive.h"
|
||||
#include "r_utility.h"
|
||||
|
@ -873,6 +874,11 @@ void sector_t::TransferSpecial(sector_t *model)
|
|||
Flags = (Flags&~SECF_SPECIALFLAGS) | (model->Flags & SECF_SPECIALFLAGS);
|
||||
}
|
||||
|
||||
int sector_t::GetTerrain(int pos) const
|
||||
{
|
||||
return terrainnum[pos] >= 0 ? terrainnum[pos] : TerrainTypes[GetTexture(pos)];
|
||||
}
|
||||
|
||||
FArchive &operator<< (FArchive &arc, secspecial_t &p)
|
||||
{
|
||||
if (SaveVersion < 4529)
|
||||
|
|
|
@ -1532,6 +1532,7 @@ void P_LoadSectors (MapData *map, FMissingTextureTracker &missingtex)
|
|||
|
||||
ss->gravity = 1.f; // [RH] Default sector gravity of 1.0
|
||||
ss->ZoneNumber = 0xFFFF;
|
||||
ss->terrainnum[sector_t::ceiling] = ss->terrainnum[sector_t::floor] = -1;
|
||||
|
||||
// [RH] Sectors default to white light with the default fade.
|
||||
// If they are outside (have a sky ceiling), they use the outside fog.
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "s_sound.h"
|
||||
#include "p_local.h"
|
||||
#include "templates.h"
|
||||
#include "farchive.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
@ -121,7 +122,6 @@ static void ParseSplash (FScanner &sc);
|
|||
static void ParseTerrain (FScanner &sc);
|
||||
static void ParseFloor (FScanner &sc);
|
||||
static int FindSplash (FName name);
|
||||
static int FindTerrain (FName name);
|
||||
static void GenericParse (FScanner &sc, FGenericParse *parser, const char **keywords,
|
||||
void *fields, const char *type, FName name);
|
||||
static void ParseDamage (FScanner &sc, int keyword, void *fields);
|
||||
|
@ -427,7 +427,7 @@ void ParseTerrain (FScanner &sc)
|
|||
|
||||
sc.MustGetString ();
|
||||
name = sc.String;
|
||||
terrainnum = (int)FindTerrain (name);
|
||||
terrainnum = (int)P_FindTerrain (name);
|
||||
if (terrainnum < 0)
|
||||
{
|
||||
FTerrainDef def;
|
||||
|
@ -637,7 +637,7 @@ static void ParseFloor (FScanner &sc)
|
|||
return;
|
||||
}
|
||||
sc.MustGetString ();
|
||||
terrain = FindTerrain (sc.String);
|
||||
terrain = P_FindTerrain (sc.String);
|
||||
if (terrain == -1)
|
||||
{
|
||||
Printf ("Unknown terrain %s\n", sc.String);
|
||||
|
@ -657,7 +657,7 @@ static void ParseDefault (FScanner &sc)
|
|||
int terrain;
|
||||
|
||||
sc.MustGetString ();
|
||||
terrain = FindTerrain (sc.String);
|
||||
terrain = P_FindTerrain (sc.String);
|
||||
if (terrain == -1)
|
||||
{
|
||||
Printf ("Unknown terrain %s\n", sc.String);
|
||||
|
@ -692,7 +692,7 @@ int FindSplash (FName name)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
int FindTerrain (FName name)
|
||||
int P_FindTerrain (FName name)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
|
@ -705,3 +705,26 @@ int FindTerrain (FName name)
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void P_SerializeTerrain(FArchive &arc, int &terrainnum)
|
||||
{
|
||||
FName val;
|
||||
if (arc.IsStoring())
|
||||
{
|
||||
if (terrainnum < 0 || terrainnum >= (int)Terrains.Size())
|
||||
{
|
||||
val = NAME_Null;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = Terrains[terrainnum].Name;
|
||||
}
|
||||
arc << val;
|
||||
}
|
||||
else
|
||||
{
|
||||
arc << val;
|
||||
terrainnum = P_FindTerrain(val);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,4 +122,8 @@ struct FTerrainDef
|
|||
extern TArray<FSplashDef> Splashes;
|
||||
extern TArray<FTerrainDef> Terrains;
|
||||
|
||||
class FArchive;
|
||||
int P_FindTerrain(FName name);
|
||||
void P_SerializeTerrain(FArchive &arc, int &terrainnum);
|
||||
|
||||
#endif //__P_TERRAIN_H__
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "r_data/colormaps.h"
|
||||
#include "w_wad.h"
|
||||
#include "p_tags.h"
|
||||
#include "p_terrain.h"
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
|
@ -1300,6 +1301,7 @@ public:
|
|||
sec->heightsec = NULL; // sector used to get floor and ceiling height
|
||||
sec->sectornum = index;
|
||||
sec->damageinterval = 32;
|
||||
sec->terrainnum[sector_t::ceiling] = sec->terrainnum[sector_t::floor] = -1;
|
||||
if (floordrop) sec->Flags = SECF_FLOORDROP;
|
||||
// killough 3/7/98: end changes
|
||||
|
||||
|
@ -1550,6 +1552,14 @@ public:
|
|||
Flag(sec->Flags, SECF_HAZARD, key);
|
||||
break;
|
||||
|
||||
case NAME_floorterrain:
|
||||
sec->terrainnum[sector_t::floor] = P_FindTerrain(CheckString(key));
|
||||
break;
|
||||
|
||||
case NAME_ceilingterrain:
|
||||
sec->terrainnum[sector_t::ceiling] = P_FindTerrain(CheckString(key));
|
||||
break;
|
||||
|
||||
case NAME_MoreIds:
|
||||
// delay parsing of the tag string until parsing of the sector is complete
|
||||
// This ensures that the ID is always the first tag in the list.
|
||||
|
|
|
@ -1542,7 +1542,6 @@ static void SpawnPolyobj (int index, int tag, int type)
|
|||
{
|
||||
unsigned int ii;
|
||||
int i;
|
||||
int j;
|
||||
FPolyObj *po = &polyobjs[index];
|
||||
|
||||
for (ii = 0; ii < KnownPolySides.Size(); ++ii)
|
||||
|
|
|
@ -79,6 +79,7 @@ typedef float CGFloat;
|
|||
// From HIToolbox/Events.h
|
||||
enum
|
||||
{
|
||||
kVK_ANSI_F = 0x03,
|
||||
kVK_Return = 0x24,
|
||||
kVK_Tab = 0x30,
|
||||
kVK_Space = 0x31,
|
||||
|
|
|
@ -57,6 +57,8 @@ CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|||
CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
CVAR(Bool, m_filter, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
|
||||
CVAR(Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
||||
|
||||
CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
|
||||
{
|
||||
if (self < 0)
|
||||
|
@ -542,6 +544,16 @@ void ProcessKeyboardEvent(NSEvent* theEvent)
|
|||
return;
|
||||
}
|
||||
|
||||
if (k_allowfullscreentoggle
|
||||
&& (kVK_ANSI_F == keyCode)
|
||||
&& (NSCommandKeyMask & [theEvent modifierFlags])
|
||||
&& (NSKeyDown == [theEvent type])
|
||||
&& ![theEvent isARepeat])
|
||||
{
|
||||
ToggleFullscreen = !ToggleFullscreen;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GUICapture)
|
||||
{
|
||||
ProcessKeyboardEventInMenu(theEvent);
|
||||
|
|
|
@ -752,6 +752,8 @@ struct sector_t
|
|||
Flags &= ~SECF_SPECIALFLAGS;
|
||||
}
|
||||
|
||||
int GetTerrain(int pos) const;
|
||||
|
||||
void TransferSpecial(sector_t *model);
|
||||
void GetSpecial(secspecial_t *spec);
|
||||
void SetSpecial(const secspecial_t *spec);
|
||||
|
@ -787,6 +789,8 @@ struct sector_t
|
|||
// when processed as mobj properties. Fix is to make them sector properties.
|
||||
fixed_t friction, movefactor;
|
||||
|
||||
int terrainnum[2];
|
||||
|
||||
// thinker_t for reversable actions
|
||||
TObjPtr<DSectorEffect> floordata; // jff 2/22/98 make thinkers on
|
||||
TObjPtr<DSectorEffect> ceilingdata; // floors, ceilings, lighting,
|
||||
|
|
|
@ -76,7 +76,7 @@ const char *GetVersionString();
|
|||
|
||||
// Use 4500 as the base git save version, since it's higher than the
|
||||
// SVN revision ever got.
|
||||
#define SAVEVER 4529
|
||||
#define SAVEVER 4530
|
||||
|
||||
#define SAVEVERSTRINGIFY2(x) #x
|
||||
#define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x)
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
#include "v_palette.h"
|
||||
#include "w_wad.h"
|
||||
#include "r_data/colormaps.h"
|
||||
#include "SkylineBinPack.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
@ -90,9 +91,9 @@ IMPLEMENT_CLASS(D3DFB)
|
|||
|
||||
struct D3DFB::PackedTexture
|
||||
{
|
||||
D3DFB::PackingTexture *Owner;
|
||||
D3DFB::Atlas *Owner;
|
||||
|
||||
PackedTexture *Next, **Prev;
|
||||
PackedTexture **Prev, *Next;
|
||||
|
||||
// Pixels this image covers
|
||||
RECT Area;
|
||||
|
@ -104,23 +105,19 @@ struct D3DFB::PackedTexture
|
|||
bool Padded;
|
||||
};
|
||||
|
||||
struct D3DFB::PackingTexture
|
||||
struct D3DFB::Atlas
|
||||
{
|
||||
PackingTexture(D3DFB *fb, int width, int height, D3DFORMAT format);
|
||||
~PackingTexture();
|
||||
Atlas(D3DFB *fb, int width, int height, D3DFORMAT format);
|
||||
~Atlas();
|
||||
|
||||
PackedTexture *GetBestFit(int width, int height, int &area);
|
||||
void AllocateImage(PackedTexture *box, int width, int height);
|
||||
PackedTexture *AllocateBox();
|
||||
void AddEmptyBox(int left, int top, int right, int bottom);
|
||||
PackedTexture *AllocateImage(const Rect &rect, bool padded);
|
||||
void FreeBox(PackedTexture *box);
|
||||
|
||||
PackingTexture *Next;
|
||||
SkylineBinPack Packer;
|
||||
Atlas *Next;
|
||||
IDirect3DTexture9 *Tex;
|
||||
D3DFORMAT Format;
|
||||
PackedTexture *UsedList; // Boxes that contain images
|
||||
PackedTexture *EmptyList; // Boxes that contain empty space
|
||||
PackedTexture *FreeList; // Boxes that are just waiting to be used
|
||||
int Width, Height;
|
||||
bool OneUse;
|
||||
};
|
||||
|
@ -283,7 +280,7 @@ D3DFB::D3DFB (UINT adapter, int width, int height, bool fullscreen)
|
|||
ScreenWipe = NULL;
|
||||
InScene = false;
|
||||
QuadExtra = new BufferedTris[MAX_QUAD_BATCH];
|
||||
Packs = NULL;
|
||||
Atlases = NULL;
|
||||
PixelDoubling = 0;
|
||||
SkipAt = -1;
|
||||
CurrRenderTexture = 0;
|
||||
|
@ -496,7 +493,7 @@ void D3DFB::FillPresentParameters (D3DPRESENT_PARAMETERS *pp, bool fullscreen, b
|
|||
|
||||
bool D3DFB::CreateResources()
|
||||
{
|
||||
Packs = NULL;
|
||||
Atlases = NULL;
|
||||
if (!Windowed)
|
||||
{
|
||||
// Remove the window border in fullscreen mode
|
||||
|
@ -624,8 +621,8 @@ void D3DFB::ReleaseResources ()
|
|||
delete ScreenWipe;
|
||||
ScreenWipe = NULL;
|
||||
}
|
||||
PackingTexture *pack, *next;
|
||||
for (pack = Packs; pack != NULL; pack = next)
|
||||
Atlas *pack, *next;
|
||||
for (pack = Atlases; pack != NULL; pack = next)
|
||||
{
|
||||
next = pack->Next;
|
||||
delete pack;
|
||||
|
@ -1826,8 +1823,9 @@ IDirect3DTexture9 *D3DFB::GetCurrentScreen(D3DPOOL pool)
|
|||
//
|
||||
// D3DFB :: DrawPackedTextures
|
||||
//
|
||||
// DEBUG: Draws the packing textures to the screen, starting with the
|
||||
// 1-based packnum.
|
||||
// DEBUG: Draws the texture atlases to the screen, starting with the
|
||||
// 1-based packnum. Ignores atlases that are flagged for use by one
|
||||
// texture only.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
@ -1838,35 +1836,40 @@ void D3DFB::DrawPackedTextures(int packnum)
|
|||
0xFFFF9999, 0xFF99FF99, 0xFF9999FF, 0xFFFFFF99,
|
||||
0xFFFF99FF, 0xFF99FFFF, 0xFFFFCC99, 0xFF99CCFF
|
||||
};
|
||||
PackingTexture *pack;
|
||||
Atlas *pack;
|
||||
int x = 8, y = 8;
|
||||
|
||||
if (packnum <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
pack = Packs;
|
||||
pack = Atlases;
|
||||
// Find the first texture atlas that is an actual atlas.
|
||||
while (pack != NULL && pack->OneUse)
|
||||
{ // Skip textures that aren't used as packing containers
|
||||
{ // Skip textures that aren't used as atlases
|
||||
pack = pack->Next;
|
||||
}
|
||||
// Skip however many atlases we would have otherwise drawn
|
||||
// until we've skipped <packnum> of them.
|
||||
while (pack != NULL && packnum != 1)
|
||||
{
|
||||
if (!pack->OneUse)
|
||||
{ // Skip textures that aren't used as packing containers
|
||||
{ // Skip textures that aren't used as atlases
|
||||
packnum--;
|
||||
}
|
||||
pack = pack->Next;
|
||||
}
|
||||
// Draw atlases until we run out of room on the screen.
|
||||
while (pack != NULL)
|
||||
{
|
||||
if (pack->OneUse)
|
||||
{ // Skip textures that aren't used as packing containers
|
||||
{ // Skip textures that aren't used as atlases
|
||||
pack = pack->Next;
|
||||
continue;
|
||||
}
|
||||
|
||||
AddColorOnlyQuad(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0));
|
||||
AddColorOnlyRect(x-1, y-1-LBOffsetI, 258, 258, D3DCOLOR_XRGB(255,255,0));
|
||||
AddColorOnlyQuad(x, y-LBOffsetI, 256, 256, D3DCOLOR_ARGB(180,0,0,0));
|
||||
|
||||
CheckQuadBatch();
|
||||
|
||||
|
@ -1876,12 +1879,12 @@ void D3DFB::DrawPackedTextures(int packnum)
|
|||
quad->Group1 = 0;
|
||||
if (pack->Format == D3DFMT_L8/* && !tex->IsGray*/)
|
||||
{
|
||||
quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest;
|
||||
quad->Flags = BQF_WrapUV | BQF_GamePalette/* | BQF_DisableAlphaTest*/;
|
||||
quad->ShaderNum = BQS_PalTex;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest;
|
||||
quad->Flags = BQF_WrapUV/* | BQF_DisableAlphaTest*/;
|
||||
quad->ShaderNum = BQS_Plain;
|
||||
}
|
||||
quad->Palette = NULL;
|
||||
|
@ -1941,16 +1944,6 @@ void D3DFB::DrawPackedTextures(int packnum)
|
|||
VertexPos += 4;
|
||||
IndexPos += 6;
|
||||
|
||||
// Draw entries in the empty list.
|
||||
PackedTexture *box;
|
||||
int emptynum;
|
||||
for (box = pack->EmptyList, emptynum = 0; box != NULL; box = box->Next, emptynum++)
|
||||
{
|
||||
AddColorOnlyQuad(x + box->Area.left, y + box->Area.top - LBOffsetI,
|
||||
box->Area.right - box->Area.left, box->Area.bottom - box->Area.top,
|
||||
empty_colors[emptynum & 7]);
|
||||
}
|
||||
|
||||
x += 256 + 8;
|
||||
if (x > Width - 256)
|
||||
{
|
||||
|
@ -1969,82 +1962,76 @@ void D3DFB::DrawPackedTextures(int packnum)
|
|||
//
|
||||
// D3DFB :: AllocPackedTexture
|
||||
//
|
||||
// Finds space to pack an image inside a packing texture and returns it.
|
||||
// Finds space to pack an image inside a texture atlas and returns it.
|
||||
// Large images and those that need to wrap always get their own textures.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
D3DFB::PackedTexture *D3DFB::AllocPackedTexture(int w, int h, bool wrapping, D3DFORMAT format)
|
||||
{
|
||||
PackingTexture *bestpack;
|
||||
PackedTexture *bestbox;
|
||||
int area;
|
||||
Atlas *pack;
|
||||
Rect box;
|
||||
bool padded;
|
||||
|
||||
// check for 254 to account for padding
|
||||
if (w > 254 || h > 254 || wrapping)
|
||||
{ // Create a new packing texture.
|
||||
bestpack = new PackingTexture(this, w, h, format);
|
||||
bestpack->OneUse = true;
|
||||
bestbox = bestpack->GetBestFit(w, h, area);
|
||||
bestbox->Padded = false;
|
||||
{ // Create a new texture atlas.
|
||||
pack = new Atlas(this, w, h, format);
|
||||
pack->OneUse = true;
|
||||
box = pack->Packer.Insert(w, h);
|
||||
padded = false;
|
||||
}
|
||||
else
|
||||
{ // Try to find space in an existing packing texture.
|
||||
{ // Try to find space in an existing texture atlas.
|
||||
w += 2; // Add padding
|
||||
h += 2;
|
||||
int bestarea = INT_MAX;
|
||||
int bestareaever = w * h;
|
||||
bestpack = NULL;
|
||||
bestbox = NULL;
|
||||
for (PackingTexture *pack = Packs; pack != NULL; pack = pack->Next)
|
||||
for (pack = Atlases; pack != NULL; pack = pack->Next)
|
||||
{
|
||||
// Use the first atlas it fits in.
|
||||
if (pack->Format == format)
|
||||
{
|
||||
PackedTexture *box = pack->GetBestFit(w, h, area);
|
||||
if (area == bestareaever)
|
||||
{ // An exact fit! Use it!
|
||||
bestpack = pack;
|
||||
bestbox = box;
|
||||
break;
|
||||
}
|
||||
if (area < bestarea)
|
||||
box = pack->Packer.Insert(w, h);
|
||||
if (box.width != 0)
|
||||
{
|
||||
bestarea = area;
|
||||
bestpack = pack;
|
||||
bestbox = box;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestpack == NULL)
|
||||
{ // Create a new packing texture.
|
||||
bestpack = new PackingTexture(this, 256, 256, format);
|
||||
bestbox = bestpack->GetBestFit(w, h, bestarea);
|
||||
if (pack == NULL)
|
||||
{ // Create a new texture atlas.
|
||||
pack = new Atlas(this, 256, 256, format);
|
||||
box = pack->Packer.Insert(w, h);
|
||||
}
|
||||
bestbox->Padded = true;
|
||||
padded = true;
|
||||
}
|
||||
bestpack->AllocateImage(bestbox, w, h);
|
||||
return bestbox;
|
||||
assert(box.width != 0 && box.height != 0);
|
||||
return pack->AllocateImage(box, padded);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture Constructor
|
||||
// Atlas Constructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
D3DFB::PackingTexture::PackingTexture(D3DFB *fb, int w, int h, D3DFORMAT format)
|
||||
D3DFB::Atlas::Atlas(D3DFB *fb, int w, int h, D3DFORMAT format)
|
||||
: Packer(w, h, true)
|
||||
{
|
||||
Tex = NULL;
|
||||
Format = format;
|
||||
UsedList = NULL;
|
||||
EmptyList = NULL;
|
||||
FreeList = NULL;
|
||||
OneUse = false;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
Next = NULL;
|
||||
|
||||
Next = fb->Packs;
|
||||
fb->Packs = this;
|
||||
// Attach to the end of the atlas list
|
||||
Atlas **prev = &fb->Atlases;
|
||||
while (*prev != NULL)
|
||||
{
|
||||
prev = &((*prev)->Next);
|
||||
}
|
||||
*prev = this;
|
||||
|
||||
#if 1
|
||||
if (FAILED(fb->D3DDevice->CreateTexture(w, h, 1, 0, format, D3DPOOL_MANAGED, &Tex, NULL)))
|
||||
|
@ -2061,18 +2048,15 @@ D3DFB::PackingTexture::PackingTexture(D3DFB *fb, int w, int h, D3DFORMAT format)
|
|||
}
|
||||
Width = w;
|
||||
Height = h;
|
||||
|
||||
// The whole texture is initially empty.
|
||||
AddEmptyBox(0, 0, w, h);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture Destructor
|
||||
// Atlas Destructor
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
D3DFB::PackingTexture::~PackingTexture()
|
||||
D3DFB::Atlas::~Atlas()
|
||||
{
|
||||
PackedTexture *box, *next;
|
||||
|
||||
|
@ -2082,85 +2066,36 @@ D3DFB::PackingTexture::~PackingTexture()
|
|||
next = box->Next;
|
||||
delete box;
|
||||
}
|
||||
for (box = EmptyList; box != NULL; box = next)
|
||||
{
|
||||
next = box->Next;
|
||||
delete box;
|
||||
}
|
||||
for (box = FreeList; box != NULL; box = next)
|
||||
{
|
||||
next = box->Next;
|
||||
delete box;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture :: GetBestFit
|
||||
//
|
||||
// Returns the empty box that provides the best fit for the requested
|
||||
// dimensions, or NULL if none of them are large enough.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
D3DFB::PackedTexture *D3DFB::PackingTexture::GetBestFit(int w, int h, int &area)
|
||||
{
|
||||
PackedTexture *box;
|
||||
int smallestarea = INT_MAX;
|
||||
PackedTexture *smallestbox = NULL;
|
||||
|
||||
for (box = EmptyList; box != NULL; box = box->Next)
|
||||
{
|
||||
int boxw = box->Area.right - box->Area.left;
|
||||
int boxh = box->Area.bottom - box->Area.top;
|
||||
if (boxw >= w && boxh >= h)
|
||||
{
|
||||
int boxarea = boxw * boxh;
|
||||
if (boxarea < smallestarea)
|
||||
{
|
||||
smallestarea = boxarea;
|
||||
smallestbox = box;
|
||||
if (boxw == w && boxh == h)
|
||||
{ // An exact fit! Use it!
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
area = smallestarea;
|
||||
return smallestbox;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture :: AllocateImage
|
||||
// Atlas :: AllocateImage
|
||||
//
|
||||
// Moves the box from the empty list to the used list, sizing it to the
|
||||
// requested dimensions and adding additional boxes to the empty list if
|
||||
// needed.
|
||||
//
|
||||
// The passed box *MUST* be in this packing texture's empty list.
|
||||
// The passed box *MUST* be in this texture atlas's empty list.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void D3DFB::PackingTexture::AllocateImage(D3DFB::PackedTexture *box, int w, int h)
|
||||
D3DFB::PackedTexture *D3DFB::Atlas::AllocateImage(const Rect &rect, bool padded)
|
||||
{
|
||||
RECT start = box->Area;
|
||||
PackedTexture *box = new PackedTexture;
|
||||
|
||||
box->Area.right = box->Area.left + w;
|
||||
box->Area.bottom = box->Area.top + h;
|
||||
box->Owner = this;
|
||||
box->Area.left = rect.x;
|
||||
box->Area.top = rect.y;
|
||||
box->Area.right = rect.x + rect.width;
|
||||
box->Area.bottom = rect.y + rect.height;
|
||||
|
||||
box->Left = float(box->Area.left + box->Padded) / Width;
|
||||
box->Right = float(box->Area.right - box->Padded) / Width;
|
||||
box->Top = float(box->Area.top + box->Padded) / Height;
|
||||
box->Bottom = float(box->Area.bottom - box->Padded) / Height;
|
||||
box->Left = float(box->Area.left + padded) / Width;
|
||||
box->Right = float(box->Area.right - padded) / Width;
|
||||
box->Top = float(box->Area.top + padded) / Height;
|
||||
box->Bottom = float(box->Area.bottom - padded) / Height;
|
||||
|
||||
// Remove it from the empty list.
|
||||
*(box->Prev) = box->Next;
|
||||
if (box->Next != NULL)
|
||||
{
|
||||
box->Next->Prev = box->Prev;
|
||||
}
|
||||
box->Padded = padded;
|
||||
|
||||
// Add it to the used list.
|
||||
box->Next = UsedList;
|
||||
|
@ -2171,158 +2106,36 @@ void D3DFB::PackingTexture::AllocateImage(D3DFB::PackedTexture *box, int w, int
|
|||
UsedList = box;
|
||||
box->Prev = &UsedList;
|
||||
|
||||
// If we didn't use the whole box, split the remainder into the empty list.
|
||||
if (box->Area.bottom + 7 < start.bottom && box->Area.right + 7 < start.right)
|
||||
{
|
||||
// Split like this:
|
||||
// +---+------+
|
||||
// |###| |
|
||||
// +---+------+
|
||||
// | |
|
||||
// | |
|
||||
// +----------+
|
||||
if (box->Area.bottom < start.bottom)
|
||||
{
|
||||
AddEmptyBox(start.left, box->Area.bottom, start.right, start.bottom);
|
||||
}
|
||||
if (box->Area.right < start.right)
|
||||
{
|
||||
AddEmptyBox(box->Area.right, start.top, start.right, box->Area.bottom);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split like this:
|
||||
// +---+------+
|
||||
// |###| |
|
||||
// +---+ |
|
||||
// | | |
|
||||
// | | |
|
||||
// +---+------+
|
||||
if (box->Area.bottom < start.bottom)
|
||||
{
|
||||
AddEmptyBox(start.left, box->Area.bottom, box->Area.right, start.bottom);
|
||||
}
|
||||
if (box->Area.right < start.right)
|
||||
{
|
||||
AddEmptyBox(box->Area.right, start.top, start.right, start.bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture :: AddEmptyBox
|
||||
//
|
||||
// Adds a box with the specified dimensions to the empty list.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void D3DFB::PackingTexture::AddEmptyBox(int left, int top, int right, int bottom)
|
||||
{
|
||||
PackedTexture *box = AllocateBox();
|
||||
box->Area.left = left;
|
||||
box->Area.top = top;
|
||||
box->Area.right = right;
|
||||
box->Area.bottom = bottom;
|
||||
box->Next = EmptyList;
|
||||
if (box->Next != NULL)
|
||||
{
|
||||
box->Next->Prev = &box->Next;
|
||||
}
|
||||
box->Prev = &EmptyList;
|
||||
EmptyList = box;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture :: AllocateBox
|
||||
//
|
||||
// Returns a new PackedTexture box, either by retrieving one off the free
|
||||
// list or by creating a new one. The box is not linked into a list.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
D3DFB::PackedTexture *D3DFB::PackingTexture::AllocateBox()
|
||||
{
|
||||
PackedTexture *box;
|
||||
|
||||
if (FreeList != NULL)
|
||||
{
|
||||
box = FreeList;
|
||||
FreeList = box->Next;
|
||||
if (box->Next != NULL)
|
||||
{
|
||||
box->Next->Prev = &FreeList;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
box = new PackedTexture;
|
||||
box->Owner = this;
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// PackingTexture :: FreeBox
|
||||
// Atlas :: FreeBox
|
||||
//
|
||||
// Removes a box from its current list and adds it to the empty list,
|
||||
// updating EmptyArea. If there are no boxes left in the used list, then
|
||||
// the empty list is replaced with a single box, so the texture can be
|
||||
// subdivided again.
|
||||
// Removes a box from the used list and deletes it. Space is returned to the
|
||||
// waste list. Once all boxes for this atlas are freed, the entire bin
|
||||
// packer is reinitialized for maximum efficiency.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void D3DFB::PackingTexture::FreeBox(D3DFB::PackedTexture *box)
|
||||
void D3DFB::Atlas::FreeBox(D3DFB::PackedTexture *box)
|
||||
{
|
||||
*(box->Prev) = box->Next;
|
||||
if (box->Next != NULL)
|
||||
{
|
||||
box->Next->Prev = box->Prev;
|
||||
}
|
||||
box->Next = EmptyList;
|
||||
box->Prev = &EmptyList;
|
||||
if (EmptyList != NULL)
|
||||
{
|
||||
EmptyList->Prev = &box->Next;
|
||||
}
|
||||
EmptyList = box;
|
||||
Rect waste;
|
||||
waste.x = box->Area.left;
|
||||
waste.y = box->Area.top;
|
||||
waste.width = box->Area.right - box->Area.left;
|
||||
waste.height = box->Area.bottom - box->Area.top;
|
||||
box->Owner->Packer.AddWaste(waste);
|
||||
delete box;
|
||||
if (UsedList == NULL)
|
||||
{ // No more space in use! Move all but this into the free list.
|
||||
if (box->Next != NULL)
|
||||
{
|
||||
D3DFB::PackedTexture *lastbox;
|
||||
|
||||
// Find the last box in the free list.
|
||||
lastbox = FreeList;
|
||||
if (lastbox != NULL)
|
||||
{
|
||||
while (lastbox->Next != NULL)
|
||||
{
|
||||
lastbox = lastbox->Next;
|
||||
}
|
||||
}
|
||||
// Chain the empty list to the end of the free list.
|
||||
if (lastbox != NULL)
|
||||
{
|
||||
lastbox->Next = box->Next;
|
||||
box->Next->Prev = &lastbox->Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeList = box->Next;
|
||||
box->Next->Prev = &FreeList;
|
||||
}
|
||||
box->Next = NULL;
|
||||
}
|
||||
// Now this is the only box in the empty list, so it should
|
||||
// contain the whole texture.
|
||||
box->Area.left = 0;
|
||||
box->Area.top = 0;
|
||||
box->Area.right = Width;
|
||||
box->Area.bottom = Height;
|
||||
{
|
||||
Packer.Init(Width, Height, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2407,6 +2220,7 @@ bool D3DTex::CheckWrapping(bool wrapping)
|
|||
|
||||
bool D3DTex::Create(D3DFB *fb, bool wrapping)
|
||||
{
|
||||
assert(Box == NULL);
|
||||
if (Box != NULL)
|
||||
{
|
||||
Box->Owner->FreeBox(Box);
|
||||
|
@ -3436,6 +3250,22 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR
|
|||
IndexPos += 6;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// D3DFB :: AddColorOnlyRect
|
||||
//
|
||||
// Like AddColorOnlyQuad, except it's hollow.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void D3DFB::AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color)
|
||||
{
|
||||
AddColorOnlyQuad(left, top, width - 1, 1, color); // top
|
||||
AddColorOnlyQuad(left + width - 1, top, 1, height - 1, color); // right
|
||||
AddColorOnlyQuad(left + 1, top + height - 1, width - 1, 1, color); // bottom
|
||||
AddColorOnlyQuad(left, top + 1, 1, height - 1, color); // left
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// D3DFB :: CheckQuadBatch
|
||||
|
@ -4004,7 +3834,7 @@ void D3DFB::SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR bo
|
|||
SetTexture(1, texture);
|
||||
}
|
||||
|
||||
void D3DFB::SetPalTexBilinearConstants(PackingTexture *tex)
|
||||
void D3DFB::SetPalTexBilinearConstants(Atlas *tex)
|
||||
{
|
||||
#if 0
|
||||
float con[8];
|
||||
|
|
|
@ -279,7 +279,7 @@ private:
|
|||
friend class D3DPal;
|
||||
|
||||
struct PackedTexture;
|
||||
struct PackingTexture;
|
||||
struct Atlas;
|
||||
|
||||
struct FBVERTEX
|
||||
{
|
||||
|
@ -375,6 +375,7 @@ private:
|
|||
static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1);
|
||||
void DoWindowedGamma();
|
||||
void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color);
|
||||
void AddColorOnlyRect(int left, int top, int width, int height, D3DCOLOR color);
|
||||
void CheckQuadBatch(int numtris=2, int numverts=4);
|
||||
void BeginQuadBatch();
|
||||
void EndQuadBatch();
|
||||
|
@ -392,7 +393,7 @@ private:
|
|||
void SetPixelShader(IDirect3DPixelShader9 *shader);
|
||||
void SetTexture(int tnum, IDirect3DTexture9 *texture);
|
||||
void SetPaletteTexture(IDirect3DTexture9 *texture, int count, D3DCOLOR border_color);
|
||||
void SetPalTexBilinearConstants(PackingTexture *texture);
|
||||
void SetPalTexBilinearConstants(Atlas *texture);
|
||||
|
||||
BOOL AlphaTestEnabled;
|
||||
BOOL AlphaBlendEnabled;
|
||||
|
@ -431,7 +432,7 @@ private:
|
|||
BYTE BlockNum;
|
||||
D3DPal *Palettes;
|
||||
D3DTex *Textures;
|
||||
PackingTexture *Packs;
|
||||
Atlas *Atlases;
|
||||
HRESULT LastHR;
|
||||
|
||||
UINT Adapter;
|
||||
|
|
20
zdoom.vcproj
20
zdoom.vcproj
|
@ -666,6 +666,10 @@
|
|||
RelativePath=".\src\gitinfo.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\GuillotineBinPack.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\hu_scores.cpp"
|
||||
>
|
||||
|
@ -1026,6 +1030,10 @@
|
|||
RelativePath=".\src\skins.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\SkylineBinPack.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\st_stuff.cpp"
|
||||
>
|
||||
|
@ -1311,6 +1319,10 @@
|
|||
RelativePath=".\src\gstrings.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\GuillotineBinPack.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\hu_stuff.h"
|
||||
>
|
||||
|
@ -1491,6 +1503,10 @@
|
|||
RelativePath=".\src\po_man.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\Rect.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\s_playlist.h"
|
||||
>
|
||||
|
@ -1515,6 +1531,10 @@
|
|||
RelativePath=".\src\skins.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\SkylineBinPack.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\st_start.h"
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue