1 // DasherViewSquare.cpp
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"
24 #include "..\Win32\Common\WinCommon.h"
27 #include "DasherViewSquare.h"
28 #include "DasherView.h"
29 #include "DasherTypes.h"
31 #include "Observable.h"
38 using namespace Dasher
;
41 // Track memory leaks on Windows to the line that new'd the memory
43 #ifdef _DEBUG_MEMLEAKS
44 #define DEBUG_NEW new( _NORMAL_BLOCK, THIS_FILE, __LINE__ )
47 static char THIS_FILE
[] = __FILE__
;
51 // FIXME - quite a lot of the code here probably should be moved to
52 // the parent class (DasherView). I think we really should make the
53 // parent class less general - we're probably not going to implement
54 // anything which uses radically different co-ordinate transforms, and
55 // we can always override if necessary.
57 // FIXME - duplicated 'mode' code throught - needs to be fixed (actually, mode related stuff, Input2Dasher etc should probably be at least partially in some other class)
59 CDasherViewSquare::CDasherViewSquare(CSettingsUser
*pCreateFrom
, CDasherScreen
*DasherScreen
, Opts::ScreenOrientations orient
)
60 : CDasherView(DasherScreen
,orient
), CSettingsUserObserver(pCreateFrom
), m_Y1(4), m_Y2(0.95 * CDasherModel::MAX_Y
), m_Y3(0.05 * CDasherModel::MAX_Y
), m_bVisibleRegionValid(false) {
62 //Note, nonlinearity parameters set in SetScaleFactor
63 ScreenResized(DasherScreen
);
66 CDasherViewSquare::~CDasherViewSquare() {}
68 void CDasherViewSquare::SetOrientation(Opts::ScreenOrientations newOrient
) {
69 if (newOrient
== GetOrientation()) return;
70 CDasherView::SetOrientation(newOrient
);
71 m_bVisibleRegionValid
=false;
75 void CDasherViewSquare::HandleEvent(int iParameter
) {
81 m_bVisibleRegionValid
= false;
86 CDasherNode
*CDasherViewSquare::Render(CDasherNode
*pRoot
, myint iRootMin
, myint iRootMax
,
87 CExpansionPolicy
&policy
) {
88 DASHER_ASSERT(pRoot
!= 0);
93 VisibleRegion(iDasherMinX
, iDasherMinY
, iDasherMaxX
, iDasherMaxY
);
98 CDasherNode
*pOutput
= pRoot
->Parent();
100 // Blank the region around the root node:
101 if (GetLongParameter(LP_SHAPE_TYPE
)==0) { //disjoint rects, so go round root
102 if(iRootMin
> iDasherMinY
)
103 DasherDrawRectangle(iDasherMaxX
, iDasherMinY
, iDasherMinX
, iRootMin
, 0, -1, 0);
105 if(iRootMax
< iDasherMaxY
)
106 DasherDrawRectangle(iDasherMaxX
, iRootMax
, iDasherMinX
, iDasherMaxY
, 0, -1, 0);
108 //to left (greater Dasher X)
109 if (iRootMax
- iRootMin
< iDasherMaxX
)
110 DasherDrawRectangle(iDasherMaxX
, std::max(iRootMin
,iDasherMinY
), iRootMax
-iRootMin
, std::min(iRootMax
,iDasherMaxY
), 0, -1, 0);
113 DasherDrawRectangle(0, iDasherMinY
, iDasherMinX
, iDasherMaxY
, 0, -1, 0);
116 DisjointRender(pRoot
, iRootMin
, iRootMax
, NULL
, policy
, std::numeric_limits
<double>::infinity(), pOutput
);
118 //overlapping rects/shapes
120 //LEFT of Y axis, would be entirely covered by the root node parent (before we render root)
121 // (getColour() gives the right colour, even if pOutput is invisible - in that case it gives
122 // the colour of its parent)
123 DasherDrawRectangle(iDasherMaxX
, iDasherMinY
, 0, iDasherMaxY
, pOutput
->getColour(), -1, 0);
124 //RIGHT of Y axis, should be white.
125 DasherDrawRectangle(0, iDasherMinY
, iDasherMinX
, iDasherMaxY
, 0, -1, 0);
126 } else //easy case, whole screen is white (outside root node, e.g. when starting)
127 Screen()->DrawRectangle(0, 0, Screen()->GetWidth(), Screen()->GetHeight(), 0, -1, 0);
128 NewRender(pRoot
, iRootMin
, iRootMax
, NULL
, policy
, std::numeric_limits
<double>::infinity(), pOutput
);
131 // Labels are drawn in a second parse to get the overlapping right
132 for (vector
<CTextString
*>::iterator it
=m_DelayedTexts
.begin(), E
=m_DelayedTexts
.end(); it
!=E
; it
++)
134 m_DelayedTexts
.clear();
136 // Finally decorate the view
141 /// Draw text specified in Dasher co-ordinates. The position is
142 /// specified as two co-ordinates, intended to the be the corners of
143 /// the leading edge of the containing box.
145 CDasherViewSquare::CTextString
*CDasherViewSquare::DasherDrawText(myint iDasherMaxX
, myint iDasherMidY
, CDasherScreen::Label
*pLabel
, CTextString
*pParent
, int iColor
) {
148 Dasher2Screen(iDasherMaxX
, iDasherMidY
, x
, y
);
150 //compute font size...
151 int iSize
= GetLongParameter(LP_DASHER_FONTSIZE
);
153 const myint
iMaxY(CDasherModel::MAX_Y
);
154 if (Screen()->MultiSizeFonts() && iSize
>4) {
155 //font size maxes out at ((iMaxY*3)/2)+iMaxY)/iMaxY = 3/2*smallest
156 // which is reached when iDasherMaxX == iMaxY/2, i.e. the crosshair
157 iSize
= ((min(iDasherMaxX
*3,(iMaxY
*3)/2) + iMaxY
) * iSize
) / iMaxY
;
159 //old style fonts; ignore iSize passed-in.
160 myint iLeftTimesFontSize
= (iMaxY
- iDasherMaxX
)*iSize
;
161 if(iLeftTimesFontSize
< iMaxY
* 19/ 20)
163 else if(iLeftTimesFontSize
< iMaxY
* 159 / 160)
170 CTextString
*pRet
= new CTextString(pLabel
, x
, y
, iSize
, iColor
);
171 vector
<CTextString
*> &dest(pParent
? pParent
->m_children
: m_DelayedTexts
);
172 dest
.push_back(pRet
);
176 void CDasherViewSquare::DoDelayedText(CTextString
*pText
) {
178 //note that it'd be better to compute old-style font sizes here, or even after shunting
179 // across according to the aiMax array, but this needs Dasher co-ordinates, which were
180 // more easily available at CTextString creation time. If it really doesn't look as good,
181 // can put in extra calls to Screen2Dasher....
182 screenint
x(pText
->m_ix
), y(pText
->m_iy
);
183 pair
<screenint
,screenint
> textDims
=Screen()->TextSize(pText
->m_pLabel
, pText
->m_iSize
);
184 switch (GetOrientation()) {
185 case Dasher::Opts::LeftToRight
: {
186 screenint iRight
= x
+ textDims
.first
;
187 if (iRight
< Screen()->GetWidth()) {
188 Screen()->DrawString(pText
->m_pLabel
, x
, y
-textDims
.second
/2, pText
->m_iSize
, pText
->m_iColor
);
189 for (vector
<CTextString
*>::iterator it
= pText
->m_children
.begin(); it
!=pText
->m_children
.end(); it
++) {
190 CTextString
*pChild
=*it
;
191 pChild
->m_ix
= max(pChild
->m_ix
, iRight
);
192 DoDelayedText(pChild
);
194 pText
->m_children
.clear();
198 case Dasher::Opts::RightToLeft
: {
199 screenint iLeft
= x
-textDims
.first
;
201 Screen()->DrawString(pText
->m_pLabel
, iLeft
, y
-textDims
.second
/2, pText
->m_iSize
, pText
->m_iColor
);
202 for (vector
<CTextString
*>::iterator it
= pText
->m_children
.begin(); it
!=pText
->m_children
.end(); it
++) {
203 CTextString
*pChild
=*it
;
204 pChild
->m_ix
= min(pChild
->m_ix
, iLeft
);
207 pText
->m_children
.clear();
211 case Dasher::Opts::TopToBottom
: {
212 screenint iBottom
= y
+ textDims
.second
;
213 if (iBottom
< Screen()->GetHeight()) {
214 Screen()->DrawString(pText
->m_pLabel
, x
-textDims
.first
/2, y
, pText
->m_iSize
, pText
->m_iColor
);
215 for (vector
<CTextString
*>::iterator it
= pText
->m_children
.begin(); it
!=pText
->m_children
.end(); it
++) {
216 CTextString
*pChild
=*it
;
217 pChild
->m_iy
= max(pChild
->m_iy
, iBottom
);
220 pText
->m_children
.clear();
224 case Dasher::Opts::BottomToTop
: {
225 screenint iTop
= y
- textDims
.second
;
227 Screen()->DrawString(pText
->m_pLabel
, x
-textDims
.first
/2, iTop
, pText
->m_iSize
, pText
->m_iColor
);
228 for (vector
<CTextString
*>::iterator it
= pText
->m_children
.begin(); it
!=pText
->m_children
.end(); it
++) {
229 CTextString
*pChild
=*it
;
230 pChild
->m_iy
= min(pChild
->m_iy
, iTop
);
233 pText
->m_children
.clear();
243 CDasherViewSquare::CTextString::~CTextString() {
244 for (vector
<CTextString
*>::iterator it
= m_children
.begin(); it
!=m_children
.end(); it
++)
248 void CDasherViewSquare::TruncateTri(myint x
, myint y1
, myint y2
, myint midy1
, myint midy2
, int fillColor
, int outlineColor
, int lineWidth
) {
249 DASHER_ASSERT (y1
<=midy1
&& midy1
<=midy2
&& midy2
<=y2
);
250 myint iVisibleMinX
, iVisibleMaxX
, iVisibleMinY
, iVisibleMaxY
;
251 VisibleRegion(iVisibleMinX
, iVisibleMinY
, iVisibleMaxX
, iVisibleMaxY
);
253 myint
x1(x
), x2(x
); //(max)x-coords of the two lines
254 myint
tempx1(0),tempx2(0); //& min x-coords
255 //intersect y1's diagonal with screen
256 if (!ClipLineToVisible(x1
,midy1
,tempx1
,y1
)) {
257 //entirely offscreen....i.e. off top/bottom
258 //DASHER_ASSERT (midy1 < iVisibleMinY);//no, args undefined if returns false!
259 midy1
= y1
= iVisibleMinY
;
260 x1
= min(x1
, iVisibleMaxX
);
263 //intersect y2's diagonal with screen
264 if (!ClipLineToVisible(tempx2
, y2
, x2
, midy2
)) {
265 //entirely offscreen again, i.e. off bottom/top
266 midy2
= y2
= iVisibleMaxY
;
267 x2
= min(x2
, iVisibleMaxX
);
271 //both will be clipped to be <= iVisibleMaxX by above. If they are still
272 // unequal, one must have been further clipped by passing off top/bottom
273 // (i.e., the point of max x is off the top/off the bottom), in which case
274 // the other line is entirely offscreen:
275 DASHER_ASSERT(midy1
== midy2
); //point/line of max x has been removed
278 //(0,y1) - (x1,midy1) hit max-y edge of screen
279 //(0,y2) - (x2,midy2) entirely offscreen
280 DASHER_ASSERT(midy1
==iVisibleMaxY
&& y2
== midy2
);
283 // (0,y2) - (x2, midy2) hit min-y edge of screen
284 // (0,y1) - (x1,midy1) entirely offscreen
285 DASHER_ASSERT(midy2
== iVisibleMinY
&& y1
== midy1
);
289 // midy1,x1 is now start point
290 vector
<CDasherScreen::point
> pts(1);
291 Dasher2Screen(x1
, midy1
, pts
[0].x
, pts
[0].y
);
292 DasherLine2Screen(x1
, midy1
, tempx1
, y1
, pts
);
294 //did not reach y axis
295 DASHER_ASSERT(y1
== iVisibleMinY
);
296 pts
.push_back(CDasherScreen::point());
297 Dasher2Screen(0, iVisibleMinY
, pts
.back().x
, pts
.back().y
);
299 //that gets us to the min-y (y1) end of the line along the y-axis
301 //add line along y-axis...
302 pts
.push_back(CDasherScreen::point());
303 Dasher2Screen(0, y2
, pts
.back().x
, pts
.back().y
);
306 //y2's diagonal did not reach y-axis
307 DASHER_ASSERT(y2
== iVisibleMaxY
);
308 pts
.push_back(CDasherScreen::point());
309 Dasher2Screen(tempx2
, iVisibleMaxY
, pts
.back().x
, pts
.back().y
);
311 //and the diagonal part...
312 DasherLine2Screen(tempx2
, y2
, x2
, midy2
, pts
);
314 if (midy1
!= midy2
) {
315 //is the max-x extent a line, after cropping - i.e. handles both
316 // normal triangle (orig midy1==orig midy2) being cropped to screen edge,
317 // and trunc tri (orig midy1 < orig midy2, possibly cropped) cases
318 DASHER_ASSERT(x1
== x2
);
319 pts
.push_back(CDasherScreen::point());
320 Dasher2Screen(x1
, midy1
, pts
.back().x
, pts
.back().y
);
321 } else DASHER_ASSERT(pts
.back().x
== pts
[0].x
&& pts
.back().y
== pts
[0].y
);
323 CDasherScreen::point
*p_array
=new CDasherScreen::point
[pts
.size()];
324 for (unsigned int i
= 0; i
<pts
.size(); i
++) p_array
[i
] = pts
[i
];
325 Screen()->Polygon(p_array
, pts
.size(), fillColor
, outlineColor
, lineWidth
);
329 #define sq(X) ((X)*(X))
330 void CDasherViewSquare::Circle(myint Range
, myint y1
, myint y2
, int fCol
, int oCol
, int lWidth
) {
331 std::vector
<CDasherScreen::point
> pts
;
332 myint
cy((y1
+y2
)/2),r(Range
/2), x1
, x2
;
333 myint iDasherMinX
, iDasherMinY
, iDasherMaxX
, iDasherMaxY
;
334 VisibleRegion(iDasherMinX
, iDasherMinY
, iDasherMaxX
, iDasherMaxY
);
336 CDasherScreen::point p
;
337 //run along bottom edge...
338 if (y1
< iDasherMinY
) {
339 Dasher2Screen(0, iDasherMinY
, p
.x
, p
.y
);
341 //intersect with bottom edge
342 x1
= min(iDasherMaxX
, myint(sqrt(double(r
*r
- sq(cy
-iDasherMinY
)))));
347 Dasher2Screen(x1
,y1
,p
.x
,p
.y
);
351 if (y2
> iDasherMaxY
) {
353 x2
= min(iDasherMaxX
, myint(sqrt(double(r
*r
- sq(iDasherMaxY
-cy
)))));
354 Dasher2Screen(x2
, y2
=iDasherMaxY
, p
.x
, p
.y
);
355 //that's target point for end of curved section.
356 if (x2
==iDasherMaxX
&& x1
==iDasherMaxX
) {
357 //circle entirely covers screen
358 DASHER_ASSERT(y1
==iDasherMinY
);
359 DasherDrawRectangle(iDasherMaxX
, iDasherMinY
, 0, iDasherMaxY
, fCol
, oCol
, lWidth
);
362 //will also need final point at top-right (0,y2 in dasher coords)....
364 Dasher2Screen(x2
=0,y2
,p
.x
,p
.y
);
366 CircleTo(cy
,r
,y1
,x1
,y2
,x2
,p
,pts
, 2.0);
367 if (iDasherMaxY
== y2
) {
368 Dasher2Screen(0, iDasherMaxX
, p
.x
, p
.y
);
371 CDasherScreen::point
*p_array
= new CDasherScreen::point
[pts
.size()];
372 for (unsigned int i
=0; i
<pts
.size(); i
++) p_array
[i
] = pts
[i
];
373 Screen()->Polygon(p_array
, pts
.size(), fCol
, oCol
, lWidth
);
377 void CDasherViewSquare::CircleTo(myint cy
, myint r
, myint y1
, myint x1
, myint y3
, myint x3
, CDasherScreen::point dest
, vector
<CDasherScreen::point
> &pts
, double dXMul
) {
379 myint
x2(sqrt(double(sq(r
)-sq(cy
-y2
)))*dXMul
);
380 CDasherScreen::point mid
; //where midpoint of circle/arc should be
381 Dasher2Screen(x2
, y2
, mid
.x
, mid
.y
); //(except "midpoint" measured along y axis)
382 int lmx
=(pts
.back().x
+ dest
.x
)/2, lmy
= (pts
.back().y
+ dest
.y
)/2; //midpoint of straight line
383 if (abs(dest
.y
-pts
.back().y
)<2 || abs(mid
.x
-lmx
) + abs(mid
.y
-lmy
)<2) {
384 //okay, use straight line
387 CircleTo(cy
, r
, y1
, x1
, y2
, x2
, mid
, pts
, dXMul
);
388 CircleTo(cy
, r
, y2
, x2
, y3
, x3
, dest
, pts
, dXMul
);
393 void CDasherViewSquare::DasherSpaceArc(myint cy
, myint r
, myint x1
, myint y1
, myint x2
, myint y2
, int iColour
, int iLineWidth
) {
394 CDasherScreen::point p
;
396 Dasher2Screen(x1
, y1
, p
.x
, p
.y
);
397 vector
<CDasherScreen::point
> pts
;
399 //if circle goes behind crosshair and we want the point of max-x, force division into two sections with that point as boundary
400 if (r
>CDasherModel::ORIGIN_X
&& ((y1
< cy
) ^ (y2
< cy
))) {
401 Dasher2Screen(r
, cy
, p
.x
, p
.y
);
402 CircleTo(cy
, r
, y1
, x1
, cy
, r
, p
, pts
, 1.0);
405 Dasher2Screen(x2
, y2
, p
.x
, p
.y
);
406 CircleTo(cy
, r
, y1
, x1
, y2
, x2
, p
, pts
, 1.0);
407 CDasherScreen::point
*p_array
= new CDasherScreen::point
[pts
.size()];
408 for (unsigned int i
=0; i
<pts
.size(); i
++) p_array
[i
] = pts
[i
];
409 Screen()->Polyline(p_array
, pts
.size(), iLineWidth
, iColour
);
412 void CDasherViewSquare::Quadric(myint Range
, myint lowY
, myint highY
, int fillColor
, int outlineColour
, int lineWidth
) {
413 static const double RR2
=1.0/sqrt(2.0);
414 const int midY
=(lowY
+highY
)/2;
416 CDasherScreen::point p_array
[2*NUM_STEPS
+2];
417 myint minX
,maxX
,minY
,maxY
;
418 VisibleRegion(minX
, minY
, maxX
, maxY
);
420 myint
x1(0), y1(highY
), x2(Range
*RR2
),y2(highY
*RR2
+ midY
*(1.0-RR2
)), x3(Range
), y3(midY
);
421 for (int i
=0; i
<=NUM_STEPS
; i
++) {
422 double f
=i
/(double)NUM_STEPS
, of
= 1.0-f
;
423 Dasher2Screen(min(maxX
,myint(of
*of
*x1
+ 2.0*of
*f
*x2
+ f
*f
*x3
)), max(minY
,min(maxY
,myint(of
*of
*y1
+ 2.0*of
*f
*y2
+ f
*f
*y3
))), p_array
[i
].x
, p_array
[i
].y
);
427 myint
x1(Range
), y1(midY
), x2(Range
*RR2
), y2(lowY
*RR2
+ midY
*(1.0-RR2
)), x3(0), y3(lowY
);
428 for (int i
=0; i
<=NUM_STEPS
; i
++) {
429 double f
=i
/(double)NUM_STEPS
, of
= 1.0-f
;
430 Dasher2Screen(min(maxX
,myint(of
*of
*x1
+ 2.0*of
*f
*x2
+ f
*f
*x3
)),max(minY
,min(maxY
,myint(of
*of
*y1
+ 2.0*of
*f
*y2
+ f
*f
*y3
))), p_array
[i
+NUM_STEPS
+1].x
, p_array
[i
+NUM_STEPS
+1].y
);
434 Screen()->Polygon(p_array
, 2*NUM_STEPS
+2, fillColor
, outlineColour
, lineWidth
);
438 bool CDasherViewSquare::IsSpaceAroundNode(myint y1
, myint y2
) {
444 VisibleRegion(iVisibleMinX
, iVisibleMinY
, iVisibleMaxX
, iVisibleMaxY
);
445 const myint
maxX(y2
-y1
);
446 if ((maxX
< iVisibleMaxX
) || (y1
> iVisibleMinY
) || (y2
< iVisibleMaxY
))
447 return true; //space around sq => space around anything smaller!
449 //in theory, even if the crosshair is off-screen (!), anything spanning y1-y2 should cover it...
450 DASHER_ASSERT (CoversCrosshair(y2
-y1
, y1
, y2
));
452 switch (GetLongParameter(LP_SHAPE_TYPE
)) {
453 case 0: //non-overlapping rects
454 case 1: //overlapping rects
456 case 2: { //simple triangles
457 const myint
iMidY((y1
+y2
)/2);
458 return (iMidY
< iVisibleMaxY
&& (y2
-iVisibleMaxY
)*maxX
< iVisibleMaxX
*(y2
-iMidY
))
459 || (iMidY
> iVisibleMinY
&& (iVisibleMinY
-y1
)*maxX
< iVisibleMaxX
*(iMidY
-y1
));
461 case 3: { //truncated triangles
462 const myint
y113((y1
+y1
+y2
)/3), y123((y1
+y2
+y2
)/3);
463 return (y123
< iVisibleMaxY
&& (y2
-iVisibleMaxY
)*maxX
< iVisibleMaxX
*(y2
-y123
))
464 || (y113
> iVisibleMinY
&& (iVisibleMinY
-y1
)*maxX
< iVisibleMaxX
*(y123
-y1
));
467 //erm. seems hard. fall-through to circle, as it isn't far out -
468 // unfortunately it's not a conservative approximation, the circle
469 // covers the quadric not the other way around, so we'll say the
470 // circle covers the screen when the quadric doesn't :-(. However
471 // atm circles seem better generally so fixing quadrics is a low priority!
472 case 5: { //circle - or rather ellipse, x diameter is twice y diam, hence the *2 to normalize
473 const myint
iMidY((y1
+y2
)/2); //centerX=0, radius = maxX
474 const myint
maxYDiff(max(iVisibleMaxY
-iMidY
,iMidY
-iVisibleMinY
)*2);
475 return maxYDiff
*maxYDiff
+ iVisibleMaxX
*iVisibleMaxX
> maxX
*maxX
;
482 void CDasherViewSquare::DisjointRender(CDasherNode
*pRender
, myint y1
, myint y2
,
483 CTextString
*pPrevText
, CExpansionPolicy
&policy
, double dMaxCost
,
484 CDasherNode
*&pOutput
)
486 DASHER_ASSERT_VALIDPTR_RW(pRender
);
490 // Set the NF_SUPER flag if this node entirely frames the visual
497 VisibleRegion(iDasherMinX
, iDasherMinY
, iDasherMaxX
, iDasherMaxY
);
498 pRender
->SetFlag(NF_SUPER
, (y2
-y1
>= iDasherMaxX
) && (y1
<= iDasherMinY
) && (y2
>= iDasherMaxY
));
500 const int myColor
= pRender
->getColour();
502 if( pRender
->getLabel() )
504 const int textColor
= GetLongParameter(LP_OUTLINE_WIDTH
)<0 ? myColor
: 4;
505 myint ny1
= std::min(iDasherMaxY
, std::max(iDasherMinY
, y1
)),
506 ny2
= std::min(iDasherMaxY
, std::max(iDasherMinY
, y2
));
507 CTextString
*pText
= DasherDrawText(y2
-y1
, (ny1
+ny2
)/2, pRender
->getLabel(), pPrevText
, textColor
);
508 if (pRender
->bShove()) pPrevText
= pText
;
511 const myint
Range(y2
-y1
);
513 //Does node cover crosshair?
514 if (pOutput
== pRender
->Parent() && Range
> CDasherModel::ORIGIN_X
&& y1
< CDasherModel::ORIGIN_Y
&& y2
> CDasherModel::ORIGIN_Y
) {
516 if (pRender
->ChildCount()==0) {
517 //covers crosshair! forcibly populate, now!
518 policy
.ExpandNode(pRender
);
521 if (pRender
->ChildCount() == 0) {
522 //allow empty node to be expanded, it's big enough.
523 policy
.pushNode(pRender
, y1
, y2
, true, dMaxCost
);
524 //and render whole node in one go
525 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(y1
,iDasherMinY
),0, std::min(y2
,iDasherMaxY
), myColor
, -1, 0);
526 //fall through to draw outline
528 //Node has children. It can therefore be collapsed...however,
529 // we don't allow a node covering the crosshair to be collapsed
530 // (at best this'll mean there's nowhere useful to go forwards;
531 // at worst, all kinds of crashes trying to do text output!)
533 //No reason why we can't collapse a game mode node that's too small/offscreen
534 // - we've got its coordinates, and can recreate its children and set their
535 // NF_GAME flags appropriately when it becomes renderable again...
536 if (pRender
!= pOutput
)
537 dMaxCost
= policy
.pushNode(pRender
, y1
, y2
, false, dMaxCost
);
543 if (CDasherNode
*pChild
= pRender
->onlyChildRendered
)
545 //if child still covers screen, render _just_ it and return
546 myint newy1
= y1
+ (Range
* pChild
->Lbnd()) / CDasherModel::NORMALIZATION
;
547 myint newy2
= y1
+ (Range
* pChild
->Hbnd()) / CDasherModel::NORMALIZATION
;
548 if (newy1
< iDasherMinY
&& newy2
> iDasherMaxY
) {
549 //still covers entire screen. Parent should too...
550 DASHER_ASSERT(dMaxCost
== std::numeric_limits
<double>::infinity());
552 if (newy2
-newy1
< iDasherMaxX
) //fill in to it's left...
553 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(y1
,iDasherMinY
), newy2
-newy1
, std::min(y2
,iDasherMaxY
), myColor
, -1, 0);
554 DisjointRender(pChild
, newy1
, newy2
, pPrevText
,
555 policy
, dMaxCost
, pOutput
);
556 //leave pRender->onlyChildRendered set, so remaining children are skipped
559 pRender
->onlyChildRendered
= NULL
;
562 if (!pRender
->onlyChildRendered
) {
563 //render all children...
565 for(CDasherNode::ChildMap::const_iterator i
= pRender
->GetChildren().begin();
566 i
!= pRender
->GetChildren().end(); i
++) {
568 CDasherNode
*pChild
= *i
;
570 myint newy1
= y1
+ (Range
* pChild
->Lbnd()) / CDasherModel::NORMALIZATION
;
571 myint newy2
= y1
+ (Range
* pChild
->Hbnd()) / CDasherModel::NORMALIZATION
;
573 if (pChild
->GetFlag(NF_GAME
)) {
574 CGameNodeDrawEvent
evt(pChild
, newy1
, newy2
);
575 Observable
<CGameNodeDrawEvent
*>::DispatchEvent(&evt
);
577 //switch to "render just one child" mode if all others are off the screen,
578 //and if this _won't_ cause us to avoid rendering a game node...
579 if (newy1
< iDasherMinY
&& newy2
> iDasherMaxY
&& (!pRender
->GetFlag(NF_GAME
) || pChild
->GetFlag(NF_GAME
))) {
580 DASHER_ASSERT(dMaxCost
== std::numeric_limits
<double>::infinity());
581 pRender
->onlyChildRendered
= pChild
;
582 if (newy2
-newy1
< iDasherMaxX
)
583 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(y1
,iDasherMinY
), newy2
-newy1
, std::min(y2
,iDasherMaxY
), myColor
, -1, 0);
584 DisjointRender(pChild
, newy1
, newy2
, pPrevText
, policy
, dMaxCost
, pOutput
);
585 //ensure we don't blank over this child in "finishing off" the parent (!)
587 //all remaining children are offscreen. quickly delete, avoid recomputing ranges...
588 while ((++i
)!=pRender
->GetChildren().end())
589 if (!(*i
)->GetFlag(NF_SEEN
)) (*i
)->Delete_children();
591 } else if (newy2
-newy1
>= GetLongParameter(LP_MIN_NODE_SIZE
) //simple test if big enough
592 && newy1
<= iDasherMaxY
&& newy2
>= iDasherMinY
) //at least partly on screen
594 //child should be rendered!
595 //fill in to its left
596 DasherDrawRectangle(std::min(y2
-y1
,iDasherMaxX
), std::max(newy1
,iDasherMinY
), std::min(newy2
-newy1
,iDasherMaxX
), std::min(newy2
,iDasherMaxY
), myColor
, -1, 0);
598 if (std::max(lasty
,iDasherMinY
)<newy1
) //fill in interval above child up to the last drawn child
599 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(lasty
,iDasherMinY
),0, std::min(newy1
,iDasherMaxY
), myColor
, -1, 0);
601 DisjointRender(pChild
, newy1
, newy2
, pPrevText
, policy
, dMaxCost
, pOutput
);
603 // We get here if the node is too small to render or is off-screen.
604 // So, collapse it immediately.
605 if(!pChild
->GetFlag(NF_SEEN
))
606 pChild
->Delete_children();
609 //all children rendered.
610 if (lasty
<min(y2
,iDasherMaxY
)) {
611 // Finish off the drawing process, filling in any part of the parent below the last-rendered child
612 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(lasty
, iDasherMinY
), 0, std::min(y2
, iDasherMaxY
), myColor
, -1, 0);
615 //end rendering children, fall through to outline
617 // Lastly, draw the outline
618 if(GetLongParameter(LP_OUTLINE_WIDTH
) && pRender
->GetFlag(NF_VISIBLE
)) {
619 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(y1
,iDasherMinY
),0, std::min(y2
,iDasherMaxY
), -1, -1, abs(GetLongParameter(LP_OUTLINE_WIDTH
)));
623 bool CDasherViewSquare::CoversCrosshair(myint Range
, myint y1
, myint y2
) {
624 if (Range
> CDasherModel::ORIGIN_X
&& y1
< CDasherModel::ORIGIN_Y
&& y2
> CDasherModel::ORIGIN_Y
) {
625 switch (GetLongParameter(LP_SHAPE_TYPE
)) {
626 case 0: //Disjoint rectangles
629 case 2: { //Triangles
630 myint
iMidY((y1
+y2
)/2);
631 return (iMidY
> CDasherModel::ORIGIN_Y
)
632 ? ((CDasherModel::ORIGIN_Y
-y1
)*Range
) > (iMidY
- y1
) * CDasherModel::ORIGIN_X
633 : ((y2
-CDasherModel::ORIGIN_Y
)*Range
) > (y2
- iMidY
) * CDasherModel::ORIGIN_X
;
635 case 3: { //Truncated tris
636 myint
midy1((y1
+y1
+y2
)/3), midy2((y1
+y2
+y2
)/3);
637 if (midy1
> CDasherModel::ORIGIN_Y
) //(0,y1) - (Range,midy1)
638 return (CDasherModel::ORIGIN_Y
-y1
)*Range
> (midy1
- y1
) * CDasherModel::ORIGIN_X
;
639 if (midy2
> CDasherModel::ORIGIN_Y
) // (Range,midy1) - (Range,midy2)
641 return (y2
- CDasherModel::ORIGIN_Y
)*Range
> (y2
- midy2
) * CDasherModel::ORIGIN_X
;
644 case 4: //quadrics. We'll approximate with circles, as they're easier...
645 // however, note that the circle is bigger, so this'll output things
646 // too soon/aggressively :-(.
647 // (hence, fallthrough to:)
648 case 5: { //circles - actually ellipses, as x diameter is twice y diameter, hence the *4
649 const myint
y_dist(CDasherModel::ORIGIN_Y
- (y1
+y2
)/2);
650 return CDasherModel::ORIGIN_X
* CDasherModel::ORIGIN_X
+ y_dist
*y_dist
*4 < Range
*Range
;
657 void CDasherViewSquare::NewRender(CDasherNode
*pRender
, myint y1
, myint y2
,
658 CTextString
*pPrevText
, CExpansionPolicy
&policy
, double dMaxCost
,
659 CDasherNode
*&pOutput
)
661 //when we have only one child node to render, which'll be the last thing we
662 // do before returning, we make a tail call by jumping here, rather than
663 // pushing another stack frame:
665 DASHER_ASSERT_VALIDPTR_RW(pRender
);
669 // Set the NF_SUPER flag if this node entirely frames the visual
676 VisibleRegion(iDasherMinX
, iDasherMinY
, iDasherMaxX
, iDasherMaxY
);
677 pRender
->SetFlag(NF_SUPER
, !IsSpaceAroundNode(y1
, y2
));
679 const int myColor
= pRender
->getColour();
681 if( pRender
->getLabel() )
683 const int textColor
= GetLongParameter(LP_OUTLINE_WIDTH
)<0 ? myColor
: 4;
684 myint ny1
= std::min(iDasherMaxY
, std::max(iDasherMinY
, y1
)),
685 ny2
= std::min(iDasherMaxY
, std::max(iDasherMinY
, y2
));
686 CTextString
*pText
= DasherDrawText(y2
-y1
, (ny1
+ny2
)/2, pRender
->getLabel(), pPrevText
, textColor
);
687 if (pRender
->bShove()) pPrevText
= pText
;
690 const myint
Range(y2
-y1
);
691 // Draw node...we can both fill & outline in one go, since
692 // we're drawing the whole node at once (unlike disjoint-rects),
693 // as any part of the outline which is obscured by a child node,
694 // will have the outline of the child node painted over it,
695 // and all outlines are the same colour.
697 //"invisible" nodes are given same colour as parent, so we neither fill
698 // nor outline them. TODO this isn't quite right, as nodes that are
699 // _supposed_ to be the same colour as their parent, will have no outlines...
700 // (thankfully having 2 "phases" means this doesn't happen in standard
702 if (pRender
->GetFlag(NF_VISIBLE
)) {
703 //outline width 0 = fill only; >0 = fill + outline; <0 = outline only
704 int fillColour
= GetLongParameter(LP_OUTLINE_WIDTH
)>=0 ? myColor
: -1;
705 int lineWidth
= abs(GetLongParameter(LP_OUTLINE_WIDTH
));
706 switch (GetLongParameter(LP_SHAPE_TYPE
)) {
707 case 1: //overlapping rects
708 DasherDrawRectangle(std::min(Range
,iDasherMaxX
), std::max(y1
,iDasherMinY
), 0, std::min(y2
,iDasherMaxY
), fillColour
, -1, lineWidth
);
710 case 2: //simple triangles
711 TruncateTri(Range
, y1
, y2
, (y1
+y2
)/2, (y1
+y2
)/2, fillColour
, -1, lineWidth
);
713 case 3: //truncated triangles
714 TruncateTri(Range
, y1
, y2
, (y1
+y1
+y2
)/3, (y1
+y2
+y2
)/3, fillColour
, -1, lineWidth
);
717 Quadric(Range
, y1
, y2
, fillColour
, -1, lineWidth
);
720 Circle(Range
, y1
, y2
, fillColour
, -1, lineWidth
);
725 //Does node cover crosshair?
726 if (pOutput
== pRender
->Parent() && CoversCrosshair(Range
, y1
, y2
))
729 if (pRender
->ChildCount() == 0) {
730 if (pOutput
==pRender
) {
731 //covers crosshair! forcibly populate, now!
732 policy
.ExpandNode(pRender
);
734 //allow empty node to be expanded, it's big enough.
735 policy
.pushNode(pRender
, y1
, y2
, true, dMaxCost
);
736 return; //no children atm => nothing more to do
739 //Node has children. It can therefore be collapsed...however,
740 // we don't allow a node covering the crosshair to be collapsed
741 // (at best this'll mean there's nowhere useful to go forwards;
742 // at worst, all kinds of crashes trying to do text output!)
744 //No reason why we can't collapse a game mode node that's too small/offscreen
745 // - we've got its coordinates, and can recreate its children and set their
746 // NF_GAME flags appropriately when it becomes renderable again...
747 if (pRender
!= pOutput
)
748 dMaxCost
= policy
.pushNode(pRender
, y1
, y2
, false, dMaxCost
);
750 //Node has children - either it already did, or else it covers the crosshair,
751 // and we've just made them...so render them.
753 //first check if there's only one child we need to render
754 if (CDasherNode
*pChild
= pRender
->onlyChildRendered
) {
755 //if child still covers screen, render _just_ it and return
756 myint newy1
= y1
+ (Range
* pChild
->Lbnd()) / CDasherModel::NORMALIZATION
;
757 myint newy2
= y1
+ (Range
* pChild
->Hbnd()) / CDasherModel::NORMALIZATION
;
759 (newy1
< iDasherMinY
&& newy2
> iDasherMaxY
)) { //covers entire y-axis!
760 //render just that child; nothing more to do for this node => tail call to beginning
761 pRender
= pChild
; y1
=newy1
; y2
=newy2
;
764 pRender
->onlyChildRendered
= NULL
;
767 //ok, need to render all children...
768 myint newy1
=y1
,newy2
;
769 CDasherNode::ChildMap::const_iterator I
= pRender
->GetChildren().begin(), E
= pRender
->GetChildren().end();
771 CDasherNode
*pChild(*I
);
773 newy2
= y1
+ (Range
* pChild
->Hbnd()) / CDasherModel::NORMALIZATION
;
774 if (pChild
->GetFlag(NF_GAME
)) {
775 CGameNodeDrawEvent
evt(pChild
, newy1
, newy2
);
776 Observable
<CGameNodeDrawEvent
*>::DispatchEvent(&evt
);
778 if (newy1
<=iDasherMaxY
&& newy2
>= iDasherMinY
) { //onscreen
779 if (newy2
-newy1
> GetLongParameter(LP_MIN_NODE_SIZE
)) {
780 //definitely big enough to render.
781 NewRender(pChild
, newy1
, newy2
, pPrevText
, policy
, dMaxCost
, pOutput
);
782 } else if (!pChild
->GetFlag(NF_SEEN
)) pChild
->Delete_children();
783 if (newy2
>iDasherMaxY
&& !pRender
->GetFlag(NF_GAME
)) {
784 //remaining children offscreen and no game-mode child we might skip
785 // (among the remainder, or any previous off the top of the screen)
786 if (newy1
< iDasherMinY
) pRender
->onlyChildRendered
= pChild
; //previous children also offscreen!
787 break; //skip remaining children
794 //broke out of loop. Possibly more to delete...
795 while (++I
!=E
) if (!(*I
)->GetFlag(NF_SEEN
)) (*I
)->Delete_children();
797 //all children rendered.
800 /// Convert screen co-ordinates to dasher co-ordinates. This doesn't
801 /// include the nonlinear mapping for eyetracking mode etc - it is
802 /// just the inverse of the mapping used to calculate the screen
803 /// positions of boxes etc.
805 void CDasherViewSquare::Screen2Dasher(screenint iInputX
, screenint iInputY
, myint
&iDasherX
, myint
&iDasherY
) {
807 // Things we're likely to need:
809 screenint iScreenWidth
= Screen()->GetWidth();
810 screenint iScreenHeight
= Screen()->GetHeight();
812 switch(GetOrientation()) {
813 case Dasher::Opts::LeftToRight
:
814 iDasherX
= ( iScreenWidth
- iInputX
) * SCALE_FACTOR
/ iScaleFactorX
;
815 iDasherY
= CDasherModel::MAX_Y
/ 2 + ( iInputY
- iScreenHeight
/ 2 ) * SCALE_FACTOR
/ iScaleFactorY
;
817 case Dasher::Opts::RightToLeft
:
818 iDasherX
= myint( ( iInputX
) * SCALE_FACTOR
/ iScaleFactorX
);
819 iDasherY
= myint(CDasherModel::MAX_Y
/ 2 + ( iInputY
- iScreenHeight
/ 2 ) * SCALE_FACTOR
/ iScaleFactorY
);
821 case Dasher::Opts::TopToBottom
:
822 iDasherX
= myint( ( iScreenHeight
- iInputY
) * SCALE_FACTOR
/ iScaleFactorX
);
823 iDasherY
= myint(CDasherModel::MAX_Y
/ 2 + ( iInputX
- iScreenWidth
/ 2 ) * SCALE_FACTOR
/ iScaleFactorY
);
825 case Dasher::Opts::BottomToTop
:
826 iDasherX
= myint( ( iInputY
) * SCALE_FACTOR
/ iScaleFactorX
);
827 iDasherY
= myint(CDasherModel::MAX_Y
/ 2 + ( iInputX
- iScreenWidth
/ 2 ) * SCALE_FACTOR
/ iScaleFactorY
);
833 iDasherX
= ixmap(iDasherX
);
834 iDasherY
= iymap(iDasherY
);
838 void CDasherViewSquare::SetScaleFactor( void )
840 //Parameters for X non-linearity.
841 // Set some defaults here, in case we change(d) them later...
842 m_iXlogThres
=CDasherModel::MAX_Y
/2; //threshold: DasherX's less than this are linear; those greater are logarithmic
844 //set log scaling coefficient (unused if LP_NONLINEAR_X==0)
845 // note previous value = 1/0.2, i.e. a value of LP_NONLINEAR_X =~= 4.8
846 m_dXlogCoeff
= exp(GetLongParameter(LP_NONLINEAR_X
)/3.0);
848 const bool bHoriz(GetOrientation() == Dasher::Opts::LeftToRight
|| GetOrientation() == Dasher::Opts::RightToLeft
);
849 const screenint
iScreenWidth(Screen()->GetWidth()), iScreenHeight(Screen()->GetHeight());
850 const double dPixelsX(bHoriz
? iScreenWidth
: iScreenHeight
), dPixelsY(bHoriz
? iScreenHeight
: iScreenWidth
);
852 //Defaults/starting values, will be modified later according to scheme in use...
853 iMarginWidth
= GetLongParameter(LP_MARGIN_WIDTH
);
854 double dScaleFactorY(dPixelsY
/ CDasherModel::MAX_Y
);
855 double dScaleFactorX(dPixelsX
/ static_cast<double>(CDasherModel::MAX_Y
+ iMarginWidth
) );
857 switch (GetLongParameter(LP_GEOMETRY
)) {
860 if (dScaleFactorX
< dScaleFactorY
) {
861 //fewer (pixels per dasher coord) in X direction - i.e., X is more compressed.
862 //So, use X scale for Y too...except first, we'll _try_ to reduce the difference
863 // by changing the relative scaling of X and Y (by at most 20%):
864 double dMul
= max(0.8, dScaleFactorX
/ dScaleFactorY
);
865 dScaleFactorY
= std::max(dScaleFactorX
/dMul
, dScaleFactorY
/ 4.0);
866 dScaleFactorX
*= 0.9;
867 iMarginWidth
= (CDasherModel::MAX_Y
/20.0 + iMarginWidth
*0.95)/0.9;
869 //X has more room; use Y scale for both -> will get lots history
870 // however, "compensate" by relaxing the default "relative scaling" of X
871 // (normally only 90% of Y) towards 1...
872 double dXmpc
= std::min(1.0,0.9 * dScaleFactorX
/ dScaleFactorY
);
873 dScaleFactorX
= max(dScaleFactorY
, dScaleFactorX
/ 4.0)*dXmpc
;
874 iMarginWidth
= (iMarginWidth
+ dPixelsX
/dScaleFactorX
- CDasherModel::MAX_Y
)/2;
878 //all new styles fix the y axis the way we want it (i.e. leave as above),
879 // and just do different things with x...
881 //square with x-hair possibly offscreen
882 dScaleFactorX
= dScaleFactorY
;
886 //2 or 3 => squish x (so xhair always visible)
887 const double dDesiredXPerPixel( (CDasherModel::MAX_Y
+ iMarginWidth
) / dPixelsX
), dMinXPerPixel((CDasherModel::ORIGIN_X
+iMarginWidth
)/dPixelsX
);
888 const double dAspect(1.0/dScaleFactorY
/dDesiredXPerPixel
);
889 double dDasherXPerPixel( (dAspect
<1.0)
890 ? (dMinXPerPixel
+pow(dAspect
,3.0)*(dDesiredXPerPixel
-dMinXPerPixel
)) //tall+thin
891 : (1.0/dScaleFactorY
)); //square or wide+low
892 iMarginWidth
/= 0.9; //this comes from the old scaling by m_dXmpc=0.9. Drop in new scheme?
893 if (GetLongParameter(LP_GEOMETRY
)==3) {
894 //make whole screen logarithmic (but keep xhair in same place)
895 myint
crosshair(xmap(2048)); //should be 2048...
897 dDasherXPerPixel
*= xmap(2048)/static_cast<double>(crosshair
);
899 dScaleFactorX
= 0.9 / dDasherXPerPixel
;
902 iScaleFactorX
= myint(dScaleFactorX
* SCALE_FACTOR
);
903 iScaleFactorY
= myint(dScaleFactorY
* SCALE_FACTOR
);
907 for (screenint x
=0; x
<iScreenWidth
; x
++) {
909 Screen2Dasher(x
, 0, dx
, dy
);
911 Dasher2Screen(dx
, dy
, fx
, fy
);
913 std::cout
<< "ERROR ScreenX " << x
<< " becomes " << dx
<< " back to " << fx
<< std::endl
;;
915 for (screenint y
=0; y
<iScreenHeight
; y
++) {
917 Screen2Dasher(0, y
, dx
, dy
);
919 Dasher2Screen(dx
, dy
, fx
, fy
);
921 std::cout
<< "ERROR ScreenY " << y
<< " becomes " << dy
<< " back to " << fy
<< std::endl
;
925 //notify listeners that coordinates have changed...
926 Observable
<CDasherView
*>::DispatchEvent(this);
930 inline myint
CDasherViewSquare::CustomIDivScaleFactor(myint iNumerator
) {
931 // Integer division rounding away from zero
933 long long int num
, denom
, quot
, rem
;
937 denom
= SCALE_FACTOR
;
939 DASHER_ASSERT(denom
!= 0);
942 lldiv_t ans
= ::lldiv(num
, denom
);
960 // return (iNumerator + iDenominator - 1) / iDenominator;
963 void CDasherViewSquare::Dasher2Screen(myint iDasherX
, myint iDasherY
, screenint
&iScreenX
, screenint
&iScreenY
) {
965 // Apply the nonlinearities
967 iDasherX
= xmap(iDasherX
);
968 iDasherY
= ymap(iDasherY
);
970 // Things we're likely to need:
972 screenint iScreenWidth
= Screen()->GetWidth();
973 screenint iScreenHeight
= Screen()->GetHeight();
975 // Note that integer division is rounded *away* from zero here to
976 // ensure that this really is the inverse of the map the other way
979 switch( GetOrientation() ) {
980 case Dasher::Opts::LeftToRight
:
981 iScreenX
= screenint(iScreenWidth
-
982 CustomIDivScaleFactor(iDasherX
* iScaleFactorX
));
983 iScreenY
= screenint(iScreenHeight
/ 2 +
984 CustomIDivScaleFactor(( iDasherY
- CDasherModel::MAX_Y
/ 2 ) * iScaleFactorY
));
986 case Dasher::Opts::RightToLeft
:
987 iScreenX
= screenint(CustomIDivScaleFactor(iDasherX
* iScaleFactorX
));
988 iScreenY
= screenint(iScreenHeight
/ 2 +
989 CustomIDivScaleFactor( (iDasherY
- CDasherModel::MAX_Y
/2) * iScaleFactorY
));
991 case Dasher::Opts::TopToBottom
:
992 iScreenX
= screenint(iScreenWidth
/ 2 +
993 CustomIDivScaleFactor( (iDasherY
- CDasherModel::MAX_Y
/2) * iScaleFactorY
));
994 iScreenY
= screenint(iScreenHeight
-
995 CustomIDivScaleFactor( iDasherX
* iScaleFactorX
));
997 case Dasher::Opts::BottomToTop
:
998 iScreenX
= screenint(iScreenWidth
/ 2 +
999 CustomIDivScaleFactor(( iDasherY
- CDasherModel::MAX_Y
/2 ) * iScaleFactorY
));
1000 iScreenY
= screenint(CustomIDivScaleFactor( iDasherX
* iScaleFactorX
));
1007 void CDasherViewSquare::Dasher2Polar(myint iDasherX
, myint iDasherY
, double &r
, double &theta
) {
1008 iDasherX
= xmap(iDasherX
);
1009 iDasherY
= ymap(iDasherY
);
1011 myint iDasherOX
= xmap(CDasherModel::ORIGIN_X
);
1012 myint iDasherOY
= ymap(CDasherModel::ORIGIN_Y
);
1014 double x
= -(iDasherX
- iDasherOX
) / double(iDasherOX
); //Use normalised coords so min r works
1015 double y
= -(iDasherY
- iDasherOY
) / double(iDasherOY
);
1016 theta
= atan2(y
, x
);
1017 r
= sqrt(x
* x
+ y
* y
);
1020 void CDasherViewSquare::DasherLine2Screen(myint x1
, myint y1
, myint x2
, myint y2
, vector
<CDasherScreen::point
> &vPoints
) {
1021 if (x1
!=x2
&& y1
!=y2
) { //only diagonal lines ever get changed...
1022 if (GetBoolParameter(BP_NONLINEAR_Y
)) {
1023 if ((y1
< m_Y3
&& y2
> m_Y3
) ||(y2
< m_Y3
&& y1
> m_Y3
)) {
1024 //crosses bottom non-linearity border
1025 myint x_mid
= x1
+(x2
-x1
) * (m_Y3
-y1
)/(y2
-y1
);
1026 DasherLine2Screen(x1
, y1
, x_mid
, m_Y3
, vPoints
);
1028 }//else //no, a single line might cross _both_ borders!
1029 if ((y1
> m_Y2
&& y2
< m_Y2
) || (y2
> m_Y2
&& y1
< m_Y2
)) {
1030 //crosses top non-linearity border
1031 myint x_mid
= x1
+ (x2
-x1
) * (m_Y2
-y1
)/(y2
-y1
);
1032 DasherLine2Screen(x1
, y1
, x_mid
, m_Y2
, vPoints
);
1036 if (GetLongParameter(LP_NONLINEAR_X
) && (x1
> m_iXlogThres
|| x2
> m_iXlogThres
)) {
1037 //into logarithmic section
1038 CDasherScreen::point pStart
, pScreenMid
, pEnd
;
1039 Dasher2Screen(x2
, y2
, pEnd
.x
, pEnd
.y
);
1041 Dasher2Screen(x1
, y1
, pStart
.x
, pStart
.y
);
1042 //a straight line on the screen between pStart and pEnd passes through pScreenMid:
1043 pScreenMid
.x
= (pStart
.x
+ pEnd
.x
)/2;
1044 pScreenMid
.y
= (pStart
.y
+ pEnd
.y
)/2;
1045 //whereas a straight line _in_Dasher_space_ passes through pDasherMid:
1046 myint xMid
=(x1
+x2
)/2, yMid
=(y1
+y2
)/2;
1047 CDasherScreen::point pDasherMid
;
1048 Dasher2Screen(xMid
, yMid
, pDasherMid
.x
, pDasherMid
.y
);
1050 //since we know both endpoints are in the same section of the screen wrt. Y nonlinearity,
1051 //the midpoint along the DasherY axis of both lines should be the same.
1052 if (GetOrientation()==Dasher::Opts::LeftToRight
|| GetOrientation()==Dasher::Opts::RightToLeft
) {
1053 DASHER_ASSERT(abs(pDasherMid
.y
- pScreenMid
.y
)<=1);//allow for rounding error
1054 if (abs(pDasherMid
.x
- pScreenMid
.x
)<=1) break; //call a straight line accurate enough
1056 DASHER_ASSERT(abs(pDasherMid
.x
- pScreenMid
.x
)<=1);
1057 if (abs(pDasherMid
.y
- pScreenMid
.y
)<=1) break;
1059 //line should appear bent. Subdivide!
1060 DasherLine2Screen(x1
,y1
,xMid
,yMid
,vPoints
); //recurse for first half (to Dasher-space midpoint)
1061 if (x1
==xMid
|| y1
== yMid
) break; // as test on entry, only diagonal lines need to be bent...
1062 x1
=xMid
; y1
=yMid
; //& loop round for second half
1064 //broke out of loop. a straight line (x1,y1)-(x2,y2) on the screen is an accurate portrayal of a straight line in Dasher-space.
1065 vPoints
.push_back(pEnd
);
1068 //ok, not in x nonlinear section; fall through.
1071 CDasherScreen::point pTest
;
1072 Dasher2Screen(x1
, y1
, pTest
.x
, pTest
.y
);
1073 DASHER_ASSERT(vPoints
.back().x
== pTest
.x
&& vPoints
.back().y
== pTest
.y
);
1075 CDasherScreen::point p
;
1076 Dasher2Screen(x2
, y2
, p
.x
, p
.y
);
1077 vPoints
.push_back(p
);
1080 void CDasherViewSquare::VisibleRegion( myint
&iDasherMinX
, myint
&iDasherMinY
, myint
&iDasherMaxX
, myint
&iDasherMaxY
) {
1081 // TODO: Change output parameters to pointers and allow NULL to mean
1082 // 'I don't care'. Need to be slightly careful about this as it will
1083 // require a slightly more sophisticated caching mechanism
1085 if(!m_bVisibleRegionValid
) {
1087 switch( GetOrientation() ) {
1088 case Dasher::Opts::LeftToRight
:
1089 Screen2Dasher(Screen()->GetWidth(),0,m_iDasherMinX
,m_iDasherMinY
);
1090 Screen2Dasher(0,Screen()->GetHeight(),m_iDasherMaxX
,m_iDasherMaxY
);
1092 case Dasher::Opts::RightToLeft
:
1093 Screen2Dasher(0,0,m_iDasherMinX
,m_iDasherMinY
);
1094 Screen2Dasher(Screen()->GetWidth(),Screen()->GetHeight(),m_iDasherMaxX
,m_iDasherMaxY
);
1096 case Dasher::Opts::TopToBottom
:
1097 Screen2Dasher(0,Screen()->GetHeight(),m_iDasherMinX
,m_iDasherMinY
);
1098 Screen2Dasher(Screen()->GetWidth(),0,m_iDasherMaxX
,m_iDasherMaxY
);
1100 case Dasher::Opts::BottomToTop
:
1101 Screen2Dasher(0,0,m_iDasherMinX
,m_iDasherMinY
);
1102 Screen2Dasher(Screen()->GetWidth(),Screen()->GetHeight(),m_iDasherMaxX
,m_iDasherMaxY
);
1108 m_bVisibleRegionValid
= true;
1111 iDasherMinX
= m_iDasherMinX
;
1112 iDasherMaxX
= m_iDasherMaxX
;
1113 iDasherMinY
= m_iDasherMinY
;
1114 iDasherMaxY
= m_iDasherMaxY
;
1117 // void CDasherViewSquare::NewDrawGoTo(myint iDasherMin, myint iDasherMax, bool bActive) {
1118 // myint iHeight(iDasherMax - iDasherMin);
1132 // CDasherScreen::point p[4];
1134 // Dasher2Screen( 0, iDasherMin, p[0].x, p[0].y);
1135 // Dasher2Screen( iHeight, iDasherMin, p[1].x, p[1].y);
1136 // Dasher2Screen( iHeight, iDasherMax, p[2].x, p[2].y);
1137 // Dasher2Screen( 0, iDasherMax, p[3].x, p[3].y);
1139 // Screen()->Polyline(p, 4, iWidth, iColour);
1142 void CDasherViewSquare::ScreenResized(CDasherScreen
*NewScreen
) {
1143 m_bVisibleRegionValid
= false;