| |
| |
|
|
| |
| |
| #ifdef _DEBUG |
| #define _STLP_DEBUG 1 |
| #endif |
| |
| #include "ext/StrokeDeformationImpl.h" |
| #include "ext/StrokeDeformation.h" |
| #include "ext/SquarePotential.h" |
| #include "ext/StrokeParametricDeformer.h" |
| |
| #include "ext/ContextStatus.h" |
| #include "ext/Designer.h" |
| |
| |
| #include <tcurves.h> |
| #include <tstrokeutil.h> |
| |
| #include <tmathutil.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <vector> |
| |
| #include "DeformationSelector.h" |
| |
| using namespace ToonzExt; |
| |
| namespace |
| { |
| |
| |
| |
| |
| |
| double |
| computeReductionFactor(double pixelSize, |
| double factor) |
| { |
| assert(pixelSize > 0.0 && |
| factor > 0.0); |
| if (factor <= 0.0) |
| factor = 1.0; |
| if (pixelSize <= 0.0 || |
| pixelSize > 1.0) |
| pixelSize = 1.0; |
| return pixelSize * factor; |
| } |
| |
| |
| double |
| retrieveParamAtLengthWithOffset(const TStroke *stroke2change, |
| double length, |
| double offset) |
| { |
| if (!isValid(stroke2change) || |
| !isValid(offset) || |
| length < 0) |
| return -1; |
| |
| double |
| strokeLength = stroke2change->getLength(); |
| assert(strokeLength >= 0.0 && |
| "Not valid length"); |
| if (strokeLength < 0) { |
| return -1; |
| } |
| |
| double |
| lengthAtW = stroke2change->getLength(offset); |
| |
| assert(strokeLength >= lengthAtW && |
| "Position of parameter is greater than stroke length!!!"); |
| |
| if (strokeLength < lengthAtW) |
| return -1; |
| |
| double |
| newLength = -1; |
| |
| if (stroke2change->isSelfLoop()) { |
| if (length >= 0) |
| newLength = length > lengthAtW ? length + lengthAtW : lengthAtW - length; |
| } else |
| newLength = std::min(length + lengthAtW, |
| strokeLength); |
| |
| return stroke2change->getParameterAtLength(newLength); |
| } |
| |
| |
| |
| bool rotateStroke(const TStroke *stroke2change, |
| TStroke *&rotated, |
| double &from, |
| double &to, |
| TPointD &old_w0_pos) |
| { |
| if (!stroke2change || |
| !isValid(from) || |
| !isValid(to)) |
| return false; |
| |
| rotated = 0; |
| |
| |
| |
| old_w0_pos = convert(stroke2change->getControlPoint(0)); |
| |
| double |
| rotateAtLength = stroke2change->getLength(to); |
| |
| assert(rotateAtLength >= 0.0); |
| if (rotateAtLength < 0) |
| return false; |
| |
| |
| rotated = ToonzExt::rotateControlPoint(stroke2change, |
| ToonzExt::EvenInt(0), |
| rotateAtLength); |
| if (!rotated) |
| return false; |
| |
| from = rotated->getW(stroke2change->getPoint(from)); |
| to = rotated->getW(stroke2change->getPoint(to)); |
| |
| |
| ToonzExt::cloneStrokeStatus(stroke2change, |
| rotated); |
| return true; |
| } |
| |
| |
| |
| TStroke * |
| rotateControlPointAtPoint(const TStroke *stroke, |
| const TPointD &pnt) |
| { |
| if (!stroke || |
| tdistance2(stroke->getPoint(0.0), |
| pnt) < sq(TConsts::epsilon)) |
| return 0; |
| |
| double |
| w = stroke->getW(pnt); |
| TPointD |
| theSamePnt = stroke->getPoint(w); |
| double |
| length = stroke->getLength(w); |
| return ToonzExt::rotateControlPoint(stroke, |
| ToonzExt::EvenInt(0), |
| length); |
| } |
| |
| |
| |
| bool findExtremesFromActionLength(double toolActionLength, |
| const TStroke *stroke, |
| double w, |
| ToonzExt::Interval &out) |
| { |
| out = ToonzExt::Interval(-1.0, -1.0); |
| |
| if (!stroke || |
| 0.0 > w || |
| w > 1.0) |
| return false; |
| |
| double |
| emiToolSize = 0.5 * toolActionLength; |
| double |
| strokelength = stroke->getLength(); |
| double |
| lengthAtParam = stroke->getLength(w); |
| |
| assert(emiToolSize >= 0.0 && |
| strokelength >= 0.0 && |
| lengthAtParam >= 0.0); |
| |
| if (emiToolSize > strokelength * 0.5) { |
| if (stroke->isSelfLoop()) { |
| emiToolSize = strokelength * 0.5; |
| |
| |
| lengthAtParam += emiToolSize; |
| |
| if (lengthAtParam > strokelength) |
| lengthAtParam -= strokelength; |
| out.first = |
| out.second = stroke->getParameterAtLength(lengthAtParam); |
| } else { |
| out.first = 0.0; |
| out.second = 1.0; |
| } |
| |
| return true; |
| } |
| |
| if (emiToolSize < 0.0 || |
| strokelength < 0.0 || |
| lengthAtParam < 0.0) |
| return false; |
| |
| |
| out.first = lengthAtParam - emiToolSize; |
| out.second = lengthAtParam + emiToolSize; |
| |
| if (stroke->isSelfLoop()) { |
| if (out.first < 0.0) { |
| out.first = strokelength + out.first; |
| assert(out.first < strokelength); |
| } |
| |
| if (out.second > strokelength) { |
| out.second = out.second - strokelength; |
| assert(0.0 < out.second && |
| out.second < strokelength); |
| } |
| } else { |
| out.first = std::max(out.first, 0.0); |
| out.second = std::min(out.second, strokelength); |
| } |
| |
| |
| out.first = stroke->getParameterAtLength(out.first); |
| out.second = stroke->getParameterAtLength(out.second); |
| #ifdef _DEBUG |
| if (!stroke->isSelfLoop()) |
| assert(out.first <= out.second); |
| #endif |
| return true; |
| } |
| |
| |
| |
| bool areDifferent(const TStroke *ref1, |
| const TStroke *ref2) |
| { |
| if (!isValid(ref1) || |
| !isValid(ref2)) |
| return true; |
| |
| int cpCount1 = ref1->getControlPointCount(); |
| int cpCount2 = ref2->getControlPointCount(); |
| if (cpCount1 != cpCount2) |
| return true; |
| |
| while (--cpCount1 >= 0) { |
| if (ref1->getControlPoint(cpCount1) != |
| ref2->getControlPoint(cpCount1)) |
| return true; |
| } |
| assert(cpCount1 == -1); |
| return false; |
| } |
| |
| |
| } |
| |
| |
| |
| TStroke * |
| StrokeDeformationImpl::copyOfLastSelectedStroke_ = 0; |
| |
| |
| |
| const ContextStatus *& |
| StrokeDeformationImpl::getImplStatus() |
| { |
| |
| |
| static const ContextStatus *contextStatus_instance; |
| return contextStatus_instance; |
| } |
| |
| |
| |
| TStroke *& |
| StrokeDeformationImpl::getLastSelectedStroke() |
| { |
| static TStroke *lastSelectedStroke_instance; |
| return lastSelectedStroke_instance; |
| } |
| |
| |
| |
| void StrokeDeformationImpl::setLastSelectedStroke(TStroke *stroke) |
| { |
| TStroke *&lastSelStroke = getLastSelectedStroke(); |
| lastSelStroke = stroke; |
| if (lastSelStroke) { |
| delete copyOfLastSelectedStroke_; |
| copyOfLastSelectedStroke_ = new TStroke(*lastSelStroke); |
| } |
| } |
| |
| |
| |
| int &StrokeDeformationImpl::getLastSelectedDegree() |
| { |
| static int lastSelectedDegree_instance; |
| return lastSelectedDegree_instance; |
| } |
| |
| |
| |
| void StrokeDeformationImpl::setLastSelectedDegree(int degree) |
| { |
| getLastSelectedDegree() = degree; |
| } |
| |
| |
| |
| ToonzExt::Intervals & |
| StrokeDeformationImpl::getSpiresList() |
| { |
| static ToonzExt::Intervals listOfSpire_instance; |
| return listOfSpire_instance; |
| } |
| |
| |
| |
| ToonzExt::Intervals & |
| StrokeDeformationImpl::getStraightsList() |
| { |
| static ToonzExt::Intervals listOfStraight_instance; |
| return listOfStraight_instance; |
| } |
| |
| |
| |
| StrokeDeformationImpl::StrokeDeformationImpl() |
| { |
| shortcutKey_ = ContextStatus::NONE; |
| cursorId_ = -1; |
| this->reset(); |
| } |
| |
| |
| |
| StrokeDeformationImpl::~StrokeDeformationImpl() |
| { |
| clearPointerContainer(strokes_); |
| delete potential_; |
| potential_ = 0; |
| delete deformer_; |
| deformer_ = 0; |
| delete copyOfLastSelectedStroke_; |
| copyOfLastSelectedStroke_ = 0; |
| } |
| |
| |
| |
| bool StrokeDeformationImpl::activate_impl(const ContextStatus *status) |
| { |
| assert(status && "Not status available"); |
| |
| if (!status || |
| !this->init(status)) |
| return false; |
| |
| double |
| w = status->w_; |
| |
| ToonzExt::Interval |
| extremes = this->getExtremes(); |
| |
| TStroke * |
| stroke2transform; |
| |
| if (!this->computeStroke2Transform(status, |
| stroke2transform, |
| w, |
| extremes)) |
| return false; |
| |
| |
| if (areAlmostEqual(extremes.first, w)) |
| w = extremes.first; |
| |
| if (areAlmostEqual(extremes.second, w)) |
| w = extremes.second; |
| |
| assert(extremes.first <= w && |
| w <= extremes.second); |
| |
| if (extremes.first > w || |
| w > extremes.second) { |
| return false; |
| } |
| |
| std::vector<double> |
| splitParameter; |
| splitParameter.push_back(extremes.first); |
| splitParameter.push_back(extremes.second); |
| |
| assert(strokes_.empty()); |
| if (!strokes_.empty()) |
| clearPointerContainer(strokes_); |
| splitStroke(*stroke2transform, |
| splitParameter, |
| strokes_); |
| |
| assert(strokes_.size() == 3); |
| |
| |
| if (strokes_.size() != 3) { |
| clearPointerContainer(strokes_); |
| return false; |
| } |
| |
| |
| stroke2manipulate_ = strokes_[1]; |
| assert(stroke2manipulate_ && " Not valid reference to stroke to move!!!"); |
| if (!stroke2manipulate_) |
| return false; |
| |
| |
| { |
| TStroke *tmp_stroke = strokes_[2]; |
| if (isAlmostZero(tmp_stroke->getLength())) { |
| std::vector<TStroke *>::iterator |
| it = strokes_.begin(); |
| std::advance(it, 2); |
| strokes_.erase(it); |
| delete tmp_stroke; |
| } |
| tmp_stroke = strokes_[0]; |
| if (isAlmostZero(tmp_stroke->getLength())) { |
| strokes_.erase(strokes_.begin()); |
| delete tmp_stroke; |
| } |
| |
| |
| if (isAlmostZero(stroke2manipulate_->getLength())) { |
| int |
| count = stroke2manipulate_->getControlPointCount() - 1; |
| assert(count > 0); |
| |
| TThickPoint |
| pnt0 = stroke2manipulate_->getControlPoint(0), |
| pntn = stroke2manipulate_->getControlPoint(count), |
| delta = TThickPoint(2.0 * TConsts::epsilon, |
| -2.0 * TConsts::epsilon, |
| 0.0); |
| |
| stroke2manipulate_->setControlPoint(0, pnt0 - delta); |
| stroke2manipulate_->setControlPoint(count, pntn + delta); |
| } |
| } |
| |
| |
| int |
| cp_count = stroke2manipulate_->getChunkCount(); |
| const TThickQuadratic |
| *tq; |
| bool |
| haveEmpty = false; |
| while (--cp_count >= 0) { |
| tq = stroke2manipulate_->getChunk(cp_count); |
| |
| if (tq->getLength() == 0.0) |
| haveEmpty = true; |
| } |
| |
| if (haveEmpty) { |
| const double |
| reductionFactor = computeReductionFactor(getImplStatus()->pixelSize_, |
| 1.0); |
| stroke2manipulate_->reduceControlPoints(reductionFactor); |
| } |
| |
| TPointD |
| pntOnStroke = stroke2transform->getPoint(w); |
| |
| |
| |
| w = stroke2manipulate_->getW(pntOnStroke); |
| |
| |
| |
| |
| |
| |
| double lengthOfAction = this->findActionLength(); |
| |
| bool |
| exception = false; |
| |
| try { |
| |
| delete deformer_; |
| deformer_ = new StrokeParametricDeformer(lengthOfAction, |
| w, |
| stroke2manipulate_, |
| potential_->clone()); |
| } catch (std::invalid_argument &) { |
| exception = true; |
| assert(!"Invalid argument"); |
| } catch (...) { |
| exception = true; |
| } |
| |
| assert(deformer_ && |
| "Deformer is not available"); |
| |
| if (exception) { |
| deformer_ = 0; |
| return false; |
| } |
| |
| assert(getImplStatus() != 0 && "ContextStatus is null???"); |
| |
| if (!getImplStatus()) { |
| delete deformer_; |
| this->reset(); |
| return false; |
| } |
| |
| |
| deformer_->setDiff(getImplStatus()->deformerSensibility_); |
| |
| |
| |
| stroke2manipulate_->insertControlPoints(w); |
| |
| |
| |
| |
| |
| |
| |
| |
| if (old_w0_pos_ != TConsts::napd) { |
| double |
| w = stroke2manipulate_->getW(old_w0_pos_); |
| TPointD |
| pnt = stroke2manipulate_->getPoint(w); |
| |
| if (tdistance2(pnt, old_w0_pos_) < sq(TConsts::epsilon)) { |
| |
| old_w0_ = w; |
| } else |
| old_w0_ = -1; |
| } |
| |
| bool |
| test = increaseControlPoints(*stroke2manipulate_, |
| *deformer_, |
| getImplStatus()->pixelSize_); |
| |
| |
| |
| |
| |
| |
| if (!test) |
| return false; |
| |
| stroke2manipulate_->disableComputeOfCaches(); |
| return true; |
| } |
| |
| |
| |
| void StrokeDeformationImpl::update_impl(const TPointD &delta) |
| { |
| assert(stroke2manipulate_ && |
| deformer_ && |
| "Stroke and Deformer are available"); |
| |
| if (!stroke2manipulate_ || |
| !deformer_) |
| return; |
| |
| |
| deformer_->setMouseMove(delta.x, delta.y); |
| |
| modifyControlPoints(*stroke2manipulate_, |
| *deformer_); |
| } |
| |
| |
| |
| TStroke * |
| StrokeDeformationImpl::deactivate_impl() |
| { |
| if (!stroke2manipulate_ || |
| !getImplStatus()) { |
| this->reset(); |
| return 0; |
| } |
| |
| |
| if (old_w0_ != -1) |
| old_w0_pos_ = stroke2manipulate_->getPoint(old_w0_); |
| |
| const double |
| reductionFactor = computeReductionFactor(getImplStatus()->pixelSize_, |
| 3.0); |
| |
| |
| |
| const int |
| size = stroke2manipulate_->getControlPointCount(); |
| std::vector<TThickPoint> |
| pnt(size); |
| for (int i = 0; |
| i < size; |
| ++i) |
| pnt[i] = stroke2manipulate_->getControlPoint(i); |
| |
| std::vector<TStroke *>::iterator |
| it = std::find(strokes_.begin(), |
| strokes_.end(), |
| stroke2manipulate_); |
| assert(it != strokes_.end()); |
| |
| int pos = std::distance(strokes_.begin(), |
| it); |
| stroke2manipulate_ = 0; |
| delete strokes_[pos]; |
| |
| TStroke *tmpStroke = new TStroke(pnt); |
| assert((tmpStroke != 0) && "Not valid stroke!"); |
| strokes_[pos] = tmpStroke; |
| strokes_[pos]->reduceControlPoints(reductionFactor); |
| |
| |
| |
| |
| |
| TStroke |
| *ref = Toonz::merge(strokes_); |
| |
| ToonzExt::cloneStrokeStatus(getImplStatus()->stroke2change_, |
| ref); |
| |
| if (old_w0_pos_ != TConsts::napd) { |
| |
| |
| TStroke * |
| rotated = rotateControlPointAtPoint(ref, |
| old_w0_pos_); |
| if (rotated) { |
| delete ref; |
| ref = rotated; |
| if (getImplStatus()) |
| ToonzExt::cloneStrokeStatus(getImplStatus()->stroke2change_, |
| ref); |
| else { |
| delete ref; |
| ref = 0; |
| } |
| } |
| } |
| |
| this->reset(); |
| return ref; |
| } |
| |
| |
| |
| void StrokeDeformationImpl::reset() |
| { |
| old_w0_ = -1; |
| old_w0_pos_ = TConsts::napd; |
| |
| deformer_ = 0; |
| getImplStatus() = 0; |
| this->setLastSelectedDegree(-1); |
| this->setLastSelectedStroke(0); |
| this->getSpiresList().clear(); |
| this->getStraightsList().clear(); |
| |
| stroke2manipulate_ = 0; |
| clearPointerContainer(strokes_); |
| |
| } |
| |
| |
| |
| TStroke * |
| StrokeDeformationImpl::getTransformedStroke() |
| { |
| return this->stroke2manipulate_; |
| } |
| |
| |
| |
| ToonzExt::Potential * |
| StrokeDeformationImpl::getPotential() |
| { |
| return this->potential_; |
| } |
| |
| |
| |
| void StrokeDeformationImpl::draw(Designer *designer) |
| { |
| } |
| |
| |
| |
| void StrokeDeformationImpl::setPotential(Potential *potential) |
| { |
| assert(potential); |
| potential_ = potential; |
| } |
| |
| |
| |
| bool StrokeDeformationImpl::check(const ContextStatus *status) |
| { |
| if (!status || |
| !this->init(status)) |
| return false; |
| |
| return this->check_(status); |
| } |
| |
| |
| |
| TPointD & |
| StrokeDeformationImpl::oldW0() |
| { |
| return old_w0_pos_; |
| } |
| |
| |
| |
| ToonzExt::Interval |
| StrokeDeformationImpl::getExtremes() |
| { |
| ToonzExt::Interval |
| extremes = ToonzExt::Interval(-1.0, -1.0); |
| |
| if (!getImplStatus()) |
| return extremes; |
| |
| |
| if (getImplStatus()->isManual_ == true) { |
| findExtremesFromActionLength(getImplStatus()->lengthOfAction_, |
| getImplStatus()->stroke2change_, |
| getImplStatus()->w_, |
| extremes); |
| } else { |
| |
| this->findExtremes_(getImplStatus(), |
| extremes); |
| } |
| |
| return extremes; |
| } |
| |
| |
| |
| bool StrokeDeformationImpl::init(const ContextStatus *status) |
| { |
| if (!status || |
| !isValid(status->stroke2change_) || |
| !isValid(status->w_)) { |
| this->reset(); |
| return false; |
| } |
| |
| getImplStatus() = status; |
| |
| if (!this->getLastSelectedStroke() || |
| this->getLastSelectedStroke() != status->stroke2change_ || |
| areDifferent(copyOfLastSelectedStroke_, |
| status->stroke2change_) || |
| (this->getLastSelectedDegree() == -1) || |
| (this->getLastSelectedDegree() != status->cornerSize_)) { |
| this->getSpiresList().clear(); |
| this->getStraightsList().clear(); |
| |
| ToonzExt::findCorners(status->stroke2change_, |
| this->getSpiresList(), |
| this->getStraightsList(), |
| status->cornerSize_, |
| TConsts::epsilon); |
| this->setLastSelectedStroke(status->stroke2change_); |
| this->setLastSelectedDegree(status->cornerSize_); |
| } |
| return true; |
| } |
| |
| |
| |
| bool StrokeDeformationImpl::computeStroke2Transform(const ContextStatus *status, |
| TStroke *&stroke2transform, |
| double &w, |
| ToonzExt::Interval &extremes) |
| { |
| if (!status || |
| !isValid(w)) |
| return false; |
| |
| stroke2transform = 0; |
| |
| if (status->stroke2change_->isSelfLoop()) { |
| if (extremes.first > extremes.second) { |
| double |
| new_w = (extremes.first + extremes.second) * 0.5; |
| |
| if (!rotateStroke(status->stroke2change_, |
| stroke2transform, |
| w, |
| new_w, |
| old_w0_pos_)) |
| return false; |
| |
| ContextStatus |
| tmpStatus = *status; |
| tmpStatus.stroke2change_ = stroke2transform; |
| tmpStatus.w_ = w; |
| this->check(&tmpStatus); |
| extremes = this->getExtremes(); |
| this->init(status); |
| return true; |
| } else if (extremes.first == extremes.second) { |
| double |
| positionOfFixPoint = -1; |
| |
| |
| |
| |
| switch (this->getSpiresList().size()) { |
| case 0: |
| assert(extremes.first == -1); |
| positionOfFixPoint = retrieveParamAtLengthWithOffset(status->stroke2change_, |
| status->stroke2change_->getLength() * 0.5, |
| w); |
| break; |
| case 1: |
| if (extremes.first != -1) |
| positionOfFixPoint = retrieveParamAtLengthWithOffset(status->stroke2change_, |
| status->stroke2change_->getLength() * 0.5, |
| w); |
| else |
| positionOfFixPoint = this->getSpiresList()[0].first; |
| break; |
| case 2: |
| |
| default: |
| assert(extremes.first != -1); |
| |
| |
| |
| |
| |
| positionOfFixPoint = extremes.first; |
| break; |
| } |
| |
| if (!rotateStroke(status->stroke2change_, |
| stroke2transform, |
| w, |
| positionOfFixPoint, |
| old_w0_pos_)) |
| return false; |
| |
| extremes = ToonzExt::Interval(0.0, 1.0); |
| return true; |
| } |
| } |
| |
| if (!isValid(extremes.first) || |
| !isValid(extremes.second)) |
| return false; |
| |
| try { |
| if (!stroke2transform) |
| stroke2transform = new TStroke(*status->stroke2change_); |
| } catch (...) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| |
| |