From 59d5adace318a4fb57c54147bd0d6e13d8255ee2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 30 Jul 2020 10:26:20 +0200 Subject: [PATCH] [apps/shared] Optimize polar curve range display Change-Id: Ic1b044212711d1f73e147cb0857084ff9d61fbd9 --- apps/graph/graph/graph_view.cpp | 31 +++++++----- apps/shared/curve_view.cpp | 88 +++++++++++++++++++++++++++++++++ apps/shared/curve_view.h | 1 + 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 907d4b100..ad19d7067 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -46,6 +46,9 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float tCacheMin, tCacheStep; if (type == ContinuousFunction::PlotType::Cartesian) { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + /* Here, tCacheMin can depend on rect (and change as the user move) + * because cache can be panned for cartesian curves, instead of being + * entirely invalidated. */ tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft); tCacheStep = pixelWidth(); } else { @@ -54,8 +57,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { } ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); - // Cartesian if (type == Shared::ContinuousFunction::PlotType::Cartesian) { + // Cartesian drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; @@ -75,18 +78,22 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right()); drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GrayVeryDark, false); } - continue; + } else if (type == Shared::ContinuousFunction::PlotType::Polar) { + // Polar + drawPolarCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), false, f->color()); + } else { + // Parametric + assert(type == Shared::ContinuousFunction::PlotType::Parametric); + drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), false, f->color()); } - - // Polar or parametric - assert( - type == Shared::ContinuousFunction::PlotType::Polar || - type == Shared::ContinuousFunction::PlotType::Parametric); - drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { - ContinuousFunction * f = (ContinuousFunction *)model; - Poincare::Context * c = (Poincare::Context *)context; - return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), false, f->color()); } } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 115142165..519802523 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include using namespace Poincare; @@ -650,6 +652,92 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } +float PolarThetaFromCoordinates(float x, float y, Preferences::AngleUnit angleUnit) { + // Return θ, between -π and π in given angleUnit for a (x,y) position. + return Trigonometry::ConvertRadianToAngleUnit(std::arg(std::complex(x,y)), angleUnit).real(); +} + +void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const { + // Compute rect limits + float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); + float rectUp = pixelToFloat(Axis::Vertical, rect.top() + k_externRectMargin); + float rectDown = pixelToFloat(Axis::Vertical, rect.bottom() - k_externRectMargin); + + if (std::isnan(rectLeft) || std::isnan(rectRight) || std::isnan(rectUp) || std::isnan(rectDown)) { + return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + + bool rectOverlapsNegativeAbscissaAxis = false; + if (rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f) { + if (rectRight > 0.0f) { + // Origin is inside rect, tStart and tEnd cannot be optimized + return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + // Rect view overlaps the abscissa, on the left of the origin. + rectOverlapsNegativeAbscissaAxis = true; + } + + Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + + float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); + /* Compute angular coordinate of each corners of rect. + * t4 --- t3 + * | | + * t1 --- t2 */ + float t1 = PolarThetaFromCoordinates(rectLeft, rectDown, angleUnit); + float t2 = PolarThetaFromCoordinates(rectRight, rectDown, angleUnit); + float t3 = PolarThetaFromCoordinates(rectRight, rectUp, angleUnit); + float t4 = PolarThetaFromCoordinates(rectLeft, rectUp, angleUnit); + + /* The area between tMin and tMax (modulo π) is the area where something might + * be plotted. */ + float tMin = std::min(std::min(t1,t2),std::min(t3,t4)); + float tMax = std::max(std::max(t1,t2),std::max(t3,t4)); + + if (rectOverlapsNegativeAbscissaAxis) { + /* PolarThetaFromCoordinates yields coordinates between -π and π. When rect + * is overlapping the negative abscissa (at this point, the origin cannot be + * inside rect), t1 and t2 have a negative angle whereas t3 and t4 have a + * positive angle. We ensure here that tMin is t3 (modulo 2π), tMax is t2, + * and that tMax-tMin is minimal and positive. */ + tMin = t3 - 2 * piInAngleUnit; + tMax = t2; + } + + /* Draw curve on intervals where (tMin%π,tMax%π) intersects (tStart,tEnd). + * For instance : if tStart=-π, tEnd=3π, tMin=π/4 and tMax=π/3, a curve is + * drawn between the intervals : + * - [ π/4, π/3 ], [ 2π + π/4, 2π + π/3 ] + * - [ -π + π/4, -π + π/3 ], [ π + π/4, π + π/3 ] in case f(θ) is negative*/ + + // 1 - Translate tMin and tMax to the left so that no intersection is missed + while (tMax - piInAngleUnit > tStart) { + tMin -= piInAngleUnit; + tMax -= piInAngleUnit; + } + + // 2 - Translate tMin and tMax to the right until tMin is greater than tEnd + while (tMin < tEnd) { + float t1 = std::max(tMin, tStart); + float t2 = std::min(tMax, tEnd); + // Draw curve if there is an intersection + if (t1 <= t2) { + /* To maximize cache hits, we floor (and ceil) t1 (and t2) to the closest + * cached value. More of the curve is drawn. */ + int i = std::floor((t1 - tStart) / tStep); + float tCache1 = tStart + tStep * i; + + int j = std::ceil((t2 - tStart) / tStep); + float tCache2 = std::min(tStart + tStep * j, tEnd); + + drawCurve(ctx, rect, tCache1, tCache2, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + tMin += piInAngleUnit; + tMax += piInAngleUnit; + } +} + void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound, float highlightUpperBound) const { float rectMin = pixelToFloat(Axis::Horizontal, rect.left()); diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 00a3119a0..481429489 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -109,6 +109,7 @@ protected: void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; + void drawPolarCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; void drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const; void computeLabels(Axis axis);