diff --git a/stuff/config/current.txt b/stuff/config/current.txt index 1bf1c92..8c0cb2d 100644 --- a/stuff/config/current.txt +++ b/stuff/config/current.txt @@ -187,6 +187,7 @@ "STD_colorRaylitFx.intensity" "Intensity" "STD_colorRaylitFx.decay" "Decay" "STD_colorRaylitFx.smoothness" "Smoothness" + "STD_colorRaylitFx.radius" "Radius" "STD_colorRaylitFx.includeInput" "Keep Image" "STD_erodeDilateFx" "Erode/Dilate" @@ -370,6 +371,7 @@ "STD_raylitFx.intensity" "Intensity" "STD_raylitFx.decay" "Decay" "STD_raylitFx.smoothness" "Smoothness" + "STD_raylitFx.radius" "Radius" "STD_raylitFx.includeInput" "Keep Image" "STD_raylitFx.invert" "Invert" diff --git a/stuff/profiles/layouts/fxs/STD_colorRaylitFx.xml b/stuff/profiles/layouts/fxs/STD_colorRaylitFx.xml index b103cff..7f842bf 100644 --- a/stuff/profiles/layouts/fxs/STD_colorRaylitFx.xml +++ b/stuff/profiles/layouts/fxs/STD_colorRaylitFx.xml @@ -5,6 +5,7 @@ intensity decay smoothness + radius includeInput diff --git a/stuff/profiles/layouts/fxs/STD_raylitFx.xml b/stuff/profiles/layouts/fxs/STD_raylitFx.xml index 307e02f..76fbcad 100644 --- a/stuff/profiles/layouts/fxs/STD_raylitFx.xml +++ b/stuff/profiles/layouts/fxs/STD_raylitFx.xml @@ -6,6 +6,7 @@ intensity decay smoothness + radius includeInput invert diff --git a/toonz/sources/common/trop/traylit.cpp b/toonz/sources/common/trop/traylit.cpp index 7785ed1..a0a09c9 100644 --- a/toonz/sources/common/trop/traylit.cpp +++ b/toonz/sources/common/trop/traylit.cpp @@ -47,22 +47,24 @@ of the ray we're tracing /*-- 8bit/16bitの違いを吸収する係数 --*/ double factor = max / 255.0; - double scale = - params.m_scale; // NOTE: These variable initializations are, well, - double decay = log(params.m_decay / 100.0 + 1.0) + - 1.0; // heuristic at least. They were probably tested - double intensity = - 1e8 * log(params.m_intensity / 100.0 + 1.0) / - scale; // to be good, but didn't quite make any REAL sense. - double smoothness = log(params.m_smoothness * 5.0 / 100.0 + 1.0); // + // NOTE: These variable initializations are, well, + // heuristic at least. They were probably tested + // to be good, but didn't quite make any REAL sense. // They could be done MUCH better, but changing them + // would alter the way raylit has been applied until now. + // Should be changed at some point, though... + + double scale = params.m_scale; + double decay = log(params.m_decay / 100.0 + 1.0) + 1.0; + double intensity = 1e8 * log(params.m_intensity / 100.0 + 1.0) / scale; + double smoothness = log(params.m_smoothness * 5.0 / 100.0 + 1.0); + double radius = params.m_radius; + /*-- 1ステップ進んだ時、次のピクセルで光源が無かったときの光の弱まる割合 --*/ - double neg_delta_p = - smoothness * - intensity; // would alter the way raylit has been applied until now. + double neg_delta_p = smoothness * intensity; /*-- 1ステップ進んだ時、次のピクセルで光源が有ったときの光の強まる割合 --*/ double quot_delta_p = intensity / max; // - // Should be changed at some point, though... + /*-- * m_colorはRaylitFxのColor値。r_fac、g_fac、b_facは各チャンネルをPremultiplyした値 * --*/ @@ -136,13 +138,23 @@ of the ray we're tracing bool insideDst = (x >= 0) && (y >= 0); if (insideDst) { // Write the corresponding destination pixel - if (lightness > 0.0) - value = (int)(factor * lightness / - (rayPos.x * - pow((double)(sq(rayPos.x) + sq(rayPos.y) + sq_z), - decay)) + - 0.5); // * ^-d... 0.5 rounds - else + if (lightness > 0.0) { + if (radius == 0.0) { + value = (int)(factor * lightness / + (rayPos.x * + pow((double)(sq(rayPos.x) + sq(rayPos.y) + sq_z), + decay)) + + 0.5); // * ^-d... 0.5 rounds + } else { + double ratio = std::max(0.001, 1.0 - radius / norm(rayPos)); + value = (int)(factor * lightness / + (rayPos.x * ratio * + pow((double)(sq(rayPos.x * ratio) + + sq(rayPos.y * ratio) + sq_z), + decay)) + + 0.5); // * ^-d... 0.5 rounds + } + } else value = 0; // NOTE: pow() could be slow. If that is the case, it could be cached @@ -188,15 +200,19 @@ void performColorRaylit(T *bufIn, T *bufOut, int dxIn, int dyIn, int dxOut, double lightness_r, lightness_g, lightness_b; double factor = max / 255.0; - double scale = - params.m_scale; // NOTE: These variable initializations are, well, - double decay = log(params.m_decay / 100.0 + 1.0) + - 1.0; // heuristic at least. They were probably tested - double intensity = - 1e8 * log(params.m_intensity / 100.0 + 1.0) / - scale; // to be good, but didn't quite make any REAL sense. - double smoothness = log(params.m_smoothness * 5.0 / 100.0 + 1.0); // + // NOTE: These variable initializations are, well, + // heuristic at least. They were probably tested + // to be good, but didn't quite make any REAL sense. // They could be done MUCH better, but changing them + // would alter the way raylit has been applied until now. + // Should be changed at some point, though... + + double scale = params.m_scale; + double decay = log(params.m_decay / 100.0 + 1.0) + 1.0; + double intensity = 1e8 * log(params.m_intensity / 100.0 + 1.0) / scale; + double smoothness = log(params.m_smoothness * 5.0 / 100.0 + 1.0); + double radius = params.m_radius; + double neg_delta_p = smoothness * intensity; // would alter the way raylit has been applied until now. @@ -260,9 +276,18 @@ void performColorRaylit(T *bufIn, T *bufOut, int dxIn, int dyIn, int dxOut, bool insideDst = (x >= 0) && (y >= 0); if (insideDst) { // Write the corresponding destination pixel - fac = - factor / (rayPos.x * - pow((double)(sq(rayPos.x) + sq(rayPos.y) + sq_z), decay)); + if (radius == 0.0) { + fac = factor / + (rayPos.x * + pow((double)(sq(rayPos.x) + sq(rayPos.y) + sq_z), decay)); + } else { + double ratio = std::max(0.001, 1.0 - radius / norm(rayPos)); + fac = + factor / + (rayPos.x * ratio * + pow((double)(sq(rayPos.x * ratio) + sq(rayPos.y * ratio) + sq_z), + decay)); + } // NOTE: pow() could be slow. If that is the case, it could be cached // for the whole octant along the longest ray at integer positions, @@ -343,12 +368,12 @@ void computeOctant(const TRasterPT &src, const TRasterPT &dst, int octant, dyIn = srcWrap, dyOut = dstWrap, y0 = tfloor(pOut.y), y1 = lyOut; if (octant == 5 || octant == 8) dyIn = -srcWrap, dyOut = -dstWrap, y0 = lyOut - tfloor(pOut.y) - 1, - y1 = lyOut, std::swap(srcRect.y0, srcRect.y1), srcRect.y0 = lyOut - srcRect.y0, - srcRect.y1 = lyOut - srcRect.y1; + y1 = lyOut, std::swap(srcRect.y0, srcRect.y1), + srcRect.y0 = lyOut - srcRect.y0, srcRect.y1 = lyOut - srcRect.y1; if (octant == 6 || octant == 7) dxIn = -srcWrap, dxOut = -dstWrap, x0 = lyOut - tfloor(pOut.y) - 1, - x1 = lyOut, std::swap(srcRect.y0, srcRect.y1), srcRect.y0 = lyOut - srcRect.y0, - srcRect.y1 = lyOut - srcRect.y1; + x1 = lyOut, std::swap(srcRect.y0, srcRect.y1), + srcRect.y0 = lyOut - srcRect.y0, srcRect.y1 = lyOut - srcRect.y1; /*-- 縦向きのピザ領域を計算する場合は、90度回転してから --*/ // Swap x and y axis where necessary diff --git a/toonz/sources/include/tparamuiconcept.h b/toonz/sources/include/tparamuiconcept.h index b72a278..3d0fbc8 100644 --- a/toonz/sources/include/tparamuiconcept.h +++ b/toonz/sources/include/tparamuiconcept.h @@ -66,6 +66,8 @@ public: LINEAR_RANGE, // A band-like range between two points. // { [2 TPointParamP] } + RAYLIT, + TYPESCOUNT }; diff --git a/toonz/sources/include/trop.h b/toonz/sources/include/trop.h index 6602e34..6c80b10 100644 --- a/toonz/sources/include/trop.h +++ b/toonz/sources/include/trop.h @@ -350,6 +350,7 @@ struct RaylitParams { double m_scale; bool m_invert; bool m_includeInput; + double m_radius; }; //! Make raylit effect on \b srcRas raster and put the result in \b dstRas. diff --git a/toonz/sources/stdfx/raylitfx.cpp b/toonz/sources/stdfx/raylitfx.cpp index 34d6f3c..08c9260 100644 --- a/toonz/sources/stdfx/raylitfx.cpp +++ b/toonz/sources/stdfx/raylitfx.cpp @@ -19,6 +19,7 @@ protected: TDoubleParamP m_decay; TDoubleParamP m_smoothness; TBoolParamP m_includeInput; + TDoubleParamP m_radius; public: BaseRaylitFx() @@ -27,18 +28,22 @@ public: , m_intensity(60) , m_decay(1.0) , m_smoothness(100) - , m_includeInput(false) { + , m_includeInput(false) + , m_radius(0.0) { m_p->getX()->setMeasureName("fxLength"); m_p->getY()->setMeasureName("fxLength"); + m_radius->setMeasureName("fxLength"); bindParam(this, "p", m_p); bindParam(this, "z", m_z); bindParam(this, "intensity", m_intensity); bindParam(this, "decay", m_decay); bindParam(this, "smoothness", m_smoothness); bindParam(this, "includeInput", m_includeInput); + bindParam(this, "radius", m_radius); addInputPort("Source", m_input); + m_radius->setValueRange(0.0, std::numeric_limits::max()); } ~BaseRaylitFx() {} @@ -56,11 +61,19 @@ public: const TRenderSettings &info) override; void getParamUIs(TParamUIConcept *&concepts, int &length) override { - concepts = new TParamUIConcept[length = 1]; + concepts = new TParamUIConcept[length = 3]; concepts[0].m_type = TParamUIConcept::POINT; concepts[0].m_label = "Center"; concepts[0].m_params.push_back(m_p); + + concepts[1].m_type = TParamUIConcept::RADIUS; + concepts[1].m_label = "Radius"; + concepts[1].m_params.push_back(m_radius); + concepts[1].m_params.push_back(m_p); + + concepts[2].m_type = TParamUIConcept::RAYLIT; + concepts[2].m_params.push_back(m_p); } }; @@ -69,7 +82,7 @@ public: bool BaseRaylitFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info) { if (m_input.isConnected()) { - bool ret = m_input->doGetBBox(frame, bBox, info); + bool ret = m_input->doGetBBox(frame, bBox, info); if (ret) bBox = TConsts::infiniteRectD; return ret; } else { @@ -171,6 +184,7 @@ void RaylitFx::doCompute(TTile &tileOut, double frame, params.m_lightOriginSrc.y -= (int)tileIn.m_pos.y; params.m_lightOriginDst.x -= (int)tileOut.m_pos.x; params.m_lightOriginDst.y -= (int)tileOut.m_pos.y; + params.m_radius = m_radius->getValue(frame); TRop::raylit(tileOut.getRaster(), tileIn.getRaster(), params); } @@ -229,6 +243,7 @@ void ColorRaylitFx::doCompute(TTile &tileOut, double frame, params.m_lightOriginSrc.y -= (int)tileIn.m_pos.y; params.m_lightOriginDst.x -= (int)tileOut.m_pos.x; params.m_lightOriginDst.y -= (int)tileOut.m_pos.y; + params.m_radius = m_radius->getValue(frame); TRop::glassRaylit(tileOut.getRaster(), tileIn.getRaster(), params); } diff --git a/toonz/sources/tnztools/edittoolgadgets.cpp b/toonz/sources/tnztools/edittoolgadgets.cpp index eb95383..ed86a52 100644 --- a/toonz/sources/tnztools/edittoolgadgets.cpp +++ b/toonz/sources/tnztools/edittoolgadgets.cpp @@ -1529,6 +1529,176 @@ void LinearRangeFxGadget::leftButtonUp(const TPointD &pos, const TMouseEvent &) { m_handle = None; } + +//============================================================================= + +class RayLitFxGadget final : public FxGadget { + TPointParamP m_center; + + enum HANDLE { Body = 0, Near, Far, None } m_handle = None; + + TPointD m_clickedPos, m_mousePos; + TPointD m_targetPos, m_anotherPos; + +public: + RayLitFxGadget(FxGadgetController *controller, + const TPointParamP ¢erPoint); + + void draw(bool picking) 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; +}; + +//--------------------------------------------------------------------------- + +RayLitFxGadget::RayLitFxGadget(FxGadgetController *controller, + const TPointParamP ¢erPoint) + : FxGadget(controller, 3), m_center(centerPoint) { + addParam(centerPoint->getX()); + addParam(centerPoint->getY()); +} + +//--------------------------------------------------------------------------- + +void RayLitFxGadget::draw(bool picking) { + auto setColorById = [&](int id) { + if (isSelected(id)) + glColor3dv(m_selectedColor); + else + glColor3d(0, 0, 1); + }; + + auto drawArrow = [&]() { + double arrowLength = getPixelSize() * 20; + double arrowTip = getPixelSize() * 5; + + glBegin(GL_LINES); + glVertex2d(-arrowLength, 0.0); + glVertex2d(arrowLength, 0.0); + + glVertex2d(-arrowLength + arrowTip, arrowTip); + glVertex2d(-arrowLength, 0.0); + + glVertex2d(-arrowLength + arrowTip, -arrowTip); + glVertex2d(-arrowLength, 0.0); + + glVertex2d(arrowLength - arrowTip, arrowTip); + glVertex2d(arrowLength, 0.0); + + glVertex2d(arrowLength - arrowTip, -arrowTip); + glVertex2d(arrowLength, 0.0); + glEnd(); + }; + + setPixelSize(); + double lineHalf = getPixelSize() * 100; + double lineInterval = getPixelSize() * 50; + double r = getPixelSize() * 3; + + glPushMatrix(); + + TPointD center = getValue(m_center); + double dCenter = norm(center); + TPointD handleVec; + if (dCenter > lineHalf) { + handleVec = normalize(center) * lineHalf; + setColorById(Body); + glPushName(getId() + Body); + glBegin(GL_LINES); + glVertex2d(handleVec.x * 0.95, handleVec.y * 0.95); + glVertex2d(-handleVec.x * 0.95, -handleVec.y * 0.95); + glEnd(); + glPopName(); + + double angle = std::atan2(-center.y, -center.x) * M_180_PI; + double theta = M_180_PI * lineInterval / dCenter; + + glColor3d(0, 0, 1); + glLineStipple(1, 0x00FF); + glEnable(GL_LINE_STIPPLE); + glPushMatrix(); + glTranslated(center.x, center.y, 0); + glRotated(angle, 0, 0, 1); + for (int i = -3; i <= 3; i++) { + if (i == 0) continue; + glPushMatrix(); + glRotated(theta * (double)i, 0, 0, 1); + glBegin(GL_LINES); + glVertex2d(dCenter - lineHalf, 0.0); + glVertex2d(dCenter + lineHalf, 0.0); + glEnd(); + glPopMatrix(); + } + + glPopMatrix(); + glDisable(GL_LINE_STIPPLE); + + for (int id = Near; id <= Far; id++) { + TPointD hPos = (id == Near) ? handleVec : -handleVec; + setColorById(id); + glPushName(getId() + id); + glPushMatrix(); + glTranslated(hPos.x, hPos.y, 0); + tglDrawRect(-r, -r, r, r); + glPopMatrix(); + glPopName(); + } + } + + if (m_handle == Body) { + glPushMatrix(); + TPointD centerOffset = center - m_targetPos; + handleVec = normalize(m_targetPos) * lineHalf; + glTranslated(centerOffset.x, centerOffset.y, 0); + glBegin(GL_LINES); + glVertex2d(handleVec.x, handleVec.y); + glVertex2d(-handleVec.x, -handleVec.y); + glEnd(); + glPopMatrix(); + } + glPopMatrix(); +} + +//--------------------------------------------------------------------------- + +void RayLitFxGadget::leftButtonDown(const TPointD &pos, const TMouseEvent &) { + m_handle = (HANDLE)m_selected; + if (m_handle == None) return; + m_clickedPos = pos; + m_targetPos = getValue(m_center); +} + +//--------------------------------------------------------------------------- + +void RayLitFxGadget::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { + if (m_handle == None) return; + TPointD d = pos - m_clickedPos; + + if (m_handle == Body) { + setValue(m_center, m_targetPos + d); + return; + } + + double angle = + std::atan2(pos.y, pos.x) - std::atan2(m_clickedPos.y, m_clickedPos.x); + double scale = norm(pos) / norm(m_clickedPos); + + QTransform transform; + QPointF p = transform.rotateRadians(angle) + .scale(scale, scale) + .map(QPointF(m_targetPos.x, m_targetPos.y)); + + setValue(m_center, TPointD(p.x(), p.y())); +} + +//--------------------------------------------------------------------------- + +void RayLitFxGadget::leftButtonUp(const TPointD &pos, const TMouseEvent &) { + m_handle = None; +} + //************************************************************************************* // FxGadgetController implementation //************************************************************************************* @@ -1713,6 +1883,12 @@ FxGadget *FxGadgetController::allocateGadget(const TParamUIConcept &uiConcept) { uiConcept.m_params[1]); break; } + + case TParamUIConcept::RAYLIT: { + assert(uiConcept.m_params.size() == 1); + gadget = new RayLitFxGadget(this, uiConcept.m_params[0]); + break; + } default: break; }