source: git/src/gfxcore.cc @ b49ac56

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

src/: Whitespace cleanup.

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