Renderer, ...: use PixelRect::GetCenter()
[xcsoar.git] / src / Renderer / AirspaceRenderer.cpp
blobfa1c1440cbb557020a6f1b043e73c0f103d87db3
1 /*
2 Copyright_License {
4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #include "AirspaceRenderer.hpp"
25 #include "MapSettings.hpp"
26 #include "Projection/WindowProjection.hpp"
27 #include "Screen/Canvas.hpp"
28 #include "MapWindow/MapCanvas.hpp"
29 #include "Look/AirspaceLook.hpp"
30 #include "Airspace/Airspaces.hpp"
31 #include "Airspace/AirspacePolygon.hpp"
32 #include "Airspace/AirspaceCircle.hpp"
33 #include "Airspace/AirspaceVisitor.hpp"
34 #include "Airspace/AirspaceVisibility.hpp"
35 #include "Airspace/AirspaceWarning.hpp"
36 #include "Airspace/ProtectedAirspaceWarningManager.hpp"
37 #include "Engine/Airspace/AirspaceWarningManager.hpp"
38 #include "MapWindow/MapDrawHelper.hpp"
39 #include "Screen/Layout.hpp"
40 #include "NMEA/Aircraft.hpp"
42 #ifdef ENABLE_OPENGL
43 #include "Screen/OpenGL/Scope.hpp"
44 #endif
46 class AirspaceWarningCopy
48 private:
49 StaticArray<const AbstractAirspace *,64> ids_inside, ids_warning, ids_acked;
50 StaticArray<GeoPoint,32> locations;
52 public:
53 void Visit(const AirspaceWarning& as) {
54 if (as.GetWarningState() == AirspaceWarning::WARNING_INSIDE) {
55 ids_inside.checked_append(&as.GetAirspace());
56 } else if (as.GetWarningState() > AirspaceWarning::WARNING_CLEAR) {
57 ids_warning.checked_append(&as.GetAirspace());
58 locations.checked_append(as.GetSolution().location);
61 if (!as.IsAckExpired())
62 ids_acked.checked_append(&as.GetAirspace());
65 void Visit(const AirspaceWarningManager &awm) {
66 for (auto i = awm.begin(), end = awm.end(); i != end; ++i)
67 Visit(*i);
70 void Visit(const ProtectedAirspaceWarningManager &awm) {
71 const ProtectedAirspaceWarningManager::Lease lease(awm);
72 Visit(lease);
75 const StaticArray<GeoPoint,32> &GetLocations() const {
76 return locations;
79 bool HasWarning(const AbstractAirspace &as) const {
80 return as.IsActive() && Find(as, ids_warning);
83 bool IsAcked(const AbstractAirspace &as) const {
84 return (!as.IsActive()) || Find(as, ids_acked);
87 bool IsInside(const AbstractAirspace &as) const {
88 return as.IsActive() && Find(as, ids_inside);
91 void VisitWarnings(AirspaceVisitor &visitor) const {
92 for (auto it = ids_warning.begin(), end = ids_warning.end(); it != end; ++it)
93 if (!IsAcked(**it))
94 visitor.Visit(**it);
97 void VisitInside(AirspaceVisitor &visitor) const {
98 for (auto it = ids_inside.begin(), end = ids_inside.end(); it != end; ++it)
99 if (!IsAcked(**it))
100 visitor.Visit(**it);
103 private:
104 bool Find(const AbstractAirspace& as,
105 const StaticArray<const AbstractAirspace *,64> &list) const {
106 return list.contains(&as);
111 class AirspaceMapVisible : public AirspacePredicate
113 const AirspaceVisibility visible_predicate;
114 const AirspaceWarningCopy &warnings;
116 public:
117 AirspaceMapVisible(const AirspaceComputerSettings &_computer_settings,
118 const AirspaceRendererSettings &_renderer_settings,
119 const AircraftState& _state,
120 const AirspaceWarningCopy& _warnings)
121 :visible_predicate(_computer_settings, _renderer_settings, _state),
122 warnings(_warnings) {}
124 bool operator()(const AbstractAirspace& airspace) const {
125 return visible_predicate(airspace) ||
126 warnings.IsInside(airspace) ||
127 warnings.HasWarning(airspace);
131 #ifdef ENABLE_OPENGL
133 class AirspaceVisitorRenderer final
134 : public AirspaceVisitor, protected MapCanvas
136 const AirspaceLook &look;
137 const AirspaceWarningCopy &warning_manager;
138 const AirspaceRendererSettings &settings;
140 public:
141 AirspaceVisitorRenderer(Canvas &_canvas, const WindowProjection &_projection,
142 const AirspaceLook &_look,
143 const AirspaceWarningCopy &_warnings,
144 const AirspaceRendererSettings &_settings)
145 :MapCanvas(_canvas, _projection,
146 _projection.GetScreenBounds().Scale(fixed(1.1))),
147 look(_look), warning_manager(_warnings), settings(_settings)
149 glStencilMask(0xff);
150 glClear(GL_STENCIL_BUFFER_BIT);
151 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
154 ~AirspaceVisitorRenderer() {
155 glStencilMask(0xff);
158 private:
159 void VisitCircle(const AirspaceCircle &airspace) {
160 RasterPoint screen_center = projection.GeoToScreen(airspace.GetCenter());
161 unsigned screen_radius = projection.GeoToScreenDistance(airspace.GetRadius());
162 GLEnable stencil(GL_STENCIL_TEST);
164 if (!warning_manager.IsAcked(airspace) &&
165 settings.classes[airspace.GetType()].fill_mode !=
166 AirspaceClassRendererSettings::FillMode::NONE) {
167 GLEnable blend(GL_BLEND);
168 SetupInterior(airspace);
169 if (warning_manager.HasWarning(airspace) ||
170 warning_manager.IsInside(airspace) ||
171 look.thick_pen.GetWidth() >= 2 * screen_radius ||
172 settings.classes[airspace.GetType()].fill_mode ==
173 AirspaceClassRendererSettings::FillMode::ALL) {
174 // fill whole circle
175 canvas.DrawCircle(screen_center.x, screen_center.y, screen_radius);
176 } else {
177 // draw a ring inside the circle
178 Color color = settings.classes[airspace.GetType()].fill_color;
179 Pen pen_donut(look.thick_pen.GetWidth() / 2, color.WithAlpha(90));
180 canvas.SelectHollowBrush();
181 canvas.Select(pen_donut);
182 canvas.DrawCircle(screen_center.x, screen_center.y,
183 screen_radius - look.thick_pen.GetWidth() / 4);
187 // draw outline
188 if (SetupOutline(airspace))
189 canvas.DrawCircle(screen_center.x, screen_center.y, screen_radius);
192 void VisitPolygon(const AirspacePolygon &airspace) {
193 if (!PreparePolygon(airspace.GetPoints()))
194 return;
196 bool fill_airspace = warning_manager.HasWarning(airspace) ||
197 warning_manager.IsInside(airspace) ||
198 settings.classes[airspace.GetType()].fill_mode ==
199 AirspaceClassRendererSettings::FillMode::ALL;
200 GLEnable stencil(GL_STENCIL_TEST);
202 if (!warning_manager.IsAcked(airspace) &&
203 settings.classes[airspace.GetType()].fill_mode !=
204 AirspaceClassRendererSettings::FillMode::NONE) {
205 if (!fill_airspace) {
206 // set stencil for filling (bit 0)
207 SetFillStencil();
208 DrawPrepared();
211 // fill interior without overpainting any previous outlines
213 SetupInterior(airspace, !fill_airspace);
214 GLEnable blend(GL_BLEND);
215 DrawPrepared();
218 if (!fill_airspace) {
219 // clear fill stencil (bit 0)
220 ClearFillStencil();
221 DrawPrepared();
225 // draw outline
226 if (SetupOutline(airspace))
227 DrawPrepared();
230 protected:
231 virtual void Visit(const AbstractAirspace &airspace) override {
232 switch (airspace.GetShape()) {
233 case AbstractAirspace::Shape::CIRCLE:
234 VisitCircle((const AirspaceCircle &)airspace);
235 break;
237 case AbstractAirspace::Shape::POLYGON:
238 VisitPolygon((const AirspacePolygon &)airspace);
239 break;
243 private:
244 bool SetupOutline(const AbstractAirspace &airspace) {
245 AirspaceClass type = airspace.GetType();
247 if (settings.black_outline)
248 canvas.SelectBlackPen();
249 else if (settings.classes[type].border_width == 0)
250 // Don't draw outlines if border_width == 0
251 return false;
252 else
253 canvas.Select(look.pens[type]);
255 canvas.SelectHollowBrush();
257 // set bit 1 in stencil buffer, where an outline is drawn
258 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
259 glStencilFunc(GL_ALWAYS, 3, 3);
260 glStencilMask(2);
261 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
263 return true;
266 void SetupInterior(const AbstractAirspace &airspace,
267 bool check_fillstencil = false) {
268 // restrict drawing area and don't paint over previously drawn outlines
269 if (check_fillstencil)
270 glStencilFunc(GL_EQUAL, 1, 3);
271 else
272 glStencilFunc(GL_EQUAL, 0, 2);
273 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
274 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
276 Color color = settings.classes[airspace.GetType()].fill_color;
277 canvas.Select(Brush(color.WithAlpha(90)));
278 canvas.SelectNullPen();
281 void SetFillStencil() {
282 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
283 glStencilFunc(GL_ALWAYS, 3, 3);
284 glStencilMask(1);
285 glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
287 canvas.SelectHollowBrush();
288 canvas.Select(look.thick_pen);
291 void ClearFillStencil() {
292 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
293 glStencilFunc(GL_ALWAYS, 3, 3);
294 glStencilMask(1);
295 glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
297 canvas.SelectHollowBrush();
298 canvas.Select(look.thick_pen);
302 class AirspaceFillRenderer final
303 : public AirspaceVisitor, protected MapCanvas
305 const AirspaceLook &look;
306 const AirspaceWarningCopy &warning_manager;
307 const AirspaceRendererSettings &settings;
309 public:
310 AirspaceFillRenderer(Canvas &_canvas, const WindowProjection &_projection,
311 const AirspaceLook &_look,
312 const AirspaceWarningCopy &_warnings,
313 const AirspaceRendererSettings &_settings)
314 :MapCanvas(_canvas, _projection,
315 _projection.GetScreenBounds().Scale(fixed(1.1))),
316 look(_look), warning_manager(_warnings), settings(_settings)
318 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
321 private:
322 void VisitCircle(const AirspaceCircle &airspace) {
323 RasterPoint screen_center = projection.GeoToScreen(airspace.GetCenter());
324 unsigned screen_radius = projection.GeoToScreenDistance(airspace.GetRadius());
327 GLEnable blend(GL_BLEND);
328 SetupInterior(airspace);
329 canvas.DrawCircle(screen_center.x, screen_center.y, screen_radius);
332 // draw outline
333 if (SetupOutline(airspace))
334 canvas.DrawCircle(screen_center.x, screen_center.y, screen_radius);
337 void VisitPolygon(const AirspacePolygon &airspace) {
338 if (!PreparePolygon(airspace.GetPoints()))
339 return;
341 if (!warning_manager.IsAcked(airspace)) {
342 // fill interior without overpainting any previous outlines
344 SetupInterior(airspace);
345 GLEnable blend(GL_BLEND);
346 DrawPrepared();
350 // draw outline
351 if (SetupOutline(airspace))
352 DrawPrepared();
355 protected:
356 virtual void Visit(const AbstractAirspace &airspace) override {
357 switch (airspace.GetShape()) {
358 case AbstractAirspace::Shape::CIRCLE:
359 VisitCircle((const AirspaceCircle &)airspace);
360 break;
362 case AbstractAirspace::Shape::POLYGON:
363 VisitPolygon((const AirspacePolygon &)airspace);
364 break;
368 private:
369 bool SetupOutline(const AbstractAirspace &airspace) {
370 AirspaceClass type = airspace.GetType();
372 if (settings.black_outline)
373 canvas.SelectBlackPen();
374 else if (settings.classes[type].border_width == 0)
375 // Don't draw outlines if border_width == 0
376 return false;
377 else
378 canvas.Select(look.pens[type]);
380 canvas.SelectHollowBrush();
382 return true;
385 void SetupInterior(const AbstractAirspace &airspace) {
386 Color color = settings.classes[airspace.GetType()].fill_color;
387 canvas.Select(Brush(color.WithAlpha(48)));
388 canvas.SelectNullPen();
392 #else // !ENABLE_OPENGL
395 * Class to render airspaces onto map in two passes,
396 * one for border, one for area.
397 * This is a bit slow because projections are performed twice.
398 * The old way of doing it was possibly faster but required a lot
399 * of code overhead.
401 class AirspaceVisitorMap final
402 : public AirspaceVisitor, public MapDrawHelper
404 const AirspaceLook &look;
405 const AirspaceWarningCopy &warnings;
407 public:
408 AirspaceVisitorMap(MapDrawHelper &_helper,
409 const AirspaceWarningCopy &_warnings,
410 const AirspaceRendererSettings &_settings,
411 const AirspaceLook &_airspace_look)
412 :MapDrawHelper(_helper),
413 look(_airspace_look), warnings(_warnings)
415 switch (settings.fill_mode) {
416 case AirspaceRendererSettings::FillMode::DEFAULT:
417 case AirspaceRendererSettings::FillMode::PADDING:
418 use_stencil = !IsAncientHardware();
419 break;
421 case AirspaceRendererSettings::FillMode::ALL:
422 use_stencil = false;
423 break;
427 private:
428 void VisitCircle(const AirspaceCircle &airspace) {
429 if (warnings.IsAcked(airspace))
430 return;
432 AirspaceClass airspace_class = airspace.GetType();
433 if (settings.classes[airspace_class].fill_mode ==
434 AirspaceClassRendererSettings::FillMode::NONE)
435 return;
437 BufferRenderStart();
438 SetBufferPens(airspace);
440 RasterPoint center = proj.GeoToScreen(airspace.GetCenter());
441 unsigned radius = proj.GeoToScreenDistance(airspace.GetRadius());
442 DrawCircle(center, radius);
445 void VisitPolygon(const AirspacePolygon &airspace) {
446 if (warnings.IsAcked(airspace))
447 return;
449 AirspaceClass airspace_class = airspace.GetType();
450 if (settings.classes[airspace_class].fill_mode ==
451 AirspaceClassRendererSettings::FillMode::NONE)
452 return;
454 BufferRenderStart();
455 SetBufferPens(airspace);
456 DrawSearchPointVector(airspace.GetPoints());
459 protected:
460 virtual void Visit(const AbstractAirspace &airspace) override {
461 switch (airspace.GetShape()) {
462 case AbstractAirspace::Shape::CIRCLE:
463 VisitCircle((const AirspaceCircle &)airspace);
464 break;
466 case AbstractAirspace::Shape::POLYGON:
467 VisitPolygon((const AirspacePolygon &)airspace);
468 break;
472 public:
473 void DrawIntercepts() {
474 BufferRenderFinish();
477 private:
478 void SetBufferPens(const AbstractAirspace &airspace) {
479 AirspaceClass airspace_class = airspace.GetType();
481 #ifndef HAVE_HATCHED_BRUSH
482 buffer.Select(look.solid_brushes[airspace_class]);
483 #else /* HAVE_HATCHED_BRUSH */
485 #ifdef HAVE_ALPHA_BLEND
486 if (settings.transparency && AlphaBlendAvailable()) {
487 buffer.Select(look.solid_brushes[airspace_class]);
488 } else {
489 #endif
490 // this color is used as the black bit
491 buffer.SetTextColor(LightColor(settings.classes[airspace_class].fill_color));
493 // get brush, can be solid or a 1bpp bitmap
494 buffer.Select(look.brushes[settings.classes[airspace_class].brush]);
496 buffer.SetBackgroundOpaque();
497 buffer.SetBackgroundColor(COLOR_WHITE);
498 #ifdef HAVE_ALPHA_BLEND
500 #endif
502 buffer.SelectNullPen();
504 if (use_stencil) {
505 if (warnings.HasWarning(airspace) || warnings.IsInside(airspace) ||
506 settings.classes[airspace_class].fill_mode ==
507 AirspaceClassRendererSettings::FillMode::ALL) {
508 stencil.SelectBlackBrush();
509 stencil.SelectNullPen();
510 } else {
511 stencil.Select(look.thick_pen);
512 stencil.SelectHollowBrush();
516 #endif /* HAVE_HATCHED_BRUSH */
520 class AirspaceOutlineRenderer final
521 : public AirspaceVisitor, protected MapCanvas
523 const AirspaceLook &look;
524 const AirspaceRendererSettings &settings;
526 public:
527 AirspaceOutlineRenderer(Canvas &_canvas, const WindowProjection &_projection,
528 const AirspaceLook &_look,
529 const AirspaceRendererSettings &_settings)
530 :MapCanvas(_canvas, _projection,
531 _projection.GetScreenBounds().Scale(fixed(1.1))),
532 look(_look), settings(_settings)
534 if (settings.black_outline)
535 canvas.SelectBlackPen();
536 canvas.SelectHollowBrush();
539 protected:
540 bool SetupCanvas(const AbstractAirspace &airspace) {
541 if (settings.black_outline)
542 return true;
544 AirspaceClass type = airspace.GetType();
545 if (settings.classes[type].border_width == 0)
546 // Don't draw outlines if border_width == 0
547 return false;
549 canvas.Select(look.pens[type]);
551 return true;
554 public:
555 void VisitCircle(const AirspaceCircle &airspace) {
556 if (SetupCanvas(airspace))
557 DrawCircle(airspace.GetCenter(), airspace.GetRadius());
560 void VisitPolygon(const AirspacePolygon &airspace) {
561 if (SetupCanvas(airspace))
562 DrawPolygon(airspace.GetPoints());
565 virtual void Visit(const AbstractAirspace &airspace) override {
566 switch (airspace.GetShape()) {
567 case AbstractAirspace::Shape::CIRCLE:
568 VisitCircle((const AirspaceCircle &)airspace);
569 break;
571 case AbstractAirspace::Shape::POLYGON:
572 VisitPolygon((const AirspacePolygon &)airspace);
573 break;
578 #endif // !ENABLE_OPENGL
580 void
581 AirspaceRenderer::DrawIntersections(Canvas &canvas,
582 const WindowProjection &projection) const
584 for (unsigned i = intersections.size(); i--;) {
585 RasterPoint sc;
586 if (projection.GeoToScreenIfVisible(intersections[i], sc))
587 look.intercept_icon.Draw(canvas, sc.x, sc.y);
591 void
592 AirspaceRenderer::Draw(Canvas &canvas,
593 #ifndef ENABLE_OPENGL
594 Canvas &buffer_canvas, Canvas &stencil_canvas,
595 #endif
596 const WindowProjection &projection,
597 const AirspaceRendererSettings &settings,
598 const AirspaceWarningCopy &awc,
599 const AirspacePredicate &visible)
601 if (airspaces == NULL)
602 return;
604 #ifdef ENABLE_OPENGL
605 if (settings.fill_mode == AirspaceRendererSettings::FillMode::ALL) {
606 AirspaceFillRenderer renderer(canvas, projection, look, awc,
607 settings);
608 airspaces->VisitWithinRange(projection.GetGeoScreenCenter(),
609 projection.GetScreenDistanceMeters(),
610 renderer, visible);
611 } else {
612 AirspaceVisitorRenderer renderer(canvas, projection, look, awc,
613 settings);
614 airspaces->VisitWithinRange(projection.GetGeoScreenCenter(),
615 projection.GetScreenDistanceMeters(),
616 renderer, visible);
618 #else
619 MapDrawHelper helper(canvas, buffer_canvas, stencil_canvas, projection,
620 settings);
621 AirspaceVisitorMap v(helper, awc, settings,
622 look);
624 // JMW TODO wasteful to draw twice, can't it be drawn once?
625 // we are using two draws so borders go on top of everything
627 airspaces->VisitWithinRange(projection.GetGeoScreenCenter(),
628 projection.GetScreenDistanceMeters(),
629 v, visible);
631 awc.VisitWarnings(v);
632 awc.VisitInside(v);
634 v.DrawIntercepts();
636 AirspaceOutlineRenderer outline_renderer(canvas, projection, look, settings);
637 airspaces->VisitWithinRange(projection.GetGeoScreenCenter(),
638 projection.GetScreenDistanceMeters(),
639 outline_renderer, visible);
640 awc.VisitWarnings(outline_renderer);
641 awc.VisitInside(outline_renderer);
642 #endif
644 intersections = awc.GetLocations();
647 void
648 AirspaceRenderer::Draw(Canvas &canvas,
649 #ifndef ENABLE_OPENGL
650 Canvas &buffer_canvas, Canvas &stencil_canvas,
651 #endif
652 const WindowProjection &projection,
653 const AirspaceRendererSettings &settings)
655 if (airspaces == NULL)
656 return;
658 AirspaceWarningCopy awc;
659 if (warning_manager != NULL)
660 awc.Visit(*warning_manager);
662 Draw(canvas,
663 #ifndef ENABLE_OPENGL
664 buffer_canvas, stencil_canvas,
665 #endif
666 projection, settings, awc, AirspacePredicateTrue());
669 void
670 AirspaceRenderer::Draw(Canvas &canvas,
671 #ifndef ENABLE_OPENGL
672 Canvas &buffer_canvas, Canvas &stencil_canvas,
673 #endif
674 const WindowProjection &projection,
675 const MoreData &basic,
676 const DerivedInfo &calculated,
677 const AirspaceComputerSettings &computer_settings,
678 const AirspaceRendererSettings &settings)
680 if (airspaces == NULL)
681 return;
683 AirspaceWarningCopy awc;
684 if (warning_manager != NULL)
685 awc.Visit(*warning_manager);
687 const AirspaceMapVisible visible(computer_settings, settings,
688 ToAircraftState(basic, calculated), awc);
689 Draw(canvas,
690 #ifndef ENABLE_OPENGL
691 buffer_canvas, stencil_canvas,
692 #endif
693 projection, settings, awc, visible);