source: git/src/gfxcore.cc @ 68be120

RELEASE/1.1RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernlogstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 68be120 was 5627cbb, checked in by Olly Betts <olly@…>, 14 years ago
  • Fix to build with a "unicode" build of wx.
  • Add "Copy" button to the About dialog to copy the system info to the clipboard.
  • List OpenGL extensions last, since there are usually lots of them with a modern gfx card.
  • When processing survey data, auto-scroll the log window until we've reported a warning or error.
  • Put the survey data log window in a splitter in the standard frame rather than having a separate frame for it.

git-svn-id: file:///home/survex-svn/survex/branches/survex-1_1@3356 4b37db11-9a0c-4f06-9ece-9ab7cdaee568

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