Updated German translation
[dasher.git] / Src / DasherCore / DasherModel.cpp
blob3bc9a22076f0f6d4d2861be78f074716a53925c3
1 // DasherModel.cpp
2 //
3 // Copyright (c) 2008 The Dasher Team
4 //
5 // This file is part of Dasher.
6 //
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"
23 #include <sstream>
25 #include <iostream>
26 #include <cstring>
27 #include "DasherModel.h"
28 #include "DasherView.h"
29 #include "Parameters.h"
31 #include "Event.h"
32 #include "NodeCreationManager.h"
33 #include "AlphabetManager.h"
35 using namespace Dasher;
36 using namespace std;
38 // Track memory leaks on Windows to the line that new'd the memory
39 #ifdef _WIN32
40 #ifdef _DEBUG_MEMLEAKS
41 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
42 #define new DEBUG_NEW
43 #undef THIS_FILE
44 static char THIS_FILE[] = __FILE__;
45 #endif
46 #endif
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;
57 m_Rootmin = 0;
58 m_Rootmax = 0;
59 m_iDisplayOffset = 0;
60 m_dTotalNats = 0.0;
62 // TODO: Need to rationalise the require conversion methods
63 #ifdef JAPANESE
64 m_bRequireConversion = true;
65 #else
66 m_bRequireConversion = false;
67 #endif
69 m_dAddProb = 0.003;
71 m_Rootmin_min = int64_min / NORMALIZATION / 2;
72 m_Rootmax_max = int64_max / NORMALIZATION / 2;
76 CDasherModel::~CDasherModel() {
77 if(oldroots.size() > 0) {
78 delete oldroots[0];
79 oldroots.clear();
80 // At this point we have also deleted the root - so better NULL pointer
81 m_Root = NULL;
82 } else {
83 delete m_Root;
84 m_Root = NULL;
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]);
104 delete oldroots[0];
105 oldroots.pop_front();
107 DASHER_ASSERT(pNewRoot->GetFlag(NF_SEEN));
108 m_Root = pNewRoot;
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);
146 else {
147 pNewRoot = oldroots.back();
148 oldroots.pop_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);
164 return false;
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));
172 m_Root = pNewRoot;
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);
182 return true;
185 void CDasherModel::ClearRootQueue() {
186 while(oldroots.size() > 0) {
187 if(oldroots.size() > 1) {
188 oldroots[0]->OrphanChild(oldroots[1]);
190 else {
191 oldroots[0]->OrphanChild(m_Root);
193 delete oldroots[0];
194 oldroots.pop_front();
198 void CDasherModel::SetNode(CDasherNode *pNewRoot) {
200 AbortOffset();
201 ClearRootQueue();
202 delete m_Root;
204 m_Root = pNewRoot;
206 // Create children of the root...
207 ExpandNode(m_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!
260 return false;
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);
267 OutputTo(pChild);
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...
274 Make_root(pChild);
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))
279 break;
281 ++it;
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
288 // screen.
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;
300 return true;
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;
303 return false;
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..
311 dasherint i=1;
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;
316 //so, first approx:
317 dasherint rt = 1<<i;
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
357 //We are using:
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 ";
372 #endif
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;
385 if (bOver) {
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;
392 } else {
393 m1 = (m1*n)/d;
394 m2 = (m2*n)/d;
397 //then make the stepping function, which follows, behave as if we were at limX:
398 targetRange=2*limX;
401 #ifndef DEBUG_DYNAMICS
402 if (bExact) {
403 //#else, for DEBUG_DYNAMICS, we compute the exact movement either way, to compare.
404 #endif
405 double frac;
406 if (targetRange == MAX_Y) {
407 frac=1.0/nSteps;
408 } else {
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
420 #else
421 m1*=frac; m2*=frac;
422 } else //conditional - only do one of exact/approx
423 #endif
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;
441 if (bExact)
442 m1=m1t, m2=m2t; //overwrite approx values (we needed them somewhere!)
443 #endif
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;
455 pNewNode->Output();
456 pNewNode->SetFlag(NF_SEEN, true); //becomes NF_SEEN after output.
458 } else {
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);
480 return;
483 // TODO: Do we really need to delete all of the children at this point?
484 pNode->Delete_children(); // trial commented out - pconlon
486 #ifdef DEBUG
487 unsigned int iExpect = pNode->ExpectedNumChildren();
488 #endif
489 pNode->PopulateChildren();
490 #ifdef DEBUG
491 if (iExpect != pNode->GetChildren().size()) {
492 std::cout << "(Note: expected " << iExpect << " children, actually created " << pNode->GetChildren().size() << ")" << std::endl;
494 #endif
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);
516 OutputTo(pOutput);
518 while (CDasherNode *pNewRoot = m_Root->onlyChildRendered) {
519 #ifdef DEBUG
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));
525 #endif
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))) {
529 Make_root(pNewRoot);
530 } else
531 break;
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);
553 //heights:
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...
559 if (nh==oh)
560 dFrac = s/static_cast<double>(max);
561 else {
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;