3 // Copyright (c) 2008 The Dasher Team
5 // This file is part of Dasher.
7 // Dasher is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
12 // Dasher is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with Dasher; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "../Common/Common.h"
27 #include "DasherModel.h"
28 #include "DasherView.h"
29 #include "Parameters.h"
32 #include "NodeCreationManager.h"
33 #include "AlphabetManager.h"
35 using namespace Dasher
;
38 // Track memory leaks on Windows to the line that new'd the memory
40 #ifdef _DEBUG_MEMLEAKS
41 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
44 static char THIS_FILE
[] = __FILE__
;
49 //If preprocessor variable DEBUG_DYNAMICS is defined, will display the difference
50 // between computed (approximate) one-step motion, and ideal/exact motion (using pow()).
51 //#define DEBUG_DYNAMICS
53 CDasherModel::CDasherModel() {
55 m_pLastOutput
= m_Root
= NULL
;
62 // TODO: Need to rationalise the require conversion methods
64 m_bRequireConversion
= true;
66 m_bRequireConversion
= false;
71 m_Rootmin_min
= int64_min
/ NORMALIZATION
/ 2;
72 m_Rootmax_max
= int64_max
/ NORMALIZATION
/ 2;
76 CDasherModel::~CDasherModel() {
77 if(oldroots
.size() > 0) {
80 // At this point we have also deleted the root - so better NULL pointer
88 void CDasherModel::Make_root(CDasherNode
*pNewRoot
) {
89 // std::cout << "Make root" << std::endl;
91 DASHER_ASSERT(pNewRoot
!= NULL
);
92 DASHER_ASSERT(pNewRoot
->Parent() == m_Root
);
94 m_Root
->DeleteNephews(pNewRoot
);
95 m_Root
->SetFlag(NF_COMMITTED
, true);
97 // TODO: Is the stack necessary at all? We may as well just keep the
98 // existing data structure?
99 oldroots
.push_back(m_Root
);
101 // TODO: tidy up conditional
102 while((oldroots
.size() > 10) && (!m_bRequireConversion
|| (oldroots
[0]->GetFlag(NF_CONVERTED
)))) {
103 oldroots
[0]->OrphanChild(oldroots
[1]);
105 oldroots
.pop_front();
107 DASHER_ASSERT(pNewRoot
->GetFlag(NF_SEEN
));
110 // Update the root coordinates, as well as any currently scheduled locations
111 const myint range
= m_Rootmax
- m_Rootmin
;
112 m_Rootmax
= m_Rootmin
+ (range
* m_Root
->Hbnd()) / NORMALIZATION
;
113 m_Rootmin
= m_Rootmin
+ (range
* m_Root
->Lbnd()) / NORMALIZATION
;
115 for(std::deque
<pair
<myint
,myint
> >::iterator
it(m_deGotoQueue
.begin()); it
!= m_deGotoQueue
.end(); ++it
) {
116 //Some of these co-ordinate pairs can be bigger than m_Rootmin_min - m_Rootmax_max,
117 // hence using unsigned type...
118 const uint64 r
= it
->second
- it
->first
;
119 it
->second
= it
->first
+ (r
* m_Root
->Hbnd()) / NORMALIZATION
;
120 it
->first
+= (r
* m_Root
->Lbnd()) / NORMALIZATION
;
124 bool CDasherModel::Reparent_root() {
125 DASHER_ASSERT(m_Root
!= NULL
);
127 // Change the root node to the parent of the existing node. We need
128 // to recalculate the coordinates for the "new" root as the user may
129 // have moved around within the current root
130 CDasherNode
*pNewRoot
;
132 if(oldroots
.size() == 0) {
133 pNewRoot
= m_Root
->RebuildParent();
134 // Fail if there's no existing parent and no way of recreating one
135 if(pNewRoot
== NULL
) return false;
136 //better propagate gameness backwards, the original nodes must have been NF_GAME too
137 if (m_Root
->GetFlag(NF_GAME
))
138 for (CDasherNode
*pTemp
=pNewRoot
; pTemp
; pTemp
=pTemp
->Parent())
139 pTemp
->SetFlag(NF_GAME
, true);
140 //RebuildParent() can create multiple generations of parents at once;
141 // make sure our cache has all such that were created, so we delete them
142 // if we ever delete all our other nodes.
143 for (CDasherNode
*pTemp
= pNewRoot
; (pTemp
= pTemp
->Parent()); )
144 oldroots
.push_front(pTemp
);
147 pNewRoot
= oldroots
.back();
151 DASHER_ASSERT(m_Root
->Parent() == pNewRoot
);
153 const myint
lower(m_Root
->Lbnd()), upper(m_Root
->Hbnd());
154 const myint
iRange(upper
-lower
);
155 myint
iRootWidth(m_Rootmax
- m_Rootmin
);
157 // Fail if the new root is bigger than allowed by normalisation
158 if(((myint(NORMALIZATION
- upper
) / static_cast<double>(iRange
)) >
159 (m_Rootmax_max
- m_Rootmax
)/static_cast<double>(iRootWidth
)) ||
160 ((myint(lower
) / static_cast<double>(iRange
)) >
161 (m_Rootmin
- m_Rootmin_min
) / static_cast<double>(iRootWidth
))) {
162 //but cache the (currently-unusable) root node - else we'll keep recreating (and deleting) it on every frame...
163 oldroots
.push_back(pNewRoot
);
167 //don't uncommit until they reverse out of the node
168 // (or committing would enter the node into the LM a second time)
170 //Update the root coordinates to reflect the new root
171 DASHER_ASSERT(pNewRoot
->GetFlag(NF_SEEN
));
174 m_Rootmax
= m_Rootmax
+ ((NORMALIZATION
- upper
) * iRootWidth
) / iRange
;
175 m_Rootmin
= m_Rootmin
- (lower
* iRootWidth
) / iRange
;
177 for(std::deque
<pair
<myint
,myint
> >::iterator
it(m_deGotoQueue
.begin()); it
!= m_deGotoQueue
.end(); ++it
) {
178 iRootWidth
= it
->second
- it
->first
;
179 it
->second
+= (myint(NORMALIZATION
- upper
) * iRootWidth
/ iRange
);
180 it
->first
-= (myint(lower
) * iRootWidth
/ iRange
);
185 void CDasherModel::ClearRootQueue() {
186 while(oldroots
.size() > 0) {
187 if(oldroots
.size() > 1) {
188 oldroots
[0]->OrphanChild(oldroots
[1]);
191 oldroots
[0]->OrphanChild(m_Root
);
194 oldroots
.pop_front();
198 void CDasherModel::SetNode(CDasherNode
*pNewRoot
) {
206 // Create children of the root...
209 // Set the root coordinates so that the root node is an appropriate
210 // size and we're not in any of the children
211 m_Root
->SetFlag(NF_SEEN
, true); //(but we are in the node itself)
212 m_pLastOutput
= m_Root
;
214 double dFraction( 1 - (1 - m_Root
->MostProbableChild() / static_cast<double>(NORMALIZATION
)) / 2.0 );
216 //TODO somewhere round here, old code checked whether the InputFilter implemented
217 // GetMinWidth, if so called LimitRoot w/that width - i.e., make sure iWidth
218 // is no more than that minimum. Should we do something similar here???
220 int iWidth( static_cast<int>( MAX_Y
/ (2.0*dFraction
) ) );
222 m_Rootmin
= MAX_Y
/ 2 - iWidth
/ 2;
223 m_Rootmax
= MAX_Y
/ 2 + iWidth
/ 2;
226 int CDasherModel::GetOffset() {
227 return m_pLastOutput
? m_pLastOutput
->offset()+1 : m_Root
? m_Root
->offset()+1 : 0;
230 CDasherNode
*CDasherModel::Get_node_under_crosshair() {
231 return m_pLastOutput
;
234 bool CDasherModel::NextScheduledStep()
236 if (m_deGotoQueue
.size() == 0) return false;
237 myint
newRootmin(m_deGotoQueue
.front().first
), newRootmax(m_deGotoQueue
.front().second
);
238 m_deGotoQueue
.pop_front();
240 m_dTotalNats
+= log((newRootmax
- newRootmin
) / static_cast<double>(m_Rootmax
- m_Rootmin
));
242 m_iDisplayOffset
= (m_iDisplayOffset
* 90) / 100;
244 // Now actually zoom to the new location
246 while (newRootmax
>= m_Rootmax_max
|| newRootmin
<= m_Rootmin_min
) {
247 // can't make existing root any bigger because of overflow. So force a new root
248 // to be chosen (so that Dasher doesn't just stop!)...
250 //pick _child_ covering crosshair...
251 const myint
iWidth(m_Rootmax
-m_Rootmin
);
252 for (CDasherNode::ChildMap::const_iterator it
= m_Root
->GetChildren().begin(); ;) {
253 CDasherNode
*pChild(*it
);
254 DASHER_ASSERT(m_Rootmin
+ ((pChild
->Lbnd() * iWidth
) / NORMALIZATION
) <= ORIGIN_Y
);
255 if (m_Rootmin
+ ((pChild
->Hbnd() * iWidth
) / NORMALIZATION
) > ORIGIN_Y
) {
256 //found child to make root. proceed only if new root is on the game path....
257 if (m_Root
->GetFlag(NF_GAME
) && !pChild
->GetFlag(NF_GAME
)) {
258 //If the user's strayed that far off the game path,
259 // having Dasher stop seems reasonable!
263 //make pChild the root node...
264 //first we're gonna have to force it to be output, as a non-output root won't work...
265 if (!pChild
->GetFlag(NF_SEEN
)) {
266 DASHER_ASSERT(m_pLastOutput
== m_Root
);
269 //we need to update the target coords (newRootmin,newRootmax)
270 // to reflect the new coordinate system based upon pChild as root.
271 //Make_root automatically updates any such pairs stored in m_deGotoQueue, so:
272 m_deGotoQueue
.push_back(pair
<myint
,myint
>(newRootmin
,newRootmax
));
273 //...when we make pChild the root...
275 //...we can retrieve new, equivalent, coordinates for it
276 newRootmin
= m_deGotoQueue
.back().first
; newRootmax
= m_deGotoQueue
.back().second
;
277 m_deGotoQueue
.pop_back();
278 // (note that the next check below will make sure these coords do cover (0, ORIGIN_Y))
282 DASHER_ASSERT(it
!= m_Root
->GetChildren().end()); //must find a child!
286 // Check that we haven't drifted too far. The rule is that we're not
287 // allowed to let the root max and min cross the midpoint of the
289 newRootmin
= min(newRootmin
, ORIGIN_Y
- 1 - m_iDisplayOffset
);
290 newRootmax
= max(newRootmax
, ORIGIN_Y
+ 1 - m_iDisplayOffset
);
292 // Only allow the update if it won't make the
293 // root too small. We should have re-generated a deeper root
294 // before now already, but the original root is an exception.
295 // (as is trying to go back beyond the earliest char in the current
296 // alphabet, if there are preceding characters not in that alphabet)
297 if ((newRootmax
- newRootmin
) > MAX_Y
/ 4) {
298 m_Rootmax
= newRootmax
;
299 m_Rootmin
= newRootmin
;
301 } //else, we just stop - this prevents the user from zooming too far back
302 //outside the root node (when we can't generate an older root). return true;
306 ///A very approximate square root. Finds the square root of (just) the
307 /// most significant bit, then two iterations of Newton.
308 inline dasherint
mysqrt(dasherint in
) {
309 //1. Find greatest i satisfying 1<<(i<<1) < in; let rt = 1<<i be first approx
310 // but find by binary chop: at first double each time..
312 while (dasherint(1)<<4*i
< in
) i
*=2;
313 //then try successively smaller bits.
314 for (dasherint test
=i
; test
/=2;)
315 if (dasherint(1)<<2*(i
+test
) < in
) i
+=test
;
318 rt
= (rt
+in
/rt
)/2;//better
319 return (rt
+in
/rt
)/2;//better still
321 //Some empirical results (from DEBUG_DYNAMICS, at about 40fps with XLimit=400)
322 // with one iteration, error in rate of data entry is ~~10% near xhair, falls
323 // as we get further away, then abruptly jumps up to 30% near the x limit
324 // (and beyond it, but also before reaching it).
325 //With two iterations, error is 0-1% near xhair, gradually rising to 10%
326 // near/at the x limit.
327 //However, reversing is less good - it can go twice as fast at extreme x...
330 void CDasherModel::ScheduleOneStep(dasherint y1
, dasherint y2
, int nSteps
, int limX
, bool bExact
) {
332 m_deGotoQueue
.clear();
334 // Rename for readability.
335 const dasherint R1
= m_Rootmin
;
336 const dasherint R2
= m_Rootmax
;
338 // Calculate the bounds of the root node when the target range y1-y2
339 // fills the viewport.
340 // This is where we want to be in iSteps updates
341 dasherint targetRange
=y2
-y1
;
343 const dasherint r1
= MAX_Y
*(R1
-y1
)/targetRange
;
344 const dasherint r2
= MAX_Y
*(R2
-y1
)/targetRange
;
346 dasherint m1
=(r1
-R1
),m2
=(r2
-R2
);
348 //Any interpolation (R1,R2) + alpha*(m1,m2) moves along the correct path.
349 // Just have to decide how far, i.e. what alpha.
351 //Possible schemes (using rw=r2-r1, Rw=R2-R1)
352 // (Note: if y2-y1 == MAX_Y, alpha=1/nSteps is correct, and in some schemes must be a special case)
353 // alpha = (pow(rw/Rw,1/nSteps)-1)*rW / (rw-Rw) : correct/ideal, but uses pow
354 // alpha = 1/nSteps : moves forwards too fast, reverses too slow (correct for translation)
355 // 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.
356 // alpha = (y2-y1) / (MAX_Y*(nSteps-1) + y2-y1) : too slow forwards, reverses too quick
358 // alpha = sqrt(y2-y1) / (sqrt(MAX_Y)*(nSteps-1) + sqrt(y2-y1))
359 // with approx sqrt on y2-y1
360 //this is pretty good going forwards, but reverses faster than the ideal, on the order of 2*
362 if (targetRange
< 2*limX
) {
363 #ifdef DEBUG_DYNAMICS
365 const dasherint Rw
=R2
-R1
, rw
=r2
-r1
;
366 dasherint apsq
= mysqrt(y2
-y1
);
367 dasherint denom
= 64*(nSteps
-1) + apsq
;
368 dasherint nw
= (rw
*apsq
+ Rw
*64*(nSteps
-1))/denom
;
369 double bits
= (log(nw
) - log(Rw
))/log(2);
370 std::cout
<< "Too fast at X " << (y2
-y1
)/2 << ": would enter " << bits
<< "b = " << (bits
*nSteps
) << " in " << nSteps
<< "steps; will now enter ";
373 //atm we have Rw=R2-R1, rw=r2-r1 = Rw*MAX_Y/targetRange, (m1,m2) to take us there
375 //if targetRange were = 2*limX, we'd have rw' = Rw*MAX_Y/2*limX < rw
376 //the movement necessary to take us to rw', rather than rw, is thus:
377 // (m1',m2') = (m1,m2) * (rw' - Rw) / (rw-Rw) => scale m1,m2 by (rw'-Rw)/(rw-Rw)
378 // = (Rw*MAX_Y/(2*limX) - Rw)/(Rw*MAX_Y/targetRange-Rw)
379 // = (MAX_Y/(2*limX)-1) / (MAX_Y/targetRange-1)
380 // = (MAX_Y-(2*limX))/(2*limX) / ((MAX_Y-targetRange)/targetRange)
381 // = (MAX_Y-(2*limX)) / (2*limX) * targetRange / (MAX_Y-targetRange)
383 const dasherint n
=targetRange
*(MAX_Y
-2*limX
), d
=(MAX_Y
-targetRange
)*2*limX
;
384 bool bOver
=max(abs(m1
),abs(m2
))>std::numeric_limits
<dasherint
>::max()/n
;
386 //std::cout << "Overflow in max-speed-limit " << m1 << "," << m2 << " =wd> " << ((m1*n)/d) << "," << ((m2*n)/d);
387 //so do it a harder way, but which uses smaller intermediates:
388 // (Yes, this is valid even if !bOver. Could use it all the time?)
389 m1
= (m1
/d
)*n
+ ((m1
% d
) * n
) / d
;
390 m2
= (m2
/d
)*n
+ ((m2
% d
) * n
) / d
;
391 //std::cout << " => " << m1 << "," << m2 << std::endl;
397 //then make the stepping function, which follows, behave as if we were at limX:
401 #ifndef DEBUG_DYNAMICS
403 //#else, for DEBUG_DYNAMICS, we compute the exact movement either way, to compare.
406 if (targetRange
== MAX_Y
) {
409 double tr(targetRange
);
410 //expansion factor (of root node) for one step, post-speed-limit
411 double eFac
= pow(MAX_Y
/tr
,1.0/nSteps
);
412 //fraction of way along linear interpolation Rw->rw that yields that width:
413 // = (Rw*eFac - Rw) / (rw-Rw)
414 // = Rw * (eFac-1.0) / (Rw*MAX_Y/tr-Rw)
415 // = (eFac - 1.0) / (MAX_Y/tr - 1.0)
416 frac
= (eFac
-1.0) / (MAX_Y
/tr
- 1.0);
418 #ifdef DEBUG_DYNAMICS
419 const dasherint m1t
=m1
*frac
, m2t
=m2
*frac
; //keep original m1,m2 to compare
422 } else //conditional - only do one of exact/approx
424 { //begin block A (regardless of #ifdef)
426 //approximate dynamics: interpolate
427 // apsq parts rw to 64*(nSteps-1) parts Rw
428 // (no need to compute target width)
429 dasherint apsq
= mysqrt(targetRange
);
430 dasherint denom
= 64*(nSteps
-1) + apsq
;
432 // so new width nw = (64*(nSteps-1)*Rw + apsq*rw)/denom
433 // = Rw*(64*(nSteps-1) + apsq*MAX_Y/targetRange)/denom
434 m1
= (m1
*apsq
)/denom
, m2
=(m2
*apsq
)/denom
;
435 #ifdef DEBUG_DYNAMICS
436 std::cout
<< "Move " << m1
<< "," << m2
<< " should be " << m1t
<< "," << m2t
;
437 double dActualBits
= (log((R2
+m2
)-(R1
+m1
))-log(R2
-R1
))/log(2);
438 double dDesiredBits
= (log((R2
+m2t
)-(R1
+m1t
))-log(R2
-R1
))/log(2);
439 std::cout
<< " enters " << dActualBits
<< "b = " << (dActualBits
*nSteps
) << " in " << nSteps
<< "steps, should be "
440 << dDesiredBits
<< "=>" << (dDesiredBits
*nSteps
) << ", error " << int(abs(dDesiredBits
-dActualBits
)*100/dDesiredBits
) << "%" << std::endl
;
442 m1
=m1t
, m2
=m2t
; //overwrite approx values (we needed them somewhere!)
444 } //end block A (regardless of #ifdef)
446 m_deGotoQueue
.push_back(pair
<myint
,myint
>(R1
+m1
, R2
+m2
));
449 void CDasherModel::OutputTo(CDasherNode
*pNewNode
) {
450 //first, recurse back up to last seen node (must be processed ancestor-first)
451 if (pNewNode
&& !pNewNode
->GetFlag(NF_SEEN
)) {
452 OutputTo(pNewNode
->Parent());
454 m_pLastOutput
= pNewNode
;
456 pNewNode
->SetFlag(NF_SEEN
, true); //becomes NF_SEEN after output.
459 //either pNewNode is null, or else it's been seen. So delete back to that...
460 while (m_pLastOutput
!= pNewNode
) {
461 // if pNewNode is null, m_pLastOutput is not; else, pNewNode has been seen,
462 // so we should encounter it on the way back out to the root, _before_ null
463 m_pLastOutput
->SetFlag(NF_COMMITTED
, false);
464 m_pLastOutput
->Undo();
465 m_pLastOutput
->SetFlag(NF_SEEN
, false);
467 m_pLastOutput
= m_pLastOutput
->Parent();
468 DASHER_ASSERT(m_pLastOutput
|| !pNewNode
); //if m_pLastOutput null, then pNewNode is too.
473 void CDasherModel::ExpandNode(CDasherNode
*pNode
) {
474 DASHER_ASSERT(pNode
!= NULL
);
476 // TODO: Is NF_ALLCHILDREN any more useful/efficient than reading the map size?
478 if(pNode
->GetFlag(NF_ALLCHILDREN
)) {
479 DASHER_ASSERT(pNode
->GetChildren().size() > 0);
483 // TODO: Do we really need to delete all of the children at this point?
484 pNode
->Delete_children(); // trial commented out - pconlon
487 unsigned int iExpect
= pNode
->ExpectedNumChildren();
489 pNode
->PopulateChildren();
491 if (iExpect
!= pNode
->GetChildren().size()) {
492 std::cout
<< "(Note: expected " << iExpect
<< " children, actually created " << pNode
->GetChildren().size() << ")" << std::endl
;
496 pNode
->SetFlag(NF_ALLCHILDREN
, true);
498 DispatchEvent(pNode
);
501 void CDasherModel::RenderToView(CDasherView
*pView
, CExpansionPolicy
&policy
) {
503 DASHER_ASSERT(pView
!= NULL
);
504 DASHER_ASSERT(m_Root
!= NULL
);
506 while(pView
->IsSpaceAroundNode(m_Rootmin
,m_Rootmax
)) {
507 if (!Reparent_root()) break;
510 // The Render routine will fill iGameTargetY with the Dasher Coordinate of the
511 // youngest node with NF_GAME set. The model is responsible for setting NF_GAME on
512 // the appropriate Nodes.
513 CDasherNode
*pOutput
= pView
->Render(m_Root
, m_Rootmin
+ m_iDisplayOffset
, m_Rootmax
+ m_iDisplayOffset
, policy
);
518 while (CDasherNode
*pNewRoot
= m_Root
->onlyChildRendered
) {
520 //if only one child was rendered, no other child covers the screen -
521 // as no other child was onscreen at all!
522 for (CDasherNode::ChildMap::const_iterator it
= m_Root
->GetChildren().begin(); it
!= m_Root
->GetChildren().end(); it
++) {
523 DASHER_ASSERT(*it
== pNewRoot
|| !(*it
)->GetFlag(NF_SUPER
));
526 if (pNewRoot
->GetFlag(NF_SUPER
) &&
527 // Stay on the game path, if there is one (!)
528 (!m_Root
->GetFlag(NF_GAME
) || pNewRoot
->GetFlag(NF_GAME
))) {
536 void CDasherModel::ScheduleZoom(dasherint y1
, dasherint y2
, int nsteps
) {
538 m_deGotoQueue
.clear();
540 // Rename for readability.
541 const dasherint R1
= m_Rootmin
;
542 const dasherint R2
= m_Rootmax
;
544 const dasherint r1
= MAX_Y
*(m_Rootmin
-y1
)/(y2
-y1
);
545 const dasherint r2
= MAX_Y
*(m_Rootmax
-y1
)/(y2
-y1
);
547 //We're going to interpolate in steps whose size starts at nsteps
548 // and decreases by one each time - so cumulatively:
549 // <nsteps> <2*nsteps-1> <3*nsteps-3> <4*nsteps-6>
550 // (until the next value is the same as the previous)
551 //These will sum to / reach (triangular number formula):
552 const int max((nsteps
*(nsteps
+1))/2);
554 const myint
oh(R2
-R1
), nh(r2
-r1
);
555 //log(the amount by which we wish to multiply the height):
556 const double logHeightMul(nh
==oh
? 0 : log(nh
/static_cast<double>(oh
)));
557 for (int s
= nsteps
; nsteps
>1; s
+=(--nsteps
)) {
558 double dFrac
; //(linear) fraction of way from oh to nh...
560 dFrac
= s
/static_cast<double>(max
);
562 //interpolate expansion logarithmically to get new height:
563 const double h(oh
*exp((logHeightMul
*s
)/max
));
564 //then treat that as a fraction of the way between oh to nh linearly
565 dFrac
= (h
-oh
)/(nh
-oh
);
567 //and use that fraction to interpolate from R to r
568 m_deGotoQueue
.push_back(pair
<myint
,myint
>(R1
+dFrac
*(r1
-R1
), R2
+dFrac
*(r2
-R2
)));
570 //final point, done accurately/simply:
571 m_deGotoQueue
.push_back(pair
<myint
,myint
>(r1
,r2
));
575 void CDasherModel::ClearScheduledSteps() {
576 m_deGotoQueue
.clear();
580 void CDasherModel::Offset(int iOffset
) {
581 m_Rootmin
+= iOffset
;
582 m_Rootmax
+= iOffset
;
584 m_iDisplayOffset
-= iOffset
;
587 void CDasherModel::AbortOffset() {
588 m_Rootmin
+= m_iDisplayOffset
;
589 m_Rootmax
+= m_iDisplayOffset
;
591 m_iDisplayOffset
= 0;
594 void CDasherModel::LimitRoot(int iMaxWidth
) {
595 m_Rootmin
= ORIGIN_Y
- iMaxWidth
/ 2;
596 m_Rootmax
= ORIGIN_Y
+ iMaxWidth
/ 2;