diff --git a/stuff/config/current.txt b/stuff/config/current.txt index a6cd128..e8ac97b 100644 --- a/stuff/config/current.txt +++ b/stuff/config/current.txt @@ -1272,6 +1272,20 @@ "STD_iwa_TextFx.boxColor" "Box Color" "STD_iwa_TextFx.showBorder" "Show Border" + "STD_iwa_CorridorGradientFx" "Corridor Gradient Iwa" + "STD_iwa_CorridorGradientFx.shape" "Shape" + "STD_iwa_CorridorGradientFx.curveType" "Type" + "STD_iwa_CorridorGradientFx.bottom_left_in" "Bottom Left In" + "STD_iwa_CorridorGradientFx.bottom_left_out" "Bottom Left Out" + "STD_iwa_CorridorGradientFx.bottom_right_in" "Bottom Right In" + "STD_iwa_CorridorGradientFx.bottom_right_out" "Bottom Right Out" + "STD_iwa_CorridorGradientFx.top_right_in" "Top Right In" + "STD_iwa_CorridorGradientFx.top_right_out" "Top Right Out" + "STD_iwa_CorridorGradientFx.top_left_in" "Top Left In" + "STD_iwa_CorridorGradientFx.top_left_out" "Top Left Out" + "STD_iwa_CorridorGradientFx.inner_color" "Inner Color" + "STD_iwa_CorridorGradientFx.outer_color" "Outer Color" + STD_iwa_TiledParticlesFx "Tiled Particles Iwa" diff --git a/stuff/profiles/layouts/fxs/STD_iwa_CorridorGradientFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_CorridorGradientFx.xml new file mode 100644 index 0000000..5258f74 --- /dev/null +++ b/stuff/profiles/layouts/fxs/STD_iwa_CorridorGradientFx.xml @@ -0,0 +1,18 @@ + + + shape + curveType + + bottom_left_in + bottom_right_in + top_left_in + top_right_in + inner_color + + bottom_left_out + bottom_right_out + top_left_out + top_right_out + outer_color + + diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst index b7c1e33..ca26b7c 100644 --- a/stuff/profiles/layouts/fxs/fxs.lst +++ b/stuff/profiles/layouts/fxs/fxs.lst @@ -44,6 +44,7 @@ STD_radialGradientFx STD_spiralFx STD_squareGradientFx + STD_iwa_CorridorGradientFx STD_iwa_AdjustExposureFx diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt index f0a0017..4aa2aaf 100644 --- a/toonz/sources/stdfx/CMakeLists.txt +++ b/toonz/sources/stdfx/CMakeLists.txt @@ -76,6 +76,7 @@ set(HEADERS iwa_timecodefx.h iwa_bokehreffx.h iwa_textfx.h + iwa_corridorgradientfx.h ) set(SOURCES @@ -258,6 +259,7 @@ set(SOURCES iwa_bokehreffx.cpp iwa_barreldistortfx.cpp iwa_textfx.cpp + iwa_corridorgradientfx.cpp ) set(OBJCSOURCES diff --git a/toonz/sources/stdfx/freedistortfx.cpp b/toonz/sources/stdfx/freedistortfx.cpp index 8e3dfab..45db345 100644 --- a/toonz/sources/stdfx/freedistortfx.cpp +++ b/toonz/sources/stdfx/freedistortfx.cpp @@ -19,7 +19,7 @@ namespace { inline bool myIsEmpty(const TRectD &r) { return r.x0 >= r.x1 || r.y0 >= r.y1; } -} +} // namespace //============================================================================== @@ -415,19 +415,21 @@ void FreeDistortBaseFx::safeTransform(double frame, int port, // ------------------------------------------------------------------------ void FreeDistortBaseFx::getParamUIs(TParamUIConcept *&concepts, int &length) { - concepts = new TParamUIConcept[length = 14]; + concepts = new TParamUIConcept[length = 6]; concepts[0].m_type = TParamUIConcept::QUAD; - concepts[0].m_params.push_back(m_p00_b); - concepts[0].m_params.push_back(m_p10_b); - concepts[0].m_params.push_back(m_p11_b); concepts[0].m_params.push_back(m_p01_b); + concepts[0].m_params.push_back(m_p11_b); + concepts[0].m_params.push_back(m_p10_b); + concepts[0].m_params.push_back(m_p00_b); + concepts[0].m_label = " Src"; concepts[1].m_type = TParamUIConcept::QUAD; - concepts[1].m_params.push_back(m_p00_a); - concepts[1].m_params.push_back(m_p10_a); - concepts[1].m_params.push_back(m_p11_a); concepts[1].m_params.push_back(m_p01_a); + concepts[1].m_params.push_back(m_p11_a); + concepts[1].m_params.push_back(m_p10_a); + concepts[1].m_params.push_back(m_p00_a); + concepts[1].m_label = " Dst"; concepts[2].m_type = TParamUIConcept::VECTOR; concepts[2].m_params.push_back(m_p00_b); @@ -444,38 +446,6 @@ void FreeDistortBaseFx::getParamUIs(TParamUIConcept *&concepts, int &length) { concepts[5].m_type = TParamUIConcept::VECTOR; concepts[5].m_params.push_back(m_p11_b); concepts[5].m_params.push_back(m_p11_a); - - concepts[6].m_type = TParamUIConcept::POINT; - concepts[6].m_label = "Bottom Left Src"; - concepts[6].m_params.push_back(m_p00_b); - - concepts[7].m_type = TParamUIConcept::POINT; - concepts[7].m_label = "Bottom Right Src"; - concepts[7].m_params.push_back(m_p10_b); - - concepts[8].m_type = TParamUIConcept::POINT; - concepts[8].m_label = "Top Left Src"; - concepts[8].m_params.push_back(m_p01_b); - - concepts[9].m_type = TParamUIConcept::POINT; - concepts[9].m_label = "Top Right Src"; - concepts[9].m_params.push_back(m_p11_b); - - concepts[10].m_type = TParamUIConcept::POINT; - concepts[10].m_label = "Bottom Left Dst"; - concepts[10].m_params.push_back(m_p00_a); - - concepts[11].m_type = TParamUIConcept::POINT; - concepts[11].m_label = "Bottom Right Dst"; - concepts[11].m_params.push_back(m_p10_a); - - concepts[12].m_type = TParamUIConcept::POINT; - concepts[12].m_label = "Top Left Dst"; - concepts[12].m_params.push_back(m_p01_a); - - concepts[13].m_type = TParamUIConcept::POINT; - concepts[13].m_label = "Top Right Dst"; - concepts[13].m_params.push_back(m_p11_a); } // ------------------------------------------------------------------------ @@ -672,7 +642,7 @@ void doBlur(TRasterPT &r, double blur0, double blur1, double transp0, r->unlock(); delete[] row; } -} +} // namespace // ------------------------------------------------------------------------ diff --git a/toonz/sources/stdfx/iwa_corridorgradientfx.cpp b/toonz/sources/stdfx/iwa_corridorgradientfx.cpp new file mode 100644 index 0000000..96e826c --- /dev/null +++ b/toonz/sources/stdfx/iwa_corridorgradientfx.cpp @@ -0,0 +1,378 @@ +#include "iwa_corridorgradientfx.h" + +#include "trop.h" +#include "tparamuiconcept.h" +#include "tspectrumparam.h" +#include "gradients.h" + +#include + +#include +#include + +//------------------------------------------------------------ + +Iwa_CorridorGradientFx::Iwa_CorridorGradientFx() + : m_shape(new TIntEnumParam(0, "Quadrangle")) + , m_innerColor(TPixel32::White) + , m_outerColor(TPixel32::Black) + , m_curveType(new TIntEnumParam()) { + for (int inout = 0; inout < 2; inout++) { + double size = (inout == 0) ? 50. : 400.; + std::string inout_str = (inout == 0) ? "_in" : "_out"; + + for (int c = 0; c < 4; c++) { + Qt::Corner corner = (Qt::Corner)c; + TPointD basePos(1, 1); + if (corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner) + basePos.x *= -1; + if (corner == Qt::BottomLeftCorner || corner == Qt::BottomRightCorner) + basePos.y *= -1; + + m_points[inout][corner] = basePos * size; + + m_points[inout][corner]->getX()->setMeasureName("fxLength"); + m_points[inout][corner]->getY()->setMeasureName("fxLength"); + + std::string TB_str = + (corner == Qt::TopLeftCorner || corner == Qt::TopRightCorner) + ? "top" + : "bottom"; + std::string LR_str = + (corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner) + ? "_left" + : "_right"; + + bindParam(this, TB_str + LR_str + inout_str, m_points[inout][corner]); + } + } + + m_shape->addItem(1, "Circle"); + bindParam(this, "shape", m_shape); + + m_curveType->addItem(EaseInOut, "Ease In-Out"); + m_curveType->addItem(Linear, "Linear"); + m_curveType->addItem(EaseIn, "Ease In"); + m_curveType->addItem(EaseOut, "Ease Out"); + m_curveType->setDefaultValue(Linear); + m_curveType->setValue(Linear); + bindParam(this, "curveType", m_curveType); + + bindParam(this, "inner_color", m_innerColor); + bindParam(this, "outer_color", m_outerColor); +} + +//------------------------------------------------------------ + +bool Iwa_CorridorGradientFx::doGetBBox(double frame, TRectD &bBox, + const TRenderSettings &ri) { + bBox = TConsts::infiniteRectD; + return true; +} + +//------------------------------------------------------------ + +namespace { + +QPointF toQPointF(const TPointD &p) { return QPointF(p.x, p.y); } + +double WedgeProduct(const TPointD v, const TPointD w) { + return v.x * w.y - v.y * w.x; +} + +struct BilinearParam { + TPointD p0, b1, b2, b3; +}; + +//------------------------------------------------------------ + +double getFactor(const TPointD &p, const BilinearParam ¶m, + const GradientCurveType type) { + double t; + TPointD q = p - param.p0; + // Set up quadratic formula + float A = WedgeProduct(param.b2, param.b3); + float B = WedgeProduct(param.b3, q) - WedgeProduct(param.b1, param.b2); + float C = WedgeProduct(param.b1, q); + + // Solve for v + if (std::abs(A) < 0.001) { + // Linear form + t = -C / B; + } else { + // Quadratic form + float discrim = B * B - 4 * A * C; + t = 0.5 * (-B - std::sqrt(discrim)) / A; + } + double factor; + switch (type) { + case Linear: + factor = t; + break; + case EaseIn: + factor = t * t; + break; + case EaseOut: + factor = 1.0 - (1.0 - t) * (1.0 - t); + break; + case EaseInOut: + default: + factor = (-2 * t + 3) * (t * t); + break; + } + return factor; +} + +//------------------------------------------------------------ + +template +void doQuadrangleT(RASTER ras, TDimensionI dim, TPointD pos[2][4], + const TSpectrumT &spectrum, GradientCurveType type) { + auto buildPolygon = [&](QPolygonF &pol, Qt::Corner c1, Qt::Corner c2) { + pol << toQPointF(pos[0][(int)c1]) << toQPointF(pos[1][(int)c1]) + << toQPointF(pos[1][(int)c2]) << toQPointF(pos[0][(int)c2]); + }; + + auto buildBilinearParam = [&](BilinearParam &bp, Qt::Corner c1, + Qt::Corner c2) { + bp.p0 = pos[0][(int)c1]; + bp.b1 = pos[0][(int)c2] - pos[0][(int)c1]; + bp.b2 = pos[1][(int)c1] - pos[0][(int)c1]; + bp.b3 = + pos[0][(int)c1] - pos[0][(int)c2] - pos[1][(int)c1] + pos[1][(int)c2]; + }; + + std::array polygons; + std::array params; + + // Top + buildPolygon(polygons[0], Qt::TopLeftCorner, Qt::TopRightCorner); + buildBilinearParam(params[0], Qt::TopLeftCorner, Qt::TopRightCorner); + // Left + buildPolygon(polygons[1], Qt::BottomLeftCorner, Qt::TopLeftCorner); + buildBilinearParam(params[1], Qt::BottomLeftCorner, Qt::TopLeftCorner); + // Bottom + buildPolygon(polygons[2], Qt::BottomRightCorner, Qt::BottomLeftCorner); + buildBilinearParam(params[2], Qt::BottomRightCorner, Qt::BottomLeftCorner); + // Right + buildPolygon(polygons[3], Qt::TopRightCorner, Qt::BottomRightCorner); + buildBilinearParam(params[3], Qt::TopRightCorner, Qt::BottomRightCorner); + + QPolygonF innerPolygon; + innerPolygon << toQPointF(pos[0][Qt::TopLeftCorner]) + << toQPointF(pos[0][Qt::TopRightCorner]) + << toQPointF(pos[0][Qt::BottomRightCorner]) + << toQPointF(pos[0][Qt::BottomLeftCorner]); + + ras->lock(); + for (int j = 0; j < ras->getLy(); j++) { + PIXEL *pix = ras->pixels(j); + PIXEL *endPix = pix + ras->getLx(); + int i = 0; + while (pix < endPix) { + QPointF p(i, j); + + double factor; + bool found = false; + for (int edge = 0; edge < 4; edge++) { + if (polygons[edge].containsPoint(p, Qt::WindingFill)) { + factor = getFactor(TPointD(i, j), params.at(edge), type); + found = true; + break; + } + } + if (!found) { + if (innerPolygon.containsPoint(p, Qt::WindingFill)) + factor = 0.0; + else + factor = 1.0; + } + + *pix++ = spectrum.getPremultipliedValue(factor); + i++; + } + } + ras->unlock(); +} + +//------------------------------------------------------------ + +template +void doCircleT(RASTER ras, TDimensionI dim, TPointD pos[2][4], + const TSpectrumT &spectrum, GradientCurveType type) { + auto lerp = [](TPointD p1, TPointD p2, double f) { + return p1 * (1 - f) + p2 * f; + }; + auto bilinearPos = [&](TPointD uv, int inout) { + return lerp(lerp(pos[inout][Qt::BottomLeftCorner], + pos[inout][Qt::BottomRightCorner], uv.x), + lerp(pos[inout][Qt::TopLeftCorner], + pos[inout][Qt::TopRightCorner], uv.x), + uv.y); + }; + + const int DIVNUM = 36; + + std::array innerPos; + std::array outerPos; + double tmpRadius = std::sqrt(2.0) / 2.0; + for (int div = 0; div < DIVNUM; div++) { + double angle = 2.0 * M_PI * (double)div / (double)DIVNUM; + // circle position in uv coordinate + TPointD uv(tmpRadius * std::cos(angle) + 0.5, + tmpRadius * std::sin(angle) + 0.5); + // compute inner and outer circle positions by bilinear interpolation + // using uv coordinate values. + innerPos[div] = bilinearPos(uv, 0); + outerPos[div] = bilinearPos(uv, 1); + } + + // - - - - - - - - + + auto buildPolygon = [&](QPolygonF &pol, int id1, int id2) { + pol << toQPointF(innerPos[id2]) << toQPointF(outerPos[id2]) + << toQPointF(outerPos[id1]) << toQPointF(innerPos[id1]); + }; + + auto buildBilinearParam = [&](BilinearParam &bp, int id1, int id2) { + bp.p0 = innerPos[id2]; + bp.b1 = innerPos[id1] - innerPos[id2]; + bp.b2 = outerPos[id2] - innerPos[id2]; + bp.b3 = innerPos[id2] - innerPos[id1] - outerPos[id2] + outerPos[id1]; + }; + std::array polygons; + std::array params; + QPolygonF innerPolygon; + for (int div = 0; div < DIVNUM; div++) { + int next_div = (div == DIVNUM - 1) ? 0 : div + 1; + // create polygon and bilinear parameters for each piece surrounding the + // circle + buildPolygon(polygons[div], div, next_div); + buildBilinearParam(params[div], div, next_div); + // create inner circle polygon + innerPolygon << toQPointF(innerPos[div]); + } + + // - - - ok, ready to render + + ras->lock(); + + for (int j = 0; j < ras->getLy(); j++) { + PIXEL *pix = ras->pixels(j); + PIXEL *endPix = pix + ras->getLx(); + int i = 0; + while (pix < endPix) { + QPointF p(i, j); + double factor; + bool found = false; + for (int div = 0; div < DIVNUM; div++) { + // check if the point is inside of the surrounding pieces + if (polygons[div].containsPoint(p, Qt::WindingFill)) { + // compute factor by invert bilinear interpolation + factor = getFactor(TPointD(i, j), params.at(div), type); + found = true; + break; + } + } + if (!found) { + if (innerPolygon.containsPoint(p, Qt::WindingFill)) + factor = 0.0; + else + factor = 1.0; + } + + *pix++ = spectrum.getPremultipliedValue(factor); + + i++; + } + } + ras->unlock(); +} + +}; // namespace + +//------------------------------------------------------------ + +void Iwa_CorridorGradientFx::doCompute(TTile &tile, double frame, + const TRenderSettings &ri) { + if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster())) { + throw TRopException("unsupported input pixel type"); + } + + // convert shape position to render region coordinate + TPointD pos[2][4]; + TAffine aff = ri.m_affine; + TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy()); + TPointD dimOffset((float)dimOut.lx / 2.0f, (float)dimOut.ly / 2.0f); + for (int inout = 0; inout < 2; inout++) { + for (int c = 0; c < 4; c++) { + TPointD _point = m_points[inout][c]->getValue(frame); + pos[inout][c] = aff * _point - + (tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset; + } + } + + std::vector colors = { + TSpectrum::ColorKey(0, m_innerColor->getValue(frame)), + TSpectrum::ColorKey(1, m_outerColor->getValue(frame))}; + TSpectrumParamP m_colors = TSpectrumParamP(colors); + + tile.getRaster()->clear(); + TRaster32P outRas32 = (TRaster32P)tile.getRaster(); + TRaster64P outRas64 = (TRaster64P)tile.getRaster(); + if (m_shape->getValue() == 0) { // Quadrangle + if (outRas32) + doQuadrangleT( + outRas32, dimOut, pos, m_colors->getValue(frame), + (GradientCurveType)m_curveType->getValue()); + else if (outRas64) + doQuadrangleT( + outRas64, dimOut, pos, m_colors->getValue64(frame), + (GradientCurveType)m_curveType->getValue()); + } else { // m_shape == 1 : Circle + if (outRas32) + doCircleT( + outRas32, dimOut, pos, m_colors->getValue(frame), + (GradientCurveType)m_curveType->getValue()); + else if (outRas64) + doCircleT( + outRas64, dimOut, pos, m_colors->getValue64(frame), + (GradientCurveType)m_curveType->getValue()); + } +} + +//------------------------------------------------------------ + +void Iwa_CorridorGradientFx::getParamUIs(TParamUIConcept *&concepts, + int &length) { + concepts = new TParamUIConcept[length = 6]; + + int vectorUiIdOffset = 2; + + std::array loopIds{Qt::TopLeftCorner, Qt::TopRightCorner, + Qt::BottomRightCorner, + Qt::BottomLeftCorner}; + + for (int inout = 0; inout < 2; inout++) { + concepts[inout].m_type = TParamUIConcept::QUAD; + + for (int c = 0; c < 4; c++) { + Qt::Corner corner = loopIds[c]; + + // quad ui + concepts[inout].m_params.push_back(m_points[inout][(int)corner]); + concepts[inout].m_label = (inout == 0) ? " In" : " Out"; + + // vector ui + if (inout == 0) + concepts[vectorUiIdOffset + (int)corner].m_type = + TParamUIConcept::VECTOR; + concepts[vectorUiIdOffset + (int)corner].m_params.push_back( + m_points[inout][(int)corner]); + } + } +} + +//------------------------------------------------------------ + +FX_PLUGIN_IDENTIFIER(Iwa_CorridorGradientFx, "iwa_CorridorGradientFx"); diff --git a/toonz/sources/stdfx/iwa_corridorgradientfx.h b/toonz/sources/stdfx/iwa_corridorgradientfx.h new file mode 100644 index 0000000..c16ecc3 --- /dev/null +++ b/toonz/sources/stdfx/iwa_corridorgradientfx.h @@ -0,0 +1,31 @@ +#pragma once +#ifndef IWA_CORRIDORGRADIENTFX_H +#define IWA_CORRIDORGRADIENTFX_H + +#include "tfxparam.h" +#include "stdfx.h" +#include "tparamset.h" + +class Iwa_CorridorGradientFx final : public TStandardZeraryFx { + FX_PLUGIN_DECLARATION(Iwa_CorridorGradientFx) + + TIntEnumParamP m_shape; + TIntEnumParamP m_curveType; + TPointParamP m_points[2][4]; // [in/out][Qt::Corner] + + TPixelParamP m_innerColor; + TPixelParamP m_outerColor; + +public: + Iwa_CorridorGradientFx(); + + bool canHandle(const TRenderSettings &info, double frame) override { + return true; + } + bool doGetBBox(double frame, TRectD &bBox, + const TRenderSettings &ri) override; + void doCompute(TTile &tile, double frame, const TRenderSettings &ri) override; + void getParamUIs(TParamUIConcept *&concepts, int &length) override; +}; + +#endif \ No newline at end of file diff --git a/toonz/sources/tnztools/edittoolgadgets.cpp b/toonz/sources/tnztools/edittoolgadgets.cpp index 3fb6f5d..c3c30ee 100644 --- a/toonz/sources/tnztools/edittoolgadgets.cpp +++ b/toonz/sources/tnztools/edittoolgadgets.cpp @@ -30,8 +30,13 @@ int getDevPixRatio() { static int devPixRatio = QApplication::desktop()->devicePixelRatio(); return devPixRatio; } + +TPointD hadamard(const TPointD &v1, const TPointD &v2) { + return TPointD(v1.x * v2.x, v1.y * v2.y); } +} // namespace + //************************************************************************************* // FxGadgetUndo definition //************************************************************************************* @@ -131,13 +136,14 @@ public: // FxGadget implementation //************************************************************************************* -FxGadget::FxGadget(FxGadgetController *controller) +FxGadget::FxGadget(FxGadgetController *controller, int handleCount) : m_id(-1) - , m_selected(false) + , m_selected(-1) , m_controller(controller) , m_pixelSize(1) , m_undo(0) - , m_scaleFactor(1) { + , m_scaleFactor(1) + , m_handleCount(handleCount) { controller->assignId(this); } @@ -592,7 +598,7 @@ void DiamondFxGadget::draw(bool picking) { //--------------------------------------------------------------------------- void DiamondFxGadget::leftButtonDrag(const TPointD &pos, const TMouseEvent &) { - double sz = fabs(pos.x) + fabs(pos.y); + double sz = fabs(pos.x) + fabs(pos.y); if (sz < 0.1) sz = 0.1; setValue(m_param, sz); } @@ -841,12 +847,11 @@ public: class VectorFxGadget final : public FxGadget { TPointParamP m_pa, m_pb; - int m_selected; public: VectorFxGadget(FxGadgetController *controller, const TPointParamP &pa, const TPointParamP &pb) - : FxGadget(controller), m_pa(pa), m_pb(pb), m_selected(0) { + : FxGadget(controller), m_pa(pa), m_pb(pb) { addParam(pa->getX()); addParam(pa->getY()); addParam(pb->getX()); @@ -859,7 +864,7 @@ public: glColor3dv(m_selectedColor); else glColor3d(0, 0, 1); - glPushName(getId()); + // glPushName(getId()); double pixelSize = getPixelSize(); TPointD pa = getValue(m_pa); TPointD pb = getValue(m_pb); @@ -882,11 +887,11 @@ public: } tglDrawSegment(pbb, pbb - u * a + v * b); tglDrawSegment(pbb, pbb - u * a - v * b); - drawDot(pa); - drawDot(pb); - } else - drawDot(pa); - glPopName(); + // drawDot(pa); + // drawDot(pb); + } // else + // drawDot(pa); + // glPopName(); } void leftButtonDown(const TPointD &pos, const TMouseEvent &) override {} @@ -897,53 +902,269 @@ public: //============================================================================= class QuadFxGadget final : public FxGadget { - TPointParamP m_pa, m_pb, m_pc, m_pd; + TPointParamP m_TL, m_TR, m_BR, m_BL; + + enum HANDLE { + Body = 0, + TopLeft, + TopRight, + BottomRight, + BottomLeft, + TopEdge, + RightEdge, + BottomEdge, + LeftEdge, + None + } m_handle = None; + + TPointD m_pivot; + TPointD m_dragStartPos; + TPointD m_startTL, m_startTR, m_startBR, m_startBL; public: - QuadFxGadget(FxGadgetController *controller, const TPointParamP &pa, - const TPointParamP &pb, const TPointParamP &pc, - const TPointParamP &pd) - : FxGadget(controller), m_pa(pa), m_pb(pb), m_pc(pc), m_pd(pd) { - addParam(pa->getX()); - addParam(pa->getY()); - addParam(pb->getX()); - addParam(pb->getY()); - addParam(pc->getX()); - addParam(pc->getY()); - addParam(pd->getX()); - addParam(pd->getY()); + QuadFxGadget(FxGadgetController *controller, const TPointParamP &topLeft, + const TPointParamP &topRight, const TPointParamP &bottomRight, + const TPointParamP &bottomLeft) + : FxGadget(controller, 9) + , m_TL(topLeft) + , m_TR(topRight) + , m_BR(bottomRight) + , m_BL(bottomLeft) { + addParam(topLeft->getX()); + addParam(topLeft->getY()); + addParam(topRight->getX()); + addParam(topRight->getY()); + addParam(bottomRight->getX()); + addParam(bottomRight->getY()); + addParam(bottomLeft->getX()); + addParam(bottomLeft->getY()); } void draw(bool picking) override { + int idBase = getId(); + + auto setColorById = [&](int id) { + if (isSelected(id)) + glColor3dv(m_selectedColor); + else + glColor3d(0, 0, 1); + }; + + auto id2Str = [](const HANDLE handleId) -> std::string { + switch (handleId) { + case TopLeft: + return "Top Left"; + case TopRight: + return "Top Right"; + case BottomRight: + return "Bottom Right"; + case BottomLeft: + return "Bottom Left"; + default: + return ""; + } + }; + + auto drawPoint = [&](const TPointD &pos, int id) { + setColorById(id); + glPushName(idBase + id); + double unit = getPixelSize(); + glPushMatrix(); + glTranslated(pos.x, pos.y, 0); + double r = unit * 3; + tglDrawRect(-r, -r, r, r); + glPopMatrix(); + glPopName(); + + if (isSelected(id) && id >= TopLeft && id <= BottomLeft) { + drawTooltip(pos + TPointD(7, 3) * unit, + id2Str((HANDLE)id) + getLabel()); + } + }; + setPixelSize(); - if (isSelected()) - glColor3dv(m_selectedColor); - else - glColor3d(0, 0, 1); - // glPushName(getId()); - double pixelSize = getPixelSize(); - TPointD pa = getValue(m_pa); - TPointD pb = getValue(m_pb); - TPointD pc = getValue(m_pc); - TPointD pd = getValue(m_pd); + + // lines for moving all vertices + glPushName(idBase + Body); + setColorById(Body); + double pixelSize = getPixelSize(); + TPointD topLeft = getValue(m_TL); + TPointD topRight = getValue(m_TR); + TPointD bottomRight = getValue(m_BR); + TPointD bottomLeft = getValue(m_BL); glLineStipple(1, 0xCCCC); glEnable(GL_LINE_STIPPLE); glBegin(GL_LINE_STRIP); - tglVertex(pa); - tglVertex(pb); - tglVertex(pc); - tglVertex(pd); - tglVertex(pa); + tglVertex(topLeft); + tglVertex(topRight); + tglVertex(bottomRight); + tglVertex(bottomLeft); + tglVertex(topLeft); glEnd(); glDisable(GL_LINE_STIPPLE); - // glPopName(); + glPopName(); + + // corners + drawPoint(topLeft, TopLeft); + drawPoint(topRight, TopRight); + drawPoint(bottomRight, BottomRight); + drawPoint(bottomLeft, BottomLeft); + + // center of the edges + drawPoint((topLeft + topRight) * 0.5, TopEdge); + drawPoint((topRight + bottomRight) * 0.5, RightEdge); + drawPoint((bottomRight + bottomLeft) * 0.5, BottomEdge); + drawPoint((bottomLeft + topLeft) * 0.5, LeftEdge); } - void leftButtonDown(const TPointD &pos, const TMouseEvent &) override {} - void leftButtonDrag(const TPointD &pos, const TMouseEvent &) override {} - void leftButtonUp(const TPointD &pos, const TMouseEvent &) override {} + void leftButtonDown(const TPointD &pos, const TMouseEvent &) override; + void leftButtonDrag(const TPointD &pos, const TMouseEvent &) override; + void leftButtonUp(const TPointD &pos, const TMouseEvent &) override; }; +//--------------------------------------------------------------------------- + +void QuadFxGadget::leftButtonDown(const TPointD &pos, const TMouseEvent &) { + m_handle = (HANDLE)m_selected; + m_dragStartPos = pos; + m_startTL = getValue(m_TL); + m_startTR = getValue(m_TR); + m_startBR = getValue(m_BR); + m_startBL = getValue(m_BL); + m_pivot = (m_startTL + m_startTR + m_startBR + m_startBL) * 0.25; +} + +//--------------------------------------------------------------------------- + +void QuadFxGadget::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { + TPointD offset = pos - m_dragStartPos; + + auto scaleShape = [&](const TPointD &start, const TPointD &pivot) { + TPointD startVec = start - pivot; + TPointD endVec = start + offset - pivot; + TPointD scaleFac((startVec.x == 0.0) ? 1.0 : endVec.x / startVec.x, + (startVec.y == 0.0) ? 1.0 : endVec.y / startVec.y); + if (e.isShiftPressed()) { + if (std::abs(scaleFac.x) > std::abs(scaleFac.y)) + scaleFac.y = scaleFac.x; + else + scaleFac.x = scaleFac.y; + } + if (m_startTL != pivot) + setValue(m_TL, pivot + hadamard((m_startTL - pivot), scaleFac)); + if (m_startTR != pivot) + setValue(m_TR, pivot + hadamard((m_startTR - pivot), scaleFac)); + if (m_startBR != pivot) + setValue(m_BR, pivot + hadamard((m_startBR - pivot), scaleFac)); + if (m_startBL != pivot) + setValue(m_BL, pivot + hadamard((m_startBL - pivot), scaleFac)); + }; + + auto doCorner = [&](const TPointParamP point, const TPointD &start, + const TPointD &opposite) { + if (e.isCtrlPressed()) + setValue(point, start + offset); + else if (e.isAltPressed()) + scaleShape(start, m_pivot); + else + scaleShape(start, opposite); + }; + + auto doEdge = [&](const TPointParamP p1, const TPointParamP p2) { + if (e.isShiftPressed()) { + if (std::abs(offset.x) > std::abs(offset.y)) + offset.y = 0; + else + offset.x = 0; + } + if (m_TL == p1 || m_TL == p2) + setValue(m_TL, m_startTL + offset); + else if (e.isAltPressed()) + setValue(m_TL, m_startTL - offset); + if (m_TR == p1 || m_TR == p2) + setValue(m_TR, m_startTR + offset); + else if (e.isAltPressed()) + setValue(m_TR, m_startTR - offset); + if (m_BR == p1 || m_BR == p2) + setValue(m_BR, m_startBR + offset); + else if (e.isAltPressed()) + setValue(m_BR, m_startBR - offset); + if (m_BL == p1 || m_BL == p2) + setValue(m_BL, m_startBL + offset); + else if (e.isAltPressed()) + setValue(m_BL, m_startBL - offset); + }; + + auto pointRotate = [&](const TPointD pos, const double angle) { + TPointD p = pos - m_pivot; + return m_pivot + TPointD(p.x * std::cos(angle) - p.y * std::sin(angle), + p.x * std::sin(angle) + p.y * std::cos(angle)); + }; + + switch (m_handle) { + case Body: + if (e.isCtrlPressed()) { // rotate + TPointD startVec = m_dragStartPos - m_pivot; + TPointD currentVec = pos - m_pivot; + if (currentVec == TPointD()) return; + double angle = std::atan2(currentVec.y, currentVec.x) - + std::atan2(startVec.y, startVec.x); + if (e.isShiftPressed()) { + angle = std::round(angle / (M_PI / 2.0)) * (M_PI / 2.0); + } + setValue(m_TL, pointRotate(m_startTL, angle)); + setValue(m_TR, pointRotate(m_startTR, angle)); + setValue(m_BR, pointRotate(m_startBR, angle)); + setValue(m_BL, pointRotate(m_startBL, angle)); + } else { // translate + // move all shapes + if (e.isShiftPressed()) { + if (std::abs(offset.x) > std::abs(offset.y)) + offset.y = 0; + else + offset.x = 0; + } + setValue(m_TL, m_startTL + offset); + setValue(m_TR, m_startTR + offset); + setValue(m_BR, m_startBR + offset); + setValue(m_BL, m_startBL + offset); + } + break; + case TopLeft: + doCorner(m_TL, m_startTL, m_startBR); + break; + case TopRight: + doCorner(m_TR, m_startTR, m_startBL); + break; + case BottomRight: + doCorner(m_BR, m_startBR, m_startTL); + break; + case BottomLeft: + doCorner(m_BL, m_startBL, m_startTR); + break; + case TopEdge: + doEdge(m_TL, m_TR); + break; + case RightEdge: + doEdge(m_TR, m_BR); + break; + case BottomEdge: + doEdge(m_BR, m_BL); + break; + case LeftEdge: + doEdge(m_BL, m_TL); + break; + default: + break; + } +} + +//--------------------------------------------------------------------------- + +void QuadFxGadget::leftButtonUp(const TPointD &pos, const TMouseEvent &) { + m_handle = None; +} + //************************************************************************************* // FxGadgetController implementation //************************************************************************************* @@ -981,8 +1202,10 @@ void FxGadgetController::clearGadgets() { void FxGadgetController::assignId(FxGadget *gadget) { gadget->setId(m_nextId); - m_idTable[m_nextId] = gadget; - ++m_nextId; + for (int g = 0; g < gadget->getHandleCount(); g++) { + m_idTable[m_nextId] = gadget; + ++m_nextId; + } } //--------------------------------------------------------------------------- @@ -1009,9 +1232,10 @@ void FxGadgetController::selectById(unsigned int id) { it = m_idTable.find(id); FxGadget *selectedGadget = it != m_idTable.end() ? it->second : 0; if (selectedGadget != m_selectedGadget) { - if (m_selectedGadget) m_selectedGadget->select(false); + if (m_selectedGadget) m_selectedGadget->select(-1); m_selectedGadget = selectedGadget; - if (m_selectedGadget) m_selectedGadget->select(true); + if (m_selectedGadget) + m_selectedGadget->select(id - m_selectedGadget->getId()); } } @@ -1144,7 +1368,7 @@ void FxGadgetController::onFxSwitched() { // before, the levels were considered as nonZeraryFx and the edit tool // gadget was not displayed! Vinz { - if (zfx) fx = zfx->getZeraryFx(); + if (zfx) fx = zfx->getZeraryFx(); m_editingNonZeraryFx = false; } diff --git a/toonz/sources/tnztools/edittoolgadgets.h b/toonz/sources/tnztools/edittoolgadgets.h index 27e6e12..a26141f 100644 --- a/toonz/sources/tnztools/edittoolgadgets.h +++ b/toonz/sources/tnztools/edittoolgadgets.h @@ -32,7 +32,6 @@ class TParamUIConcept; class FxGadget : public TParamObserver { GLuint m_id; - bool m_selected; std::vector m_params; double m_pixelSize; @@ -42,11 +41,14 @@ class FxGadget : public TParamObserver { protected: FxGadgetController *m_controller; + int m_handleCount = 1; + // -1 : not selected, 0~ : handle id + int m_selected; public: static GLdouble m_selectedColor[3]; // rgb - FxGadget(FxGadgetController *controller); + FxGadget(FxGadgetController *controller, int handleCount = 1); virtual ~FxGadget(); void setLabel(std::string label) { m_label = label; } @@ -64,8 +66,11 @@ public: void setId(GLuint id) { m_id = id; } GLuint getId() const { return m_id; } - void select(bool selected) { m_selected = selected; } - bool isSelected() const { return m_selected; } + int getHandleCount() { return m_handleCount; } + + void select(int selected) { m_selected = selected; } + bool isSelected() const { return m_selected >= 0; } + bool isSelected(int id) const { return m_selected == id; } void setPixelSize(); // uses tglGetPixelSize2() void setPixelSize(double pixelSize) { m_pixelSize = pixelSize; } @@ -108,7 +113,7 @@ public: //----------------------------------------------------------------------------- -} // EditToolGadgets namespace +} // namespace EditToolGadgets class FxGadgetController final : public QObject { Q_OBJECT