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"
43 #include "Screen/OpenGL/Scope.hpp"
46 class AirspaceWarningCopy
49 StaticArray
<const AbstractAirspace
*,64> ids_inside
, ids_warning
, ids_acked
;
50 StaticArray
<GeoPoint
,32> locations
;
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
)
70 void Visit(const ProtectedAirspaceWarningManager
&awm
) {
71 const ProtectedAirspaceWarningManager::Lease
lease(awm
);
75 const StaticArray
<GeoPoint
,32> &GetLocations() const {
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
)
97 void VisitInside(AirspaceVisitor
&visitor
) const {
98 for (auto it
= ids_inside
.begin(), end
= ids_inside
.end(); it
!= end
; ++it
)
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
;
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
);
133 class AirspaceVisitorRenderer final
134 : public AirspaceVisitor
, protected MapCanvas
136 const AirspaceLook
&look
;
137 const AirspaceWarningCopy
&warning_manager
;
138 const AirspaceRendererSettings
&settings
;
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
)
150 glClear(GL_STENCIL_BUFFER_BIT
);
151 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
154 ~AirspaceVisitorRenderer() {
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
) {
175 canvas
.DrawCircle(screen_center
.x
, screen_center
.y
, screen_radius
);
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);
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()))
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)
211 // fill interior without overpainting any previous outlines
213 SetupInterior(airspace
, !fill_airspace
);
214 GLEnable
blend(GL_BLEND
);
218 if (!fill_airspace
) {
219 // clear fill stencil (bit 0)
226 if (SetupOutline(airspace
))
231 virtual void Visit(const AbstractAirspace
&airspace
) override
{
232 switch (airspace
.GetShape()) {
233 case AbstractAirspace::Shape::CIRCLE
:
234 VisitCircle((const AirspaceCircle
&)airspace
);
237 case AbstractAirspace::Shape::POLYGON
:
238 VisitPolygon((const AirspacePolygon
&)airspace
);
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
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);
261 glStencilOp(GL_KEEP
, GL_KEEP
, GL_REPLACE
);
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);
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);
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);
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
;
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
);
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
);
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()))
341 if (!warning_manager
.IsAcked(airspace
)) {
342 // fill interior without overpainting any previous outlines
344 SetupInterior(airspace
);
345 GLEnable
blend(GL_BLEND
);
351 if (SetupOutline(airspace
))
356 virtual void Visit(const AbstractAirspace
&airspace
) override
{
357 switch (airspace
.GetShape()) {
358 case AbstractAirspace::Shape::CIRCLE
:
359 VisitCircle((const AirspaceCircle
&)airspace
);
362 case AbstractAirspace::Shape::POLYGON
:
363 VisitPolygon((const AirspacePolygon
&)airspace
);
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
378 canvas
.Select(look
.pens
[type
]);
380 canvas
.SelectHollowBrush();
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
401 class AirspaceVisitorMap final
402 : public AirspaceVisitor
, public MapDrawHelper
404 const AirspaceLook
&look
;
405 const AirspaceWarningCopy
&warnings
;
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();
421 case AirspaceRendererSettings::FillMode::ALL
:
428 void VisitCircle(const AirspaceCircle
&airspace
) {
429 if (warnings
.IsAcked(airspace
))
432 AirspaceClass airspace_class
= airspace
.GetType();
433 if (settings
.classes
[airspace_class
].fill_mode
==
434 AirspaceClassRendererSettings::FillMode::NONE
)
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
))
449 AirspaceClass airspace_class
= airspace
.GetType();
450 if (settings
.classes
[airspace_class
].fill_mode
==
451 AirspaceClassRendererSettings::FillMode::NONE
)
455 SetBufferPens(airspace
);
456 DrawSearchPointVector(airspace
.GetPoints());
460 virtual void Visit(const AbstractAirspace
&airspace
) override
{
461 switch (airspace
.GetShape()) {
462 case AbstractAirspace::Shape::CIRCLE
:
463 VisitCircle((const AirspaceCircle
&)airspace
);
466 case AbstractAirspace::Shape::POLYGON
:
467 VisitPolygon((const AirspacePolygon
&)airspace
);
473 void DrawIntercepts() {
474 BufferRenderFinish();
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
]);
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
502 buffer
.SelectNullPen();
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();
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
;
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();
540 bool SetupCanvas(const AbstractAirspace
&airspace
) {
541 if (settings
.black_outline
)
544 AirspaceClass type
= airspace
.GetType();
545 if (settings
.classes
[type
].border_width
== 0)
546 // Don't draw outlines if border_width == 0
549 canvas
.Select(look
.pens
[type
]);
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
);
571 case AbstractAirspace::Shape::POLYGON
:
572 VisitPolygon((const AirspacePolygon
&)airspace
);
578 #endif // !ENABLE_OPENGL
581 AirspaceRenderer::DrawIntersections(Canvas
&canvas
,
582 const WindowProjection
&projection
) const
584 for (unsigned i
= intersections
.size(); i
--;) {
586 if (projection
.GeoToScreenIfVisible(intersections
[i
], sc
))
587 look
.intercept_icon
.Draw(canvas
, sc
.x
, sc
.y
);
592 AirspaceRenderer::Draw(Canvas
&canvas
,
593 #ifndef ENABLE_OPENGL
594 Canvas
&buffer_canvas
, Canvas
&stencil_canvas
,
596 const WindowProjection
&projection
,
597 const AirspaceRendererSettings
&settings
,
598 const AirspaceWarningCopy
&awc
,
599 const AirspacePredicate
&visible
)
601 if (airspaces
== NULL
)
605 if (settings
.fill_mode
== AirspaceRendererSettings::FillMode::ALL
) {
606 AirspaceFillRenderer
renderer(canvas
, projection
, look
, awc
,
608 airspaces
->VisitWithinRange(projection
.GetGeoScreenCenter(),
609 projection
.GetScreenDistanceMeters(),
612 AirspaceVisitorRenderer
renderer(canvas
, projection
, look
, awc
,
614 airspaces
->VisitWithinRange(projection
.GetGeoScreenCenter(),
615 projection
.GetScreenDistanceMeters(),
619 MapDrawHelper
helper(canvas
, buffer_canvas
, stencil_canvas
, projection
,
621 AirspaceVisitorMap
v(helper
, awc
, settings
,
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(),
631 awc
.VisitWarnings(v
);
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
);
644 intersections
= awc
.GetLocations();
648 AirspaceRenderer::Draw(Canvas
&canvas
,
649 #ifndef ENABLE_OPENGL
650 Canvas
&buffer_canvas
, Canvas
&stencil_canvas
,
652 const WindowProjection
&projection
,
653 const AirspaceRendererSettings
&settings
)
655 if (airspaces
== NULL
)
658 AirspaceWarningCopy awc
;
659 if (warning_manager
!= NULL
)
660 awc
.Visit(*warning_manager
);
663 #ifndef ENABLE_OPENGL
664 buffer_canvas
, stencil_canvas
,
666 projection
, settings
, awc
, AirspacePredicateTrue());
670 AirspaceRenderer::Draw(Canvas
&canvas
,
671 #ifndef ENABLE_OPENGL
672 Canvas
&buffer_canvas
, Canvas
&stencil_canvas
,
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
)
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
);
690 #ifndef ENABLE_OPENGL
691 buffer_canvas
, stencil_canvas
,
693 projection
, settings
, awc
, visible
);