source: git/src/gfxcore.cc @ 5d7219a4

debian/1.2.41-1
Last change on this file since 5d7219a4 was 7750c72, checked in by Olly Betts <olly@…>, 4 months ago

Attempt to work around macOS 10.14 problems

Trying to create a wxPaintDC for the wxGLCanvas window fails with an
assertion about something which it seems is no longer always true in
macOS 10.14. To avoid this we check for the assertion condition first
and temporarily disable assertions while we create the wxPaintDC.

Hopefully this fixes #101, reported by floho.

  • Property mode set to 100644
File size: 119.5 KB
Line 
1//
2//  gfxcore.cc
3//
4//  Core drawing code for Aven.
5//
6//  Copyright (C) 2000-2003,2005,2006 Mark R. Shinwell
7//  Copyright (C) 2001-2003,2004,2005,2006,2007,2010,2011,2012,2014,2015,2016,2017,2018 Olly Betts
8//  Copyright (C) 2005 Martin Green
9//
10//  This program is free software; you can redistribute it and/or modify
11//  it under the terms of the GNU General Public License as published by
12//  the Free Software Foundation; either version 2 of the License, or
13//  (at your option) any later version.
14//
15//  This program is distributed in the hope that it will be useful,
16//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18//  GNU General Public License for more details.
19//
20//  You should have received a copy of the GNU General Public License
21//  along with this program; if not, write to the Free Software
22//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
23//
24
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
29#include <assert.h>
30#include <float.h>
31
32#include "aven.h"
33#include "aventreectrl.h"
34#include "date.h"
35#include "filename.h"
36#include "gfxcore.h"
37#include "hash.h"
38#include "mainfrm.h"
39#include "message.h"
40#include "useful.h"
41#include "printing.h"
42#include "guicontrol.h"
43#include "moviemaker.h"
44
45#include <wx/confbase.h>
46#include <wx/wfstream.h>
47#include <wx/image.h>
48#include <wx/zipstrm.h>
49
50#define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H 1
51#include <proj_api.h>
52
53const unsigned long DEFAULT_HGT_DIM = 3601;
54const unsigned long DEFAULT_HGT_SIZE = sqrd(DEFAULT_HGT_DIM) * 2;
55
56// Values for m_SwitchingTo
57#define PLAN 1
58#define ELEVATION 2
59#define NORTH 3
60#define EAST 4
61#define SOUTH 5
62#define WEST 6
63
64// Any error value higher than this is clamped to this.
65#define MAX_ERROR 12.0
66
67// Any length greater than pow(10, LOG_LEN_MAX) will be clamped to this.
68const Double LOG_LEN_MAX = 1.5;
69
70// How many bins per letter height to use when working out non-overlapping
71// labels.
72const unsigned int QUANTISE_FACTOR = 2;
73
74#include "avenpal.h"
75
76static const int INDICATOR_BOX_SIZE = 60;
77static const int INDICATOR_GAP = 2;
78static const int INDICATOR_MARGIN = 5;
79static const int INDICATOR_OFFSET_X = 15;
80static const int INDICATOR_OFFSET_Y = 15;
81static const int INDICATOR_RADIUS = INDICATOR_BOX_SIZE / 2 - INDICATOR_MARGIN;
82static const int KEY_OFFSET_X = 10;
83static const int KEY_OFFSET_Y = 10;
84static const int KEY_EXTRA_LEFT_MARGIN = 2;
85static const int KEY_BLOCK_WIDTH = 20;
86static const int KEY_BLOCK_HEIGHT = 16;
87static const int TICK_LENGTH = 4;
88static const int SCALE_BAR_OFFSET_X = 15;
89static const int SCALE_BAR_OFFSET_Y = 12;
90static const int SCALE_BAR_HEIGHT = 12;
91
92static const gla_colour TEXT_COLOUR = col_GREEN;
93static const gla_colour HERE_COLOUR = col_WHITE;
94static const gla_colour NAME_COLOUR = col_GREEN;
95static const gla_colour SEL_COLOUR = col_WHITE;
96// Used with colour by date for legs without date information and with colour
97// by error for legs not in a loop.
98static const gla_colour NODATA_COLOUR = col_LIGHT_GREY_2;
99
100// Number of entries across and down the hit-test grid:
101#define HITTEST_SIZE 20
102
103// How close the pointer needs to be to a station to be considered:
104#define MEASURE_THRESHOLD 7
105
106// vector for lighting angle
107static const Vector3 light(.577, .577, .577);
108
109BEGIN_EVENT_TABLE(GfxCore, GLACanvas)
110    EVT_PAINT(GfxCore::OnPaint)
111    EVT_LEFT_DOWN(GfxCore::OnLButtonDown)
112    EVT_LEFT_UP(GfxCore::OnLButtonUp)
113    EVT_MIDDLE_DOWN(GfxCore::OnMButtonDown)
114    EVT_MIDDLE_UP(GfxCore::OnMButtonUp)
115    EVT_RIGHT_DOWN(GfxCore::OnRButtonDown)
116    EVT_RIGHT_UP(GfxCore::OnRButtonUp)
117    EVT_MOUSEWHEEL(GfxCore::OnMouseWheel)
118    EVT_MOTION(GfxCore::OnMouseMove)
119    EVT_LEAVE_WINDOW(GfxCore::OnLeaveWindow)
120    EVT_SIZE(GfxCore::OnSize)
121    EVT_IDLE(GfxCore::OnIdle)
122    EVT_CHAR(GfxCore::OnKeyPress)
123END_EVENT_TABLE()
124
125GfxCore::GfxCore(MainFrm* parent, wxWindow* parent_win, GUIControl* control) :
126    GLACanvas(parent_win, 100),
127    m_Scale(0.0),
128    initial_scale(1.0),
129    m_ScaleBarWidth(0),
130    m_Control(control),
131    m_LabelGrid(NULL),
132    m_Parent(parent),
133    m_DoneFirstShow(false),
134    m_TiltAngle(0.0),
135    m_PanAngle(0.0),
136    m_Rotating(false),
137    m_RotationStep(0.0),
138    m_SwitchingTo(0),
139    m_Crosses(false),
140    m_Legs(true),
141    m_Splays(SHOW_FADED),
142    m_Dupes(SHOW_DASHED),
143    m_Names(false),
144    m_Scalebar(true),
145    m_ColourKey(true),
146    m_OverlappingNames(false),
147    m_Compass(true),
148    m_Clino(true),
149    m_Tubes(false),
150    m_ColourBy(COLOUR_BY_DEPTH),
151    m_HaveData(false),
152    m_HaveTerrain(true),
153    m_MouseOutsideCompass(false),
154    m_MouseOutsideElev(false),
155    m_Surface(false),
156    m_Entrances(false),
157    m_FixedPts(false),
158    m_ExportedPts(false),
159    m_Grid(false),
160    m_BoundingBox(false),
161    m_Terrain(false),
162    m_Degrees(false),
163    m_Metric(false),
164    m_Percent(false),
165    m_HitTestDebug(false),
166    m_RenderStats(false),
167    m_PointGrid(NULL),
168    m_HitTestGridValid(false),
169    m_here(NULL),
170    m_there(NULL),
171    presentation_mode(0),
172    pres_reverse(false),
173    pres_speed(0.0),
174    movie(NULL),
175    current_cursor(GfxCore::CURSOR_DEFAULT),
176    sqrd_measure_threshold(sqrd(MEASURE_THRESHOLD)),
177    dem(NULL),
178    last_time(0),
179    n_tris(0)
180{
181    AddQuad = &GfxCore::AddQuadrilateralDepth;
182    AddPoly = &GfxCore::AddPolylineDepth;
183    wxConfigBase::Get()->Read(wxT("metric"), &m_Metric, true);
184    wxConfigBase::Get()->Read(wxT("degrees"), &m_Degrees, true);
185    wxConfigBase::Get()->Read(wxT("percent"), &m_Percent, false);
186
187    for (int pen = 0; pen < NUM_COLOUR_BANDS + 1; ++pen) {
188        m_Pens[pen].SetColour(REDS[pen] / 255.0,
189                              GREENS[pen] / 255.0,
190                              BLUES[pen] / 255.0);
191    }
192
193    timer.Start();
194}
195
196GfxCore::~GfxCore()
197{
198    TryToFreeArrays();
199
200    delete[] m_PointGrid;
201}
202
203void GfxCore::TryToFreeArrays()
204{
205    // Free up any memory allocated for arrays.
206    delete[] m_LabelGrid;
207    m_LabelGrid = NULL;
208}
209
210//
211//  Initialisation methods
212//
213
214void GfxCore::Initialise(bool same_file)
215{
216    // Initialise the view from the parent holding the survey data.
217
218    TryToFreeArrays();
219
220    m_DoneFirstShow = false;
221
222    m_HitTestGridValid = false;
223    m_here = NULL;
224    m_there = NULL;
225
226    m_MouseOutsideCompass = m_MouseOutsideElev = false;
227
228    if (!same_file) {
229        // Apply default parameters unless reloading the same file.
230        DefaultParameters();
231    }
232
233    m_HaveData = true;
234
235    // Clear any cached OpenGL lists which depend on the data.
236    InvalidateList(LIST_SCALE_BAR);
237    InvalidateList(LIST_DEPTH_KEY);
238    InvalidateList(LIST_DATE_KEY);
239    InvalidateList(LIST_ERROR_KEY);
240    InvalidateList(LIST_GRADIENT_KEY);
241    InvalidateList(LIST_LENGTH_KEY);
242    InvalidateList(LIST_UNDERGROUND_LEGS);
243    InvalidateList(LIST_TUBES);
244    InvalidateList(LIST_SURFACE_LEGS);
245    InvalidateList(LIST_BLOBS);
246    InvalidateList(LIST_CROSSES);
247    InvalidateList(LIST_GRID);
248    InvalidateList(LIST_SHADOW);
249    InvalidateList(LIST_TERRAIN);
250
251    // Set diameter of the viewing volume.
252    auto ext = m_Parent->GetExtent();
253    double cave_diameter = sqrt(sqrd(ext.GetX()) +
254                                sqrd(ext.GetY()) +
255                                sqrd(ext.GetZ()));
256
257    // Allow for terrain.
258    double diameter = max(1000.0 * 2, cave_diameter * 2);
259
260    if (!same_file) {
261        SetVolumeDiameter(diameter);
262
263        // Set initial scale based on the size of the cave.
264        initial_scale = diameter / cave_diameter;
265        SetScale(initial_scale);
266    } else {
267        // Adjust the position when restricting the view to a subsurvey (or
268        // expanding the view to show the whole survey).
269        AddTranslation(m_Parent->GetOffset() - offsets);
270
271        // Try to keep the same scale, allowing for the
272        // cave having grown (or shrunk).
273        double rescale = GetVolumeDiameter() / diameter;
274        SetVolumeDiameter(diameter);
275        SetScale(GetScale() / rescale); // ?
276        initial_scale = initial_scale * rescale;
277    }
278
279    offsets = m_Parent->GetOffset();
280
281    ForceRefresh();
282}
283
284void GfxCore::FirstShow()
285{
286    GLACanvas::FirstShow();
287
288    const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
289    list<LabelInfo*>::iterator pos = m_Parent->GetLabelsNC();
290    while (pos != m_Parent->GetLabelsNCEnd()) {
291        LabelInfo* label = *pos++;
292        // Calculate and set the label width for use when plotting
293        // none-overlapping labels.
294        int ext_x;
295        GLACanvas::GetTextExtent(label->GetText(), &ext_x, NULL);
296        label->set_width(unsigned(ext_x) / quantise + 1);
297    }
298
299    m_DoneFirstShow = true;
300}
301
302//
303//  Recalculating methods
304//
305
306void GfxCore::SetScale(Double scale)
307{
308    if (scale < 0.05) {
309        scale = 0.05;
310    } else if (scale > GetVolumeDiameter()) {
311        scale = GetVolumeDiameter();
312    }
313
314    m_Scale = scale;
315    m_HitTestGridValid = false;
316    if (m_here && m_here == &temp_here) SetHere();
317
318    GLACanvas::SetScale(scale);
319}
320
321bool GfxCore::HasUndergroundLegs() const
322{
323    return m_Parent->HasUndergroundLegs();
324}
325
326bool GfxCore::HasSplays() const
327{
328    return m_Parent->HasSplays();
329}
330
331bool GfxCore::HasDupes() const
332{
333    return m_Parent->HasDupes();
334}
335
336bool GfxCore::HasSurfaceLegs() const
337{
338    return m_Parent->HasSurfaceLegs();
339}
340
341bool GfxCore::HasTubes() const
342{
343    return m_Parent->HasTubes();
344}
345
346void GfxCore::UpdateBlobs()
347{
348    InvalidateList(LIST_BLOBS);
349}
350
351//
352//  Event handlers
353//
354
355void GfxCore::OnLeaveWindow(wxMouseEvent&) {
356    SetHere();
357    ClearCoords();
358}
359
360void GfxCore::OnIdle(wxIdleEvent& event)
361{
362    // Handle an idle event.
363    if (Animating()) {
364        Animate();
365        // If still animating, we want more idle events.
366        if (Animating())
367            event.RequestMore();
368    } else {
369        // If we're idle, don't show a bogus FPS next time we render.
370        last_time = 0;
371    }
372}
373
374void GfxCore::OnPaint(wxPaintEvent&)
375{
376    // Redraw the window.
377
378#ifdef __WXMAC__
379    // Attempt to work around wxPaintDC assertion failure on macOS 10.14:
380    // https://trac.survex.com/ticket/101
381    bool reenable_assertions = false;
382    if (MacGetCGContextRef() == NULL && wxTheAssertHandler != NULL) {
383        wxTheAssertHandler = NULL;
384        reenable_assertions = true;
385    }
386#endif
387
388    // Get a graphics context.
389    wxPaintDC dc(this);
390
391#ifdef __WXMAC__
392    if (reenable_assertions) {
393        wxSetDefaultAssertHandler();
394    }
395#endif
396
397    if (m_HaveData) {
398        // Make sure we're initialised.
399        bool first_time = !m_DoneFirstShow;
400        if (first_time) {
401            FirstShow();
402        }
403
404        StartDrawing();
405
406        // Clear the background.
407        Clear();
408
409        // Set up model transformation matrix.
410        SetDataTransform();
411
412        if (m_Legs || m_Tubes) {
413            if (m_Tubes) {
414                EnableSmoothPolygons(true); // FIXME: allow false for wireframe view
415                DrawList(LIST_TUBES);
416                DisableSmoothPolygons();
417            }
418
419            // Draw the underground legs.  Do this last so that anti-aliasing
420            // works over polygons.
421            SetColour(col_GREEN);
422            DrawList(LIST_UNDERGROUND_LEGS);
423        }
424
425        if (m_Surface) {
426            // Draw the surface legs.
427            DrawList(LIST_SURFACE_LEGS);
428        }
429
430        if (m_BoundingBox) {
431            DrawShadowedBoundingBox();
432        }
433        if (m_Grid) {
434            // Draw the grid.
435            DrawList(LIST_GRID);
436        }
437
438        DrawList(LIST_BLOBS);
439
440        if (m_Crosses) {
441            DrawList(LIST_CROSSES);
442        }
443
444        if (m_Terrain) {
445            // Disable texturing while drawing terrain.
446            bool texturing = GetTextured();
447            if (texturing) GLACanvas::ToggleTextured();
448
449            // This is needed if blobs and/or crosses are drawn using lines -
450            // otherwise the terrain doesn't appear when they are enabled.
451            SetDataTransform();
452
453            // We don't want to be able to see the terrain through itself, so
454            // do a "Z-prepass" - plot the terrain once only updating the
455            // Z-buffer, then again with Z-clipping only plotting where the
456            // depth matches the value in the Z-buffer.
457            DrawListZPrepass(LIST_TERRAIN);
458
459            if (texturing) GLACanvas::ToggleTextured();
460        }
461
462        SetIndicatorTransform();
463
464        // Draw station names.
465        if (m_Names /*&& !m_Control->MouseDown() && !Animating()*/) {
466            SetColour(NAME_COLOUR);
467
468            if (m_OverlappingNames) {
469                SimpleDrawNames();
470            } else {
471                NattyDrawNames();
472            }
473        }
474
475        if (!highlighted_survey.empty()) {
476            HighlightSurvey();
477        }
478
479        if (m_HitTestDebug) {
480            // Show the hit test grid bucket sizes...
481            SetColour(m_HitTestGridValid ? col_LIGHT_GREY : col_DARK_GREY);
482            if (m_PointGrid) {
483                for (int i = 0; i != HITTEST_SIZE; ++i) {
484                    int x = (GetXSize() + 1) * i / HITTEST_SIZE + 2;
485                    for (int j = 0; j != HITTEST_SIZE; ++j) {
486                        int square = i + j * HITTEST_SIZE;
487                        unsigned long bucket_size = m_PointGrid[square].size();
488                        if (bucket_size) {
489                            int y = (GetYSize() + 1) * (HITTEST_SIZE - 1 - j) / HITTEST_SIZE;
490                            DrawIndicatorText(x, y, wxString::Format(wxT("%lu"), bucket_size));
491                        }
492                    }
493                }
494            }
495
496            EnableDashedLines();
497            BeginLines();
498            for (int i = 0; i != HITTEST_SIZE; ++i) {
499                int x = (GetXSize() + 1) * i / HITTEST_SIZE;
500                PlaceIndicatorVertex(x, 0);
501                PlaceIndicatorVertex(x, GetYSize());
502            }
503            for (int j = 0; j != HITTEST_SIZE; ++j) {
504                int y = (GetYSize() + 1) * (HITTEST_SIZE - 1 - j) / HITTEST_SIZE;
505                PlaceIndicatorVertex(0, y);
506                PlaceIndicatorVertex(GetXSize(), y);
507            }
508            EndLines();
509            DisableDashedLines();
510        }
511
512        long now = timer.Time();
513        if (m_RenderStats) {
514            // Show stats about rendering.
515            SetColour(col_TURQUOISE);
516            int y = GetYSize() - GetFontSize();
517            if (last_time != 0.0) {
518                // timer.Time() measure in milliseconds.
519                double fps = 1000.0 / (now - last_time);
520                DrawIndicatorText(1, y, wxString::Format(wxT("FPS:% 5.1f"), fps));
521            }
522            y -= GetFontSize();
523            DrawIndicatorText(1, y, wxString::Format(wxT("▲:%lu"), (unsigned long)n_tris));
524        }
525        last_time = now;
526
527        // Draw indicators.
528        //
529        // There's no advantage in generating an OpenGL list for the
530        // indicators since they change with almost every redraw (and
531        // sometimes several times between redraws).  This way we avoid
532        // the need to track when to update the indicator OpenGL list,
533        // and also avoid indicator update bugs when we don't quite get this
534        // right...
535        DrawIndicators();
536
537        if (zoombox.active()) {
538            SetColour(SEL_COLOUR);
539            EnableDashedLines();
540            BeginPolyline();
541            glaCoord Y = GetYSize();
542            PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y1);
543            PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y2);
544            PlaceIndicatorVertex(zoombox.x2, Y - zoombox.y2);
545            PlaceIndicatorVertex(zoombox.x2, Y - zoombox.y1);
546            PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y1);
547            EndPolyline();
548            DisableDashedLines();
549        } else if (MeasuringLineActive()) {
550            // Draw "here" and "there".
551            double hx, hy;
552            SetColour(HERE_COLOUR);
553            if (m_here) {
554                double dummy;
555                Transform(*m_here, &hx, &hy, &dummy);
556                if (m_here != &temp_here) DrawRing(hx, hy);
557            }
558            if (m_there) {
559                double tx, ty;
560                double dummy;
561                Transform(*m_there, &tx, &ty, &dummy);
562                if (m_here) {
563                    BeginLines();
564                    PlaceIndicatorVertex(hx, hy);
565                    PlaceIndicatorVertex(tx, ty);
566                    EndLines();
567                }
568                BeginBlobs();
569                DrawBlob(tx, ty);
570                EndBlobs();
571            }
572        }
573
574        FinishDrawing();
575    } else {
576        dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME));
577        dc.Clear();
578    }
579}
580
581void GfxCore::DrawBoundingBox()
582{
583    const Vector3 v = 0.5 * m_Parent->GetExtent();
584
585    SetColour(col_BLUE);
586    EnableDashedLines();
587    BeginPolyline();
588    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
589    PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
590    PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
591    PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
592    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
593    EndPolyline();
594    BeginPolyline();
595    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
596    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
597    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
598    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
599    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
600    EndPolyline();
601    BeginLines();
602    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
603    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
604    PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
605    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
606    PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
607    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
608    PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
609    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
610    EndLines();
611    DisableDashedLines();
612}
613
614void GfxCore::DrawShadowedBoundingBox()
615{
616    const Vector3 v = 0.5 * m_Parent->GetExtent();
617
618    DrawBoundingBox();
619
620    PolygonOffset(true);
621    SetColour(col_DARK_GREY);
622    BeginQuadrilaterals();
623    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
624    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
625    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
626    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
627    EndQuadrilaterals();
628    PolygonOffset(false);
629
630    DrawList(LIST_SHADOW);
631}
632
633void GfxCore::DrawGrid()
634{
635    // Draw the grid.
636    SetColour(col_RED);
637
638    // Calculate the extent of the survey, in metres across the screen plane.
639    Double m_across_screen = SurveyUnitsAcrossViewport();
640    // Calculate the length of the scale bar in metres.
641    //--move this elsewhere
642    Double size_snap = pow(10.0, floor(log10(0.75 * m_across_screen)));
643    Double t = m_across_screen * 0.75 / size_snap;
644    if (t >= 5.0) {
645        size_snap *= 5.0;
646    }
647    else if (t >= 2.0) {
648        size_snap *= 2.0;
649    }
650
651    Double grid_size = size_snap * 0.1;
652    Double edge = grid_size * 2.0;
653    auto ext = m_Parent->GetExtent();
654    Double grid_z = -ext.GetZ() * 0.5 - grid_size;
655    Double left = -ext.GetX() * 0.5 - edge;
656    Double right = ext.GetX() * 0.5 + edge;
657    Double bottom = -ext.GetY() * 0.5 - edge;
658    Double top = ext.GetY() * 0.5 + edge;
659    int count_x = (int) ceil((right - left) / grid_size);
660    int count_y = (int) ceil((top - bottom) / grid_size);
661    Double actual_right = left + count_x*grid_size;
662    Double actual_top = bottom + count_y*grid_size;
663
664    BeginLines();
665
666    for (int xc = 0; xc <= count_x; xc++) {
667        Double x = left + xc*grid_size;
668
669        PlaceVertex(x, bottom, grid_z);
670        PlaceVertex(x, actual_top, grid_z);
671    }
672
673    for (int yc = 0; yc <= count_y; yc++) {
674        Double y = bottom + yc*grid_size;
675        PlaceVertex(left, y, grid_z);
676        PlaceVertex(actual_right, y, grid_z);
677    }
678
679    EndLines();
680}
681
682int GfxCore::GetClinoOffset() const
683{
684    int result = INDICATOR_OFFSET_X;
685    if (m_Compass) {
686        result += 6 + GetCompassWidth() + INDICATOR_GAP;
687    }
688    return result;
689}
690
691void GfxCore::DrawTick(int angle_cw)
692{
693    const Double theta = rad(angle_cw);
694    const wxCoord length1 = INDICATOR_RADIUS;
695    const wxCoord length0 = length1 + TICK_LENGTH;
696    wxCoord x0 = wxCoord(length0 * sin(theta));
697    wxCoord y0 = wxCoord(length0 * cos(theta));
698    wxCoord x1 = wxCoord(length1 * sin(theta));
699    wxCoord y1 = wxCoord(length1 * cos(theta));
700
701    PlaceIndicatorVertex(x0, y0);
702    PlaceIndicatorVertex(x1, y1);
703}
704
705void GfxCore::DrawArrow(gla_colour col1, gla_colour col2) {
706    Vector3 p1(0, INDICATOR_RADIUS, 0);
707    Vector3 p2(INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 150deg
708    Vector3 p3(-INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 210deg
709    Vector3 pc(0, 0, 0);
710
711    DrawTriangle(col_LIGHT_GREY, col1, p2, p1, pc);
712    DrawTriangle(col_LIGHT_GREY, col2, p3, p1, pc);
713}
714
715void GfxCore::DrawCompass() {
716    // Ticks.
717    BeginLines();
718    for (int angle = 315; angle > 0; angle -= 45) {
719        DrawTick(angle);
720    }
721    SetColour(col_GREEN);
722    DrawTick(0);
723    EndLines();
724
725    // Compass background.
726    DrawCircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS);
727
728    // Compass arrow.
729    DrawArrow(col_INDICATOR_1, col_INDICATOR_2);
730}
731
732// Draw the non-rotating background to the clino.
733void GfxCore::DrawClinoBack() {
734    BeginLines();
735    for (int angle = 0; angle <= 180; angle += 90) {
736        DrawTick(angle);
737    }
738
739    SetColour(col_GREY);
740    PlaceIndicatorVertex(0, INDICATOR_RADIUS);
741    PlaceIndicatorVertex(0, -INDICATOR_RADIUS);
742    PlaceIndicatorVertex(0, 0);
743    PlaceIndicatorVertex(INDICATOR_RADIUS, 0);
744
745    EndLines();
746}
747
748void GfxCore::DrawClino() {
749    // Ticks.
750    SetColour(col_GREEN);
751    BeginLines();
752    DrawTick(0);
753    EndLines();
754
755    // Clino background.
756    DrawSemicircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS, 0);
757
758    // Elevation arrow.
759    DrawArrow(col_INDICATOR_2, col_INDICATOR_1);
760}
761
762void GfxCore::Draw2dIndicators()
763{
764    // Draw the compass and elevation indicators.
765
766    const int centre_y = INDICATOR_BOX_SIZE / 2 + INDICATOR_OFFSET_Y;
767
768    const int comp_centre_x = GetCompassXPosition();
769
770    if (m_Compass && !m_Parent->IsExtendedElevation()) {
771        // If the user is dragging the compass with the pointer outside the
772        // compass, we snap to 45 degree multiples, and the ticks go white.
773        SetColour(m_MouseOutsideCompass ? col_WHITE : col_LIGHT_GREY_2);
774        DrawList2D(LIST_COMPASS, comp_centre_x, centre_y, -m_PanAngle);
775    }
776
777    const int elev_centre_x = GetClinoXPosition();
778
779    if (m_Clino) {
780        // If the user is dragging the clino with the pointer outside the
781        // clino, we snap to 90 degree multiples, and the ticks go white.
782        SetColour(m_MouseOutsideElev ? col_WHITE : col_LIGHT_GREY_2);
783        DrawList2D(LIST_CLINO_BACK, elev_centre_x, centre_y, 0);
784        DrawList2D(LIST_CLINO, elev_centre_x, centre_y, 90 - m_TiltAngle);
785    }
786
787    SetColour(TEXT_COLOUR);
788
789    static int triple_zero_width = 0;
790    static int height = 0;
791    if (!triple_zero_width) {
792        GetTextExtent(wxT("000"), &triple_zero_width, &height);
793    }
794    const int y_off = INDICATOR_OFFSET_Y + INDICATOR_BOX_SIZE + height / 2;
795
796    if (m_Compass && !m_Parent->IsExtendedElevation()) {
797        wxString str;
798        int value;
799        int brg_unit;
800        if (m_Degrees) {
801            value = int(m_PanAngle);
802            /* TRANSLATORS: degree symbol - probably should be translated to
803             * itself. */
804            brg_unit = /*°*/344;
805        } else {
806            value = int(m_PanAngle * 200.0 / 180.0);
807            /* TRANSLATORS: symbol for grad (400 grad = 360 degrees = full
808             * circle). */
809            brg_unit = /*ᵍ*/345;
810        }
811        str.Printf(wxT("%03d"), value);
812        str += wmsg(brg_unit);
813        DrawIndicatorText(comp_centre_x - triple_zero_width / 2, y_off, str);
814
815        // TRANSLATORS: Used in aven above the compass indicator at the lower
816        // right of the display, with a bearing below "Facing".  This indicates the
817        // direction the viewer is "facing" in.
818        //
819        // Try to keep this translation short - ideally at most 10 characters -
820        // as otherwise the compass and clino will be moved further apart to
821        // make room. */
822        str = wmsg(/*Facing*/203);
823        int w;
824        GetTextExtent(str, &w, NULL);
825        DrawIndicatorText(comp_centre_x - w / 2, y_off + height, str);
826    }
827
828    if (m_Clino) {
829        if (m_TiltAngle == -90.0) {
830            // TRANSLATORS: Label used for "clino" in Aven when the view is
831            // from directly above.
832            //
833            // Try to keep this translation short - ideally at most 10
834            // characters - as otherwise the compass and clino will be moved
835            // further apart to make room. */
836            wxString str = wmsg(/*Plan*/432);
837            static int width = 0;
838            if (!width) {
839                GetTextExtent(str, &width, NULL);
840            }
841            int x = elev_centre_x - width / 2;
842            DrawIndicatorText(x, y_off + height / 2, str);
843        } else if (m_TiltAngle == 90.0) {
844            // TRANSLATORS: Label used for "clino" in Aven when the view is
845            // from directly below.
846            //
847            // Try to keep this translation short - ideally at most 10
848            // characters - as otherwise the compass and clino will be moved
849            // further apart to make room. */
850            wxString str = wmsg(/*Kiwi Plan*/433);
851            static int width = 0;
852            if (!width) {
853                GetTextExtent(str, &width, NULL);
854            }
855            int x = elev_centre_x - width / 2;
856            DrawIndicatorText(x, y_off + height / 2, str);
857        } else {
858            int angle;
859            wxString str;
860            int width;
861            int unit;
862            if (m_Percent) {
863                static int zero_width = 0;
864                if (!zero_width) {
865                    GetTextExtent(wxT("0"), &zero_width, NULL);
866                }
867                width = zero_width;
868                if (m_TiltAngle > 89.99) {
869                    angle = 1000000;
870                } else if (m_TiltAngle < -89.99) {
871                    angle = -1000000;
872                } else {
873                    angle = int(100 * tan(rad(m_TiltAngle)));
874                }
875                if (angle > 99999 || angle < -99999) {
876                    str = angle > 0 ? wxT("+") : wxT("-");
877                    /* TRANSLATORS: infinity symbol - used for the percentage gradient on
878                     * vertical angles. */
879                    str += wmsg(/*∞*/431);
880                } else {
881                    str = angle ? wxString::Format(wxT("%+03d"), angle) : wxT("0");
882                }
883                /* TRANSLATORS: symbol for percentage gradient (100% = 45
884                 * degrees = 50 grad). */
885                unit = /*%*/96;
886            } else if (m_Degrees) {
887                static int zero_zero_width = 0;
888                if (!zero_zero_width) {
889                    GetTextExtent(wxT("00"), &zero_zero_width, NULL);
890                }
891                width = zero_zero_width;
892                angle = int(m_TiltAngle);
893                str = angle ? wxString::Format(wxT("%+03d"), angle) : wxT("00");
894                unit = /*°*/344;
895            } else {
896                width = triple_zero_width;
897                angle = int(m_TiltAngle * 200.0 / 180.0);
898                str = angle ? wxString::Format(wxT("%+04d"), angle) : wxT("000");
899                unit = /*ᵍ*/345;
900            }
901
902            int sign_offset = 0;
903            if (unit == /*%*/96) {
904                // Right align % since the width changes so much.
905                GetTextExtent(str, &sign_offset, NULL);
906                sign_offset -= width;
907            } else if (angle < 0) {
908                // Adjust horizontal position so the left of the first digit is
909                // always in the same place.
910                static int minus_width = 0;
911                if (!minus_width) {
912                    GetTextExtent(wxT("-"), &minus_width, NULL);
913                }
914                sign_offset = minus_width;
915            } else if (angle > 0) {
916                // Adjust horizontal position so the left of the first digit is
917                // always in the same place.
918                static int plus_width = 0;
919                if (!plus_width) {
920                    GetTextExtent(wxT("+"), &plus_width, NULL);
921                }
922                sign_offset = plus_width;
923            }
924
925            str += wmsg(unit);
926            DrawIndicatorText(elev_centre_x - sign_offset - width / 2, y_off, str);
927
928            // TRANSLATORS: Label used for "clino" in Aven when the view is
929            // neither from directly above nor from directly below.  It is
930            // also used in the dialog for editing a marked position in a
931            // presentation.
932            //
933            // Try to keep this translation short - ideally at most 10
934            // characters - as otherwise the compass and clino will be moved
935            // further apart to make room. */
936            str = wmsg(/*Elevation*/118);
937            static int elevation_width = 0;
938            if (!elevation_width) {
939                GetTextExtent(str, &elevation_width, NULL);
940            }
941            int x = elev_centre_x - elevation_width / 2;
942            DrawIndicatorText(x, y_off + height, str);
943        }
944    }
945}
946
947void GfxCore::NattyDrawNames()
948{
949    // Draw station names, without overlapping.
950
951    const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
952    const unsigned int quantised_x = GetXSize() / quantise;
953    const unsigned int quantised_y = GetYSize() / quantise;
954    const size_t buffer_size = quantised_x * quantised_y;
955
956    if (!m_LabelGrid) m_LabelGrid = new char[buffer_size];
957
958    memset((void*) m_LabelGrid, 0, buffer_size);
959
960    const SurveyFilter* filter = m_Parent->GetTreeFilter();
961    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
962    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
963        if (m_Splays == SHOW_HIDE && (*label)->IsSplayEnd())
964            continue;
965
966        if (!((m_Surface && (*label)->IsSurface()) ||
967              (m_Legs && (*label)->IsUnderground()) ||
968              (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
969            // if this station isn't to be displayed, skip to the next
970            // (last case is for stns with no legs attached)
971            continue;
972        }
973        if (filter && !filter->CheckVisible((*label)->GetText()))
974            continue;
975
976        double x, y, z;
977
978        Transform(**label, &x, &y, &z);
979        // Check if the label is behind us (in perspective view).
980        if (z <= 0.0 || z >= 1.0) continue;
981
982        // Apply a small shift so that translating the view doesn't make which
983        // labels are displayed change as the resulting twinkling effect is
984        // distracting.
985        double tx, ty, tz;
986        Transform(Vector3(), &tx, &ty, &tz);
987        tx -= floor(tx / quantise) * quantise;
988        ty -= floor(ty / quantise) * quantise;
989
990        tx = x - tx;
991        if (tx < 0) continue;
992
993        ty = y - ty;
994        if (ty < 0) continue;
995
996        unsigned int iy = unsigned(ty) / quantise;
997        if (iy >= quantised_y) continue;
998        unsigned int width = (*label)->get_width();
999        unsigned int ix = unsigned(tx) / quantise;
1000        if (ix + width >= quantised_x) continue;
1001
1002        char * test = m_LabelGrid + ix + iy * quantised_x;
1003        if (memchr(test, 1, width)) continue;
1004
1005        x += 3;
1006        y -= GetFontSize() / 2;
1007        DrawIndicatorText((int)x, (int)y, (*label)->GetText());
1008
1009        if (iy > QUANTISE_FACTOR) iy = QUANTISE_FACTOR;
1010        test -= quantised_x * iy;
1011        iy += 4;
1012        while (--iy && test < m_LabelGrid + buffer_size) {
1013            memset(test, 1, width);
1014            test += quantised_x;
1015        }
1016    }
1017}
1018
1019void GfxCore::SimpleDrawNames()
1020{
1021    const SurveyFilter* filter = m_Parent->GetTreeFilter();
1022    // Draw all station names, without worrying about overlaps
1023    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
1024    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
1025        if (m_Splays == SHOW_HIDE && (*label)->IsSplayEnd())
1026            continue;
1027
1028        if (!((m_Surface && (*label)->IsSurface()) ||
1029              (m_Legs && (*label)->IsUnderground()) ||
1030              (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
1031            // if this station isn't to be displayed, skip to the next
1032            // (last case is for stns with no legs attached)
1033            continue;
1034        }
1035        if (filter && !filter->CheckVisible((*label)->GetText()))
1036            continue;
1037
1038        double x, y, z;
1039        Transform(**label, &x, &y, &z);
1040
1041        // Check if the label is behind us (in perspective view).
1042        if (z <= 0) continue;
1043
1044        x += 3;
1045        y -= GetFontSize() / 2;
1046        DrawIndicatorText((int)x, (int)y, (*label)->GetText());
1047    }
1048}
1049
1050void GfxCore::DrawColourKey(int num_bands, const wxString & other, const wxString & units)
1051{
1052    int total_block_height =
1053        KEY_BLOCK_HEIGHT * (num_bands == 1 ? num_bands : num_bands - 1);
1054    if (!other.empty()) total_block_height += KEY_BLOCK_HEIGHT * 2;
1055    if (!units.empty()) total_block_height += KEY_BLOCK_HEIGHT;
1056
1057    const int bottom = -total_block_height;
1058
1059    int size = 0;
1060    if (!other.empty()) GetTextExtent(other, &size, NULL);
1061    int band;
1062    for (band = 0; band < num_bands; ++band) {
1063        int x;
1064        GetTextExtent(key_legends[band], &x, NULL);
1065        if (x > size) size = x;
1066    }
1067
1068    int left = -KEY_BLOCK_WIDTH - size;
1069
1070    key_lowerleft[m_ColourBy].x = left - KEY_EXTRA_LEFT_MARGIN;
1071    key_lowerleft[m_ColourBy].y = bottom;
1072
1073    int y = bottom;
1074    if (!units.empty()) y += KEY_BLOCK_HEIGHT;
1075
1076    if (!other.empty()) {
1077        DrawRectangle(NODATA_COLOUR, col_BLACK,
1078                      left, y,
1079                      KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1080        y += KEY_BLOCK_HEIGHT * 2;
1081    }
1082
1083    int start = y;
1084    if (num_bands == 1) {
1085        DrawShadedRectangle(GetPen(0), GetPen(0), left, y,
1086                            KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1087        y += KEY_BLOCK_HEIGHT;
1088    } else {
1089        for (band = 0; band < num_bands - 1; ++band) {
1090            DrawShadedRectangle(GetPen(band), GetPen(band + 1), left, y,
1091                                KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1092            y += KEY_BLOCK_HEIGHT;
1093        }
1094    }
1095
1096    SetColour(col_BLACK);
1097    BeginPolyline();
1098    PlaceIndicatorVertex(left, y);
1099    PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, y);
1100    PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, start);
1101    PlaceIndicatorVertex(left, start);
1102    PlaceIndicatorVertex(left, y);
1103    EndPolyline();
1104
1105    SetColour(TEXT_COLOUR);
1106
1107    y = bottom;
1108    if (!units.empty()) {
1109        GetTextExtent(units, &size, NULL);
1110        DrawIndicatorText(left + (KEY_BLOCK_WIDTH - size) / 2, y, units);
1111        y += KEY_BLOCK_HEIGHT;
1112    }
1113    y -= GetFontSize() / 2;
1114    left += KEY_BLOCK_WIDTH + 5;
1115
1116    if (!other.empty()) {
1117        y += KEY_BLOCK_HEIGHT / 2;
1118        DrawIndicatorText(left, y, other);
1119        y += KEY_BLOCK_HEIGHT * 2 - KEY_BLOCK_HEIGHT / 2;
1120    }
1121
1122    if (num_bands == 1) {
1123        y += KEY_BLOCK_HEIGHT / 2;
1124        DrawIndicatorText(left, y, key_legends[0]);
1125    } else {
1126        for (band = 0; band < num_bands; ++band) {
1127            DrawIndicatorText(left, y, key_legends[band]);
1128            y += KEY_BLOCK_HEIGHT;
1129        }
1130    }
1131}
1132
1133void GfxCore::DrawDepthKey()
1134{
1135    Double z_ext = m_Parent->GetDepthExtent();
1136    int num_bands = 1;
1137    int sf = 0;
1138    if (z_ext > 0.0) {
1139        num_bands = GetNumColourBands();
1140        Double z_range = z_ext;
1141        if (!m_Metric) z_range /= METRES_PER_FOOT;
1142        sf = max(0, 1 - (int)floor(log10(z_range)));
1143    }
1144
1145    Double z_min = m_Parent->GetDepthMin() + m_Parent->GetOffset().GetZ();
1146    for (int band = 0; band < num_bands; ++band) {
1147        Double z = z_min;
1148        if (band)
1149            z += z_ext * band / (num_bands - 1);
1150
1151        if (!m_Metric)
1152            z /= METRES_PER_FOOT;
1153
1154        key_legends[band].Printf(wxT("%.*f"), sf, z);
1155    }
1156
1157    DrawColourKey(num_bands, wxString(), wmsg(m_Metric ? /*m*/424: /*ft*/428));
1158}
1159
1160void GfxCore::DrawDateKey()
1161{
1162    int num_bands;
1163    if (!HasDateInformation()) {
1164        num_bands = 0;
1165    } else {
1166        int date_ext = m_Parent->GetDateExtent();
1167        if (date_ext == 0) {
1168            num_bands = 1;
1169        } else {
1170            num_bands = GetNumColourBands();
1171        }
1172        for (int band = 0; band < num_bands; ++band) {
1173            int y, m, d;
1174            int days = m_Parent->GetDateMin();
1175            if (band)
1176                days += date_ext * band / (num_bands - 1);
1177            ymd_from_days_since_1900(days, &y, &m, &d);
1178            key_legends[band].Printf(wxT("%04d-%02d-%02d"), y, m, d);
1179        }
1180    }
1181
1182    wxString other;
1183    if (!m_Parent->HasCompleteDateInfo()) {
1184        /* TRANSLATORS: Used in the "colour key" for "colour by date" if there
1185         * are surveys without date information.  Try to keep this fairly short.
1186         */
1187        other = wmsg(/*Undated*/221);
1188    }
1189
1190    DrawColourKey(num_bands, other, wxString());
1191}
1192
1193void GfxCore::DrawErrorKey()
1194{
1195    int num_bands;
1196    if (HasErrorInformation()) {
1197        // Use fixed colours for each error factor so it's directly visually
1198        // comparable between surveys.
1199        num_bands = GetNumColourBands();
1200        for (int band = 0; band < num_bands; ++band) {
1201            double E = MAX_ERROR * band / (num_bands - 1);
1202            key_legends[band].Printf(wxT("%.2f"), E);
1203        }
1204    } else {
1205        num_bands = 0;
1206    }
1207
1208    // Always show the "Not in loop" legend for now (FIXME).
1209    /* TRANSLATORS: Used in the "colour key" for "colour by error" for surveys
1210     * which aren’t part of a loop and so have no error information. Try to keep
1211     * this fairly short. */
1212    DrawColourKey(num_bands, wmsg(/*Not in loop*/290), wxString());
1213}
1214
1215void GfxCore::DrawGradientKey()
1216{
1217    int num_bands;
1218    // Use fixed colours for each gradient so it's directly visually comparable
1219    // between surveys.
1220    num_bands = GetNumColourBands();
1221    wxString units = wmsg(m_Degrees ? /*°*/344 : /*ᵍ*/345);
1222    for (int band = 0; band < num_bands; ++band) {
1223        double gradient = double(band) / (num_bands - 1);
1224        if (m_Degrees) {
1225            gradient *= 90.0;
1226        } else {
1227            gradient *= 100.0;
1228        }
1229        key_legends[band].Printf(wxT("%.f%s"), gradient, units);
1230    }
1231
1232    DrawColourKey(num_bands, wxString(), wxString());
1233}
1234
1235void GfxCore::DrawLengthKey()
1236{
1237    int num_bands;
1238    // Use fixed colours for each length so it's directly visually comparable
1239    // between surveys.
1240    num_bands = GetNumColourBands();
1241    for (int band = 0; band < num_bands; ++band) {
1242        double len = pow(10, LOG_LEN_MAX * band / (num_bands - 1));
1243        if (!m_Metric) {
1244            len /= METRES_PER_FOOT;
1245        }
1246        key_legends[band].Printf(wxT("%.1f"), len);
1247    }
1248
1249    DrawColourKey(num_bands, wxString(), wmsg(m_Metric ? /*m*/424: /*ft*/428));
1250}
1251
1252void GfxCore::DrawScaleBar()
1253{
1254    // Calculate how many metres of survey are currently displayed across the
1255    // screen.
1256    Double across_screen = SurveyUnitsAcrossViewport();
1257
1258    double f = double(GetClinoXPosition() - INDICATOR_BOX_SIZE / 2 - SCALE_BAR_OFFSET_X) / GetXSize();
1259    if (f > 0.75) {
1260        f = 0.75;
1261    } else if (f < 0.5) {
1262        // Stop it getting squeezed to nothing.
1263        // FIXME: In this case we should probably move the compass and clino up
1264        // to make room rather than letting stuff overlap.
1265        f = 0.5;
1266    }
1267
1268    // Convert to imperial measurements if required.
1269    Double multiplier = 1.0;
1270    if (!m_Metric) {
1271        across_screen /= METRES_PER_FOOT;
1272        multiplier = METRES_PER_FOOT;
1273        if (across_screen >= 5280.0 / f) {
1274            across_screen /= 5280.0;
1275            multiplier *= 5280.0;
1276        }
1277    }
1278
1279    // Calculate the length of the scale bar.
1280    Double size_snap = pow(10.0, floor(log10(f * across_screen)));
1281    Double t = across_screen * f / size_snap;
1282    if (t >= 5.0) {
1283        size_snap *= 5.0;
1284    } else if (t >= 2.0) {
1285        size_snap *= 2.0;
1286    }
1287
1288    if (!m_Metric) size_snap *= multiplier;
1289
1290    // Actual size of the thing in pixels:
1291    int size = int((size_snap / SurveyUnitsAcrossViewport()) * GetXSize());
1292    m_ScaleBarWidth = size;
1293
1294    // Draw it...
1295    const int end_y = SCALE_BAR_OFFSET_Y + SCALE_BAR_HEIGHT;
1296    int interval = size / 10;
1297
1298    gla_colour col = col_WHITE;
1299    for (int ix = 0; ix < 10; ix++) {
1300        int x = SCALE_BAR_OFFSET_X + int(ix * ((Double) size / 10.0));
1301
1302        DrawRectangle(col, col, x, end_y, interval + 2, SCALE_BAR_HEIGHT);
1303
1304        col = (col == col_WHITE) ? col_GREY : col_WHITE;
1305    }
1306
1307    // Add labels.
1308    wxString str;
1309    int units;
1310    if (m_Metric) {
1311        Double km = size_snap * 1e-3;
1312        if (km >= 1.0) {
1313            size_snap = km;
1314            /* TRANSLATORS: abbreviation for "kilometres" (unit of length),
1315             * used e.g.  "5km".
1316             *
1317             * If there should be a space between the number and this, include
1318             * one in the translation. */
1319            units = /*km*/423;
1320        } else if (size_snap >= 1.0) {
1321            /* TRANSLATORS: abbreviation for "metres" (unit of length), used
1322             * e.g. "10m".
1323             *
1324             * If there should be a space between the number and this, include
1325             * one in the translation. */
1326            units = /*m*/424;
1327        } else {
1328            size_snap *= 1e2;
1329            /* TRANSLATORS: abbreviation for "centimetres" (unit of length),
1330             * used e.g.  "50cm".
1331             *
1332             * If there should be a space between the number and this, include
1333             * one in the translation. */
1334            units = /*cm*/425;
1335        }
1336    } else {
1337        size_snap /= METRES_PER_FOOT;
1338        Double miles = size_snap / 5280.0;
1339        if (miles >= 1.0) {
1340            size_snap = miles;
1341            if (size_snap >= 2.0) {
1342                /* TRANSLATORS: abbreviation for "miles" (unit of length,
1343                 * plural), used e.g.  "2 miles".
1344                 *
1345                 * If there should be a space between the number and this,
1346                 * include one in the translation. */
1347                units = /* miles*/426;
1348            } else {
1349                /* TRANSLATORS: abbreviation for "mile" (unit of length,
1350                 * singular), used e.g.  "1 mile".
1351                 *
1352                 * If there should be a space between the number and this,
1353                 * include one in the translation. */
1354                units = /* mile*/427;
1355            }
1356        } else if (size_snap >= 1.0) {
1357            /* TRANSLATORS: abbreviation for "feet" (unit of length), used e.g.
1358             * as "10ft".
1359             *
1360             * If there should be a space between the number and this, include
1361             * one in the translation. */
1362            units = /*ft*/428;
1363        } else {
1364            size_snap *= 12.0;
1365            /* TRANSLATORS: abbreviation for "inches" (unit of length), used
1366             * e.g. as "6in".
1367             *
1368             * If there should be a space between the number and this, include
1369             * one in the translation. */
1370            units = /*in*/429;
1371        }
1372    }
1373    if (size_snap >= 1.0) {
1374        str.Printf(wxT("%.f%s"), size_snap, wmsg(units).c_str());
1375    } else {
1376        int sf = -(int)floor(log10(size_snap));
1377        str.Printf(wxT("%.*f%s"), sf, size_snap, wmsg(units).c_str());
1378    }
1379
1380    int text_width, text_height;
1381    GetTextExtent(str, &text_width, &text_height);
1382    const int text_y = end_y - text_height + 1;
1383    SetColour(TEXT_COLOUR);
1384    DrawIndicatorText(SCALE_BAR_OFFSET_X, text_y, wxT("0"));
1385    DrawIndicatorText(SCALE_BAR_OFFSET_X + size - text_width, text_y, str);
1386}
1387
1388bool GfxCore::CheckHitTestGrid(const wxPoint& point, bool centre)
1389{
1390    if (Animating()) return false;
1391
1392    if (point.x < 0 || point.x >= GetXSize() ||
1393        point.y < 0 || point.y >= GetYSize()) {
1394        return false;
1395    }
1396
1397    SetDataTransform();
1398
1399    if (!m_HitTestGridValid) CreateHitTestGrid();
1400
1401    int grid_x = point.x * HITTEST_SIZE / (GetXSize() + 1);
1402    int grid_y = point.y * HITTEST_SIZE / (GetYSize() + 1);
1403
1404    LabelInfo *best = NULL;
1405    int dist_sqrd = sqrd_measure_threshold;
1406    int square = grid_x + grid_y * HITTEST_SIZE;
1407    list<LabelInfo*>::iterator iter = m_PointGrid[square].begin();
1408
1409    while (iter != m_PointGrid[square].end()) {
1410        LabelInfo *pt = *iter++;
1411
1412        double cx, cy, cz;
1413
1414        Transform(*pt, &cx, &cy, &cz);
1415
1416        cy = GetYSize() - cy;
1417
1418        int dx = point.x - int(cx);
1419        int ds = dx * dx;
1420        if (ds >= dist_sqrd) continue;
1421        int dy = point.y - int(cy);
1422
1423        ds += dy * dy;
1424        if (ds >= dist_sqrd) continue;
1425
1426        dist_sqrd = ds;
1427        best = pt;
1428
1429        if (ds == 0) break;
1430    }
1431
1432    if (best) {
1433        m_Parent->ShowInfo(best, m_there);
1434        if (centre) {
1435            // FIXME: allow Ctrl-Click to not set there or something?
1436            CentreOn(*best);
1437            WarpPointer(GetXSize() / 2, GetYSize() / 2);
1438            SetThere(best);
1439            m_Parent->SelectTreeItem(best);
1440        }
1441    } else {
1442        // Left-clicking not on a survey cancels the measuring line.
1443        if (centre) {
1444            ClearTreeSelection();
1445        } else {
1446            m_Parent->ShowInfo(best, m_there);
1447            double x, y, z;
1448            ReverseTransform(point.x, GetYSize() - point.y, &x, &y, &z);
1449            temp_here.assign(Vector3(x, y, z));
1450            SetHere(&temp_here);
1451        }
1452    }
1453
1454    return best;
1455}
1456
1457void GfxCore::OnSize(wxSizeEvent& event)
1458{
1459    // Handle a change in window size.
1460    wxSize size = event.GetSize();
1461
1462    if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
1463        // Before things are fully initialised, we sometimes get a bogus
1464        // resize message...
1465        // FIXME have changes in MainFrm cured this?  It still happens with
1466        // 1.0.32 and wxGTK 2.5.2 (load a file from the command line).
1467        // With 1.1.6 and wxGTK 2.4.2 we only get negative sizes if MainFrm
1468        // is resized such that the GfxCore window isn't visible.
1469        //printf("OnSize(%d,%d)\n", size.GetWidth(), size.GetHeight());
1470        return;
1471    }
1472
1473    event.Skip();
1474
1475    if (m_DoneFirstShow) {
1476        TryToFreeArrays();
1477
1478        m_HitTestGridValid = false;
1479
1480        ForceRefresh();
1481    }
1482}
1483
1484void GfxCore::DefaultParameters()
1485{
1486    // Set default viewing parameters.
1487
1488    m_Surface = false;
1489    if (!m_Parent->HasUndergroundLegs()) {
1490        if (m_Parent->HasSurfaceLegs()) {
1491            // If there are surface legs, but no underground legs, turn
1492            // surface surveys on.
1493            m_Surface = true;
1494        } else {
1495            // If there are no legs (e.g. after loading a .pos file), turn
1496            // crosses on.
1497            m_Crosses = true;
1498        }
1499    }
1500
1501    m_PanAngle = 0.0;
1502    if (m_Parent->IsExtendedElevation()) {
1503        m_TiltAngle = 0.0;
1504    } else {
1505        m_TiltAngle = -90.0;
1506    }
1507
1508    SetRotation(m_PanAngle, m_TiltAngle);
1509    SetTranslation(Vector3());
1510
1511    m_RotationStep = 30.0;
1512    m_Rotating = false;
1513    m_SwitchingTo = 0;
1514    m_Entrances = false;
1515    m_FixedPts = false;
1516    m_ExportedPts = false;
1517    m_Grid = false;
1518    m_BoundingBox = false;
1519    m_Tubes = false;
1520    if (GetPerspective()) TogglePerspective();
1521
1522    // Set the initial scale.
1523    SetScale(initial_scale);
1524}
1525
1526void GfxCore::Defaults()
1527{
1528    // Restore default scale, rotation and translation parameters.
1529    DefaultParameters();
1530
1531    // Invalidate all the cached lists.
1532    GLACanvas::FirstShow();
1533
1534    ForceRefresh();
1535}
1536
1537void GfxCore::Animate()
1538{
1539    // Don't show pointer coordinates while animating.
1540    // FIXME : only do this when we *START* animating!  Use a static copy
1541    // of the value of "Animating()" last time we were here to track this?
1542    // MainFrm now checks if we're trying to clear already cleared labels
1543    // and just returns, but it might be simpler to check here!
1544    ClearCoords();
1545    m_Parent->ShowInfo();
1546
1547    long t;
1548    if (movie) {
1549        ReadPixels(movie->GetWidth(), movie->GetHeight(), movie->GetBuffer());
1550        if (!movie->AddFrame()) {
1551            wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
1552            delete movie;
1553            movie = NULL;
1554            presentation_mode = 0;
1555            return;
1556        }
1557        t = 1000 / 25; // 25 frames per second
1558    } else {
1559        static long t_prev = 0;
1560        t = timer.Time();
1561        // Avoid redrawing twice in the same frame.
1562        long delta_t = (t_prev == 0 ? 1000 / MAX_FRAMERATE : t - t_prev);
1563        if (delta_t < 1000 / MAX_FRAMERATE)
1564            return;
1565        t_prev = t;
1566        if (presentation_mode == PLAYING && pres_speed != 0.0)
1567            t = delta_t;
1568    }
1569
1570    if (presentation_mode == PLAYING && pres_speed != 0.0) {
1571        // FIXME: It would probably be better to work relative to the time we
1572        // passed the last mark, but that's complicated by the speed
1573        // potentially changing (or even the direction of playback reversing)
1574        // at any point during playback.
1575        Double tick = t * 0.001 * fabs(pres_speed);
1576        while (tick >= next_mark_time) {
1577            tick -= next_mark_time;
1578            this_mark_total = 0;
1579            PresentationMark prev_mark = next_mark;
1580            if (prev_mark.angle < 0) prev_mark.angle += 360.0;
1581            else if (prev_mark.angle >= 360.0) prev_mark.angle -= 360.0;
1582            if (pres_reverse)
1583                next_mark = m_Parent->GetPresMark(MARK_PREV);
1584            else
1585                next_mark = m_Parent->GetPresMark(MARK_NEXT);
1586            if (!next_mark.is_valid()) {
1587                SetView(prev_mark);
1588                presentation_mode = 0;
1589                if (movie && !movie->Close()) {
1590                    wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
1591                }
1592                delete movie;
1593                movie = NULL;
1594                break;
1595            }
1596
1597            double tmp = (pres_reverse ? prev_mark.time : next_mark.time);
1598            if (tmp > 0) {
1599                next_mark_time = tmp;
1600            } else {
1601                double d = (next_mark - prev_mark).magnitude();
1602                // FIXME: should ignore component of d which is unseen in
1603                // non-perspective mode?
1604                next_mark_time = sqrd(d / 30.0);
1605                double a = next_mark.angle - prev_mark.angle;
1606                if (a > 180.0) {
1607                    next_mark.angle -= 360.0;
1608                    a = 360.0 - a;
1609                } else if (a < -180.0) {
1610                    next_mark.angle += 360.0;
1611                    a += 360.0;
1612                } else {
1613                    a = fabs(a);
1614                }
1615                next_mark_time += sqrd(a / 60.0);
1616                double ta = fabs(next_mark.tilt_angle - prev_mark.tilt_angle);
1617                next_mark_time += sqrd(ta / 60.0);
1618                double s = fabs(log(next_mark.scale) - log(prev_mark.scale));
1619                next_mark_time += sqrd(s / 2.0);
1620                next_mark_time = sqrt(next_mark_time);
1621                // was: next_mark_time = max(max(d / 30, s / 2), max(a, ta) / 60);
1622                //printf("*** %.6f from (\nd: %.6f\ns: %.6f\na: %.6f\nt: %.6f )\n",
1623                //       next_mark_time, d/30.0, s/2.0, a/60.0, ta/60.0);
1624                if (tmp < 0) next_mark_time /= -tmp;
1625            }
1626        }
1627
1628        if (presentation_mode) {
1629            // Advance position towards next_mark
1630            double p = tick / next_mark_time;
1631            double q = 1 - p;
1632            PresentationMark here = GetView();
1633            if (next_mark.angle < 0) {
1634                if (here.angle >= next_mark.angle + 360.0)
1635                    here.angle -= 360.0;
1636            } else if (next_mark.angle >= 360.0) {
1637                if (here.angle <= next_mark.angle - 360.0)
1638                    here.angle += 360.0;
1639            }
1640            here.assign(q * here + p * next_mark);
1641            here.angle = q * here.angle + p * next_mark.angle;
1642            if (here.angle < 0) here.angle += 360.0;
1643            else if (here.angle >= 360.0) here.angle -= 360.0;
1644            here.tilt_angle = q * here.tilt_angle + p * next_mark.tilt_angle;
1645            here.scale = exp(q * log(here.scale) + p * log(next_mark.scale));
1646            SetView(here);
1647            this_mark_total += tick;
1648            next_mark_time -= tick;
1649        }
1650
1651        ForceRefresh();
1652        return;
1653    }
1654
1655    // When rotating...
1656    if (m_Rotating) {
1657        Double step = base_pan + (t - base_pan_time) * 1e-3 * m_RotationStep - m_PanAngle;
1658        TurnCave(step);
1659    }
1660
1661    if (m_SwitchingTo == PLAN) {
1662        // When switching to plan view...
1663        Double step = base_tilt - (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1664        TiltCave(step);
1665        if (m_TiltAngle == -90.0) {
1666            m_SwitchingTo = 0;
1667        }
1668    } else if (m_SwitchingTo == ELEVATION) {
1669        // When switching to elevation view...
1670        Double step;
1671        if (m_TiltAngle > 0.0) {
1672            step = base_tilt - (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1673        } else {
1674            step = base_tilt + (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1675        }
1676        if (fabs(step) >= fabs(m_TiltAngle)) {
1677            m_SwitchingTo = 0;
1678            step = -m_TiltAngle;
1679        }
1680        TiltCave(step);
1681    } else if (m_SwitchingTo) {
1682        // Rotate the shortest way around to the destination angle.  If we're
1683        // 180 off, we favour turning anticlockwise, as auto-rotation does by
1684        // default.
1685        Double target = (m_SwitchingTo - NORTH) * 90;
1686        Double diff = target - m_PanAngle;
1687        diff = fmod(diff, 360);
1688        if (diff <= -180)
1689            diff += 360;
1690        else if (diff > 180)
1691            diff -= 360;
1692        if (m_RotationStep < 0 && diff == 180.0)
1693            diff = -180.0;
1694        Double step = base_pan - m_PanAngle;
1695        Double delta = (t - base_pan_time) * 1e-3 * fabs(m_RotationStep);
1696        if (diff > 0) {
1697            step += delta;
1698        } else {
1699            step -= delta;
1700        }
1701        step = fmod(step, 360);
1702        if (step <= -180)
1703            step += 360;
1704        else if (step > 180)
1705            step -= 360;
1706        if (fabs(step) >= fabs(diff)) {
1707            m_SwitchingTo = 0;
1708            step = diff;
1709        }
1710        TurnCave(step);
1711    }
1712
1713    ForceRefresh();
1714}
1715
1716// How much to allow around the box - this is because of the ring shape
1717// at one end of the line.
1718static const int HIGHLIGHTED_PT_SIZE = 2; // FIXME: tie in to blob and ring size
1719#define MARGIN (HIGHLIGHTED_PT_SIZE * 2 + 1)
1720void GfxCore::RefreshLine(const Point *a, const Point *b, const Point *c)
1721{
1722#ifdef __WXMSW__
1723    (void)a;
1724    (void)b;
1725    (void)c;
1726    // FIXME: We get odd redraw artifacts if we just update the line, and
1727    // redrawing the whole scene doesn't actually seem to be measurably
1728    // slower.  That may not be true with software rendering though...
1729    ForceRefresh();
1730#else
1731    // Best of all might be to copy the window contents before we draw the
1732    // line, then replace each time we redraw.
1733
1734    // Calculate the minimum rectangle which includes the old and new
1735    // measuring lines to minimise the redraw time
1736    int l = INT_MAX, r = INT_MIN, u = INT_MIN, d = INT_MAX;
1737    double X, Y, Z;
1738    if (a) {
1739        if (!Transform(*a, &X, &Y, &Z)) {
1740            printf("oops\n");
1741        } else {
1742            int x = int(X);
1743            int y = GetYSize() - 1 - int(Y);
1744            l = x;
1745            r = x;
1746            u = y;
1747            d = y;
1748        }
1749    }
1750    if (b) {
1751        if (!Transform(*b, &X, &Y, &Z)) {
1752            printf("oops\n");
1753        } else {
1754            int x = int(X);
1755            int y = GetYSize() - 1 - int(Y);
1756            l = min(l, x);
1757            r = max(r, x);
1758            u = max(u, y);
1759            d = min(d, y);
1760        }
1761    }
1762    if (c) {
1763        if (!Transform(*c, &X, &Y, &Z)) {
1764            printf("oops\n");
1765        } else {
1766            int x = int(X);
1767            int y = GetYSize() - 1 - int(Y);
1768            l = min(l, x);
1769            r = max(r, x);
1770            u = max(u, y);
1771            d = min(d, y);
1772        }
1773    }
1774    l -= MARGIN;
1775    r += MARGIN;
1776    u += MARGIN;
1777    d -= MARGIN;
1778    RefreshRect(wxRect(l, d, r - l, u - d), false);
1779#endif
1780}
1781
1782void GfxCore::HighlightSurvey()
1783{
1784    SurveyFilter filter;
1785    filter.add(highlighted_survey);
1786    filter.SetSeparator(m_Parent->GetSeparator());
1787
1788    double x_min = HUGE_VAL, x_max = -HUGE_VAL;
1789    double y_min = HUGE_VAL, y_max = -HUGE_VAL;
1790    double xpy_min = HUGE_VAL, xpy_max = -HUGE_VAL;
1791    double xmy_min = HUGE_VAL, xmy_max = -HUGE_VAL;
1792    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1793    double x_tot = 0, y_tot = 0;
1794    size_t c = 0;
1795    while (pos != m_Parent->GetLabelsEnd()) {
1796        const LabelInfo* label = *pos++;
1797        if (!filter.CheckVisible(label->GetText()))
1798            continue;
1799
1800        double x, y, z;
1801        Transform(*label, &x, &y, &z);
1802        if (x < x_min) x_min = x;
1803        if (x > x_max) x_max = x;
1804        if (y < y_min) y_min = y;
1805        if (y > y_max) y_max = y;
1806        double xpy = x + y;
1807        if (xpy < xpy_min) xpy_min = xpy;
1808        if (xpy > xpy_max) xpy_max = xpy;
1809        double xmy = x - y;
1810        if (xmy < xmy_min) xmy_min = xmy;
1811        if (xmy > xmy_max) xmy_max = xmy;
1812        x_tot += x;
1813        y_tot += y;
1814        ++c;
1815    }
1816    for (int f = 0; f != 8; ++f) {
1817        list<traverse>::const_iterator trav = m_Parent->traverses_begin(f, &filter);
1818        list<traverse>::const_iterator tend = m_Parent->traverses_end(f);
1819        while (trav != tend) {
1820            for (auto&& p : *trav) {
1821                double x, y, z;
1822                Transform(p, &x, &y, &z);
1823                if (x < x_min) x_min = x;
1824                if (x > x_max) x_max = x;
1825                if (y < y_min) y_min = y;
1826                if (y > y_max) y_max = y;
1827                double xpy = x + y;
1828                if (xpy < xpy_min) xpy_min = xpy;
1829                if (xpy > xpy_max) xpy_max = xpy;
1830                double xmy = x - y;
1831                if (xmy < xmy_min) xmy_min = xmy;
1832                if (xmy > xmy_max) xmy_max = xmy;
1833                x_tot += x;
1834                y_tot += y;
1835                ++c;
1836            }
1837            trav = m_Parent->traverses_next(f, &filter, trav);
1838        }
1839    }
1840
1841    if (c == 0) return;
1842
1843    // Minimum margin around survey.
1844    const double M = 4.0;
1845    // X/Y component when M measured diagonally.
1846    const double D = M * sqrt(2.0) / 2.0;
1847
1848    SetColour(col_WHITE);
1849    BeginPolyloop();
1850    PlaceIndicatorVertex(xmy_max + y_min, y_min - M);
1851    PlaceIndicatorVertex(xmy_max + y_min + D, y_min - D);
1852    PlaceIndicatorVertex(x_max + D, x_max - xmy_max - D);
1853    PlaceIndicatorVertex(x_max + M, x_max - xmy_max);
1854    PlaceIndicatorVertex(x_max + M, xpy_max - x_max);
1855    PlaceIndicatorVertex(x_max + D, xpy_max - x_max + D);
1856    PlaceIndicatorVertex(xpy_max - y_max + D, y_max + D);
1857    PlaceIndicatorVertex(xpy_max - y_max, y_max + M);
1858    PlaceIndicatorVertex(xmy_min + y_max, y_max + M);
1859    PlaceIndicatorVertex(xmy_min + y_max - D, y_max + D);
1860    PlaceIndicatorVertex(x_min - D, x_min - xmy_min + D);
1861    PlaceIndicatorVertex(x_min - M, x_min - xmy_min);
1862    PlaceIndicatorVertex(x_min - M, xpy_min - x_min);
1863    PlaceIndicatorVertex(x_min - D, xpy_min - x_min - D);
1864    PlaceIndicatorVertex(xpy_min - y_min - D, y_min - D);
1865    PlaceIndicatorVertex(xpy_min - y_min, y_min - M);
1866    EndPolyloop();
1867}
1868
1869void GfxCore::ZoomToSurvey(const wxString& survey) {
1870    SurveyFilter filter;
1871    filter.add(survey);
1872    filter.SetSeparator(m_Parent->GetSeparator());
1873
1874    Double xmin = DBL_MAX;
1875    Double xmax = -DBL_MAX;
1876    Double ymin = DBL_MAX;
1877    Double ymax = -DBL_MAX;
1878    Double zmin = DBL_MAX;
1879    Double zmax = -DBL_MAX;
1880
1881    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1882    while (pos != m_Parent->GetLabelsEnd()) {
1883        LabelInfo* label = *pos++;
1884
1885        if (!filter.CheckVisible(label->GetText()))
1886            continue;
1887
1888        if (label->GetX() < xmin) xmin = label->GetX();
1889        if (label->GetX() > xmax) xmax = label->GetX();
1890        if (label->GetY() < ymin) ymin = label->GetY();
1891        if (label->GetY() > ymax) ymax = label->GetY();
1892        if (label->GetZ() < zmin) zmin = label->GetZ();
1893        if (label->GetZ() > zmax) zmax = label->GetZ();
1894    }
1895
1896    SetViewTo(xmin, xmax, ymin, ymax, zmin, zmax);
1897}
1898
1899void GfxCore::SetHereFromTree(const LabelInfo * p)
1900{
1901    SetHere(p);
1902    m_Parent->ShowInfo(m_here, m_there);
1903    SetHereSurvey(wxString());
1904}
1905
1906void GfxCore::SetHere(const LabelInfo *p)
1907{
1908    if (p == m_here) return;
1909    bool line_active = MeasuringLineActive();
1910    const LabelInfo * old = m_here;
1911    m_here = p;
1912    if (line_active || MeasuringLineActive())
1913        RefreshLine(old, m_there, m_here);
1914}
1915
1916void GfxCore::SetThere(const LabelInfo * p)
1917{
1918    if (p == m_there) return;
1919    const LabelInfo * old = m_there;
1920    m_there = p;
1921    RefreshLine(m_here, old, m_there);
1922}
1923
1924void GfxCore::CreateHitTestGrid()
1925{
1926    if (!m_PointGrid) {
1927        // Initialise hit-test grid.
1928        m_PointGrid = new list<LabelInfo*>[HITTEST_SIZE * HITTEST_SIZE];
1929    } else {
1930        // Clear hit-test grid.
1931        for (int i = 0; i < HITTEST_SIZE * HITTEST_SIZE; i++) {
1932            m_PointGrid[i].clear();
1933        }
1934    }
1935
1936    const SurveyFilter* filter = m_Parent->GetTreeFilter();
1937    // Fill the grid.
1938    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1939    list<LabelInfo*>::const_iterator end = m_Parent->GetLabelsEnd();
1940    while (pos != end) {
1941        LabelInfo* label = *pos++;
1942
1943        if (m_Splays == SHOW_HIDE && label->IsSplayEnd())
1944            continue;
1945
1946        if (!((m_Surface && label->IsSurface()) ||
1947              (m_Legs && label->IsUnderground()) ||
1948              (!label->IsSurface() && !label->IsUnderground()))) {
1949            // if this station isn't to be displayed, skip to the next
1950            // (last case is for stns with no legs attached)
1951            continue;
1952        }
1953
1954        if (filter && !filter->CheckVisible(label->GetText()))
1955            continue;
1956
1957        // Calculate screen coordinates.
1958        double cx, cy, cz;
1959        Transform(*label, &cx, &cy, &cz);
1960        if (cx < 0 || cx >= GetXSize()) continue;
1961        if (cy < 0 || cy >= GetYSize()) continue;
1962
1963        cy = GetYSize() - cy;
1964
1965        // On-screen, so add to hit-test grid...
1966        int grid_x = int(cx * HITTEST_SIZE / (GetXSize() + 1));
1967        int grid_y = int(cy * HITTEST_SIZE / (GetYSize() + 1));
1968
1969        m_PointGrid[grid_x + grid_y * HITTEST_SIZE].push_back(label);
1970    }
1971
1972    m_HitTestGridValid = true;
1973}
1974
1975//
1976//  Methods for controlling the orientation of the survey
1977//
1978
1979void GfxCore::TurnCave(Double angle)
1980{
1981    // Turn the cave around its z-axis by a given angle.
1982
1983    m_PanAngle += angle;
1984    // Wrap to range [0, 360):
1985    m_PanAngle = fmod(m_PanAngle, 360.0);
1986    if (m_PanAngle < 0.0) {
1987        m_PanAngle += 360.0;
1988    }
1989
1990    m_HitTestGridValid = false;
1991    if (m_here && m_here == &temp_here) SetHere();
1992
1993    SetRotation(m_PanAngle, m_TiltAngle);
1994}
1995
1996void GfxCore::TurnCaveTo(Double angle)
1997{
1998    if (m_Rotating) {
1999        // If we're rotating, jump to the specified angle.
2000        TurnCave(angle - m_PanAngle);
2001        SetPanBase();
2002        return;
2003    }
2004
2005    int new_switching_to = ((int)angle) / 90 + NORTH;
2006    if (new_switching_to == m_SwitchingTo) {
2007        // A second order to switch takes us there right away
2008        TurnCave(angle - m_PanAngle);
2009        m_SwitchingTo = 0;
2010        ForceRefresh();
2011    } else {
2012        SetPanBase();
2013        m_SwitchingTo = new_switching_to;
2014    }
2015}
2016
2017void GfxCore::TiltCave(Double tilt_angle)
2018{
2019    // Tilt the cave by a given angle.
2020    if (m_TiltAngle + tilt_angle > 90.0) {
2021        m_TiltAngle = 90.0;
2022    } else if (m_TiltAngle + tilt_angle < -90.0) {
2023        m_TiltAngle = -90.0;
2024    } else {
2025        m_TiltAngle += tilt_angle;
2026    }
2027
2028    m_HitTestGridValid = false;
2029    if (m_here && m_here == &temp_here) SetHere();
2030
2031    SetRotation(m_PanAngle, m_TiltAngle);
2032}
2033
2034void GfxCore::TranslateCave(int dx, int dy)
2035{
2036    AddTranslationScreenCoordinates(dx, dy);
2037    m_HitTestGridValid = false;
2038
2039    if (m_here && m_here == &temp_here) SetHere();
2040
2041    ForceRefresh();
2042}
2043
2044void GfxCore::DragFinished()
2045{
2046    m_MouseOutsideCompass = m_MouseOutsideElev = false;
2047    ForceRefresh();
2048}
2049
2050void GfxCore::ClearCoords()
2051{
2052    m_Parent->ClearCoords();
2053}
2054
2055void GfxCore::SetCoords(wxPoint point)
2056{
2057    // We can't work out 2D coordinates from a perspective view, and it
2058    // doesn't really make sense to show coordinates while we're animating.
2059    if (GetPerspective() || Animating()) return;
2060
2061    // Update the coordinate or altitude display, given the (x, y) position in
2062    // window coordinates.  The relevant display is updated depending on
2063    // whether we're in plan or elevation view.
2064
2065    double cx, cy, cz;
2066
2067    SetDataTransform();
2068    ReverseTransform(point.x, GetYSize() - 1 - point.y, &cx, &cy, &cz);
2069
2070    if (ShowingPlan()) {
2071        m_Parent->SetCoords(cx + m_Parent->GetOffset().GetX(),
2072                            cy + m_Parent->GetOffset().GetY(),
2073                            m_there);
2074    } else if (ShowingElevation()) {
2075        m_Parent->SetAltitude(cz + m_Parent->GetOffset().GetZ(),
2076                              m_there);
2077    } else {
2078        m_Parent->ClearCoords();
2079    }
2080}
2081
2082int GfxCore::GetCompassWidth() const
2083{
2084    static int result = 0;
2085    if (result == 0) {
2086        result = INDICATOR_BOX_SIZE;
2087        int width;
2088        const wxString & msg = wmsg(/*Facing*/203);
2089        GetTextExtent(msg, &width, NULL);
2090        if (width > result) result = width;
2091    }
2092    return result;
2093}
2094
2095int GfxCore::GetClinoWidth() const
2096{
2097    static int result = 0;
2098    if (result == 0) {
2099        result = INDICATOR_BOX_SIZE;
2100        int width;
2101        const wxString & msg1 = wmsg(/*Plan*/432);
2102        GetTextExtent(msg1, &width, NULL);
2103        if (width > result) result = width;
2104        const wxString & msg2 = wmsg(/*Kiwi Plan*/433);
2105        GetTextExtent(msg2, &width, NULL);
2106        if (width > result) result = width;
2107        const wxString & msg3 = wmsg(/*Elevation*/118);
2108        GetTextExtent(msg3, &width, NULL);
2109        if (width > result) result = width;
2110    }
2111    return result;
2112}
2113
2114int GfxCore::GetCompassXPosition() const
2115{
2116    // Return the x-coordinate of the centre of the compass in window
2117    // coordinates.
2118    return GetXSize() - INDICATOR_OFFSET_X - GetCompassWidth() / 2;
2119}
2120
2121int GfxCore::GetClinoXPosition() const
2122{
2123    // Return the x-coordinate of the centre of the compass in window
2124    // coordinates.
2125    return GetXSize() - GetClinoOffset() - GetClinoWidth() / 2;
2126}
2127
2128int GfxCore::GetIndicatorYPosition() const
2129{
2130    // Return the y-coordinate of the centre of the indicators in window
2131    // coordinates.
2132    return GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE / 2;
2133}
2134
2135int GfxCore::GetIndicatorRadius() const
2136{
2137    // Return the radius of each indicator.
2138    return (INDICATOR_BOX_SIZE - INDICATOR_MARGIN * 2) / 2;
2139}
2140
2141bool GfxCore::PointWithinCompass(wxPoint point) const
2142{
2143    // Determine whether a point (in window coordinates) lies within the
2144    // compass.
2145    if (!ShowingCompass()) return false;
2146
2147    glaCoord dx = point.x - GetCompassXPosition();
2148    glaCoord dy = point.y - GetIndicatorYPosition();
2149    glaCoord radius = GetIndicatorRadius();
2150
2151    return (dx * dx + dy * dy <= radius * radius);
2152}
2153
2154bool GfxCore::PointWithinClino(wxPoint point) const
2155{
2156    // Determine whether a point (in window coordinates) lies within the clino.
2157    if (!ShowingClino()) return false;
2158
2159    glaCoord dx = point.x - GetClinoXPosition();
2160    glaCoord dy = point.y - GetIndicatorYPosition();
2161    glaCoord radius = GetIndicatorRadius();
2162
2163    return (dx * dx + dy * dy <= radius * radius);
2164}
2165
2166bool GfxCore::PointWithinScaleBar(wxPoint point) const
2167{
2168    // Determine whether a point (in window coordinates) lies within the scale
2169    // bar.
2170    if (!ShowingScaleBar()) return false;
2171
2172    return (point.x >= SCALE_BAR_OFFSET_X &&
2173            point.x <= SCALE_BAR_OFFSET_X + m_ScaleBarWidth &&
2174            point.y <= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT &&
2175            point.y >= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT*2);
2176}
2177
2178bool GfxCore::PointWithinColourKey(wxPoint point) const
2179{
2180    // Determine whether a point (in window coordinates) lies within the key.
2181    point.x -= GetXSize() - KEY_OFFSET_X;
2182    point.y = KEY_OFFSET_Y - point.y;
2183    return (point.x >= key_lowerleft[m_ColourBy].x && point.x <= 0 &&
2184            point.y >= key_lowerleft[m_ColourBy].y && point.y <= 0);
2185}
2186
2187void GfxCore::SetCompassFromPoint(wxPoint point)
2188{
2189    // Given a point in window coordinates, set the heading of the survey.  If
2190    // the point is outside the compass, it snaps to 45 degree intervals;
2191    // otherwise it operates as normal.
2192
2193    wxCoord dx = point.x - GetCompassXPosition();
2194    wxCoord dy = point.y - GetIndicatorYPosition();
2195    wxCoord radius = GetIndicatorRadius();
2196
2197    double angle = deg(atan2(double(dx), double(dy))) - 180.0;
2198    if (dx * dx + dy * dy <= radius * radius) {
2199        TurnCave(angle - m_PanAngle);
2200        m_MouseOutsideCompass = false;
2201    } else {
2202        TurnCave(int(angle / 45.0) * 45.0 - m_PanAngle);
2203        m_MouseOutsideCompass = true;
2204    }
2205
2206    ForceRefresh();
2207}
2208
2209void GfxCore::SetClinoFromPoint(wxPoint point)
2210{
2211    // Given a point in window coordinates, set the elevation of the survey.
2212    // If the point is outside the clino, it snaps to 90 degree intervals;
2213    // otherwise it operates as normal.
2214
2215    glaCoord dx = point.x - GetClinoXPosition();
2216    glaCoord dy = point.y - GetIndicatorYPosition();
2217    glaCoord radius = GetIndicatorRadius();
2218
2219    if (dx >= 0 && dx * dx + dy * dy <= radius * radius) {
2220        TiltCave(-deg(atan2(double(dy), double(dx))) - m_TiltAngle);
2221        m_MouseOutsideElev = false;
2222    } else if (dy >= INDICATOR_MARGIN) {
2223        TiltCave(-90.0 - m_TiltAngle);
2224        m_MouseOutsideElev = true;
2225    } else if (dy <= -INDICATOR_MARGIN) {
2226        TiltCave(90.0 - m_TiltAngle);
2227        m_MouseOutsideElev = true;
2228    } else {
2229        TiltCave(-m_TiltAngle);
2230        m_MouseOutsideElev = true;
2231    }
2232
2233    ForceRefresh();
2234}
2235
2236void GfxCore::SetScaleBarFromOffset(wxCoord dx)
2237{
2238    // Set the scale of the survey, given an offset as to how much the mouse has
2239    // been dragged over the scalebar since the last scale change.
2240
2241    SetScale((m_ScaleBarWidth + dx) * m_Scale / m_ScaleBarWidth);
2242    ForceRefresh();
2243}
2244
2245void GfxCore::RedrawIndicators()
2246{
2247    // Redraw the compass and clino indicators.
2248
2249    int total_width = GetCompassWidth() + INDICATOR_GAP + GetClinoWidth();
2250    RefreshRect(wxRect(GetXSize() - INDICATOR_OFFSET_X - total_width,
2251                       GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE,
2252                       total_width,
2253                       INDICATOR_BOX_SIZE), false);
2254}
2255
2256void GfxCore::StartRotation()
2257{
2258    // Start the survey rotating.
2259
2260    if (m_SwitchingTo >= NORTH)
2261        m_SwitchingTo = 0;
2262    m_Rotating = true;
2263    SetPanBase();
2264}
2265
2266void GfxCore::ToggleRotation()
2267{
2268    // Toggle the survey rotation on/off.
2269
2270    if (m_Rotating) {
2271        StopRotation();
2272    } else {
2273        StartRotation();
2274    }
2275}
2276
2277void GfxCore::StopRotation()
2278{
2279    // Stop the survey rotating.
2280
2281    m_Rotating = false;
2282    ForceRefresh();
2283}
2284
2285bool GfxCore::IsExtendedElevation() const
2286{
2287    return m_Parent->IsExtendedElevation();
2288}
2289
2290void GfxCore::ReverseRotation()
2291{
2292    // Reverse the direction of rotation.
2293
2294    m_RotationStep = -m_RotationStep;
2295    if (m_Rotating)
2296        SetPanBase();
2297}
2298
2299void GfxCore::RotateSlower(bool accel)
2300{
2301    // Decrease the speed of rotation, optionally by an increased amount.
2302    if (fabs(m_RotationStep) == 1.0)
2303        return;
2304
2305    m_RotationStep *= accel ? (1 / 1.44) : (1 / 1.2);
2306
2307    if (fabs(m_RotationStep) < 1.0) {
2308        m_RotationStep = (m_RotationStep > 0 ? 1.0 : -1.0);
2309    }
2310    if (m_Rotating)
2311        SetPanBase();
2312}
2313
2314void GfxCore::RotateFaster(bool accel)
2315{
2316    // Increase the speed of rotation, optionally by an increased amount.
2317    if (fabs(m_RotationStep) == 180.0)
2318        return;
2319
2320    m_RotationStep *= accel ? 1.44 : 1.2;
2321    if (fabs(m_RotationStep) > 180.0) {
2322        m_RotationStep = (m_RotationStep > 0 ? 180.0 : -180.0);
2323    }
2324    if (m_Rotating)
2325        SetPanBase();
2326}
2327
2328void GfxCore::SwitchToElevation()
2329{
2330    // Perform an animated switch to elevation view.
2331
2332    if (m_SwitchingTo != ELEVATION) {
2333        SetTiltBase();
2334        m_SwitchingTo = ELEVATION;
2335    } else {
2336        // A second order to switch takes us there right away
2337        TiltCave(-m_TiltAngle);
2338        m_SwitchingTo = 0;
2339        ForceRefresh();
2340    }
2341}
2342
2343void GfxCore::SwitchToPlan()
2344{
2345    // Perform an animated switch to plan view.
2346
2347    if (m_SwitchingTo != PLAN) {
2348        SetTiltBase();
2349        m_SwitchingTo = PLAN;
2350    } else {
2351        // A second order to switch takes us there right away
2352        TiltCave(-90.0 - m_TiltAngle);
2353        m_SwitchingTo = 0;
2354        ForceRefresh();
2355    }
2356}
2357
2358void GfxCore::SetViewTo(Double xmin, Double xmax, Double ymin, Double ymax, Double zmin, Double zmax)
2359{
2360
2361    SetTranslation(-Vector3((xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2));
2362    Double scale = HUGE_VAL;
2363    const Vector3 ext = m_Parent->GetExtent();
2364    if (xmax > xmin) {
2365        Double s = ext.GetX() / (xmax - xmin);
2366        if (s < scale) scale = s;
2367    }
2368    if (ymax > ymin) {
2369        Double s = ext.GetY() / (ymax - ymin);
2370        if (s < scale) scale = s;
2371    }
2372    if (!ShowingPlan() && zmax > zmin) {
2373        Double s = ext.GetZ() / (zmax - zmin);
2374        if (s < scale) scale = s;
2375    }
2376    if (scale != HUGE_VAL) SetScale(scale);
2377    ForceRefresh();
2378}
2379
2380bool GfxCore::CanRaiseViewpoint() const
2381{
2382    // Determine if the survey can be viewed from a higher angle of elevation.
2383
2384    return GetPerspective() ? (m_TiltAngle < 90.0) : (m_TiltAngle > -90.0);
2385}
2386
2387bool GfxCore::CanLowerViewpoint() const
2388{
2389    // Determine if the survey can be viewed from a lower angle of elevation.
2390
2391    return GetPerspective() ? (m_TiltAngle > -90.0) : (m_TiltAngle < 90.0);
2392}
2393
2394bool GfxCore::HasDepth() const
2395{
2396    return m_Parent->GetDepthExtent() == 0.0;
2397}
2398
2399bool GfxCore::HasErrorInformation() const
2400{
2401    return m_Parent->HasErrorInformation();
2402}
2403
2404bool GfxCore::HasDateInformation() const
2405{
2406    return m_Parent->GetDateMin() >= 0;
2407}
2408
2409bool GfxCore::ShowingPlan() const
2410{
2411    // Determine if the survey is in plan view.
2412
2413    return (m_TiltAngle == -90.0);
2414}
2415
2416bool GfxCore::ShowingElevation() const
2417{
2418    // Determine if the survey is in elevation view.
2419
2420    return (m_TiltAngle == 0.0);
2421}
2422
2423bool GfxCore::ShowingMeasuringLine() const
2424{
2425    // Determine if the measuring line is being shown.  Only check if "there"
2426    // is valid, since that means the measuring line anchor is out.
2427
2428    return m_there;
2429}
2430
2431void GfxCore::ToggleFlag(bool* flag, int update)
2432{
2433    *flag = !*flag;
2434    if (update == UPDATE_BLOBS) {
2435        UpdateBlobs();
2436    } else if (update == UPDATE_BLOBS_AND_CROSSES) {
2437        UpdateBlobs();
2438        InvalidateList(LIST_CROSSES);
2439        m_HitTestGridValid = false;
2440    }
2441    ForceRefresh();
2442}
2443
2444int GfxCore::GetNumEntrances() const
2445{
2446    return m_Parent->GetNumEntrances();
2447}
2448
2449int GfxCore::GetNumFixedPts() const
2450{
2451    return m_Parent->GetNumFixedPts();
2452}
2453
2454int GfxCore::GetNumExportedPts() const
2455{
2456    return m_Parent->GetNumExportedPts();
2457}
2458
2459void GfxCore::ToggleTerrain()
2460{
2461    if (!m_Terrain && !dem) {
2462        // OnOpenTerrain() calls us if a file is selected.
2463        wxCommandEvent dummy;
2464        m_Parent->OnOpenTerrain(dummy);
2465        return;
2466    }
2467    ToggleFlag(&m_Terrain);
2468}
2469
2470void GfxCore::ToggleFatFinger()
2471{
2472    if (sqrd_measure_threshold == sqrd(MEASURE_THRESHOLD)) {
2473        sqrd_measure_threshold = sqrd(5 * MEASURE_THRESHOLD);
2474        wxMessageBox(wxT("Fat finger enabled"), wxT("Aven Debug"), wxOK | wxICON_INFORMATION);
2475    } else {
2476        sqrd_measure_threshold = sqrd(MEASURE_THRESHOLD);
2477        wxMessageBox(wxT("Fat finger disabled"), wxT("Aven Debug"), wxOK | wxICON_INFORMATION);
2478    }
2479}
2480
2481void GfxCore::ClearTreeSelection()
2482{
2483    m_Parent->ClearTreeSelection();
2484}
2485
2486void GfxCore::CentreOn(const Point &p)
2487{
2488    SetTranslation(-p);
2489    m_HitTestGridValid = false;
2490
2491    ForceRefresh();
2492}
2493
2494void GfxCore::ForceRefresh()
2495{
2496    Refresh(false);
2497}
2498
2499void GfxCore::GenerateList(unsigned int l)
2500{
2501    assert(m_HaveData);
2502
2503    switch (l) {
2504        case LIST_COMPASS:
2505            DrawCompass();
2506            break;
2507        case LIST_CLINO:
2508            DrawClino();
2509            break;
2510        case LIST_CLINO_BACK:
2511            DrawClinoBack();
2512            break;
2513        case LIST_SCALE_BAR:
2514            DrawScaleBar();
2515            break;
2516        case LIST_DEPTH_KEY:
2517            DrawDepthKey();
2518            break;
2519        case LIST_DATE_KEY:
2520            DrawDateKey();
2521            break;
2522        case LIST_ERROR_KEY:
2523            DrawErrorKey();
2524            break;
2525        case LIST_GRADIENT_KEY:
2526            DrawGradientKey();
2527            break;
2528        case LIST_LENGTH_KEY:
2529            DrawLengthKey();
2530            break;
2531        case LIST_UNDERGROUND_LEGS:
2532            GenerateDisplayList(false);
2533            break;
2534        case LIST_TUBES:
2535            GenerateDisplayListTubes();
2536            break;
2537        case LIST_SURFACE_LEGS:
2538            GenerateDisplayList(true);
2539            break;
2540        case LIST_BLOBS:
2541            GenerateBlobsDisplayList();
2542            break;
2543        case LIST_CROSSES: {
2544            BeginCrosses();
2545            SetColour(col_LIGHT_GREY);
2546            const SurveyFilter* filter = m_Parent->GetTreeFilter();
2547            list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2548            while (pos != m_Parent->GetLabelsEnd()) {
2549                const LabelInfo* label = *pos++;
2550
2551                if (m_Splays == SHOW_HIDE && label->IsSplayEnd())
2552                    continue;
2553
2554                if ((m_Surface && label->IsSurface()) ||
2555                    (m_Legs && label->IsUnderground()) ||
2556                    (!label->IsSurface() && !label->IsUnderground())) {
2557                    // Check if this station should be displayed
2558                    // (last case above is for stns with no legs attached)
2559                    if (filter && !filter->CheckVisible(label->GetText()))
2560                        continue;
2561                    DrawCross(label->GetX(), label->GetY(), label->GetZ());
2562                }
2563            }
2564            EndCrosses();
2565            break;
2566        }
2567        case LIST_GRID:
2568            DrawGrid();
2569            break;
2570        case LIST_SHADOW:
2571            GenerateDisplayListShadow();
2572            break;
2573        case LIST_TERRAIN:
2574            DrawTerrain();
2575            break;
2576        default:
2577            assert(false);
2578            break;
2579    }
2580}
2581
2582void GfxCore::ToggleSmoothShading()
2583{
2584    GLACanvas::ToggleSmoothShading();
2585    InvalidateList(LIST_TUBES);
2586    ForceRefresh();
2587}
2588
2589void GfxCore::GenerateDisplayList(bool surface)
2590{
2591    unsigned surf_or_not = surface ? img_FLAG_SURFACE : 0;
2592    // Generate the display list for the surface or underground legs.
2593    for (int f = 0; f != 8; ++f) {
2594        if ((f & img_FLAG_SURFACE) != surf_or_not) continue;
2595        const unsigned SHOW_DASHED_AND_FADED = unsigned(-1);
2596        unsigned style = SHOW_NORMAL;
2597        if ((f & img_FLAG_SPLAY) && m_Splays != SHOW_NORMAL) {
2598            style = m_Splays;
2599        } else if (f & img_FLAG_DUPLICATE) {
2600            style = m_Dupes;
2601        }
2602        if (f & img_FLAG_SURFACE) {
2603            if (style == SHOW_FADED) {
2604                style = SHOW_DASHED_AND_FADED;
2605            } else {
2606                style = SHOW_DASHED;
2607            }
2608        }
2609
2610        switch (style) {
2611            case SHOW_HIDE:
2612                continue;
2613            case SHOW_FADED:
2614                SetAlpha(0.4);
2615                break;
2616            case SHOW_DASHED:
2617                EnableDashedLines();
2618                break;
2619            case SHOW_DASHED_AND_FADED:
2620                SetAlpha(0.4);
2621                EnableDashedLines();
2622                break;
2623        }
2624
2625        void (GfxCore::* add_poly)(const traverse&);
2626        if (surface) {
2627            if (m_ColourBy == COLOUR_BY_ERROR) {
2628                add_poly = &GfxCore::AddPolylineError;
2629            } else {
2630                add_poly = &GfxCore::AddPolyline;
2631            }
2632        } else {
2633            add_poly = AddPoly;
2634        }
2635
2636        const SurveyFilter* filter = m_Parent->GetTreeFilter();
2637        list<traverse>::const_iterator trav = m_Parent->traverses_begin(f, filter);
2638        list<traverse>::const_iterator tend = m_Parent->traverses_end(f);
2639        while (trav != tend) {
2640            (this->*add_poly)(*trav);
2641            trav = m_Parent->traverses_next(f, filter, trav);
2642        }
2643
2644        switch (style) {
2645            case SHOW_FADED:
2646                SetAlpha(1.0);
2647                break;
2648            case SHOW_DASHED:
2649                DisableDashedLines();
2650                break;
2651            case SHOW_DASHED_AND_FADED:
2652                DisableDashedLines();
2653                SetAlpha(1.0);
2654                break;
2655        }
2656    }
2657}
2658
2659void GfxCore::GenerateDisplayListTubes()
2660{
2661    // Generate the display list for the tubes.
2662    list<vector<XSect>>::iterator trav = m_Parent->tubes_begin();
2663    list<vector<XSect>>::iterator tend = m_Parent->tubes_end();
2664    while (trav != tend) {
2665        SkinPassage(*trav);
2666        ++trav;
2667    }
2668}
2669
2670void GfxCore::GenerateDisplayListShadow()
2671{
2672    const SurveyFilter* filter = m_Parent->GetTreeFilter();
2673    SetColour(col_BLACK);
2674    for (int f = 0; f != 8; ++f) {
2675        // Only include underground legs in the shadow.
2676        if ((f & img_FLAG_SURFACE) != 0) continue;
2677        list<traverse>::const_iterator trav = m_Parent->traverses_begin(f, filter);
2678        list<traverse>::const_iterator tend = m_Parent->traverses_end(f);
2679        while (trav != tend) {
2680            AddPolylineShadow(*trav);
2681            trav = m_Parent->traverses_next(f, filter, trav);
2682        }
2683    }
2684}
2685
2686void
2687GfxCore::parse_hgt_filename(const wxString & lc_name)
2688{
2689    char * leaf = leaf_from_fnm(lc_name.utf8_str());
2690    const char * p = leaf;
2691    char * q;
2692    char dirn = *p++;
2693    o_y = strtoul(p, &q, 10);
2694    p = q;
2695    if (dirn == 's')
2696        o_y = -o_y;
2697    ++o_y;
2698    dirn = *p++;
2699    o_x = strtoul(p, &q, 10);
2700    if (dirn == 'w')
2701        o_x = -o_x;
2702    bigendian = true;
2703    nodata_value = -32768;
2704    osfree(leaf);
2705}
2706
2707size_t
2708GfxCore::parse_hdr(wxInputStream & is, unsigned long & skipbytes)
2709{
2710    // ESRI docs say NBITS defaults to 8.
2711    unsigned long nbits = 8;
2712    // ESRI docs say NBANDS defaults to 1.
2713    unsigned long nbands = 1;
2714    unsigned long bandrowbytes = 0;
2715    unsigned long totalrowbytes = 0;
2716    // ESRI docs say ULXMAP defaults to 0.
2717    o_x = 0.0;
2718    // ESRI docs say ULYMAP defaults to NROWS - 1.
2719    o_y = HUGE_VAL;
2720    // ESRI docs say XDIM and YDIM default to 1.
2721    step_x = step_y = 1.0;
2722    while (!is.Eof()) {
2723        wxString line;
2724        int ch;
2725        while ((ch = is.GetC()) != wxEOF) {
2726            if (ch == '\n' || ch == '\r') break;
2727            line += wxChar(ch);
2728        }
2729#define CHECK(X, COND) \
2730} else if (line.StartsWith(wxT(X " "))) { \
2731size_t v = line.find_first_not_of(wxT(' '), sizeof(X)); \
2732if (v == line.npos || !(COND)) { \
2733err += wxT("Unexpected value for " X); \
2734}
2735        wxString err;
2736        if (false) {
2737        // I = little-endian; M = big-endian
2738        CHECK("BYTEORDER", (bigendian = (line[v] == 'M')) || line[v] == 'I')
2739        // ESRI docs say LAYOUT defaults to BIL if not specified.
2740        CHECK("LAYOUT", line.substr(v) == wxT("BIL"))
2741        CHECK("NROWS", line.substr(v).ToCULong(&dem_height))
2742        CHECK("NCOLS", line.substr(v).ToCULong(&dem_width))
2743        // ESRI docs say NBANDS defaults to 1 if not specified.
2744        CHECK("NBANDS", line.substr(v).ToCULong(&nbands) && nbands == 1)
2745        CHECK("NBITS", line.substr(v).ToCULong(&nbits) && nbits == 16)
2746        CHECK("BANDROWBYTES", line.substr(v).ToCULong(&bandrowbytes))
2747        CHECK("TOTALROWBYTES", line.substr(v).ToCULong(&totalrowbytes))
2748        // PIXELTYPE is a GDAL extension, so may not be present.
2749        CHECK("PIXELTYPE", line.substr(v) == wxT("SIGNEDINT"))
2750        CHECK("ULXMAP", line.substr(v).ToCDouble(&o_x))
2751        CHECK("ULYMAP", line.substr(v).ToCDouble(&o_y))
2752        CHECK("XDIM", line.substr(v).ToCDouble(&step_x))
2753        CHECK("YDIM", line.substr(v).ToCDouble(&step_y))
2754        CHECK("NODATA", line.substr(v).ToCLong(&nodata_value))
2755        CHECK("SKIPBYTES", line.substr(v).ToCULong(&skipbytes))
2756        }
2757        if (!err.empty()) {
2758            wxMessageBox(err);
2759        }
2760    }
2761    if (o_y == HUGE_VAL) {
2762        o_y = dem_height - 1;
2763    }
2764    if (bandrowbytes != 0) {
2765        if (nbits * dem_width != bandrowbytes * 8) {
2766            wxMessageBox("BANDROWBYTES setting indicates unused bits after each band - not currently supported");
2767        }
2768    }
2769    if (totalrowbytes != 0) {
2770        // This is the ESRI default for BIL, for BIP it would be
2771        // nbands * bandrowbytes.
2772        if (nbands * nbits * dem_width != totalrowbytes * 8) {
2773            wxMessageBox("TOTALROWBYTES setting indicates unused bits after "
2774                         "each row - not currently supported");
2775        }
2776    }
2777    return ((nbits * dem_width + 7) / 8) * dem_height;
2778}
2779
2780bool
2781GfxCore::read_bil(wxInputStream & is, size_t size, unsigned long skipbytes)
2782{
2783    bool know_size = true;
2784    if (!size) {
2785        // If the stream doesn't know its size, GetSize() returns 0.
2786        size = is.GetSize();
2787        if (!size) {
2788            size = DEFAULT_HGT_SIZE;
2789            know_size = false;
2790        }
2791    }
2792    dem = new unsigned short[size / 2];
2793    if (skipbytes) {
2794        if (is.SeekI(skipbytes, wxFromStart) == ::wxInvalidOffset) {
2795            while (skipbytes) {
2796                unsigned long to_read = skipbytes;
2797                if (size < to_read) to_read = size;
2798                is.Read(reinterpret_cast<char *>(dem), to_read);
2799                size_t c = is.LastRead();
2800                if (c == 0) {
2801                    wxMessageBox(wxT("Failed to skip terrain data header"));
2802                    break;
2803                }
2804                skipbytes -= c;
2805            }
2806        }
2807    }
2808
2809    if (!is.ReadAll(dem, size)) {
2810        if (know_size) {
2811            // FIXME: On __WXMSW__ currently we fail to
2812            // read any data from files in zips.
2813            delete [] dem;
2814            dem = NULL;
2815            wxMessageBox(wxT("Failed to read terrain data"));
2816            return false;
2817        }
2818        size = is.LastRead();
2819    }
2820
2821    if (dem_width == 0 && dem_height == 0) {
2822        dem_width = dem_height = sqrt(size / 2);
2823        if (dem_width * dem_height * 2 != size) {
2824            delete [] dem;
2825            dem = NULL;
2826            wxMessageBox(wxT("HGT format data doesn't form a square"));
2827            return false;
2828        }
2829        step_x = step_y = 1.0 / dem_width;
2830    }
2831
2832    return true;
2833}
2834
2835bool GfxCore::LoadDEM(const wxString & file)
2836{
2837    if (m_Parent->GetCSProj().empty()) {
2838        wxMessageBox(wxT("No coordinate system specified in survey data"));
2839        return false;
2840    }
2841
2842    delete [] dem;
2843    dem = NULL;
2844
2845    size_t size = 0;
2846    // Default is to not skip any bytes.
2847    unsigned long skipbytes = 0;
2848    // For .hgt files, default to using filesize to determine.
2849    dem_width = dem_height = 0;
2850    // ESRI say "The default byte order is the same as that of the host machine
2851    // executing the software", but that's stupid so we default to
2852    // little-endian.
2853    bigendian = false;
2854
2855    wxFileInputStream fs(file);
2856    if (!fs.IsOk()) {
2857        wxMessageBox(wxT("Failed to open DEM file"));
2858        return false;
2859    }
2860
2861    const wxString & lc_file = file.Lower();
2862    if (lc_file.EndsWith(wxT(".hgt"))) {
2863        parse_hgt_filename(lc_file);
2864        read_bil(fs, size, skipbytes);
2865    } else if (lc_file.EndsWith(wxT(".bil"))) {
2866        wxString hdr_file = file;
2867        hdr_file.replace(file.size() - 4, 4, wxT(".hdr"));
2868        wxFileInputStream hdr_is(hdr_file);
2869        if (!hdr_is.IsOk()) {
2870            wxMessageBox(wxT("Failed to open HDR file '") + hdr_file + wxT("'"));
2871            return false;
2872        }
2873        size = parse_hdr(hdr_is, skipbytes);
2874        read_bil(fs, size, skipbytes);
2875    } else if (lc_file.EndsWith(wxT(".zip"))) {
2876        wxZipEntry * ze_data = NULL;
2877        wxZipInputStream zs(fs);
2878        wxZipEntry * ze;
2879        while ((ze = zs.GetNextEntry()) != NULL) {
2880            if (!ze->IsDir()) {
2881                const wxString & lc_name = ze->GetName().Lower();
2882                if (!ze_data && lc_name.EndsWith(wxT(".hgt"))) {
2883                    // SRTM .hgt files are raw binary data, with the filename
2884                    // encoding the coordinates.
2885                    parse_hgt_filename(lc_name);
2886                    read_bil(zs, size, skipbytes);
2887                    delete ze;
2888                    break;
2889                }
2890
2891                if (!ze_data && lc_name.EndsWith(wxT(".bil"))) {
2892                    if (size) {
2893                        read_bil(zs, size, skipbytes);
2894                        break;
2895                    }
2896                    ze_data = ze;
2897                    continue;
2898                }
2899
2900                if (lc_name.EndsWith(wxT(".hdr"))) {
2901                    size = parse_hdr(zs, skipbytes);
2902                    if (ze_data) {
2903                        if (!zs.OpenEntry(*ze_data)) {
2904                            wxMessageBox(wxT("Couldn't read DEM data from .zip file"));
2905                            break;
2906                        }
2907                        read_bil(zs, size, skipbytes);
2908                    }
2909                } else if (lc_name.EndsWith(wxT(".prj"))) {
2910                    //FIXME: check this matches the datum string we use
2911                    //Projection    GEOGRAPHIC
2912                    //Datum         WGS84
2913                    //Zunits        METERS
2914                    //Units         DD
2915                    //Spheroid      WGS84
2916                    //Xshift        0.0000000000
2917                    //Yshift        0.0000000000
2918                    //Parameters
2919                }
2920            }
2921            delete ze;
2922        }
2923        delete ze_data;
2924    }
2925
2926    if (!dem) {
2927        return false;
2928    }
2929
2930    InvalidateList(LIST_TERRAIN);
2931    ForceRefresh();
2932    return true;
2933}
2934
2935void GfxCore::DrawTerrainTriangle(const Vector3 & a, const Vector3 & b, const Vector3 & c)
2936{
2937    Vector3 n = (b - a) * (c - a);
2938    n.normalise();
2939    Double factor = dot(n, light) * .95 + .05;
2940    SetColour(col_WHITE, factor);
2941    PlaceVertex(a);
2942    PlaceVertex(b);
2943    PlaceVertex(c);
2944    ++n_tris;
2945}
2946
2947// Like wxBusyCursor, but you can cancel it early.
2948class AvenBusyCursor {
2949    bool active;
2950
2951  public:
2952    AvenBusyCursor() : active(true) {
2953        wxBeginBusyCursor();
2954    }
2955
2956    void stop() {
2957        if (active) {
2958            active = false;
2959            wxEndBusyCursor();
2960        }
2961    }
2962
2963    ~AvenBusyCursor() {
2964        stop();
2965    }
2966};
2967
2968void GfxCore::DrawTerrain()
2969{
2970    if (!dem) return;
2971
2972    AvenBusyCursor hourglass;
2973
2974    // Draw terrain to twice the extent, or at least 1km.
2975    double r_sqrd = sqrd(max(m_Parent->GetExtent().magnitude(), 1000.0));
2976#define WGS84_DATUM_STRING "+proj=longlat +ellps=WGS84 +datum=WGS84"
2977    static projPJ pj_in = pj_init_plus(WGS84_DATUM_STRING);
2978    if (!pj_in) {
2979        ToggleTerrain();
2980        delete [] dem;
2981        dem = NULL;
2982        hourglass.stop();
2983        error(/*Failed to initialise input coordinate system “%s”*/287, WGS84_DATUM_STRING);
2984        return;
2985    }
2986    static projPJ pj_out = pj_init_plus(m_Parent->GetCSProj().c_str());
2987    if (!pj_out) {
2988        ToggleTerrain();
2989        delete [] dem;
2990        dem = NULL;
2991        hourglass.stop();
2992        error(/*Failed to initialise output coordinate system “%s”*/288, (const char *)m_Parent->GetCSProj().c_str());
2993        return;
2994    }
2995    n_tris = 0;
2996    SetAlpha(0.3);
2997    BeginTriangles();
2998    const Vector3 & off = m_Parent->GetOffset();
2999    vector<Vector3> prevcol(dem_height + 1);
3000    for (size_t x = 0; x < dem_width; ++x) {
3001        double X_ = (o_x + x * step_x) * DEG_TO_RAD;
3002        Vector3 prev;
3003        for (size_t y = 0; y < dem_height; ++y) {
3004            unsigned short elev = dem[x + y * dem_width];
3005#ifdef WORDS_BIGENDIAN
3006            const bool MACHINE_BIGENDIAN = true;
3007#else
3008            const bool MACHINE_BIGENDIAN = false;
3009#endif
3010            if (bigendian != MACHINE_BIGENDIAN) {
3011#if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408)
3012                elev = __builtin_bswap16(elev);
3013#else
3014                elev = (elev >> 8) | (elev << 8);
3015#endif
3016            }
3017            double Z = (short)elev;
3018            Vector3 pt;
3019            if (Z == nodata_value) {
3020                pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
3021            } else {
3022                double X = X_;
3023                double Y = (o_y - y * step_y) * DEG_TO_RAD;
3024                pj_transform(pj_in, pj_out, 1, 1, &X, &Y, &Z);
3025                pt = Vector3(X, Y, Z) - off;
3026                double dist_2 = sqrd(pt.GetX()) + sqrd(pt.GetY());
3027                if (dist_2 > r_sqrd) {
3028                    pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
3029                }
3030            }
3031            if (x > 0 && y > 0) {
3032                const Vector3 & a = prevcol[y - 1];
3033                const Vector3 & b = prevcol[y];
3034                // If all points are valid, split the quadrilateral into
3035                // triangles along the shorter 3D diagonal, which typically
3036                // looks better:
3037                //
3038                //               ----->
3039                //     prev---a    x     prev---a
3040                //   |   |P  /|            |\  S|
3041                // y |   |  / |    or      | \  |
3042                //   V   | /  |            |  \ |
3043                //       |/  Q|            |R  \|
3044                //       b----pt           b----pt
3045                //
3046                //       FORWARD           BACKWARD
3047                enum { NONE = 0, P = 1, Q = 2, R = 4, S = 8, ALL = P|Q|R|S };
3048                int valid =
3049                    ((prev.GetZ() != DBL_MAX)) |
3050                    ((a.GetZ() != DBL_MAX) << 1) |
3051                    ((b.GetZ() != DBL_MAX) << 2) |
3052                    ((pt.GetZ() != DBL_MAX) << 3);
3053                static const int tris_map[16] = {
3054                    NONE, // nothing valid
3055                    NONE, // prev
3056                    NONE, // a
3057                    NONE, // a, prev
3058                    NONE, // b
3059                    NONE, // b, prev
3060                    NONE, // b, a
3061                    P, // b, a, prev
3062                    NONE, // pt
3063                    NONE, // pt, prev
3064                    NONE, // pt, a
3065                    S, // pt, a, prev
3066                    NONE, // pt, b
3067                    R, // pt, b, prev
3068                    Q, // pt, b, a
3069                    ALL, // pt, b, a, prev
3070                };
3071                int tris = tris_map[valid];
3072                if (tris == ALL) {
3073                    // All points valid.
3074                    if ((a - b).magnitude() < (prev - pt).magnitude()) {
3075                        tris = P | Q;
3076                    } else {
3077                        tris = R | S;
3078                    }
3079                }
3080                if (tris & P)
3081                    DrawTerrainTriangle(a, prev, b);
3082                if (tris & Q)
3083                    DrawTerrainTriangle(a, b, pt);
3084                if (tris & R)
3085                    DrawTerrainTriangle(pt, prev, b);
3086                if (tris & S)
3087                    DrawTerrainTriangle(a, prev, pt);
3088            }
3089            prev = prevcol[y];
3090            prevcol[y].assign(pt);
3091        }
3092    }
3093    EndTriangles();
3094    SetAlpha(1.0);
3095    if (n_tris == 0) {
3096        ToggleTerrain();
3097        delete [] dem;
3098        dem = NULL;
3099        hourglass.stop();
3100        /* TRANSLATORS: Aven shows a circle of terrain covering the area
3101         * of the survey plus a bit, but the terrain data file didn't
3102         * contain any data inside that circle.
3103         */
3104        error(/*No terrain data near area of survey*/161);
3105    }
3106}
3107
3108// Plot blobs.
3109void GfxCore::GenerateBlobsDisplayList()
3110{
3111    if (!(m_Entrances || m_FixedPts || m_ExportedPts ||
3112          m_Parent->GetNumHighlightedPts()))
3113        return;
3114
3115    // Plot blobs.
3116    const SurveyFilter* filter = m_Parent->GetTreeFilter();
3117    gla_colour prev_col = col_BLACK; // not a colour used for blobs
3118    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
3119    BeginBlobs();
3120    while (pos != m_Parent->GetLabelsEnd()) {
3121        const LabelInfo* label = *pos++;
3122
3123        // When more than one flag is set on a point:
3124        // search results take priority over entrance highlighting
3125        // which takes priority over fixed point
3126        // highlighting, which in turn takes priority over exported
3127        // point highlighting.
3128
3129        if (m_Splays == SHOW_HIDE && label->IsSplayEnd())
3130            continue;
3131
3132        if (!((m_Surface && label->IsSurface()) ||
3133              (m_Legs && label->IsUnderground()) ||
3134              (!label->IsSurface() && !label->IsUnderground()))) {
3135            // if this station isn't to be displayed, skip to the next
3136            // (last case is for stns with no legs attached)
3137            continue;
3138        }
3139        if (filter && !filter->CheckVisible(label->GetText()))
3140            continue;
3141
3142        gla_colour col;
3143
3144        if (label->IsHighLighted()) {
3145            col = col_YELLOW;
3146        } else if (m_Entrances && label->IsEntrance()) {
3147            col = col_GREEN;
3148        } else if (m_FixedPts && label->IsFixedPt()) {
3149            col = col_RED;
3150        } else if (m_ExportedPts && label->IsExportedPt()) {
3151            col = col_TURQUOISE;
3152        } else {
3153            continue;
3154        }
3155
3156        // Stations are sorted by blob type, so colour changes are infrequent.
3157        if (col != prev_col) {
3158            SetColour(col);
3159            prev_col = col;
3160        }
3161        DrawBlob(label->GetX(), label->GetY(), label->GetZ());
3162    }
3163    EndBlobs();
3164}
3165
3166void GfxCore::DrawIndicators()
3167{
3168    // Draw colour key.
3169    if (m_ColourKey) {
3170        drawing_list key_list = LIST_LIMIT_;
3171        switch (m_ColourBy) {
3172            case COLOUR_BY_DEPTH:
3173                key_list = LIST_DEPTH_KEY; break;
3174            case COLOUR_BY_DATE:
3175                key_list = LIST_DATE_KEY; break;
3176            case COLOUR_BY_ERROR:
3177                key_list = LIST_ERROR_KEY; break;
3178            case COLOUR_BY_GRADIENT:
3179                key_list = LIST_GRADIENT_KEY; break;
3180            case COLOUR_BY_LENGTH:
3181                key_list = LIST_LENGTH_KEY; break;
3182#if 0 // FIXME Key for survey colours?
3183            case COLOUR_BY_SURVEY:
3184                key_list = LIST_SURVEY_KEY; break;
3185#endif
3186        }
3187        if (key_list != LIST_LIMIT_) {
3188            DrawList2D(key_list, GetXSize() - KEY_OFFSET_X,
3189                       GetYSize() - KEY_OFFSET_Y, 0);
3190        }
3191    }
3192
3193    // Draw compass or elevation/heading indicators.
3194    if (m_Compass || m_Clino) {
3195        if (!m_Parent->IsExtendedElevation()) Draw2dIndicators();
3196    }
3197
3198    // Draw scalebar.
3199    if (m_Scalebar && !GetPerspective()) {
3200        DrawList2D(LIST_SCALE_BAR, 0, 0, 0);
3201    }
3202}
3203
3204void GfxCore::PlaceVertexWithColour(const Vector3 & v,
3205                                    glaTexCoord tex_x, glaTexCoord tex_y,
3206                                    Double factor)
3207{
3208    SetColour(col_WHITE, factor);
3209    PlaceVertex(v, tex_x, tex_y);
3210}
3211
3212void GfxCore::SetDepthColour(Double z, Double factor) {
3213    // Set the drawing colour based on the altitude.
3214    Double z_ext = m_Parent->GetDepthExtent();
3215
3216    z -= m_Parent->GetDepthMin();
3217    // points arising from tubes may be slightly outside the limits...
3218    if (z < 0) z = 0;
3219    if (z > z_ext) z = z_ext;
3220
3221    if (z == 0) {
3222        SetColour(GetPen(0), factor);
3223        return;
3224    }
3225
3226    assert(z_ext > 0.0);
3227    Double how_far = z / z_ext;
3228    assert(how_far >= 0.0);
3229    assert(how_far <= 1.0);
3230
3231    int band = int(floor(how_far * (GetNumColourBands() - 1)));
3232    GLAPen pen1 = GetPen(band);
3233    if (band < GetNumColourBands() - 1) {
3234        const GLAPen& pen2 = GetPen(band + 1);
3235
3236        Double interval = z_ext / (GetNumColourBands() - 1);
3237        Double into_band = z / interval - band;
3238
3239//      printf("%g z_offset=%g interval=%g band=%d\n", into_band,
3240//             z_offset, interval, band);
3241        // FIXME: why do we need to clamp here?  Is it because the walls can
3242        // extend further up/down than the centre-line?
3243        if (into_band < 0.0) into_band = 0.0;
3244        if (into_band > 1.0) into_band = 1.0;
3245        assert(into_band >= 0.0);
3246        assert(into_band <= 1.0);
3247
3248        pen1.Interpolate(pen2, into_band);
3249    }
3250    SetColour(pen1, factor);
3251}
3252
3253void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v, Double factor)
3254{
3255    SetDepthColour(v.GetZ(), factor);
3256    PlaceVertex(v);
3257}
3258
3259void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v,
3260                                         glaTexCoord tex_x, glaTexCoord tex_y,
3261                                         Double factor)
3262{
3263    SetDepthColour(v.GetZ(), factor);
3264    PlaceVertex(v, tex_x, tex_y);
3265}
3266
3267void GfxCore::SplitLineAcrossBands(int band, int band2,
3268                                   const Vector3 &p, const Vector3 &q,
3269                                   Double factor)
3270{
3271    const int step = (band < band2) ? 1 : -1;
3272    for (int i = band; i != band2; i += step) {
3273        const Double z = GetDepthBoundaryBetweenBands(i, i + step);
3274
3275        // Find the intersection point of the line p -> q
3276        // with the plane parallel to the xy-plane with z-axis intersection z.
3277        assert(q.GetZ() - p.GetZ() != 0.0);
3278
3279        const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
3280//      assert(0.0 <= t && t <= 1.0);           FIXME: rounding problems!
3281
3282        const Double x = p.GetX() + t * (q.GetX() - p.GetX());
3283        const Double y = p.GetY() + t * (q.GetY() - p.GetY());
3284
3285        PlaceVertexWithDepthColour(Vector3(x, y, z), factor);
3286    }
3287}
3288
3289void GfxCore::SplitPolyAcrossBands(vector<vector<Split>>& splits,
3290                                   int band, int band2,
3291                                   const Vector3 &p, const Vector3 &q,
3292                                   glaTexCoord ptx, glaTexCoord pty,
3293                                   glaTexCoord w, glaTexCoord h)
3294{
3295    const int step = (band < band2) ? 1 : -1;
3296    for (int i = band; i != band2; i += step) {
3297        const Double z = GetDepthBoundaryBetweenBands(i, i + step);
3298
3299        // Find the intersection point of the line p -> q
3300        // with the plane parallel to the xy-plane with z-axis intersection z.
3301        assert(q.GetZ() - p.GetZ() != 0.0);
3302
3303        const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
3304//      assert(0.0 <= t && t <= 1.0);           FIXME: rounding problems!
3305
3306        const Double x = p.GetX() + t * (q.GetX() - p.GetX());
3307        const Double y = p.GetY() + t * (q.GetY() - p.GetY());
3308        glaTexCoord tx = ptx, ty = pty;
3309        if (w) tx += t * w;
3310        if (h) ty += t * h;
3311
3312        splits[i].push_back(Split(Vector3(x, y, z), tx, ty));
3313        splits[i + step].push_back(Split(Vector3(x, y, z), tx, ty));
3314    }
3315}
3316
3317int GfxCore::GetDepthColour(Double z) const
3318{
3319    // Return the (0-based) depth colour band index for a z-coordinate.
3320    Double z_ext = m_Parent->GetDepthExtent();
3321    z -= m_Parent->GetDepthMin();
3322    // We seem to get rounding differences causing z to sometimes be slightly
3323    // less than GetDepthMin() here, and it can certainly be true for passage
3324    // tubes, so just clamp the value to 0.
3325    if (z <= 0) return 0;
3326    // We seem to get rounding differences causing z to sometimes exceed z_ext
3327    // by a small amount here (see: https://trac.survex.com/ticket/26) and it
3328    // can certainly be true for passage tubes, so just clamp the value.
3329    if (z >= z_ext) return GetNumColourBands() - 1;
3330    return int(z / z_ext * (GetNumColourBands() - 1));
3331}
3332
3333Double GfxCore::GetDepthBoundaryBetweenBands(int a, int b) const
3334{
3335    // Return the z-coordinate of the depth colour boundary between
3336    // two adjacent depth colour bands (specified by 0-based indices).
3337
3338    assert((a == b - 1) || (a == b + 1));
3339    if (GetNumColourBands() == 1) return 0;
3340
3341    int band = (a > b) ? a : b; // boundary N lies on the bottom of band N.
3342    Double z_ext = m_Parent->GetDepthExtent();
3343    return (z_ext * band / (GetNumColourBands() - 1)) + m_Parent->GetDepthMin();
3344}
3345
3346void GfxCore::AddPolyline(const traverse & centreline)
3347{
3348    BeginPolyline();
3349    SetColour(col_WHITE);
3350    vector<PointInfo>::const_iterator i = centreline.begin();
3351    PlaceVertex(*i);
3352    ++i;
3353    while (i != centreline.end()) {
3354        PlaceVertex(*i);
3355        ++i;
3356    }
3357    EndPolyline();
3358}
3359
3360void GfxCore::AddPolylineShadow(const traverse & centreline)
3361{
3362    BeginPolyline();
3363    const double z = -0.5 * m_Parent->GetExtent().GetZ();
3364    vector<PointInfo>::const_iterator i = centreline.begin();
3365    PlaceVertex(i->GetX(), i->GetY(), z);
3366    ++i;
3367    while (i != centreline.end()) {
3368        PlaceVertex(i->GetX(), i->GetY(), z);
3369        ++i;
3370    }
3371    EndPolyline();
3372}
3373
3374void GfxCore::AddPolylineDepth(const traverse & centreline)
3375{
3376    BeginPolyline();
3377    vector<PointInfo>::const_iterator i, prev_i;
3378    i = centreline.begin();
3379    int band0 = GetDepthColour(i->GetZ());
3380    PlaceVertexWithDepthColour(*i);
3381    prev_i = i;
3382    ++i;
3383    while (i != centreline.end()) {
3384        int band = GetDepthColour(i->GetZ());
3385        if (band != band0) {
3386            SplitLineAcrossBands(band0, band, *prev_i, *i);
3387            band0 = band;
3388        }
3389        PlaceVertexWithDepthColour(*i);
3390        prev_i = i;
3391        ++i;
3392    }
3393    EndPolyline();
3394}
3395
3396void GfxCore::AddQuadrilateral(const Vector3 &a, const Vector3 &b,
3397                               const Vector3 &c, const Vector3 &d)
3398{
3399    Vector3 normal = (a - c) * (d - b);
3400    normal.normalise();
3401    Double factor = dot(normal, light) * .3 + .7;
3402    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3403    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3404    // FIXME: should plot triangles instead to avoid rendering glitches.
3405    BeginQuadrilaterals();
3406    PlaceVertexWithColour(a, 0, 0, factor);
3407    PlaceVertexWithColour(b, w, 0, factor);
3408    PlaceVertexWithColour(c, w, h, factor);
3409    PlaceVertexWithColour(d, 0, h, factor);
3410    EndQuadrilaterals();
3411}
3412
3413void GfxCore::AddQuadrilateralDepth(const Vector3 &a, const Vector3 &b,
3414                                    const Vector3 &c, const Vector3 &d)
3415{
3416    Vector3 normal = (a - c) * (d - b);
3417    normal.normalise();
3418    Double factor = dot(normal, light) * .3 + .7;
3419    int a_band, b_band, c_band, d_band;
3420    a_band = GetDepthColour(a.GetZ());
3421    a_band = min(max(a_band, 0), GetNumColourBands());
3422    b_band = GetDepthColour(b.GetZ());
3423    b_band = min(max(b_band, 0), GetNumColourBands());
3424    c_band = GetDepthColour(c.GetZ());
3425    c_band = min(max(c_band, 0), GetNumColourBands());
3426    d_band = GetDepthColour(d.GetZ());
3427    d_band = min(max(d_band, 0), GetNumColourBands());
3428    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3429    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3430    int min_band = min(min(a_band, b_band), min(c_band, d_band));
3431    int max_band = max(max(a_band, b_band), max(c_band, d_band));
3432    if (min_band == max_band) {
3433        // Simple case - the polygon is entirely within one band.
3434        BeginPolygon();
3435////    PlaceNormal(normal);
3436        PlaceVertexWithDepthColour(a, 0, 0, factor);
3437        PlaceVertexWithDepthColour(b, w, 0, factor);
3438        PlaceVertexWithDepthColour(c, w, h, factor);
3439        PlaceVertexWithDepthColour(d, 0, h, factor);
3440        EndPolygon();
3441    } else {
3442        // We need to make a separate polygon for each depth band...
3443        vector<vector<Split>> splits;
3444        splits.resize(max_band + 1);
3445        splits[a_band].push_back(Split(a, 0, 0));
3446        if (a_band != b_band) {
3447            SplitPolyAcrossBands(splits, a_band, b_band, a, b, 0, 0, w, 0);
3448        }
3449        splits[b_band].push_back(Split(b, w, 0));
3450        if (b_band != c_band) {
3451            SplitPolyAcrossBands(splits, b_band, c_band, b, c, w, 0, 0, h);
3452        }
3453        splits[c_band].push_back(Split(c, w, h));
3454        if (c_band != d_band) {
3455            SplitPolyAcrossBands(splits, c_band, d_band, c, d, w, h, -w, 0);
3456        }
3457        splits[d_band].push_back(Split(d, 0, h));
3458        if (d_band != a_band) {
3459            SplitPolyAcrossBands(splits, d_band, a_band, d, a, 0, h, 0, -h);
3460        }
3461        for (int band = min_band; band <= max_band; ++band) {
3462            BeginPolygon();
3463            for (auto&& item : splits[band]) {
3464                PlaceVertexWithDepthColour(item.vec, item.tx, item.ty, factor);
3465            }
3466            EndPolygon();
3467        }
3468    }
3469}
3470
3471void GfxCore::SetColourFromDate(int date, Double factor)
3472{
3473    // Set the drawing colour based on a date.
3474
3475    if (date == -1) {
3476        // Undated.
3477        SetColour(NODATA_COLOUR, factor);
3478        return;
3479    }
3480
3481    int date_offset = date - m_Parent->GetDateMin();
3482    if (date_offset == 0) {
3483        // Earliest date - handle as a special case for the single date case.
3484        SetColour(GetPen(0), factor);
3485        return;
3486    }
3487
3488    int date_ext = m_Parent->GetDateExtent();
3489    Double how_far = (Double)date_offset / date_ext;
3490    assert(how_far >= 0.0);
3491    assert(how_far <= 1.0);
3492    SetColourFrom01(how_far, factor);
3493}
3494
3495void GfxCore::AddPolylineDate(const traverse & centreline)
3496{
3497    BeginPolyline();
3498    vector<PointInfo>::const_iterator i, prev_i;
3499    i = centreline.begin();
3500    int date = i->GetDate();
3501    SetColourFromDate(date, 1.0);
3502    PlaceVertex(*i);
3503    prev_i = i;
3504    while (++i != centreline.end()) {
3505        int newdate = i->GetDate();
3506        if (newdate != date) {
3507            EndPolyline();
3508            BeginPolyline();
3509            date = newdate;
3510            SetColourFromDate(date, 1.0);
3511            PlaceVertex(*prev_i);
3512        }
3513        PlaceVertex(*i);
3514        prev_i = i;
3515    }
3516    EndPolyline();
3517}
3518
3519static int static_date_hack; // FIXME
3520
3521void GfxCore::AddQuadrilateralDate(const Vector3 &a, const Vector3 &b,
3522                                   const Vector3 &c, const Vector3 &d)
3523{
3524    Vector3 normal = (a - c) * (d - b);
3525    normal.normalise();
3526    Double factor = dot(normal, light) * .3 + .7;
3527    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3528    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3529    // FIXME: should plot triangles instead to avoid rendering glitches.
3530    BeginQuadrilaterals();
3531////    PlaceNormal(normal);
3532    SetColourFromDate(static_date_hack, factor);
3533    PlaceVertex(a, 0, 0);
3534    PlaceVertex(b, w, 0);
3535    PlaceVertex(c, w, h);
3536    PlaceVertex(d, 0, h);
3537    EndQuadrilaterals();
3538}
3539
3540static double static_E_hack; // FIXME
3541
3542void GfxCore::SetColourFromError(double E, Double factor)
3543{
3544    // Set the drawing colour based on an error value.
3545
3546    if (E < 0) {
3547        SetColour(NODATA_COLOUR, factor);
3548        return;
3549    }
3550
3551    Double how_far = E / MAX_ERROR;
3552    assert(how_far >= 0.0);
3553    if (how_far > 1.0) how_far = 1.0;
3554    SetColourFrom01(how_far, factor);
3555}
3556
3557void GfxCore::AddQuadrilateralError(const Vector3 &a, const Vector3 &b,
3558                                    const Vector3 &c, const Vector3 &d)
3559{
3560    Vector3 normal = (a - c) * (d - b);
3561    normal.normalise();
3562    Double factor = dot(normal, light) * .3 + .7;
3563    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3564    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3565    // FIXME: should plot triangles instead to avoid rendering glitches.
3566    BeginQuadrilaterals();
3567////    PlaceNormal(normal);
3568    SetColourFromError(static_E_hack, factor);
3569    PlaceVertex(a, 0, 0);
3570    PlaceVertex(b, w, 0);
3571    PlaceVertex(c, w, h);
3572    PlaceVertex(d, 0, h);
3573    EndQuadrilaterals();
3574}
3575
3576void GfxCore::AddPolylineError(const traverse & centreline)
3577{
3578    BeginPolyline();
3579    SetColourFromError(centreline.E, 1.0);
3580    vector<PointInfo>::const_iterator i;
3581    for(i = centreline.begin(); i != centreline.end(); ++i) {
3582        PlaceVertex(*i);
3583    }
3584    EndPolyline();
3585}
3586
3587// gradient is in *radians*.
3588void GfxCore::SetColourFromGradient(double gradient, Double factor)
3589{
3590    // Set the drawing colour based on the gradient of the leg.
3591
3592    const Double GRADIENT_MAX = M_PI_2;
3593    gradient = fabs(gradient);
3594    Double how_far = gradient / GRADIENT_MAX;
3595    SetColourFrom01(how_far, factor);
3596}
3597
3598void GfxCore::AddPolylineGradient(const traverse & centreline)
3599{
3600    vector<PointInfo>::const_iterator i, prev_i;
3601    i = centreline.begin();
3602    prev_i = i;
3603    while (++i != centreline.end()) {
3604        BeginPolyline();
3605        SetColourFromGradient((*i - *prev_i).gradient(), 1.0);
3606        PlaceVertex(*prev_i);
3607        PlaceVertex(*i);
3608        prev_i = i;
3609        EndPolyline();
3610    }
3611}
3612
3613static double static_gradient_hack; // FIXME
3614
3615void GfxCore::AddQuadrilateralGradient(const Vector3 &a, const Vector3 &b,
3616                                       const Vector3 &c, const Vector3 &d)
3617{
3618    Vector3 normal = (a - c) * (d - b);
3619    normal.normalise();
3620    Double factor = dot(normal, light) * .3 + .7;
3621    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3622    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3623    // FIXME: should plot triangles instead to avoid rendering glitches.
3624    BeginQuadrilaterals();
3625////    PlaceNormal(normal);
3626    SetColourFromGradient(static_gradient_hack, factor);
3627    PlaceVertex(a, 0, 0);
3628    PlaceVertex(b, w, 0);
3629    PlaceVertex(c, w, h);
3630    PlaceVertex(d, 0, h);
3631    EndQuadrilaterals();
3632}
3633
3634void GfxCore::SetColourFromLength(double length, Double factor)
3635{
3636    // Set the drawing colour based on log(length_of_leg).
3637
3638    Double log_len = log10(length);
3639    Double how_far = log_len / LOG_LEN_MAX;
3640    how_far = max(how_far, 0.0);
3641    how_far = min(how_far, 1.0);
3642    SetColourFrom01(how_far, factor);
3643}
3644
3645void GfxCore::SetColourFromSurvey(const wxString& survey)
3646{
3647    // Set the drawing colour based on hash of name.
3648    int hash = hash_string(survey.utf8_str());
3649    wxImage::HSVValue hsv((hash & 0xff) / 256.0, (((hash >> 8) & 0x7f) | 0x80) / 256.0, 0.9);
3650    wxImage::RGBValue rgb = wxImage::HSVtoRGB(hsv);
3651    GLAPen pen;
3652    pen.SetColour(rgb.red / 256.0, rgb.green / 256.0, rgb.blue / 256.0);
3653    SetColour(pen);
3654}
3655
3656void GfxCore::SetColourFromSurveyStation(const wxString& name, Double factor)
3657{
3658    // Set the drawing colour based on hash of survey name.
3659    const char* p = name.utf8_str();
3660    const char* q = strrchr(p, m_Parent->GetSeparator());
3661    size_t len = q ? (q - p) : strlen(p);
3662    int hash = hash_data(p, len);
3663    wxImage::HSVValue hsv((hash & 0xff) / 256.0, (((hash >> 8) & 0x7f) | 0x80) / 256.0, 0.9);
3664    wxImage::RGBValue rgb = wxImage::HSVtoRGB(hsv);
3665    GLAPen pen;
3666    pen.SetColour(rgb.red / 256.0, rgb.green / 256.0, rgb.blue / 256.0);
3667    SetColour(pen, factor);
3668}
3669
3670void GfxCore::SetColourFrom01(double how_far, Double factor)
3671{
3672    double b;
3673    double into_band = modf(how_far * (GetNumColourBands() - 1), &b);
3674    int band(b);
3675    GLAPen pen1 = GetPen(band);
3676    // With 24bit colour, interpolating by less than this can have no effect.
3677    if (into_band >= 1.0 / 512.0) {
3678        const GLAPen& pen2 = GetPen(band + 1);
3679        pen1.Interpolate(pen2, into_band);
3680    }
3681    SetColour(pen1, factor);
3682}
3683
3684void GfxCore::AddPolylineLength(const traverse & centreline)
3685{
3686    vector<PointInfo>::const_iterator i, prev_i;
3687    i = centreline.begin();
3688    prev_i = i;
3689    while (++i != centreline.end()) {
3690        BeginPolyline();
3691        SetColourFromLength((*i - *prev_i).magnitude(), 1.0);
3692        PlaceVertex(*prev_i);
3693        PlaceVertex(*i);
3694        prev_i = i;
3695        EndPolyline();
3696    }
3697}
3698
3699static double static_length_hack; // FIXME
3700
3701void GfxCore::AddQuadrilateralLength(const Vector3 &a, const Vector3 &b,
3702                                     const Vector3 &c, const Vector3 &d)
3703{
3704    Vector3 normal = (a - c) * (d - b);
3705    normal.normalise();
3706    Double factor = dot(normal, light) * .3 + .7;
3707    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3708    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3709    // FIXME: should plot triangles instead to avoid rendering glitches.
3710    BeginQuadrilaterals();
3711////    PlaceNormal(normal);
3712    SetColourFromLength(static_length_hack, factor);
3713    PlaceVertex(a, 0, 0);
3714    PlaceVertex(b, w, 0);
3715    PlaceVertex(c, w, h);
3716    PlaceVertex(d, 0, h);
3717    EndQuadrilaterals();
3718}
3719
3720void GfxCore::AddPolylineSurvey(const traverse & centreline)
3721{
3722    SetColourFromSurvey(centreline.name);
3723    vector<PointInfo>::const_iterator i, prev_i;
3724    i = centreline.begin();
3725    prev_i = i;
3726    while (++i != centreline.end()) {
3727        BeginPolyline();
3728        PlaceVertex(*prev_i);
3729        PlaceVertex(*i);
3730        prev_i = i;
3731        EndPolyline();
3732    }
3733}
3734
3735static const wxString* static_survey_hack;
3736
3737void GfxCore::AddQuadrilateralSurvey(const Vector3 &a, const Vector3 &b,
3738                                     const Vector3 &c, const Vector3 &d)
3739{
3740    Vector3 normal = (a - c) * (d - b);
3741    normal.normalise();
3742    Double factor = dot(normal, light) * .3 + .7;
3743    glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3744    glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3745    // FIXME: should plot triangles instead to avoid rendering glitches.
3746    BeginQuadrilaterals();
3747////    PlaceNormal(normal);
3748    SetColourFromSurveyStation(*static_survey_hack, factor);
3749    PlaceVertex(a, 0, 0);
3750    PlaceVertex(b, w, 0);
3751    PlaceVertex(c, w, h);
3752    PlaceVertex(d, 0, h);
3753    EndQuadrilaterals();
3754}
3755
3756void
3757GfxCore::SkinPassage(vector<XSect> & centreline)
3758{
3759    const SurveyFilter* filter = m_Parent->GetTreeFilter();
3760    assert(centreline.size() > 1);
3761    Vector3 U[4];
3762    XSect* prev_pt_v = NULL;
3763    Vector3 last_right(1.0, 0.0, 0.0);
3764
3765//  FIXME: it's not simple to set the colour of a tube based on error...
3766//    static_E_hack = something...
3767    vector<XSect>::iterator i = centreline.begin();
3768    vector<XSect>::size_type segment = 0;
3769    while (i != centreline.end()) {
3770        // get the coordinates of this vertex
3771        XSect & pt_v = *i++;
3772
3773        bool cover_end = false;
3774
3775        Vector3 right, up;
3776
3777        const Vector3 up_v(0.0, 0.0, 1.0);
3778
3779        static_survey_hack = &(pt_v.GetLabel());
3780        if (segment == 0) {
3781            assert(i != centreline.end());
3782            // first segment
3783
3784            // get the coordinates of the next vertex
3785            const XSect & next_pt_v = *i;
3786
3787            // calculate vector from this pt to the next one
3788            Vector3 leg_v = next_pt_v - pt_v;
3789
3790            // obtain a vector in the LRUD plane
3791            right = leg_v * up_v;
3792            if (right.magnitude() == 0) {
3793                right = last_right;
3794                // Obtain a second vector in the LRUD plane,
3795                // perpendicular to the first.
3796                //up = right * leg_v;
3797                up = up_v;
3798            } else {
3799                last_right = right;
3800                up = up_v;
3801            }
3802
3803            cover_end = true;
3804            static_date_hack = next_pt_v.GetDate();
3805        } else if (segment + 1 == centreline.size()) {
3806            // last segment
3807
3808            // Calculate vector from the previous pt to this one.
3809            Vector3 leg_v = pt_v - *prev_pt_v;
3810
3811            // Obtain a horizontal vector in the LRUD plane.
3812            right = leg_v * up_v;
3813            if (right.magnitude() == 0) {
3814                right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
3815                // Obtain a second vector in the LRUD plane,
3816                // perpendicular to the first.
3817                //up = right * leg_v;
3818                up = up_v;
3819            } else {
3820                last_right = right;
3821                up = up_v;
3822            }
3823
3824            cover_end = true;
3825            static_date_hack = pt_v.GetDate();
3826        } else {
3827            assert(i != centreline.end());
3828            // Intermediate segment.
3829
3830            // Get the coordinates of the next vertex.
3831            const XSect & next_pt_v = *i;
3832
3833            // Calculate vectors from this vertex to the
3834            // next vertex, and from the previous vertex to
3835            // this one.
3836            Vector3 leg1_v = pt_v - *prev_pt_v;
3837            Vector3 leg2_v = next_pt_v - pt_v;
3838
3839            // Obtain horizontal vectors perpendicular to
3840            // both legs, then normalise and average to get
3841            // a horizontal bisector.
3842            Vector3 r1 = leg1_v * up_v;
3843            Vector3 r2 = leg2_v * up_v;
3844            r1.normalise();
3845            r2.normalise();
3846            right = r1 + r2;
3847            if (right.magnitude() == 0) {
3848                // This is the "mid-pitch" case...
3849                right = last_right;
3850            }
3851            if (r1.magnitude() == 0) {
3852                up = up_v;
3853
3854                // Rotate pitch section to minimise the
3855                // "torsional stress" - FIXME: use
3856                // triangles instead of rectangles?
3857                int shift = 0;
3858                Double maxdotp = 0;
3859
3860                // Scale to unit vectors in the LRUD plane.
3861                right.normalise();
3862                up.normalise();
3863                Vector3 vec = up - right;
3864                for (int orient = 0; orient <= 3; ++orient) {
3865                    Vector3 tmp = U[orient] - prev_pt_v->GetPoint();
3866                    tmp.normalise();
3867                    Double dotp = dot(vec, tmp);
3868                    if (dotp > maxdotp) {
3869                        maxdotp = dotp;
3870                        shift = orient;
3871                    }
3872                }
3873                if (shift) {
3874                    if (shift != 2) {
3875                        Vector3 temp(U[0]);
3876                        U[0] = U[shift];
3877                        U[shift] = U[2];
3878                        U[2] = U[shift ^ 2];
3879                        U[shift ^ 2] = temp;
3880                    } else {
3881                        swap(U[0], U[2]);
3882                        swap(U[1], U[3]);
3883                    }
3884                }
3885#if 0
3886                // Check that the above code actually permuted
3887                // the vertices correctly.
3888                shift = 0;
3889                maxdotp = 0;
3890                for (int j = 0; j <= 3; ++j) {
3891                    Vector3 tmp = U[j] - *prev_pt_v;
3892                    tmp.normalise();
3893                    Double dotp = dot(vec, tmp);
3894                    if (dotp > maxdotp) {
3895                        maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
3896                        shift = j;
3897                    }
3898                }
3899                if (shift) {
3900                    printf("New shift = %d!\n", shift);
3901                    shift = 0;
3902                    maxdotp = 0;
3903                    for (int j = 0; j <= 3; ++j) {
3904                        Vector3 tmp = U[j] - *prev_pt_v;
3905                        tmp.normalise();
3906                        Double dotp = dot(vec, tmp);
3907                        printf("    %d : %.8f\n", j, dotp);
3908                    }
3909                }
3910#endif
3911            } else {
3912                up = up_v;
3913            }
3914            last_right = right;
3915            static_date_hack = pt_v.GetDate();
3916        }
3917
3918        // Scale to unit vectors in the LRUD plane.
3919        right.normalise();
3920        up.normalise();
3921
3922        Double l = fabs(pt_v.GetL());
3923        Double r = fabs(pt_v.GetR());
3924        Double u = fabs(pt_v.GetU());
3925        Double d = fabs(pt_v.GetD());
3926
3927        // Produce coordinates of the corners of the LRUD "plane".
3928        Vector3 v[4];
3929        v[0] = pt_v.GetPoint() - right * l + up * u;
3930        v[1] = pt_v.GetPoint() + right * r + up * u;
3931        v[2] = pt_v.GetPoint() + right * r - up * d;
3932        v[3] = pt_v.GetPoint() - right * l - up * d;
3933
3934        if (segment > 0) {
3935            if (!filter || (filter->CheckVisible(pt_v.GetLabel()) &&
3936                            filter->CheckVisible(prev_pt_v->GetLabel()))) {
3937                const Vector3 & delta = pt_v - *prev_pt_v;
3938                static_length_hack = delta.magnitude();
3939                static_gradient_hack = delta.gradient();
3940                (this->*AddQuad)(v[0], v[1], U[1], U[0]);
3941                (this->*AddQuad)(v[2], v[3], U[3], U[2]);
3942                (this->*AddQuad)(v[1], v[2], U[2], U[1]);
3943                (this->*AddQuad)(v[3], v[0], U[0], U[3]);
3944            }
3945        }
3946
3947        if (cover_end) {
3948            if (!filter || filter->CheckVisible(pt_v.GetLabel())) {
3949                if (segment == 0) {
3950                    (this->*AddQuad)(v[0], v[1], v[2], v[3]);
3951                } else {
3952                    (this->*AddQuad)(v[3], v[2], v[1], v[0]);
3953                }
3954            }
3955        }
3956
3957        prev_pt_v = &pt_v;
3958        U[0] = v[0];
3959        U[1] = v[1];
3960        U[2] = v[2];
3961        U[3] = v[3];
3962
3963        ++segment;
3964    }
3965}
3966
3967void GfxCore::FullScreenMode()
3968{
3969    m_Parent->ViewFullScreen();
3970}
3971
3972bool GfxCore::IsFullScreen() const
3973{
3974    return m_Parent->IsFullScreen();
3975}
3976
3977bool GfxCore::FullScreenModeShowingMenus() const
3978{
3979    return m_Parent->FullScreenModeShowingMenus();
3980}
3981
3982void GfxCore::FullScreenModeShowMenus(bool show)
3983{
3984    m_Parent->FullScreenModeShowMenus(show);
3985}
3986
3987void
3988GfxCore::MoveViewer(double forward, double up, double right)
3989{
3990    double cT = cos(rad(m_TiltAngle));
3991    double sT = sin(rad(m_TiltAngle));
3992    double cP = cos(rad(m_PanAngle));
3993    double sP = sin(rad(m_PanAngle));
3994    Vector3 v_forward(cT * sP, cT * cP, sT);
3995    Vector3 v_up(sT * sP, sT * cP, -cT);
3996    Vector3 v_right(-cP, sP, 0);
3997    assert(fabs(dot(v_forward, v_up)) < 1e-6);
3998    assert(fabs(dot(v_forward, v_right)) < 1e-6);
3999    assert(fabs(dot(v_right, v_up)) < 1e-6);
4000    Vector3 move = v_forward * forward + v_up * up + v_right * right;
4001    AddTranslation(-move);
4002    // Show current position.
4003    m_Parent->SetCoords(m_Parent->GetOffset() - GetTranslation());
4004    ForceRefresh();
4005}
4006
4007PresentationMark GfxCore::GetView() const
4008{
4009    return PresentationMark(GetTranslation() + m_Parent->GetOffset(),
4010                            m_PanAngle, -m_TiltAngle, m_Scale);
4011}
4012
4013void GfxCore::SetView(const PresentationMark & p)
4014{
4015    m_SwitchingTo = 0;
4016    SetTranslation(p - m_Parent->GetOffset());
4017    m_PanAngle = p.angle;
4018    m_TiltAngle = -p.tilt_angle; // FIXME: nasty reversed sense (and above)
4019    SetRotation(m_PanAngle, m_TiltAngle);
4020    SetScale(p.scale);
4021    ForceRefresh();
4022}
4023
4024void GfxCore::PlayPres(double speed, bool change_speed) {
4025    if (!change_speed || presentation_mode == 0) {
4026        if (speed == 0.0) {
4027            presentation_mode = 0;
4028            return;
4029        }
4030        presentation_mode = PLAYING;
4031        next_mark = m_Parent->GetPresMark(MARK_FIRST);
4032        SetView(next_mark);
4033        next_mark_time = 0; // There already!
4034        this_mark_total = 0;
4035        pres_reverse = (speed < 0);
4036    }
4037
4038    if (change_speed) pres_speed = speed;
4039
4040    if (speed != 0.0) {
4041        bool new_pres_reverse = (speed < 0);
4042        if (new_pres_reverse != pres_reverse) {
4043            pres_reverse = new_pres_reverse;
4044            if (pres_reverse) {
4045                next_mark = m_Parent->GetPresMark(MARK_PREV);
4046            } else {
4047                next_mark = m_Parent->GetPresMark(MARK_NEXT);
4048            }
4049            swap(this_mark_total, next_mark_time);
4050        }
4051    }
4052}
4053
4054void GfxCore::SetColourBy(int colour_by) {
4055    m_ColourBy = colour_by;
4056    switch (colour_by) {
4057        case COLOUR_BY_DEPTH:
4058            AddQuad = &GfxCore::AddQuadrilateralDepth;
4059            AddPoly = &GfxCore::AddPolylineDepth;
4060            break;
4061        case COLOUR_BY_DATE:
4062            AddQuad = &GfxCore::AddQuadrilateralDate;
4063            AddPoly = &GfxCore::AddPolylineDate;
4064            break;
4065        case COLOUR_BY_ERROR:
4066            AddQuad = &GfxCore::AddQuadrilateralError;
4067            AddPoly = &GfxCore::AddPolylineError;
4068            break;
4069        case COLOUR_BY_GRADIENT:
4070            AddQuad = &GfxCore::AddQuadrilateralGradient;
4071            AddPoly = &GfxCore::AddPolylineGradient;
4072            break;
4073        case COLOUR_BY_LENGTH:
4074            AddQuad = &GfxCore::AddQuadrilateralLength;
4075            AddPoly = &GfxCore::AddPolylineLength;
4076            break;
4077        case COLOUR_BY_SURVEY:
4078            AddQuad = &GfxCore::AddQuadrilateralSurvey;
4079            AddPoly = &GfxCore::AddPolylineSurvey;
4080            break;
4081        default: // case COLOUR_BY_NONE:
4082            AddQuad = &GfxCore::AddQuadrilateral;
4083            AddPoly = &GfxCore::AddPolyline;
4084            break;
4085    }
4086
4087    InvalidateList(LIST_UNDERGROUND_LEGS);
4088    InvalidateList(LIST_SURFACE_LEGS);
4089    InvalidateList(LIST_TUBES);
4090
4091    ForceRefresh();
4092}
4093
4094bool GfxCore::ExportMovie(const wxString & fnm)
4095{
4096    FILE* fh = wxFopen(fnm.fn_str(), wxT("wb"));
4097    if (fh == NULL) {
4098        wxGetApp().ReportError(wxString::Format(wmsg(/*Failed to open output file “%s”*/47), fnm.c_str()));
4099        return false;
4100    }
4101
4102    wxString ext;
4103    wxFileName::SplitPath(fnm, NULL, NULL, NULL, &ext, wxPATH_NATIVE);
4104
4105    int width;
4106    int height;
4107    GetSize(&width, &height);
4108    // Round up to next multiple of 2 (required by ffmpeg).
4109    width += (width & 1);
4110    height += (height & 1);
4111
4112    movie = new MovieMaker();
4113
4114    // movie takes ownership of fh.
4115    if (!movie->Open(fh, ext.utf8_str(), width, height)) {
4116        wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
4117        delete movie;
4118        movie = NULL;
4119        return false;
4120    }
4121
4122    PlayPres(1);
4123    return true;
4124}
4125
4126void
4127GfxCore::OnPrint(const wxString &filename, const wxString &title,
4128                 const wxString &datestamp,
4129                 bool close_after_print)
4130{
4131    svxPrintDlg * p;
4132    p = new svxPrintDlg(m_Parent, filename, title, datestamp,
4133                        m_PanAngle, m_TiltAngle,
4134                        m_Names, m_Crosses, m_Legs, m_Surface, m_Splays,
4135                        m_Tubes, m_Entrances, m_FixedPts, m_ExportedPts,
4136                        true, close_after_print);
4137    p->Show(true);
4138}
4139
4140void
4141GfxCore::OnExport(const wxString &filename, const wxString &title,
4142                  const wxString &datestamp)
4143{
4144    svxPrintDlg * p;
4145    p = new svxPrintDlg(m_Parent, filename, title, datestamp,
4146                        m_PanAngle, m_TiltAngle,
4147                        m_Names, m_Crosses, m_Legs, m_Surface, m_Splays,
4148                        m_Tubes, m_Entrances, m_FixedPts, m_ExportedPts,
4149                        false);
4150    p->Show(true);
4151}
4152
4153static wxCursor
4154make_cursor(const unsigned char * bits, const unsigned char * mask,
4155            int hotx, int hoty)
4156{
4157#if defined __WXGTK__ && !defined __WXGTK3__
4158    // Use this code for GTK < 3 only - it doesn't work properly with GTK3
4159    // (reported and should be fixed in wxWidgets 3.0.4 and 3.1.1, see:
4160    // https://trac.wxwidgets.org/ticket/17916)
4161    return wxCursor((const char *)bits, 32, 32, hotx, hoty,
4162                    (const char *)mask, wxBLACK, wxWHITE);
4163#else
4164# ifdef __WXMAC__
4165    // The default Mac cursor is black with a white edge, so
4166    // invert our custom cursors to match.
4167    char b[128];
4168    for (int i = 0; i < 128; ++i)
4169        b[i] = bits[i] ^ 0xff;
4170# else
4171    const char * b = reinterpret_cast<const char *>(bits);
4172# endif
4173    wxBitmap cursor_bitmap(b, 32, 32);
4174    wxBitmap mask_bitmap(reinterpret_cast<const char *>(mask), 32, 32);
4175    cursor_bitmap.SetMask(new wxMask(mask_bitmap, *wxWHITE));
4176    wxImage cursor_image = cursor_bitmap.ConvertToImage();
4177    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, hotx);
4178    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, hoty);
4179    return wxCursor(cursor_image);
4180#endif
4181}
4182
4183const
4184#include "hand.xbm"
4185const
4186#include "handmask.xbm"
4187
4188const
4189#include "brotate.xbm"
4190const
4191#include "brotatemask.xbm"
4192
4193const
4194#include "vrotate.xbm"
4195const
4196#include "vrotatemask.xbm"
4197
4198const
4199#include "rotate.xbm"
4200const
4201#include "rotatemask.xbm"
4202
4203const
4204#include "rotatezoom.xbm"
4205const
4206#include "rotatezoommask.xbm"
4207
4208void
4209GfxCore::UpdateCursor(GfxCore::cursor new_cursor)
4210{
4211    // Check if we're already showing that cursor.
4212    if (current_cursor == new_cursor) return;
4213
4214    current_cursor = new_cursor;
4215    switch (current_cursor) {
4216        case GfxCore::CURSOR_DEFAULT:
4217            GLACanvas::SetCursor(wxNullCursor);
4218            break;
4219        case GfxCore::CURSOR_POINTING_HAND:
4220            GLACanvas::SetCursor(wxCursor(wxCURSOR_HAND));
4221            break;
4222        case GfxCore::CURSOR_DRAGGING_HAND:
4223            GLACanvas::SetCursor(make_cursor(hand_bits, handmask_bits, 12, 18));
4224            break;
4225        case GfxCore::CURSOR_HORIZONTAL_RESIZE:
4226            GLACanvas::SetCursor(wxCursor(wxCURSOR_SIZEWE));
4227            break;
4228        case GfxCore::CURSOR_ROTATE_HORIZONTALLY:
4229            GLACanvas::SetCursor(make_cursor(rotate_bits, rotatemask_bits, 15, 15));
4230            break;
4231        case GfxCore::CURSOR_ROTATE_VERTICALLY:
4232            GLACanvas::SetCursor(make_cursor(vrotate_bits, vrotatemask_bits, 15, 15));
4233            break;
4234        case GfxCore::CURSOR_ROTATE_EITHER_WAY:
4235            GLACanvas::SetCursor(make_cursor(brotate_bits, brotatemask_bits, 15, 15));
4236            break;
4237        case GfxCore::CURSOR_ZOOM:
4238            GLACanvas::SetCursor(wxCursor(wxCURSOR_MAGNIFIER));
4239            break;
4240        case GfxCore::CURSOR_ZOOM_ROTATE:
4241            GLACanvas::SetCursor(make_cursor(rotatezoom_bits, rotatezoommask_bits, 15, 15));
4242            break;
4243    }
4244}
4245
4246bool GfxCore::MeasuringLineActive() const
4247{
4248    if (Animating()) return false;
4249    return HereIsReal() || m_there;
4250}
4251
4252bool GfxCore::HandleRClick(wxPoint point)
4253{
4254    if (PointWithinCompass(point)) {
4255        // Pop up menu.
4256        wxMenu menu;
4257        /* TRANSLATORS: View *looking* North */
4258        menu.Append(menu_ORIENT_MOVE_NORTH, wmsg(/*View &North*/240));
4259        /* TRANSLATORS: View *looking* East */
4260        menu.Append(menu_ORIENT_MOVE_EAST, wmsg(/*View &East*/241));
4261        /* TRANSLATORS: View *looking* South */
4262        menu.Append(menu_ORIENT_MOVE_SOUTH, wmsg(/*View &South*/242));
4263        /* TRANSLATORS: View *looking* West */
4264        menu.Append(menu_ORIENT_MOVE_WEST, wmsg(/*View &West*/243));
4265        menu.AppendSeparator();
4266        /* TRANSLATORS: Menu item which turns off the "north arrow" in aven. */
4267        menu.AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Hide Compass*/387));
4268        /* TRANSLATORS: tickable menu item in View menu.
4269         *
4270         * Degrees are the angular measurement where there are 360 in a full
4271         * circle. */
4272        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4273        menu.Bind(wxEVT_COMMAND_MENU_SELECTED,
4274                  [&](wxEvent& e) {
4275                      m_Parent->GetEventHandler()->ProcessEvent(e);
4276                  });
4277        PopupMenu(&menu);
4278        return true;
4279    }
4280
4281    if (PointWithinClino(point)) {
4282        // Pop up menu.
4283        wxMenu menu;
4284        menu.Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
4285        menu.Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
4286        menu.AppendSeparator();
4287        /* TRANSLATORS: Menu item which turns off the tilt indicator in aven. */
4288        menu.AppendCheckItem(menu_IND_CLINO, wmsg(/*&Hide Clino*/384));
4289        /* TRANSLATORS: tickable menu item in View menu.
4290         *
4291         * Degrees are the angular measurement where there are 360 in a full
4292         * circle. */
4293        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4294        /* TRANSLATORS: tickable menu item in View menu.
4295         *
4296         * Show the tilt of the survey as a percentage gradient (100% = 45
4297         * degrees = 50 grad). */
4298        menu.AppendCheckItem(menu_CTL_PERCENT, wmsg(/*&Percent*/430));
4299        menu.Bind(wxEVT_COMMAND_MENU_SELECTED,
4300                  [&](wxEvent& e) {
4301                      m_Parent->GetEventHandler()->ProcessEvent(e);
4302                  });
4303        PopupMenu(&menu);
4304        return true;
4305    }
4306
4307    if (PointWithinScaleBar(point)) {
4308        // Pop up menu.
4309        wxMenu menu;
4310        /* TRANSLATORS: Menu item which turns off the scale bar in aven. */
4311        menu.AppendCheckItem(menu_IND_SCALE_BAR, wmsg(/*&Hide scale bar*/385));
4312        /* TRANSLATORS: tickable menu item in View menu.
4313         *
4314         * "Metric" here means metres, km, etc (rather than feet, miles, etc)
4315         */
4316        menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
4317        menu.Bind(wxEVT_COMMAND_MENU_SELECTED,
4318                  [&](wxEvent& e) {
4319                      m_Parent->GetEventHandler()->ProcessEvent(e);
4320                  });
4321        PopupMenu(&menu);
4322        return true;
4323    }
4324
4325    if (PointWithinColourKey(point)) {
4326        // Pop up menu.
4327        wxMenu menu;
4328        menu.AppendCheckItem(menu_COLOUR_BY_DEPTH, wmsg(/*Colour by &Depth*/292));
4329        menu.AppendCheckItem(menu_COLOUR_BY_DATE, wmsg(/*Colour by D&ate*/293));
4330        menu.AppendCheckItem(menu_COLOUR_BY_ERROR, wmsg(/*Colour by &Error*/289));
4331        menu.AppendCheckItem(menu_COLOUR_BY_GRADIENT, wmsg(/*Colour by &Gradient*/85));
4332        menu.AppendCheckItem(menu_COLOUR_BY_LENGTH, wmsg(/*Colour by &Length*/82));
4333        menu.AppendCheckItem(menu_COLOUR_BY_SURVEY, wmsg(/*Colour by &Survey*/448));
4334        menu.AppendSeparator();
4335        /* TRANSLATORS: Menu item which turns off the colour key.
4336         * The "Colour Key" is the thing in aven showing which colour
4337         * corresponds to which depth, date, survey closure error, etc. */
4338        menu.AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*&Hide colour key*/386));
4339        if (m_ColourBy == COLOUR_BY_DEPTH || m_ColourBy == COLOUR_BY_LENGTH)
4340            menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
4341        else if (m_ColourBy == COLOUR_BY_GRADIENT)
4342            menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4343        menu.Bind(wxEVT_COMMAND_MENU_SELECTED,
4344                  [&](wxEvent& e) {
4345                      m_Parent->GetEventHandler()->ProcessEvent(e);
4346                  });
4347        PopupMenu(&menu);
4348        return true;
4349    }
4350
4351    return false;
4352}
4353
4354void GfxCore::SetZoomBox(wxPoint p1, wxPoint p2, bool centred, bool aspect)
4355{
4356    if (centred) {
4357        p1.x = p2.x + (p1.x - p2.x) * 2;
4358        p1.y = p2.y + (p1.y - p2.y) * 2;
4359    }
4360    if (aspect) {
4361#if 0 // FIXME: This needs more work.
4362        int sx = GetXSize();
4363        int sy = GetYSize();
4364        int dx = p1.x - p2.x;
4365        int dy = p1.y - p2.y;
4366        int dy_new = dx * sy / sx;
4367        if (abs(dy_new) >= abs(dy)) {
4368            p1.y += (dy_new - dy) / 2;
4369            p2.y -= (dy_new - dy) / 2;
4370        } else {
4371            int dx_new = dy * sx / sy;
4372            p1.x += (dx_new - dx) / 2;
4373            p2.x -= (dx_new - dx) / 2;
4374        }
4375#endif
4376    }
4377    zoombox.set(p1, p2);
4378    ForceRefresh();
4379}
4380
4381void GfxCore::ZoomBoxGo()
4382{
4383    if (!zoombox.active()) return;
4384
4385    int width = GetXSize();
4386    int height = GetYSize();
4387
4388    TranslateCave(-0.5 * (zoombox.x1 + zoombox.x2 - width),
4389                  -0.5 * (zoombox.y1 + zoombox.y2 - height));
4390    int box_w = abs(zoombox.x1 - zoombox.x2);
4391    int box_h = abs(zoombox.y1 - zoombox.y2);
4392
4393    double factor = min(double(width) / box_w, double(height) / box_h);
4394
4395    zoombox.unset();
4396
4397    SetScale(GetScale() * factor);
4398}
Note: See TracBrowser for help on using the repository browser.