[dasher: 33/38] Rewrite dynamics!
- From: Patrick Welche <pwelche src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [dasher: 33/38] Rewrite dynamics!
- Date: Tue, 3 Jan 2012 15:35:27 +0000 (UTC)
commit 8ff98264e2f25fc40c66786fe480b18dcc5a468e
Author: Alan Lawrence <acl33 inf phy cam ac uk>
Date: Tue Nov 22 18:59:24 2011 +0000
Rewrite dynamics!
Continuous movement works by stepping root node bounds, rather than viewport
(avoids computing center-of-expansion, which is undefined for translation);
new approximation using (very) approx. sqrt, or BP_EXACT_DYNAMICS to use pow()
Static (click-mode) works by interpolating growth in log space, with linear
deceleration (i.e. a quadratic) => moves quickly at first then slows down, in
all directions.
Src/DasherCore/DasherModel.cpp | 275 ++++++++++++++++++++++----------------
Src/DasherCore/DasherModel.h | 35 +++---
Src/DasherCore/DefaultFilter.cpp | 6 +-
Src/DasherCore/DynamicFilter.cpp | 30 ++---
Src/DasherCore/FrameRate.cpp | 41 ++----
Src/DasherCore/FrameRate.h | 21 +---
Src/DasherCore/Parameters.cpp | 2 +
Src/DasherCore/Parameters.h | 4 +-
8 files changed, 217 insertions(+), 197 deletions(-)
---
diff --git a/Src/DasherCore/DasherModel.cpp b/Src/DasherCore/DasherModel.cpp
index 44d4a9e..174f48a 100644
--- a/Src/DasherCore/DasherModel.cpp
+++ b/Src/DasherCore/DasherModel.cpp
@@ -46,9 +46,10 @@ static char THIS_FILE[] = __FILE__;
#endif
#endif
-// FIXME - need to get node deletion working properly and implement reference counting
-// CDasherModel
+//If preprocessor variable DEBUG_DYNAMICS is defined, will display the difference
+// between computed (approximate) one-step motion, and ideal/exact motion (using pow()).
+//#define DEBUG_DYNAMICS
CDasherModel::CDasherModel() {
@@ -303,94 +304,147 @@ bool CDasherModel::NextScheduledStep()
return false;
}
-void CDasherModel::ScheduleOneStep(myint X, myint Y, int iSteps, dasherint iMinSize) {
- myint r1, r2;
- // works out next viewpoint
-
- DASHER_ASSERT(m_Root != NULL);
- // Avoid X == 0, as this corresponds to infinite zoom
- if (X <= 0) X = 1;
+///A very approximate square root. Finds the square root of (just) the
+/// most significant bit, then two iterations of Newton.
+inline dasherint mysqrt(dasherint in) {
+ //1. Find greatest i satisfying 1<<(i<<1) < in; let rt = 1<<i be first approx
+ // but find by binary chop: at first double each time..
+ dasherint i=1;
+ while (dasherint(1)<<4*i < in) i*=2;
+ //then try successively smaller bits.
+ for (dasherint test=i; test/=2;)
+ if (dasherint(1)<<2*(i+test) < in) i+=test;
+ //so, first approx:
+ dasherint rt = 1<<i;
+ rt = (rt+in/rt)/2;//better
+ return (rt+in/rt)/2;//better still
- // If X is too large we risk overflow errors, so limit it
- dasherint iMaxX = (1 << 29) / iSteps;
- if (X > iMaxX) X = iMaxX;
+ //Some empirical results (from DEBUG_DYNAMICS, at about 40fps with XLimit=400)
+ // with one iteration, error in rate of data entry is ~~10% near xhair, falls
+ // as we get further away, then abruptly jumps up to 30% near the x limit
+ // (and beyond it, but also before reaching it).
+ //With two iterations, error is 0-1% near xhair, gradually rising to 10%
+ // near/at the x limit.
+ //However, reversing is less good - it can go twice as fast at extreme x...
+}
+
+void CDasherModel::ScheduleOneStep(dasherint y1, dasherint y2, int nSteps, int limX, bool bExact) {
- // Mouse coords X, Y
- // const dasherint Y1 = 0;
- const dasherint Y2(MAX_Y);
+ m_deGotoQueue.clear();
- // Calculate what the extremes of the viewport will be when the
- // point under the cursor is at the cross-hair. This is where
- // we want to be in iSteps updates
+ // Rename for readability.
+ const dasherint R1 = m_Rootmin;
+ const dasherint R2 = m_Rootmax;
- dasherint y1(Y - (Y2 * X) / (2 * ORIGIN_X));
- dasherint y2(Y + (Y2 * X) / (2 * ORIGIN_Y));
- dasherint oy1(y1),oy2(y2); //back these up to use later
+ // Calculate the bounds of the root node when the target range y1-y2
+ // fills the viewport.
+ // This is where we want to be in iSteps updates
+ dasherint targetRange=y2-y1;
+
+ const dasherint r1 = MAX_Y*(R1-y1)/targetRange;
+ const dasherint r2 = MAX_Y*(R2-y1)/targetRange;
- // iSteps is the number of update steps we need to get the point
- // under the cursor over to the cross hair. Calculated in order to
- // keep a constant bit-rate.
- DASHER_ASSERT(iSteps > 0);
+ dasherint m1=(r1-R1),m2=(r2-R2);
- // Calculate the new values of y1 and y2 required to perform a single update
- // step.
- {
- const dasherint denom = Y2 + (iSteps - 1) * (y2 - y1),
- newy1 = y1 * Y2 / denom,
- newy2 = ((y2 * iSteps - y1 * (iSteps - 1)) * Y2) / denom;
-
- y1 = newy1;
- y2 = newy2;
- }
+ //Any interpolation (R1,R2) + alpha*(m1,m2) moves along the correct path.
+ // Just have to decide how far, i.e. what alpha.
- // Calculate the minimum size of the viewport corresponding to the
- // maximum zoom.
+ //Possible schemes (using rw=r2-r1, Rw=R2-R1)
+ // (Note: if y2-y1 == MAX_Y, alpha=1/nSteps is correct, and in some schemes must be a special case)
+ // alpha = (pow(rw/Rw,1/nSteps)-1)*rW / (rw-Rw) : correct/ideal, but uses pow
+ // alpha = 1/nSteps : moves forwards too fast, reverses too slow (correct for translation)
+ // alpha = MAX_Y / (MAX_Y + (nSteps-1)*(y2-y1)) : (same eqn as old Dasher) more so! reversing ~~ 1/3 ideal speed, and maxes out at moderate dasherX.
+ // alpha = (y2-y1) / (MAX_Y*(nSteps-1) + y2-y1) : too slow forwards, reverses too quick
+ //We are using:
+ // alpha = sqrt(y2-y1) / (sqrt(MAX_Y)*(nSteps-1) + sqrt(y2-y1))
+ // with approx sqrt on y2-y1
+ //this is pretty good going forwards, but reverses faster than the ideal, on the order of 2*
- if((y2 - y1) < iMinSize) {
- const dasherint newy1 = y1 * (Y2 - iMinSize) / (Y2 - (y2 - y1)),
- newy2 = newy1 + iMinSize;
+ if (targetRange < 2*limX) {
+#ifdef DEBUG_DYNAMICS
+ {
+ const dasherint Rw=R2-R1, rw=r2-r1;
+ dasherint apsq = mysqrt(y2-y1);
+ dasherint denom = 64*(nSteps-1) + apsq;
+ dasherint nw = (rw*apsq + Rw*64*(nSteps-1))/denom;
+ double bits = (log(nw) - log(Rw))/log(2);
+ std::cout << "Too fast at X " << (y2-y1)/2 << ": would enter " << bits << "b = " << (bits*nSteps) << " in " << nSteps << "steps; will now enter ";
+ }
+#endif
+ //atm we have Rw=R2-R1, rw=r2-r1 = Rw*MAX_Y/targetRange, (m1,m2) to take us there
- y1 = newy1;
- y2 = newy2;
+ //if targetRange were = 2*limX, we'd have rw' = Rw*MAX_Y/2*limX < rw
+ //the movement necessary to take us to rw', rather than rw, is thus:
+ // (m1',m2') = (m1,m2) * (rw' - Rw) / (rw-Rw) => scale m1,m2 by (rw'-Rw)/(rw-Rw)
+ // = (Rw*MAX_Y/(2*limX) - Rw)/(Rw*MAX_Y/targetRange-Rw)
+ // = (MAX_Y/(2*limX)-1) / (MAX_Y/targetRange-1)
+ // = (MAX_Y-(2*limX))/(2*limX) / ((MAX_Y-targetRange)/targetRange)
+ // = (MAX_Y-(2*limX)) / (2*limX) * targetRange / (MAX_Y-targetRange)
+ {
+ const dasherint n=targetRange*(MAX_Y-2*limX), d=(MAX_Y-targetRange)*2*limX;
+ bool bOver=max(abs(m1),abs(m2))>std::numeric_limits<dasherint>::max()/n;
+ if (bOver) {
+ //std::cout << "Overflow in max-speed-limit " << m1 << "," << m2 << " =wd> " << ((m1*n)/d) << "," << ((m2*n)/d);
+ //so do it a harder way, but which uses smaller intermediates:
+ // (Yes, this is valid even if !bOver. Could use it all the time?)
+ m1 = (m1/d)*n + ((m1 % d) * n) / d;
+ m2 = (m2/d)*n + ((m2 % d) * n) / d;
+ //std::cout << " => " << m1 << "," << m2 << std::endl;
+ } else {
+ m1 = (m1*n)/d;
+ m2 = (m2*n)/d;
+ }
+ }
+ //then make the stepping function, which follows, behave as if we were at limX:
+ targetRange=2*limX;
}
- //okay, we now have target bounds for the viewport, after allowing for framerate etc.
- // we now go there in one step...
-
- // new root{min,max} r1,r2, old root{min,max} R1,R2
- const dasherint R1 = m_Rootmin;
- const dasherint R2 = m_Rootmax;
-
- // If |(0,Y2)| = |(y1,y2)|, the "zoom factor" is 1, so we just translate.
- if (Y2 == y2 - y1) {
- r1 = R1 - y1;
- r2 = R2 - y1;
- } else {
- // There is a point C on the y-axis such the ratios (y1-C):(Y1-C) and
- // (y2-C):(Y2-C) are equal - iow that divides the "target" region y1-y2
- // into the same proportions as it divides the screen (0-Y2). I.e., this
- // is the center of expansion - the point on the y-axis which everything
- // moves away from (or towards, if reversing).
-
- //We prefer to compute C from the _original_ (y1,y2) pair, as this is more
- // accurate (and avoids drifting up/down when heading straight along the
- // x-axis in dynamic button modes). However...
- if (((y2-y1) < Y2) ^ ((oy2-oy1) < Y2)) {
- //Sometimes (very occasionally), the calculation of a single-step above
- // can turn a zoom-in into a zoom-out, or vice versa, when the movement
- // is mostly translation. In which case, must compute C consistently with
- // the (scaled, single-step) movement we are going to perform, or else we
- // will end up suddenly going the wrong way along the y-axis (i.e., the
- // sense of translation will be reversed) !
- oy1=y1; oy2=y2;
+#ifndef DEBUG_DYNAMICS
+ if (bExact) {
+ //#else, for DEBUG_DYNAMICS, we compute the exact movement either way, to compare.
+#endif
+ double frac;
+ if (targetRange == MAX_Y) {
+ frac=1.0/nSteps;
+ } else {
+ double tr(targetRange);
+ //expansion factor (of root node) for one step, post-speed-limit
+ double eFac = pow(MAX_Y/tr,1.0/nSteps);
+ //fraction of way along linear interpolation Rw->rw that yields that width:
+ // = (Rw*eFac - Rw) / (rw-Rw)
+ // = Rw * (eFac-1.0) / (Rw*MAX_Y/tr-Rw)
+ // = (eFac - 1.0) / (MAX_Y/tr - 1.0)
+ frac = (eFac-1.0) / (MAX_Y/tr - 1.0);
}
- const dasherint C = (oy1 * Y2) / (oy1 + Y2 - oy2);
+#ifdef DEBUG_DYNAMICS
+ const dasherint m1t=m1*frac, m2t=m2*frac; //keep original m1,m2 to compare
+#else
+ m1*=frac; m2*=frac;
+ } else //conditional - only do one of exact/approx
+#endif
+ { //begin block A (regardless of #ifdef)
- r1 = ((R1 - C) * Y2) / (y2 - y1) + C;
- r2 = ((R2 - C) * Y2) / (y2 - y1) + C;
- }
- m_deGotoQueue.clear();
- m_deGotoQueue.push_back(pair<myint,myint>(r1,r2));
+ //approximate dynamics: interpolate
+ // apsq parts rw to 64*(nSteps-1) parts Rw
+ // (no need to compute target width)
+ dasherint apsq = mysqrt(targetRange);
+ dasherint denom = 64*(nSteps-1) + apsq;
+
+ // so new width nw = (64*(nSteps-1)*Rw + apsq*rw)/denom
+ // = Rw*(64*(nSteps-1) + apsq*MAX_Y/targetRange)/denom
+ m1 = (m1*apsq)/denom, m2=(m2*apsq)/denom;
+#ifdef DEBUG_DYNAMICS
+ std::cout << "Move " << m1 << "," << m2 << " should be " << m1t << "," << m2t;
+ double dActualBits = (log((R2+m2)-(R1+m1))-log(R2-R1))/log(2);
+ double dDesiredBits = (log((R2+m2t)-(R1+m1t))-log(R2-R1))/log(2);
+ std::cout << " enters " << dActualBits << "b = " << (dActualBits*nSteps) << " in " << nSteps << "steps, should be "
+ << dDesiredBits << "=>" << (dDesiredBits*nSteps) << ", error " << int(abs(dDesiredBits-dActualBits)*100/dDesiredBits) << "%" << std::endl;
+ if (bExact)
+ m1=m1t, m2=m2t; //overwrite approx values (we needed them somewhere!)
+#endif
+ } //end block A (regardless of #ifdef)
+
+ m_deGotoQueue.push_back(pair<myint,myint>(R1+m1, R2+m2));
}
void CDasherModel::OutputTo(CDasherNode *pNewNode) {
@@ -481,49 +535,44 @@ void CDasherModel::RenderToView(CDasherView *pView, CExpansionPolicy &policy) {
}
void CDasherModel::ScheduleZoom(dasherint y1, dasherint y2, int nsteps) {
- DASHER_ASSERT(y2>y1);
-
+
+ m_deGotoQueue.clear();
+
// Rename for readability.
- const dasherint Y1 = 0;
- const dasherint Y2 = MAX_Y;
const dasherint R1 = m_Rootmin;
const dasherint R2 = m_Rootmax;
- dasherint C, r1, r2;
-
- // If |(y1,y2)| = |(Y1,Y2)|, the "zoom factor" is 1, so we just translate.
- // y2 - y1 == Y2 - Y1 => y1 - Y1 == y2 - Y2
- C = y1 - Y1;
- if (C == y2 - Y2) {
- r1 = R1 + C;
- r2 = R2 + C;
- } else {
- // There is a point C on the y-axis such the ratios (y1-C):(Y1-C) and
- // (y2-C):(Y2-C) are equal. (Obvious when drawn on separate parallel axes.)
- C = (y1 * Y2 - y2 * Y1) / (y1 + Y2 - y2 - Y1);
-
- // So another point r's zoomed y coordinate R, has the same ratio (r-C):(R-C)
- if (y1 != C) {
- r1 = ((R1 - C) * (Y1 - C)) / (y1 - C) + C;
- r2 = ((R2 - C) * (Y1 - C)) / (y1 - C) + C;
- } else if (y2 != C) {
- r1 = ((R1 - C) * (Y2 - C)) / (y2 - C) + C;
- r2 = ((R2 - C) * (Y2 - C)) / (y2 - C) + C;
- } else { // implies y1 = y2
- std::cerr << "Impossible geometry in CDasherModel::ScheduleZoom\n";
- }
- }
-
- // sNewItem seems to contain a list of root{min,max} for the frames of the
- // zoom, so split r -> R into n steps, with accurate R
- m_deGotoQueue.clear();
- for (int s = nsteps - 1; s >= 0; --s) {
- m_deGotoQueue.push_back(pair<myint,myint>(
- r1 - (s * (r1 - R1)) / nsteps,
- r2 - (s * (r2 - R2)) / nsteps));
+ const dasherint r1 = MAX_Y*(m_Rootmin-y1)/(y2-y1);
+ const dasherint r2 = MAX_Y*(m_Rootmax-y1)/(y2-y1);
+
+ //We're going to interpolate in steps whose size starts at nsteps
+ // and decreases by one each time - so cumulatively:
+ // <nsteps> <2*nsteps-1> <3*nsteps-3> <4*nsteps-6>
+ // (until the next value is the same as the previous)
+ //These will sum to / reach (triangular number formula):
+ const int max((nsteps*(nsteps+1))/2);
+ //heights:
+ const myint oh(R2-R1), nh(r2-r1);
+ //log(the amount by which we wish to multiply the height):
+ const double logHeightMul(nh==oh ? 0 : log(nh/static_cast<double>(oh)));
+ for (int s = nsteps; nsteps>1; s+=(--nsteps)) {
+ double dFrac; //(linear) fraction of way from oh to nh...
+ if (nh==oh)
+ dFrac = s/static_cast<double>(max);
+ else {
+ //interpolate expansion logarithmically to get new height:
+ const double h(oh*exp((logHeightMul*s)/max));
+ //then treat that as a fraction of the way between oh to nh linearly
+ dFrac = (h-oh)/(nh-oh);
+ }
+ //and use that fraction to interpolate from R to r
+ m_deGotoQueue.push_back(pair<myint,myint>(R1+dFrac*(r1-R1), R2+dFrac*(r2-R2)));
}
+ //final point, done accurately/simply:
+ m_deGotoQueue.push_back(pair<myint,myint>(r1,r2));
}
+
void CDasherModel::ClearScheduledSteps() {
m_deGotoQueue.clear();
}
diff --git a/Src/DasherCore/DasherModel.h b/Src/DasherCore/DasherModel.h
index 60e1961..3e28920 100644
--- a/Src/DasherCore/DasherModel.h
+++ b/Src/DasherCore/DasherModel.h
@@ -70,24 +70,10 @@ class Dasher::CDasherModel: public Observable<CDasherNode*>, private NoClones
CDasherModel();
~CDasherModel();
- /// @name Dymanic evolution
- /// Routines detailing the timer dependent evolution of the model
+ /// @name Offset routines
+ /// For "bouncing" the display up and down (dynamic button modes)
/// @{
- ///
- /// Schedules *one step* of movement towards the specified
- /// co-ordinates - used by timer callbacks for non-button modes.
- /// Interpolates movement according to iSteps and iMinSize, and calculates
- /// new co-ordinates for the root node (after *one step*) into m_deGotoQueue
- /// just as ScheduleZoom. For further information, see Doc/geometry.tex.
- ///
- /// \param mousex dasherx co-ordinate towards which to move (e.g. mouse pos)
- /// \param mousey dashery co-ordinate towards which to move (e.g. mouse pos)
- /// \param iSteps number of frames which should get us all the way to (mousex,mousey)
- /// \param iMinSize limit on rate of expansion due to bitrate (as moving
- /// all the way to the mouse at mousex==1 would be an absurd rate of data entry,
- /// becoming infinite at mousex==0).
- void ScheduleOneStep(myint mousex, myint mousey, int iSteps, dasherint iMinSize);
///
/// Apply an offset to the 'target' coordinates - implements the jumps in
@@ -134,8 +120,8 @@ class Dasher::CDasherModel: public Observable<CDasherNode*>, private NoClones
/// @}
///
- /// @name Scheduled operation
- /// E.g. response to button mode
+ /// @name Dynamics
+ /// For controlling movement of the model
/// @{
///
@@ -147,6 +133,19 @@ class Dasher::CDasherModel: public Observable<CDasherNode*>, private NoClones
/// \param y1,y2 - target range of y axis, i.e. to move to 0,MAXY
/// \param nSteps number of steps to schedule to take us all the way there
void ScheduleZoom(dasherint y1, dasherint y2, int nSteps);
+
+ /// Schedule one frame of movement, with the property that
+ /// <nsteps> calls with the same parameter, should bring
+ /// the given range of Dasher Y-space to fill the axis.
+ /// (Roughly - we use approximations! - but more accurate
+ /// than the first step of a zoom).
+ /// \param y1,y2 - target range of y axis, i.e. to move to 0,MAXY
+ /// \param nSteps number of steps that would take us all the way there
+ /// \param limX X coord at which max speed achieved (any X coord lower than
+ /// this, will be slowed down to that speed).
+ /// \param bExact whether to do "exact" calculations (slower, using floating-point
+ /// pow), or approximate with integers (will move at not-ideal rate in some directions)
+ void ScheduleOneStep(dasherint y1, dasherint y2, int nSteps, int limX, bool bExact);
///Cancel any steps previously scheduled (most likely by ScheduleZoom)
void ClearScheduledSteps();
diff --git a/Src/DasherCore/DefaultFilter.cpp b/Src/DasherCore/DefaultFilter.cpp
index 4c1affe..6617b3e 100644
--- a/Src/DasherCore/DefaultFilter.cpp
+++ b/Src/DasherCore/DefaultFilter.cpp
@@ -16,7 +16,9 @@ static SModuleSettings sSettings[] = {
{BP_REMAP_XTREME, T_BOOL, -1, -1, -1, -1, _("At top and bottom, scroll more and translate less (makes error-correcting easier)")},
{LP_GEOMETRY, T_LONG, 0, 3, 1, 1, _("Screen geometry (mostly for tall thin screens) - 0=old-style, 1=square no-xhair, 2=squish, 3=squish+log")},
{LP_SHAPE_TYPE, T_LONG, 0, 5, 1, 1, _("Shape type: 0=disjoint rects, 1=overlapping, 2=triangles, 3=trunc-tris, 4=quadrics, 5=circles")},
+ {LP_X_LIMIT_SPEED, T_LONG, 1, 800, 1536, 1, _("Distance from right-hand-side Y-axis, at which maximum speed is reached. (2048=xhair)")},
{BP_TURBO_MODE, T_BOOL, -1, -1, -1, -1, _("Hold right mouse button / key 1 to go 3/4 faster")},
+ {BP_EXACT_DYNAMICS, T_BOOL, -1, -1, -1, -1, _("Use exact computation of per-frame movement (slower)")},
};
bool CDefaultFilter::GetSettings(SModuleSettings **sets, int *iCount) {
@@ -62,7 +64,9 @@ bool CDefaultFilter::DecorateView(CDasherView *pView, CDasherInput *pInput) {
x[0] = CDasherModel::ORIGIN_X;
y[0] = CDasherModel::ORIGIN_Y;
- x[1] = m_iLastX;
+ //If the user's finger/mouse is in the margin, draw the line to the closest
+ // point we'll actually head to.
+ x[1] = max(myint(1),m_iLastX);
y[1] = m_iLastY;
// Actually plot the line
diff --git a/Src/DasherCore/DynamicFilter.cpp b/Src/DasherCore/DynamicFilter.cpp
index eb402f7..d86960f 100644
--- a/Src/DasherCore/DynamicFilter.cpp
+++ b/Src/DasherCore/DynamicFilter.cpp
@@ -32,27 +32,17 @@ bool CDynamicFilter::OneStepTowards(CDasherModel *pModel, myint X, myint Y, unsi
if (dSpeedMul<=0.0) return false; //going nowhere
m_pFramerate->RecordFrame(iTime); //Hmmm, even if we don't do anything else?
- //The maximum number of bits we should allow to be entered in this frame:
- // (after adjusting for slow start, turbo mode, control node slowdown, etc.)
- double dBits = m_pFramerate->GetMaxBitsPerFrame()*dSpeedMul;
-
- //Compute max expansion, i.e. the minimum size we should allow the range 0..MAX_Y
- // to be shrunk down to, for this frame. We cache the most-recent result to
- // avoid an exp() (and a division): in the majority of cases this doesn't change
- // between frames, but only does so when the maxbitrate changes, or dspeedmul
- // changes (e.g. continuously during slow start, or when entering/leaving turbo
- // mode or a control node).
- if (dBits != m_dLastBits) m_iLastMinSize = static_cast<myint>(CDasherModel::MAX_Y / exp(m_dLastBits = dBits));
- //However, note measurements on iPhone suggest even one exp() per frame is not
- // a significant overhead; so the caching may be unnecessary, but it's easy.
+ // iSteps is the number of update steps we need to get the point
+ // under the cursor over to the cross hair. Calculated in order to
+ // keep a constant bit-rate.
+ const int iSteps(static_cast<int>(m_pFramerate->Steps() / dSpeedMul));
+ DASHER_ASSERT(iSteps > 0);
+
+ // If X is too large we risk overflow errors, so limit it
+ // Not rescaling Y in this case: at that X, all Y's are nearly equivalent!
+ X = max(myint(1),min(X, myint(1<<29)/iSteps));
- //If we wanted to take things further we could generalize this cache to cover
- // exp()s done in the dynamic button modes too, and thus to allow them to adjust
- // lag, guide markers, etc., according to the dSpeedMul in use. (And/or
- // to do slow-start more efficiently by interpolating cache values.)
- pModel->ScheduleOneStep(X, Y,
- static_cast<int>(m_pFramerate->Steps() / dSpeedMul),
- m_iLastMinSize);
+ pModel->ScheduleOneStep(Y-X, Y+X, iSteps, GetLongParameter(LP_X_LIMIT_SPEED), GetBoolParameter(BP_EXACT_DYNAMICS));
return true;
}
diff --git a/Src/DasherCore/FrameRate.cpp b/Src/DasherCore/FrameRate.cpp
index 49242bd..9677a8a 100644
--- a/Src/DasherCore/FrameRate.cpp
+++ b/Src/DasherCore/FrameRate.cpp
@@ -11,8 +11,8 @@ CFrameRate::CFrameRate(CSettingsUser *pCreator) :
m_iTime = 0;
//try and carry on from where we left off at last run
- HandleEvent(LP_MAX_BITRATE);
- //calls UpdateSteps(), which sets m_dRXMax and m_iSteps
+ HandleEvent(LP_X_LIMIT_SPEED);
+ //Sets m_dBitsAtLimX and m_iSteps
}
void CFrameRate::RecordFrame(unsigned long Time)
@@ -36,43 +36,30 @@ void CFrameRate::RecordFrame(unsigned long Time)
// Calculate the framerate and reset framerate statistics for next
// sampling period
- double dFrNow;
if(m_iTime2 - m_iTime > 0) {
- dFrNow = m_iFrames * 1000.0 / (m_iTime2 - m_iTime);
+ double dFrNow = m_iFrames * 1000.0 / (m_iTime2 - m_iTime);
//LP_FRAMERATE records a decaying average, smoothed 50:50 with previous value
SetLongParameter(LP_FRAMERATE, long(GetLongParameter(LP_FRAMERATE) + (dFrNow*100))/2);
m_iTime = m_iTime2;
m_iFrames = 0;
- } else //best guess: use decaying average
- dFrNow = GetLongParameter(LP_FRAMERATE) / 100.0;
- UpdateSteps(dFrNow);
+ DASHER_TRACEOUTPUT("Fr %f Steps %d Samples %d Time2 %d\n", dFrNow, m_iSteps, m_iSamples, m_iTime2);
- DASHER_TRACEOUTPUT("Fr %f Steps %d Samples %d Time2 %d maxbits %f\n", dFrNow, m_iSteps, m_iSamples, m_iTime2, m_dFrameBits);
+ }
}
}
void CFrameRate::HandleEvent(int iParameter) {
-
switch (iParameter) {
- case LP_MAX_BITRATE:
- UpdateSteps(GetLongParameter(LP_FRAMERATE) / 100.0); //use the decaying average as current
- break;
+ case LP_X_LIMIT_SPEED:
+ m_dBitsAtLimX = (log(CDasherModel::MAX_Y) - log (2*GetLongParameter(LP_X_LIMIT_SPEED)))/log(2);
+ //fallthrough
+ case LP_MAX_BITRATE:
+ case LP_FRAMERATE:
+ //Calculate m_iSteps from the decaying-average framerate, as the number
+ // of steps that, at the X limit, will cause LP_MAX_BITRATE bits to be
+ // entered per second
+ m_iSteps = std::max(1,(int)(GetLongParameter(LP_FRAMERATE)*m_dBitsAtLimX/GetLongParameter(LP_MAX_BITRATE)));
}
}
-
-const double LN2 = log(2.0);
-const double STEPS_COEFF = -log(0.2) / LN2;
-
-void CFrameRate::UpdateSteps(double dFrNow) {
- const double dMaxbitrate = GetLongParameter(LP_MAX_BITRATE) / 100.0;
- // Update auxiliary variables - even if we didn't recalc the framerate
- // (means we reach sensible values more quickly after first loading)
- m_dFrameBits = dMaxbitrate * LN2 / dFrNow;
-
- //Calculate m_iSteps from the decaying-average framerate, and ensure
- // it is at least 1 (else, if framerate slows to <4, we get 0 steps!)
- m_iSteps = std::max(1,(int)(STEPS_COEFF * (GetLongParameter(LP_FRAMERATE)/100.0) / dMaxbitrate));
-
-}
diff --git a/Src/DasherCore/FrameRate.h b/Src/DasherCore/FrameRate.h
index 31ed87b..6e44b83 100644
--- a/Src/DasherCore/FrameRate.h
+++ b/Src/DasherCore/FrameRate.h
@@ -24,16 +24,10 @@ namespace Dasher {
class CFrameRate : public CSettingsUserObserver {
public:
CFrameRate(CSettingsUser *pCreator);
-
- virtual void HandleEvent(int iParameter);
- ///The maximum amount by which one frame may zoom in. Used as a hard
- /// upper-bound on the speed of movement (however far to RHS the cursor is),
- /// calculated from the most-recent (instantaneous) framerate rather than
- /// the decaying average used for the Steps() parameter.
- double GetMaxBitsPerFrame() {
- return m_dFrameBits;
- }
+ //Responds to a change to LP_FRAMERATE or LP_MAX_BITRATE
+ // by recomputing the Steps() parameter.
+ virtual void HandleEvent(int iParameter);
///The number of frames, in which we will attempt to bring
/// the target location (under the cursor, or in dynamic button
@@ -53,11 +47,8 @@ public:
}
void RecordFrame(unsigned long Time);
-
- bool OneStepTowards(CDasherModel *pModel, myint y1, myint y2, unsigned long iTime, double dSpeedMul);
private:
- double m_dFrameBits; // the maximum zoomin per frame
///number of frames that have been sampled
int m_iFrames;
///time at which first sampled frame was rendered
@@ -66,10 +57,8 @@ private:
int m_iSamples;
int m_iSteps;
-
- ///updates m_dRXMax and m_iSteps
- /// \param dFrNow current (non-decaying-average) framerate (if available!)
- void UpdateSteps(double dFrNow);
+
+ double m_dBitsAtLimX;
};
/// \}
}
diff --git a/Src/DasherCore/Parameters.cpp b/Src/DasherCore/Parameters.cpp
index bd4c10f..bebe518 100644
--- a/Src/DasherCore/Parameters.cpp
+++ b/Src/DasherCore/Parameters.cpp
@@ -22,6 +22,7 @@ const bp_table boolparamtable[] = {
{BP_MOUSEPOS_MODE, "StartOnMousePosition", false, "StartOnMousePosition"},
{BP_PALETTE_CHANGE, "PaletteChange", true, "PaletteChange"},
{BP_TURBO_MODE, "TurboMode", true, "Boost speed when holding key1 or right mouse button"},
+ {BP_EXACT_DYNAMICS, "ExactDynamics", false, "Use exact computation of per-frame movement (slower)"},
{BP_AUTOCALIBRATE, "Autocalibrate", false, "Automatically learn TargetOffset e.g. gazetracking"},
{BP_REMAP_XTREME, "RemapXtreme", false, "Pointer at extreme Y translates more and zooms less"},
{BP_LM_DICTIONARY, "Dictionary", true, "Whether the word-based language model uses a dictionary"},
@@ -143,6 +144,7 @@ const lp_table longparamtable[] = {
{LP_MARGIN_WIDTH, "MarginWidth", 300, "Width of RHS margin (in Dasher co-ords)"},
#endif
{LP_TARGET_OFFSET, "TargetOffset", 0, "Vertical distance between mouse pointer and target (400=screen height)"},
+ {LP_X_LIMIT_SPEED, "XLimitSpeed", 800, "X Co-ordinate at which maximum speed is reached (<2048=xhair)"},
{LP_GAME_HELP_DIST, "GameHelpDistance", 1920, "Distance of sentence from center to decide user needs help"},
{LP_GAME_HELP_TIME, "GameHelpTime", 0, "Time for which user must need help before help drawn"},
};
diff --git a/Src/DasherCore/Parameters.h b/Src/DasherCore/Parameters.h
index fd4be5b..7114a48 100644
--- a/Src/DasherCore/Parameters.h
+++ b/Src/DasherCore/Parameters.h
@@ -32,7 +32,7 @@ enum {
BP_SHOW_SLIDER, BP_START_MOUSE,
BP_START_SPACE, BP_STOP_IDLE, BP_CONTROL_MODE,
BP_COLOUR_MODE, BP_MOUSEPOS_MODE,
- BP_PALETTE_CHANGE, BP_TURBO_MODE,
+ BP_PALETTE_CHANGE, BP_TURBO_MODE, BP_EXACT_DYNAMICS,
BP_AUTOCALIBRATE, BP_REMAP_XTREME,
BP_LM_DICTIONARY,
BP_LM_LETTER_EXCLUSION, BP_AUTO_SPEEDCONTROL,
@@ -64,7 +64,7 @@ enum {
LP_DYNAMIC_BUTTON_LAG, LP_STATIC1B_TIME, LP_STATIC1B_ZOOM,
LP_DEMO_SPRING, LP_DEMO_NOISE_MEM, LP_DEMO_NOISE_MAG, LP_MAXZOOM,
LP_DYNAMIC_SPEED_INC, LP_DYNAMIC_SPEED_FREQ, LP_DYNAMIC_SPEED_DEC,
- LP_TAP_TIME, LP_MARGIN_WIDTH, LP_TARGET_OFFSET,
+ LP_TAP_TIME, LP_MARGIN_WIDTH, LP_TARGET_OFFSET, LP_X_LIMIT_SPEED,
LP_GAME_HELP_DIST, LP_GAME_HELP_TIME,
END_OF_LPS
};
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]