void MapWindow::DrawVisualGlide(LKSurface& Surface, const DiagrammStruct& sDia) { const RECT& rci = sDia.rc; unsigned short numboxrows = 1; #if BUGSTOP LKASSERT(Current_Multimap_SizeY < SIZE4); #endif switch (Current_Multimap_SizeY) { case SIZE0: case SIZE1: numboxrows = 3; break; case SIZE2: numboxrows = 2; break; case SIZE3: numboxrows = 1; break; case SIZE4: return; default: LKASSERT(0); break; } if (!ScreenLandscape) { numboxrows++; if (numboxrows > 3) numboxrows = 3; } TCHAR tmpT[30]; line1Font = LK8VisualTopFont; line2Font = LK8VisualBotFont; SIZE textSizeTop, textSizeBot; Surface.SelectObject(line1Font); _stprintf(tmpT, _T("MMMM")); Surface.GetTextSize(tmpT, &textSizeTop); Surface.SelectObject(line2Font); _stprintf(tmpT, _T("55.5%s 79%s%s "), Units::GetDistanceName(), MsgToken(2179), MsgToken(2183)); Surface.GetTextSize(tmpT, &textSizeBot); // we can cut the waypoint name, but not the value data, so we use the second row of data // to size the box for everything. maxtSizeX = textSizeBot.cx; int a = (rci.right-rci.left) / (textSizeBot.cx+BOXINTERVAL); int b = (rci.right-rci.left) - a * (textSizeBot.cx)-(BOXINTERVAL * (a + 1)); boxSizeX = textSizeBot.cx + (b / (a + 1)); boxSizeY = textSizeTop.cy + 1; // single line (wp name) + distance from bottombar if (numboxrows > 1) { boxSizeY += (textSizeBot.cy * (numboxrows - 1)) - NIBLSCALE(2); if (numboxrows > 2) boxSizeY -= NIBLSCALE(1); } #if DEBUG_SCR StartupStore(_T("boxX=%d boxY=%d \n"), boxSizeX, boxSizeY); #endif #if DEBUG_SCR StartupStore(_T("VG AREA LTRB: %d,%d %d,%d\n"), rci.left, rci.top, rci.right, rci.bottom); #endif const auto oldBrush = Surface.SelectObject(LKBrush_White); const auto oldPen = Surface.SelectObject(LK_BLACK_PEN); BrushReference brush_back; if (!INVERTCOLORS) { brush_back = LKBrush_Black; } else { brush_back = LKBrush_Nlight; } Surface.FillRect(&rci, brush_back); POINT center, p1, p2; center.y = rci.top + (rci.bottom - rci.top) / 2; center.x = rci.left + (rci.right - rci.left) / 2; // numSlotX is the number items we can print horizontally. unsigned short numSlotX = (rci.right - rci.left) / (boxSizeX + BOXINTERVAL); if (numSlotX > MAXBSLOT) numSlotX = MAXBSLOT; #if BUGSTOP LKASSERT(numSlotX > 0); #endif if (numSlotX == 0) return; unsigned short boxInterval = ((rci.right - rci.left)-(boxSizeX * numSlotX)) / (numSlotX + 1); unsigned short oddoffset = ( (rci.right-rci.left) - (boxSizeX * numSlotX) - boxInterval * (numSlotX + 1)) / 2; /* #if BUGSTOP // not really harmful LKASSERT(oddoffset<=boxInterval); #endif */ #if DEBUG_SCR StartupStore(_T("numSlotX=%d ScreenSizeX=%d boxSizeX=%d interval=%d offset=%d\n"), numSlotX, ScreenSizeX, boxSizeX, boxInterval, oddoffset); #endif unsigned int t; // The horizontal grid unsigned int slotCenterX[MAXBSLOT + 1]; for (t = 0; t < numSlotX; t++) { slotCenterX[t] = (t * boxSizeX) + boxInterval * (t + 1)+(boxSizeX / 2) + oddoffset+rci.left; #if DEBUG_SCR StartupStore(_T("slotCenterX[%d]=%d\n"), t, slotCenterX[t]); #endif } // Vertical coordinates of each up/down subwindow, excluding center line int upYtop = rci.top; #if MIDCENTER int upYbottom = center.y + (boxSizeY / 2); int downYtop = center.y - (boxSizeY / 2); #else int upYbottom = center.y - CENTERYSPACE; int downYtop = center.y + CENTERYSPACE; #endif int upSizeY = upYbottom - upYtop - (boxSizeY); ; int downYbottom = rci.bottom; int downSizeY = downYbottom - downYtop - (boxSizeY); ; #if 0 // Reassign dynamically the vertical scale for each subwindow size double vscale = 1000 * (100 - Current_Multimap_SizeY) / 100; #else // Set the vertical range double vscale; if (Units::GetUserAltitudeUnit() == unFeet) vscale = (1000 / TOFEET); else vscale = 300.0; #endif Surface.SetBackgroundTransparent(); RECT trc = rci; // Top part of visual rect, target is over us=unreachable=red trc.top = rci.top; trc.bottom = center.y - 1; #ifndef DITHER RenderSky(Surface, trc, RGB_WHITE, LKColor(150, 255, 150), GC_NO_COLOR_STEPS / 2); #else RenderSky(Surface, trc, RGB_WHITE, RGB_WHITE, GC_NO_COLOR_STEPS / 2); #endif // Bottom part, target is below us=reachable=green trc.top = center.y + 1; trc.bottom = rci.bottom; #ifndef DITHER RenderSky(Surface, trc, LKColor(255, 150, 150), RGB_WHITE, GC_NO_COLOR_STEPS / 2); #else RenderSky(Surface, trc, RGB_WHITE, RGB_WHITE,GC_NO_COLOR_STEPS / 2); #endif // Draw center line p1.x = rci.left + 1; p1.y = center.y; p2.x = rci.right - 1; p2.y = center.y; Surface.SelectObject(LKPen_Black_N1); Surface.DrawSolidLine(p1, p2, rci); #if DEBUG_SCR StartupStore(_T("... Center line: Y=%d\n"), center.y); #endif Surface.SelectObject(line1Font); Surface.SelectObject(LKPen_Black_N0); ResetVisualGlideGlobals(); short res = GetVisualGlidePoints(numSlotX); if (res == INVALID_VALUE) { #if DEBUG_DVG StartupStore(_T("...... GVGP says not ready, wait..\n")); #endif return; } if (res == 0) { #if DEBUG_DVG StartupStore(_T("...... GVGP says no data available!\n")); #endif return; } // Print them all! int offset = (boxSizeY / 2) + CENTERYSPACE; LKBrush bcolor; LKColor rgbcolor, textcolor; int wp; unsigned short zeroslot = 0; double minbrgdiff = 999.0; double minabrgdiff = 999.0; // absolute never negative for (unsigned short n = 0; n < numSlotX; n++) { wp = slotWpIndex[n]; if (!ValidWayPoint(wp)) { // empty slot nothing to print continue; } double brgdiff = WayPointCalc[wp].Bearing - DrawInfo.TrackBearing; // this check is worthless if (brgdiff < -180.0) { brgdiff += 360.0; } else { if (brgdiff > 180.0) brgdiff -= 360.0; } double abrgdiff = brgdiff; if (abrgdiff < 0) abrgdiff *= -1; if (abrgdiff < minabrgdiff) { zeroslot = n; minabrgdiff = abrgdiff; minbrgdiff = brgdiff; } } // Draw vertical line #define DEGRANGE 10 // degrees left and right to perfect target if (minabrgdiff < 1) { p1.x = slotCenterX[zeroslot]; } else { // set fullscale range if (minabrgdiff > DEGRANGE) { minabrgdiff = DEGRANGE; if (minbrgdiff < 0) minbrgdiff = -1 * DEGRANGE; else minbrgdiff = DEGRANGE; } // we shift of course in the opposite direction p1.x = slotCenterX[zeroslot]-(int) ((boxSizeX / (DEGRANGE * 2)) * minbrgdiff); } p2.x = p1.x; p1.y = rci.top + 1; p2.y = rci.bottom - 1; Surface.SelectObject(LKPen_Black_N1); Surface.DrawSolidLine(p1, p2, rci); for (unsigned short n = 0; n < numSlotX; n++) { wp = slotWpIndex[n]; if (!ValidWayPoint(wp)) { // empty slot nothing to print continue; } #if DEBUG_DVG StartupStore(_T("... DVG PRINT [%d]=%d <%s>\n"), n, wp, WayPointList[wp].Name); #endif Sideview_VGWpt[n] = wp; double altdiff = WayPointCalc[wp].AltArriv[AltArrivMode]; int ty; #if DEBUG_SCR StartupStore(_T("... wp=<%s>\n"), WayPointList[wp].Name); #endif // Since terrain can be approximated due to low precision maps, or waypoint position or altitude error, // we have a common problem: we get an obstacle to get to the waypoint because it is // positioned "BELOW" the terrain itself. We try to reduce this problem here. #define SAFETERRAIN 50 // Positive arrival altitude for the waypoint, upper window if (altdiff >= 0) { if (altdiff == 0)altdiff = 1; double d = vscale / altdiff; if (d == 0) d = 1; ty = upYbottom - (int) ((double) upSizeY / d)-(boxSizeY / 2); #if DEBUG_SCR StartupStore(_T("... upYbottom=%d upSizeY=%d / (vscale=%f/altdiff=%f = %f) =- %d ty=%d offset=%d\n"), upYbottom, upSizeY, vscale, altdiff, d, (int) ((double) upSizeY / d), ty, offset); #endif if ((ty - offset) < upYtop) ty = upYtop + offset; if ((ty + offset) > upYbottom) ty = upYbottom - offset; #if DEBUG_SCR StartupStore(_T("... upYtop=%d upYbottom=%d final ty=%d\n"), upYtop, upYbottom, ty); #endif // // This is too confusing. We want simple colors, not shaded // rgbcolor = MixColors( LKColor(50,255,50), LKColor(230,255,230), altdiff/(vscale-50)); // if (altdiff <= SAFETERRAIN) { rgbcolor = RGB_LIGHTYELLOW; } else { if (!CheckLandableReachableTerrainNew(&DrawInfo, &DerivedDrawInfo, WayPointCalc[wp].Distance, WayPointCalc[wp].Bearing)) { rgbcolor = RGB_LIGHTRED; } else { #ifdef DITHER rgbcolor = RGB_WHITE; #else rgbcolor = RGB_LIGHTGREEN; #endif } } bcolor.Create(rgbcolor); } else { double d = vscale / altdiff; if (d == 0) d = -1; ty = downYtop - (int) ((double) downSizeY / d)+(boxSizeY / 2); // - because the left part is negative, we are really adding. if ((ty - offset) < downYtop) ty = downYtop + offset; if ((ty + offset) > downYbottom) ty = downYbottom - offset; #ifdef DITHER rgbcolor = RGB_WHITE; // negative part, no need to render dark #else rgbcolor = RGB_LIGHTRED; #endif bcolor.Create(rgbcolor); } TCHAR line2[40], line3[40]; TCHAR value[40], unit[30]; TCHAR name[NAME_SIZE + 1]; double ar = (WayPointCalc[wp].AltArriv[AltArrivMode] * ALTITUDEMODIFY); _tcscpy(name, WayPointList[wp].Name); CharUpper(name); if (IsSafetyAltitudeInUse(wp)) textcolor = RGB_DARKBLUE; else textcolor = RGB_BLACK; switch (numboxrows) { case 0: #if BUGSTOP LKASSERT(0); #endif return; case 1: // 1 line: waypoint name VGTextInBox(Surface, n, 1, name, NULL, NULL, slotCenterX[n], ty, textcolor, bcolor); break; case 2: // 2 lines: waypoint name + altdiff LKFormatAltDiff(wp, false, value, unit); // Should we print also the GR? if ((ar >= -9999 && ar <= 9999) && (WayPointCalc[wp].GR < MAXEFFICIENCYSHOW)) { if (ar >= -999 && ar <= 999) _stprintf(line2, _T("%s "), value); else _stprintf(line2, _T("%s "), value); LKFormatGR(wp, false, value, unit); _tcscat(line2, value); } else { _stprintf(line2, _T("%s ---"), value); } VGTextInBox(Surface, n, 2, name, line2, NULL, slotCenterX[n], ty, textcolor, bcolor); break; case 3: // 3 lines: waypoint name + dist + altdiff LKFormatDist(wp, false, value, unit); _stprintf(line2, _T("%s%s"), value, unit); LKFormatBrgDiff(wp, false, value, unit); _stprintf(tmpT, _T(" %s%s"), value, unit); _tcscat(line2, tmpT); LKFormatAltDiff(wp, false, value, unit); // Should we print also the GR? if ((ar >= -9999 && ar <= 9999) && (WayPointCalc[wp].GR < MAXEFFICIENCYSHOW)) { if (ar >= -999 && ar <= 999) _stprintf(line3, _T("%s "), value); else _stprintf(line3, _T("%s "), value); LKFormatGR(wp, false, value, unit); _tcscat(line3, value); } else { _stprintf(line3, _T("%s ---"), value); } VGTextInBox(Surface, n, 3, name, line2, line3, slotCenterX[n], ty, textcolor, bcolor); break; default: #if BUGSTOP LKASSERT(0); #endif return; } } // for numSlotX // Cleanup and return Surface.SelectObject(oldBrush); Surface.SelectObject(oldPen); return; }
short MapWindow::GetVisualGlidePoints(unsigned short numslots) { #if BUGSTOP LKASSERT(numslots <= MAXBSLOT); #else if (numslots > MAXBSLOT) numslots = MAXBSLOT; #endif static short currentFilledNumber = -1; static double tmpSlotBrgDiff[MAXBSLOT + 1]; int i; // RESET COMMAND by passing 0, normally by EVENT_NEWRUN if (numslots == 0) { #if DEBUG_GVG StartupStore(_T("...... GVGP: RESET\n")); #endif for (i = 0; i < MAXBSLOT; i++) { slotWpIndex[i] = INVALID_VALUE; } currentFilledNumber = INVALID_VALUE; ResetVisualGlideGlobals(); return INVALID_VALUE; } bool ndr = NearestDataReady; NearestDataReady = false; // No data ready.. // if cfn is -1 we did not ever calculate it yet // otherwise 0 or >0 means use what we have already in the list if (!ndr) { #if DEBUG_GVG StartupStore(_T("...... GVGP: no data ready, currentFN=%d\n"), currentFilledNumber); #endif return currentFilledNumber; } if (SortedNumber <= 0) { #if DEBUG_GVG StartupStore(_T("...... GVGP: SortedNumber is 0, no available wpts in this range!\n")); #endif return 0; } int *pindex; int wpindex = 0; pindex = SortedTurnpointIndex; // // Reset content // currentFilledNumber = 0; for (i = 0; i < MAXBSLOT; i++) { slotWpIndex[i] = INVALID_VALUE; tmpSlotBrgDiff[i] = -999; } // // set up fine tuned parameters for this run // int maxgratio = 1, mingratio = 1; double maxdistance = 300; // in METERS, not in KM! if (ISPARAGLIDER) { maxgratio = (int) (GlidePolar::bestld / 2); mingratio = (int) (GlidePolar::bestld * 1.5); maxdistance = 100; } if (ISGLIDER) { maxgratio = (int) (GlidePolar::bestld / 2); mingratio = (int) (GlidePolar::bestld * 1.5); maxdistance = 300; } if (ISGAAIRCRAFT) { maxgratio = (int) (GlidePolar::bestld / 2); mingratio = (int) (GlidePolar::bestld * 2); maxdistance = 300; } if (ISCAR) { maxgratio = 1; mingratio = 999; maxdistance = 100; } // // WE USE THE 2.3 PAGE (Nearest turnpoints) sorted by DIRECTION // // We do this in several passes. #define MAXPHASES 4 unsigned short phase = 1; #if DEBUG_GVG StartupStore(_T("GVGP: USING %d Sorted Items available\n"), SortedNumber); int count = 0; #endif _tryagain: for (i = 0; i < numslots; i++) { LKASSERT(phase <= MAXPHASES); #if DEBUG_GVG if (i >= SortedNumber) { StartupStore(_T("...... GVGP: PHASE %d warning not enough SortedNumber (%d) for i=%d\n"), phase, SortedNumber, i); } #endif // Did we found at least one valid in current phase? Otherwise we skip the rest of numslots. // And we pass directly to the next phase. bool found = false; // look up for an empty slot, needed if running after phase 1 if (slotWpIndex[i] != INVALID_VALUE) continue; // browse results for the best usable items for (int k = 0; k < SortedNumber; k++) { wpindex = *(pindex + k); if (!ValidWayPoint(wpindex)) { // since we are not synced with DoNearest update cycle, we might fall here while it // has reset the sorted list. No worry, in the worst case we miss a waypoint printed // for a second, and the list might be inaccurate for the current second. // But in the next run it will be ok, because at the end of its run, the DoNearest // will be setting DataReady flag on and we shall update correctly. continue; } #if DEBUG_GVG count++; #endif // did we already use it? bool alreadyused = false; for (int j = 0; j < numslots; j++) { if (slotWpIndex[j] == INVALID_VALUE) break; if (slotWpIndex[j] == wpindex) { alreadyused = true; break; } } if (alreadyused) continue; // unused one, decide if good or not // We do this in 3 phases.. double distance = WayPointCalc[wpindex].Distance; double brgdiff = WayPointCalc[wpindex].Bearing - DrawInfo.TrackBearing; if (brgdiff < -180.0) { brgdiff += 360.0; } else { if (brgdiff > 180.0) brgdiff -= 360.0; } double abrgdiff = brgdiff; if (abrgdiff < 0) abrgdiff *= -1; #if 0 // do we already have a wp with same bearing? for (int j = 0; j < numslots; j++) { if ((int) tmpSlotBrgDiff[j] == (int) brgdiff) { //StartupStore(_T("%s with bdiff=%d already used\n"),WayPointList[wpindex].Name,(int)brgdiff); alreadyused = true; break; } } if (alreadyused) continue; #endif // Careful: we are selecting abrgdiff from a list that is tuned to max 60, so ok. // DIRECTIONRANGE definition in DoNearest // Then we make a selective insertion, in several steps // First, we pick mountain passes and task points, generally priority #1 points // accepted from +-45 deg, but only if they are not absolutely unreachable if (phase == 1) { if (abrgdiff > 45) continue; // if we are practically over the waypoint, skip it if (distance < maxdistance) continue; // Consider only Mt.Passes and task points not below us... if ((WayPointList[wpindex].Style == STYLE_MTPASS) || ((WayPointList[wpindex].InTask) && (distance > 100))) { // ... that are not within an obvious glide ratio // (considering even strong headwind, we want a very positive arrival) if (WayPointCalc[wpindex].AltArriv[AltArrivMode] > 150) { if (WayPointCalc[wpindex].GR <= (maxgratio / 1.5)) continue; } if (WayPointCalc[wpindex].AltArriv[AltArrivMode]<-100) { if (WayPointCalc[wpindex].GR >= mingratio) continue; } goto _takeit; } continue; } // Second we take anything not obviously reachable if (phase == 2) { if (abrgdiff > 45) continue; if (distance < maxdistance) continue; if (WayPointCalc[wpindex].AltArriv[AltArrivMode] > 150) { if (WayPointCalc[wpindex].GR <= (maxgratio)) continue; } goto _takeit; } if (phase == 3) { if (abrgdiff > 45) continue; if (distance < maxdistance) continue; goto _takeit; } // else we accept anything, in the original sort order _takeit: // ok good, use it slotWpIndex[i] = wpindex; tmpSlotBrgDiff[i] = brgdiff; found = true; #if DEBUG_GVG StartupStore(_T("PHASE %d slot [%d] of %d : wp=%d <%s> brg=%f\n"), phase, i, numslots, wpindex, WayPointList[wpindex].Name, brgdiff); #endif currentFilledNumber++; break; } // for all sorted wps if (!found) { #if DEBUG_GVG StartupStore(_T("PHASE %d nothing found during search (stop at slot %d), advance to next phase\n"), phase, i >= numslots ? numslots - 1 : i); #endif break; } if (currentFilledNumber >= numslots) { #if DEBUG_GVG StartupStore(_T("PHASE %d stop search, all slots taken\n"), phase); #endif break; } } // for all slots to be filled if ((currentFilledNumber < numslots) && (phase < MAXPHASES)) { #if DEBUG_GVG StartupStore(_T("PHASE %d filled %d of %d slots, going phase %d\n"), phase, currentFilledNumber, numslots, phase + 1); #endif phase++; goto _tryagain; } #if DEBUG_GVG else { StartupStore(_T("PHASE %d filled %d of %d slots\n"), phase, currentFilledNumber, numslots); } StartupStore(_T("TOTAL COUNTS=%d\n"), count); #endif // // All wpts in the array are shuffled, unsorted by direction. // We must reposition them horizontally, using their bearing // int tmpSlotWpIndex[MAXBSLOT + 1]; for (i = 0; i < MAXBSLOT; i++) tmpSlotWpIndex[i] = INVALID_VALUE; #if DEBUG_GVG for (i = 0; i < numslots; i++) { StartupStore(_T(">>> [%d] %f (wp=%d)\n"), i, tmpSlotBrgDiff[i], slotWpIndex[i]); } #endif bool invalid = true, valid = false; unsigned short g; for (unsigned short nslot = 0; nslot < numslots; nslot++) { g = 0; double minim = 999; valid = false; #if DEBUGSORT StartupStore(_T(".... Slot [%d]:\n"), nslot); #endif for (unsigned short k = 0; k < numslots; k++) { if (tmpSlotBrgDiff[k] <= minim) { // is this already used? invalid = false; if (slotWpIndex[k] == -1) { #if DEBUGSORT StartupStore(_T(".... not using g=%d, it is an invalid waypoint\n"), k); #endif continue; } for (unsigned short n = 0; n < nslot; n++) { if (tmpSlotWpIndex[n] == slotWpIndex[k]) { #if DEBUGSORT StartupStore(_T(".... not using g=%d, it is already used in newslot=%d\n"), k, n); #endif invalid = true; continue; } } if (invalid || !ValidWayPoint(slotWpIndex[k])) continue; // We do have a valid choice g = k; minim = tmpSlotBrgDiff[k]; #if DEBUGSORT StartupStore(_T(".... minim=%f g=%d\n"), minim, g); #endif valid = true; } } if (valid) { tmpSlotWpIndex[nslot] = slotWpIndex[g]; #if DEBUGSORT StartupStore(_T(".... FINAL for SLOT %d: minim=%f g=%d\n"), nslot, minim, g); #endif } #if DEBUGSORT else { StartupStore(_T(".... FINAL for SLOT %d: no valid point\n"), nslot); } #endif } for (i = 0; i < numslots; i++) { slotWpIndex[i] = tmpSlotWpIndex[i]; } return currentFilledNumber; }