1662 lines
56 KiB
C++
1662 lines
56 KiB
C++
|
|
|
|
// TnzCore includes
|
|
#include "tpixelutils.h"
|
|
#include "tstroke.h"
|
|
#include "tofflinegl.h"
|
|
#include "tstencilcontrol.h"
|
|
#include "tvectorgl.h"
|
|
#include "tvectorrenderdata.h"
|
|
#include "tcolorfunctions.h"
|
|
#include "tpalette.h"
|
|
#include "tropcm.h"
|
|
#include "trasterimage.h"
|
|
#include "tvectorimage.h"
|
|
#include "tmeshimage.h"
|
|
#include "tcolorstyles.h"
|
|
#include "timage_io.h"
|
|
#include "tregion.h"
|
|
#include "toonz/toonzscene.h"
|
|
|
|
// TnzBase includes
|
|
#include "tenv.h"
|
|
|
|
// TnzExt includes
|
|
#include "ext/meshutils.h"
|
|
#include "ext/plasticskeleton.h"
|
|
#include "ext/plasticskeletondeformation.h"
|
|
#include "ext/plasticdeformerstorage.h"
|
|
|
|
// TnzLib includes
|
|
#include "toonz/stageplayer.h"
|
|
#include "toonz/stage.h"
|
|
#include "toonz/stage2.h"
|
|
#include "toonz/tcolumnfx.h"
|
|
#include "toonz/txsheet.h"
|
|
#include "toonz/txshsimplelevel.h"
|
|
#include "toonz/txshchildlevel.h"
|
|
#include "toonz/txshcolumn.h"
|
|
#include "toonz/txshcell.h"
|
|
#include "toonz/onionskinmask.h"
|
|
#include "toonz/dpiscale.h"
|
|
#include "toonz/imagemanager.h"
|
|
#include "toonz/tstageobjecttree.h"
|
|
#include "toonz/glrasterpainter.h"
|
|
#include "toonz/preferences.h"
|
|
#include "toonz/fill.h"
|
|
#include "toonz/levelproperties.h"
|
|
#include "toonz/autoclose.h"
|
|
#include "toonz/txshleveltypes.h"
|
|
#include "imagebuilders.h"
|
|
#include "toonz/tframehandle.h"
|
|
#include "toonz/preferences.h"
|
|
|
|
// Qt includes
|
|
#include <QImage>
|
|
#include <QPainter>
|
|
#include <QPolygon>
|
|
#include <QThreadStorage>
|
|
#include <QMatrix>
|
|
#include <QThread>
|
|
#include <QGuiApplication>
|
|
|
|
#include "toonz/stagevisitor.h"
|
|
|
|
//**********************************************************************************************
|
|
// Stage namespace
|
|
//**********************************************************************************************
|
|
|
|
/*! \namespace Stage
|
|
\brief The Stage namespace provides objects, classes and methods useful to
|
|
view or display images.
|
|
*/
|
|
|
|
using namespace Stage;
|
|
|
|
/*! \var Stage::inch
|
|
For historical reasons camera stand is defined in a coordinate
|
|
system in which
|
|
an inch is equal to 'Stage::inch' unit.
|
|
Pay attention: modify this value condition apparent line
|
|
thickness of
|
|
images .pli.
|
|
*/
|
|
// const double Stage::inch = 53.33333;
|
|
|
|
//**********************************************************************************************
|
|
// Local namespace
|
|
//**********************************************************************************************
|
|
|
|
namespace {
|
|
|
|
QImage rasterToQImage(const TRaster32P &ras) {
|
|
QImage image(ras->getRawData(), ras->getLx(), ras->getLy(),
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
return image;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
QImage rasterToQImage(const TRasterGR8P &ras) {
|
|
QImage image(ras->getLx(), ras->getLy(), QImage::Format_ARGB32_Premultiplied);
|
|
int lx = ras->getLx(), ly = ras->getLy();
|
|
|
|
for (int y = 0; y < ly; y++) {
|
|
TPixelGR8 *pix = ras->pixels(y);
|
|
TPixelGR8 *endPix = pix + lx;
|
|
QRgb *outPix = (QRgb *)image.scanLine(y);
|
|
for (; pix < endPix; ++pix) {
|
|
int value = pix->value;
|
|
*outPix++ = qRgba(value, value, value, 255);
|
|
}
|
|
}
|
|
return image;
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
QImage rasterToQImage(const TRasterP &ras) {
|
|
if (TRaster32P src32 = ras)
|
|
return rasterToQImage(src32);
|
|
else if (TRasterGR8P srcGr8 = ras)
|
|
return rasterToQImage(srcGr8);
|
|
|
|
// assert(!"Cannot use drawImage with this image!");
|
|
return QImage();
|
|
}
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
//! Draw orthogonal projection of \b bbox onto x-axis and y-axis.
|
|
void draw3DShadow(const TRectD &bbox, double z, double phi) {
|
|
// bruttino assai, ammetto
|
|
|
|
double a = bigBoxSize[0];
|
|
double b = bigBoxSize[1];
|
|
|
|
glColor3d(0.9, 0.9, 0.86);
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(bbox.x0, bbox.y0, z);
|
|
glVertex3d(bbox.x0, bbox.y1, z);
|
|
glVertex3d(bbox.x1, bbox.y1, z);
|
|
glVertex3d(bbox.x1, bbox.y0, z);
|
|
glVertex3d(bbox.x0, bbox.y0, z);
|
|
glEnd();
|
|
|
|
double y = -b;
|
|
double x = phi >= 0 ? a : -a;
|
|
|
|
double xm = 0.5 * (bbox.x0 + bbox.x1);
|
|
double ym = 0.5 * (bbox.y0 + bbox.y1);
|
|
|
|
if (bbox.y0 > y) {
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(xm, y, z);
|
|
glVertex3d(xm, bbox.y0, z);
|
|
glEnd();
|
|
} else if (bbox.y1 < y) {
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(xm, y, z);
|
|
glVertex3d(xm, bbox.y1, z);
|
|
glEnd();
|
|
}
|
|
|
|
if (bbox.x0 > x) {
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(x, ym, z);
|
|
glVertex3d(bbox.x0, ym, z);
|
|
glEnd();
|
|
} else if (bbox.x1 < x) {
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(x, ym, z);
|
|
glVertex3d(bbox.x1, ym, z);
|
|
glEnd();
|
|
}
|
|
|
|
glColor3d(0.0, 0.0, 0.0);
|
|
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(bbox.x0, -b, z);
|
|
glVertex3d(bbox.x1, -b, z);
|
|
glEnd();
|
|
|
|
glBegin(GL_LINE_STRIP);
|
|
glVertex3d(x, bbox.y0, z);
|
|
glVertex3d(x, bbox.y1, z);
|
|
glEnd();
|
|
}
|
|
|
|
//=====================================================================
|
|
|
|
// Plastic function declarations
|
|
|
|
/*!
|
|
Returns from the specified player the stage object to be plastic
|
|
deformed - or 0 if current Toonz rules prevent it from being deformed.
|
|
*/
|
|
TStageObject *plasticDeformedObj(const Stage::Player &player,
|
|
const PlasticVisualSettings &pvs);
|
|
|
|
//! Draws the specified mesh image
|
|
void onMeshImage(TMeshImage *mi, const Stage::Player &player,
|
|
const ImagePainter::VisualSettings &vs,
|
|
const TAffine &viewAff);
|
|
|
|
//! Applies Plastic deformation of the specified player's stage object.
|
|
void onPlasticDeformedImage(TStageObject *playerObj,
|
|
const Stage::Player &player,
|
|
const ImagePainter::VisualSettings &vs,
|
|
const TAffine &viewAff);
|
|
|
|
} // namespace
|
|
|
|
//**********************************************************************************************
|
|
// Picker implementation
|
|
//**********************************************************************************************
|
|
|
|
Picker::Picker(const TAffine &viewAff, const TPointD &point,
|
|
const ImagePainter::VisualSettings &vs, int devPixRatio)
|
|
: Visitor(vs)
|
|
, m_viewAff(viewAff)
|
|
, m_point(point)
|
|
, m_columnIndexes()
|
|
, m_minDist2(25.0)
|
|
, m_devPixRatio(devPixRatio) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::setMinimumDistance(double d) {
|
|
m_minDist2 = (double)(m_devPixRatio * m_devPixRatio) * d * d;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::onImage(const Stage::Player &player) {
|
|
// if m_currentColumnIndex is other than the default value (-1),
|
|
// then pick only the current column.
|
|
if (m_currentColumnIndex != -1 &&
|
|
m_currentColumnIndex != player.m_ancestorColumnIndex)
|
|
return;
|
|
|
|
bool picked = false;
|
|
TAffine aff = m_viewAff * player.m_placement;
|
|
TPointD point = aff.inv() * m_point;
|
|
|
|
const TImageP &img = player.image();
|
|
|
|
if (TVectorImageP vi = img) {
|
|
double w = 0;
|
|
UINT strokeIndex = 0;
|
|
double dist2 = 0;
|
|
TRegion *r = vi->getRegion(point);
|
|
int styleId = 0;
|
|
if (r) styleId = r->getStyle();
|
|
if (styleId != 0)
|
|
picked = true;
|
|
else if (vi->getNearestStroke(point, w, strokeIndex, dist2)) {
|
|
dist2 *= aff.det();
|
|
|
|
TStroke *stroke = vi->getStroke(strokeIndex);
|
|
TThickPoint thickPoint = stroke->getThickPoint(w);
|
|
double len2 = thickPoint.thick * thickPoint.thick * aff.det();
|
|
double checkDist = std::max(m_minDist2, len2);
|
|
if (dist2 < checkDist) picked = true;
|
|
}
|
|
} else if (TRasterImageP ri = img) {
|
|
TRaster32P ras = ri->getRaster();
|
|
if (!ras) return;
|
|
|
|
ras->lock();
|
|
TPointD pp = player.m_dpiAff.inv() * point + ras->getCenterD();
|
|
TPoint p(tround(pp.x), tround(pp.y));
|
|
if (!ras->getBounds().contains(p)) return;
|
|
|
|
TPixel32 *pix = ras->pixels(p.y);
|
|
if (pix[p.x].m != 0) picked = true;
|
|
|
|
TAffine aff2 = (aff * player.m_dpiAff).inv();
|
|
|
|
TPointD pa(p.x, p.y);
|
|
TPointD dx = aff2 * (m_point + TPointD(3, 0)) - aff2 * m_point;
|
|
TPointD dy = aff2 * (m_point + TPointD(0, 3)) - aff2 * m_point;
|
|
double rx = dx.x * dx.x + dx.y * dx.y;
|
|
double ry = dy.x * dy.x + dy.y * dy.y;
|
|
int radius = tround(sqrt(rx > ry ? rx : ry));
|
|
TRect rect = TRect(p.x - radius, p.y - radius, p.x + radius, p.y + radius) *
|
|
ras->getBounds();
|
|
for (int y = rect.y0; !picked && y <= rect.y1; y++) {
|
|
pix = ras->pixels(y);
|
|
for (int x = rect.x0; !picked && x <= rect.x1; x++)
|
|
if (pix[x].m != 0) picked = true;
|
|
}
|
|
|
|
ras->unlock();
|
|
} else if (TToonzImageP ti = img) {
|
|
TRasterCM32P ras = ti->getRaster();
|
|
if (!ras) return;
|
|
|
|
ras->lock();
|
|
TPointD pp = player.m_dpiAff.inv() * point + ras->getCenterD();
|
|
TPoint p(tround(pp.x), tround(pp.y));
|
|
if (!ras->getBounds().contains(p)) return;
|
|
|
|
TPixelCM32 *pix = ras->pixels(p.y) + p.x;
|
|
if (!pix->isPurePaint() || pix->getPaint() != 0) picked = true;
|
|
|
|
ras->unlock();
|
|
}
|
|
|
|
if (picked) {
|
|
int columnIndex = player.m_ancestorColumnIndex;
|
|
if (m_columnIndexes.empty() || m_columnIndexes.back() != columnIndex)
|
|
m_columnIndexes.push_back(columnIndex);
|
|
|
|
int row = player.m_frame;
|
|
if (m_rows.empty() || m_rows.back() != row) m_rows.push_back(row);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::beginMask() {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::endMask() {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::enableMask(TStencilControl::MaskType maskType) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::disableMask() {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int Picker::getColumnIndex() const {
|
|
if (m_columnIndexes.empty())
|
|
return -1;
|
|
else
|
|
return m_columnIndexes.back();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void Picker::getColumnIndexes(std::vector<int> &indexes) const {
|
|
indexes = m_columnIndexes;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int Picker::getRow() const {
|
|
if (m_rows.empty())
|
|
return -1;
|
|
else
|
|
return m_rows.back();
|
|
}
|
|
|
|
//**********************************************************************************************
|
|
// RasterPainter implementation
|
|
//**********************************************************************************************
|
|
|
|
RasterPainter::RasterPainter(const TDimension &dim, const TAffine &viewAff,
|
|
const TRect &rect,
|
|
const ImagePainter::VisualSettings &vs,
|
|
bool checkFlags)
|
|
: Visitor(vs)
|
|
, m_dim(dim)
|
|
, m_viewAff(viewAff)
|
|
, m_clipRect(rect)
|
|
, m_maskLevel(0)
|
|
, m_singleColumnEnabled(false)
|
|
, m_checkFlags(checkFlags)
|
|
, m_doRasterDarkenBlendedView(false) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//! Utilizzato solo per TAB Pro
|
|
void RasterPainter::beginMask() {
|
|
flushRasterImages(); // per evitare che venga fatto dopo il beginMask
|
|
++m_maskLevel;
|
|
TStencilControl::instance()->beginMask();
|
|
}
|
|
//! Utilizzato solo per TAB Pro
|
|
void RasterPainter::endMask() {
|
|
flushRasterImages(); // se ci sono delle immagini raster nella maschera
|
|
// devono uscire ora
|
|
--m_maskLevel;
|
|
TStencilControl::instance()->endMask();
|
|
}
|
|
//! Utilizzato solo per TAB Pro
|
|
void RasterPainter::enableMask(TStencilControl::MaskType maskType) {
|
|
TStencilControl::instance()->enableMask(maskType);
|
|
}
|
|
//! Utilizzato solo per TAB Pro
|
|
void RasterPainter::disableMask() {
|
|
flushRasterImages(); // se ci sono delle immagini raster mascherate devono
|
|
// uscire ora
|
|
TStencilControl::instance()->disableMask();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TEnv::DoubleVar AutocloseDistance("InknpaintAutocloseDistance", 10.0);
|
|
TEnv::DoubleVar AutocloseAngle("InknpaintAutocloseAngle", 60.0);
|
|
TEnv::IntVar AutocloseInk("InknpaintAutocloseInk", 1);
|
|
TEnv::IntVar AutocloseOpacity("InknpaintAutocloseOpacity", 255);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int RasterPainter::getNodesCount() { return m_nodes.size(); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void RasterPainter::clearNodes() { m_nodes.clear(); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TRasterP RasterPainter::getRaster(int index, QMatrix &matrix) {
|
|
if ((int)m_nodes.size() <= index) return TRasterP();
|
|
|
|
if (m_nodes[index].m_onionMode != Node::eOnionSkinNone) return TRasterP();
|
|
|
|
if (m_nodes.empty()) return TRasterP();
|
|
|
|
double delta = sqrt(fabs(m_nodes[0].m_aff.det()));
|
|
TRectD bbox = m_nodes[0].m_bbox.enlarge(delta);
|
|
|
|
int i;
|
|
for (i = 1; i < (int)m_nodes.size(); i++) {
|
|
delta = sqrt(fabs(m_nodes[i].m_aff.det()));
|
|
bbox += m_nodes[i].m_bbox.enlarge(delta);
|
|
}
|
|
TRect rect(tfloor(bbox.x0), tfloor(bbox.y0), tceil(bbox.x1), tceil(bbox.y1));
|
|
rect = rect * TRect(0, 0, m_dim.lx - 1, m_dim.ly - 1);
|
|
|
|
TAffine aff = TTranslation(-rect.x0, -rect.y0) * m_nodes[index].m_aff;
|
|
matrix = QMatrix(aff.a11, aff.a21, aff.a12, aff.a22, aff.a13, aff.a23);
|
|
|
|
return m_nodes[index].m_raster;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! Make frame visualization.
|
|
\n If onon-skin is active, create a new raster with dimension containing
|
|
all
|
|
frame with onion-skin; recall \b TRop::quickPut with argument
|
|
each frame
|
|
with onion-skin and new raster. If onion-skin is not active
|
|
recall
|
|
\b TRop::quickPut with argument current frame and new raster.
|
|
*/
|
|
|
|
namespace {
|
|
QThreadStorage<std::vector<char> *> threadBuffers;
|
|
}
|
|
|
|
void RasterPainter::flushRasterImages() {
|
|
if (m_nodes.empty()) return;
|
|
|
|
// Build nodes bbox union
|
|
double delta = sqrt(fabs(m_nodes[0].m_aff.det()));
|
|
TRectD bbox = m_nodes[0].m_bbox.enlarge(delta);
|
|
|
|
int i, nodesCount = m_nodes.size();
|
|
for (i = 1; i < nodesCount; ++i) {
|
|
delta = sqrt(fabs(m_nodes[i].m_aff.det()));
|
|
bbox += m_nodes[i].m_bbox.enlarge(delta);
|
|
}
|
|
|
|
TRect rect(tfloor(bbox.x0), tfloor(bbox.y0), tceil(bbox.x1), tceil(bbox.y1));
|
|
rect = rect * TRect(0, 0, m_dim.lx - 1, m_dim.ly - 1);
|
|
|
|
int lx = rect.getLx(), ly = rect.getLy();
|
|
TDimension dim(lx, ly);
|
|
|
|
// this is needed since a stop motion live view
|
|
// doesn't register as a node correctly
|
|
// there is probably a better way to do this.
|
|
if (rect.getLx() == 0 && lx == 0) {
|
|
rect = m_clipRect;
|
|
dim = m_dim;
|
|
}
|
|
|
|
// Build a raster buffer of sufficient size to hold said union.
|
|
// The buffer is per-thread cached in order to improve the rendering speed.
|
|
if (!threadBuffers.hasLocalData())
|
|
threadBuffers.setLocalData(new std::vector<char>());
|
|
|
|
int size = dim.lx * dim.ly * sizeof(TPixel32);
|
|
|
|
std::vector<char> *vbuff = (std::vector<char> *)threadBuffers.localData();
|
|
if (size > (int)vbuff->size()) vbuff->resize(size);
|
|
|
|
TRaster32P ras(dim.lx, dim.ly, dim.lx, (TPixel32 *)&(*vbuff)[0]);
|
|
TRaster32P ras2;
|
|
|
|
if (m_vs.m_colorMask != 0) {
|
|
ras2 = TRaster32P(ras->getSize());
|
|
ras->clear();
|
|
} else
|
|
ras2 = ras;
|
|
|
|
// Clear the buffer - it will hold all the stacked nodes content to be overed
|
|
// on top of the OpenGL buffer through a glDrawPixel()
|
|
ras->lock();
|
|
|
|
ras->clear(); // ras is typically reused - and we need it transparent first
|
|
|
|
TRect r = rect - rect.getP00();
|
|
TRaster32P viewedRaster = ras->extract(r);
|
|
|
|
int current = -1;
|
|
|
|
// Retrieve preferences-related data
|
|
int tc = m_checkFlags ? ToonzCheck::instance()->getChecks() : 0;
|
|
int index = ToonzCheck::instance()->getColorIndex();
|
|
|
|
TPixel32 frontOnionColor, backOnionColor;
|
|
bool onionInksOnly;
|
|
|
|
Preferences::instance()->getOnionData(frontOnionColor, backOnionColor,
|
|
onionInksOnly);
|
|
|
|
// Stack every node on top of the raster buffer
|
|
for (i = 0; i < nodesCount; ++i) {
|
|
if (m_nodes[i].m_isCurrentColumn) current = i;
|
|
|
|
TAffine aff = TTranslation(-rect.x0, -rect.y0) * m_nodes[i].m_aff;
|
|
TDimension imageDim = m_nodes[i].m_raster->getSize();
|
|
TPointD offset(0.5, 0.5);
|
|
aff *= TTranslation(offset); // very quick and very dirty fix: in
|
|
// camerastand the images seems shifted of an
|
|
// half pixel...it's a quickput approximation?
|
|
|
|
TPixel32 colorscale = TPixel32(0, 0, 0, m_nodes[i].m_alpha);
|
|
int inksOnly;
|
|
|
|
if (m_nodes[i].m_onionMode != Node::eOnionSkinNone) {
|
|
inksOnly = onionInksOnly;
|
|
|
|
if (m_nodes[i].m_onionMode == Node::eOnionSkinFront)
|
|
colorscale = TPixel32(frontOnionColor.r, frontOnionColor.g,
|
|
frontOnionColor.b, m_nodes[i].m_alpha);
|
|
else if (m_nodes[i].m_onionMode == Node::eOnionSkinBack)
|
|
colorscale = TPixel32(backOnionColor.r, backOnionColor.g,
|
|
backOnionColor.b, m_nodes[i].m_alpha);
|
|
} else {
|
|
if (m_nodes[i].m_filterColor != TPixel32::Black) {
|
|
colorscale = m_nodes[i].m_filterColor;
|
|
colorscale.m = (typename TPixel32::Channel)((int)colorscale.m *
|
|
(int)m_nodes[i].m_alpha /
|
|
TPixel32::maxChannelValue);
|
|
}
|
|
inksOnly = tc & ToonzCheck::eInksOnly;
|
|
}
|
|
|
|
if (TRaster32P src32 = m_nodes[i].m_raster)
|
|
TRop::quickPut(viewedRaster, src32, aff, colorscale,
|
|
m_nodes[i].m_doPremultiply, m_nodes[i].m_whiteTransp,
|
|
m_nodes[i].m_isFirstColumn, m_doRasterDarkenBlendedView);
|
|
else if (TRasterGR8P srcGr8 = m_nodes[i].m_raster)
|
|
TRop::quickPut(viewedRaster, srcGr8, aff, colorscale);
|
|
else if (TRasterCM32P srcCm = m_nodes[i].m_raster) {
|
|
assert(m_nodes[i].m_palette);
|
|
int oldframe = m_nodes[i].m_palette->getFrame();
|
|
m_nodes[i].m_palette->setFrame(m_nodes[i].m_frame);
|
|
|
|
TPaletteP plt;
|
|
int styleIndex = -1;
|
|
if ((tc & ToonzCheck::eGap || tc & ToonzCheck::eAutoclose) &&
|
|
m_nodes[i].m_isCurrentColumn) {
|
|
srcCm = srcCm->clone();
|
|
plt = m_nodes[i].m_palette->clone();
|
|
styleIndex = plt->addStyle(TPixel::Magenta);
|
|
if (tc & ToonzCheck::eAutoclose)
|
|
TAutocloser(srcCm, AutocloseDistance, AutocloseAngle, styleIndex,
|
|
AutocloseOpacity)
|
|
.exec();
|
|
if (tc & ToonzCheck::eGap)
|
|
AreaFiller(srcCm).rectFill(m_nodes[i].m_savebox, 1, true, true,
|
|
false);
|
|
} else
|
|
plt = m_nodes[i].m_palette;
|
|
|
|
if (tc == 0 || tc == ToonzCheck::eBlackBg ||
|
|
!m_nodes[i].m_isCurrentColumn)
|
|
TRop::quickPut(viewedRaster, srcCm, plt, aff, colorscale, inksOnly);
|
|
else {
|
|
TRop::CmappedQuickputSettings settings;
|
|
|
|
settings.m_globalColorScale = colorscale;
|
|
settings.m_inksOnly = inksOnly;
|
|
settings.m_transparencyCheck =
|
|
tc & (ToonzCheck::eTransparency | ToonzCheck::eGap);
|
|
settings.m_blackBgCheck = tc & ToonzCheck::eBlackBg;
|
|
/*-- InkCheck, Ink#1Check, PaintCheckはカレントカラムにのみ有効 --*/
|
|
settings.m_inkIndex =
|
|
m_nodes[i].m_isCurrentColumn
|
|
? (tc & ToonzCheck::eInk ? index
|
|
: (tc & ToonzCheck::eInk1 ? 1 : -1))
|
|
: -1;
|
|
settings.m_paintIndex = m_nodes[i].m_isCurrentColumn
|
|
? (tc & ToonzCheck::ePaint ? index : -1)
|
|
: -1;
|
|
|
|
Preferences::instance()->getTranspCheckData(
|
|
settings.m_transpCheckBg, settings.m_transpCheckInk,
|
|
settings.m_transpCheckPaint);
|
|
|
|
settings.m_isOnionSkin = m_nodes[i].m_onionMode != Node::eOnionSkinNone;
|
|
settings.m_gapCheckIndex = styleIndex;
|
|
|
|
TRop::quickPut(viewedRaster, srcCm, plt, aff, settings);
|
|
}
|
|
|
|
srcCm = TRasterCM32P();
|
|
plt = TPaletteP();
|
|
|
|
m_nodes[i].m_palette->setFrame(oldframe);
|
|
} else
|
|
assert(!"Cannot use quickput with this raster combination!");
|
|
}
|
|
|
|
if (m_vs.m_colorMask != 0) {
|
|
TRop::setChannel(ras, ras, m_vs.m_colorMask, false);
|
|
TRop::quickPut(ras2, ras, TAffine());
|
|
}
|
|
|
|
// Now, output the raster buffer on top of the OpenGL buffer
|
|
glPushAttrib(GL_COLOR_BUFFER_BIT); // Preserve blending and stuff
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE,
|
|
GL_ONE_MINUS_SRC_ALPHA); // The raster buffer is intended in
|
|
// premultiplied form - thus the GL_ONE on src
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_DITHER);
|
|
glDisable(GL_LOGIC_OP);
|
|
|
|
/* disable, since these features are never enabled, and cause OpenGL to assert
|
|
* on systems that don't support them: see #591 */
|
|
#if 0
|
|
#ifdef GL_EXT_convolution
|
|
if( GLEW_EXT_convolution ) {
|
|
glDisable(GL_CONVOLUTION_1D_EXT);
|
|
glDisable(GL_CONVOLUTION_2D_EXT);
|
|
glDisable(GL_SEPARABLE_2D_EXT);
|
|
}
|
|
#endif
|
|
|
|
#ifdef GL_EXT_histogram
|
|
if( GLEW_EXT_histogram ) {
|
|
glDisable(GL_HISTOGRAM_EXT);
|
|
glDisable(GL_MINMAX_EXT);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef GL_EXT_texture3D
|
|
if (GL_EXT_texture3D) {
|
|
glDisable(GL_TEXTURE_3D_EXT);
|
|
}
|
|
#endif
|
|
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
glRasterPos2d(rect.x0, rect.y0);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
glDrawPixels(ras2->getLx(), ras2->getLy(), // Perform the over
|
|
TGL_FMT, TGL_TYPE, ras2->getRawData()); //
|
|
|
|
ras->unlock();
|
|
glPopMatrix();
|
|
|
|
glPopAttrib(); // Restore blending status
|
|
|
|
if (m_vs.m_showBBox && current > -1) {
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
tglColor(TPixel(200, 200, 200));
|
|
tglMultMatrix(m_nodes[current].m_aff);
|
|
tglDrawRect(m_nodes[current].m_raster->getBounds());
|
|
glPopMatrix();
|
|
}
|
|
|
|
m_nodes.clear();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! Make frame visualization in QPainter.
|
|
\n Draw in painter mode just raster image in m_nodes.
|
|
\n Onon-skin or channel mode are not considered.
|
|
*/
|
|
void RasterPainter::drawRasterImages(QPainter &p, QPolygon cameraPol) {
|
|
if (m_nodes.empty()) return;
|
|
|
|
double delta = sqrt(fabs(m_nodes[0].m_aff.det()));
|
|
TRectD bbox = m_nodes[0].m_bbox.enlarge(delta);
|
|
|
|
int i;
|
|
for (i = 1; i < (int)m_nodes.size(); i++) {
|
|
delta = sqrt(fabs(m_nodes[i].m_aff.det()));
|
|
bbox += m_nodes[i].m_bbox.enlarge(delta);
|
|
}
|
|
TRect rect(tfloor(bbox.x0), tfloor(bbox.y0), tceil(bbox.x1), tceil(bbox.y1));
|
|
rect = rect * TRect(0, 0, m_dim.lx - 1, m_dim.ly - 1);
|
|
|
|
TRect r = rect - rect.getP00();
|
|
TAffine flipY(1, 0, 0, 0, -1, m_dim.ly);
|
|
p.setClipRegion(QRegion(cameraPol));
|
|
for (i = 0; i < (int)m_nodes.size(); i++) {
|
|
if (m_nodes[i].m_onionMode != Node::eOnionSkinNone) continue;
|
|
p.resetTransform();
|
|
TRasterP ras = m_nodes[i].m_raster;
|
|
TAffine aff = TTranslation(-rect.x0, -rect.y0) * flipY * m_nodes[i].m_aff;
|
|
QMatrix matrix(aff.a11, aff.a21, aff.a12, aff.a22, aff.a13, aff.a23);
|
|
QImage image = rasterToQImage(ras);
|
|
if (image.isNull()) continue;
|
|
p.setMatrix(matrix);
|
|
p.drawImage(rect.getP00().x, rect.getP00().y, image);
|
|
}
|
|
|
|
p.resetTransform();
|
|
m_nodes.clear();
|
|
}
|
|
|
|
static void buildAutocloseImage(
|
|
TVectorImage *vaux, TVectorImage *vi,
|
|
const std::vector<std::pair<int, double>> &startPoints,
|
|
const std::vector<std::pair<int, double>> &endPoints) {
|
|
for (UINT i = 0; i < startPoints.size(); i++) {
|
|
TThickPoint p1 = vi->getStroke(startPoints[i].first)
|
|
->getThickPoint(startPoints[i].second);
|
|
TThickPoint p2 =
|
|
vi->getStroke(endPoints[i].first)->getThickPoint(endPoints[i].second);
|
|
std::vector<TThickPoint> points(3);
|
|
points[0] = p1;
|
|
points[1] = 0.5 * (p1 + p2);
|
|
points[2] = p2;
|
|
points[0].thick = points[1].thick = points[2].thick = 0.0;
|
|
TStroke *auxStroke = new TStroke(points);
|
|
auxStroke->setStyle(2);
|
|
vaux->addStroke(auxStroke);
|
|
}
|
|
}
|
|
|
|
TEnv::DoubleVar AutocloseFactor("InknpaintAutocloseFactor", 4.0);
|
|
|
|
static void drawAutocloses(TVectorImage *vi, TVectorRenderData &rd) {
|
|
static TPalette *plt = 0;
|
|
if (!plt) {
|
|
plt = new TPalette();
|
|
plt->addStyle(TPixel::Magenta);
|
|
}
|
|
|
|
std::vector<std::pair<int, double>> startPoints, endPoints;
|
|
getClosingPoints(vi->getBBox(), AutocloseFactor, vi, startPoints, endPoints);
|
|
TVectorImage *vaux = new TVectorImage();
|
|
|
|
rd.m_palette = plt;
|
|
buildAutocloseImage(vaux, vi, startPoints, endPoints);
|
|
// temporarily disable fill check, to preserve the gap indicator color
|
|
bool tCheckEnabledOriginal = rd.m_tcheckEnabled;
|
|
rd.m_tcheckEnabled = false;
|
|
// draw
|
|
tglDraw(rd, vaux);
|
|
// restore original value
|
|
rd.m_tcheckEnabled = tCheckEnabledOriginal;
|
|
delete vaux;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! Take image from \b Stage::Player \b data and recall the right method for
|
|
this kind of image, for vector image recall \b onVectorImage(),
|
|
for raster
|
|
image recall \b onRasterImage() for toonz image recall \b
|
|
onToonzImage().
|
|
*/
|
|
void RasterPainter::onImage(const Stage::Player &player) {
|
|
if (m_singleColumnEnabled && !player.m_isCurrentColumn && !player.m_isMask)
|
|
return;
|
|
|
|
// Attempt Plastic-deformed drawing
|
|
// For now generating icons of plastic-deformed image causes crash as
|
|
// QOffscreenSurface is created outside the gui thread.
|
|
// As a quick workaround, ignore the deformation if this is called from
|
|
// non-gui thread (i.e. icon generator thread)
|
|
// 12/1/2018 Now the scene icon is rendered without deformation either
|
|
// since it causes unknown error with opengl contexts..
|
|
TStageObject *obj =
|
|
::plasticDeformedObj(player, m_vs.m_plasticVisualSettings);
|
|
if (obj && QThread::currentThread() == qGuiApp->thread() &&
|
|
(!m_vs.m_forSceneIcon || m_vs.m_forReference)) {
|
|
flushRasterImages();
|
|
::onPlasticDeformedImage(obj, player, m_vs, m_viewAff);
|
|
} else {
|
|
// Common image draw
|
|
const TImageP &img = player.image();
|
|
|
|
if (TVectorImageP vi = img)
|
|
onVectorImage(vi.getPointer(), player);
|
|
else if (TRasterImageP ri = img)
|
|
onRasterImage(ri.getPointer(), player);
|
|
else if (TToonzImageP ti = img)
|
|
onToonzImage(ti.getPointer(), player);
|
|
else if (TMeshImageP mi = img) {
|
|
// Never draw mesh when building reference image
|
|
if (m_vs.m_forReference) return;
|
|
flushRasterImages();
|
|
::onMeshImage(mi.getPointer(), player, m_vs, m_viewAff);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! View a vector cell images.
|
|
\n If onion-skin is active compute \b TOnionFader value.
|
|
Create and boot a \b TVectorRenderData and recall \b tglDraw().
|
|
*/
|
|
void RasterPainter::onVectorImage(TVectorImage *vi,
|
|
const Stage::Player &player) {
|
|
flushRasterImages();
|
|
|
|
// When loaded, vectorimages needs to have regions recomputed, but doing that
|
|
// while loading them
|
|
// is quite slow (think about loading whole scenes!..). They are recomputed
|
|
// the first time they
|
|
// are selected and shown on screen...except when playing back, to avoid
|
|
// slowness!
|
|
|
|
// (Daniele) This function should *NOT* be responsible of that.
|
|
// It's the *image itself* that should recalculate or initialize
|
|
// said data
|
|
// if queried about it and turns out it's not available...
|
|
|
|
if (!player.m_isPlaying && player.m_isCurrentColumn)
|
|
vi->recomputeRegionsIfNeeded();
|
|
|
|
const Preferences &prefs = *Preferences::instance();
|
|
|
|
TColorFunction *cf = 0, *guidedCf = 0;
|
|
TPalette *vPalette = vi->getPalette();
|
|
TPixel32 bgColor = TPixel32::White;
|
|
|
|
int tc = (m_checkFlags && player.m_isCurrentColumn)
|
|
? ToonzCheck::instance()->getChecks()
|
|
: 0;
|
|
bool inksOnly = tc & ToonzCheck::eInksOnly;
|
|
|
|
int oldFrame = vPalette->getFrame();
|
|
vPalette->setFrame(player.m_frame);
|
|
|
|
UCHAR useOpacity = player.m_opacity;
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
useOpacity *= 0.30;
|
|
|
|
if (player.m_onionSkinDistance != c_noOnionSkin) {
|
|
TPixel32 frontOnionColor, backOnionColor;
|
|
|
|
if (player.m_onionSkinDistance != 0 &&
|
|
(!player.m_isShiftAndTraceEnabled ||
|
|
Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts())) {
|
|
prefs.getOnionData(frontOnionColor, backOnionColor, inksOnly);
|
|
bgColor =
|
|
(player.m_onionSkinDistance < 0) ? backOnionColor : frontOnionColor;
|
|
}
|
|
|
|
double m[4] = {1.0, 1.0, 1.0, 1.0}, c[4];
|
|
|
|
// Weighted addition to RGB and matte multiplication
|
|
m[3] = 1.0 -
|
|
((player.m_onionSkinDistance == 0)
|
|
? 0.1
|
|
: OnionSkinMask::getOnionSkinFade(player.m_onionSkinDistance));
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn) m[3] *= 0.30;
|
|
|
|
c[0] = (1.0 - m[3]) * bgColor.r, c[1] = (1.0 - m[3]) * bgColor.g,
|
|
c[2] = (1.0 - m[3]) * bgColor.b;
|
|
c[3] = 0.0;
|
|
|
|
cf = new TGenericColorFunction(m, c);
|
|
} else if (player.m_filterColor != TPixel::Black) {
|
|
TPixel32 colorScale = player.m_filterColor;
|
|
colorScale.m = useOpacity;
|
|
cf = new TColumnColorFilterFunction(colorScale);
|
|
} else if (useOpacity < 255)
|
|
cf = new TTranspFader(useOpacity / 255.0);
|
|
|
|
TVectorRenderData rd(m_viewAff * player.m_placement, TRect(), vPalette, cf,
|
|
true // alpha enabled
|
|
);
|
|
|
|
rd.m_drawRegions = !inksOnly;
|
|
rd.m_inkCheckEnabled = tc & ToonzCheck::eInk;
|
|
rd.m_ink1CheckEnabled = tc & ToonzCheck::eInk1;
|
|
rd.m_paintCheckEnabled = tc & ToonzCheck::ePaint;
|
|
rd.m_blackBgEnabled = tc & ToonzCheck::eBlackBg;
|
|
rd.m_colorCheckIndex = ToonzCheck::instance()->getColorIndex();
|
|
rd.m_show0ThickStrokes = prefs.getShow0ThickLines();
|
|
rd.m_regionAntialias = prefs.getRegionAntialias();
|
|
rd.m_animatedGuidedDrawing = prefs.getAnimatedGuidedDrawing();
|
|
if (player.m_onionSkinDistance != 0 &&
|
|
(player.m_isCurrentColumn || player.m_isCurrentXsheetLevel)) {
|
|
if (player.m_isGuidedDrawingEnabled == 3 // show guides on all
|
|
|| (player.m_isGuidedDrawingEnabled == 1 && // show guides on closest
|
|
(player.m_onionSkinDistance == player.m_firstBackOnionSkin ||
|
|
player.m_onionSkinDistance == player.m_firstFrontOnionSkin)) ||
|
|
(player.m_isGuidedDrawingEnabled == 2 && // show guides on farthest
|
|
(player.m_onionSkinDistance == player.m_onionSkinBackSize ||
|
|
player.m_onionSkinDistance == player.m_onionSkinFrontSize)) ||
|
|
(player.m_isEditingLevel && // fix for level editing mode sending extra
|
|
// players
|
|
player.m_isGuidedDrawingEnabled == 2 &&
|
|
player.m_onionSkinDistance == player.m_lastBackVisibleSkin)) {
|
|
rd.m_showGuidedDrawing = player.m_isGuidedDrawingEnabled > 0;
|
|
int currentStrokeCount = 0;
|
|
int totalStrokes = vi->getStrokeCount();
|
|
TXshSimpleLevel *sl = player.m_sl;
|
|
|
|
if (sl) {
|
|
TImageP image = sl->getFrame(player.m_currentFrameId, false);
|
|
TVectorImageP vecImage = image;
|
|
if (vecImage) currentStrokeCount = vecImage->getStrokeCount();
|
|
if (currentStrokeCount < 0) currentStrokeCount = 0;
|
|
if (player.m_guidedFrontStroke != -1 &&
|
|
(player.m_onionSkinDistance == player.m_onionSkinFrontSize ||
|
|
player.m_onionSkinDistance == player.m_firstFrontOnionSkin))
|
|
rd.m_indexToHighlight = player.m_guidedFrontStroke;
|
|
else if (player.m_guidedBackStroke != -1 &&
|
|
(player.m_onionSkinDistance == player.m_onionSkinBackSize ||
|
|
player.m_onionSkinDistance == player.m_firstBackOnionSkin))
|
|
rd.m_indexToHighlight = player.m_guidedBackStroke;
|
|
else if (currentStrokeCount < totalStrokes)
|
|
rd.m_indexToHighlight = currentStrokeCount;
|
|
|
|
double guidedM[4] = {1.0, 1.0, 1.0, 1.0}, guidedC[4];
|
|
TPixel32 bgColor = TPixel32::Blue;
|
|
guidedM[3] =
|
|
1.0 -
|
|
((player.m_onionSkinDistance == 0)
|
|
? 0.1
|
|
: OnionSkinMask::getOnionSkinFade(player.m_onionSkinDistance));
|
|
|
|
guidedC[0] = (1.0 - guidedM[3]) * bgColor.r,
|
|
guidedC[1] = (1.0 - guidedM[3]) * bgColor.g,
|
|
guidedC[2] = (1.0 - guidedM[3]) * bgColor.b;
|
|
guidedC[3] = 0.0;
|
|
|
|
guidedCf = new TGenericColorFunction(guidedM, guidedC);
|
|
rd.m_guidedCf = guidedCf;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tc & (ToonzCheck::eTransparency | ToonzCheck::eGap)) {
|
|
TPixel dummy;
|
|
rd.m_tcheckEnabled = true;
|
|
|
|
if (rd.m_blackBgEnabled)
|
|
prefs.getTranspCheckData(rd.m_tCheckInk, dummy, rd.m_tCheckPaint);
|
|
else
|
|
prefs.getTranspCheckData(dummy, rd.m_tCheckInk, rd.m_tCheckPaint);
|
|
}
|
|
|
|
if (m_vs.m_colorMask != 0) {
|
|
glColorMask((m_vs.m_colorMask & TRop::RChan) ? GL_TRUE : GL_FALSE,
|
|
(m_vs.m_colorMask & TRop::GChan) ? GL_TRUE : GL_FALSE,
|
|
(m_vs.m_colorMask & TRop::BChan) ? GL_TRUE : GL_FALSE, GL_TRUE);
|
|
}
|
|
TVectorImageP viDelete;
|
|
if (tc & ToonzCheck::eGap) {
|
|
viDelete = vi->clone();
|
|
vi = viDelete.getPointer();
|
|
vi->selectFill(vi->getBBox(), 0, 1, true, true, false);
|
|
}
|
|
|
|
TStroke *guidedStroke = 0;
|
|
if (m_maskLevel > 0)
|
|
tglDrawMask(rd, vi);
|
|
else
|
|
tglDraw(rd, vi, &guidedStroke);
|
|
|
|
if (tc & ToonzCheck::eAutoclose) drawAutocloses(vi, rd);
|
|
|
|
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
vPalette->setFrame(oldFrame);
|
|
|
|
delete cf;
|
|
delete guidedCf;
|
|
|
|
if (guidedStroke) m_guidedStrokes.push_back(guidedStroke);
|
|
}
|
|
|
|
//-----------------------------------------------------
|
|
|
|
/*! Create a \b Node and put it in \b m_nodes.
|
|
*/
|
|
void RasterPainter::onRasterImage(TRasterImage *ri,
|
|
const Stage::Player &player) {
|
|
TRasterP r = ri->getRaster();
|
|
|
|
TAffine aff;
|
|
aff = m_viewAff * player.m_placement * player.m_dpiAff;
|
|
aff = TTranslation(m_dim.lx * 0.5, m_dim.ly * 0.5) * aff *
|
|
TTranslation(-r->getCenterD() +
|
|
convert(ri->getOffset())); // this offset is !=0 only if
|
|
// in cleanup camera test mode
|
|
|
|
TRectD bbox = TRectD(0, 0, m_dim.lx, m_dim.ly);
|
|
bbox *= convert(m_clipRect);
|
|
if (bbox.isEmpty()) return;
|
|
|
|
UCHAR useOpacity = player.m_opacity;
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
useOpacity *= 0.30;
|
|
|
|
int alpha = 255;
|
|
Node::OnionMode onionMode = Node::eOnionSkinNone;
|
|
if (player.m_onionSkinDistance != c_noOnionSkin) {
|
|
// GetOnionSkinFade va bene per il vettoriale mentre il raster funziona al
|
|
// contrario
|
|
// 1 opaco -> 0 completamente trasparente
|
|
// inverto quindi il risultato della funzione stando attento al caso 0
|
|
// (in cui era scolpito il valore 0.9)
|
|
double onionSkiFade = player.m_onionSkinDistance == 0
|
|
? 0.9
|
|
: (1.0 - OnionSkinMask::getOnionSkinFade(
|
|
player.m_onionSkinDistance));
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
onionSkiFade *= 0.30;
|
|
alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255);
|
|
if (player.m_isShiftAndTraceEnabled &&
|
|
!Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts())
|
|
onionMode = Node::eOnionSkinNone;
|
|
else {
|
|
onionMode =
|
|
(player.m_onionSkinDistance > 0)
|
|
? Node::eOnionSkinFront
|
|
: ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack
|
|
: Node::eOnionSkinNone);
|
|
}
|
|
} else if (useOpacity < 255)
|
|
alpha = useOpacity;
|
|
TXshSimpleLevel *sl = player.m_sl;
|
|
bool doPremultiply = false;
|
|
bool whiteTransp = false;
|
|
if (sl) {
|
|
LevelProperties *levelProp = sl->getProperties();
|
|
if (levelProp->doPremultiply())
|
|
doPremultiply = true;
|
|
else if (levelProp->whiteTransp())
|
|
whiteTransp = true;
|
|
}
|
|
|
|
bool ignoreAlpha =
|
|
(Preferences::instance()->isIgnoreAlphaonColumn1Enabled() &&
|
|
player.m_column == 0 &&
|
|
isSubsheetChainOnColumn0(sl->getScene()->getTopXsheet(), player.m_xsh,
|
|
player.m_frame));
|
|
|
|
m_nodes.push_back(Node(r, 0, alpha, aff, ri->getSavebox(), bbox,
|
|
player.m_frame, player.m_isCurrentColumn, onionMode,
|
|
doPremultiply, whiteTransp, ignoreAlpha,
|
|
player.m_filterColor));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! Create a \b Node and put it in \b m_nodes.
|
|
*/
|
|
void RasterPainter::onToonzImage(TToonzImage *ti, const Stage::Player &player) {
|
|
TRasterCM32P r = ti->getRaster();
|
|
if (!ti->getPalette()) return;
|
|
|
|
TAffine aff = m_viewAff * player.m_placement * player.m_dpiAff;
|
|
aff = TTranslation(m_dim.lx / 2.0, m_dim.ly / 2.0) * aff *
|
|
TTranslation(-r->getCenterD());
|
|
|
|
TRectD bbox = TRectD(0, 0, m_dim.lx, m_dim.ly);
|
|
bbox *= convert(m_clipRect);
|
|
if (bbox.isEmpty()) return;
|
|
|
|
UCHAR useOpacity = player.m_opacity;
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
useOpacity *= 0.30;
|
|
|
|
int alpha = 255;
|
|
Node::OnionMode onionMode = Node::eOnionSkinNone;
|
|
if (player.m_onionSkinDistance != c_noOnionSkin) {
|
|
// GetOnionSkinFade is good for the vector while the raster works at the
|
|
// Opposite 1 opaque -> 0 completely transparent
|
|
// I therefore reverse the result of the function by being attentive to
|
|
// case 0
|
|
// (where the value 0.9 was carved)
|
|
double onionSkiFade = player.m_onionSkinDistance == 0
|
|
? 0.9
|
|
: (1.0 - OnionSkinMask::getOnionSkinFade(
|
|
player.m_onionSkinDistance));
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
onionSkiFade *= 0.30;
|
|
alpha = tcrop(tround(onionSkiFade * 255.0), 0, 255);
|
|
|
|
if (player.m_isShiftAndTraceEnabled &&
|
|
!Preferences::instance()->areOnionColorsUsedForShiftAndTraceGhosts())
|
|
onionMode = Node::eOnionSkinNone;
|
|
else {
|
|
onionMode =
|
|
(player.m_onionSkinDistance > 0)
|
|
? Node::eOnionSkinFront
|
|
: ((player.m_onionSkinDistance < 0) ? Node::eOnionSkinBack
|
|
: Node::eOnionSkinNone);
|
|
}
|
|
|
|
} else if (useOpacity < 255)
|
|
alpha = useOpacity;
|
|
|
|
m_nodes.push_back(Node(r, ti->getPalette(), alpha, aff, ti->getSavebox(),
|
|
bbox, player.m_frame, player.m_isCurrentColumn,
|
|
onionMode, false, false, false, player.m_filterColor));
|
|
}
|
|
|
|
//**********************************************************************************************
|
|
// OpenGLPainter implementation
|
|
//**********************************************************************************************
|
|
|
|
OpenGlPainter::OpenGlPainter(const TAffine &viewAff, const TRect &rect,
|
|
const ImagePainter::VisualSettings &vs,
|
|
bool isViewer, bool alphaEnabled)
|
|
: Visitor(vs)
|
|
, m_viewAff(viewAff)
|
|
, m_clipRect(rect)
|
|
, m_camera3d(false)
|
|
, m_phi(0)
|
|
, m_maskLevel(0)
|
|
, m_isViewer(isViewer)
|
|
, m_alphaEnabled(alphaEnabled)
|
|
, m_paletteHasChanged(false)
|
|
, m_minZ(0)
|
|
, m_singleColumnEnabled(false) {}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGlPainter::onImage(const Stage::Player &player) {
|
|
if (m_singleColumnEnabled && !player.m_isCurrentColumn && !player.m_isMask)
|
|
return;
|
|
|
|
if (player.m_z < m_minZ) m_minZ = player.m_z;
|
|
|
|
glPushAttrib(GL_ALL_ATTRIB_BITS);
|
|
glPushMatrix();
|
|
|
|
if (m_camera3d)
|
|
glTranslated(
|
|
0, 0,
|
|
player.m_z); // Ok, move object along z as specified in the player
|
|
|
|
// Attempt Plastic-deformed drawing
|
|
if (TStageObject *obj =
|
|
::plasticDeformedObj(player, m_vs.m_plasticVisualSettings))
|
|
::onPlasticDeformedImage(obj, player, m_vs, m_viewAff);
|
|
else if (const TImageP &image = player.image()) {
|
|
if (TVectorImageP vi = image)
|
|
onVectorImage(vi.getPointer(), player);
|
|
else if (TRasterImageP ri = image)
|
|
onRasterImage(ri.getPointer(), player);
|
|
else if (TToonzImageP ti = image)
|
|
onToonzImage(ti.getPointer(), player);
|
|
else if (TMeshImageP mi = image)
|
|
onMeshImage(mi.getPointer(), player, m_vs, m_viewAff);
|
|
}
|
|
|
|
glPopMatrix();
|
|
glPopAttrib();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGlPainter::onVectorImage(TVectorImage *vi,
|
|
const Stage::Player &player) {
|
|
if (m_camera3d && (player.m_onionSkinDistance == c_noOnionSkin ||
|
|
player.m_onionSkinDistance == 0)) {
|
|
const TRectD &bbox = player.m_placement * player.m_dpiAff * vi->getBBox();
|
|
draw3DShadow(bbox, player.m_z, m_phi);
|
|
}
|
|
|
|
TColorFunction *cf = 0;
|
|
TOnionFader fader;
|
|
|
|
TPalette *vPalette = vi->getPalette();
|
|
|
|
int oldFrame = vPalette->getFrame();
|
|
vPalette->setFrame(player.m_frame); // Hehe. Should be locking
|
|
// vPalette's mutex here...
|
|
if (player.m_onionSkinDistance != c_noOnionSkin) {
|
|
TPixel32 bgColor = TPixel32::White;
|
|
fader = TOnionFader(
|
|
bgColor, OnionSkinMask::getOnionSkinFade(player.m_onionSkinDistance));
|
|
cf = &fader;
|
|
}
|
|
|
|
TVectorRenderData rd =
|
|
isViewer() ? TVectorRenderData(TVectorRenderData::ViewerSettings(),
|
|
m_viewAff * player.m_placement, m_clipRect,
|
|
vPalette)
|
|
: TVectorRenderData(TVectorRenderData::ProductionSettings(),
|
|
m_viewAff * player.m_placement, m_clipRect,
|
|
vPalette);
|
|
|
|
rd.m_alphaChannel = m_alphaEnabled;
|
|
rd.m_is3dView = m_camera3d;
|
|
|
|
if (m_maskLevel > 0)
|
|
tglDrawMask(rd, vi);
|
|
else
|
|
tglDraw(rd, vi);
|
|
|
|
vPalette->setFrame(oldFrame);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGlPainter::onRasterImage(TRasterImage *ri,
|
|
const Stage::Player &player) {
|
|
if (m_camera3d && (player.m_onionSkinDistance == c_noOnionSkin ||
|
|
player.m_onionSkinDistance == 0)) {
|
|
TRectD bbox(ri->getBBox());
|
|
|
|
// Since bbox is in image coordinates, adjust to level coordinates first
|
|
bbox -= ri->getRaster()->getCenterD() - convert(ri->getOffset());
|
|
|
|
// Then, to world coordinates
|
|
bbox = player.m_placement * player.m_dpiAff * bbox;
|
|
|
|
draw3DShadow(bbox, player.m_z, m_phi);
|
|
}
|
|
|
|
bool tlvFlag = player.m_sl && player.m_sl->getType() == TZP_XSHLEVEL;
|
|
|
|
if (tlvFlag &&
|
|
m_paletteHasChanged) // o.o! Raster images here - should never be
|
|
assert(false); // dealing with tlv stuff!
|
|
|
|
bool premultiplied = tlvFlag; // xD
|
|
static std::vector<char>
|
|
matteChan; // Wtf this is criminal. Although probably this
|
|
// stuff is used only in the main thread... hmmm....
|
|
TRaster32P r = (TRaster32P)ri->getRaster();
|
|
r->lock();
|
|
|
|
if (c_noOnionSkin != player.m_onionSkinDistance) {
|
|
double fade =
|
|
0.5 - 0.45 * (1 - 1 / (1 + 0.15 * abs(player.m_onionSkinDistance)));
|
|
if ((int)matteChan.size() < r->getLx() * r->getLy())
|
|
matteChan.resize(r->getLx() * r->getLy());
|
|
|
|
int k = 0;
|
|
for (int y = 0; y < r->getLy(); ++y) {
|
|
TPixel32 *pix = r->pixels(y);
|
|
TPixel32 *endPix = pix + r->getLx();
|
|
|
|
while (pix < endPix) {
|
|
matteChan[k++] = pix->m;
|
|
pix->m = (int)(pix->m * fade);
|
|
pix++;
|
|
}
|
|
}
|
|
|
|
premultiplied = false; // pfff
|
|
}
|
|
|
|
TAffine aff = player.m_dpiAff;
|
|
|
|
glPushAttrib(GL_ALL_ATTRIB_BITS);
|
|
tglEnableBlending(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
GLRasterPainter::drawRaster(m_viewAff * player.m_placement * aff *
|
|
TTranslation(convert(ri->getOffset())),
|
|
ri, premultiplied);
|
|
|
|
glPopAttrib();
|
|
|
|
if (c_noOnionSkin != player.m_onionSkinDistance) {
|
|
int k = 0;
|
|
for (int y = 0; y < r->getLy(); ++y) {
|
|
TPixel32 *pix = r->pixels(y);
|
|
TPixel32 *endPix = pix + r->getLx();
|
|
|
|
while (pix < endPix) pix++->m = matteChan[k++];
|
|
}
|
|
}
|
|
|
|
r->unlock();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGlPainter::onToonzImage(TToonzImage *ti, const Stage::Player &player) {
|
|
if (m_camera3d && (player.m_onionSkinDistance == c_noOnionSkin ||
|
|
player.m_onionSkinDistance == 0)) {
|
|
TRectD bbox(ti->getBBox());
|
|
|
|
bbox -= ti->getRaster()->getCenterD();
|
|
bbox = player.m_placement * player.m_dpiAff * bbox;
|
|
|
|
draw3DShadow(bbox, player.m_z, m_phi);
|
|
}
|
|
|
|
TRasterCM32P ras = ti->getRaster();
|
|
TRaster32P ras32(ras->getSize());
|
|
ras32->fill(TPixel32(0, 0, 0, 0));
|
|
|
|
// onionSkin
|
|
TRop::quickPut(ras32, ras, ti->getPalette(), TAffine());
|
|
TAffine aff = player.m_dpiAff;
|
|
|
|
TRasterImageP ri(ras32);
|
|
|
|
GLRasterPainter::drawRaster(m_viewAff * player.m_placement * aff, ri, true);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void OpenGlPainter::beginMask() {
|
|
++m_maskLevel;
|
|
TStencilControl::instance()->beginMask();
|
|
}
|
|
void OpenGlPainter::endMask() {
|
|
--m_maskLevel;
|
|
TStencilControl::instance()->endMask();
|
|
}
|
|
void OpenGlPainter::enableMask(TStencilControl::MaskType maskType) {
|
|
TStencilControl::instance()->enableMask(maskType);
|
|
}
|
|
void OpenGlPainter::disableMask() {
|
|
TStencilControl::instance()->disableMask();
|
|
}
|
|
|
|
//**********************************************************************************************
|
|
// Plastic functions implementation
|
|
//**********************************************************************************************
|
|
|
|
namespace {
|
|
|
|
TStageObject *plasticDeformedObj(const Stage::Player &player,
|
|
const PlasticVisualSettings &pvs) {
|
|
struct locals {
|
|
static inline bool isDeformableMeshColumn(
|
|
TXshColumn *column, const PlasticVisualSettings &pvs) {
|
|
return (column->getColumnType() == TXshColumn::eMeshType) &&
|
|
(pvs.m_showOriginalColumn != column);
|
|
}
|
|
|
|
static inline bool isDeformableMeshLevel(TXshSimpleLevel *sl) {
|
|
return sl && (sl->getType() == MESH_XSHLEVEL);
|
|
}
|
|
}; // locals
|
|
|
|
if (pvs.m_applyPlasticDeformation && player.m_column >= 0) {
|
|
// Check whether the player's column is a direct stage-schematic child of a
|
|
// mesh object
|
|
TStageObject *playerObj =
|
|
player.m_xsh->getStageObject(TStageObjectId::ColumnId(player.m_column));
|
|
assert(playerObj);
|
|
|
|
const TStageObjectId &parentId = playerObj->getParent();
|
|
if (parentId.isColumn() && playerObj->getParentHandle()[0] != 'H') {
|
|
TXshColumn *parentCol = player.m_xsh->getColumn(parentId.getIndex());
|
|
|
|
if (locals::isDeformableMeshColumn(parentCol, pvs) &&
|
|
!locals::isDeformableMeshLevel(player.m_sl)) {
|
|
const SkDP &sd = player.m_xsh->getStageObject(parentId)
|
|
->getPlasticSkeletonDeformation();
|
|
|
|
const TXshCell &parentCell =
|
|
player.m_xsh->getCell(player.m_frame, parentId.getIndex());
|
|
|
|
if (parentCell.getFrameId().isStopFrame()) return 0;
|
|
|
|
TXshSimpleLevel *parentSl = parentCell.getSimpleLevel();
|
|
|
|
if (sd && locals::isDeformableMeshLevel(parentSl)) return playerObj;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void onMeshImage(TMeshImage *mi, const Stage::Player &player,
|
|
const ImagePainter::VisualSettings &vs,
|
|
const TAffine &viewAff) {
|
|
assert(mi);
|
|
|
|
static const double soMinColor[4] = {0.0, 0.0, 0.0,
|
|
0.6}; // Translucent black
|
|
static const double soMaxColor[4] = {1.0, 1.0, 1.0,
|
|
0.6}; // Translucent white
|
|
static const double rigMinColor[4] = {0.0, 1.0, 0.0,
|
|
0.6}; // Translucent green
|
|
static const double rigMaxColor[4] = {1.0, 0.0, 0.0, 0.6}; // Translucent red
|
|
|
|
bool doOnionSkin = (player.m_onionSkinDistance != c_noOnionSkin);
|
|
bool onionSkinImage = doOnionSkin && (player.m_onionSkinDistance != 0);
|
|
bool drawMeshes =
|
|
vs.m_plasticVisualSettings.m_drawMeshesWireframe && !onionSkinImage;
|
|
bool drawSO = vs.m_plasticVisualSettings.m_drawSO && !onionSkinImage;
|
|
bool drawRigidity =
|
|
vs.m_plasticVisualSettings.m_drawRigidity && !onionSkinImage;
|
|
|
|
// Currently skipping onion skinned meshes
|
|
if (onionSkinImage) return;
|
|
|
|
// Build dpi
|
|
TPointD meshSlDpi(player.m_sl->getDpi(player.m_fid, 0));
|
|
assert(meshSlDpi.x != 0.0 && meshSlDpi.y != 0.0);
|
|
|
|
// Build reference change affines
|
|
|
|
const TAffine &worldMeshToMeshAff =
|
|
TScale(meshSlDpi.x / Stage::inch, meshSlDpi.y / Stage::inch);
|
|
const TAffine &meshToWorldMeshAff =
|
|
TScale(Stage::inch / meshSlDpi.x, Stage::inch / meshSlDpi.y);
|
|
const TAffine &worldMeshToWorldAff = player.m_placement;
|
|
|
|
const TAffine &meshToWorldAff = worldMeshToWorldAff * meshToWorldMeshAff;
|
|
|
|
// Prepare OpenGL
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_LINE_SMOOTH);
|
|
|
|
// Push mesh coordinates
|
|
glPushMatrix();
|
|
tglMultMatrix(viewAff * meshToWorldAff);
|
|
|
|
// Fetch deformation
|
|
PlasticSkeletonDeformation *deformation = 0;
|
|
double sdFrame;
|
|
|
|
if (vs.m_plasticVisualSettings.m_applyPlasticDeformation &&
|
|
player.m_column >= 0) {
|
|
TXshColumn *column = player.m_xsh->getColumn(player.m_column);
|
|
|
|
if (column != vs.m_plasticVisualSettings.m_showOriginalColumn) {
|
|
TStageObject *playerObj = player.m_xsh->getStageObject(
|
|
TStageObjectId::ColumnId(player.m_column));
|
|
|
|
deformation = playerObj->getPlasticSkeletonDeformation().getPointer();
|
|
sdFrame = playerObj->paramsTime(player.m_frame);
|
|
}
|
|
}
|
|
|
|
UCHAR useOpacity = player.m_opacity;
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
useOpacity *= 0.30;
|
|
|
|
if (deformation) {
|
|
// Retrieve the associated plastic deformers data (this may eventually
|
|
// update the deforms)
|
|
const PlasticDeformerDataGroup *dataGroup =
|
|
PlasticDeformerStorage::instance()->process(
|
|
sdFrame, mi, deformation, deformation->skeletonId(sdFrame),
|
|
worldMeshToMeshAff);
|
|
|
|
// Draw faces first
|
|
if (drawSO)
|
|
tglDrawSO(*mi, (double *)soMinColor, (double *)soMaxColor, dataGroup,
|
|
true);
|
|
|
|
if (drawRigidity)
|
|
tglDrawRigidity(*mi, (double *)rigMinColor, (double *)rigMaxColor,
|
|
dataGroup, true);
|
|
|
|
// Draw edges next
|
|
if (drawMeshes) {
|
|
glColor4d(0.0, 1.0, 0.0, 0.7 * useOpacity / 255.0); // Green
|
|
tglDrawEdges(*mi, dataGroup); // The mesh must be deformed
|
|
}
|
|
} else {
|
|
// Draw un-deformed data
|
|
|
|
// Draw faces first
|
|
if (drawSO) tglDrawSO(*mi, (double *)soMinColor, (double *)soMaxColor);
|
|
|
|
if (drawRigidity)
|
|
tglDrawRigidity(*mi, (double *)rigMinColor, (double *)rigMaxColor);
|
|
|
|
// Just draw the mesh image next
|
|
if (drawMeshes) {
|
|
glColor4d(0.0, 1.0, 0.0, 0.7 * useOpacity / 255.0); // Green
|
|
tglDrawEdges(*mi);
|
|
}
|
|
}
|
|
|
|
// Cleanup OpenGL
|
|
glPopMatrix();
|
|
|
|
glDisable(GL_LINE_SMOOTH);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//! Applies Plastic deformation of the specified player's stage object.
|
|
void onPlasticDeformedImage(TStageObject *playerObj,
|
|
const Stage::Player &player,
|
|
const ImagePainter::VisualSettings &vs,
|
|
const TAffine &viewAff) {
|
|
bool doOnionSkin = (player.m_onionSkinDistance != c_noOnionSkin);
|
|
bool onionSkinImage = doOnionSkin && (player.m_onionSkinDistance != 0);
|
|
|
|
// Deal with color scaling due to transparency / onion skin
|
|
double pixScale[4] = {1.0, 1.0, 1.0, 1.0};
|
|
|
|
UCHAR useOpacity = player.m_opacity;
|
|
if (player.m_isLightTableEnabled && !player.m_isCurrentColumn)
|
|
useOpacity *= 0.30;
|
|
|
|
if (doOnionSkin) {
|
|
if (onionSkinImage) {
|
|
TPixel32 frontOnionColor, backOnionColor;
|
|
bool inksOnly;
|
|
|
|
Preferences::instance()->getOnionData(frontOnionColor, backOnionColor,
|
|
inksOnly);
|
|
|
|
const TPixel32 &refColor =
|
|
(player.m_onionSkinDistance < 0) ? backOnionColor : frontOnionColor;
|
|
|
|
pixScale[3] =
|
|
1.0 - OnionSkinMask::getOnionSkinFade(player.m_onionSkinDistance);
|
|
pixScale[0] =
|
|
(refColor.r / 255.0) * pixScale[3]; // refColor is not premultiplied
|
|
pixScale[1] = (refColor.g / 255.0) * pixScale[3];
|
|
pixScale[2] = (refColor.b / 255.0) * pixScale[3];
|
|
}
|
|
} else if (useOpacity < 255) {
|
|
pixScale[3] = useOpacity / 255.0;
|
|
pixScale[0] = pixScale[1] = pixScale[2] = 0.0;
|
|
}
|
|
|
|
// Build the Mesh-related data
|
|
const TXshCell &cell =
|
|
player.m_xsh->getCell(player.m_frame, playerObj->getParent().getIndex());
|
|
|
|
TXshSimpleLevel *meshLevel = cell.getSimpleLevel();
|
|
const TFrameId &meshFid = cell.getFrameId();
|
|
|
|
const TMeshImageP &mi = meshLevel->getFrame(meshFid, false);
|
|
if (!mi) return;
|
|
|
|
// Build deformation-related data
|
|
TStageObject *parentObj =
|
|
player.m_xsh->getStageObject(playerObj->getParent());
|
|
assert(playerObj);
|
|
|
|
const PlasticSkeletonDeformationP &deformation =
|
|
parentObj->getPlasticSkeletonDeformation().getPointer();
|
|
assert(deformation);
|
|
|
|
double sdFrame = parentObj->paramsTime(player.m_frame);
|
|
|
|
// Build dpis
|
|
|
|
TPointD meshSlDpi(meshLevel->getDpi(meshFid, 0));
|
|
assert(meshSlDpi.x != 0.0 && meshSlDpi.y != 0.0);
|
|
|
|
TPointD slDpi(player.m_sl ? player.m_sl->getDpi(player.m_fid, 0) : TPointD());
|
|
if (slDpi.x == 0.0 || slDpi.y == 0.0 ||
|
|
player.m_sl->getType() == PLI_XSHLEVEL)
|
|
slDpi.x = slDpi.y = Stage::inch;
|
|
|
|
// Build reference transforms
|
|
|
|
const TAffine &worldTextureToWorldMeshAff =
|
|
playerObj->getLocalPlacement(player.m_frame);
|
|
const TAffine &worldTextureToWorldAff = player.m_placement;
|
|
|
|
if (fabs(worldTextureToWorldMeshAff.det()) < 1e-6)
|
|
return; // Skip near-singular mesh/texture placements
|
|
|
|
const TAffine &worldMeshToWorldTextureAff = worldTextureToWorldMeshAff.inv();
|
|
const TAffine &worldMeshToWorldAff =
|
|
worldTextureToWorldAff * worldMeshToWorldTextureAff;
|
|
|
|
const TAffine &meshToWorldMeshAff =
|
|
TScale(Stage::inch / meshSlDpi.x, Stage::inch / meshSlDpi.y);
|
|
const TAffine &worldMeshToMeshAff =
|
|
TScale(meshSlDpi.x / Stage::inch, meshSlDpi.y / Stage::inch);
|
|
const TAffine &worldTextureToTextureAff = TScale(
|
|
slDpi.x / Stage::inch,
|
|
slDpi.y /
|
|
Stage::inch); // ::getDpiScale().inv() should be used instead...
|
|
|
|
const TAffine &meshToWorldAff = worldMeshToWorldAff * meshToWorldMeshAff;
|
|
const TAffine &meshToTextureAff = worldTextureToTextureAff *
|
|
worldMeshToWorldTextureAff *
|
|
meshToWorldMeshAff;
|
|
|
|
// Retrieve a drawable texture from the player's simple level
|
|
const DrawableTextureDataP &texData = player.texture();
|
|
if (!texData) return;
|
|
|
|
// Retrieve the associated plastic deformers data (this may eventually update
|
|
// the deforms)
|
|
const PlasticDeformerDataGroup *dataGroup =
|
|
PlasticDeformerStorage::instance()->process(
|
|
sdFrame, mi.getPointer(), deformation.getPointer(),
|
|
deformation->skeletonId(sdFrame), worldMeshToMeshAff);
|
|
assert(dataGroup);
|
|
|
|
// Set up OpenGL stuff
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_LINE_SMOOTH);
|
|
|
|
// Push mesh coordinates
|
|
glPushMatrix();
|
|
|
|
tglMultMatrix(viewAff * meshToWorldAff);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
// Applying modulation by the specified transparency parameter
|
|
|
|
glColor4d(pixScale[3], pixScale[3], pixScale[3], pixScale[3]);
|
|
tglDraw(*mi, *texData, meshToTextureAff, *dataGroup);
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
if (onionSkinImage) {
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
|
|
// Add Onion skin color. Observe that this way we don't consider blending
|
|
// with the texture's
|
|
// alpha - to obtain that, there is no simple way...
|
|
|
|
double k = (1.0 - pixScale[3]);
|
|
glColor4d(k * pixScale[0], k * pixScale[1], k * pixScale[2], 0.0);
|
|
tglDrawFaces(*mi, dataGroup);
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
glDisable(GL_LINE_SMOOTH);
|
|
glDisable(GL_BLEND);
|
|
assert(glGetError() == GL_NO_ERROR);
|
|
}
|
|
|
|
} // namespace
|