source: git/src/gfxcore.cc @ ff9f695

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereostereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since ff9f695 was ee3e284, checked in by Olly Betts <olly@…>, 14 years ago

src/gfxcore.cc: Fix how we pass on events from right-click menus of
indicators. The previous code seemed to work, but wasn't correct
as highlighted by trying to build it with 2.9.2 which now refuses
to build to avoid people getting this wrong.

git-svn-id: file:///home/survex-svn/survex/trunk@3873 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

  • Property mode set to 100644
File size: 79.2 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 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 "gfxcore.h"
35#include "mainfrm.h"
36#include "message.h"
37#include "useful.h"
38#include "printwx.h"
39#include "guicontrol.h"
40#include "moviemaker.h"
41
42#include <wx/confbase.h>
43#include <wx/image.h>
44
45#define MAX(a, b) ((a) > (b) ? (a) : (b))
46#define MAX3(a, b, c) ((a) > (b) ? MAX(a, c) : MAX(b, c))
47
48// Values for m_SwitchingTo
49#define PLAN 1
50#define ELEVATION 2
51#define NORTH 3
52#define EAST 4
53#define SOUTH 5
54#define WEST 6
55
56// Any error value higher than this is clamped to this.
57#define MAX_ERROR 12.0
58
59// How many bins per letter height to use when working out non-overlapping
60// labels.
61const unsigned int QUANTISE_FACTOR = 2;
62
63#include "avenpal.h"
64
65static const int COMPASS_OFFSET_X = 60;
66static const int COMPASS_OFFSET_Y = 80;
67static const int INDICATOR_BOX_SIZE = 60;
68static const int INDICATOR_GAP = 2;
69static const int INDICATOR_MARGIN = 5;
70static const int INDICATOR_OFFSET_X = 15;
71static const int INDICATOR_OFFSET_Y = 15;
72static const int INDICATOR_RADIUS = INDICATOR_BOX_SIZE / 2 - INDICATOR_MARGIN;
73static const int CLINO_OFFSET_X = 6 + INDICATOR_OFFSET_X +
74                                  INDICATOR_BOX_SIZE + INDICATOR_GAP;
75static const int KEY_OFFSET_X = 10;
76static const int KEY_OFFSET_Y = 10;
77static const int KEY_EXTRA_LEFT_MARGIN = 2;
78static const int KEY_BLOCK_WIDTH = 20;
79static const int KEY_BLOCK_HEIGHT = 16;
80static const int TICK_LENGTH = 4;
81static const int SCALE_BAR_OFFSET_X = 15;
82static const int SCALE_BAR_OFFSET_Y = 12;
83static const int SCALE_BAR_HEIGHT = 12;
84
85static const gla_colour TEXT_COLOUR = col_GREEN;
86static const gla_colour HERE_COLOUR = col_WHITE;
87static const gla_colour NAME_COLOUR = col_GREEN;
88
89#define HITTEST_SIZE 20
90
91// vector for lighting angle
92static const Vector3 light(.577, .577, .577);
93
94BEGIN_EVENT_TABLE(GfxCore, wxGLCanvas)
95    EVT_PAINT(GfxCore::OnPaint)
96    EVT_LEFT_DOWN(GfxCore::OnLButtonDown)
97    EVT_LEFT_UP(GfxCore::OnLButtonUp)
98    EVT_MIDDLE_DOWN(GfxCore::OnMButtonDown)
99    EVT_MIDDLE_UP(GfxCore::OnMButtonUp)
100    EVT_RIGHT_DOWN(GfxCore::OnRButtonDown)
101    EVT_RIGHT_UP(GfxCore::OnRButtonUp)
102    EVT_MOUSEWHEEL(GfxCore::OnMouseWheel)
103    EVT_MOTION(GfxCore::OnMouseMove)
104    EVT_LEAVE_WINDOW(GfxCore::OnLeaveWindow)
105    EVT_SIZE(GfxCore::OnSize)
106    EVT_IDLE(GfxCore::OnIdle)
107    EVT_CHAR(GfxCore::OnKeyPress)
108END_EVENT_TABLE()
109
110GfxCore::GfxCore(MainFrm* parent, wxWindow* parent_win, GUIControl* control) :
111    GLACanvas(parent_win, 100),
112    m_Scale(0.0),
113    m_ScaleBarWidth(0),
114    m_Control(control),
115    m_LabelGrid(NULL),
116    m_Parent(parent),
117    m_DoneFirstShow(false),
118    m_TiltAngle(0.0),
119    m_PanAngle(0.0),
120    m_Rotating(false),
121    m_RotationStep(0.0),
122    m_SwitchingTo(0),
123    m_Crosses(false),
124    m_Legs(true),
125    m_Names(false),
126    m_Scalebar(true),
127    m_ColourKey(true),
128    m_OverlappingNames(false),
129    m_Compass(true),
130    m_Clino(true),
131    m_Tubes(false),
132    m_ColourBy(COLOUR_BY_DEPTH),
133    m_HaveData(false),
134    m_MouseOutsideCompass(false),
135    m_MouseOutsideElev(false),
136    m_Surface(false),
137    m_Entrances(false),
138    m_FixedPts(false),
139    m_ExportedPts(false),
140    m_Grid(false),
141    m_BoundingBox(false),
142    m_Degrees(false),
143    m_Metric(false),
144    m_HitTestGridValid(false),
145    m_here_is_temporary(false),
146    presentation_mode(0),
147    pres_reverse(false),
148    pres_speed(0.0),
149    movie(NULL),
150    current_cursor(GfxCore::CURSOR_DEFAULT)
151{
152    AddQuad = &GfxCore::AddQuadrilateralDepth;
153    AddPoly = &GfxCore::AddPolylineDepth;
154    wxConfigBase::Get()->Read(wxT("metric"), &m_Metric, true);
155    wxConfigBase::Get()->Read(wxT("degrees"), &m_Degrees, true);
156    m_here.Invalidate();
157    m_there.Invalidate();
158
159    // Initialise grid for hit testing.
160    m_PointGrid = new list<LabelInfo*>[HITTEST_SIZE * HITTEST_SIZE];
161
162    m_Pens = new GLAPen[NUM_COLOUR_BANDS + 1];
163    for (int pen = 0; pen < NUM_COLOUR_BANDS + 1; ++pen) {
164        m_Pens[pen].SetColour(REDS[pen] / 255.0,
165                              GREENS[pen] / 255.0,
166                              BLUES[pen] / 255.0);
167    }
168}
169
170GfxCore::~GfxCore()
171{
172    TryToFreeArrays();
173
174    delete[] m_Pens;
175    delete[] m_PointGrid;
176}
177
178void GfxCore::TryToFreeArrays()
179{
180    // Free up any memory allocated for arrays.
181    delete[] m_LabelGrid;
182    m_LabelGrid = NULL;
183}
184
185//
186//  Initialisation methods
187//
188
189void GfxCore::Initialise(bool same_file)
190{
191    // Initialise the view from the parent holding the survey data.
192
193    TryToFreeArrays();
194
195    m_DoneFirstShow = false;
196
197    m_HitTestGridValid = false;
198    m_here_is_temporary = true;
199    m_here.Invalidate();
200    m_there.Invalidate();
201
202    m_MouseOutsideCompass = m_MouseOutsideElev = false;
203
204    if (!same_file) {
205        // Apply default parameters unless reloading the same file.
206        DefaultParameters();
207
208        // Set the initial scale.
209        SetScale(1.0);
210    }
211
212    m_HaveData = true;
213
214    ForceRefresh();
215}
216
217void GfxCore::FirstShow()
218{
219    GLACanvas::FirstShow();
220
221    const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
222    list<LabelInfo*>::iterator pos = m_Parent->GetLabelsNC();
223    while (pos != m_Parent->GetLabelsNCEnd()) {
224        LabelInfo* label = *pos++;
225        // Calculate and set the label width for use when plotting
226        // none-overlapping labels.
227        int ext_x;
228        GLACanvas::GetTextExtent(label->GetText(), &ext_x, NULL);
229        label->set_width(unsigned(ext_x) / quantise + 1);
230    }
231
232    SetBackgroundColour(0.0, 0.0, 0.0);
233
234    // Set diameter of the viewing volume.
235    SetVolumeDiameter(sqrt(sqrd(m_Parent->GetXExtent()) +
236                           sqrd(m_Parent->GetYExtent()) +
237                           sqrd(m_Parent->GetZExtent())));
238
239    m_DoneFirstShow = true;
240}
241
242//
243//  Recalculating methods
244//
245
246void GfxCore::SetScale(Double scale)
247{
248    if (scale < 0.05) {
249        scale = 0.05;
250    } else if (scale > GetVolumeDiameter()) {
251        scale = GetVolumeDiameter();
252    }
253
254    m_Scale = scale;
255    m_HitTestGridValid = false;
256    if (m_here_is_temporary) SetHere();
257
258    GLACanvas::SetScale(scale);
259}
260
261bool GfxCore::HasUndergroundLegs() const
262{
263    return m_Parent->HasUndergroundLegs();
264}
265
266bool GfxCore::HasSurfaceLegs() const
267{
268    return m_Parent->HasSurfaceLegs();
269}
270
271bool GfxCore::HasTubes() const
272{
273    return m_Parent->HasTubes();
274}
275
276void GfxCore::UpdateBlobs()
277{
278    InvalidateList(LIST_BLOBS);
279}
280
281//
282//  Event handlers
283//
284
285void GfxCore::OnLeaveWindow(wxMouseEvent& event) {
286    SetHere();
287    ClearCoords();
288}
289
290void GfxCore::OnIdle(wxIdleEvent& event)
291{
292    // Handle an idle event.
293    if (Animate()) {
294        ForceRefresh();
295        event.RequestMore();
296    }
297}
298
299void GfxCore::OnPaint(wxPaintEvent&)
300{
301    // Redraw the window.
302
303    // Get a graphics context.
304    wxPaintDC dc(this);
305
306    assert(GetContext());
307
308    if (m_HaveData) {
309        // Make sure we're initialised.
310        bool first_time = !m_DoneFirstShow;
311        if (first_time) {
312            FirstShow();
313        }
314
315        StartDrawing();
316
317        // Clear the background.
318        Clear();
319
320        // Set up model transformation matrix.
321        SetDataTransform();
322
323        timer.Start(); // reset timer
324
325        if (m_Legs || m_Tubes) {
326            if (m_Tubes) {
327                EnableSmoothPolygons(true); // FIXME: allow false for wireframe view
328                DrawList(LIST_TUBES);
329                DisableSmoothPolygons();
330            }
331
332            // Draw the underground legs.  Do this last so that anti-aliasing
333            // works over polygons.
334            SetColour(col_GREEN);
335            DrawList(LIST_UNDERGROUND_LEGS);
336        }
337
338        if (m_Surface) {
339            // Draw the surface legs.
340            DrawList(LIST_SURFACE_LEGS);
341        }
342
343        if (m_BoundingBox) {
344            DrawShadowedBoundingBox();
345        }
346        if (m_Grid) {
347            // Draw the grid.
348            DrawList(LIST_GRID);
349        }
350
351        DrawList(LIST_BLOBS);
352
353        if (m_Crosses) {
354            DrawList(LIST_CROSSES);
355        }
356
357        SetIndicatorTransform();
358
359        // Draw station names.
360        if (m_Names /*&& !m_Control->MouseDown() && !Animating()*/) {
361            SetColour(NAME_COLOUR);
362
363            if (m_OverlappingNames) {
364                SimpleDrawNames();
365            } else {
366                NattyDrawNames();
367            }
368        }
369
370        if (MeasuringLineActive()) {
371            // Draw "here" and "there".
372            double hx, hy;
373            SetColour(HERE_COLOUR);
374            if (m_here.IsValid()) {
375                double dummy;
376                Transform(m_here, &hx, &hy, &dummy);
377                if (!m_here_is_temporary) DrawRing(hx, hy);
378            }
379            if (m_there.IsValid()) {
380                double tx, ty;
381                double dummy;
382                Transform(m_there, &tx, &ty, &dummy);
383                if (m_here.IsValid()) {
384                    BeginLines();
385                    PlaceIndicatorVertex(hx, hy);
386                    PlaceIndicatorVertex(tx, ty);
387                    EndLines();
388                }
389                BeginBlobs();
390                DrawBlob(tx, ty);
391                EndBlobs();
392            }
393        }
394
395        // Draw indicators.
396        //
397        // There's no advantage in generating an OpenGL list for the
398        // indicators since they change with almost every redraw (and
399        // sometimes several times between redraws).  This way we avoid
400        // the need to track when to update the indicator OpenGL list,
401        // and also avoid indicator update bugs when we don't quite get this
402        // right...
403        DrawIndicators();
404
405        FinishDrawing();
406
407        drawtime = timer.Time();
408    } else {
409        dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME));
410        dc.Clear();
411    }
412}
413
414void GfxCore::DrawBoundingBox()
415{
416    const Vector3 v = 0.5 * m_Parent->GetExtent();
417
418    SetColour(col_BLUE);
419    EnableDashedLines();
420    BeginPolyline();
421    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
422    PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
423    PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
424    PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
425    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
426    EndPolyline();
427    BeginPolyline();
428    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
429    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
430    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
431    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
432    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
433    EndPolyline();
434    BeginLines();
435    PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
436    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
437    PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
438    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
439    PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
440    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
441    PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
442    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
443    EndLines();
444    DisableDashedLines();
445}
446
447void GfxCore::DrawShadowedBoundingBox()
448{
449    const Vector3 v = 0.5 * m_Parent->GetExtent();
450
451    DrawBoundingBox();
452
453    PolygonOffset(true);
454    SetColour(col_DARK_GREY);
455    BeginQuadrilaterals();
456    PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
457    PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
458    PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
459    PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
460    EndQuadrilaterals();
461    PolygonOffset(false);
462
463    DrawList(LIST_SHADOW);
464}
465
466void GfxCore::DrawGrid()
467{
468    // Draw the grid.
469    SetColour(col_RED);
470
471    // Calculate the extent of the survey, in metres across the screen plane.
472    Double m_across_screen = SurveyUnitsAcrossViewport();
473    // Calculate the length of the scale bar in metres.
474    //--move this elsewhere
475    Double size_snap = pow(10.0, floor(log10(0.75 * m_across_screen)));
476    Double t = m_across_screen * 0.75 / size_snap;
477    if (t >= 5.0) {
478        size_snap *= 5.0;
479    }
480    else if (t >= 2.0) {
481        size_snap *= 2.0;
482    }
483
484    Double grid_size = size_snap * 0.1;
485    Double edge = grid_size * 2.0;
486    Double grid_z = -m_Parent->GetZExtent() * 0.5 - grid_size;
487    Double left = -m_Parent->GetXExtent() * 0.5 - edge;
488    Double right = m_Parent->GetXExtent() * 0.5 + edge;
489    Double bottom = -m_Parent->GetYExtent() * 0.5 - edge;
490    Double top = m_Parent->GetYExtent() * 0.5 + edge;
491    int count_x = (int) ceil((right - left) / grid_size);
492    int count_y = (int) ceil((top - bottom) / grid_size);
493    Double actual_right = left + count_x*grid_size;
494    Double actual_top = bottom + count_y*grid_size;
495
496    BeginLines();
497
498    for (int xc = 0; xc <= count_x; xc++) {
499        Double x = left + xc*grid_size;
500
501        PlaceVertex(x, bottom, grid_z);
502        PlaceVertex(x, actual_top, grid_z);
503    }
504
505    for (int yc = 0; yc <= count_y; yc++) {
506        Double y = bottom + yc*grid_size;
507        PlaceVertex(left, y, grid_z);
508        PlaceVertex(actual_right, y, grid_z);
509    }
510
511    EndLines();
512}
513
514int GfxCore::GetClinoOffset() const
515{
516    return m_Compass ? CLINO_OFFSET_X : INDICATOR_OFFSET_X;
517}
518
519void GfxCore::DrawTick(int angle_cw)
520{
521    const Double theta = rad(angle_cw);
522    const wxCoord length1 = INDICATOR_RADIUS;
523    const wxCoord length0 = length1 + TICK_LENGTH;
524    wxCoord x0 = wxCoord(length0 * sin(theta));
525    wxCoord y0 = wxCoord(length0 * cos(theta));
526    wxCoord x1 = wxCoord(length1 * sin(theta));
527    wxCoord y1 = wxCoord(length1 * cos(theta));
528
529    PlaceIndicatorVertex(x0, y0);
530    PlaceIndicatorVertex(x1, y1);
531}
532
533void GfxCore::DrawArrow(gla_colour col1, gla_colour col2) {
534    Vector3 p1(0, INDICATOR_RADIUS, 0);
535    Vector3 p2(INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 150deg
536    Vector3 p3(-INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 210deg
537    Vector3 pc(0, 0, 0);
538
539    DrawTriangle(col_LIGHT_GREY, col1, p2, p1, pc);
540    DrawTriangle(col_LIGHT_GREY, col2, p3, p1, pc);
541}
542
543void GfxCore::DrawCompass() {
544    // Ticks.
545    BeginLines();
546    for (int angle = 315; angle > 0; angle -= 45) {
547        DrawTick(angle);
548    }
549    SetColour(col_GREEN);
550    DrawTick(0);
551    EndLines();
552
553    // Compass background.
554    DrawCircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS);
555
556    // Compass arrow.
557    DrawArrow(col_INDICATOR_1, col_INDICATOR_2);
558}
559
560// Draw the non-rotating background to the clino.
561void GfxCore::DrawClinoBack() {
562    BeginLines();
563    for (int angle = 0; angle <= 180; angle += 90) {
564        DrawTick(angle);
565    }
566
567    SetColour(col_GREY);
568    PlaceIndicatorVertex(0, INDICATOR_RADIUS);
569    PlaceIndicatorVertex(0, -INDICATOR_RADIUS);
570    PlaceIndicatorVertex(0, 0);
571    PlaceIndicatorVertex(INDICATOR_RADIUS, 0);
572
573    EndLines();
574}
575
576void GfxCore::DrawClino() {
577    // Ticks.
578    SetColour(col_GREEN);
579    BeginLines();
580    DrawTick(0);
581    EndLines();
582
583    // Clino background.
584    DrawSemicircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS, 0);
585
586    // Elevation arrow.
587    DrawArrow(col_INDICATOR_2, col_INDICATOR_1);
588}
589
590void GfxCore::Draw2dIndicators()
591{
592    // Draw the compass and elevation indicators.
593
594    const int centre_y = INDICATOR_BOX_SIZE / 2 + INDICATOR_OFFSET_Y;
595
596    const int comp_centre_x = GetCompassXPosition();
597
598    if (m_Compass && !m_Parent->IsExtendedElevation()) {
599        // If the user is dragging the compass with the pointer outside the
600        // compass, we snap to 45 degree multiples, and the ticks go white.
601        SetColour(m_MouseOutsideCompass ? col_WHITE : col_LIGHT_GREY_2);
602        DrawList2D(LIST_COMPASS, comp_centre_x, centre_y, -m_PanAngle);
603    }
604
605    const int elev_centre_x = GetClinoXPosition();
606
607    if (m_Clino) {
608        // If the user is dragging the clino with the pointer outside the
609        // clino, we snap to 90 degree multiples, and the ticks go white.
610        SetColour(m_MouseOutsideElev ? col_WHITE : col_LIGHT_GREY_2);
611        DrawList2D(LIST_CLINO_BACK, elev_centre_x, centre_y, 0);
612        DrawList2D(LIST_CLINO, elev_centre_x, centre_y, 90 - m_TiltAngle);
613    }
614
615    SetColour(TEXT_COLOUR);
616
617    static int triple_zero_width = 0;
618    static int height = 0;
619    if (!triple_zero_width) {
620        GetTextExtent(wxT("000"), &triple_zero_width, &height);
621    }
622    const int y_off = INDICATOR_OFFSET_Y + INDICATOR_BOX_SIZE + height / 2;
623
624    if (m_Compass && !m_Parent->IsExtendedElevation()) {
625        wxString str;
626        int value;
627        if (m_Degrees) {
628            value = int(m_PanAngle);
629        } else {
630            value = int(m_PanAngle * 200.0 / 180.0);
631        }
632        str.Printf(wxT("%03d"), value);
633        DrawIndicatorText(comp_centre_x - triple_zero_width / 2, y_off, str);
634
635        str = wmsg(/*Facing*/203);
636        int w;
637        GetTextExtent(str, &w, NULL);
638        DrawIndicatorText(comp_centre_x - w / 2, y_off + height, str);
639    }
640
641    if (m_Clino) {
642        int angle;
643        wxString str;
644        int width;
645        if (m_Degrees) {
646            static int zero_zero_width = 0;
647            if (!zero_zero_width) {
648                GetTextExtent(wxT("00"), &zero_zero_width, NULL);
649            }
650            width = zero_zero_width;
651            angle = int(m_TiltAngle);
652            str = angle ? wxString::Format(wxT("%+03d"), angle) : wxT("00");
653        } else {
654            width = triple_zero_width;
655            angle = int(m_TiltAngle * 200.0 / 180.0);
656            str = angle ? wxString::Format(wxT("%+04d"), angle) : wxT("000");
657        }
658
659        // Adjust horizontal position so the left of the first digit is
660        // always in the same place.
661        int sign_offset = 0;
662        if (angle < 0) {
663            static int minus_width = 0;
664            if (!minus_width) {
665                GetTextExtent(wxT("-"), &minus_width, NULL);
666            }
667            sign_offset = minus_width + 1;
668        } else if (angle > 0) {
669            static int plus_width = 0;
670            if (!plus_width) {
671                GetTextExtent(wxT("+"), &plus_width, NULL);
672            }
673            sign_offset = plus_width + 1;
674        }
675        DrawIndicatorText(elev_centre_x - sign_offset - width / 2, y_off, str);
676
677        str = wmsg(/*Elevation*/118);
678        int w;
679        GetTextExtent(str, &w, NULL);
680        DrawIndicatorText(elev_centre_x - w / 2, y_off + height, str);
681    }
682}
683
684void GfxCore::NattyDrawNames()
685{
686    // Draw station names, without overlapping.
687
688    const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
689    const unsigned int quantised_x = GetXSize() / quantise;
690    const unsigned int quantised_y = GetYSize() / quantise;
691    const size_t buffer_size = quantised_x * quantised_y;
692
693    if (!m_LabelGrid) m_LabelGrid = new char[buffer_size];
694
695    memset((void*) m_LabelGrid, 0, buffer_size);
696
697    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
698    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
699        if (!((m_Surface && (*label)->IsSurface()) ||
700              (m_Legs && (*label)->IsUnderground()) ||
701              (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
702            // if this station isn't to be displayed, skip to the next
703            // (last case is for stns with no legs attached)
704            continue;
705        }
706
707        double x, y, z;
708
709        Transform(**label, &x, &y, &z);
710        // Check if the label is behind us (in perspective view).
711        if (z <= 0.0 || z >= 1.0) continue;
712
713        // Apply a small shift so that translating the view doesn't make which
714        // labels are displayed change as the resulting twinkling effect is
715        // distracting.
716        double tx, ty, tz;
717        Transform(Vector3(), &tx, &ty, &tz);
718        tx -= floor(tx / quantise) * quantise;
719        ty -= floor(ty / quantise) * quantise;
720
721        tx = x - tx;
722        if (tx < 0) continue;
723
724        ty = y - ty;
725        if (ty < 0) continue;
726
727        unsigned int iy = unsigned(ty) / quantise;
728        if (iy >= quantised_y) continue;
729        unsigned int width = (*label)->get_width();
730        unsigned int ix = unsigned(tx) / quantise;
731        if (ix + width >= quantised_x) continue;
732
733        char * test = m_LabelGrid + ix + iy * quantised_x;
734        if (memchr(test, 1, width)) continue;
735
736        x += 3;
737        y -= GetFontSize() / 2;
738        DrawIndicatorText((int)x, (int)y, (*label)->GetText());
739
740        if (iy > QUANTISE_FACTOR) iy = QUANTISE_FACTOR;
741        test -= quantised_x * iy;
742        iy += 4;
743        while (--iy && test < m_LabelGrid + buffer_size) {
744            memset(test, 1, width);
745            test += quantised_x;
746        }
747    }
748}
749
750void GfxCore::SimpleDrawNames()
751{
752    // Draw all station names, without worrying about overlaps
753    list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
754    for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
755        if (!((m_Surface && (*label)->IsSurface()) ||
756              (m_Legs && (*label)->IsUnderground()) ||
757              (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
758            // if this station isn't to be displayed, skip to the next
759            // (last case is for stns with no legs attached)
760            continue;
761        }
762
763        double x, y, z;
764        Transform(**label, &x, &y, &z);
765
766        // Check if the label is behind us (in perspective view).
767        if (z <= 0) continue;
768
769        x += 3;
770        y -= GetFontSize() / 2;
771        DrawIndicatorText((int)x, (int)y, (*label)->GetText());
772    }
773}
774
775void GfxCore::DrawColourKey(int num_bands, const wxString & other)
776{
777    int total_block_height =
778        KEY_BLOCK_HEIGHT * (num_bands == 1 ? num_bands : num_bands - 1);
779    if (!other.empty()) total_block_height += KEY_BLOCK_HEIGHT * 2;
780
781    const int bottom = -total_block_height;
782
783    int size = 0;
784    if (!other.empty()) GetTextExtent(other, &size, NULL);
785    int band;
786    for (band = 0; band < num_bands; ++band) {
787        int x;
788        GetTextExtent(key_legends[band], &x, NULL);
789        if (x > size) size = x;
790    }
791
792    int left = -KEY_BLOCK_WIDTH - size;
793
794    key_lowerleft[m_ColourBy].x = left - KEY_EXTRA_LEFT_MARGIN;
795    key_lowerleft[m_ColourBy].y = bottom;
796
797    int y = bottom;
798
799    if (!other.empty()) {
800        DrawShadedRectangle(GetSurfacePen(), GetSurfacePen(), left, y,
801                KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
802        SetColour(col_BLACK);
803        BeginPolyline();
804        PlaceIndicatorVertex(left, y);
805        PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, y);
806        PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, y + KEY_BLOCK_HEIGHT);
807        PlaceIndicatorVertex(left, y + KEY_BLOCK_HEIGHT);
808        PlaceIndicatorVertex(left, y);
809        EndPolyline();
810        y += KEY_BLOCK_HEIGHT * 2;
811    }
812
813    int start = y;
814    if (num_bands == 1) {
815        DrawShadedRectangle(GetPen(0), GetPen(0), left, y,
816                            KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
817        y += KEY_BLOCK_HEIGHT;
818    } else {
819        for (band = 0; band < num_bands - 1; ++band) {
820            DrawShadedRectangle(GetPen(band), GetPen(band + 1), left, y,
821                                KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
822            y += KEY_BLOCK_HEIGHT;
823        }
824    }
825
826    SetColour(col_BLACK);
827    BeginPolyline();
828    PlaceIndicatorVertex(left, y);
829    PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, y);
830    PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, start);
831    PlaceIndicatorVertex(left, start);
832    PlaceIndicatorVertex(left, y);
833    EndPolyline();
834
835    y = bottom - GetFontSize() / 2;
836    left += KEY_BLOCK_WIDTH + 5;
837
838    SetColour(TEXT_COLOUR);
839    if (!other.empty()) {
840        y += KEY_BLOCK_HEIGHT / 2;
841        DrawIndicatorText(left, y, other);
842        y += KEY_BLOCK_HEIGHT * 2 - KEY_BLOCK_HEIGHT / 2;
843    }
844
845    if (num_bands == 1) {
846        y += KEY_BLOCK_HEIGHT / 2;
847        DrawIndicatorText(left, y, key_legends[0]);
848    } else {
849        for (band = 0; band < num_bands; ++band) {
850            DrawIndicatorText(left, y, key_legends[band]);
851            y += KEY_BLOCK_HEIGHT;
852        }
853    }
854}
855
856void GfxCore::DrawDepthKey()
857{
858    Double z_ext = m_Parent->GetDepthExtent();
859    int num_bands;
860    if (z_ext == 0) {
861        num_bands = 1;
862    } else {
863        num_bands = GetNumColourBands();
864    }
865
866    for (int band = 0; band < GetNumColourBands(); ++band) {
867        Double z = m_Parent->GetDepthMin() + m_Parent->GetOffset().GetZ();
868        if (band)
869            z += z_ext * band / (num_bands - 1);
870        key_legends[band] = FormatLength(z, false);
871    }
872
873    DrawColourKey(num_bands, wxString());
874}
875
876void GfxCore::DrawDateKey()
877{
878    int num_bands;
879    if (!HasDateInformation()) {
880        num_bands = 0;
881    } else {
882        int date_ext = m_Parent->GetDateExtent();
883        if (date_ext == 0) {
884            num_bands = 1;
885        } else {
886            num_bands = GetNumColourBands();
887        }
888        for (int band = 0; band < num_bands; ++band) {
889            int y, m, d;
890            int days = m_Parent->GetDateMin();
891            if (band)
892                days += date_ext * band / (num_bands - 1);
893            ymd_from_days_since_1900(days, &y, &m, &d);
894            key_legends[band].Printf(wxT("%04d-%02d-%02d"), y, m, d);
895        }
896    }
897
898    wxString other;
899    if (!m_Parent->HasCompleteDateInfo()) {
900        other = wmsg(/*Undated*/221);
901    }
902
903    DrawColourKey(num_bands, other);
904}
905
906void GfxCore::DrawErrorKey()
907{
908    int num_bands;
909    if (HasErrorInformation()) {
910        // Use fixed colours for each error factor so it's directly visually
911        // comparable between surveys.
912        num_bands = GetNumColourBands();
913        for (int band = 0; band < num_bands; ++band) {
914            double E = MAX_ERROR * band / (num_bands - 1);
915            key_legends[band].Printf(wxT("%.2f"), E);
916        }
917    } else {
918        num_bands = 0;
919    }
920
921    // Always show the "Not in loop" legend for now (FIXME).
922    DrawColourKey(num_bands, wmsg(/*Not in loop*/290));
923}
924
925wxString GfxCore::FormatLength(Double size_snap, bool scalebar)
926{
927    wxString str;
928    bool negative = (size_snap < 0.0);
929
930    if (negative) {
931        size_snap = -size_snap;
932    }
933
934    if (size_snap == 0.0) {
935        str = wxT("0");
936    } else if (m_Metric) {
937#ifdef SILLY_UNITS
938        if (size_snap < 1e-12) {
939            str.Printf(wxT("%.3gpm"), size_snap * 1e12);
940        } else if (size_snap < 1e-9) {
941            str.Printf(wxT("%.fpm"), size_snap * 1e12);
942        } else if (size_snap < 1e-6) {
943            str.Printf(wxT("%.fnm"), size_snap * 1e9);
944        } else if (size_snap < 1e-3) {
945            str.Printf(wxT("%.fum"), size_snap * 1e6);
946#else
947        if (size_snap < 1e-3) {
948            str.Printf(wxT("%.3gmm"), size_snap * 1e3);
949#endif
950        } else if (size_snap < 1e-2) {
951            str.Printf(wxT("%.fmm"), size_snap * 1e3);
952        } else if (size_snap < 1.0) {
953            str.Printf(wxT("%.fcm"), size_snap * 100.0);
954        } else if (size_snap < 1e3) {
955            str.Printf(wxT("%.fm"), size_snap);
956#ifdef SILLY_UNITS
957        } else if (size_snap < 1e6) {
958            str.Printf(wxT("%.fkm"), size_snap * 1e-3);
959        } else if (size_snap < 1e9) {
960            str.Printf(wxT("%.fMm"), size_snap * 1e-6);
961        } else {
962            str.Printf(wxT("%.fGm"), size_snap * 1e-9);
963#else
964        } else {
965            str.Printf(scalebar ? wxT("%.fkm") : wxT("%.2fkm"), size_snap * 1e-3);
966#endif
967        }
968    } else {
969        size_snap /= METRES_PER_FOOT;
970        if (scalebar) {
971            Double inches = size_snap * 12;
972            if (inches < 1.0) {
973                str.Printf(wxT("%.3gin"), inches);
974            } else if (size_snap < 1.0) {
975                str.Printf(wxT("%.fin"), inches);
976            } else if (size_snap < 5279.5) {
977                str.Printf(wxT("%.fft"), size_snap);
978            } else {
979                str.Printf(wxT("%.f miles"), size_snap / 5280.0);
980            }
981        } else {
982            str.Printf(wxT("%.fft"), size_snap);
983        }
984    }
985
986    return negative ? wxString(wxT("-")) + str : str;
987}
988
989void GfxCore::DrawScaleBar()
990{
991    // Draw the scalebar.
992    if (GetPerspective()) return;
993
994    // Calculate how many metres of survey are currently displayed across the
995    // screen.
996    Double across_screen = SurveyUnitsAcrossViewport();
997
998    double f = double(GetClinoXPosition() - INDICATOR_BOX_SIZE / 2 - SCALE_BAR_OFFSET_X) / GetXSize();
999    if (f > 0.75) {
1000        f = 0.75;
1001    } else if (f < 0.5) {
1002        // Stop it getting squeezed to nothing.
1003        // FIXME: In this case we should probably move the compass and clino up
1004        // to make room rather than letting stuff overlap.
1005        f = 0.5;
1006    }
1007
1008    // Convert to imperial measurements if required.
1009    Double multiplier = 1.0;
1010    if (!m_Metric) {
1011        across_screen /= METRES_PER_FOOT;
1012        multiplier = METRES_PER_FOOT;
1013        if (across_screen >= 5280.0 / f) {
1014            across_screen /= 5280.0;
1015            multiplier *= 5280.0;
1016        }
1017    }
1018
1019    // Calculate the length of the scale bar.
1020    Double size_snap = pow(10.0, floor(log10(f * across_screen)));
1021    Double t = across_screen * f / size_snap;
1022    if (t >= 5.0) {
1023        size_snap *= 5.0;
1024    } else if (t >= 2.0) {
1025        size_snap *= 2.0;
1026    }
1027
1028    if (!m_Metric) size_snap *= multiplier;
1029
1030    // Actual size of the thing in pixels:
1031    int size = int((size_snap / SurveyUnitsAcrossViewport()) * GetXSize());
1032    m_ScaleBarWidth = size;
1033
1034    // Draw it...
1035    const int end_y = SCALE_BAR_OFFSET_Y + SCALE_BAR_HEIGHT;
1036    int interval = size / 10;
1037
1038    gla_colour col = col_WHITE;
1039    for (int ix = 0; ix < 10; ix++) {
1040        int x = SCALE_BAR_OFFSET_X + int(ix * ((Double) size / 10.0));
1041
1042        DrawRectangle(col, col, x, end_y, interval + 2, SCALE_BAR_HEIGHT);
1043
1044        col = (col == col_WHITE) ? col_GREY : col_WHITE;
1045    }
1046
1047    // Add labels.
1048    wxString str = FormatLength(size_snap);
1049
1050    int text_width, text_height;
1051    GetTextExtent(str, &text_width, &text_height);
1052    const int text_y = end_y - text_height + 1;
1053    SetColour(TEXT_COLOUR);
1054    DrawIndicatorText(SCALE_BAR_OFFSET_X, text_y, wxT("0"));
1055    DrawIndicatorText(SCALE_BAR_OFFSET_X + size - text_width, text_y, str);
1056}
1057
1058bool GfxCore::CheckHitTestGrid(const wxPoint& point, bool centre)
1059{
1060    if (Animating()) return false;
1061
1062    if (point.x < 0 || point.x >= GetXSize() ||
1063        point.y < 0 || point.y >= GetYSize()) {
1064        return false;
1065    }
1066
1067    SetDataTransform();
1068
1069    if (!m_HitTestGridValid) CreateHitTestGrid();
1070
1071    int grid_x = (point.x * (HITTEST_SIZE - 1)) / GetXSize();
1072    int grid_y = (point.y * (HITTEST_SIZE - 1)) / GetYSize();
1073
1074    LabelInfo *best = NULL;
1075    int dist_sqrd = 25;
1076    int square = grid_x + grid_y * HITTEST_SIZE;
1077    list<LabelInfo*>::iterator iter = m_PointGrid[square].begin();
1078
1079    while (iter != m_PointGrid[square].end()) {
1080        LabelInfo *pt = *iter++;
1081
1082        double cx, cy, cz;
1083
1084        Transform(*pt, &cx, &cy, &cz);
1085
1086        cy = GetYSize() - cy;
1087
1088        int dx = point.x - int(cx);
1089        int ds = dx * dx;
1090        if (ds >= dist_sqrd) continue;
1091        int dy = point.y - int(cy);
1092
1093        ds += dy * dy;
1094        if (ds >= dist_sqrd) continue;
1095
1096        dist_sqrd = ds;
1097        best = pt;
1098
1099        if (ds == 0) break;
1100    }
1101
1102    if (best) {
1103        m_Parent->ShowInfo(best);
1104        if (centre) {
1105            // FIXME: allow Ctrl-Click to not set there or something?
1106            CentreOn(*best);
1107            WarpPointer(GetXSize() / 2, GetYSize() / 2);
1108            SetThere(*best);
1109            m_Parent->SelectTreeItem(best);
1110        }
1111    } else {
1112        // Left-clicking not on a survey cancels the measuring line.
1113        if (centre) {
1114            ClearTreeSelection();
1115        } else {
1116            m_Parent->ShowInfo(best);
1117            double x, y, z;
1118            ReverseTransform(point.x, GetYSize() - point.y, &x, &y, &z);
1119            SetHere(Point(Vector3(x, y, z)));
1120            m_here_is_temporary = true;
1121        }
1122    }
1123
1124    return best;
1125}
1126
1127void GfxCore::OnSize(wxSizeEvent& event)
1128{
1129    // Handle a change in window size.
1130    wxSize size = event.GetSize();
1131
1132    if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
1133        // Before things are fully initialised, we sometimes get a bogus
1134        // resize message...
1135        // FIXME have changes in MainFrm cured this?  It still happens with
1136        // 1.0.32 and wxGTK 2.5.2 (load a file from the command line).
1137        // With 1.1.6 and wxGTK 2.4.2 we only get negative sizes if MainFrm
1138        // is resized such that the GfxCore window isn't visible.
1139        //printf("OnSize(%d,%d)\n", size.GetWidth(), size.GetHeight());
1140        return;
1141    }
1142
1143    GLACanvas::OnSize(event);
1144
1145    if (m_DoneFirstShow) {
1146        TryToFreeArrays();
1147
1148        m_HitTestGridValid = false;
1149
1150        ForceRefresh();
1151    }
1152}
1153
1154void GfxCore::DefaultParameters()
1155{
1156    // Set default viewing parameters.
1157
1158    m_Surface = false;
1159    if (!m_Parent->HasUndergroundLegs()) {
1160        if (m_Parent->HasSurfaceLegs()) {
1161            // If there are surface legs, but no underground legs, turn
1162            // surface surveys on.
1163            m_Surface = true;
1164        } else {
1165            // If there are no legs (e.g. after loading a .pos file), turn
1166            // crosses on.
1167            m_Crosses = true;
1168        }
1169    }
1170
1171    m_PanAngle = 0.0;
1172    if (m_Parent->IsExtendedElevation()) {
1173        m_TiltAngle = 0.0;
1174    } else {
1175        m_TiltAngle = -90.0;
1176    }
1177
1178    SetRotation(m_PanAngle, m_TiltAngle);
1179    SetTranslation(Vector3());
1180
1181    m_RotationStep = 30.0;
1182    m_Rotating = false;
1183    m_SwitchingTo = 0;
1184    m_Entrances = false;
1185    m_FixedPts = false;
1186    m_ExportedPts = false;
1187    m_Grid = false;
1188    m_BoundingBox = false;
1189    m_Tubes = false;
1190    if (GetPerspective()) TogglePerspective();
1191}
1192
1193void GfxCore::Defaults()
1194{
1195    // Restore default scale, rotation and translation parameters.
1196    DefaultParameters();
1197    SetScale(1.0);
1198
1199    // Invalidate all the cached lists.
1200    GLACanvas::FirstShow();
1201
1202    ForceRefresh();
1203}
1204
1205// return: true if animation occured (and ForceRefresh() needs to be called)
1206bool GfxCore::Animate()
1207{
1208    if (!Animating()) return false;
1209
1210    // Don't show pointer coordinates while animating.
1211    // FIXME : only do this when we *START* animating!  Use a static copy
1212    // of the value of "Animating()" last time we were here to track this?
1213    // MainFrm now checks if we're trying to clear already cleared labels
1214    // and just returns, but it might be simpler to check here!
1215    ClearCoords();
1216    m_Parent->ShowInfo(NULL);
1217
1218    static double last_t = 0;
1219    double t;
1220    if (movie) {
1221        ReadPixels(movie->GetWidth(), movie->GetHeight(), movie->GetBuffer());
1222        movie->AddFrame();
1223        t = 1.0 / 25.0; // 25 frames per second
1224    } else {
1225        t = timer.Time() * 1.0e-3;
1226        if (t == 0) t = 0.001;
1227        else if (t > 1.0) t = 1.0;
1228        if (last_t > 0) t = (t + last_t) / 2;
1229        last_t = t;
1230    }
1231
1232    if (presentation_mode == PLAYING && pres_speed != 0.0) {
1233        t *= fabs(pres_speed);
1234        while (t >= next_mark_time) {
1235            t -= next_mark_time;
1236            this_mark_total = 0;
1237            PresentationMark prev_mark = next_mark;
1238            if (prev_mark.angle < 0) prev_mark.angle += 360.0;
1239            else if (prev_mark.angle >= 360.0) prev_mark.angle -= 360.0;
1240            if (pres_reverse)
1241                next_mark = m_Parent->GetPresMark(MARK_PREV);
1242            else
1243                next_mark = m_Parent->GetPresMark(MARK_NEXT);
1244            if (!next_mark.is_valid()) {
1245                SetView(prev_mark);
1246                presentation_mode = 0;
1247                delete movie;
1248                movie = NULL;
1249                break;
1250            }
1251
1252            double tmp = (pres_reverse ? prev_mark.time : next_mark.time);
1253            if (tmp > 0) {
1254                next_mark_time = tmp;
1255            } else {
1256                double d = (next_mark - prev_mark).magnitude();
1257                // FIXME: should ignore component of d which is unseen in
1258                // non-perspective mode?
1259                next_mark_time = sqrd(d / 30.0);
1260                double a = next_mark.angle - prev_mark.angle;
1261                if (a > 180.0) {
1262                    next_mark.angle -= 360.0;
1263                    a = 360.0 - a;
1264                } else if (a < -180.0) {
1265                    next_mark.angle += 360.0;
1266                    a += 360.0;
1267                } else {
1268                    a = fabs(a);
1269                }
1270                next_mark_time += sqrd(a / 60.0);
1271                double ta = fabs(next_mark.tilt_angle - prev_mark.tilt_angle);
1272                next_mark_time += sqrd(ta / 60.0);
1273                double s = fabs(log(next_mark.scale) - log(prev_mark.scale));
1274                next_mark_time += sqrd(s / 2.0);
1275                next_mark_time = sqrt(next_mark_time);
1276                // was: next_mark_time = max(max(d / 30, s / 2), max(a, ta) / 60);
1277                //printf("*** %.6f from (\nd: %.6f\ns: %.6f\na: %.6f\nt: %.6f )\n",
1278                //       next_mark_time, d/30.0, s/2.0, a/60.0, ta/60.0);
1279                if (tmp < 0) next_mark_time /= -tmp;
1280            }
1281        }
1282
1283        if (presentation_mode) {
1284            // Advance position towards next_mark
1285            double p = t / next_mark_time;
1286            double q = 1 - p;
1287            PresentationMark here = GetView();
1288            if (next_mark.angle < 0) {
1289                if (here.angle >= next_mark.angle + 360.0)
1290                    here.angle -= 360.0;
1291            } else if (next_mark.angle >= 360.0) {
1292                if (here.angle <= next_mark.angle - 360.0)
1293                    here.angle += 360.0;
1294            }
1295            here.assign(q * here + p * next_mark);
1296            here.angle = q * here.angle + p * next_mark.angle;
1297            if (here.angle < 0) here.angle += 360.0;
1298            else if (here.angle >= 360.0) here.angle -= 360.0;
1299            here.tilt_angle = q * here.tilt_angle + p * next_mark.tilt_angle;
1300            here.scale = exp(q * log(here.scale) + p * log(next_mark.scale));
1301            SetView(here);
1302            this_mark_total += t;
1303            next_mark_time -= t;
1304        }
1305    }
1306
1307    // When rotating...
1308    if (m_Rotating) {
1309        TurnCave(m_RotationStep * t);
1310    }
1311
1312    if (m_SwitchingTo == PLAN) {
1313        // When switching to plan view...
1314        TiltCave(-90.0 * t);
1315        if (m_TiltAngle == -90.0) {
1316            m_SwitchingTo = 0;
1317        }
1318    } else if (m_SwitchingTo == ELEVATION) {
1319        // When switching to elevation view...
1320        if (fabs(m_TiltAngle) < 90.0 * t) {
1321            m_SwitchingTo = 0;
1322            TiltCave(-m_TiltAngle);
1323        } else if (m_TiltAngle > 0.0) {
1324            TiltCave(-90.0 * t);
1325        } else {
1326            TiltCave(90.0 * t);
1327        }
1328    } else if (m_SwitchingTo) {
1329        int angle = (m_SwitchingTo - NORTH) * 90;
1330        double diff = angle - m_PanAngle;
1331        if (diff < -180) diff += 360;
1332        if (diff > 180) diff -= 360;
1333        double move = 90.0 * t;
1334        if (move >= fabs(diff)) {
1335            TurnCave(diff);
1336            m_SwitchingTo = 0;
1337        } else if (diff < 0) {
1338            TurnCave(-move);
1339        } else {
1340            TurnCave(move);
1341        }
1342    }
1343
1344    return true;
1345}
1346
1347// How much to allow around the box - this is because of the ring shape
1348// at one end of the line.
1349static const int HIGHLIGHTED_PT_SIZE = 2; // FIXME: tie in to blob and ring size
1350#define MARGIN (HIGHLIGHTED_PT_SIZE * 2 + 1)
1351void GfxCore::RefreshLine(const Point &a, const Point &b, const Point &c)
1352{
1353    // Best of all might be to copy the window contents before we draw the
1354    // line, then replace each time we redraw.
1355
1356    // Calculate the minimum rectangle which includes the old and new
1357    // measuring lines to minimise the redraw time
1358    int l = INT_MAX, r = INT_MIN, u = INT_MIN, d = INT_MAX;
1359    double X, Y, Z;
1360    if (a.IsValid()) {
1361        if (!Transform(a, &X, &Y, &Z)) {
1362            printf("oops\n");
1363        } else {
1364            int x = int(X);
1365            int y = GetYSize() - 1 - int(Y);
1366            l = x - MARGIN;
1367            r = x + MARGIN;
1368            u = y + MARGIN;
1369            d = y - MARGIN;
1370        }
1371    }
1372    if (b.IsValid()) {
1373        if (!Transform(b, &X, &Y, &Z)) {
1374            printf("oops\n");
1375        } else {
1376            int x = int(X);
1377            int y = GetYSize() - 1 - int(Y);
1378            l = min(l, x - MARGIN);
1379            r = max(r, x + MARGIN);
1380            u = max(u, y + MARGIN);
1381            d = min(d, y - MARGIN);
1382        }
1383    }
1384    if (c.IsValid()) {
1385        if (!Transform(c, &X, &Y, &Z)) {
1386            printf("oops\n");
1387        } else {
1388            int x = int(X);
1389            int y = GetYSize() - 1 - int(Y);
1390            l = min(l, x - MARGIN);
1391            r = max(r, x + MARGIN);
1392            u = max(u, y + MARGIN);
1393            d = min(d, y - MARGIN);
1394        }
1395    }
1396    const wxRect R(l, d, r - l, u - d);
1397    Refresh(false, &R);
1398}
1399
1400void GfxCore::SetHere()
1401{
1402    if (!m_here.IsValid()) return;
1403    bool line_active = MeasuringLineActive();
1404    Point old = m_here;
1405    m_here.Invalidate();
1406    if (line_active || MeasuringLineActive())
1407        RefreshLine(old, m_there, m_here);
1408}
1409
1410void GfxCore::SetHere(const Point &p)
1411{
1412    bool line_active = MeasuringLineActive();
1413    Point old = m_here;
1414    m_here = p;
1415    if (line_active || MeasuringLineActive())
1416        RefreshLine(old, m_there, m_here);
1417    m_here_is_temporary = false;
1418}
1419
1420void GfxCore::SetThere()
1421{
1422    if (!m_there.IsValid()) return;
1423    Point old = m_there;
1424    m_there.Invalidate();
1425    RefreshLine(m_here, old, m_there);
1426}
1427
1428void GfxCore::SetThere(const Point &p)
1429{
1430    Point old = m_there;
1431    m_there = p;
1432    RefreshLine(m_here, old, m_there);
1433}
1434
1435void GfxCore::CreateHitTestGrid()
1436{
1437    // Clear hit-test grid.
1438    for (int i = 0; i < HITTEST_SIZE * HITTEST_SIZE; i++) {
1439        m_PointGrid[i].clear();
1440    }
1441
1442    // Fill the grid.
1443    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1444    list<LabelInfo*>::const_iterator end = m_Parent->GetLabelsEnd();
1445    while (pos != end) {
1446        LabelInfo* label = *pos++;
1447
1448        if (!((m_Surface && label->IsSurface()) ||
1449              (m_Legs && label->IsUnderground()) ||
1450              (!label->IsSurface() && !label->IsUnderground()))) {
1451            // if this station isn't to be displayed, skip to the next
1452            // (last case is for stns with no legs attached)
1453            continue;
1454        }
1455
1456        // Calculate screen coordinates.
1457        double cx, cy, cz;
1458        Transform(*label, &cx, &cy, &cz);
1459        if (cx < 0 || cx >= GetXSize()) continue;
1460        if (cy < 0 || cy >= GetYSize()) continue;
1461
1462        cy = GetYSize() - cy;
1463
1464        // On-screen, so add to hit-test grid...
1465        int grid_x = int((cx * (HITTEST_SIZE - 1)) / GetXSize());
1466        int grid_y = int((cy * (HITTEST_SIZE - 1)) / GetYSize());
1467
1468        m_PointGrid[grid_x + grid_y * HITTEST_SIZE].push_back(label);
1469    }
1470
1471    m_HitTestGridValid = true;
1472}
1473
1474//
1475//  Methods for controlling the orientation of the survey
1476//
1477
1478void GfxCore::TurnCave(Double angle)
1479{
1480    // Turn the cave around its z-axis by a given angle.
1481
1482    m_PanAngle += angle;
1483    if (m_PanAngle >= 360.0) {
1484        m_PanAngle -= 360.0;
1485    } else if (m_PanAngle < 0.0) {
1486        m_PanAngle += 360.0;
1487    }
1488
1489    m_HitTestGridValid = false;
1490    if (m_here_is_temporary) SetHere();
1491
1492    SetRotation(m_PanAngle, m_TiltAngle);
1493}
1494
1495void GfxCore::TurnCaveTo(Double angle)
1496{
1497    timer.Start(drawtime);
1498    int new_switching_to = ((int)angle) / 90 + NORTH;
1499    if (new_switching_to == m_SwitchingTo) {
1500        // A second order to switch takes us there right away
1501        TurnCave(angle - m_PanAngle);
1502        m_SwitchingTo = 0;
1503        ForceRefresh();
1504    } else {
1505        m_SwitchingTo = new_switching_to;
1506    }
1507}
1508
1509void GfxCore::TiltCave(Double tilt_angle)
1510{
1511    // Tilt the cave by a given angle.
1512    if (m_TiltAngle + tilt_angle > 90.0) {
1513        m_TiltAngle = 90.0;
1514    } else if (m_TiltAngle + tilt_angle < -90.0) {
1515        m_TiltAngle = -90.0;
1516    } else {
1517        m_TiltAngle += tilt_angle;
1518    }
1519
1520    m_HitTestGridValid = false;
1521    if (m_here_is_temporary) SetHere();
1522
1523    SetRotation(m_PanAngle, m_TiltAngle);
1524}
1525
1526void GfxCore::TranslateCave(int dx, int dy)
1527{
1528    AddTranslationScreenCoordinates(dx, dy);
1529    m_HitTestGridValid = false;
1530
1531    if (m_here_is_temporary) SetHere();
1532
1533    ForceRefresh();
1534}
1535
1536void GfxCore::DragFinished()
1537{
1538    m_MouseOutsideCompass = m_MouseOutsideElev = false;
1539    ForceRefresh();
1540}
1541
1542void GfxCore::ClearCoords()
1543{
1544    m_Parent->ClearCoords();
1545}
1546
1547void GfxCore::SetCoords(wxPoint point)
1548{
1549    // We can't work out 2D coordinates from a perspective view, and it
1550    // doesn't really make sense to show coordinates while we're animating.
1551    if (GetPerspective() || Animating()) return;
1552
1553    // Update the coordinate or altitude display, given the (x, y) position in
1554    // window coordinates.  The relevant display is updated depending on
1555    // whether we're in plan or elevation view.
1556
1557    double cx, cy, cz;
1558
1559    SetDataTransform();
1560    ReverseTransform(point.x, GetYSize() - 1 - point.y, &cx, &cy, &cz);
1561
1562    if (ShowingPlan()) {
1563        m_Parent->SetCoords(cx + m_Parent->GetOffset().GetX(),
1564                            cy + m_Parent->GetOffset().GetY());
1565    } else if (ShowingElevation()) {
1566        m_Parent->SetAltitude(cz + m_Parent->GetOffset().GetZ());
1567    } else {
1568        m_Parent->ClearCoords();
1569    }
1570}
1571
1572int GfxCore::GetCompassXPosition() const
1573{
1574    // Return the x-coordinate of the centre of the compass in window
1575    // coordinates.
1576    return GetXSize() - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE / 2;
1577}
1578
1579int GfxCore::GetClinoXPosition() const
1580{
1581    // Return the x-coordinate of the centre of the compass in window
1582    // coordinates.
1583    return GetXSize() - GetClinoOffset() - INDICATOR_BOX_SIZE / 2;
1584}
1585
1586int GfxCore::GetIndicatorYPosition() const
1587{
1588    // Return the y-coordinate of the centre of the indicators in window
1589    // coordinates.
1590    return GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE / 2;
1591}
1592
1593int GfxCore::GetIndicatorRadius() const
1594{
1595    // Return the radius of each indicator.
1596    return (INDICATOR_BOX_SIZE - INDICATOR_MARGIN * 2) / 2;
1597}
1598
1599bool GfxCore::PointWithinCompass(wxPoint point) const
1600{
1601    // Determine whether a point (in window coordinates) lies within the
1602    // compass.
1603    if (!ShowingCompass()) return false;
1604
1605    glaCoord dx = point.x - GetCompassXPosition();
1606    glaCoord dy = point.y - GetIndicatorYPosition();
1607    glaCoord radius = GetIndicatorRadius();
1608
1609    return (dx * dx + dy * dy <= radius * radius);
1610}
1611
1612bool GfxCore::PointWithinClino(wxPoint point) const
1613{
1614    // Determine whether a point (in window coordinates) lies within the clino.
1615    if (!ShowingClino()) return false;
1616
1617    glaCoord dx = point.x - GetClinoXPosition();
1618    glaCoord dy = point.y - GetIndicatorYPosition();
1619    glaCoord radius = GetIndicatorRadius();
1620
1621    return (dx * dx + dy * dy <= radius * radius);
1622}
1623
1624bool GfxCore::PointWithinScaleBar(wxPoint point) const
1625{
1626    // Determine whether a point (in window coordinates) lies within the scale
1627    // bar.
1628    if (!ShowingScaleBar()) return false;
1629
1630    return (point.x >= SCALE_BAR_OFFSET_X &&
1631            point.x <= SCALE_BAR_OFFSET_X + m_ScaleBarWidth &&
1632            point.y <= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT &&
1633            point.y >= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT*2);
1634}
1635
1636bool GfxCore::PointWithinColourKey(wxPoint point) const
1637{
1638    // Determine whether a point (in window coordinates) lies within the key.
1639    point.x -= GetXSize() - KEY_OFFSET_X;
1640    point.y = KEY_OFFSET_Y - point.y;
1641    return (point.x >= key_lowerleft[m_ColourBy].x && point.x <= 0 &&
1642            point.y >= key_lowerleft[m_ColourBy].y && point.y <= 0);
1643}
1644
1645void GfxCore::SetCompassFromPoint(wxPoint point)
1646{
1647    // Given a point in window coordinates, set the heading of the survey.  If
1648    // the point is outside the compass, it snaps to 45 degree intervals;
1649    // otherwise it operates as normal.
1650
1651    wxCoord dx = point.x - GetCompassXPosition();
1652    wxCoord dy = point.y - GetIndicatorYPosition();
1653    wxCoord radius = GetIndicatorRadius();
1654
1655    double angle = deg(atan2(double(dx), double(dy))) - 180.0;
1656    if (dx * dx + dy * dy <= radius * radius) {
1657        TurnCave(angle - m_PanAngle);
1658        m_MouseOutsideCompass = false;
1659    } else {
1660        TurnCave(int(angle / 45.0) * 45.0 - m_PanAngle);
1661        m_MouseOutsideCompass = true;
1662    }
1663
1664    ForceRefresh();
1665}
1666
1667void GfxCore::SetClinoFromPoint(wxPoint point)
1668{
1669    // Given a point in window coordinates, set the elevation of the survey.
1670    // If the point is outside the clino, it snaps to 90 degree intervals;
1671    // otherwise it operates as normal.
1672
1673    glaCoord dx = point.x - GetClinoXPosition();
1674    glaCoord dy = point.y - GetIndicatorYPosition();
1675    glaCoord radius = GetIndicatorRadius();
1676
1677    if (dx >= 0 && dx * dx + dy * dy <= radius * radius) {
1678        TiltCave(-deg(atan2(double(dy), double(dx))) - m_TiltAngle);
1679        m_MouseOutsideElev = false;
1680    } else if (dy >= INDICATOR_MARGIN) {
1681        TiltCave(-90.0 - m_TiltAngle);
1682        m_MouseOutsideElev = true;
1683    } else if (dy <= -INDICATOR_MARGIN) {
1684        TiltCave(90.0 - m_TiltAngle);
1685        m_MouseOutsideElev = true;
1686    } else {
1687        TiltCave(-m_TiltAngle);
1688        m_MouseOutsideElev = true;
1689    }
1690
1691    ForceRefresh();
1692}
1693
1694void GfxCore::SetScaleBarFromOffset(wxCoord dx)
1695{
1696    // Set the scale of the survey, given an offset as to how much the mouse has
1697    // been dragged over the scalebar since the last scale change.
1698
1699    SetScale((m_ScaleBarWidth + dx) * m_Scale / m_ScaleBarWidth);
1700    ForceRefresh();
1701}
1702
1703void GfxCore::RedrawIndicators()
1704{
1705    // Redraw the compass and clino indicators.
1706
1707    const wxRect r(GetXSize() - INDICATOR_OFFSET_X - INDICATOR_BOX_SIZE*2 -
1708                     INDICATOR_GAP,
1709                   GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE,
1710                   INDICATOR_BOX_SIZE*2 + INDICATOR_GAP,
1711                   INDICATOR_BOX_SIZE);
1712
1713    Refresh(false, &r);
1714}
1715
1716void GfxCore::StartRotation()
1717{
1718    // Start the survey rotating.
1719
1720    m_Rotating = true;
1721    timer.Start(drawtime);
1722}
1723
1724void GfxCore::ToggleRotation()
1725{
1726    // Toggle the survey rotation on/off.
1727
1728    if (m_Rotating) {
1729        StopRotation();
1730    } else {
1731        StartRotation();
1732    }
1733}
1734
1735void GfxCore::StopRotation()
1736{
1737    // Stop the survey rotating.
1738
1739    m_Rotating = false;
1740    ForceRefresh();
1741}
1742
1743bool GfxCore::IsExtendedElevation() const
1744{
1745    return m_Parent->IsExtendedElevation();
1746}
1747
1748void GfxCore::ReverseRotation()
1749{
1750    // Reverse the direction of rotation.
1751
1752    m_RotationStep = -m_RotationStep;
1753}
1754
1755void GfxCore::RotateSlower(bool accel)
1756{
1757    // Decrease the speed of rotation, optionally by an increased amount.
1758
1759    m_RotationStep /= accel ? 1.44 : 1.2;
1760    if (m_RotationStep < 1.0) {
1761        m_RotationStep = 1.0;
1762    }
1763}
1764
1765void GfxCore::RotateFaster(bool accel)
1766{
1767    // Increase the speed of rotation, optionally by an increased amount.
1768
1769    m_RotationStep *= accel ? 1.44 : 1.2;
1770    if (m_RotationStep > 450.0) {
1771        m_RotationStep = 450.0;
1772    }
1773}
1774
1775void GfxCore::SwitchToElevation()
1776{
1777    // Perform an animated switch to elevation view.
1778
1779    switch (m_SwitchingTo) {
1780        case 0:
1781            timer.Start(drawtime);
1782            m_SwitchingTo = ELEVATION;
1783            break;
1784
1785        case PLAN:
1786            m_SwitchingTo = ELEVATION;
1787            break;
1788
1789        case ELEVATION:
1790            // a second order to switch takes us there right away
1791            TiltCave(-m_TiltAngle);
1792            m_SwitchingTo = 0;
1793            ForceRefresh();
1794    }
1795}
1796
1797void GfxCore::SwitchToPlan()
1798{
1799    // Perform an animated switch to plan view.
1800
1801    switch (m_SwitchingTo) {
1802        case 0:
1803            timer.Start(drawtime);
1804            m_SwitchingTo = PLAN;
1805            break;
1806
1807        case ELEVATION:
1808            m_SwitchingTo = PLAN;
1809            break;
1810
1811        case PLAN:
1812            // A second order to switch takes us there right away
1813            TiltCave(-90.0 - m_TiltAngle);
1814            m_SwitchingTo = 0;
1815            ForceRefresh();
1816    }
1817}
1818
1819void GfxCore::SetViewTo(Double xmin, Double xmax, Double ymin, Double ymax, Double zmin, Double zmax)
1820{
1821
1822    SetTranslation(-Vector3((xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2));
1823    Double scale = HUGE_VAL;
1824    const Vector3 ext = m_Parent->GetExtent();
1825    if (xmax > xmin) {
1826        Double s = ext.GetX() / (xmax - xmin);
1827        if (s < scale) scale = s;
1828    }
1829    if (ymax > ymin) {
1830        Double s = ext.GetY() / (ymax - ymin);
1831        if (s < scale) scale = s;
1832    }
1833    if (!ShowingPlan() && zmax > zmin) {
1834        Double s = ext.GetZ() / (zmax - zmin);
1835        if (s < scale) scale = s;
1836    }
1837    if (scale != HUGE_VAL) SetScale(scale);
1838    ForceRefresh();
1839}
1840
1841bool GfxCore::CanRaiseViewpoint() const
1842{
1843    // Determine if the survey can be viewed from a higher angle of elevation.
1844
1845    return GetPerspective() ? (m_TiltAngle < 90.0) : (m_TiltAngle > -90.0);
1846}
1847
1848bool GfxCore::CanLowerViewpoint() const
1849{
1850    // Determine if the survey can be viewed from a lower angle of elevation.
1851
1852    return GetPerspective() ? (m_TiltAngle > -90.0) : (m_TiltAngle < 90.0);
1853}
1854
1855bool GfxCore::HasDepth() const
1856{
1857    return m_Parent->GetDepthExtent() == 0.0;
1858}
1859
1860bool GfxCore::HasErrorInformation() const
1861{
1862    return m_Parent->HasErrorInformation();
1863}
1864
1865bool GfxCore::HasDateInformation() const
1866{
1867    return m_Parent->GetDateMin() >= 0;
1868}
1869
1870bool GfxCore::ShowingPlan() const
1871{
1872    // Determine if the survey is in plan view.
1873
1874    return (m_TiltAngle == -90.0);
1875}
1876
1877bool GfxCore::ShowingElevation() const
1878{
1879    // Determine if the survey is in elevation view.
1880
1881    return (m_TiltAngle == 0.0);
1882}
1883
1884bool GfxCore::ShowingMeasuringLine() const
1885{
1886    // Determine if the measuring line is being shown.
1887
1888    return (m_there.IsValid() && m_here.IsValid());
1889}
1890
1891void GfxCore::ToggleFlag(bool* flag, int update)
1892{
1893    *flag = !*flag;
1894    if (update == UPDATE_BLOBS) {
1895        UpdateBlobs();
1896    } else if (update == UPDATE_BLOBS_AND_CROSSES) {
1897        UpdateBlobs();
1898        InvalidateList(LIST_CROSSES);
1899    }
1900    ForceRefresh();
1901}
1902
1903int GfxCore::GetNumEntrances() const
1904{
1905    return m_Parent->GetNumEntrances();
1906}
1907
1908int GfxCore::GetNumFixedPts() const
1909{
1910    return m_Parent->GetNumFixedPts();
1911}
1912
1913int GfxCore::GetNumExportedPts() const
1914{
1915    return m_Parent->GetNumExportedPts();
1916}
1917
1918void GfxCore::ClearTreeSelection()
1919{
1920    m_Parent->ClearTreeSelection();
1921}
1922
1923void GfxCore::CentreOn(const Point &p)
1924{
1925    SetTranslation(-p);
1926    m_HitTestGridValid = false;
1927
1928    ForceRefresh();
1929}
1930
1931void GfxCore::ForceRefresh()
1932{
1933    Refresh(false);
1934}
1935
1936void GfxCore::GenerateList(unsigned int l)
1937{
1938    assert(m_HaveData);
1939
1940    switch (l) {
1941        case LIST_COMPASS:
1942            DrawCompass();
1943            break;
1944        case LIST_CLINO:
1945            DrawClino();
1946            break;
1947        case LIST_CLINO_BACK:
1948            DrawClinoBack();
1949            break;
1950        case LIST_SCALE_BAR:
1951            DrawScaleBar();
1952            break;
1953        case LIST_DEPTH_KEY:
1954            DrawDepthKey();
1955            break;
1956        case LIST_DATE_KEY:
1957            DrawDateKey();
1958            break;
1959        case LIST_ERROR_KEY:
1960            DrawErrorKey();
1961            break;
1962        case LIST_UNDERGROUND_LEGS:
1963            GenerateDisplayList();
1964            break;
1965        case LIST_TUBES:
1966            GenerateDisplayListTubes();
1967            break;
1968        case LIST_SURFACE_LEGS:
1969            GenerateDisplayListSurface();
1970            break;
1971        case LIST_BLOBS:
1972            GenerateBlobsDisplayList();
1973            break;
1974        case LIST_CROSSES: {
1975            BeginCrosses();
1976            SetColour(col_LIGHT_GREY);
1977            list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1978            while (pos != m_Parent->GetLabelsEnd()) {
1979                const LabelInfo* label = *pos++;
1980
1981                if ((m_Surface && label->IsSurface()) ||
1982                    (m_Legs && label->IsUnderground()) ||
1983                    (!label->IsSurface() && !label->IsUnderground())) {
1984                    // Check if this station should be displayed
1985                    // (last case is for stns with no legs attached)
1986                    DrawCross(label->GetX(), label->GetY(), label->GetZ());
1987                }
1988            }
1989            EndCrosses();
1990            break;
1991        }
1992        case LIST_GRID:
1993            DrawGrid();
1994            break;
1995        case LIST_SHADOW:
1996            GenerateDisplayListShadow();
1997            break;
1998        default:
1999            assert(false);
2000            break;
2001    }
2002}
2003
2004void GfxCore::ToggleSmoothShading()
2005{
2006    GLACanvas::ToggleSmoothShading();
2007    InvalidateList(LIST_TUBES);
2008    ForceRefresh();
2009}
2010
2011void GfxCore::GenerateDisplayList()
2012{
2013    // Generate the display list for the underground legs.
2014    list<traverse>::const_iterator trav = m_Parent->traverses_begin();
2015    list<traverse>::const_iterator tend = m_Parent->traverses_end();
2016    while (trav != tend) {
2017        (this->*AddPoly)(*trav);
2018        ++trav;
2019    }
2020}
2021
2022void GfxCore::GenerateDisplayListTubes()
2023{
2024    // Generate the display list for the tubes.
2025    list<vector<XSect> >::const_iterator trav = m_Parent->tubes_begin();
2026    list<vector<XSect> >::const_iterator tend = m_Parent->tubes_end();
2027    while (trav != tend) {
2028        SkinPassage(*trav);
2029        ++trav;
2030    }
2031}
2032
2033void GfxCore::GenerateDisplayListSurface()
2034{
2035    // Generate the display list for the surface legs.
2036    EnableDashedLines();
2037    list<traverse>::const_iterator trav = m_Parent->surface_traverses_begin();
2038    list<traverse>::const_iterator tend = m_Parent->surface_traverses_end();
2039    while (trav != tend) {
2040        if (m_ColourBy == COLOUR_BY_ERROR) {
2041            AddPolylineError(*trav);
2042        } else {
2043            AddPolyline(*trav);
2044        }
2045        ++trav;
2046    }
2047    DisableDashedLines();
2048}
2049
2050void GfxCore::GenerateDisplayListShadow()
2051{
2052    SetColour(col_BLACK);
2053    list<traverse>::const_iterator trav = m_Parent->traverses_begin();
2054    list<traverse>::const_iterator tend = m_Parent->traverses_end();
2055    while (trav != tend) {
2056        AddPolylineShadow(*trav);
2057        ++trav;
2058    }
2059}
2060
2061// Plot blobs.
2062void GfxCore::GenerateBlobsDisplayList()
2063{
2064    if (!(m_Entrances || m_FixedPts || m_ExportedPts ||
2065          m_Parent->GetNumHighlightedPts()))
2066        return;
2067
2068    // Plot blobs.
2069    gla_colour prev_col = col_BLACK; // not a colour used for blobs
2070    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2071    BeginBlobs();
2072    while (pos != m_Parent->GetLabelsEnd()) {
2073        const LabelInfo* label = *pos++;
2074
2075        // When more than one flag is set on a point:
2076        // search results take priority over entrance highlighting
2077        // which takes priority over fixed point
2078        // highlighting, which in turn takes priority over exported
2079        // point highlighting.
2080
2081        if (!((m_Surface && label->IsSurface()) ||
2082              (m_Legs && label->IsUnderground()) ||
2083              (!label->IsSurface() && !label->IsUnderground()))) {
2084            // if this station isn't to be displayed, skip to the next
2085            // (last case is for stns with no legs attached)
2086            continue;
2087        }
2088
2089        gla_colour col;
2090
2091        if (label->IsHighLighted()) {
2092            col = col_YELLOW;
2093        } else if (m_Entrances && label->IsEntrance()) {
2094            col = col_GREEN;
2095        } else if (m_FixedPts && label->IsFixedPt()) {
2096            col = col_RED;
2097        } else if (m_ExportedPts && label->IsExportedPt()) {
2098            col = col_TURQUOISE;
2099        } else {
2100            continue;
2101        }
2102
2103        // Stations are sorted by blob type, so colour changes are infrequent.
2104        if (col != prev_col) {
2105            SetColour(col);
2106            prev_col = col;
2107        }
2108        DrawBlob(label->GetX(), label->GetY(), label->GetZ());
2109    }
2110    EndBlobs();
2111}
2112
2113void GfxCore::DrawIndicators()
2114{
2115    // Draw colour key.
2116    if (m_ColourKey) {
2117        if (m_ColourBy == COLOUR_BY_DEPTH) {
2118            DrawList2D(LIST_DEPTH_KEY, GetXSize() - KEY_OFFSET_X,
2119                       GetYSize() - KEY_OFFSET_Y, 0);
2120        } else if (m_ColourBy == COLOUR_BY_DATE) {
2121            DrawList2D(LIST_DATE_KEY, GetXSize() - KEY_OFFSET_X,
2122                       GetYSize() - KEY_OFFSET_Y, 0);
2123        } else if (m_ColourBy == COLOUR_BY_ERROR) {
2124            DrawList2D(LIST_ERROR_KEY, GetXSize() - KEY_OFFSET_X,
2125                       GetYSize() - KEY_OFFSET_Y, 0);
2126        }
2127    }
2128
2129    // Draw compass or elevation/heading indicators.
2130    if (m_Compass || m_Clino) {
2131        if (!m_Parent->IsExtendedElevation()) Draw2dIndicators();
2132    }
2133
2134    // Draw scalebar.
2135    if (m_Scalebar) {
2136        DrawList2D(LIST_SCALE_BAR, 0, 0, 0);
2137    }
2138}
2139
2140void GfxCore::PlaceVertexWithColour(const Vector3 & v,
2141                                    glaTexCoord tex_x, glaTexCoord tex_y,
2142                                    Double factor)
2143{
2144    SetColour(GetSurfacePen(), factor); // FIXME : assumes surface pen is white!
2145    PlaceVertex(v, tex_x, tex_y);
2146}
2147
2148void GfxCore::SetDepthColour(Double z, Double factor) {
2149    // Set the drawing colour based on the altitude.
2150    Double z_ext = m_Parent->GetDepthExtent();
2151
2152    z -= m_Parent->GetDepthMin();
2153    // points arising from tubes may be slightly outside the limits...
2154    if (z < 0) z = 0;
2155    if (z > z_ext) z = z_ext;
2156
2157    if (z == 0) {
2158        SetColour(GetPen(0), factor);
2159        return;
2160    }
2161
2162    assert(z_ext > 0.0);
2163    Double how_far = z / z_ext;
2164    assert(how_far >= 0.0);
2165    assert(how_far <= 1.0);
2166
2167    int band = int(floor(how_far * (GetNumColourBands() - 1)));
2168    GLAPen pen1 = GetPen(band);
2169    if (band < GetNumColourBands() - 1) {
2170        const GLAPen& pen2 = GetPen(band + 1);
2171
2172        Double interval = z_ext / (GetNumColourBands() - 1);
2173        Double into_band = z / interval - band;
2174
2175//      printf("%g z_offset=%g interval=%g band=%d\n", into_band,
2176//             z_offset, interval, band);
2177        // FIXME: why do we need to clamp here?  Is it because the walls can
2178        // extend further up/down than the centre-line?
2179        if (into_band < 0.0) into_band = 0.0;
2180        if (into_band > 1.0) into_band = 1.0;
2181        assert(into_band >= 0.0);
2182        assert(into_band <= 1.0);
2183
2184        pen1.Interpolate(pen2, into_band);
2185    }
2186    SetColour(pen1, factor);
2187}
2188
2189void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v, Double factor)
2190{
2191    SetDepthColour(v.GetZ(), factor);
2192    PlaceVertex(v);
2193}
2194
2195void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v,
2196                                         glaTexCoord tex_x, glaTexCoord tex_y,
2197                                         Double factor)
2198{
2199    SetDepthColour(v.GetZ(), factor);
2200    PlaceVertex(v, tex_x, tex_y);
2201}
2202
2203void GfxCore::SplitLineAcrossBands(int band, int band2,
2204                                   const Vector3 &p, const Vector3 &q,
2205                                   Double factor)
2206{
2207    const int step = (band < band2) ? 1 : -1;
2208    for (int i = band; i != band2; i += step) {
2209        const Double z = GetDepthBoundaryBetweenBands(i, i + step);
2210
2211        // Find the intersection point of the line p -> q
2212        // with the plane parallel to the xy-plane with z-axis intersection z.
2213        assert(q.GetZ() - p.GetZ() != 0.0);
2214
2215        const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
2216//      assert(0.0 <= t && t <= 1.0);           FIXME: rounding problems!
2217
2218        const Double x = p.GetX() + t * (q.GetX() - p.GetX());
2219        const Double y = p.GetY() + t * (q.GetY() - p.GetY());
2220
2221        PlaceVertexWithDepthColour(Vector3(x, y, z), factor);
2222    }
2223}
2224
2225int GfxCore::GetDepthColour(Double z) const
2226{
2227    // Return the (0-based) depth colour band index for a z-coordinate.
2228    Double z_ext = m_Parent->GetDepthExtent();
2229    z -= m_Parent->GetDepthMin();
2230    // We seem to get rounding differences causing z to sometimes be slightly
2231    // less than GetDepthMin() here, and it can certainly be true for passage
2232    // tubes, so just clamp the value to 0.
2233    if (z <= 0) return 0;
2234    assert(z_ext > 0.0);
2235    // We seem to get rounding differences causing z to sometimes exceed z_ext
2236    // by a small amount here (see: http://trac.survex.com/ticket/26) and it
2237    // can certainly be true for passage tubes, so just clamp the value.
2238    if (z >= z_ext) return GetNumColourBands() - 1;
2239    return int(z / z_ext * (GetNumColourBands() - 1));
2240}
2241
2242Double GfxCore::GetDepthBoundaryBetweenBands(int a, int b) const
2243{
2244    // Return the z-coordinate of the depth colour boundary between
2245    // two adjacent depth colour bands (specified by 0-based indices).
2246
2247    assert((a == b - 1) || (a == b + 1));
2248    if (GetNumColourBands() == 1) return 0;
2249
2250    int band = (a > b) ? a : b; // boundary N lies on the bottom of band N.
2251    Double z_ext = m_Parent->GetDepthExtent();
2252    return (z_ext * band / (GetNumColourBands() - 1)) + m_Parent->GetDepthMin();
2253}
2254
2255void GfxCore::AddPolyline(const traverse & centreline)
2256{
2257    BeginPolyline();
2258    SetColour(GetSurfacePen());
2259    vector<PointInfo>::const_iterator i = centreline.begin();
2260    PlaceVertex(*i);
2261    ++i;
2262    while (i != centreline.end()) {
2263        PlaceVertex(*i);
2264        ++i;
2265    }
2266    EndPolyline();
2267}
2268
2269void GfxCore::AddPolylineShadow(const traverse & centreline)
2270{
2271    BeginPolyline();
2272    const double z = -0.5 * m_Parent->GetZExtent();
2273    vector<PointInfo>::const_iterator i = centreline.begin();
2274    PlaceVertex(i->GetX(), i->GetY(), z);
2275    ++i;
2276    while (i != centreline.end()) {
2277        PlaceVertex(i->GetX(), i->GetY(), z);
2278        ++i;
2279    }
2280    EndPolyline();
2281}
2282
2283void GfxCore::AddPolylineDepth(const traverse & centreline)
2284{
2285    BeginPolyline();
2286    vector<PointInfo>::const_iterator i, prev_i;
2287    i = centreline.begin();
2288    int band0 = GetDepthColour(i->GetZ());
2289    PlaceVertexWithDepthColour(*i);
2290    prev_i = i;
2291    ++i;
2292    while (i != centreline.end()) {
2293        int band = GetDepthColour(i->GetZ());
2294        if (band != band0) {
2295            SplitLineAcrossBands(band0, band, *prev_i, *i);
2296            band0 = band;
2297        }
2298        PlaceVertexWithDepthColour(*i);
2299        prev_i = i;
2300        ++i;
2301    }
2302    EndPolyline();
2303}
2304
2305void GfxCore::AddQuadrilateral(const Vector3 &a, const Vector3 &b,
2306                               const Vector3 &c, const Vector3 &d)
2307{
2308    Vector3 normal = (a - c) * (d - b);
2309    normal.normalise();
2310    Double factor = dot(normal, light) * .3 + .7;
2311    glaTexCoord w(ceil(((b - a).magnitude() + (d - c).magnitude()) * .5));
2312    glaTexCoord h(ceil(((b - c).magnitude() + (d - a).magnitude()) * .5));
2313    // FIXME: should plot triangles instead to avoid rendering glitches.
2314    BeginQuadrilaterals();
2315    PlaceVertexWithColour(a, 0, 0, factor);
2316    PlaceVertexWithColour(b, w, 0, factor);
2317    PlaceVertexWithColour(c, w, h, factor);
2318    PlaceVertexWithColour(d, 0, h, factor);
2319    EndQuadrilaterals();
2320}
2321
2322void GfxCore::AddQuadrilateralDepth(const Vector3 &a, const Vector3 &b,
2323                                    const Vector3 &c, const Vector3 &d)
2324{
2325    Vector3 normal = (a - c) * (d - b);
2326    normal.normalise();
2327    Double factor = dot(normal, light) * .3 + .7;
2328    int a_band, b_band, c_band, d_band;
2329    a_band = GetDepthColour(a.GetZ());
2330    a_band = min(max(a_band, 0), GetNumColourBands());
2331    b_band = GetDepthColour(b.GetZ());
2332    b_band = min(max(b_band, 0), GetNumColourBands());
2333    c_band = GetDepthColour(c.GetZ());
2334    c_band = min(max(c_band, 0), GetNumColourBands());
2335    d_band = GetDepthColour(d.GetZ());
2336    d_band = min(max(d_band, 0), GetNumColourBands());
2337    // All this splitting is incorrect - we need to make a separate polygon
2338    // for each depth band...
2339    glaTexCoord w(ceil(((b - a).magnitude() + (d - c).magnitude()) * .5));
2340    glaTexCoord h(ceil(((b - c).magnitude() + (d - a).magnitude()) * .5));
2341    BeginPolygon();
2342////    PlaceNormal(normal);
2343    PlaceVertexWithDepthColour(a, 0, 0, factor);
2344    if (a_band != b_band) {
2345        SplitLineAcrossBands(a_band, b_band, a, b, factor);
2346    }
2347    PlaceVertexWithDepthColour(b, w, 0, factor);
2348    if (b_band != c_band) {
2349        SplitLineAcrossBands(b_band, c_band, b, c, factor);
2350    }
2351    PlaceVertexWithDepthColour(c, w, h, factor);
2352    if (c_band != d_band) {
2353        SplitLineAcrossBands(c_band, d_band, c, d, factor);
2354    }
2355    PlaceVertexWithDepthColour(d, 0, h, factor);
2356    if (d_band != a_band) {
2357        SplitLineAcrossBands(d_band, a_band, d, a, factor);
2358    }
2359    EndPolygon();
2360}
2361
2362void GfxCore::SetColourFromDate(int date, Double factor)
2363{
2364    // Set the drawing colour based on a date.
2365
2366    if (date == -1) {
2367        // Undated.
2368        SetColour(GetSurfacePen(), factor);
2369        return;
2370    }
2371
2372    int date_offset = date - m_Parent->GetDateMin();
2373    if (date_offset == 0) {
2374        // Earliest date - handle as a special case for the single date case.
2375        SetColour(GetPen(0), factor);
2376        return;
2377    }
2378
2379    int date_ext = m_Parent->GetDateExtent();
2380    Double how_far = (Double)date_offset / date_ext;
2381    assert(how_far >= 0.0);
2382    assert(how_far <= 1.0);
2383
2384    int band = int(floor(how_far * (GetNumColourBands() - 1)));
2385    GLAPen pen1 = GetPen(band);
2386    if (band < GetNumColourBands() - 1) {
2387        const GLAPen& pen2 = GetPen(band + 1);
2388
2389        Double interval = date_ext / (GetNumColourBands() - 1);
2390        Double into_band = date_offset / interval - band;
2391
2392//      printf("%g z_offset=%g interval=%g band=%d\n", into_band,
2393//             z_offset, interval, band);
2394        // FIXME: why do we need to clamp here?  Is it because the walls can
2395        // extend further up/down than the centre-line?
2396        if (into_band < 0.0) into_band = 0.0;
2397        if (into_band > 1.0) into_band = 1.0;
2398        assert(into_band >= 0.0);
2399        assert(into_band <= 1.0);
2400
2401        pen1.Interpolate(pen2, into_band);
2402    }
2403    SetColour(pen1, factor);
2404}
2405
2406void GfxCore::AddPolylineDate(const traverse & centreline)
2407{
2408    BeginPolyline();
2409    vector<PointInfo>::const_iterator i, prev_i;
2410    i = centreline.begin();
2411    int date = i->GetDate();
2412    SetColourFromDate(date, 1.0);
2413    PlaceVertex(*i);
2414    prev_i = i;
2415    while (++i != centreline.end()) {
2416        int newdate = i->GetDate();
2417        if (newdate != date) {
2418            EndPolyline();
2419            BeginPolyline();
2420            date = newdate;
2421            SetColourFromDate(date, 1.0);
2422            PlaceVertex(*prev_i);
2423        }
2424        PlaceVertex(*i);
2425        prev_i = i;
2426    }
2427    EndPolyline();
2428}
2429
2430static int static_date_hack; // FIXME
2431
2432void GfxCore::AddQuadrilateralDate(const Vector3 &a, const Vector3 &b,
2433                                   const Vector3 &c, const Vector3 &d)
2434{
2435    Vector3 normal = (a - c) * (d - b);
2436    normal.normalise();
2437    Double factor = dot(normal, light) * .3 + .7;
2438    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
2439    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
2440    // FIXME: should plot triangles instead to avoid rendering glitches.
2441    BeginQuadrilaterals();
2442////    PlaceNormal(normal);
2443    SetColourFromDate(static_date_hack, factor);
2444    PlaceVertex(a, 0, 0);
2445    PlaceVertex(b, w, 0);
2446    PlaceVertex(c, w, h);
2447    PlaceVertex(d, 0, h);
2448    EndQuadrilaterals();
2449}
2450
2451static double static_E_hack; // FIXME
2452
2453void GfxCore::SetColourFromError(double E, Double factor)
2454{
2455    // Set the drawing colour based on an error value.
2456
2457    if (E < 0) {
2458        SetColour(GetSurfacePen(), factor);
2459        return;
2460    }
2461
2462    Double how_far = E / MAX_ERROR;
2463    assert(how_far >= 0.0);
2464    if (how_far > 1.0) how_far = 1.0;
2465
2466    int band = int(floor(how_far * (GetNumColourBands() - 1)));
2467    GLAPen pen1 = GetPen(band);
2468    if (band < GetNumColourBands() - 1) {
2469        const GLAPen& pen2 = GetPen(band + 1);
2470
2471        Double interval = MAX_ERROR / (GetNumColourBands() - 1);
2472        Double into_band = E / interval - band;
2473
2474//      printf("%g z_offset=%g interval=%g band=%d\n", into_band,
2475//             z_offset, interval, band);
2476        // FIXME: why do we need to clamp here?  Is it because the walls can
2477        // extend further up/down than the centre-line?
2478        if (into_band < 0.0) into_band = 0.0;
2479        if (into_band > 1.0) into_band = 1.0;
2480        assert(into_band >= 0.0);
2481        assert(into_band <= 1.0);
2482
2483        pen1.Interpolate(pen2, into_band);
2484    }
2485    SetColour(pen1, factor);
2486}
2487
2488void GfxCore::AddQuadrilateralError(const Vector3 &a, const Vector3 &b,
2489                                    const Vector3 &c, const Vector3 &d)
2490{
2491    Vector3 normal = (a - c) * (d - b);
2492    normal.normalise();
2493    Double factor = dot(normal, light) * .3 + .7;
2494    int w = int(ceil(((b - a).magnitude() + (d - c).magnitude()) / 2));
2495    int h = int(ceil(((b - c).magnitude() + (d - a).magnitude()) / 2));
2496    // FIXME: should plot triangles instead to avoid rendering glitches.
2497    BeginQuadrilaterals();
2498////    PlaceNormal(normal);
2499    SetColourFromError(static_E_hack, factor);
2500    PlaceVertex(a, 0, 0);
2501    PlaceVertex(b, w, 0);
2502    PlaceVertex(c, w, h);
2503    PlaceVertex(d, 0, h);
2504    EndQuadrilaterals();
2505}
2506
2507void GfxCore::AddPolylineError(const traverse & centreline)
2508{
2509    BeginPolyline();
2510    SetColourFromError(centreline.E, 1.0);
2511    vector<PointInfo>::const_iterator i;
2512    for(i = centreline.begin(); i != centreline.end(); ++i) {
2513        PlaceVertex(*i);
2514    }
2515    EndPolyline();
2516}
2517
2518void
2519GfxCore::SkinPassage(const vector<XSect> & centreline)
2520{
2521    assert(centreline.size() > 1);
2522    Vector3 U[4];
2523    XSect prev_pt_v;
2524    Vector3 last_right(1.0, 0.0, 0.0);
2525
2526//  FIXME: it's not simple to set the colour of a tube based on error...
2527//    static_E_hack = something...
2528    vector<XSect>::const_iterator i = centreline.begin();
2529    vector<XSect>::size_type segment = 0;
2530    while (i != centreline.end()) {
2531        // get the coordinates of this vertex
2532        const XSect & pt_v = *i++;
2533
2534        double z_pitch_adjust = 0.0;
2535        bool cover_end = false;
2536
2537        Vector3 right, up;
2538
2539        const Vector3 up_v(0.0, 0.0, 1.0);
2540
2541        if (segment == 0) {
2542            assert(i != centreline.end());
2543            // first segment
2544
2545            // get the coordinates of the next vertex
2546            const XSect & next_pt_v = *i;
2547
2548            // calculate vector from this pt to the next one
2549            Vector3 leg_v = next_pt_v - pt_v;
2550
2551            // obtain a vector in the LRUD plane
2552            right = leg_v * up_v;
2553            if (right.magnitude() == 0) {
2554                right = last_right;
2555                // Obtain a second vector in the LRUD plane,
2556                // perpendicular to the first.
2557                //up = right * leg_v;
2558                up = up_v;
2559            } else {
2560                last_right = right;
2561                up = up_v;
2562            }
2563
2564            cover_end = true;
2565            static_date_hack = next_pt_v.GetDate();
2566        } else if (segment + 1 == centreline.size()) {
2567            // last segment
2568
2569            // Calculate vector from the previous pt to this one.
2570            Vector3 leg_v = pt_v - prev_pt_v;
2571
2572            // Obtain a horizontal vector in the LRUD plane.
2573            right = leg_v * up_v;
2574            if (right.magnitude() == 0) {
2575                right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
2576                // Obtain a second vector in the LRUD plane,
2577                // perpendicular to the first.
2578                //up = right * leg_v;
2579                up = up_v;
2580            } else {
2581                last_right = right;
2582                up = up_v;
2583            }
2584
2585            cover_end = true;
2586            static_date_hack = pt_v.GetDate();
2587        } else {
2588            assert(i != centreline.end());
2589            // Intermediate segment.
2590
2591            // Get the coordinates of the next vertex.
2592            const XSect & next_pt_v = *i;
2593
2594            // Calculate vectors from this vertex to the
2595            // next vertex, and from the previous vertex to
2596            // this one.
2597            Vector3 leg1_v = pt_v - prev_pt_v;
2598            Vector3 leg2_v = next_pt_v - pt_v;
2599
2600            // Obtain horizontal vectors perpendicular to
2601            // both legs, then normalise and average to get
2602            // a horizontal bisector.
2603            Vector3 r1 = leg1_v * up_v;
2604            Vector3 r2 = leg2_v * up_v;
2605            r1.normalise();
2606            r2.normalise();
2607            right = r1 + r2;
2608            if (right.magnitude() == 0) {
2609                // This is the "mid-pitch" case...
2610                right = last_right;
2611            }
2612            if (r1.magnitude() == 0) {
2613                Vector3 n = leg1_v;
2614                n.normalise();
2615                z_pitch_adjust = n.GetZ();
2616                //up = Vector3(0, 0, leg1_v.GetZ());
2617                //up = right * up;
2618                up = up_v;
2619
2620                // Rotate pitch section to minimise the
2621                // "tortional stress" - FIXME: use
2622                // triangles instead of rectangles?
2623                int shift = 0;
2624                Double maxdotp = 0;
2625
2626                // Scale to unit vectors in the LRUD plane.
2627                right.normalise();
2628                up.normalise();
2629                Vector3 vec = up - right;
2630                for (int orient = 0; orient <= 3; ++orient) {
2631                    Vector3 tmp = U[orient] - prev_pt_v;
2632                    tmp.normalise();
2633                    Double dotp = dot(vec, tmp);
2634                    if (dotp > maxdotp) {
2635                        maxdotp = dotp;
2636                        shift = orient;
2637                    }
2638                }
2639                if (shift) {
2640                    if (shift != 2) {
2641                        Vector3 temp(U[0]);
2642                        U[0] = U[shift];
2643                        U[shift] = U[2];
2644                        U[2] = U[shift ^ 2];
2645                        U[shift ^ 2] = temp;
2646                    } else {
2647                        swap(U[0], U[2]);
2648                        swap(U[1], U[3]);
2649                    }
2650                }
2651#if 0
2652                // Check that the above code actually permuted
2653                // the vertices correctly.
2654                shift = 0;
2655                maxdotp = 0;
2656                for (int j = 0; j <= 3; ++j) {
2657                    Vector3 tmp = U[j] - prev_pt_v;
2658                    tmp.normalise();
2659                    Double dotp = dot(vec, tmp);
2660                    if (dotp > maxdotp) {
2661                        maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
2662                        shift = j;
2663                    }
2664                }
2665                if (shift) {
2666                    printf("New shift = %d!\n", shift);
2667                    shift = 0;
2668                    maxdotp = 0;
2669                    for (int j = 0; j <= 3; ++j) {
2670                        Vector3 tmp = U[j] - prev_pt_v;
2671                        tmp.normalise();
2672                        Double dotp = dot(vec, tmp);
2673                        printf("    %d : %.8f\n", j, dotp);
2674                    }
2675                }
2676#endif
2677            } else if (r2.magnitude() == 0) {
2678                Vector3 n = leg2_v;
2679                n.normalise();
2680                z_pitch_adjust = n.GetZ();
2681                //up = Vector3(0, 0, leg2_v.GetZ());
2682                //up = right * up;
2683                up = up_v;
2684            } else {
2685                up = up_v;
2686            }
2687            last_right = right;
2688            static_date_hack = pt_v.GetDate();
2689        }
2690
2691        // Scale to unit vectors in the LRUD plane.
2692        right.normalise();
2693        up.normalise();
2694
2695        if (z_pitch_adjust != 0) up += Vector3(0, 0, fabs(z_pitch_adjust));
2696
2697        Double l = fabs(pt_v.GetL());
2698        Double r = fabs(pt_v.GetR());
2699        Double u = fabs(pt_v.GetU());
2700        Double d = fabs(pt_v.GetD());
2701
2702        // Produce coordinates of the corners of the LRUD "plane".
2703        Vector3 v[4];
2704        v[0] = pt_v - right * l + up * u;
2705        v[1] = pt_v + right * r + up * u;
2706        v[2] = pt_v + right * r - up * d;
2707        v[3] = pt_v - right * l - up * d;
2708
2709        if (segment > 0) {
2710            (this->*AddQuad)(v[0], v[1], U[1], U[0]);
2711            (this->*AddQuad)(v[2], v[3], U[3], U[2]);
2712            (this->*AddQuad)(v[1], v[2], U[2], U[1]);
2713            (this->*AddQuad)(v[3], v[0], U[0], U[3]);
2714        }
2715
2716        if (cover_end) {
2717            (this->*AddQuad)(v[3], v[2], v[1], v[0]);
2718        }
2719
2720        prev_pt_v = pt_v;
2721        U[0] = v[0];
2722        U[1] = v[1];
2723        U[2] = v[2];
2724        U[3] = v[3];
2725
2726        ++segment;
2727    }
2728}
2729
2730void GfxCore::FullScreenMode()
2731{
2732    m_Parent->ViewFullScreen();
2733}
2734
2735bool GfxCore::IsFullScreen() const
2736{
2737    return m_Parent->IsFullScreen();
2738}
2739
2740void
2741GfxCore::MoveViewer(double forward, double up, double right)
2742{
2743    double cT = cos(rad(m_TiltAngle));
2744    double sT = sin(rad(m_TiltAngle));
2745    double cP = cos(rad(m_PanAngle));
2746    double sP = sin(rad(m_PanAngle));
2747    Vector3 v_forward(cT * sP, cT * cP, sT);
2748    Vector3 v_up(sT * sP, sT * cP, -cT);
2749    Vector3 v_right(-cP, sP, 0);
2750    assert(fabs(dot(v_forward, v_up)) < 1e-6);
2751    assert(fabs(dot(v_forward, v_right)) < 1e-6);
2752    assert(fabs(dot(v_right, v_up)) < 1e-6);
2753    Vector3 move = v_forward * forward + v_up * up + v_right * right;
2754    AddTranslation(-move);
2755    // Show current position.
2756    m_Parent->SetCoords(m_Parent->GetOffset() - GetTranslation());
2757    ForceRefresh();
2758}
2759
2760PresentationMark GfxCore::GetView() const
2761{
2762    return PresentationMark(GetTranslation() + m_Parent->GetOffset(),
2763                            m_PanAngle, -m_TiltAngle, m_Scale);
2764}
2765
2766void GfxCore::SetView(const PresentationMark & p)
2767{
2768    m_SwitchingTo = 0;
2769    SetTranslation(p - m_Parent->GetOffset());
2770    m_PanAngle = p.angle;
2771    m_TiltAngle = -p.tilt_angle; // FIXME: nasty reversed sense (and above)
2772    SetRotation(m_PanAngle, m_TiltAngle);
2773    SetScale(p.scale);
2774    ForceRefresh();
2775}
2776
2777void GfxCore::PlayPres(double speed, bool change_speed) {
2778    if (!change_speed || presentation_mode == 0) {
2779        if (speed == 0.0) {
2780            presentation_mode = 0;
2781            return;
2782        }
2783        presentation_mode = PLAYING;
2784        next_mark = m_Parent->GetPresMark(MARK_FIRST);
2785        SetView(next_mark);
2786        next_mark_time = 0; // There already!
2787        this_mark_total = 0;
2788        pres_reverse = (speed < 0);
2789    }
2790
2791    if (change_speed) pres_speed = speed;
2792
2793    if (speed != 0.0) {
2794        bool new_pres_reverse = (speed < 0);
2795        if (new_pres_reverse != pres_reverse) {
2796            pres_reverse = new_pres_reverse;
2797            if (pres_reverse) {
2798                next_mark = m_Parent->GetPresMark(MARK_PREV);
2799            } else {
2800                next_mark = m_Parent->GetPresMark(MARK_NEXT);
2801            }
2802            swap(this_mark_total, next_mark_time);
2803        }
2804    }
2805}
2806
2807void GfxCore::SetColourBy(int colour_by) {
2808    m_ColourBy = colour_by;
2809    switch (colour_by) {
2810        case COLOUR_BY_DEPTH:
2811            AddQuad = &GfxCore::AddQuadrilateralDepth;
2812            AddPoly = &GfxCore::AddPolylineDepth;
2813            break;
2814        case COLOUR_BY_DATE:
2815            AddQuad = &GfxCore::AddQuadrilateralDate;
2816            AddPoly = &GfxCore::AddPolylineDate;
2817            break;
2818        case COLOUR_BY_ERROR:
2819            AddQuad = &GfxCore::AddQuadrilateralError;
2820            AddPoly = &GfxCore::AddPolylineError;
2821            break;
2822        default: // case COLOUR_BY_NONE:
2823            AddQuad = &GfxCore::AddQuadrilateral;
2824            AddPoly = &GfxCore::AddPolyline;
2825            break;
2826    }
2827
2828    InvalidateList(LIST_UNDERGROUND_LEGS);
2829    InvalidateList(LIST_SURFACE_LEGS);
2830    InvalidateList(LIST_TUBES);
2831
2832    ForceRefresh();
2833}
2834
2835bool GfxCore::ExportMovie(const wxString & fnm)
2836{
2837    int width;
2838    int height;
2839    GetSize(&width, &height);
2840    // Round up to next multiple of 2 (required by ffmpeg).
2841    width += (width & 1);
2842    height += (height & 1);
2843
2844    movie = new MovieMaker();
2845
2846    // FIXME: This should really use fn_str() - currently we probably can't
2847    // save to a Unicode path on wxmsw.
2848    if (!movie->Open(fnm.mb_str(), width, height)) {
2849        wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
2850        delete movie;
2851        movie = NULL;
2852        return false;
2853    }
2854
2855    PlayPres(1);
2856    return true;
2857}
2858
2859void
2860GfxCore::OnPrint(const wxString &filename, const wxString &title,
2861                 const wxString &datestamp)
2862{
2863    svxPrintDlg * p;
2864    p = new svxPrintDlg(m_Parent, filename, title, datestamp,
2865                        m_PanAngle, m_TiltAngle,
2866                        m_Names, m_Crosses, m_Legs, m_Surface,
2867                        true);
2868    p->Show(true);
2869}
2870
2871void
2872GfxCore::OnExport(const wxString &filename, const wxString &title)
2873{
2874    svxPrintDlg * p;
2875    p = new svxPrintDlg(m_Parent, filename, title, wxString(),
2876                        m_PanAngle, m_TiltAngle,
2877                        m_Names, m_Crosses, m_Legs, m_Surface,
2878                        false);
2879    p->Show(true);
2880}
2881
2882static wxCursor
2883make_cursor(const unsigned char * bits, const unsigned char * mask,
2884            int hotx, int hoty)
2885{
2886#if defined __WXMSW__ || defined __WXMAC__
2887    wxBitmap cursor_bitmap(reinterpret_cast<const char *>(bits), 32, 32);
2888    wxBitmap mask_bitmap(reinterpret_cast<const char *>(mask), 32, 32);
2889    cursor_bitmap.SetMask(new wxMask(mask_bitmap, *wxWHITE));
2890    wxImage cursor_image = cursor_bitmap.ConvertToImage();
2891    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, hotx);
2892    cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, hoty);
2893    return wxCursor(cursor_image);
2894#else
2895    return wxCursor((const char *)bits, 32, 32, hotx, hoty,
2896                    (const char *)mask, wxBLACK, wxWHITE);
2897#endif
2898}
2899
2900const
2901#include "hand.xbm"
2902const
2903#include "handmask.xbm"
2904
2905const
2906#include "brotate.xbm"
2907const
2908#include "brotatemask.xbm"
2909
2910const
2911#include "vrotate.xbm"
2912const
2913#include "vrotatemask.xbm"
2914
2915const
2916#include "rotate.xbm"
2917const
2918#include "rotatemask.xbm"
2919
2920const
2921#include "rotatezoom.xbm"
2922const
2923#include "rotatezoommask.xbm"
2924
2925void
2926GfxCore::SetCursor(GfxCore::cursor new_cursor)
2927{
2928    // Check if we're already showing that cursor.
2929    if (current_cursor == new_cursor) return;
2930
2931    current_cursor = new_cursor;
2932    switch (current_cursor) {
2933        case GfxCore::CURSOR_DEFAULT:
2934            GLACanvas::SetCursor(wxNullCursor);
2935            break;
2936        case GfxCore::CURSOR_POINTING_HAND:
2937            GLACanvas::SetCursor(wxCursor(wxCURSOR_HAND));
2938            break;
2939        case GfxCore::CURSOR_DRAGGING_HAND:
2940            GLACanvas::SetCursor(make_cursor(hand_bits, handmask_bits, 12, 18));
2941            break;
2942        case GfxCore::CURSOR_HORIZONTAL_RESIZE:
2943            GLACanvas::SetCursor(wxCursor(wxCURSOR_SIZEWE));
2944            break;
2945        case GfxCore::CURSOR_ROTATE_HORIZONTALLY:
2946            GLACanvas::SetCursor(make_cursor(rotate_bits, rotatemask_bits, 15, 15));
2947            break;
2948        case GfxCore::CURSOR_ROTATE_VERTICALLY:
2949            GLACanvas::SetCursor(make_cursor(vrotate_bits, vrotatemask_bits, 15, 15));
2950            break;
2951        case GfxCore::CURSOR_ROTATE_EITHER_WAY:
2952            GLACanvas::SetCursor(make_cursor(brotate_bits, brotatemask_bits, 15, 15));
2953            break;
2954        case GfxCore::CURSOR_ZOOM:
2955            GLACanvas::SetCursor(wxCursor(wxCURSOR_MAGNIFIER));
2956            break;
2957        case GfxCore::CURSOR_ZOOM_ROTATE:
2958            GLACanvas::SetCursor(make_cursor(rotatezoom_bits, rotatezoommask_bits, 15, 15));
2959            break;
2960    }
2961}
2962
2963bool GfxCore::MeasuringLineActive() const
2964{
2965    if (Animating()) return false;
2966    return (m_here.IsValid() && !m_here_is_temporary) || m_there.IsValid();
2967}
2968
2969bool GfxCore::HandleRClick(wxPoint point)
2970{
2971    if (PointWithinCompass(point)) {
2972        // Pop up menu.
2973        wxMenu menu;
2974        menu.Append(menu_ORIENT_MOVE_NORTH, wmsg(/*View &North*/240));
2975        menu.Append(menu_ORIENT_MOVE_EAST, wmsg(/*View &East*/241));
2976        menu.Append(menu_ORIENT_MOVE_SOUTH, wmsg(/*View &South*/242));
2977        menu.Append(menu_ORIENT_MOVE_WEST, wmsg(/*View &West*/243));
2978        menu.AppendSeparator();
2979        menu.AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Hide Compass*/387));
2980        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
2981        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
2982        PopupMenu(&menu);
2983        return true;
2984    }
2985
2986    if (PointWithinClino(point)) {
2987        // Pop up menu.
2988        wxMenu menu;
2989        menu.Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
2990        menu.Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
2991        menu.AppendSeparator();
2992        menu.AppendCheckItem(menu_IND_CLINO, wmsg(/*&Hide Clino*/384));
2993        menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
2994        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
2995        PopupMenu(&menu);
2996        PopupMenu(&menu);
2997        return true;
2998    }
2999
3000    if (PointWithinScaleBar(point)) {
3001        // Pop up menu.
3002        wxMenu menu;
3003        menu.AppendCheckItem(menu_IND_SCALE_BAR, wmsg(/*&Hide scale bar*/385));
3004        menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
3005        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3006        PopupMenu(&menu);
3007        return true;
3008    }
3009
3010    if (PointWithinColourKey(point)) {
3011        // Pop up menu.
3012        wxMenu menu;
3013        menu.AppendCheckItem(menu_VIEW_COLOUR_BY_DEPTH, wmsg(/*Colour by &Depth*/292));
3014        menu.AppendCheckItem(menu_VIEW_COLOUR_BY_DATE, wmsg(/*Colour by D&ate*/293));
3015        menu.AppendCheckItem(menu_VIEW_COLOUR_BY_ERROR, wmsg(/*Colour by E&rror*/289));
3016        menu.AppendSeparator();
3017        menu.AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*&Hide colour key*/386));
3018        if (m_ColourBy == COLOUR_BY_DEPTH)
3019            menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
3020        menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
3021        PopupMenu(&menu);
3022        return true;
3023    }
3024
3025    return false;
3026}
Note: See TracBrowser for help on using the repository browser.