// Note: with no antialiasing, the coordinates in QPointF are rounded to the nearest integer. void TransitionWidget::paintEvent(QPaintEvent*) { if (pointList_ == nullptr) { return; } QPainter painter(this); painter.setFont(QFont("monospace")); if (dataUpdated_) { QFontMetrics fm = painter.fontMetrics(); textYOffset_ = 0.5 * fm.ascent(); leftMargin_ = MARGIN + TEXT_MARGIN + fm.width(transitionYLabels[0]); updateScales(); dataUpdated_ = false; } if (special_) { // Gray area. painter.fillRect(QRectF(QPointF(leftMargin_, MARGIN + 7.0 * yStep_), QPointF(leftMargin_ + graphWidth_, MARGIN + 14.0 * yStep_)), Qt::gray); // Y labels. for (unsigned int i = 0; i < specialTransitionYLabels.size(); ++i) { double y = MARGIN + i * yStep_ + textYOffset_; painter.drawText(QPointF(MARGIN, y), specialTransitionYLabels[i]); painter.drawText(QPointF(leftMargin_ + graphWidth_ + TEXT_MARGIN, y), specialTransitionYLabels[i]); } } else { // Gray areas. painter.fillRect(QRectF(QPointF(leftMargin_, MARGIN), QPointF(leftMargin_ + graphWidth_, MARGIN + 2.0 * yStep_)), Qt::gray); painter.fillRect(QRectF(QPointF(leftMargin_, MARGIN + 12.0 * yStep_), QPointF(leftMargin_ + graphWidth_, MARGIN + 14.0 * yStep_)), Qt::gray); // Y labels. for (unsigned int i = 0; i < transitionYLabels.size(); ++i) { double y = MARGIN + i * yStep_ + textYOffset_; painter.drawText(QPointF(MARGIN, y), transitionYLabels[i]); painter.drawText(QPointF(leftMargin_ + graphWidth_ + TEXT_MARGIN, y), transitionYLabels[i]); } } // Horizontal lines. for (int i = 0; i < 15; ++i) { double y = MARGIN + i * yStep_; painter.drawLine(QPointF(leftMargin_, y), QPointF(leftMargin_ + graphWidth_, y)); } QPen pen; QPen pen2; pen2.setWidth(2); painter.setRenderHint(QPainter::Antialiasing); // Lines. if (!pointList_->empty()) { painter.setPen(pen2); QPointF prevP(0.5 + timeToX(0.0), 0.5 + valueToY(0.0)); for (unsigned int i = 0, size = pointList_->size(); i < size; ++i) { const auto& point = (*pointList_)[i]; QPointF p(0.5 + timeToX(point.time), 0.5 + valueToY(point.value)); painter.drawLine(prevP, p); prevP = p; if (i != size - 1) { if (point.type != (*pointList_)[i + 1].type) { prevP.setY(0.5 + valueToY(0.0)); } } } QPointF p(0.5 + timeToX(ruleDuration_), 0.5 + valueToY(special_ ? 0.0 : 100.0)); painter.drawLine(prevP, p); painter.setPen(pen); } painter.setBrush(QBrush(Qt::black)); // Points. if (!pointList_->empty()) { for (auto& point : *pointList_) { double x = 0.5 + timeToX(point.time); double y = 0.5 + valueToY(point.value); switch (point.type) { case TRMControlModel::Transition::Point::TYPE_DIPHONE: drawDiPoint(painter, x, y); break; case TRMControlModel::Transition::Point::TYPE_TRIPHONE: drawTriPoint(painter, x, y); break; case TRMControlModel::Transition::Point::TYPE_TETRAPHONE: drawTetraPoint(painter, x, y); break; default: break; } } } // Posture markers. double yMarker = 0.5 + 0.5 * yStep_ + textYOffset_; switch (transitionType_) { case TRMControlModel::Transition::TYPE_DIPHONE: drawDiPoint(painter, 0.5 + timeToX(ruleDuration_), yMarker); break; case TRMControlModel::Transition::TYPE_TRIPHONE: drawDiPoint( painter, 0.5 + timeToX(mark1_) , yMarker); drawTriPoint(painter, 0.5 + timeToX(ruleDuration_), yMarker); break; case TRMControlModel::Transition::TYPE_TETRAPHONE: drawDiPoint( painter, 0.5 + timeToX(mark1_) , yMarker); drawTriPoint( painter, 0.5 + timeToX(mark2_) , yMarker); drawTetraPoint(painter, 0.5 + timeToX(ruleDuration_), yMarker); break; default: break; } painter.setBrush(QBrush()); painter.setRenderHint(QPainter::Antialiasing, false); // Posture vertical lines. double yPosture1 = special_ ? valueToY(-140.0) : valueToY(-20.0); double yPosture2 = special_ ? valueToY( 140.0) : valueToY(120.0); double x = timeToX(0.0); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); switch (transitionType_) { case TRMControlModel::Transition::TYPE_DIPHONE: x = timeToX(ruleDuration_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); break; case TRMControlModel::Transition::TYPE_TRIPHONE: x = timeToX(mark1_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); x = timeToX(ruleDuration_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); break; case TRMControlModel::Transition::TYPE_TETRAPHONE: x = timeToX(mark1_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); x = timeToX(mark2_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); x = timeToX(ruleDuration_); painter.drawLine(QPointF(x, yPosture1), QPointF(x, yPosture2)); break; default: break; } if (!pointList_->empty()) { if (!special_) { // Slopes. double y1 = MARGIN + 15.0 * yStep_; double y2 = y1 + yStep_; double yText = y2 - 0.5 * yStep_ + textYOffset_; for (unsigned int i = 0, end = pointList_->size() - 1; i < end; ++i) { const auto& point1 = (*pointList_)[i]; if (point1.hasSlope) { const auto& point2 = (*pointList_)[i + 1]; painter.setPen(Qt::gray); painter.drawRect(QRectF(QPointF(timeToX(point1.time), y1), QPointF(timeToX(point2.time), y2))); painter.setPen(Qt::black); painter.drawText( QPointF(timeToX(point1.time) + SLOPE_TEXT_MARGIN, yText), QString("%1").arg(point1.slope, 0, 'f', 1)); } } } // Selected point. if (selectedPointIndex_ >= 0 && static_cast<std::size_t>(selectedPointIndex_) < pointList_->size()) { const auto& point = (*pointList_)[selectedPointIndex_]; double x = timeToX(point.time); double y = valueToY(point.value); painter.drawRect(QRectF( QPointF(x - SELECTION_SIZE, y - SELECTION_SIZE), QPointF(x + SELECTION_SIZE, y + SELECTION_SIZE))); } } }
// fit a polyline to a bezier patch, return true is treshhold not exceeded (ie: you can continue) // version that uses tables from the previous iteration, to minimize amount of work done bool Path::AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP) { Geom::Point start,end; // pour une coordonnee Geom::Point cp1, cp2; worstP = 1; if (pts.size() == 2) { return true; } start[0] = data.Xk[0]; start[1] = data.Yk[0]; cp1[0] = data.Xk[1]; cp1[1] = data.Yk[1]; end[0] = data.Xk[data.nbPt - 1]; end[1] = data.Yk[data.nbPt - 1]; cp2 = cp1; if (pts.size() == 3) { // start -> cp1 -> end res.start = cp1 - start; res.end = end - cp1; worstP = 1; return true; } if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) ) { cp1 = start + res.start / 3; cp2 = end - res.end / 3; } else { // aie, non-inversible double worstD = 0; worstP = -1; for (int i = 1; i < data.nbPt; i++) { Geom::Point nPt; nPt[Geom::X] = data.Xk[i]; nPt[Geom::Y] = data.Yk[i]; double nle = DistanceToCubic(start, res, nPt); if ( data.fk[i] ) { // forced points are favored for splitting the recursion; we do this by increasing their distance if ( worstP < 0 || 2 * nle > worstD ) { worstP = i; worstD = 2 * nle; } } else { if ( worstP < 0 || nle > worstD ) { worstP = i; worstD = nle; } } } return false; } // calcul du delta= pondere par les longueurs des segments double delta = 0; { double worstD = 0; worstP = -1; Geom::Point prevAppP; Geom::Point prevP; double prevDist; prevP[Geom::X] = data.Xk[0]; prevP[Geom::Y] = data.Yk[0]; prevAppP = prevP; // le premier seulement prevDist = 0; #ifdef with_splotch_killer if ( data.nbPt <= 20 ) { for (int i = 1; i < data.nbPt - 1; i++) { Geom::Point curAppP; Geom::Point curP; double curDist; Geom::Point midAppP; Geom::Point midP; double midDist; curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + N23(data.tk[i]) * cp2[Geom::X] + N03(data.tk[i]) * data.Xk[0] + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + N23(data.tk[i]) * cp2[Geom::Y] + N03(data.tk[i]) * data.Yk[0] + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; curP[Geom::X] = data.Xk[i]; curP[Geom::Y] = data.Yk[i]; double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + N23(mtk) * cp2[Geom::X] + N03(mtk) * data.Xk[0] + N33(mtk) * data.Xk[data.nbPt - 1]; midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + N23(mtk) * cp2[Geom::Y] + N03(mtk) * data.Yk[0] + N33(mtk) * data.Yk[data.nbPt - 1]; midP = 0.5 * (curP + prevP); Geom::Point diff = curAppP - curP; curDist = dot(diff, diff); diff = midAppP - midP; midDist = dot(diff, diff); delta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; if ( curDist > worstD ) { worstD = curDist; worstP = i; } else if ( data.fk[i] && 2 * curDist > worstD ) { worstD = 2*curDist; worstP = i; } prevP = curP; prevAppP = curAppP; prevDist = curDist; } delta /= data.totLen; } else { #endif for (int i = 1; i < data.nbPt - 1; i++) { Geom::Point curAppP; Geom::Point curP; double curDist; curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + N23(data.tk[i]) * cp2[Geom::X] + N03(data.tk[i]) * data.Xk[0] + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + N23(data.tk[i]) * cp2[Geom::Y] + N03(data.tk[i]) * data.Yk[0] + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; curP[Geom::X] = data.Xk[i]; curP[Geom::Y] = data.Yk[i]; Geom::Point diff = curAppP-curP; curDist = dot(diff, diff); delta += curDist; if ( curDist > worstD ) { worstD = curDist; worstP = i; } else if ( data.fk[i] && 2 * curDist > worstD ) { worstD = 2*curDist; worstP = i; } prevP = curP; prevAppP = curAppP; prevDist = curDist; } #ifdef with_splotch_killer } #endif } if (delta < treshhold * treshhold) { // premier jet // Refine a little. for (int i = 1; i < data.nbPt - 1; i++) { Geom::Point pt(data.Xk[i], data.Yk[i]); data.tk[i] = RaffineTk(pt, start, cp1, cp2, end, data.tk[i]); if (data.tk[i] < data.tk[i - 1]) { // Force tk to be monotonic non-decreasing. data.tk[i] = data.tk[i - 1]; } } if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) == false) { // ca devrait jamais arriver, mais bon res.start = 3.0 * (cp1 - start); res.end = 3.0 * (end - cp2 ); return true; } double ndelta = 0; { double worstD = 0; worstP = -1; Geom::Point prevAppP; Geom::Point prevP(data.Xk[0], data.Yk[0]); double prevDist = 0; prevAppP = prevP; // le premier seulement #ifdef with_splotch_killer if ( data.nbPt <= 20 ) { for (int i = 1; i < data.nbPt - 1; i++) { Geom::Point curAppP; Geom::Point curP; double curDist; Geom::Point midAppP; Geom::Point midP; double midDist; curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + N23(data.tk[i]) * cp2[Geom::X] + N03(data.tk[i]) * data.Xk[0] + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + N23(data.tk[i]) * cp2[Geom::Y] + N03(data.tk[i]) * data.Yk[0] + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; curP[Geom::X] = data.Xk[i]; curP[Geom::Y] = data.Yk[i]; double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + N23(mtk) * cp2[Geom::X] + N03(mtk) * data.Xk[0] + N33(mtk) * data.Xk[data.nbPt - 1]; midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + N23(mtk) * cp2[Geom::Y] + N03(mtk) * data.Yk[0] + N33(mtk) * data.Yk[data.nbPt - 1]; midP = 0.5 * (curP + prevP); Geom::Point diff = curAppP - curP; curDist = dot(diff, diff); diff = midAppP - midP; midDist = dot(diff, diff); ndelta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; if ( curDist > worstD ) { worstD = curDist; worstP = i; } else if ( data.fk[i] && 2 * curDist > worstD ) { worstD = 2*curDist; worstP = i; } prevP = curP; prevAppP = curAppP; prevDist = curDist; } ndelta /= data.totLen; } else { #endif for (int i = 1; i < data.nbPt - 1; i++) { Geom::Point curAppP; Geom::Point curP; double curDist; curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + N23(data.tk[i]) * cp2[Geom::X] + N03(data.tk[i]) * data.Xk[0] + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + N23(data.tk[i]) * cp2[1] + N03(data.tk[i]) * data.Yk[0] + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; curP[Geom::X] = data.Xk[i]; curP[Geom::Y] = data.Yk[i]; Geom::Point diff = curAppP - curP; curDist = dot(diff, diff); ndelta += curDist; if ( curDist > worstD ) { worstD = curDist; worstP = i; } else if ( data.fk[i] && 2 * curDist > worstD ) { worstD = 2 * curDist; worstP = i; } prevP = curP; prevAppP = curAppP; prevDist = curDist; } #ifdef with_splotch_killer } #endif } if (ndelta < delta + 0.00001) { return true; } else { // nothing better to do res.start = 3.0 * (cp1 - start); res.end = 3.0 * (end - cp2 ); } return true; } return false; }