source: git/src/gfxcore.cc @ 112f80c

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

src/gfxcore.cc,src/gfxcore.h,src/mainfrm.cc: Add support for reading
DEM data which isn't in a .zip file.

  • 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    int fd = open(file.mb_str(), O_RDONLY);
2630    if (fd < 0) {
2631        wxMessageBox(wxT("Failed to open DEM file"));
2632        return false;
2633    }
2634
2635    wxFileInputStream fs(fd);
2636    const wxString & lc_file = file.Lower();
2637    if (lc_file.EndsWith(wxT(".hgt"))) {
2638        parse_hgt_filename(lc_file);
2639        read_bil(fs, size, skipbytes);
2640    } else if (lc_file.EndsWith(wxT(".bil"))) {
2641        wxString hdr_file = file;
2642        hdr_file.replace(file.size() - 4, 4, wxT(".hdr"));
2643        int hdr_fd = open(hdr_file.mb_str(), O_RDONLY);
2644        if (hdr_fd < 0) {
2645            wxMessageBox(wxT("Failed to open HDR file '") + hdr_file + wxT("'"));
2646            return false;
2647        }
2648        wxFileInputStream hdr_is(hdr_fd);
2649        size = parse_hdr(hdr_is, skipbytes);
2650        read_bil(fs, size, skipbytes);
2651    } else if (lc_file.EndsWith(wxT(".zip"))) {
2652        wxZipEntry * ze_data = NULL;
2653        wxZipInputStream zs(fs);
2654        wxZipEntry * ze;
2655        while ((ze = zs.GetNextEntry()) != NULL) {
2656            if (!ze->IsDir()) {
2657                const wxString & lc_name = ze->GetName().Lower();
2658                if (!ze_data && lc_name.EndsWith(wxT(".hgt"))) {
2659                    // SRTM .hgt files are raw binary data, with the filename
2660                    // encoding the coordinates.
2661                    parse_hgt_filename(lc_name);
2662                    read_bil(zs, size, skipbytes);
2663                    delete ze;
2664                    break;
2665                }
2666
2667                if (!ze_data && lc_name.EndsWith(wxT(".bil"))) {
2668                    if (size) {
2669                        read_bil(zs, size, skipbytes);
2670                        break;
2671                    }
2672                    ze_data = ze;
2673                    continue;
2674                }
2675
2676                if (lc_name.EndsWith(wxT(".hdr"))) {
2677                    size = parse_hdr(zs, skipbytes);
2678                    if (ze_data) {
2679                        if (!zs.OpenEntry(*ze_data)) {
2680                            wxMessageBox(wxT("Couldn't read DEM data from .zip file"));
2681                            break;
2682                        }
2683                        read_bil(zs, size, skipbytes);
2684                    }
2685                } else if (lc_name.EndsWith(wxT(".prj"))) {
2686                    //FIXME: check this matches the datum string we use
2687                    //Projection    GEOGRAPHIC
2688                    //Datum         WGS84
2689                    //Zunits        METERS
2690                    //Units         DD
2691                    //Spheroid      WGS84
2692                    //Xshift        0.0000000000
2693                    //Yshift        0.0000000000
2694                    //Parameters
2695                }
2696            }
2697            delete ze;
2698        }
2699        delete ze_data;
2700    }
2701
2702    if (!dem) {
2703        return false;
2704    }
2705
2706    InvalidateList(LIST_TERRAIN);
2707    ForceRefresh();
2708    return true;
2709}
2710
2711void GfxCore::DrawTerrainTriangle(const Vector3 & a, const Vector3 & b, const Vector3 & c)
2712{
2713    Vector3 n = (b - a) * (c - a);
2714    n.normalise();
2715    Double factor = dot(n, light) * .95 + .05;
2716    SetColour(col_WHITE, factor);
2717    PlaceVertex(a);
2718    PlaceVertex(b);
2719    PlaceVertex(c);
2720    ++n_tris;
2721}
2722
2723void GfxCore::DrawTerrain()
2724{
2725    if (!dem) return;
2726
2727    wxBusyCursor hourglass;
2728
2729    // Draw terrain to twice the extent, or at least 1km.
2730    double r_sqrd = sqrd(max(m_Parent->GetExtent().magnitude(), 1000.0));
2731#define WGS84_DATUM_STRING "+proj=longlat +ellps=WGS84 +datum=WGS84"
2732    static projPJ pj_in = pj_init_plus(WGS84_DATUM_STRING);
2733    if (!pj_in) {
2734        ToggleTerrain();
2735        error(/*Failed to initialise input coordinate system “%s”*/287, WGS84_DATUM_STRING);
2736        return;
2737    }
2738    static projPJ pj_out = pj_init_plus(m_Parent->m_cs_proj.c_str());
2739    if (!pj_out) {
2740        ToggleTerrain();
2741        error(/*Failed to initialise output coordinate system “%s”*/288, (const char *)m_Parent->m_cs_proj.c_str());
2742        return;
2743    }
2744    n_tris = 0;
2745    SetAlpha(0.3);
2746    BeginTriangles();
2747    const Vector3 & off = m_Parent->GetOffset();
2748    vector<Vector3> prevcol(dem_height + 1);
2749    for (size_t x = 0; x < dem_width; ++x) {
2750        double X_ = (o_x + x * step_x) * DEG_TO_RAD;
2751        Vector3 prev;
2752        for (size_t y = 0; y < dem_height; ++y) {
2753            unsigned short elev = dem[x + y * dem_width];
2754#ifdef WORDS_BIGENDIAN
2755            const bool MACHINE_BIGENDIAN = true;
2756#else
2757            const bool MACHINE_BIGENDIAN = false;
2758#endif
2759            if (bigendian != MACHINE_BIGENDIAN) {
2760#if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408)
2761                elev = __builtin_bswap16(elev);
2762#else
2763                elev = (elev >> 8) | (elev << 8);
2764#endif
2765            }
2766            double Z = (short)elev;
2767            Vector3 pt;
2768            if (Z == nodata_value) {
2769                pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
2770            } else {
2771                double X = X_;
2772                double Y = (o_y - y * step_y) * DEG_TO_RAD;
2773                pj_transform(pj_in, pj_out, 1, 1, &X, &Y, &Z);
2774                pt = Vector3(X, Y, Z) - off;
2775                double dist_2 = sqrd(pt.GetX()) + sqrd(pt.GetY());
2776                if (dist_2 > r_sqrd) {
2777                    pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
2778                }
2779            }
2780            if (x > 0 && y > 0) {
2781                const Vector3 & a = prevcol[y - 1];
2782                const Vector3 & b = prevcol[y];
2783                // If all points are valid, split the quadrilateral into
2784                // triangles along the shorter 3D diagonal, which typically
2785                // looks better:
2786                //
2787                //               ----->
2788                //     prev---a    x     prev---a
2789                //   |   |P  /|            |\  S|
2790                // y |   |  / |    or      | \  |
2791                //   V   | /  |            |  \ |
2792                //       |/  Q|            |R  \|
2793                //       b----pt           b----pt
2794                //
2795                //       FORWARD           BACKWARD
2796                enum { NONE = 0, P = 1, Q = 2, R = 4, S = 8, ALL = P|Q|R|S };
2797                int valid =
2798                    ((prev.GetZ() != DBL_MAX)) |
2799                    ((a.GetZ() != DBL_MAX) << 1) |
2800                    ((b.GetZ() != DBL_MAX) << 2) |
2801                    ((pt.GetZ() != DBL_MAX) << 3);
2802                static const int tris_map[16] = {
2803                    NONE, // nothing valid
2804                    NONE, // prev
2805                    NONE, // a
2806                    NONE, // a, prev
2807                    NONE, // b
2808                    NONE, // b, prev
2809                    NONE, // b, a
2810                    P, // b, a, prev
2811                    NONE, // pt
2812                    NONE, // pt, prev
2813                    NONE, // pt, a
2814                    S, // pt, a, prev
2815                    NONE, // pt, b
2816                    R, // pt, b, prev
2817                    Q, // pt, b, a
2818                    ALL, // pt, b, a, prev
2819                };
2820                int tris = tris_map[valid];
2821                if (tris == ALL) {
2822                    // All points valid.
2823                    if ((a - b).magnitude() < (prev - pt).magnitude()) {
2824                        tris = P | Q;
2825                    } else {
2826                        tris = R | S;
2827                    }
2828                }
2829                if (tris & P)
2830                    DrawTerrainTriangle(a, prev, b);
2831                if (tris & Q)
2832                    DrawTerrainTriangle(a, b, pt);
2833                if (tris & R)
2834                    DrawTerrainTriangle(pt, prev, b);
2835                if (tris & S)
2836                    DrawTerrainTriangle(a, prev, pt);
2837            }
2838            prev = prevcol[y];
2839            prevcol[y].assign(pt);
2840        }
2841    }
2842    EndTriangles();
2843    SetAlpha(1.0);
2844}
2845
2846// Plot blobs.
2847void GfxCore::GenerateBlobsDisplayList()
2848{
2849    if (!(m_Entrances || m_FixedPts || m_ExportedPts ||
2850          m_Parent->GetNumHighlightedPts()))
2851        return;
2852
2853    // Plot blobs.
2854    gla_colour prev_col = col_BLACK; // not a colour used for blobs
2855    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2856    BeginBlobs();
2857    while (pos != m_Parent->GetLabelsEnd()) {
2858        const LabelInfo* label = *pos++;
2859
2860        // When more than one flag is set on a point:
2861        // search results take priority over entrance highlighting
2862        // which takes priority over fixed point
2863        // highlighting, which in turn takes priority over exported
2864        // point highlighting.
2865
2866        if (!((m_Surface && label->IsSurface()) ||
2867              (m_Legs && label->IsUnderground()) ||
2868              (!label->IsSurface() && !label->IsUnderground()))) {
2869            // if this station isn't to be displayed, skip to the next
2870            // (last case is for stns with no legs attached)
2871            continue;
2872        }
2873
2874        gla_colour col;
2875
2876        if (label->IsHighLighted()) {
2877            col = col_YELLOW;
2878        } else if (m_Entrances && label->IsEntrance()) {
2879            col = col_GREEN;
2880        } else if (m_FixedPts && label->IsFixedPt()) {
2881            col = col_RED;
2882        } else if (m_ExportedPts && label->IsExportedPt()) {
2883            col = col_TURQUOISE;
2884        } else {
2885            continue;
2886        }
2887
2888        // Stations are sorted by blob type, so colour changes are infrequent.
2889        if (col != prev_col) {
2890            SetColour(col);
2891            prev_col = col;
2892        }
2893        DrawBlob(label->GetX(), label->GetY(), label->GetZ());
2894    }
2895    EndBlobs();
2896}
2897
2898void GfxCore::DrawIndicators()
2899{
2900    // Draw colour key.
2901    if (m_ColourKey) {
2902        drawing_list key_list = LIST_LIMIT_;
2903        switch (m_ColourBy) {
2904            case COLOUR_BY_DEPTH:
2905                key_list = LIST_DEPTH_KEY; break;
2906            case COLOUR_BY_DATE:
2907                key_list = LIST_DATE_KEY; break;
2908            case COLOUR_BY_ERROR:
2909                key_list = LIST_ERROR_KEY; break;
2910            case COLOUR_BY_GRADIENT:
2911                key_list = LIST_GRADIENT_KEY; break;
2912            case COLOUR_BY_LENGTH:
2913                key_list = LIST_LENGTH_KEY; break;
2914        }
2915        if (key_list != LIST_LIMIT_) {
2916            DrawList2D(key_list, GetXSize() - KEY_OFFSET_X,
2917                       GetYSize() - KEY_OFFSET_Y, 0);
2918        }
2919    }
2920
2921    // Draw compass or elevation/heading indicators.
2922    if (m_Compass || m_Clino) {
2923        if (!m_Parent->IsExtendedElevation()) Draw2dIndicators();
2924    }
2925
2926    // Draw scalebar.
2927    if (m_Scalebar) {
2928        DrawList2D(LIST_SCALE_BAR, 0, 0, 0);
2929    }
2930}
2931
2932void GfxCore::PlaceVertexWithColour(const Vector3 & v,
2933                                    glaTexCoord tex_x, glaTexCoord tex_y,
2934                                    Double factor)
2935{
2936    SetColour(col_WHITE, factor);
2937    PlaceVertex(v, tex_x, tex_y);
2938}
2939
2940void GfxCore::SetDepthColour(Double z, Double factor) {
2941    // Set the drawing colour based on the altitude.
2942    Double z_ext = m_Parent->GetDepthExtent();
2943
2944    z -= m_Parent->GetDepthMin();
2945    // points arising from tubes may be slightly outside the limits...
2946    if (z < 0) z = 0;
2947    if (z > z_ext) z = z_ext;
2948
2949    if (z == 0) {
2950        SetColour(GetPen(0), factor);
2951        return;
2952    }
2953
2954    assert(z_ext > 0.0);
2955    Double how_far = z / z_ext;
2956    assert(how_far >= 0.0);
2957    assert(how_far <= 1.0);
2958
2959    int band = int(floor(how_far * (GetNumColourBands() - 1)));
2960    GLAPen pen1 = GetPen(band);
2961    if (band < GetNumColourBands() - 1) {
2962        const GLAPen& pen2 = GetPen(band + 1);
2963
2964        Double interval = z_ext / (GetNumColourBands() - 1);
2965        Double into_band = z / interval - band;
2966
2967//      printf("%g z_offset=%g interval=%g band=%d\n", into_band,
2968//             z_offset, interval, band);
2969        // FIXME: why do we need to clamp here?  Is it because the walls can
2970        // extend further up/down than the centre-line?
2971        if (into_band < 0.0) into_band = 0.0;
2972        if (into_band > 1.0) into_band = 1.0;
2973        assert(into_band >= 0.0);
2974        assert(into_band <= 1.0);
2975
2976        pen1.Interpolate(pen2, into_band);
2977    }
2978    SetColour(pen1, factor);
2979}
2980
2981void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v, Double factor)
2982{
2983    SetDepthColour(v.GetZ(), factor);
2984    PlaceVertex(v);
2985}
2986
2987void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v,
2988                                         glaTexCoord tex_x, glaTexCoord tex_y,
2989                                         Double factor)
2990{
2991    SetDepthColour(v.GetZ(), factor);
2992    PlaceVertex(v, tex_x, tex_y);
2993}
2994
2995void GfxCore::SplitLineAcrossBands(int band, int band2,
2996                                   const Vector3 &p, const Vector3 &q,
2997                                   Double factor)
2998{
2999    const int step = (band < band2) ? 1 : -1;
3000    for (int i = band; i != band2; i += step) {
3001        const Double z = GetDepthBoundaryBetweenBands(i, i + step);
3002
3003        // Find the intersection point of the line p -> q
3004        // with the plane parallel to the xy-plane with z-axis intersection z.
3005        assert(q.GetZ() - p.GetZ() != 0.0);
3006
3007        const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
3008//      assert(0.0 <= t && t <= 1.0);           FIXME: rounding problems!
3009
3010        const Double x = p.GetX() + t * (q.GetX() - p.GetX());
3011        const Double y = p.GetY() + t * (q.GetY() - p.GetY());
3012
3013        PlaceVertexWithDepthColour(Vector3(x, y, z), factor);
3014    }
3015}
3016
3017int GfxCore::GetDepthColour(Double z) const
3018{
3019    // Return the (0-based) depth colour band index for a z-coordinate.
3020    Double z_ext = m_Parent->GetDepthExtent();
3021    z -= m_Parent->GetDepthMin();
3022    // We seem to get rounding differences causing z to sometimes be slightly
3023    // less than GetDepthMin() here, and it can certainly be true for passage
3024    // tubes, so just clamp the value to 0.
3025    if (z <= 0) return 0;
3026    // We seem to get rounding differences causing z to sometimes exceed z_ext
3027    // by a small amount here (see: http://trac.survex.com/ticket/26) and it
3028    // can certainly be true for passage tubes, so just clamp the value.
3029    if (z >= z_ext) return GetNumColourBands() - 1;
3030    return int(z / z_ext * (GetNumColourBands() - 1));
3031}
3032
3033Double GfxCore::GetDepthBoundaryBetweenBands(int a, int b) const
3034{
3035    // Return the z-coordinate of the depth colour boundary between
3036    // two adjacent depth colour bands (specified by 0-based indices).
3037
3038    assert((a == b - 1) || (a == b + 1));
3039    if (GetNumColourBands() == 1) return 0;
3040
3041    int band = (a > b) ? a : b; // boundary N lies on the bottom of band N.
3042    Double z_ext = m_Parent->GetDepthExtent();
3043    return (z_ext * band / (GetNumColourBands() - 1)) + m_Parent->GetDepthMin();
3044}
3045
3046void GfxCore::AddPolyline(const traverse & centreline)
3047{
3048    BeginPolyline();
3049    SetColour(col_WHITE);
3050    vector<PointInfo>::const_iterator i = centreline.begin();
3051    PlaceVertex(*i);
3052    ++i;
3053    while (i != centreline.end()) {
3054        PlaceVertex(*i);
3055        ++i;
3056    }
3057    EndPolyline();
3058}
3059
3060void GfxCore::AddPolylineShadow(const traverse & centreline)
3061{
3062    BeginPolyline();
3063    const double z = -0.5 * m_Parent->GetZExtent();
3064    vector<PointInfo>::const_iterator i = centreline.begin();
3065    PlaceVertex(i->GetX(), i->GetY(), z);
3066    ++i;
3067    while (i != centreline.end()) {
3068        PlaceVertex(i->GetX(), i->GetY(), z);
3069        ++i;
3070    }
3071    EndPolyline();
3072}
3073
3074void GfxCore::AddPolylineDepth(const traverse & centreline)
3075{
3076    BeginPolyline();
3077    vector<PointInfo>::const_iterator i, prev_i;
3078    i = centreline.begin();
3079    int band0 = GetDepthColour(i->GetZ());
3080    PlaceVertexWithDepthColour(*i);
3081    prev_i = i;
3082    ++i;
3083    while (i != centreline.end()) {
3084        int band = GetDepthColour(i->GetZ());
3085        if (band != band0) {
3086            SplitLineAcrossBands(band0, band, *prev_i, *i);
3087            band0 = band;
3088        }
3089        PlaceVertexWithDepthColour(*i);
3090        prev_i = i;
3091        ++i;
3092    }
3093    EndPolyline();
3094}
3095
3096void GfxCore::AddQuadrilateral(const Vector3 &a, const Vector3 &b,
3097                               const Vector3 &c, const Vector3 &d)
3098{
3099    Vector3 normal = (a - c) * (d - b);
3100    normal.normalise();
3101    Double factor = dot(normal, light) * .3 + .7;
3102    glaTexCoord w(ceil(((b - a).magnitude() + (d - c).magnitude()) * .5));
3103    glaTexCoord h(ceil(((b - c).magnitude() + (d - a).magnitude()) * .5));
3104    // FIXME: should plot triangles instead to avoid rendering glitches.
3105    BeginQuadrilaterals();
3106    PlaceVertexWithColour(a, 0, 0, factor);
3107    PlaceVertexWithColour(b, w, 0, factor);
3108    PlaceVertexWithColour(c, w, h, factor);
3109    PlaceVertexWithColour(d, 0, h, factor);
3110    EndQuadrilaterals();
3111}
3112
3113void GfxCore::AddQuadrilateralDepth(const Vector3 &a, const Vector3 &b,
3114                                    const Vector3 &c, const Vector3 &d)
3115{
3116    Vector3 normal = (a - c) * (d - b);
3117    normal.normalise();
3118    Double factor = dot(normal, light) * .3 + .7;
3119    int a_band, b_band, c_band, d_band;
3120    a_band = GetDepthColour(a.GetZ());
3121    a_band = min(max(a_band, 0), GetNumColourBands());
3122    b_band = GetDepthColour(b.GetZ());
3123    b_band = min(max(b_band, 0), GetNumColourBands());
3124    c_band = GetDepthColour(c.GetZ());
3125    c_band = min(max(c_band, 0), GetNumColourBands());
3126    d_band = GetDepthColour(d.GetZ());
3127    d_band = min(max(d_band, 0), GetNumColourBands());
3128    // All this splitting is incorrect - we need to make a separate polygon
3129    // for each depth band...
3130    glaTexCoord w(ceil(((b - a).magnitude() + (d - c).magnitude()) * .5));
3131    glaTexCoord h(ceil(((b - c).magnitude() + (d - a).magnitude()) * .5));
3132    BeginPolygon();
3133////    PlaceNormal(normal);
3134    PlaceVertexWithDepthColour(a, 0, 0, factor);
3135    if (a_band != b_band) {
3136        SplitLineAcrossBands(a_band, b_band, a, b, factor);
3137    }
3138    PlaceVertexWithDepthColour(b, w, 0, factor);
3139    if (b_band != c_band) {
3140        SplitLineAcrossBands(b_band, c_band, b, c, factor);
3141    }
3142    PlaceVertexWithDepthColour(c, w, h, factor);
3143    if (c_band != d_band) {
3144        SplitLineAcrossBands(c_band, d_band, c, d, factor);
3145    }
3146    PlaceVertexWithDepthColour(d, 0, h, factor);
3147    if (d_band != a_band) {
3148        SplitLineAcrossBands(d_band, a_band, d, a, factor);
3149    }
3150    EndPolygon();
3151}
3152
3153void GfxCore::SetColourFromDate(int date, Double factor)
3154{
3155    // Set the drawing colour based on a date.
3156
3157    if (date == -1) {
3158        // Undated.
3159        SetColour(col_WHITE, factor);
3160        return;
3161    }
3162
3163    int date_offset = date - m_Parent->GetDateMin();
3164    if (date_offset == 0) {
3165        // Earliest date - handle as a special case for the single date case.
3166        SetColour(GetPen(0), factor);
3167        return;
3168    }
3169
3170    int date_ext = m_Parent->GetDateExtent();
3171    Double how_far = (Double)date_offset / date_ext;
3172    assert(how_far >= 0.0);
3173    assert(how_far <= 1.0);
3174    SetColourFrom01(how_far, factor);
3175}
3176
3177void GfxCore::AddPolylineDate(const traverse & centreline)
3178{
3179    BeginPolyline();
3180    vector<PointInfo>::const_iterator i, prev_i;
3181    i = centreline.begin();
3182    int date = i->GetDate();
3183    SetColourFromDate(date, 1.0);
3184    PlaceVertex(*i);
3185    prev_i = i;
3186    while (++i != centreline.end()) {
3187        int newdate = i->GetDate();
3188        if (newdate != date) {
3189            EndPolyline();
3190            BeginPolyline();
3191            date = newdate;
3192            SetColourFromDate(date, 1.0);
3193            PlaceVertex(*prev_i);
3194        }
3195        PlaceVertex(*i);
3196        prev_i = i;
3197    }
3198    EndPolyline();
3199}
3200
3201static int static_date_hack; // FIXME
3202
3203void GfxCore::AddQuadrilateralDate(const Vector3 &a, const Vector3 &b,
3204                                   const Vector3 &c, const Vector3 &d)
3205{
3206    Vector3 normal = (a - c) * (d - b);
3207    normal.normalise();
3208    Double factor = dot(normal, light) * .3 + .7;
3209    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
3210    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
3211    // FIXME: should plot triangles instead to avoid rendering glitches.
3212    BeginQuadrilaterals();
3213////    PlaceNormal(normal);
3214    SetColourFromDate(static_date_hack, factor);
3215    PlaceVertex(a, 0, 0);
3216    PlaceVertex(b, w, 0);
3217    PlaceVertex(c, w, h);
3218    PlaceVertex(d, 0, h);
3219    EndQuadrilaterals();
3220}
3221
3222static double static_E_hack; // FIXME
3223
3224void GfxCore::SetColourFromError(double E, Double factor)
3225{
3226    // Set the drawing colour based on an error value.
3227
3228    if (E < 0) {
3229        SetColour(col_WHITE, factor);
3230        return;
3231    }
3232
3233    Double how_far = E / MAX_ERROR;
3234    assert(how_far >= 0.0);
3235    if (how_far > 1.0) how_far = 1.0;
3236    SetColourFrom01(how_far, factor);
3237}
3238
3239void GfxCore::AddQuadrilateralError(const Vector3 &a, const Vector3 &b,
3240                                    const Vector3 &c, const Vector3 &d)
3241{
3242    Vector3 normal = (a - c) * (d - b);
3243    normal.normalise();
3244    Double factor = dot(normal, light) * .3 + .7;
3245    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
3246    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
3247    // FIXME: should plot triangles instead to avoid rendering glitches.
3248    BeginQuadrilaterals();
3249////    PlaceNormal(normal);
3250    SetColourFromError(static_E_hack, factor);
3251    PlaceVertex(a, 0, 0);
3252    PlaceVertex(b, w, 0);
3253    PlaceVertex(c, w, h);
3254    PlaceVertex(d, 0, h);
3255    EndQuadrilaterals();
3256}
3257
3258void GfxCore::AddPolylineError(const traverse & centreline)
3259{
3260    BeginPolyline();
3261    SetColourFromError(centreline.E, 1.0);
3262    vector<PointInfo>::const_iterator i;
3263    for(i = centreline.begin(); i != centreline.end(); ++i) {
3264        PlaceVertex(*i);
3265    }
3266    EndPolyline();
3267}
3268
3269// gradient is in *radians*.
3270void GfxCore::SetColourFromGradient(double gradient, Double factor)
3271{
3272    // Set the drawing colour based on the gradient of the leg.
3273
3274    const Double GRADIENT_MAX = M_PI_2;
3275    gradient = fabs(gradient);
3276    Double how_far = gradient / GRADIENT_MAX;
3277    SetColourFrom01(how_far, factor);
3278}
3279
3280void GfxCore::AddPolylineGradient(const traverse & centreline)
3281{
3282    vector<PointInfo>::const_iterator i, prev_i;
3283    i = centreline.begin();
3284    prev_i = i;
3285    while (++i != centreline.end()) {
3286        BeginPolyline();
3287        SetColourFromGradient((*i - *prev_i).gradient(), 1.0);
3288        PlaceVertex(*prev_i);
3289        PlaceVertex(*i);
3290        prev_i = i;
3291        EndPolyline();
3292    }
3293}
3294
3295static double static_gradient_hack; // FIXME
3296
3297void GfxCore::AddQuadrilateralGradient(const Vector3 &a, const Vector3 &b,
3298                                       const Vector3 &c, const Vector3 &d)
3299{
3300    Vector3 normal = (a - c) * (d - b);
3301    normal.normalise();
3302    Double factor = dot(normal, light) * .3 + .7;
3303    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
3304    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
3305    // FIXME: should plot triangles instead to avoid rendering glitches.
3306    BeginQuadrilaterals();
3307////    PlaceNormal(normal);
3308    SetColourFromGradient(static_gradient_hack, factor);
3309    PlaceVertex(a, 0, 0);
3310    PlaceVertex(b, w, 0);
3311    PlaceVertex(c, w, h);
3312    PlaceVertex(d, 0, h);
3313    EndQuadrilaterals();
3314}
3315
3316void GfxCore::SetColourFromLength(double length, Double factor)
3317{
3318    // Set the drawing colour based on log(length_of_leg).
3319
3320    Double log_len = log10(length);
3321    Double how_far = log_len / LOG_LEN_MAX;
3322    how_far = max(how_far, 0.0);
3323    how_far = min(how_far, 1.0);
3324    SetColourFrom01(how_far, factor);
3325}
3326
3327void GfxCore::SetColourFrom01(double how_far, Double factor)
3328{
3329    double b;
3330    double into_band = modf(how_far * (GetNumColourBands() - 1), &b);
3331    int band(b);
3332    GLAPen pen1 = GetPen(band);
3333    // With 24bit colour, interpolating by less than this can have no effect.
3334    if (into_band >= 1.0 / 512.0) {
3335        const GLAPen& pen2 = GetPen(band + 1);
3336        pen1.Interpolate(pen2, into_band);
3337    }
3338    SetColour(pen1, factor);
3339}
3340
3341void GfxCore::AddPolylineLength(const traverse & centreline)
3342{
3343    vector<PointInfo>::const_iterator i, prev_i;
3344    i = centreline.begin();
3345    prev_i = i;
3346    while (++i != centreline.end()) {
3347        BeginPolyline();
3348        SetColourFromLength((*i - *prev_i).magnitude(), 1.0);
3349        PlaceVertex(*prev_i);
3350        PlaceVertex(*i);
3351        prev_i = i;
3352        EndPolyline();
3353    }
3354}
3355
3356static double static_length_hack; // FIXME
3357
3358void GfxCore::AddQuadrilateralLength(const Vector3 &a, const Vector3 &b,
3359                                     const Vector3 &c, const Vector3 &d)
3360{
3361    Vector3 normal = (a - c) * (d - b);
3362    normal.normalise();
3363    Double factor = dot(normal, light) * .3 + .7;
3364    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
3365    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
3366    // FIXME: should plot triangles instead to avoid rendering glitches.
3367    BeginQuadrilaterals();
3368////    PlaceNormal(normal);
3369    SetColourFromLength(static_length_hack, factor);
3370    PlaceVertex(a, 0, 0);
3371    PlaceVertex(b, w, 0);
3372    PlaceVertex(c, w, h);
3373    PlaceVertex(d, 0, h);
3374    EndQuadrilaterals();
3375}
3376
3377void
3378GfxCore::SkinPassage(vector<XSect> & centreline, bool draw)
3379{
3380    assert(centreline.size() > 1);
3381    Vector3 U[4];
3382    XSect prev_pt_v;
3383    Vector3 last_right(1.0, 0.0, 0.0);
3384
3385//  FIXME: it's not simple to set the colour of a tube based on error...
3386//    static_E_hack = something...
3387    vector<XSect>::iterator i = centreline.begin();
3388    vector<XSect>::size_type segment = 0;
3389    while (i != centreline.end()) {
3390        // get the coordinates of this vertex
3391        XSect & pt_v = *i++;
3392
3393        double z_pitch_adjust = 0.0;
3394        bool cover_end = false;
3395
3396        Vector3 right, up;
3397
3398        const Vector3 up_v(0.0, 0.0, 1.0);
3399
3400        if (segment == 0) {
3401            assert(i != centreline.end());
3402            // first segment
3403
3404            // get the coordinates of the next vertex
3405            const XSect & next_pt_v = *i;
3406
3407            // calculate vector from this pt to the next one
3408            Vector3 leg_v = next_pt_v - pt_v;
3409
3410            // obtain a vector in the LRUD plane
3411            right = leg_v * up_v;
3412            if (right.magnitude() == 0) {
3413                right = last_right;
3414                // Obtain a second vector in the LRUD plane,
3415                // perpendicular to the first.
3416                //up = right * leg_v;
3417                up = up_v;
3418            } else {
3419                last_right = right;
3420                up = up_v;
3421            }
3422
3423            cover_end = true;
3424            static_date_hack = next_pt_v.GetDate();
3425        } else if (segment + 1 == centreline.size()) {
3426            // last segment
3427
3428            // Calculate vector from the previous pt to this one.
3429            Vector3 leg_v = pt_v - prev_pt_v;
3430
3431            // Obtain a horizontal vector in the LRUD plane.
3432            right = leg_v * up_v;
3433            if (right.magnitude() == 0) {
3434                right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
3435                // Obtain a second vector in the LRUD plane,
3436                // perpendicular to the first.
3437                //up = right * leg_v;
3438                up = up_v;
3439            } else {
3440                last_right = right;
3441                up = up_v;
3442            }
3443
3444            cover_end = true;
3445            static_date_hack = pt_v.GetDate();
3446        } else {
3447            assert(i != centreline.end());
3448            // Intermediate segment.
3449
3450            // Get the coordinates of the next vertex.
3451            const XSect & next_pt_v = *i;
3452
3453            // Calculate vectors from this vertex to the
3454            // next vertex, and from the previous vertex to
3455            // this one.
3456            Vector3 leg1_v = pt_v - prev_pt_v;
3457            Vector3 leg2_v = next_pt_v - pt_v;
3458
3459            // Obtain horizontal vectors perpendicular to
3460            // both legs, then normalise and average to get
3461            // a horizontal bisector.
3462            Vector3 r1 = leg1_v * up_v;
3463            Vector3 r2 = leg2_v * up_v;
3464            r1.normalise();
3465            r2.normalise();
3466            right = r1 + r2;
3467            if (right.magnitude() == 0) {
3468                // This is the "mid-pitch" case...
3469                right = last_right;
3470            }
3471            if (r1.magnitude() == 0) {
3472                Vector3 n = leg1_v;
3473                n.normalise();
3474                z_pitch_adjust = n.GetZ();
3475                //up = Vector3(0, 0, leg1_v.GetZ());
3476                //up = right * up;
3477                up = up_v;
3478
3479                // Rotate pitch section to minimise the
3480                // "tortional stress" - FIXME: use
3481                // triangles instead of rectangles?
3482                int shift = 0;
3483                Double maxdotp = 0;
3484
3485                // Scale to unit vectors in the LRUD plane.
3486                right.normalise();
3487                up.normalise();
3488                Vector3 vec = up - right;
3489                for (int orient = 0; orient <= 3; ++orient) {
3490                    Vector3 tmp = U[orient] - prev_pt_v;
3491                    tmp.normalise();
3492                    Double dotp = dot(vec, tmp);
3493                    if (dotp > maxdotp) {
3494                        maxdotp = dotp;
3495                        shift = orient;
3496                    }
3497                }
3498                if (shift) {
3499                    if (shift != 2) {
3500                        Vector3 temp(U[0]);
3501                        U[0] = U[shift];
3502                        U[shift] = U[2];
3503                        U[2] = U[shift ^ 2];
3504                        U[shift ^ 2] = temp;
3505                    } else {
3506                        swap(U[0], U[2]);
3507                        swap(U[1], U[3]);
3508                    }
3509                }
3510#if 0
3511                // Check that the above code actually permuted
3512                // the vertices correctly.
3513                shift = 0;
3514                maxdotp = 0;
3515                for (int j = 0; j <= 3; ++j) {
3516                    Vector3 tmp = U[j] - prev_pt_v;
3517                    tmp.normalise();
3518                    Double dotp = dot(vec, tmp);
3519                    if (dotp > maxdotp) {
3520                        maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
3521                        shift = j;
3522                    }
3523                }
3524                if (shift) {
3525                    printf("New shift = %d!\n", shift);
3526                    shift = 0;
3527                    maxdotp = 0;
3528                    for (int j = 0; j <= 3; ++j) {
3529                        Vector3 tmp = U[j] - prev_pt_v;
3530                        tmp.normalise();
3531                        Double dotp = dot(vec, tmp);
3532                        printf("    %d : %.8f\n", j, dotp);
3533                    }
3534                }
3535#endif
3536            } else if (r2.magnitude() == 0) {
3537                Vector3 n = leg2_v;
3538                n.normalise();
3539                z_pitch_adjust = n.GetZ();
3540                //up = Vector3(0, 0, leg2_v.GetZ());
3541                //up = right * up;
3542                up = up_v;
3543            } else {
3544                up = up_v;
3545            }
3546            last_right = right;
3547            static_date_hack = pt_v.GetDate();
3548        }
3549
3550        // Scale to unit vectors in the LRUD plane.
3551        right.normalise();
3552        up.normalise();
3553
3554        if (z_pitch_adjust != 0) up += Vector3(0, 0, fabs(z_pitch_adjust));
3555
3556        Double l = fabs(pt_v.GetL());
3557        Double r = fabs(pt_v.GetR());
3558        Double u = fabs(pt_v.GetU());
3559        Double d = fabs(pt_v.GetD());
3560
3561        // Produce coordinates of the corners of the LRUD "plane".
3562        Vector3 v[4];
3563        v[0] = pt_v - right * l + up * u;
3564        v[1] = pt_v + right * r + up * u;
3565        v[2] = pt_v + right * r - up * d;
3566        v[3] = pt_v - right * l - up * d;
3567
3568        if (draw) {
3569            const Vector3 & delta = pt_v - prev_pt_v;
3570            static_length_hack = delta.magnitude();
3571            static_gradient_hack = delta.gradient();
3572            if (segment > 0) {
3573                (this->*AddQuad)(v[0], v[1], U[1], U[0]);
3574                (this->*AddQuad)(v[2], v[3], U[3], U[2]);
3575                (this->*AddQuad)(v[1], v[2], U[2], U[1]);
3576                (this->*AddQuad)(v[3], v[0], U[0], U[3]);
3577            }
3578
3579            if (cover_end) {
3580                (this->*AddQuad)(v[3], v[2], v[1], v[0]);
3581            }
3582        }
3583
3584        prev_pt_v = pt_v;
3585        U[0] = v[0];
3586        U[1] = v[1];
3587        U[2] = v[2];
3588        U[3] = v[3];
3589
3590        pt_v.set_right_bearing(deg(atan2(right.GetY(), right.GetX())));
3591
3592        ++segment;
3593    }
3594}
3595
3596void GfxCore::FullScreenMode()
3597{
3598    m_Parent->ViewFullScreen();
3599}
3600
3601bool GfxCore::IsFullScreen() const
3602{
3603    return m_Parent->IsFullScreen();
3604}
3605
3606bool GfxCore::FullScreenModeShowingMenus() const
3607{
3608    return m_Parent->FullScreenModeShowingMenus();
3609}
3610
3611void GfxCore::FullScreenModeShowMenus(bool show)
3612{
3613    m_Parent->FullScreenModeShowMenus(show);
3614}
3615
3616void
3617GfxCore::MoveViewer(double forward, double up, double right)
3618{
3619    double cT = cos(rad(m_TiltAngle));
3620    double sT = sin(rad(m_TiltAngle));
3621    double cP = cos(rad(m_PanAngle));
3622    double sP = sin(rad(m_PanAngle));
3623    Vector3 v_forward(cT * sP, cT * cP, sT);
3624    Vector3 v_up(sT * sP, sT * cP, -cT);
3625    Vector3 v_right(-cP, sP, 0);
3626    assert(fabs(dot(v_forward, v_up)) < 1e-6);
3627    assert(fabs(dot(v_forward, v_right)) < 1e-6);
3628    assert(fabs(dot(v_right, v_up)) < 1e-6);
3629    Vector3 move = v_forward * forward + v_up * up + v_right * right;
3630    AddTranslation(-move);
3631    // Show current position.
3632    m_Parent->SetCoords(m_Parent->GetOffset() - GetTranslation());
3633    ForceRefresh();
3634}
3635
3636PresentationMark GfxCore::GetView() const
3637{
3638    return PresentationMark(GetTranslation() + m_Parent->GetOffset(),
3639                            m_PanAngle, -m_TiltAngle, m_Scale);
3640}
3641
3642void GfxCore::SetView(const PresentationMark & p)
3643{
3644    m_SwitchingTo = 0;
3645    SetTranslation(p - m_Parent->GetOffset());
3646    m_PanAngle = p.angle;
3647    m_TiltAngle = -p.tilt_angle; // FIXME: nasty reversed sense (and above)
3648    SetRotation(m_PanAngle, m_TiltAngle);
3649    SetScale(p.scale);
3650    ForceRefresh();
3651}
3652
3653void GfxCore::PlayPres(double speed, bool change_speed) {
3654    if (!change_speed || presentation_mode == 0) {
3655        if (speed == 0.0) {
3656            presentation_mode = 0;
3657            return;
3658        }
3659        presentation_mode = PLAYING;
3660        next_mark = m_Parent->GetPresMark(MARK_FIRST);
3661        SetView(next_mark);
3662        next_mark_time = 0; // There already!
3663        this_mark_total = 0;
3664        pres_reverse = (speed < 0);
3665    }
3666
3667    if (change_speed) pres_speed = speed;
3668
3669    if (speed != 0.0) {
3670        bool new_pres_reverse = (speed < 0);
3671        if (new_pres_reverse != pres_reverse) {
3672            pres_reverse = new_pres_reverse;
3673            if (pres_reverse) {
3674                next_mark = m_Parent->GetPresMark(MARK_PREV);
3675            } else {
3676                next_mark = m_Parent->GetPresMark(MARK_NEXT);
3677            }
3678            swap(this_mark_total, next_mark_time);
3679        }
3680    }
3681}
3682
3683void GfxCore::SetColourBy(int colour_by) {
3684    m_ColourBy = colour_by;
3685    switch (colour_by) {
3686        case COLOUR_BY_DEPTH:
3687            AddQuad = &GfxCore::AddQuadrilateralDepth;
3688            AddPoly = &GfxCore::AddPolylineDepth;
3689            break;
3690        case COLOUR_BY_DATE:
3691            AddQuad = &GfxCore::AddQuadrilateralDate;
3692            AddPoly = &GfxCore::AddPolylineDate;
3693            break;
3694        case COLOUR_BY_ERROR:
3695            AddQuad = &GfxCore::AddQuadrilateralError;
3696            AddPoly = &GfxCore::AddPolylineError;
3697            break;
3698        case COLOUR_BY_GRADIENT:
3699            AddQuad = &GfxCore::AddQuadrilateralGradient;
3700            AddPoly = &GfxCore::AddPolylineGradient;
3701            break;
3702        case COLOUR_BY_LENGTH:
3703            AddQuad = &GfxCore::AddQuadrilateralLength;
3704            AddPoly = &GfxCore::AddPolylineLength;
3705            break;
3706        default: // case COLOUR_BY_NONE:
3707            AddQuad = &GfxCore::AddQuadrilateral;
3708            AddPoly = &GfxCore::AddPolyline;
3709            break;
3710    }
3711
3712    InvalidateList(LIST_UNDERGROUND_LEGS);
3713    InvalidateList(LIST_SURFACE_LEGS);
3714    InvalidateList(LIST_TUBES);
3715
3716    ForceRefresh();
3717}
3718
3719bool GfxCore::ExportMovie(const wxString & fnm)
3720{
3721    int width;
3722    int height;
3723    GetSize(&width, &height);
3724    // Round up to next multiple of 2 (required by ffmpeg).
3725    width += (width & 1);
3726    height += (height & 1);
3727
3728    movie = new MovieMaker();
3729
3730    // FIXME: This should really use fn_str() - currently we probably can't
3731    // save to a Unicode path on wxmsw.
3732    if (!movie->Open(fnm.mb_str(), width, height)) {
3733        wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
3734        delete movie;
3735        movie = NULL;
3736        return false;
3737    }
3738
3739    PlayPres(1);
3740    return true;
3741}
3742
3743void
3744GfxCore::OnPrint(const wxString &filename, const wxString &title,
3745                 const wxString &datestamp, time_t datestamp_numeric,
3746                 const wxString &cs_proj,
3747                 bool close_after_print)
3748{
3749    svxPrintDlg * p;
3750    p = new svxPrintDlg(m_Parent, filename, title, cs_proj,
3751                        datestamp, datestamp_numeric,
3752                        m_PanAngle, m_TiltAngle,
3753                        m_Names, m_Crosses, m_Legs, m_Surface, m_Tubes,
3754                        m_Entrances, m_FixedPts, m_ExportedPts,
3755                        true, close_after_print);
3756    p->Show(true);
3757}
3758
3759void
3760GfxCore::OnExport(const wxString &filename, const wxString &title,
3761                  const wxString &datestamp, time_t datestamp_numeric,
3762                  const wxString &cs_proj)
3763{
3764    // Fill in "right_bearing" for each cross-section.
3765    list<vector<XSect> >::iterator trav = m_Parent->tubes_begin();
3766    list<vector<XSect> >::iterator tend = m_Parent->tubes_end();
3767    while (trav != tend) {
3768        SkinPassage(*trav, false);
3769        ++trav;
3770    }
3771
3772    svxPrintDlg * p;
3773    p = new svxPrintDlg(m_Parent, filename, title, cs_proj,
3774                        datestamp, datestamp_numeric,
3775                        m_PanAngle, m_TiltAngle,
3776                        m_Names, m_Crosses, m_Legs, m_Surface, m_Tubes,
3777                        m_Entrances, m_FixedPts, m_ExportedPts,
3778                        false);
3779    p->Show(true);
3780}
3781
3782static wxCursor
3783make_cursor(const unsigned char * bits, const unsigned char * mask,
3784            int hotx, int hoty)
3785{
3786#if defined __WXMSW__ || defined __WXMAC__
3787# ifdef __WXMAC__
3788    // The default Mac cursor is black with a white edge, so
3789    // invert our custom cursors to match.
3790    char b[128];
3791    for (int i = 0; i < 128; ++i)
3792        b[i] = bits[i] ^ 0xff;
3793# else
3794    const char * b = reinterpret_cast<const char *>(bits);
3795# endif
3796    wxBitmap cursor_bitmap(b, 32, 32);
3797    wxBitmap mask_bitmap(reinterpret_cast<const char *>(mask), 32, 32);
3798    cursor_bitmap.SetMask(new wxMask(mask_bitmap, *wxWHITE));
3799    wxImage cursor_image = cursor_bitmap.ConvertToImage();
3800    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, hotx);
3801    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, hoty);
3802    return wxCursor(cursor_image);
3803#else
3804    return wxCursor((const char *)bits, 32, 32, hotx, hoty,
3805                    (const char *)mask, wxBLACK, wxWHITE);
3806#endif
3807}
3808
3809const
3810#include "hand.xbm"
3811const
3812#include "handmask.xbm"
3813
3814const
3815#include "brotate.xbm"
3816const
3817#include "brotatemask.xbm"
3818
3819const
3820#include "vrotate.xbm"
3821const
3822#include "vrotatemask.xbm"
3823
3824const
3825#include "rotate.xbm"
3826const
3827#include "rotatemask.xbm"
3828
3829const
3830#include "rotatezoom.xbm"
3831const
3832#include "rotatezoommask.xbm"
3833
3834void
3835GfxCore::UpdateCursor(GfxCore::cursor new_cursor)
3836{
3837    // Check if we're already showing that cursor.
3838    if (current_cursor == new_cursor) return;
3839
3840    current_cursor = new_cursor;
3841    switch (current_cursor) {
3842        case GfxCore::CURSOR_DEFAULT:
3843            GLACanvas::SetCursor(wxNullCursor);
3844            break;
3845        case GfxCore::CURSOR_POINTING_HAND:
3846            GLACanvas::SetCursor(wxCursor(wxCURSOR_HAND));
3847            break;
3848        case GfxCore::CURSOR_DRAGGING_HAND:
3849            GLACanvas::SetCursor(make_cursor(hand_bits, handmask_bits, 12, 18));
3850            break;
3851        case GfxCore::CURSOR_HORIZONTAL_RESIZE:
3852            GLACanvas::SetCursor(wxCursor(wxCURSOR_SIZEWE));
3853            break;
3854        case GfxCore::CURSOR_ROTATE_HORIZONTALLY:
3855            GLACanvas::SetCursor(make_cursor(rotate_bits, rotatemask_bits, 15, 15));
3856            break;
3857        case GfxCore::CURSOR_ROTATE_VERTICALLY:
3858            GLACanvas::SetCursor(make_cursor(vrotate_bits, vrotatemask_bits, 15, 15));
3859            break;
3860        case GfxCore::CURSOR_ROTATE_EITHER_WAY:
3861            GLACanvas::SetCursor(make_cursor(brotate_bits, brotatemask_bits, 15, 15));
3862            break;
3863        case GfxCore::CURSOR_ZOOM:
3864            GLACanvas::SetCursor(wxCursor(wxCURSOR_MAGNIFIER));
3865            break;
3866        case GfxCore::CURSOR_ZOOM_ROTATE:
3867            GLACanvas::SetCursor(make_cursor(rotatezoom_bits, rotatezoommask_bits, 15, 15));
3868            break;
3869    }
3870}
3871
3872bool GfxCore::MeasuringLineActive() const
3873{
3874    if (Animating()) return false;
3875    return HereIsReal() || m_there;
3876}
3877
3878bool GfxCore::HandleRClick(wxPoint point)
3879{
3880    if (PointWithinCompass(point)) {
3881        // Pop up menu.
3882        wxMenu menu;
3883        /* TRANSLATORS: View *looking* North */
3884        menu.Append(menu_ORIENT_MOVE_NORTH, wmsg(/*View &North*/240));
3885        /* TRANSLATORS: View *looking* East */
3886        menu.Append(menu_ORIENT_MOVE_EAST, wmsg(/*View &East*/241));
3887        /* TRANSLATORS: View *looking* South */
3888        menu.Append(menu_ORIENT_MOVE_SOUTH, wmsg(/*View &South*/242));
3889        /* TRANSLATORS: View *looking* West */
3890        menu.Append(menu_ORIENT_MOVE_WEST, wmsg(/*View &West*/243));
3891        menu.AppendSeparator();
3892        /* TRANSLATORS: Menu item which turns off the "north arrow" in aven. */
3893        menu.AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Hide Compass*/387));
3894        /* TRANSLATORS: tickable menu item in View menu.
3895         *
3896         * Degrees are the angular measurement where there are 360 in a full
3897         * circle. */
3898        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
3899        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3900        PopupMenu(&menu);
3901        return true;
3902    }
3903
3904    if (PointWithinClino(point)) {
3905        // Pop up menu.
3906        wxMenu menu;
3907        menu.Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
3908        menu.Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
3909        menu.AppendSeparator();
3910        /* TRANSLATORS: Menu item which turns off the tilt indicator in aven. */
3911        menu.AppendCheckItem(menu_IND_CLINO, wmsg(/*&Hide Clino*/384));
3912        /* TRANSLATORS: tickable menu item in View menu.
3913         *
3914         * Degrees are the angular measurement where there are 360 in a full
3915         * circle. */
3916        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
3917        /* TRANSLATORS: tickable menu item in View menu.
3918         *
3919         * Show the tilt of the survey as a percentage gradient (100% = 45
3920         * degrees = 50 grad). */
3921        menu.AppendCheckItem(menu_CTL_PERCENT, wmsg(/*&Percent*/430));
3922        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3923        PopupMenu(&menu);
3924        return true;
3925    }
3926
3927    if (PointWithinScaleBar(point)) {
3928        // Pop up menu.
3929        wxMenu menu;
3930        /* TRANSLATORS: Menu item which turns off the scale bar in aven. */
3931        menu.AppendCheckItem(menu_IND_SCALE_BAR, wmsg(/*&Hide scale bar*/385));
3932        /* TRANSLATORS: tickable menu item in View menu.
3933         *
3934         * "Metric" here means metres, km, etc (rather than feet, miles, etc)
3935         */
3936        menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
3937        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3938        PopupMenu(&menu);
3939        return true;
3940    }
3941
3942    if (PointWithinColourKey(point)) {
3943        // Pop up menu.
3944        wxMenu menu;
3945        menu.AppendCheckItem(menu_COLOUR_BY_DEPTH, wmsg(/*Colour by &Depth*/292));
3946        menu.AppendCheckItem(menu_COLOUR_BY_DATE, wmsg(/*Colour by D&ate*/293));
3947        menu.AppendCheckItem(menu_COLOUR_BY_ERROR, wmsg(/*Colour by &Error*/289));
3948        menu.AppendCheckItem(menu_COLOUR_BY_GRADIENT, wmsg(/*Colour by &Gradient*/85));
3949        menu.AppendCheckItem(menu_COLOUR_BY_LENGTH, wmsg(/*Colour by &Length*/82));
3950        menu.AppendSeparator();
3951        /* TRANSLATORS: Menu item which turns off the colour key.
3952         * The "Colour Key" is the thing in aven showing which colour
3953         * corresponds to which depth, date, survey closure error, etc. */
3954        menu.AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*&Hide colour key*/386));
3955        if (m_ColourBy == COLOUR_BY_DEPTH || m_ColourBy == COLOUR_BY_LENGTH)
3956            menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
3957        else if (m_ColourBy == COLOUR_BY_GRADIENT)
3958            menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
3959        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3960        PopupMenu(&menu);
3961        return true;
3962    }
3963
3964    return false;
3965}
3966
3967void GfxCore::SetZoomBox(wxPoint p1, wxPoint p2, bool centred, bool aspect)
3968{
3969    if (centred) {
3970        p1.x = p2.x + (p1.x - p2.x) * 2;
3971        p1.y = p2.y + (p1.y - p2.y) * 2;
3972    }
3973    if (aspect) {
3974#if 0 // FIXME: This needs more work.
3975        int sx = GetXSize();
3976        int sy = GetYSize();
3977        int dx = p1.x - p2.x;
3978        int dy = p1.y - p2.y;
3979        int dy_new = dx * sy / sx;
3980        if (abs(dy_new) >= abs(dy)) {
3981            p1.y += (dy_new - dy) / 2;
3982            p2.y -= (dy_new - dy) / 2;
3983        } else {
3984            int dx_new = dy * sx / sy;
3985            p1.x += (dx_new - dx) / 2;
3986            p2.x -= (dx_new - dx) / 2;
3987        }
3988#endif
3989    }
3990    zoombox.set(p1, p2);
3991    ForceRefresh();
3992}
3993
3994void GfxCore::ZoomBoxGo()
3995{
3996    if (!zoombox.active()) return;
3997
3998    int width = GetXSize();
3999    int height = GetYSize();
4000
4001    TranslateCave(-0.5 * (zoombox.x1 + zoombox.x2 - width),
4002                  -0.5 * (zoombox.y1 + zoombox.y2 - height));
4003    int box_w = abs(zoombox.x1 - zoombox.x2);
4004    int box_h = abs(zoombox.y1 - zoombox.y2);
4005
4006    double factor = min(double(width) / box_w, double(height) / box_h);
4007
4008    zoombox.unset();
4009
4010    SetScale(GetScale() * factor);
4011}
Note: See TracBrowser for help on using the repository browser.