source: git/src/gfxcore.cc @ 85dcdcd

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since 85dcdcd was 85dcdcd, checked in by Olly Betts <olly@…>, 7 years ago

Use symbol instead of word for status var rad units

We use the superscript 'g' in the compass and clino indicator, so
it's more consistent to use it here too. It's shorter than the word,
and the status bar is of limited length.

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