source: git/src/gfxcore.cc @ 24ff389

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

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

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

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