// TnzCore includes
#include "tstream.h"
// TnzBase includes
#include "toutputproperties.h"
#include "tfx.h"
#include "tparamcontainer.h"
#include "tparamset.h"
#include "tfxattributes.h"
// TnzLib includes
#include "toonz/fxdag.h"
#include "toonz/txshchildlevel.h"
#include "toonz/txshcell.h"
#include "toonz/observer.h"
#include "toonz/controlpointobserver.h"
#include "toonz/tcolumnfx.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshpalettecolumn.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/txshsoundcolumn.h"
#include "toonz/sceneproperties.h"
#include "toonz/toonzscene.h"
#include "toonz/columnfan.h"
#include "toonz/txshleveltypes.h"
#include "toonz/txshnoteset.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/stage.h"
#include "toonz/textureutils.h"
#include "xshhandlemanager.h"
#include "toonz/txsheet.h"
using namespace std;
DEFINE_CLASS_CODE(TXsheet, 18)
//-----------------------------------------------------------------------------
namespace
{
//-----------------------------------------------------------------------------
string getColumnDefaultName(TXsheet *xsh, int col, QString oldName)
{
TXshColumn *column = xsh->getColumn(col);
if (column) {
TXshLevelColumn *lc = column->getLevelColumn();
if (lc) {
int r0, r1;
if (lc->getRange(r0, r1)) {
TXshCell cell = lc->getCell(r0);
assert(!cell.isEmpty());
TXshLevel *level = cell.m_level.getPointer();
if (level) {
bool isNumber = true;
oldName.right(oldName.size() - 3).toInt(&isNumber);
if (oldName.left(3) == "Col" && isNumber)
return toString(level->getName());
else
return "";
}
}
}
}
return "Col" + toString(col + 1);
}
//-----------------------------------------------------------------------------
void setColumnName(TXsheet *xsh, int col)
{
TStageObject *obj = xsh->getStageObject(TStageObjectId::ColumnId(col));
QString oldName = QString::fromStdString(obj->getName());
string name = getColumnDefaultName(xsh, col, oldName);
if (!name.empty())
obj->setName(name);
}
} // namespace
//=============================================================================
// TXsheetImp
struct TXsheet::TXsheetImp {
unsigned long m_id; //!< The xsheet instance's unique identifier
TColumnSetT<TXshColumn> m_columnSet;
TStageObjectTree *m_pegTree;
FxDag *m_fxDag;
int m_frameCount;
int m_soloColumn;
int m_viewColumn;
TSoundTrackP m_mixedSound;
ColumnFan m_columnFan;
XshHandleManager *m_handleManager;
ToonzScene *m_scene;
public:
TXsheetImp();
~TXsheetImp();
static inline unsigned long newIdentifier()
{
static unsigned long currentId = 0;
return ++currentId;
}
private:
//not implemented
TXsheetImp(const TXsheetImp &);
TXsheetImp &operator=(const TXsheetImp &);
};
//-----------------------------------------------------------------------------
TXsheet::SoundProperties::SoundProperties()
: m_fromFrame(-1), m_toFrame(-1), m_frameRate(-1), m_isPreview(false)
{
}
//-----------------------------------------------------------------------------
TXsheet::SoundProperties::~SoundProperties() {}
//-----------------------------------------------------------------------------
inline bool TXsheet::SoundProperties::operator==(const SoundProperties &c) const
{
return m_fromFrame == c.m_fromFrame &&
m_toFrame == c.m_toFrame &&
m_frameRate == c.m_frameRate &&
m_isPreview == c.m_isPreview;
}
inline bool TXsheet::SoundProperties::operator!=(const SoundProperties &c) const
{
return !(*this == c);
}
//-----------------------------------------------------------------------------
TXsheet::TXsheetImp::TXsheetImp()
: m_id(newIdentifier()), m_pegTree(new TStageObjectTree), m_handleManager(0), m_fxDag(new FxDag()), m_frameCount(0), m_soloColumn(-1), m_viewColumn(-1), m_mixedSound(0), m_scene(0)
{
}
//-----------------------------------------------------------------------------
TXsheet::TXsheetImp::~TXsheetImp()
{
assert(m_pegTree);
assert(m_fxDag);
assert(m_handleManager);
delete m_pegTree;
delete m_fxDag;
delete m_handleManager;
}
//=============================================================================
// TXsheet
TXsheet::TXsheet()
: TSmartObject(m_classCode), m_player(0), m_imp(new TXsheet::TXsheetImp), m_notes(new TXshNoteSet())
{
//extern TSyntax::Grammar *createXsheetGrammar(TXsheet*);
m_soundProperties = new TXsheet::SoundProperties();
m_imp->m_handleManager = new XshHandleManager(this);
m_imp->m_pegTree->setHandleManager(m_imp->m_handleManager);
m_imp->m_pegTree->createGrammar(this);
}
//-----------------------------------------------------------------------------
TXsheet::~TXsheet()
{
texture_utils::invalidateTextures(this);
assert(m_imp);
delete m_imp;
if (m_notes)
delete m_notes;
if (m_soundProperties)
delete m_soundProperties;
}
//-----------------------------------------------------------------------------
unsigned long TXsheet::id() const
{
return m_imp->m_id;
}
//-----------------------------------------------------------------------------
int TXsheet::getFrameCount() const
{
return m_imp->m_frameCount;
}
//-----------------------------------------------------------------------------
const TXshCell &TXsheet::getCell(int row, int col) const
{
static const TXshCell emptyCell;
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column)
return emptyCell;
TXshCellColumn *xshColumn = column->getCellColumn();
if (!xshColumn)
return emptyCell;
return xshColumn->getCell(row);
}
//-----------------------------------------------------------------------------
bool TXsheet::setCell(int row, int col, const TXshCell &cell)
{
if (row < 0 || col < 0)
return false;
bool wasColumnEmpty = isColumnEmpty(col);
TXshCellColumn *cellColumn;
if (!cell.isEmpty()) {
TXshLevel *level = cell.m_level.getPointer();
assert(level);
int levelType = level->getType();
TXshColumn::ColumnType type = TXshColumn::eLevelType;
if (levelType == SND_XSHLEVEL)
type = TXshColumn::eSoundType;
else if (levelType == SND_TXT_XSHLEVEL)
type = TXshColumn::eSoundTextType;
else if (levelType == PLT_XSHLEVEL)
type = TXshColumn::ePaletteType;
else if (levelType == ZERARYFX_XSHLEVEL)
type = TXshColumn::eZeraryFxType;
else if (levelType == MESH_XSHLEVEL)
type = TXshColumn::eMeshType;
cellColumn = touchColumn(col, type)->getCellColumn();
} else {
TXshColumn *column = getColumn(col);
cellColumn = column ? column->getCellColumn() : 0;
}
if (!cellColumn || cellColumn->isLocked())
return false;
cellColumn->setXsheet(this);
if (!cellColumn->setCell(row, cell)) {
if (wasColumnEmpty) {
removeColumn(col);
insertColumn(col);
}
return false;
}
TFx *fx = cellColumn->getFx();
if (wasColumnEmpty && fx && fx->getOutputConnectionCount() == 0 && cellColumn->getPaletteColumn() == 0)
getFxDag()->addToXsheet(fx);
if (cell.isEmpty())
updateFrameCount();
else if (row >= m_imp->m_frameCount)
m_imp->m_frameCount = row + 1;
TNotifier::instance()->notify(TXsheetChange());
return true;
}
//-----------------------------------------------------------------------------
void TXsheet::getCells(int row, int col, int rowCount, TXshCell cells[]) const
{
static const TXshCell emptyCell;
int i;
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column) {
for (i = 0; i < rowCount; i++)
cells[i] = emptyCell;
return;
}
TXshCellColumn *xshColumn = column->getCellColumn();
if (!xshColumn) {
for (i = 0; i < rowCount; i++)
cells[i] = emptyCell;
return;
}
xshColumn->getCells(row, rowCount, cells);
}
//-----------------------------------------------------------------------------
bool TXsheet::setCells(int row, int col, int rowCount, const TXshCell cells[])
{
static const TXshCell emptyCell;
int i = 0;
while (i < rowCount && cells[i].isEmpty())
i++;
// inserito da Elisa verso novembre 2009.
// cosi' ha il difetto che se assegno celle vuote non fa nulla
// per ora lo commento. bisogna indagare se questo rompe qualcosa
// ho modificato il seguito per gestire il caso in cui i>=rowCount
// => niente livelli dentro cells
// if(i>=rowCount)
// return false;
TXshColumn::ColumnType type = TXshColumn::eLevelType;
if (i < rowCount) {
TXshLevel *level = cells[i].m_level.getPointer();
int levelType = level->getType();
if (levelType == SND_XSHLEVEL)
type = TXshColumn::eSoundType;
else if (levelType == SND_TXT_XSHLEVEL)
type = TXshColumn::eSoundTextType;
else if (levelType == PLT_XSHLEVEL)
type = TXshColumn::ePaletteType;
else if (levelType == ZERARYFX_XSHLEVEL)
type = TXshColumn::eZeraryFxType;
else if (levelType == MESH_XSHLEVEL)
type = TXshColumn::eMeshType;
}
bool wasColumnEmpty = isColumnEmpty(col);
TXshCellColumn *column = touchColumn(col, type)->getCellColumn();
if (!column)
return false;
int oldColRowCount = column->getMaxFrame() + 1;
bool ret = column->setCells(row, rowCount, cells);
if (!ret || column->isLocked()) {
if (wasColumnEmpty) {
removeColumn(col);
insertColumn(col);
}
return false;
}
int newColRowCount = column->getMaxFrame() + 1;
TFx *fx = column->getFx();
if (wasColumnEmpty && fx && fx->getOutputConnectionCount() == 0)
getFxDag()->addToXsheet(fx);
column->setXsheet(this);
if (newColRowCount > m_imp->m_frameCount)
m_imp->m_frameCount = newColRowCount;
else {
if (oldColRowCount == m_imp->m_frameCount && newColRowCount < m_imp->m_frameCount)
updateFrameCount();
}
return true;
}
//-----------------------------------------------------------------------------
void TXsheet::insertCells(int row, int col, int rowCount)
{
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column || column->isLocked())
return;
TXshCellColumn *xshColumn = column->getCellColumn();
if (!xshColumn)
return;
xshColumn->insertEmptyCells(row, rowCount);
//aggiorno il frame count
int fc = xshColumn->getMaxFrame() + 1;
if (fc > m_imp->m_frameCount)
m_imp->m_frameCount = fc;
}
//-----------------------------------------------------------------------------
void TXsheet::removeCells(int row, int col, int rowCount)
{
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column || column->isLocked())
return;
TXshCellColumn *xshCellColumn = column->getCellColumn();
if (!xshCellColumn)
return;
int oldColRowCount = xshCellColumn->getMaxFrame() + 1;
xshCellColumn->removeCells(row, rowCount);
//aggiornamento framecount
if (oldColRowCount == m_imp->m_frameCount)
updateFrameCount();
TNotifier::instance()->notify(TXsheetChange());
}
//-----------------------------------------------------------------------------
void TXsheet::clearCells(int row, int col, int rowCount)
{
const TXshColumnP &column = m_imp->m_columnSet.getColumn(col);
if (!column || column->isLocked())
return;
TXshCellColumn *xshCellColumn = column->getCellColumn();
if (!xshCellColumn)
return;
int oldColRowCount = xshCellColumn->getMaxFrame() + 1;
xshCellColumn->clearCells(row, rowCount);
//aggiornamento framecount
if (oldColRowCount == m_imp->m_frameCount)
updateFrameCount();
}
//-----------------------------------------------------------------------------
void TXsheet::clearAll()
{
int c0 = 0, c1 = m_imp->m_columnSet.getColumnCount() - 1;
int r0 = 0, r1 = getFrameCount() - 1;
m_imp->m_columnSet.clear();
if (m_imp->m_pegTree) {
delete m_imp->m_pegTree;
m_imp->m_pegTree = new TStageObjectTree();
m_imp->m_pegTree->setHandleManager(m_imp->m_handleManager);
m_imp->m_pegTree->createGrammar(this);
}
if (m_imp->m_fxDag) {
delete m_imp->m_fxDag;
m_imp->m_fxDag = new FxDag();
}
m_imp->m_frameCount = 0;
m_imp->m_mixedSound = 0;
}
//-----------------------------------------------------------------------------
int TXsheet::getCellRange(int col, int &r0, int &r1) const
{
r0 = 0;
r1 = -1;
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column)
return 0;
TXshCellColumn *cellColumn = column->getCellColumn();
if (!cellColumn)
return 0;
return cellColumn->getRange(r0, r1);
}
//-----------------------------------------------------------------------------
int TXsheet::getMaxFrame(int col) const
{
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
if (!column)
return 0;
return column->getMaxFrame();
}
//-----------------------------------------------------------------------------
bool TXsheet::isColumnEmpty(int col) const
{
TXshColumnP column = m_imp->m_columnSet.getColumn(col);
return column ? column->isEmpty() : true;
}
//-----------------------------------------------------------------------------
void TXsheet::getUsedLevels(set<TXshLevel *> &levels) const
{
set<const TXsheet *> visitedXshs;
vector<const TXsheet *> todoXshs;
visitedXshs.insert(this);
todoXshs.push_back(this);
while (!todoXshs.empty()) {
const TXsheet *xsh = todoXshs.back();
todoXshs.pop_back();
int c0 = 0, c1 = xsh->getColumnCount() - 1;
for (int c = c0; c <= c1; ++c) {
TXshColumnP column = const_cast<TXsheet *>(xsh)->getColumn(c);
if (!column)
continue;
TXshCellColumn *cellColumn = column->getCellColumn();
if (!cellColumn)
continue;
int r0, r1;
if (!cellColumn->getRange(r0, r1))
continue;
TXshLevel *level = 0;
for (int r = r0; r <= r1; r++) {
TXshCell cell = cellColumn->getCell(r);
if (cell.isEmpty() || !cell.m_level)
continue;
if (level != cell.m_level.getPointer()) {
level = cell.m_level.getPointer();
levels.insert(level);
if (level->getChildLevel()) {
TXsheet *childXsh = level->getChildLevel()->getXsheet();
if (visitedXshs.count(childXsh) == 0) {
visitedXshs.insert(childXsh);
todoXshs.push_back(childXsh);
}
}
}
}
}
}
}
//-----------------------------------------------------------------------------
bool TXsheet::isLevelUsed(TXshLevel *level) const
{
set<TXshLevel *> levels;
getUsedLevels(levels);
return levels.count(level) > 0;
}
//-----------------------------------------------------------------------------
TStageObject *TXsheet::getStageObject(const TStageObjectId &id) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id);
}
//-----------------------------------------------------------------------------
TStageObjectTree *TXsheet::getStageObjectTree() const
{
return m_imp->m_pegTree;
}
//-----------------------------------------------------------------------------
TAffine TXsheet::getPlacement(const TStageObjectId &id, int frame) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getPlacement(frame);
}
//-----------------------------------------------------------------------------
double TXsheet::getZ(const TStageObjectId &id, int frame) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getZ(frame);
}
//-----------------------------------------------------------------------------
double TXsheet::getNoScaleZ(const TStageObjectId &id) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getNoScaleZ();
}
//-----------------------------------------------------------------------------
TAffine TXsheet::getParentPlacement(const TStageObjectId &id, int frame) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getParentPlacement(frame);
}
//-----------------------------------------------------------------------------
TPointD TXsheet::getCenter(const TStageObjectId &id, int frame) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getCenter(frame);
}
//-----------------------------------------------------------------------------
void TXsheet::setCenter(const TStageObjectId &id, int frame, const TPointD ¢er)
{
assert(id != TStageObjectId::NoneId);
m_imp->m_pegTree->getStageObject(id)->setCenter(frame, center);
}
//-----------------------------------------------------------------------------
TStageObjectId TXsheet::getStageObjectParent(const TStageObjectId &id)
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->getParent();
}
//-----------------------------------------------------------------------------
void TXsheet::setStageObjectParent(const TStageObjectId &id, const TStageObjectId &parentId)
{
assert(id != TStageObjectId::NoneId);
m_imp->m_pegTree->getStageObject(id)->setParent(parentId);
}
//-----------------------------------------------------------------------------
bool TXsheet::hasChildren(const TStageObjectId &id) const
{
assert(id != TStageObjectId::NoneId);
return m_imp->m_pegTree->getStageObject(id)->hasChildren();
}
//-----------------------------------------------------------------------------
TAffine TXsheet::getCameraAff(int frame) const
{
TStageObjectId cameraId = getStageObjectTree()->getCurrentCameraId();
TAffine cameraAff = getPlacement(cameraId, frame);
double cameraZ = getZ(cameraId, frame);
TAffine aff = cameraAff * TScale((1000 + cameraZ) / 1000);
return aff;
}
//-----------------------------------------------------------------------------
void TXsheet::reverseCells(int r0, int c0, int r1, int c1)
{
int rowCount = r1 - r0;
if (rowCount < 0 || c1 - c0 < 0)
return;
for (int j = c0; j <= c1; j++) {
int i1, i2;
for (i1 = r0, i2 = r1; i1 < i2; i1++, i2--) {
TXshCell app1 = getCell(i1, j);
TXshCell app2 = getCell(i2, j);
setCell(i1, j, app2);
setCell(i2, j, app1);
}
}
}
//-----------------------------------------------------------------------------
void TXsheet::swingCells(int r0, int c0, int r1, int c1)
{
int rowCount = r1 - r0;
if (rowCount < 0 || c1 - c0 < 0)
return;
int r0Mod = r1 + 1;
for (int c = c0; c <= c1; ++c)
insertCells(r0Mod, c, rowCount);
for (int j = c0; j <= c1; j++) {
for (int i1 = r0Mod, i2 = r1 - 1; i2 >= r0; i1++, i2--) {
TXshCell cell = getCell(i2, j);
setCell(i1, j, cell);
}
}
}
//-----------------------------------------------------------------------------
bool TXsheet::incrementCells(int r0, int c0, int r1, int c1, vector<std::pair<TRect, TXshCell>> &forUndo)
{
for (int j = c0; j <= c1; j++) {
int i = r0;
while (getCell(i, j).isEmpty() && i <= r1 - 1)
i++;
for (; i <= r1 - 1; i++) {
if (getCell(i + 1, j).isEmpty())
break;
const TXshCell &ce1 = getCell(i, j), &ce2 = getCell(i + 1, j);
if (ce2.getSimpleLevel() != ce1.getSimpleLevel() ||
ce2.getFrameId().getNumber() < ce1.getFrameId().getNumber())
return false;
}
i = r0;
while (getCell(i, j).isEmpty() && i <= r1 - 1)
i++;
int count;
for (; i <= r1 - 1; i++) {
count = 1;
if (getCell(i + 1, j).isEmpty())
continue;
int frame1 = getCell(i, j).getFrameId().getNumber();
if (frame1 == -1)
break;
while (!getCell(i + 1, j).isEmpty() && getCell(i + 1, j).getFrameId().getNumber() == getCell(i, j).getFrameId().getNumber())
i++, count++;
int frame2 = getCell(i + 1, j).getFrameId().getNumber();
if (frame2 == -1)
break;
if (frame1 + count == frame2)
continue;
else if (frame1 + count < frame2) //aggiungo
{
int numCells = frame2 - frame1 - count;
insertCells(i + 1, j, numCells);
forUndo.push_back(std::pair<TRect, TXshCell>(TRect(i + 1, j, i + 1 + numCells - 1, j), TXshCell()));
for (int k = 1; k <= numCells; k++)
setCell(i + k, j, getCell(i, j));
i += numCells;
r1 += numCells;
} else //tolgo
{
int numCells = count - frame2 + frame1;
i = i - numCells;
forUndo.push_back(std::pair<TRect, TXshCell>(TRect(i + 1, j, i + 1 + numCells - 1, j), getCell(i + 1, j)));
removeCells(i + 1, j, numCells);
r1 -= numCells;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
void TXsheet::duplicateCells(int r0, int c0, int r1, int c1, int upTo)
{
assert(upTo >= r1 + 1);
int chunk = r1 - r0 + 1;
for (int j = c0; j <= c1; j++) {
insertCells(r1 + 1, j, upTo - (r1 + 1) + 1);
for (int i = r1 + 1; i <= upTo; i++)
setCell(i, j, getCell(r0 + ((i - (r1 + 1)) % chunk), j));
}
}
//-----------------------------------------------------------------------------
void TXsheet::stepCells(int r0, int c0, int r1, int c1, int type)
{
int nr = r1 - r0 + 1;
int nc = c1 - c0 + 1;
if (nr < 1 || nc <= 0)
return;
int size = nr * nc;
TXshCell *cells = new TXshCell[size];
if (!cells)
return;
//salvo il contenuto delle celle in cells
int k = 0;
for (int r = r0; r <= r1; r++)
for (int c = c0; c <= c1; c++) {
cells[k++] = getCell(r, c);
}
int nrows = nr * (type - 1);
for (int c = c0; c <= c1; ++c)
insertCells(r1 + 1, c, nrows);
for (int j = c0; j <= c1; j++) {
int i, k;
for (i = r0, k = j - c0; k < size; k += nc) {
for (int i1 = 0; i1 < type; i1++) {
if (cells[k].isEmpty())
clearCells(i + i1, j);
else
setCell(i + i1, j, cells[k]);
}
i += type; //dipende dal tipo di step (2 o 3 per ora)
}
}
if (cells)
delete[] cells;
cells = 0;
}
//-----------------------------------------------------------------------------
void TXsheet::increaseStepCells(int r0, int c0, int &r1, int c1)
{
int c, size = r1 - r0 + 1;
QList<int> ends;
for (c = c0; c <= c1; c++) {
int r = r0, i = 0, rEnd = r1;
while (r <= rEnd) {
TXshCell cell = getCell(r, c);
if (!cell.isEmpty()) {
insertCells(r, c);
setCell(r, c, cell);
rEnd++;
r++;
while (cell == getCell(r, c) && r <= rEnd)
r++;
} else
r++;
i++;
}
ends.append(rEnd);
}
if (ends.isEmpty())
return;
//controllo se devo cambiare la selezione
bool allIncreaseIsEqual = true;
for (c = 0; c < ends.size() - 1 && allIncreaseIsEqual; c++)
allIncreaseIsEqual = allIncreaseIsEqual && ends[c] == ends[c + 1];
if (allIncreaseIsEqual)
r1 = ends[0];
}
//-----------------------------------------------------------------------------
void TXsheet::decreaseStepCells(int r0, int c0, int &r1, int c1)
{
int c, size = r1 - r0 + 1;
QList<int> ends;
for (c = c0; c <= c1; c++) {
int r = r0, i = 0, rEnd = r1;
while (r <= rEnd) {
TXshCell cell = getCell(r, c);
if (!cell.isEmpty()) {
r++;
bool removed = false;
while (cell == getCell(r, c) && r <= rEnd) {
if (!removed) {
removed = true;
removeCells(r, c);
rEnd--;
} else
r++;
}
} else
r++;
i++;
}
ends.append(rEnd);
}
if (ends.isEmpty())
return;
//controllo se devo cambiare la selezione
bool allDecreaseIsEqual = true;
for (c = 0; c < ends.size() - 1 && allDecreaseIsEqual; c++)
allDecreaseIsEqual = allDecreaseIsEqual && ends[c] == ends[c + 1];
if (allDecreaseIsEqual)
r1 = ends[0];
}
//-----------------------------------------------------------------------------
void TXsheet::eachCells(int r0, int c0, int r1, int c1, int type)
{
int nr = r1 - r0 + 1;
int nc = c1 - c0 + 1;
if (nr < type || nc <= 0)
return;
int newRows = nr % type ? nr / type + 1 : nr / type;
int size = newRows * nc;
assert(size > 0);
TXshCell *cells = new TXshCell[size];
assert(cells);
int i, j, k;
for (j = r0, i = 0; i < size; j += type) //in cells copio il contenuto delle celle che mi interessano
{
for (k = c0; k <= c1; k++, i++)
cells[i] = getCell(j, k);
}
int c;
for (c = c0; c <= c1; ++c)
removeCells(r0 + newRows, c, nr - newRows);
for (i = r0, k = 0; i < r0 + newRows && k < size; i++)
for (j = c0; j <= c1; j++) {
//----110523 iwasawa Eachでできた空きセルに、操作前のセルの中身が残ってしまう不具合を修正
if (cells[k].isEmpty())
clearCells(i, j);
else
setCell(i, j, cells[k]);
k++;
}
if (cells)
delete[] cells;
cells = 0;
}
//-----------------------------------------------------------------------------
/*! force cells order in n-steps. returns the row amount after process
*/
int TXsheet::reframeCells(int r0, int r1, int col, int type)
{
//Row amount in the selection
int nr = r1 - r0 + 1;
if (nr < 1)
return 0;
QVector<TXshCell> cells;
cells.clear();
for (int r = r0; r <= r1; r++) {
if (cells.size() == 0 || cells.last() != getCell(r, col))
cells.push_back(getCell(r, col));
}
if (cells.empty())
return 0;
// row amount after n-step
int nrows = cells.size() * type;
// if needed, insert cells
if (nr < nrows) {
insertCells(r1 + 1, col, nrows - nr);
}
// if needed, remove cells
else if (nr > nrows) {
removeCells(r0 + nrows, col, nr - nrows);
}
int k;
for (int i = r0, k = 0; i < r0 + nrows; k++) {
for (int i1 = 0; i1 < type; i1++) {
if (cells[k].isEmpty())
clearCells(i + i1, col);
else
setCell(i + i1, col, cells[k]);
}
i += type; //dipende dal tipo di step (2 o 3 per ora)
}
return nrows; // return row amount after process
}
//-----------------------------------------------------------------------------
void TXsheet::resetStepCells(int r0, int c0, int r1, int c1)
{
int c, size = r1 - r0 + 1;
for (c = c0; c <= c1; c++) {
int r = r0, i = 0;
TXshCell *cells = new TXshCell[size];
while (r <= r1) {
//mi prendo le celle che mi servono
cells[i] = getCell(r, c);
r++;
while (cells[i] == getCell(r, c) && r <= r1)
r++;
i++;
}
size = i;
removeCells(r0, c, r1 - r0 + 1);
insertCells(r0, c, i);
i = 0;
r = r0;
for (i = 0; i < size; i++, r++)
setCell(r, c, cells[i]);
}
}
//-----------------------------------------------------------------------------
/*! Roll first cells of rect r0,c0,r1,c1. Move cells contained in first row to last row.
*/
void TXsheet::rollupCells(int r0, int c0, int r1, int c1)
{
int nc = c1 - c0 + 1;
int size = 1 * nc;
assert(size > 0);
TXshCell *cells = new TXshCell[size];
assert(cells);
//in cells copio il contenuto delle celle che mi interessano
int k;
for (k = c0; k <= c1; k++)
cells[k - c0] = getCell(r0, k);
for (k = c0; k <= c1; k++)
removeCells(r0, k, 1);
for (k = c0; k <= c1; k++) {
insertCells(r1, k, 1);
setCell(r1, k, cells[k - c0]); //setto le celle
}
if (cells)
delete[] cells;
cells = 0;
}
//-----------------------------------------------------------------------------
/*! Roll last cells of rect r0,c0,r1,c1. Move cells contained in last row to first row.
*/
void TXsheet::rolldownCells(int r0, int c0, int r1, int c1)
{
int nc = c1 - c0 + 1;
int size = 1 * nc;
assert(size > 0);
TXshCell *cells = new TXshCell[size];
assert(cells);
//in cells copio il contenuto delle celle che mi interessano
int k;
for (k = c0; k <= c1; k++)
cells[k - c0] = getCell(r1, k);
for (k = c0; k <= c1; k++)
removeCells(r1, k, 1);
for (k = c0; k <= c1; k++) {
insertCells(r0, k, 1);
setCell(r0, k, cells[k - c0]); //setto le celle
}
if (cells)
delete[] cells;
cells = 0;
}
//-----------------------------------------------------------------------------
/*! Stretch cells contained in rect r0,c0,r1,c1, from r1-r0+1 to nr.
If nr>r1-r0+1 add cells, overwise remove cells. */
void TXsheet::timeStretch(int r0, int c0, int r1, int c1, int nr)
{
int oldNr = r1 - r0 + 1;
if (nr > oldNr) /* ingrandisce */
{
int c;
for (c = c0; c <= c1; c++) {
int dn = nr - oldNr;
assert(oldNr > 0);
TXshCell *cells = new TXshCell[oldNr];
assert(cells);
getCells(r0, c, oldNr, cells);
insertCells(r0 + 1, c, dn);
int i;
for (i = nr - 1; i >= 0; i--) {
int j = i * double(oldNr) / double(nr);
if (j < i)
setCell(i + r0, c, cells[j]);
}
if (cells)
delete[] cells;
cells = 0;
}
} else /* rimpicciolisce */
{
int c;
for (c = c0; c <= c1; c++) {
int dn = oldNr - nr;
TXshCell *cells = new TXshCell[oldNr];
assert(cells);
getCells(r0, c, oldNr, cells);
int i;
for (i = 0; i < nr; i++) {
int j = i * double(oldNr) / double(nr);
if (j > i)
setCell(i + r0, c, cells[j]);
}
removeCells(r1 - dn + 1, c, dn);
if (cells)
delete[] cells;
cells = 0;
}
}
}
//-----------------------------------------------------------------------------
int TXsheet::exposeLevel(int row, int col, TXshLevel *xl, bool overwrite)
{
if (!xl)
return 0;
std::vector<TFrameId> fids;
xl->getFids(fids);
int frameCount = 1;
if (fids.empty()) {
setCell(row, col, TXshCell(xl, TFrameId(1)));
updateFrameCount();
return frameCount;
}
exposeLevel(row, col, xl, fids, overwrite);
return (int)fids.size();
}
//-----------------------------------------------------------------------------
// customized version for load level popup
int TXsheet::exposeLevel(int row,
int col,
TXshLevel *xl,
std::vector<TFrameId> &fIds_,
int xFrom,
int xTo,
int step,
int inc,
int frameCount,
bool doesFileActuallyExist)
{
if (!xl)
return 0;
std::vector<TFrameId> fids;
if (doesFileActuallyExist)
xl->getFids(fids);
else {
for (int i = 0; i < (int)fIds_.size(); i++) {
fids.push_back(fIds_[i]);
}
}
// multiple exposing
if (frameCount < 0 || xFrom < 0 || xTo < 0 || step < 0 || inc < 0) {
insertCells(row, col, xl->getFrameCount());
frameCount = 1;
if (fids.empty())
setCell(row, col, TXshCell(xl, TFrameId(1)));
else {
frameCount = (int)fids.size();
insertCells(row, col, frameCount);
std::vector<TFrameId>::iterator it;
for (it = fids.begin(); it != fids.end(); ++it)
setCell(row++, col, TXshCell(xl, *it));
}
updateFrameCount();
return frameCount;
}
//single exposing
insertCells(row, col, frameCount);
if (fids.empty()) {
setCell(row, col, TXshCell(xl, TFrameId(1)));
} else {
if (inc == 0) //inc = Auto
{
std::vector<TFrameId>::iterator it;
it = fids.begin();
while (it->getNumber() < xFrom)
it++;
if (step == 0) //Step = Auto
{
std::vector<TFrameId>::iterator next_it;
next_it = it;
next_it++;
int startFrame = it->getNumber();
for (int f = startFrame; f < startFrame + frameCount; f++) {
if (next_it != fids.end() && f >= next_it->getNumber()) {
it++;
next_it++;
}
setCell(row++, col, TXshCell(xl, *it));
}
}
else //Step != Auto
{
int loopCount = frameCount / step;
for (int loop = 0; loop < loopCount; loop++) {
for (int s = 0; s < step; s++) {
setCell(row++, col, TXshCell(xl, *it));
}
it++;
}
}
} else //inc != Auto
{
int loopCount;
if (step == 0) //Step = Auto
step = inc;
loopCount = frameCount / step;
for (int loop = 0; loop < loopCount; loop++) {
TFrameId id(xFrom + loop * inc, fids.begin()->getLetter());
for (int s = 0; s < step; s++) {
setCell(row++, col, TXshCell(xl, id));
}
}
}
}
updateFrameCount();
return frameCount;
}
//-----------------------------------------------------------------------------
void TXsheet::exposeLevel(int row, int col, TXshLevel *xl, std::vector<TFrameId> fids, bool overwrite)
{
int frameCount = (int)fids.size();
if (!overwrite)
insertCells(row, col, frameCount);
std::vector<TFrameId>::iterator it;
for (it = fids.begin(); it != fids.end(); ++it)
setCell(row++, col, TXshCell(xl, *it));
updateFrameCount();
}
//-----------------------------------------------------------------------------
void TXsheet::updateFrameCount()
{
m_imp->m_frameCount = 0;
for (int i = 0; i < m_imp->m_columnSet.getColumnCount(); ++i) {
TXshColumnP cc = m_imp->m_columnSet.getColumn(i);
if (cc && !cc->isEmpty())
m_imp->m_frameCount = tmax(m_imp->m_frameCount, cc->getMaxFrame() + 1);
}
}
//-----------------------------------------------------------------------------
void TXsheet::loadData(TIStream &is)
{
clearAll();
TStageObjectId cameraId = TStageObjectId::CameraId(0);
TStageObject *firstCamera = getStageObject(cameraId);
m_imp->m_pegTree->removeStageObject(cameraId);
int col = 0;
string tagName;
while (is.openChild(tagName)) {
if (tagName == "columns") {
while (!is.eos()) {
TPersist *p = 0;
is >> p;
TXshColumn *column = dynamic_cast<TXshColumn *>(p);
if (!column)
throw TException("expected xsheet column");
m_imp->m_columnSet.insertColumn(col++, column);
column->setXsheet(this);
if (TXshZeraryFxColumn *zc = dynamic_cast<TXshZeraryFxColumn *>(column)) {
TFx *fx = zc->getZeraryColumnFx()->getZeraryFx();
int fxTypeCount = m_imp->m_fxDag->getFxTypeCount(fx);
int maxFxTypeId = tmax(fxTypeCount, fx->getAttributes()->getId());
m_imp->m_fxDag->updateFxTypeTable(fx, maxFxTypeId);
m_imp->m_fxDag->updateFxIdTable(fx);
for (int j = 0; j < fx->getParams()->getParamCount(); j++) {
TParam *param = fx->getParams()->getParam(j);
if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(param))
getStageObjectTree()->setGrammar(dp);
else if (dynamic_cast<TPointParam *>(param) ||
dynamic_cast<TRangeParam *>(param) ||
dynamic_cast<TPixelParam *>(param)) {
TParamSet *paramSet = dynamic_cast<TParamSet *>(param);
assert(paramSet);
int f;
for (f = 0; f < paramSet->getParamCount(); f++) {
TDoubleParam *dp = dynamic_cast<TDoubleParam *>(paramSet->getParam(f).getPointer());
if (!dp)
continue;
getStageObjectTree()->setGrammar(dp);
}
}
}
}
}
} else if (tagName == "pegbars") {
TPersist *p = m_imp->m_pegTree;
is >> *p;
} else if (tagName == "fxnodes") {
m_imp->m_fxDag->loadData(is);
std::vector<TFx *> fxs;
m_imp->m_fxDag->getFxs(fxs);
for (int i = 0; i < (int)fxs.size(); i++) {
TFx *fx = fxs[i];
for (int j = 0; j < fx->getParams()->getParamCount(); j++) {
TParam *param = fx->getParams()->getParam(j);
if (TDoubleParam *dp = dynamic_cast<TDoubleParam *>(param))
getStageObjectTree()->setGrammar(dp);
else if (dynamic_cast<TPointParam *>(param) ||
dynamic_cast<TRangeParam *>(param) ||
dynamic_cast<TPixelParam *>(param)) {
TParamSet *paramSet = dynamic_cast<TParamSet *>(param);
assert(paramSet);
int f;
for (f = 0; f < paramSet->getParamCount(); f++) {
TDoubleParam *dp = dynamic_cast<TDoubleParam *>(paramSet->getParam(f).getPointer());
if (!dp)
continue;
getStageObjectTree()->setGrammar(dp);
}
}
}
}
if (is.matchEndTag())
continue;
// was ist dass?
TFxSet fxSet;
fxSet.loadData(is);
} else if (tagName == "columnFan") {
m_imp->m_columnFan.loadData(is);
} else if (tagName == "noteSet") {
m_notes->loadData(is);
} else {
throw TException("xsheet, unknown tag: " + tagName);
}
is.closeChild();
}
updateFrameCount();
}
//-----------------------------------------------------------------------------
void TXsheet::saveData(TOStream &os)
{
os.openChild("columns");
for (int c = 0; c < m_imp->m_columnSet.getColumnCount(); ++c) {
TXshColumnP column = m_imp->m_columnSet.getColumn(c);
if (column && c < getFirstFreeColumnIndex())
os << column.getPointer();
}
os.closeChild();
os.openChild("pegbars");
m_imp->m_pegTree->saveData(os, getFirstFreeColumnIndex());
//os << *(m_imp->m_pegTree);
os.closeChild();
FxDag *fxDag = getFxDag();
os.openChild("fxnodes");
fxDag->saveData(os, getFirstFreeColumnIndex());
os.closeChild();
ColumnFan *columnFan = getColumnFan();
if (!columnFan->isEmpty()) {
os.openChild("columnFan");
columnFan->saveData(os);
os.closeChild();
}
TXshNoteSet *notes = getNotes();
if (notes->getCount() > 0) {
os.openChild("noteSet");
notes->saveData(os);
os.closeChild();
}
}
//-----------------------------------------------------------------------------
PERSIST_IDENTIFIER(TXsheet, "xsheet")
//-----------------------------------------------------------------------------
void TXsheet::insertColumn(int col, TXshColumn::ColumnType type)
{
insertColumn(col, TXshColumn::createEmpty(type));
}
//-----------------------------------------------------------------------------
void TXsheet::insertColumn(int col, TXshColumn *column)
{
column->setXsheet(this);
m_imp->m_columnSet.insertColumn(col, column);
m_imp->m_pegTree->insertColumn(col);
if (column->getPaletteColumn() == 0) // palette column are not connected to the xsheet fx node
{
TFx *fx = column->getFx();
if (fx)
getFxDag()->addToXsheet(fx);
}
}
//-----------------------------------------------------------------------------
void TXsheet::removeColumn(int col)
{
TXshColumn *column = getColumn(col);
if (column) {
TFx *fx = column->getFx();
if (fx) {
getFxDag()->removeFromXsheet(fx);
// disconnetto dal columnFx tutti gli effetti connessi in uscita
TFxPort *outPort = 0;
while ((outPort = fx->getOutputConnection(0)))
outPort->setFx(0);
}
}
m_imp->m_columnSet.removeColumn(col);
m_imp->m_pegTree->removeColumn(col);
}
//-----------------------------------------------------------------------------
void TXsheet::moveColumn(int srcIndex, int dstIndex)
{
if (srcIndex == dstIndex)
return;
assert(srcIndex >= 0);
assert(dstIndex >= 0);
int col = tmax(srcIndex, dstIndex);
if (col >= m_imp->m_columnSet.getColumnCount()) {
int n = m_imp->m_columnSet.getColumnCount();
touchColumn(col, TXshColumn::eLevelType);
while (n <= col) {
TXshColumn *column = getColumn(n);
assert(column);
column->setXsheet(this);
n++;
}
}
assert(m_imp->m_columnSet.getColumnCount() > srcIndex);
assert(m_imp->m_columnSet.getColumnCount() > dstIndex);
if (srcIndex < dstIndex) {
int c0 = srcIndex;
int c1 = dstIndex;
assert(c0 < c1);
m_imp->m_columnSet.rollLeft(c0, c1 - c0 + 1);
for (int c = c0; c < c1; ++c)
m_imp->m_pegTree->swapColumns(c, c + 1);
} else {
int c0 = dstIndex;
int c1 = srcIndex;
assert(c0 < c1);
m_imp->m_columnSet.rollRight(c0, c1 - c0 + 1);
for (int c = c1 - 1; c >= c0; --c)
m_imp->m_pegTree->swapColumns(c, c + 1);
}
}
//-----------------------------------------------------------------------------
TXshColumn *TXsheet::getColumn(int col) const
{
return m_imp->m_columnSet.getColumn(col).getPointer();
}
//-----------------------------------------------------------------------------
int TXsheet::getColumnCount() const
{
return m_imp->m_columnSet.getColumnCount();
}
//-----------------------------------------------------------------------------
int TXsheet::getFirstFreeColumnIndex() const
{
int i = getColumnCount();
while (i > 0 && isColumnEmpty(i - 1))
--i;
return i;
}
//-----------------------------------------------------------------------------
TXshColumn *TXsheet::touchColumn(int index, TXshColumn::ColumnType type)
{
TXshColumn *column = m_imp->m_columnSet.touchColumn(index, type).getPointer();
if (!column)
return 0;
// NOTE (Daniele): The following && should be a bug... but I fear I'd break something changing it.
// Observe that the implied behavior is that of REPLACING AN EXISTING LEGITIMATE COLUMN!
// Please, Inquire further if you're not upon release!
if (column->isEmpty() && column->getColumnType() != type) {
removeColumn(index);
insertColumn(index, type);
column = getColumn(index);
}
return column;
}
//=============================================================================
namespace
{ // Utility function
//-----------------------------------------------------------------------------
void searchAudioColumn(TXsheet *xsh, std::vector<TXshSoundColumn *> &sounds, bool isPreview = true)
{
int i = 0;
int columns = xsh->getColumnCount();
for (; i < columns; ++i) {
TXshColumn *column = xsh->getColumn(i);
if (column) {
TXshSoundColumn *soundCol = column->getSoundColumn();
if (soundCol && ((isPreview && soundCol->isPreviewVisible()) || (!isPreview && soundCol->isCamstandVisible()))) {
sounds.push_back(soundCol);
continue;
}
}
}
}
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
TSoundTrack *TXsheet::makeSound(SoundProperties *properties)
{
std::vector<TXshSoundColumn *> sounds;
searchAudioColumn(this, sounds, properties->m_isPreview);
if (!m_imp->m_mixedSound || *properties != *m_soundProperties) {
if (!sounds.empty() && properties->m_fromFrame <= properties->m_toFrame)
m_imp->m_mixedSound = sounds[0]->mixingTogether(sounds, properties->m_fromFrame,
properties->m_toFrame, properties->m_frameRate);
else
m_imp->m_mixedSound = 0;
delete m_soundProperties;
m_soundProperties = properties;
} else
delete properties;
return m_imp->m_mixedSound.getPointer();
}
//-----------------------------------------------------------------------------
void TXsheet::scrub(int frame, bool isPreview)
{
double fps = getScene()->getProperties()->getOutputProperties()->getFrameRate();
TXsheet::SoundProperties *prop = new TXsheet::SoundProperties();
prop->m_isPreview = isPreview;
TSoundTrack *st = makeSound(prop); // Absorbs prop's ownership
if (!st)
return;
double samplePerFrame = st->getSampleRate() / fps;
double s0 = frame * samplePerFrame,
s1 = s0 + samplePerFrame;
play(st, s0, s1, false);
}
//-----------------------------------------------------------------------------
void TXsheet::stopScrub()
{
if (m_player)
m_player->stop();
}
//-----------------------------------------------------------------------------
void TXsheet::play(TSoundTrackP soundtrack, int s0, int s1, bool loop)
{
if (!TSoundOutputDevice::installed())
return;
if (!m_player)
m_player = new TSoundOutputDevice();
if (m_player) {
try {
m_player->play(soundtrack, s0, s1, loop);
} catch (TSoundDeviceException &) {
}
}
}
//-----------------------------------------------------------------------------
FxDag *TXsheet::getFxDag() const
{
return m_imp->m_fxDag;
}
//-----------------------------------------------------------------------------
ColumnFan *TXsheet::getColumnFan() const
{
return &m_imp->m_columnFan;
}
//-----------------------------------------------------------------------------
ToonzScene *TXsheet::getScene() const
{
return m_imp->m_scene;
}
//-----------------------------------------------------------------------------
void TXsheet::setScene(ToonzScene *scene)
{
m_imp->m_scene = scene;
}
//-----------------------------------------------------------------------------
bool TXsheet::checkCircularReferences(const TXshCell &cellCandidate)
{
if (cellCandidate.isEmpty() || !cellCandidate.m_level->getChildLevel())
return false;
TXsheet *childCandidate = cellCandidate.m_level->getChildLevel()->getXsheet();
return checkCircularReferences(childCandidate);
}
//-----------------------------------------------------------------------------
bool TXsheet::checkCircularReferences(TXshColumn *columnCandidate)
{
if (!columnCandidate || !columnCandidate->getLevelColumn())
return false;
TXshLevelColumn *lc = columnCandidate->getLevelColumn();
int r0 = 0, r1 = -1;
if (lc->getRange(r0, r1) <= 0)
return false;
int r;
TXshCell oldCell;
for (r = r0; r <= r1; r++) {
TXshCell cell = lc->getCell(r);
// to speed up:
if (cell.m_level.getPointer() == oldCell.m_level.getPointer())
continue;
if (checkCircularReferences(cell))
return true;
oldCell = cell;
}
return false;
}
//-----------------------------------------------------------------------------
void TXsheet::invalidateSound()
{
m_imp->m_mixedSound = TSoundTrackP();
}
//-----------------------------------------------------------------------------
bool TXsheet::checkCircularReferences(TXsheet *childCandidate)
{
if (this == childCandidate)
return true;
if (childCandidate == 0)
return false;
int i;
for (i = 0; i < childCandidate->getColumnCount(); i++)
if (checkCircularReferences(childCandidate->getColumn(i)))
return true;
return false;
}
//-----------------------------------------------------------------------------
// Builds the camstand bbox associated to the specified xsheet
TRectD TXsheet::getBBox(int r) const
{
static const double maxDouble = (std::numeric_limits<double>::max)();
static const TRectD voidRect(maxDouble, maxDouble, -maxDouble, -maxDouble);
//-----------------------------------------------------------------------
struct locals {
static TRectD getBBox(const TXsheet *xsh, int r, int c)
{
// Discriminate cell content
const TXshCell &cell = xsh->getCell(r, c);
if (cell.isEmpty())
return voidRect;
if (TXshChildLevel *cl = cell.getChildLevel())
return cl->getXsheet()->getBBox(cell.getFrameId().getNumber() - 1);
TXshSimpleLevel *sl = cell.getSimpleLevel();
if (!sl || !(sl->getType() & LEVELCOLUMN_XSHLEVEL)) // Avoid other mesh levels - which could
return voidRect; // be deformed too...
// Retrieve column affine
TAffine columnZaff;
{
TStageObject *colObj = xsh->getStageObject(TStageObjectId::ColumnId(c));
const TAffine &columnAff = colObj->getPlacement(r); // ...
double columnZ = colObj->getZ(r); // ...
double columnNoScaleZ = colObj->getGlobalNoScaleZ();
TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
TStageObject *camera = xsh->getStageObject(cameraId);
const TAffine &cameraAff = camera->getPlacement(r); // ...
double cameraZ = camera->getZ(r); // ...
if (!TStageObject::perspective(
columnZaff,
cameraAff, cameraZ,
columnAff, columnZ,
columnNoScaleZ))
return voidRect;
}
const TRectD &bbox = sl->getBBox(cell.getFrameId());
if (bbox.getLx() <= 0.0 || bbox.getLy() <= 0.0)
return voidRect;
return columnZaff * TScale(Stage::inch, Stage::inch) * bbox;
}
};
//-----------------------------------------------------------------------
// Initialize a union-neutral rect
TRectD bbox(voidRect);
// Traverse the xsheet's columns, adding the bbox of each
int c, cCount = getColumnCount();
for (c = 0; c != cCount; ++c) {
// Skip empty or invisible columns
TXshColumn *column = getColumn(c);
if (column->isEmpty() || !column->isCamstandVisible())
continue;
const TRectD &colBBox = locals::getBBox(this, r, c);
// Make the union
bbox.x0 = tmin(bbox.x0, colBBox.x0);
bbox.y0 = tmin(bbox.y0, colBBox.y0);
bbox.x1 = tmax(bbox.x1, colBBox.x1);
bbox.y1 = tmax(bbox.y1, colBBox.y1);
}
return bbox;
}