Warn about 2 digit years being assumed to be 19xx
[survex.git] / src / gfxcore.cc
blob351d6e826840719346d763abfe205e799e63011d
1 //
2 // gfxcore.cc
3 //
4 // Core drawing code for Aven.
5 //
6 // Copyright (C) 2000-2003,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2003,2004,2005,2006,2007,2010,2011,2012,2014,2015,2016,2017,2018 Olly Betts
8 // Copyright (C) 2005 Martin Green
9 //
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
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.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <assert.h>
30 #include <float.h>
32 #include "aven.h"
33 #include "date.h"
34 #include "filename.h"
35 #include "gfxcore.h"
36 #include "mainfrm.h"
37 #include "message.h"
38 #include "useful.h"
39 #include "printing.h"
40 #include "guicontrol.h"
41 #include "moviemaker.h"
43 #include <wx/confbase.h>
44 #include <wx/wfstream.h>
45 #include <wx/image.h>
46 #include <wx/zipstrm.h>
48 #include <proj_api.h>
50 const unsigned long DEFAULT_HGT_DIM = 3601;
51 const unsigned long DEFAULT_HGT_SIZE = sqrd(DEFAULT_HGT_DIM) * 2;
53 // Values for m_SwitchingTo
54 #define PLAN 1
55 #define ELEVATION 2
56 #define NORTH 3
57 #define EAST 4
58 #define SOUTH 5
59 #define WEST 6
61 // Any error value higher than this is clamped to this.
62 #define MAX_ERROR 12.0
64 // Any length greater than pow(10, LOG_LEN_MAX) will be clamped to this.
65 const Double LOG_LEN_MAX = 1.5;
67 // How many bins per letter height to use when working out non-overlapping
68 // labels.
69 const unsigned int QUANTISE_FACTOR = 2;
71 #include "avenpal.h"
73 static const int INDICATOR_BOX_SIZE = 60;
74 static const int INDICATOR_GAP = 2;
75 static const int INDICATOR_MARGIN = 5;
76 static const int INDICATOR_OFFSET_X = 15;
77 static const int INDICATOR_OFFSET_Y = 15;
78 static const int INDICATOR_RADIUS = INDICATOR_BOX_SIZE / 2 - INDICATOR_MARGIN;
79 static const int KEY_OFFSET_X = 10;
80 static const int KEY_OFFSET_Y = 10;
81 static const int KEY_EXTRA_LEFT_MARGIN = 2;
82 static const int KEY_BLOCK_WIDTH = 20;
83 static const int KEY_BLOCK_HEIGHT = 16;
84 static const int TICK_LENGTH = 4;
85 static const int SCALE_BAR_OFFSET_X = 15;
86 static const int SCALE_BAR_OFFSET_Y = 12;
87 static const int SCALE_BAR_HEIGHT = 12;
89 static const gla_colour TEXT_COLOUR = col_GREEN;
90 static const gla_colour HERE_COLOUR = col_WHITE;
91 static const gla_colour NAME_COLOUR = col_GREEN;
92 static const gla_colour SEL_COLOUR = col_WHITE;
93 // Used with colour by date for legs without date information and with colour
94 // by error for legs not in a loop.
95 static const gla_colour NODATA_COLOUR = col_LIGHT_GREY_2;
97 // Number of entries across and down the hit-test grid:
98 #define HITTEST_SIZE 20
100 // How close the pointer needs to be to a station to be considered:
101 #define MEASURE_THRESHOLD 7
103 // vector for lighting angle
104 static const Vector3 light(.577, .577, .577);
106 BEGIN_EVENT_TABLE(GfxCore, GLACanvas)
107 EVT_PAINT(GfxCore::OnPaint)
108 EVT_LEFT_DOWN(GfxCore::OnLButtonDown)
109 EVT_LEFT_UP(GfxCore::OnLButtonUp)
110 EVT_MIDDLE_DOWN(GfxCore::OnMButtonDown)
111 EVT_MIDDLE_UP(GfxCore::OnMButtonUp)
112 EVT_RIGHT_DOWN(GfxCore::OnRButtonDown)
113 EVT_RIGHT_UP(GfxCore::OnRButtonUp)
114 EVT_MOUSEWHEEL(GfxCore::OnMouseWheel)
115 EVT_MOTION(GfxCore::OnMouseMove)
116 EVT_LEAVE_WINDOW(GfxCore::OnLeaveWindow)
117 EVT_SIZE(GfxCore::OnSize)
118 EVT_IDLE(GfxCore::OnIdle)
119 EVT_CHAR(GfxCore::OnKeyPress)
120 END_EVENT_TABLE()
122 GfxCore::GfxCore(MainFrm* parent, wxWindow* parent_win, GUIControl* control) :
123 GLACanvas(parent_win, 100),
124 m_Scale(0.0),
125 initial_scale(1.0),
126 m_ScaleBarWidth(0),
127 m_Control(control),
128 m_LabelGrid(NULL),
129 m_Parent(parent),
130 m_DoneFirstShow(false),
131 m_TiltAngle(0.0),
132 m_PanAngle(0.0),
133 m_Rotating(false),
134 m_RotationStep(0.0),
135 m_SwitchingTo(0),
136 m_Crosses(false),
137 m_Legs(true),
138 m_Splays(SHOW_FADED),
139 m_Dupes(SHOW_DASHED),
140 m_Names(false),
141 m_Scalebar(true),
142 m_ColourKey(true),
143 m_OverlappingNames(false),
144 m_Compass(true),
145 m_Clino(true),
146 m_Tubes(false),
147 m_ColourBy(COLOUR_BY_DEPTH),
148 m_HaveData(false),
149 m_HaveTerrain(true),
150 m_MouseOutsideCompass(false),
151 m_MouseOutsideElev(false),
152 m_Surface(false),
153 m_Entrances(false),
154 m_FixedPts(false),
155 m_ExportedPts(false),
156 m_Grid(false),
157 m_BoundingBox(false),
158 m_Terrain(false),
159 m_Degrees(false),
160 m_Metric(false),
161 m_Percent(false),
162 m_HitTestDebug(false),
163 m_RenderStats(false),
164 m_PointGrid(NULL),
165 m_HitTestGridValid(false),
166 m_here(NULL),
167 m_there(NULL),
168 presentation_mode(0),
169 pres_reverse(false),
170 pres_speed(0.0),
171 movie(NULL),
172 current_cursor(GfxCore::CURSOR_DEFAULT),
173 sqrd_measure_threshold(sqrd(MEASURE_THRESHOLD)),
174 dem(NULL),
175 last_time(0),
176 n_tris(0)
178 AddQuad = &GfxCore::AddQuadrilateralDepth;
179 AddPoly = &GfxCore::AddPolylineDepth;
180 wxConfigBase::Get()->Read(wxT("metric"), &m_Metric, true);
181 wxConfigBase::Get()->Read(wxT("degrees"), &m_Degrees, true);
182 wxConfigBase::Get()->Read(wxT("percent"), &m_Percent, false);
184 for (int pen = 0; pen < NUM_COLOUR_BANDS + 1; ++pen) {
185 m_Pens[pen].SetColour(REDS[pen] / 255.0,
186 GREENS[pen] / 255.0,
187 BLUES[pen] / 255.0);
190 timer.Start();
193 GfxCore::~GfxCore()
195 TryToFreeArrays();
197 delete[] m_PointGrid;
200 void GfxCore::TryToFreeArrays()
202 // Free up any memory allocated for arrays.
203 delete[] m_LabelGrid;
204 m_LabelGrid = NULL;
208 // Initialisation methods
211 void GfxCore::Initialise(bool same_file)
213 // Initialise the view from the parent holding the survey data.
215 TryToFreeArrays();
217 m_DoneFirstShow = false;
219 m_HitTestGridValid = false;
220 m_here = NULL;
221 m_there = NULL;
223 m_MouseOutsideCompass = m_MouseOutsideElev = false;
225 if (!same_file) {
226 // Apply default parameters unless reloading the same file.
227 DefaultParameters();
230 m_HaveData = true;
232 // Clear any cached OpenGL lists which depend on the data.
233 InvalidateList(LIST_SCALE_BAR);
234 InvalidateList(LIST_DEPTH_KEY);
235 InvalidateList(LIST_DATE_KEY);
236 InvalidateList(LIST_ERROR_KEY);
237 InvalidateList(LIST_GRADIENT_KEY);
238 InvalidateList(LIST_LENGTH_KEY);
239 InvalidateList(LIST_UNDERGROUND_LEGS);
240 InvalidateList(LIST_TUBES);
241 InvalidateList(LIST_SURFACE_LEGS);
242 InvalidateList(LIST_BLOBS);
243 InvalidateList(LIST_CROSSES);
244 InvalidateList(LIST_GRID);
245 InvalidateList(LIST_SHADOW);
246 InvalidateList(LIST_TERRAIN);
248 // Set diameter of the viewing volume.
249 double cave_diameter = sqrt(sqrd(m_Parent->GetXExtent()) +
250 sqrd(m_Parent->GetYExtent()) +
251 sqrd(m_Parent->GetZExtent()));
253 // Allow for terrain.
254 double diameter = max(1000.0 * 2, cave_diameter * 2);
256 if (!same_file) {
257 SetVolumeDiameter(diameter);
259 // Set initial scale based on the size of the cave.
260 initial_scale = diameter / cave_diameter;
261 SetScale(initial_scale);
262 } else {
263 // Adjust the position when restricting the view to a subsurvey (or
264 // expanding the view to show the whole survey).
265 AddTranslation(m_Parent->GetOffset() - offsets);
267 // Try to keep the same scale, allowing for the
268 // cave having grown (or shrunk).
269 double rescale = GetVolumeDiameter() / diameter;
270 SetVolumeDiameter(diameter);
271 SetScale(GetScale() / rescale); // ?
272 initial_scale = initial_scale * rescale;
275 offsets = m_Parent->GetOffset();
277 ForceRefresh();
280 void GfxCore::FirstShow()
282 GLACanvas::FirstShow();
284 const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
285 list<LabelInfo*>::iterator pos = m_Parent->GetLabelsNC();
286 while (pos != m_Parent->GetLabelsNCEnd()) {
287 LabelInfo* label = *pos++;
288 // Calculate and set the label width for use when plotting
289 // none-overlapping labels.
290 int ext_x;
291 GLACanvas::GetTextExtent(label->GetText(), &ext_x, NULL);
292 label->set_width(unsigned(ext_x) / quantise + 1);
295 m_DoneFirstShow = true;
299 // Recalculating methods
302 void GfxCore::SetScale(Double scale)
304 if (scale < 0.05) {
305 scale = 0.05;
306 } else if (scale > GetVolumeDiameter()) {
307 scale = GetVolumeDiameter();
310 m_Scale = scale;
311 m_HitTestGridValid = false;
312 if (m_here && m_here == &temp_here) SetHere();
314 GLACanvas::SetScale(scale);
317 bool GfxCore::HasUndergroundLegs() const
319 return m_Parent->HasUndergroundLegs();
322 bool GfxCore::HasSplays() const
324 return m_Parent->HasSplays();
327 bool GfxCore::HasDupes() const
329 return m_Parent->HasDupes();
332 bool GfxCore::HasSurfaceLegs() const
334 return m_Parent->HasSurfaceLegs();
337 bool GfxCore::HasTubes() const
339 return m_Parent->HasTubes();
342 void GfxCore::UpdateBlobs()
344 InvalidateList(LIST_BLOBS);
348 // Event handlers
351 void GfxCore::OnLeaveWindow(wxMouseEvent&) {
352 SetHere();
353 ClearCoords();
356 void GfxCore::OnIdle(wxIdleEvent& event)
358 // Handle an idle event.
359 if (Animating()) {
360 Animate();
361 // If still animating, we want more idle events.
362 if (Animating())
363 event.RequestMore();
364 } else {
365 // If we're idle, don't show a bogus FPS next time we render.
366 last_time = 0;
370 void GfxCore::OnPaint(wxPaintEvent&)
372 // Redraw the window.
374 // Get a graphics context.
375 wxPaintDC dc(this);
377 if (m_HaveData) {
378 // Make sure we're initialised.
379 bool first_time = !m_DoneFirstShow;
380 if (first_time) {
381 FirstShow();
384 StartDrawing();
386 // Clear the background.
387 Clear();
389 // Set up model transformation matrix.
390 SetDataTransform();
392 if (m_Legs || m_Tubes) {
393 if (m_Tubes) {
394 EnableSmoothPolygons(true); // FIXME: allow false for wireframe view
395 DrawList(LIST_TUBES);
396 DisableSmoothPolygons();
399 // Draw the underground legs. Do this last so that anti-aliasing
400 // works over polygons.
401 SetColour(col_GREEN);
402 DrawList(LIST_UNDERGROUND_LEGS);
405 if (m_Surface) {
406 // Draw the surface legs.
407 DrawList(LIST_SURFACE_LEGS);
410 if (m_BoundingBox) {
411 DrawShadowedBoundingBox();
413 if (m_Grid) {
414 // Draw the grid.
415 DrawList(LIST_GRID);
418 DrawList(LIST_BLOBS);
420 if (m_Crosses) {
421 DrawList(LIST_CROSSES);
424 if (m_Terrain) {
425 // Disable texturing while drawing terrain.
426 bool texturing = GetTextured();
427 if (texturing) GLACanvas::ToggleTextured();
429 // This is needed if blobs and/or crosses are drawn using lines -
430 // otherwise the terrain doesn't appear when they are enabled.
431 SetDataTransform();
433 // We don't want to be able to see the terrain through itself, so
434 // do a "Z-prepass" - plot the terrain once only updating the
435 // Z-buffer, then again with Z-clipping only plotting where the
436 // depth matches the value in the Z-buffer.
437 DrawListZPrepass(LIST_TERRAIN);
439 if (texturing) GLACanvas::ToggleTextured();
442 SetIndicatorTransform();
444 // Draw station names.
445 if (m_Names /*&& !m_Control->MouseDown() && !Animating()*/) {
446 SetColour(NAME_COLOUR);
448 if (m_OverlappingNames) {
449 SimpleDrawNames();
450 } else {
451 NattyDrawNames();
455 if (m_HitTestDebug) {
456 // Show the hit test grid bucket sizes...
457 SetColour(m_HitTestGridValid ? col_LIGHT_GREY : col_DARK_GREY);
458 if (m_PointGrid) {
459 for (int i = 0; i != HITTEST_SIZE; ++i) {
460 int x = (GetXSize() + 1) * i / HITTEST_SIZE + 2;
461 for (int j = 0; j != HITTEST_SIZE; ++j) {
462 int square = i + j * HITTEST_SIZE;
463 unsigned long bucket_size = m_PointGrid[square].size();
464 if (bucket_size) {
465 int y = (GetYSize() + 1) * (HITTEST_SIZE - 1 - j) / HITTEST_SIZE;
466 DrawIndicatorText(x, y, wxString::Format(wxT("%lu"), bucket_size));
472 EnableDashedLines();
473 BeginLines();
474 for (int i = 0; i != HITTEST_SIZE; ++i) {
475 int x = (GetXSize() + 1) * i / HITTEST_SIZE;
476 PlaceIndicatorVertex(x, 0);
477 PlaceIndicatorVertex(x, GetYSize());
479 for (int j = 0; j != HITTEST_SIZE; ++j) {
480 int y = (GetYSize() + 1) * (HITTEST_SIZE - 1 - j) / HITTEST_SIZE;
481 PlaceIndicatorVertex(0, y);
482 PlaceIndicatorVertex(GetXSize(), y);
484 EndLines();
485 DisableDashedLines();
488 long now = timer.Time();
489 if (m_RenderStats) {
490 // Show stats about rendering.
491 SetColour(col_TURQUOISE);
492 int y = GetYSize() - GetFontSize();
493 if (last_time != 0.0) {
494 // timer.Time() measure in milliseconds.
495 double fps = 1000.0 / (now - last_time);
496 DrawIndicatorText(1, y, wxString::Format(wxT("FPS:% 5.1f"), fps));
498 y -= GetFontSize();
499 DrawIndicatorText(1, y, wxString::Format(wxT("▲:%lu"), (unsigned long)n_tris));
501 last_time = now;
503 // Draw indicators.
505 // There's no advantage in generating an OpenGL list for the
506 // indicators since they change with almost every redraw (and
507 // sometimes several times between redraws). This way we avoid
508 // the need to track when to update the indicator OpenGL list,
509 // and also avoid indicator update bugs when we don't quite get this
510 // right...
511 DrawIndicators();
513 if (zoombox.active()) {
514 SetColour(SEL_COLOUR);
515 EnableDashedLines();
516 BeginPolyline();
517 glaCoord Y = GetYSize();
518 PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y1);
519 PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y2);
520 PlaceIndicatorVertex(zoombox.x2, Y - zoombox.y2);
521 PlaceIndicatorVertex(zoombox.x2, Y - zoombox.y1);
522 PlaceIndicatorVertex(zoombox.x1, Y - zoombox.y1);
523 EndPolyline();
524 DisableDashedLines();
525 } else if (MeasuringLineActive()) {
526 // Draw "here" and "there".
527 double hx, hy;
528 SetColour(HERE_COLOUR);
529 if (m_here) {
530 double dummy;
531 Transform(*m_here, &hx, &hy, &dummy);
532 if (m_here != &temp_here) DrawRing(hx, hy);
534 if (m_there) {
535 double tx, ty;
536 double dummy;
537 Transform(*m_there, &tx, &ty, &dummy);
538 if (m_here) {
539 BeginLines();
540 PlaceIndicatorVertex(hx, hy);
541 PlaceIndicatorVertex(tx, ty);
542 EndLines();
544 BeginBlobs();
545 DrawBlob(tx, ty);
546 EndBlobs();
550 FinishDrawing();
551 } else {
552 dc.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME));
553 dc.Clear();
557 void GfxCore::DrawBoundingBox()
559 const Vector3 v = 0.5 * m_Parent->GetExtent();
561 SetColour(col_BLUE);
562 EnableDashedLines();
563 BeginPolyline();
564 PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
565 PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
566 PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
567 PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
568 PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
569 EndPolyline();
570 BeginPolyline();
571 PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
572 PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
573 PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
574 PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
575 PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
576 EndPolyline();
577 BeginLines();
578 PlaceVertex(-v.GetX(), -v.GetY(), v.GetZ());
579 PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
580 PlaceVertex(-v.GetX(), v.GetY(), v.GetZ());
581 PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
582 PlaceVertex(v.GetX(), v.GetY(), v.GetZ());
583 PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
584 PlaceVertex(v.GetX(), -v.GetY(), v.GetZ());
585 PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
586 EndLines();
587 DisableDashedLines();
590 void GfxCore::DrawShadowedBoundingBox()
592 const Vector3 v = 0.5 * m_Parent->GetExtent();
594 DrawBoundingBox();
596 PolygonOffset(true);
597 SetColour(col_DARK_GREY);
598 BeginQuadrilaterals();
599 PlaceVertex(-v.GetX(), -v.GetY(), -v.GetZ());
600 PlaceVertex(-v.GetX(), v.GetY(), -v.GetZ());
601 PlaceVertex(v.GetX(), v.GetY(), -v.GetZ());
602 PlaceVertex(v.GetX(), -v.GetY(), -v.GetZ());
603 EndQuadrilaterals();
604 PolygonOffset(false);
606 DrawList(LIST_SHADOW);
609 void GfxCore::DrawGrid()
611 // Draw the grid.
612 SetColour(col_RED);
614 // Calculate the extent of the survey, in metres across the screen plane.
615 Double m_across_screen = SurveyUnitsAcrossViewport();
616 // Calculate the length of the scale bar in metres.
617 //--move this elsewhere
618 Double size_snap = pow(10.0, floor(log10(0.75 * m_across_screen)));
619 Double t = m_across_screen * 0.75 / size_snap;
620 if (t >= 5.0) {
621 size_snap *= 5.0;
623 else if (t >= 2.0) {
624 size_snap *= 2.0;
627 Double grid_size = size_snap * 0.1;
628 Double edge = grid_size * 2.0;
629 Double grid_z = -m_Parent->GetZExtent() * 0.5 - grid_size;
630 Double left = -m_Parent->GetXExtent() * 0.5 - edge;
631 Double right = m_Parent->GetXExtent() * 0.5 + edge;
632 Double bottom = -m_Parent->GetYExtent() * 0.5 - edge;
633 Double top = m_Parent->GetYExtent() * 0.5 + edge;
634 int count_x = (int) ceil((right - left) / grid_size);
635 int count_y = (int) ceil((top - bottom) / grid_size);
636 Double actual_right = left + count_x*grid_size;
637 Double actual_top = bottom + count_y*grid_size;
639 BeginLines();
641 for (int xc = 0; xc <= count_x; xc++) {
642 Double x = left + xc*grid_size;
644 PlaceVertex(x, bottom, grid_z);
645 PlaceVertex(x, actual_top, grid_z);
648 for (int yc = 0; yc <= count_y; yc++) {
649 Double y = bottom + yc*grid_size;
650 PlaceVertex(left, y, grid_z);
651 PlaceVertex(actual_right, y, grid_z);
654 EndLines();
657 int GfxCore::GetClinoOffset() const
659 int result = INDICATOR_OFFSET_X;
660 if (m_Compass) {
661 result += 6 + GetCompassWidth() + INDICATOR_GAP;
663 return result;
666 void GfxCore::DrawTick(int angle_cw)
668 const Double theta = rad(angle_cw);
669 const wxCoord length1 = INDICATOR_RADIUS;
670 const wxCoord length0 = length1 + TICK_LENGTH;
671 wxCoord x0 = wxCoord(length0 * sin(theta));
672 wxCoord y0 = wxCoord(length0 * cos(theta));
673 wxCoord x1 = wxCoord(length1 * sin(theta));
674 wxCoord y1 = wxCoord(length1 * cos(theta));
676 PlaceIndicatorVertex(x0, y0);
677 PlaceIndicatorVertex(x1, y1);
680 void GfxCore::DrawArrow(gla_colour col1, gla_colour col2) {
681 Vector3 p1(0, INDICATOR_RADIUS, 0);
682 Vector3 p2(INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 150deg
683 Vector3 p3(-INDICATOR_RADIUS/2, INDICATOR_RADIUS*-.866025404, 0); // 210deg
684 Vector3 pc(0, 0, 0);
686 DrawTriangle(col_LIGHT_GREY, col1, p2, p1, pc);
687 DrawTriangle(col_LIGHT_GREY, col2, p3, p1, pc);
690 void GfxCore::DrawCompass() {
691 // Ticks.
692 BeginLines();
693 for (int angle = 315; angle > 0; angle -= 45) {
694 DrawTick(angle);
696 SetColour(col_GREEN);
697 DrawTick(0);
698 EndLines();
700 // Compass background.
701 DrawCircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS);
703 // Compass arrow.
704 DrawArrow(col_INDICATOR_1, col_INDICATOR_2);
707 // Draw the non-rotating background to the clino.
708 void GfxCore::DrawClinoBack() {
709 BeginLines();
710 for (int angle = 0; angle <= 180; angle += 90) {
711 DrawTick(angle);
714 SetColour(col_GREY);
715 PlaceIndicatorVertex(0, INDICATOR_RADIUS);
716 PlaceIndicatorVertex(0, -INDICATOR_RADIUS);
717 PlaceIndicatorVertex(0, 0);
718 PlaceIndicatorVertex(INDICATOR_RADIUS, 0);
720 EndLines();
723 void GfxCore::DrawClino() {
724 // Ticks.
725 SetColour(col_GREEN);
726 BeginLines();
727 DrawTick(0);
728 EndLines();
730 // Clino background.
731 DrawSemicircle(col_LIGHT_GREY_2, col_GREY, 0, 0, INDICATOR_RADIUS, 0);
733 // Elevation arrow.
734 DrawArrow(col_INDICATOR_2, col_INDICATOR_1);
737 void GfxCore::Draw2dIndicators()
739 // Draw the compass and elevation indicators.
741 const int centre_y = INDICATOR_BOX_SIZE / 2 + INDICATOR_OFFSET_Y;
743 const int comp_centre_x = GetCompassXPosition();
745 if (m_Compass && !m_Parent->IsExtendedElevation()) {
746 // If the user is dragging the compass with the pointer outside the
747 // compass, we snap to 45 degree multiples, and the ticks go white.
748 SetColour(m_MouseOutsideCompass ? col_WHITE : col_LIGHT_GREY_2);
749 DrawList2D(LIST_COMPASS, comp_centre_x, centre_y, -m_PanAngle);
752 const int elev_centre_x = GetClinoXPosition();
754 if (m_Clino) {
755 // If the user is dragging the clino with the pointer outside the
756 // clino, we snap to 90 degree multiples, and the ticks go white.
757 SetColour(m_MouseOutsideElev ? col_WHITE : col_LIGHT_GREY_2);
758 DrawList2D(LIST_CLINO_BACK, elev_centre_x, centre_y, 0);
759 DrawList2D(LIST_CLINO, elev_centre_x, centre_y, 90 - m_TiltAngle);
762 SetColour(TEXT_COLOUR);
764 static int triple_zero_width = 0;
765 static int height = 0;
766 if (!triple_zero_width) {
767 GetTextExtent(wxT("000"), &triple_zero_width, &height);
769 const int y_off = INDICATOR_OFFSET_Y + INDICATOR_BOX_SIZE + height / 2;
771 if (m_Compass && !m_Parent->IsExtendedElevation()) {
772 wxString str;
773 int value;
774 int brg_unit;
775 if (m_Degrees) {
776 value = int(m_PanAngle);
777 /* TRANSLATORS: degree symbol - probably should be translated to
778 * itself. */
779 brg_unit = /*°*/344;
780 } else {
781 value = int(m_PanAngle * 200.0 / 180.0);
782 /* TRANSLATORS: symbol for grad (400 grad = 360 degrees = full
783 * circle). */
784 brg_unit = /*ᵍ*/345;
786 str.Printf(wxT("%03d"), value);
787 str += wmsg(brg_unit);
788 DrawIndicatorText(comp_centre_x - triple_zero_width / 2, y_off, str);
790 // TRANSLATORS: Used in aven above the compass indicator at the lower
791 // right of the display, with a bearing below "Facing". This indicates the
792 // direction the viewer is "facing" in.
794 // Try to keep this translation short - ideally at most 10 characters -
795 // as otherwise the compass and clino will be moved further apart to
796 // make room. */
797 str = wmsg(/*Facing*/203);
798 int w;
799 GetTextExtent(str, &w, NULL);
800 DrawIndicatorText(comp_centre_x - w / 2, y_off + height, str);
803 if (m_Clino) {
804 if (m_TiltAngle == -90.0) {
805 // TRANSLATORS: Label used for "clino" in Aven when the view is
806 // from directly above.
808 // Try to keep this translation short - ideally at most 10
809 // characters - as otherwise the compass and clino will be moved
810 // further apart to make room. */
811 wxString str = wmsg(/*Plan*/432);
812 static int width = 0;
813 if (!width) {
814 GetTextExtent(str, &width, NULL);
816 int x = elev_centre_x - width / 2;
817 DrawIndicatorText(x, y_off + height / 2, str);
818 } else if (m_TiltAngle == 90.0) {
819 // TRANSLATORS: Label used for "clino" in Aven when the view is
820 // from directly below.
822 // Try to keep this translation short - ideally at most 10
823 // characters - as otherwise the compass and clino will be moved
824 // further apart to make room. */
825 wxString str = wmsg(/*Kiwi Plan*/433);
826 static int width = 0;
827 if (!width) {
828 GetTextExtent(str, &width, NULL);
830 int x = elev_centre_x - width / 2;
831 DrawIndicatorText(x, y_off + height / 2, str);
832 } else {
833 int angle;
834 wxString str;
835 int width;
836 int unit;
837 if (m_Percent) {
838 static int zero_width = 0;
839 if (!zero_width) {
840 GetTextExtent(wxT("0"), &zero_width, NULL);
842 width = zero_width;
843 if (m_TiltAngle > 89.99) {
844 angle = 1000000;
845 } else if (m_TiltAngle < -89.99) {
846 angle = -1000000;
847 } else {
848 angle = int(100 * tan(rad(m_TiltAngle)));
850 if (angle > 99999 || angle < -99999) {
851 str = angle > 0 ? wxT("+") : wxT("-");
852 /* TRANSLATORS: infinity symbol - used for the percentage gradient on
853 * vertical angles. */
854 str += wmsg(/*∞*/431);
855 } else {
856 str = angle ? wxString::Format(wxT("%+03d"), angle) : wxT("0");
858 /* TRANSLATORS: symbol for percentage gradient (100% = 45
859 * degrees = 50 grad). */
860 unit = /*%*/96;
861 } else if (m_Degrees) {
862 static int zero_zero_width = 0;
863 if (!zero_zero_width) {
864 GetTextExtent(wxT("00"), &zero_zero_width, NULL);
866 width = zero_zero_width;
867 angle = int(m_TiltAngle);
868 str = angle ? wxString::Format(wxT("%+03d"), angle) : wxT("00");
869 unit = /*°*/344;
870 } else {
871 width = triple_zero_width;
872 angle = int(m_TiltAngle * 200.0 / 180.0);
873 str = angle ? wxString::Format(wxT("%+04d"), angle) : wxT("000");
874 unit = /*ᵍ*/345;
877 int sign_offset = 0;
878 if (unit == /*%*/96) {
879 // Right align % since the width changes so much.
880 GetTextExtent(str, &sign_offset, NULL);
881 sign_offset -= width;
882 } else if (angle < 0) {
883 // Adjust horizontal position so the left of the first digit is
884 // always in the same place.
885 static int minus_width = 0;
886 if (!minus_width) {
887 GetTextExtent(wxT("-"), &minus_width, NULL);
889 sign_offset = minus_width;
890 } else if (angle > 0) {
891 // Adjust horizontal position so the left of the first digit is
892 // always in the same place.
893 static int plus_width = 0;
894 if (!plus_width) {
895 GetTextExtent(wxT("+"), &plus_width, NULL);
897 sign_offset = plus_width;
900 str += wmsg(unit);
901 DrawIndicatorText(elev_centre_x - sign_offset - width / 2, y_off, str);
903 // TRANSLATORS: Label used for "clino" in Aven when the view is
904 // neither from directly above nor from directly below. It is
905 // also used in the dialog for editing a marked position in a
906 // presentation.
908 // Try to keep this translation short - ideally at most 10
909 // characters - as otherwise the compass and clino will be moved
910 // further apart to make room. */
911 str = wmsg(/*Elevation*/118);
912 static int elevation_width = 0;
913 if (!elevation_width) {
914 GetTextExtent(str, &elevation_width, NULL);
916 int x = elev_centre_x - elevation_width / 2;
917 DrawIndicatorText(x, y_off + height, str);
922 void GfxCore::NattyDrawNames()
924 // Draw station names, without overlapping.
926 const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR);
927 const unsigned int quantised_x = GetXSize() / quantise;
928 const unsigned int quantised_y = GetYSize() / quantise;
929 const size_t buffer_size = quantised_x * quantised_y;
931 if (!m_LabelGrid) m_LabelGrid = new char[buffer_size];
933 memset((void*) m_LabelGrid, 0, buffer_size);
935 list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
936 for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
937 if (!((m_Surface && (*label)->IsSurface()) ||
938 (m_Legs && (*label)->IsUnderground()) ||
939 (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
940 // if this station isn't to be displayed, skip to the next
941 // (last case is for stns with no legs attached)
942 continue;
945 double x, y, z;
947 Transform(**label, &x, &y, &z);
948 // Check if the label is behind us (in perspective view).
949 if (z <= 0.0 || z >= 1.0) continue;
951 // Apply a small shift so that translating the view doesn't make which
952 // labels are displayed change as the resulting twinkling effect is
953 // distracting.
954 double tx, ty, tz;
955 Transform(Vector3(), &tx, &ty, &tz);
956 tx -= floor(tx / quantise) * quantise;
957 ty -= floor(ty / quantise) * quantise;
959 tx = x - tx;
960 if (tx < 0) continue;
962 ty = y - ty;
963 if (ty < 0) continue;
965 unsigned int iy = unsigned(ty) / quantise;
966 if (iy >= quantised_y) continue;
967 unsigned int width = (*label)->get_width();
968 unsigned int ix = unsigned(tx) / quantise;
969 if (ix + width >= quantised_x) continue;
971 char * test = m_LabelGrid + ix + iy * quantised_x;
972 if (memchr(test, 1, width)) continue;
974 x += 3;
975 y -= GetFontSize() / 2;
976 DrawIndicatorText((int)x, (int)y, (*label)->GetText());
978 if (iy > QUANTISE_FACTOR) iy = QUANTISE_FACTOR;
979 test -= quantised_x * iy;
980 iy += 4;
981 while (--iy && test < m_LabelGrid + buffer_size) {
982 memset(test, 1, width);
983 test += quantised_x;
988 void GfxCore::SimpleDrawNames()
990 // Draw all station names, without worrying about overlaps
991 list<LabelInfo*>::const_iterator label = m_Parent->GetLabels();
992 for ( ; label != m_Parent->GetLabelsEnd(); ++label) {
993 if (!((m_Surface && (*label)->IsSurface()) ||
994 (m_Legs && (*label)->IsUnderground()) ||
995 (!(*label)->IsSurface() && !(*label)->IsUnderground()))) {
996 // if this station isn't to be displayed, skip to the next
997 // (last case is for stns with no legs attached)
998 continue;
1001 double x, y, z;
1002 Transform(**label, &x, &y, &z);
1004 // Check if the label is behind us (in perspective view).
1005 if (z <= 0) continue;
1007 x += 3;
1008 y -= GetFontSize() / 2;
1009 DrawIndicatorText((int)x, (int)y, (*label)->GetText());
1013 void GfxCore::DrawColourKey(int num_bands, const wxString & other, const wxString & units)
1015 int total_block_height =
1016 KEY_BLOCK_HEIGHT * (num_bands == 1 ? num_bands : num_bands - 1);
1017 if (!other.empty()) total_block_height += KEY_BLOCK_HEIGHT * 2;
1018 if (!units.empty()) total_block_height += KEY_BLOCK_HEIGHT;
1020 const int bottom = -total_block_height;
1022 int size = 0;
1023 if (!other.empty()) GetTextExtent(other, &size, NULL);
1024 int band;
1025 for (band = 0; band < num_bands; ++band) {
1026 int x;
1027 GetTextExtent(key_legends[band], &x, NULL);
1028 if (x > size) size = x;
1031 int left = -KEY_BLOCK_WIDTH - size;
1033 key_lowerleft[m_ColourBy].x = left - KEY_EXTRA_LEFT_MARGIN;
1034 key_lowerleft[m_ColourBy].y = bottom;
1036 int y = bottom;
1037 if (!units.empty()) y += KEY_BLOCK_HEIGHT;
1039 if (!other.empty()) {
1040 DrawRectangle(NODATA_COLOUR, col_BLACK,
1041 left, y,
1042 KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1043 y += KEY_BLOCK_HEIGHT * 2;
1046 int start = y;
1047 if (num_bands == 1) {
1048 DrawShadedRectangle(GetPen(0), GetPen(0), left, y,
1049 KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1050 y += KEY_BLOCK_HEIGHT;
1051 } else {
1052 for (band = 0; band < num_bands - 1; ++band) {
1053 DrawShadedRectangle(GetPen(band), GetPen(band + 1), left, y,
1054 KEY_BLOCK_WIDTH, KEY_BLOCK_HEIGHT);
1055 y += KEY_BLOCK_HEIGHT;
1059 SetColour(col_BLACK);
1060 BeginPolyline();
1061 PlaceIndicatorVertex(left, y);
1062 PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, y);
1063 PlaceIndicatorVertex(left + KEY_BLOCK_WIDTH, start);
1064 PlaceIndicatorVertex(left, start);
1065 PlaceIndicatorVertex(left, y);
1066 EndPolyline();
1068 SetColour(TEXT_COLOUR);
1070 y = bottom;
1071 if (!units.empty()) {
1072 GetTextExtent(units, &size, NULL);
1073 DrawIndicatorText(left + (KEY_BLOCK_WIDTH - size) / 2, y, units);
1074 y += KEY_BLOCK_HEIGHT;
1076 y -= GetFontSize() / 2;
1077 left += KEY_BLOCK_WIDTH + 5;
1079 if (!other.empty()) {
1080 y += KEY_BLOCK_HEIGHT / 2;
1081 DrawIndicatorText(left, y, other);
1082 y += KEY_BLOCK_HEIGHT * 2 - KEY_BLOCK_HEIGHT / 2;
1085 if (num_bands == 1) {
1086 y += KEY_BLOCK_HEIGHT / 2;
1087 DrawIndicatorText(left, y, key_legends[0]);
1088 } else {
1089 for (band = 0; band < num_bands; ++band) {
1090 DrawIndicatorText(left, y, key_legends[band]);
1091 y += KEY_BLOCK_HEIGHT;
1096 void GfxCore::DrawDepthKey()
1098 Double z_ext = m_Parent->GetDepthExtent();
1099 int num_bands = 1;
1100 int sf = 0;
1101 if (z_ext > 0.0) {
1102 num_bands = GetNumColourBands();
1103 Double z_range = z_ext;
1104 if (!m_Metric) z_range /= METRES_PER_FOOT;
1105 sf = max(0, 1 - (int)floor(log10(z_range)));
1108 Double z_min = m_Parent->GetDepthMin() + m_Parent->GetOffset().GetZ();
1109 for (int band = 0; band < num_bands; ++band) {
1110 Double z = z_min;
1111 if (band)
1112 z += z_ext * band / (num_bands - 1);
1114 if (!m_Metric)
1115 z /= METRES_PER_FOOT;
1117 key_legends[band].Printf(wxT("%.*f"), sf, z);
1120 DrawColourKey(num_bands, wxString(), wmsg(m_Metric ? /*m*/424: /*ft*/428));
1123 void GfxCore::DrawDateKey()
1125 int num_bands;
1126 if (!HasDateInformation()) {
1127 num_bands = 0;
1128 } else {
1129 int date_ext = m_Parent->GetDateExtent();
1130 if (date_ext == 0) {
1131 num_bands = 1;
1132 } else {
1133 num_bands = GetNumColourBands();
1135 for (int band = 0; band < num_bands; ++band) {
1136 int y, m, d;
1137 int days = m_Parent->GetDateMin();
1138 if (band)
1139 days += date_ext * band / (num_bands - 1);
1140 ymd_from_days_since_1900(days, &y, &m, &d);
1141 key_legends[band].Printf(wxT("%04d-%02d-%02d"), y, m, d);
1145 wxString other;
1146 if (!m_Parent->HasCompleteDateInfo()) {
1147 /* TRANSLATORS: Used in the "colour key" for "colour by date" if there
1148 * are surveys without date information. Try to keep this fairly short.
1150 other = wmsg(/*Undated*/221);
1153 DrawColourKey(num_bands, other, wxString());
1156 void GfxCore::DrawErrorKey()
1158 int num_bands;
1159 if (HasErrorInformation()) {
1160 // Use fixed colours for each error factor so it's directly visually
1161 // comparable between surveys.
1162 num_bands = GetNumColourBands();
1163 for (int band = 0; band < num_bands; ++band) {
1164 double E = MAX_ERROR * band / (num_bands - 1);
1165 key_legends[band].Printf(wxT("%.2f"), E);
1167 } else {
1168 num_bands = 0;
1171 // Always show the "Not in loop" legend for now (FIXME).
1172 /* TRANSLATORS: Used in the "colour key" for "colour by error" for surveys
1173 * which aren’t part of a loop and so have no error information. Try to keep
1174 * this fairly short. */
1175 DrawColourKey(num_bands, wmsg(/*Not in loop*/290), wxString());
1178 void GfxCore::DrawGradientKey()
1180 int num_bands;
1181 // Use fixed colours for each gradient so it's directly visually comparable
1182 // between surveys.
1183 num_bands = GetNumColourBands();
1184 wxString units = wmsg(m_Degrees ? /*°*/344 : /*ᵍ*/345);
1185 for (int band = 0; band < num_bands; ++band) {
1186 double gradient = double(band) / (num_bands - 1);
1187 if (m_Degrees) {
1188 gradient *= 90.0;
1189 } else {
1190 gradient *= 100.0;
1192 key_legends[band].Printf(wxT("%.f%s"), gradient, units);
1195 DrawColourKey(num_bands, wxString(), wxString());
1198 void GfxCore::DrawLengthKey()
1200 int num_bands;
1201 // Use fixed colours for each length so it's directly visually comparable
1202 // between surveys.
1203 num_bands = GetNumColourBands();
1204 for (int band = 0; band < num_bands; ++band) {
1205 double len = pow(10, LOG_LEN_MAX * band / (num_bands - 1));
1206 if (!m_Metric) {
1207 len /= METRES_PER_FOOT;
1209 key_legends[band].Printf(wxT("%.1f"), len);
1212 DrawColourKey(num_bands, wxString(), wmsg(m_Metric ? /*m*/424: /*ft*/428));
1215 void GfxCore::DrawScaleBar()
1217 // Draw the scalebar.
1218 if (GetPerspective()) return;
1220 // Calculate how many metres of survey are currently displayed across the
1221 // screen.
1222 Double across_screen = SurveyUnitsAcrossViewport();
1224 double f = double(GetClinoXPosition() - INDICATOR_BOX_SIZE / 2 - SCALE_BAR_OFFSET_X) / GetXSize();
1225 if (f > 0.75) {
1226 f = 0.75;
1227 } else if (f < 0.5) {
1228 // Stop it getting squeezed to nothing.
1229 // FIXME: In this case we should probably move the compass and clino up
1230 // to make room rather than letting stuff overlap.
1231 f = 0.5;
1234 // Convert to imperial measurements if required.
1235 Double multiplier = 1.0;
1236 if (!m_Metric) {
1237 across_screen /= METRES_PER_FOOT;
1238 multiplier = METRES_PER_FOOT;
1239 if (across_screen >= 5280.0 / f) {
1240 across_screen /= 5280.0;
1241 multiplier *= 5280.0;
1245 // Calculate the length of the scale bar.
1246 Double size_snap = pow(10.0, floor(log10(f * across_screen)));
1247 Double t = across_screen * f / size_snap;
1248 if (t >= 5.0) {
1249 size_snap *= 5.0;
1250 } else if (t >= 2.0) {
1251 size_snap *= 2.0;
1254 if (!m_Metric) size_snap *= multiplier;
1256 // Actual size of the thing in pixels:
1257 int size = int((size_snap / SurveyUnitsAcrossViewport()) * GetXSize());
1258 m_ScaleBarWidth = size;
1260 // Draw it...
1261 const int end_y = SCALE_BAR_OFFSET_Y + SCALE_BAR_HEIGHT;
1262 int interval = size / 10;
1264 gla_colour col = col_WHITE;
1265 for (int ix = 0; ix < 10; ix++) {
1266 int x = SCALE_BAR_OFFSET_X + int(ix * ((Double) size / 10.0));
1268 DrawRectangle(col, col, x, end_y, interval + 2, SCALE_BAR_HEIGHT);
1270 col = (col == col_WHITE) ? col_GREY : col_WHITE;
1273 // Add labels.
1274 wxString str;
1275 int units;
1276 if (m_Metric) {
1277 Double km = size_snap * 1e-3;
1278 if (km >= 1.0) {
1279 size_snap = km;
1280 /* TRANSLATORS: abbreviation for "kilometres" (unit of length),
1281 * used e.g. "5km".
1283 * If there should be a space between the number and this, include
1284 * one in the translation. */
1285 units = /*km*/423;
1286 } else if (size_snap >= 1.0) {
1287 /* TRANSLATORS: abbreviation for "metres" (unit of length), used
1288 * e.g. "10m".
1290 * If there should be a space between the number and this, include
1291 * one in the translation. */
1292 units = /*m*/424;
1293 } else {
1294 size_snap *= 1e2;
1295 /* TRANSLATORS: abbreviation for "centimetres" (unit of length),
1296 * used e.g. "50cm".
1298 * If there should be a space between the number and this, include
1299 * one in the translation. */
1300 units = /*cm*/425;
1302 } else {
1303 size_snap /= METRES_PER_FOOT;
1304 Double miles = size_snap / 5280.0;
1305 if (miles >= 1.0) {
1306 size_snap = miles;
1307 if (size_snap >= 2.0) {
1308 /* TRANSLATORS: abbreviation for "miles" (unit of length,
1309 * plural), used e.g. "2 miles".
1311 * If there should be a space between the number and this,
1312 * include one in the translation. */
1313 units = /* miles*/426;
1314 } else {
1315 /* TRANSLATORS: abbreviation for "mile" (unit of length,
1316 * singular), used e.g. "1 mile".
1318 * If there should be a space between the number and this,
1319 * include one in the translation. */
1320 units = /* mile*/427;
1322 } else if (size_snap >= 1.0) {
1323 /* TRANSLATORS: abbreviation for "feet" (unit of length), used e.g.
1324 * as "10ft".
1326 * If there should be a space between the number and this, include
1327 * one in the translation. */
1328 units = /*ft*/428;
1329 } else {
1330 size_snap *= 12.0;
1331 /* TRANSLATORS: abbreviation for "inches" (unit of length), used
1332 * e.g. as "6in".
1334 * If there should be a space between the number and this, include
1335 * one in the translation. */
1336 units = /*in*/429;
1339 if (size_snap >= 1.0) {
1340 str.Printf(wxT("%.f%s"), size_snap, wmsg(units).c_str());
1341 } else {
1342 int sf = -(int)floor(log10(size_snap));
1343 str.Printf(wxT("%.*f%s"), sf, size_snap, wmsg(units).c_str());
1346 int text_width, text_height;
1347 GetTextExtent(str, &text_width, &text_height);
1348 const int text_y = end_y - text_height + 1;
1349 SetColour(TEXT_COLOUR);
1350 DrawIndicatorText(SCALE_BAR_OFFSET_X, text_y, wxT("0"));
1351 DrawIndicatorText(SCALE_BAR_OFFSET_X + size - text_width, text_y, str);
1354 bool GfxCore::CheckHitTestGrid(const wxPoint& point, bool centre)
1356 if (Animating()) return false;
1358 if (point.x < 0 || point.x >= GetXSize() ||
1359 point.y < 0 || point.y >= GetYSize()) {
1360 return false;
1363 SetDataTransform();
1365 if (!m_HitTestGridValid) CreateHitTestGrid();
1367 int grid_x = point.x * HITTEST_SIZE / (GetXSize() + 1);
1368 int grid_y = point.y * HITTEST_SIZE / (GetYSize() + 1);
1370 LabelInfo *best = NULL;
1371 int dist_sqrd = sqrd_measure_threshold;
1372 int square = grid_x + grid_y * HITTEST_SIZE;
1373 list<LabelInfo*>::iterator iter = m_PointGrid[square].begin();
1375 while (iter != m_PointGrid[square].end()) {
1376 LabelInfo *pt = *iter++;
1378 double cx, cy, cz;
1380 Transform(*pt, &cx, &cy, &cz);
1382 cy = GetYSize() - cy;
1384 int dx = point.x - int(cx);
1385 int ds = dx * dx;
1386 if (ds >= dist_sqrd) continue;
1387 int dy = point.y - int(cy);
1389 ds += dy * dy;
1390 if (ds >= dist_sqrd) continue;
1392 dist_sqrd = ds;
1393 best = pt;
1395 if (ds == 0) break;
1398 if (best) {
1399 m_Parent->ShowInfo(best, m_there);
1400 if (centre) {
1401 // FIXME: allow Ctrl-Click to not set there or something?
1402 CentreOn(*best);
1403 WarpPointer(GetXSize() / 2, GetYSize() / 2);
1404 SetThere(best);
1405 m_Parent->SelectTreeItem(best);
1407 } else {
1408 // Left-clicking not on a survey cancels the measuring line.
1409 if (centre) {
1410 ClearTreeSelection();
1411 } else {
1412 m_Parent->ShowInfo(best, m_there);
1413 double x, y, z;
1414 ReverseTransform(point.x, GetYSize() - point.y, &x, &y, &z);
1415 temp_here.assign(Vector3(x, y, z));
1416 SetHere(&temp_here);
1420 return best;
1423 void GfxCore::OnSize(wxSizeEvent& event)
1425 // Handle a change in window size.
1426 wxSize size = event.GetSize();
1428 if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
1429 // Before things are fully initialised, we sometimes get a bogus
1430 // resize message...
1431 // FIXME have changes in MainFrm cured this? It still happens with
1432 // 1.0.32 and wxGTK 2.5.2 (load a file from the command line).
1433 // With 1.1.6 and wxGTK 2.4.2 we only get negative sizes if MainFrm
1434 // is resized such that the GfxCore window isn't visible.
1435 //printf("OnSize(%d,%d)\n", size.GetWidth(), size.GetHeight());
1436 return;
1439 event.Skip();
1441 if (m_DoneFirstShow) {
1442 TryToFreeArrays();
1444 m_HitTestGridValid = false;
1446 ForceRefresh();
1450 void GfxCore::DefaultParameters()
1452 // Set default viewing parameters.
1454 m_Surface = false;
1455 if (!m_Parent->HasUndergroundLegs()) {
1456 if (m_Parent->HasSurfaceLegs()) {
1457 // If there are surface legs, but no underground legs, turn
1458 // surface surveys on.
1459 m_Surface = true;
1460 } else {
1461 // If there are no legs (e.g. after loading a .pos file), turn
1462 // crosses on.
1463 m_Crosses = true;
1467 m_PanAngle = 0.0;
1468 if (m_Parent->IsExtendedElevation()) {
1469 m_TiltAngle = 0.0;
1470 } else {
1471 m_TiltAngle = -90.0;
1474 SetRotation(m_PanAngle, m_TiltAngle);
1475 SetTranslation(Vector3());
1477 m_RotationStep = 30.0;
1478 m_Rotating = false;
1479 m_SwitchingTo = 0;
1480 m_Entrances = false;
1481 m_FixedPts = false;
1482 m_ExportedPts = false;
1483 m_Grid = false;
1484 m_BoundingBox = false;
1485 m_Tubes = false;
1486 if (GetPerspective()) TogglePerspective();
1488 // Set the initial scale.
1489 SetScale(initial_scale);
1492 void GfxCore::Defaults()
1494 // Restore default scale, rotation and translation parameters.
1495 DefaultParameters();
1497 // Invalidate all the cached lists.
1498 GLACanvas::FirstShow();
1500 ForceRefresh();
1503 void GfxCore::Animate()
1505 // Don't show pointer coordinates while animating.
1506 // FIXME : only do this when we *START* animating! Use a static copy
1507 // of the value of "Animating()" last time we were here to track this?
1508 // MainFrm now checks if we're trying to clear already cleared labels
1509 // and just returns, but it might be simpler to check here!
1510 ClearCoords();
1511 m_Parent->ShowInfo();
1513 long t;
1514 if (movie) {
1515 ReadPixels(movie->GetWidth(), movie->GetHeight(), movie->GetBuffer());
1516 if (!movie->AddFrame()) {
1517 wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
1518 delete movie;
1519 movie = NULL;
1520 presentation_mode = 0;
1521 return;
1523 t = 1000 / 25; // 25 frames per second
1524 } else {
1525 static long t_prev = 0;
1526 t = timer.Time();
1527 // Avoid redrawing twice in the same frame.
1528 long delta_t = (t_prev == 0 ? 1000 / MAX_FRAMERATE : t - t_prev);
1529 if (delta_t < 1000 / MAX_FRAMERATE)
1530 return;
1531 t_prev = t;
1532 if (presentation_mode == PLAYING && pres_speed != 0.0)
1533 t = delta_t;
1536 if (presentation_mode == PLAYING && pres_speed != 0.0) {
1537 // FIXME: It would probably be better to work relative to the time we
1538 // passed the last mark, but that's complicated by the speed
1539 // potentially changing (or even the direction of playback reversing)
1540 // at any point during playback.
1541 Double tick = t * 0.001 * fabs(pres_speed);
1542 while (tick >= next_mark_time) {
1543 tick -= next_mark_time;
1544 this_mark_total = 0;
1545 PresentationMark prev_mark = next_mark;
1546 if (prev_mark.angle < 0) prev_mark.angle += 360.0;
1547 else if (prev_mark.angle >= 360.0) prev_mark.angle -= 360.0;
1548 if (pres_reverse)
1549 next_mark = m_Parent->GetPresMark(MARK_PREV);
1550 else
1551 next_mark = m_Parent->GetPresMark(MARK_NEXT);
1552 if (!next_mark.is_valid()) {
1553 SetView(prev_mark);
1554 presentation_mode = 0;
1555 if (movie && !movie->Close()) {
1556 wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
1558 delete movie;
1559 movie = NULL;
1560 break;
1563 double tmp = (pres_reverse ? prev_mark.time : next_mark.time);
1564 if (tmp > 0) {
1565 next_mark_time = tmp;
1566 } else {
1567 double d = (next_mark - prev_mark).magnitude();
1568 // FIXME: should ignore component of d which is unseen in
1569 // non-perspective mode?
1570 next_mark_time = sqrd(d / 30.0);
1571 double a = next_mark.angle - prev_mark.angle;
1572 if (a > 180.0) {
1573 next_mark.angle -= 360.0;
1574 a = 360.0 - a;
1575 } else if (a < -180.0) {
1576 next_mark.angle += 360.0;
1577 a += 360.0;
1578 } else {
1579 a = fabs(a);
1581 next_mark_time += sqrd(a / 60.0);
1582 double ta = fabs(next_mark.tilt_angle - prev_mark.tilt_angle);
1583 next_mark_time += sqrd(ta / 60.0);
1584 double s = fabs(log(next_mark.scale) - log(prev_mark.scale));
1585 next_mark_time += sqrd(s / 2.0);
1586 next_mark_time = sqrt(next_mark_time);
1587 // was: next_mark_time = max(max(d / 30, s / 2), max(a, ta) / 60);
1588 //printf("*** %.6f from (\nd: %.6f\ns: %.6f\na: %.6f\nt: %.6f )\n",
1589 // next_mark_time, d/30.0, s/2.0, a/60.0, ta/60.0);
1590 if (tmp < 0) next_mark_time /= -tmp;
1594 if (presentation_mode) {
1595 // Advance position towards next_mark
1596 double p = tick / next_mark_time;
1597 double q = 1 - p;
1598 PresentationMark here = GetView();
1599 if (next_mark.angle < 0) {
1600 if (here.angle >= next_mark.angle + 360.0)
1601 here.angle -= 360.0;
1602 } else if (next_mark.angle >= 360.0) {
1603 if (here.angle <= next_mark.angle - 360.0)
1604 here.angle += 360.0;
1606 here.assign(q * here + p * next_mark);
1607 here.angle = q * here.angle + p * next_mark.angle;
1608 if (here.angle < 0) here.angle += 360.0;
1609 else if (here.angle >= 360.0) here.angle -= 360.0;
1610 here.tilt_angle = q * here.tilt_angle + p * next_mark.tilt_angle;
1611 here.scale = exp(q * log(here.scale) + p * log(next_mark.scale));
1612 SetView(here);
1613 this_mark_total += tick;
1614 next_mark_time -= tick;
1617 ForceRefresh();
1618 return;
1621 // When rotating...
1622 if (m_Rotating) {
1623 Double step = base_pan + (t - base_pan_time) * 1e-3 * m_RotationStep - m_PanAngle;
1624 TurnCave(step);
1627 if (m_SwitchingTo == PLAN) {
1628 // When switching to plan view...
1629 Double step = base_tilt - (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1630 TiltCave(step);
1631 if (m_TiltAngle == -90.0) {
1632 m_SwitchingTo = 0;
1634 } else if (m_SwitchingTo == ELEVATION) {
1635 // When switching to elevation view...
1636 Double step;
1637 if (m_TiltAngle > 0.0) {
1638 step = base_tilt - (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1639 } else {
1640 step = base_tilt + (t - base_tilt_time) * 1e-3 * 90.0 - m_TiltAngle;
1642 if (fabs(step) >= fabs(m_TiltAngle)) {
1643 m_SwitchingTo = 0;
1644 step = -m_TiltAngle;
1646 TiltCave(step);
1647 } else if (m_SwitchingTo) {
1648 // Rotate the shortest way around to the destination angle. If we're
1649 // 180 off, we favour turning anticlockwise, as auto-rotation does by
1650 // default.
1651 Double target = (m_SwitchingTo - NORTH) * 90;
1652 Double diff = target - m_PanAngle;
1653 diff = fmod(diff, 360);
1654 if (diff <= -180)
1655 diff += 360;
1656 else if (diff > 180)
1657 diff -= 360;
1658 if (m_RotationStep < 0 && diff == 180.0)
1659 diff = -180.0;
1660 Double step = base_pan - m_PanAngle;
1661 Double delta = (t - base_pan_time) * 1e-3 * fabs(m_RotationStep);
1662 if (diff > 0) {
1663 step += delta;
1664 } else {
1665 step -= delta;
1667 step = fmod(step, 360);
1668 if (step <= -180)
1669 step += 360;
1670 else if (step > 180)
1671 step -= 360;
1672 if (fabs(step) >= fabs(diff)) {
1673 m_SwitchingTo = 0;
1674 step = diff;
1676 TurnCave(step);
1679 ForceRefresh();
1682 // How much to allow around the box - this is because of the ring shape
1683 // at one end of the line.
1684 static const int HIGHLIGHTED_PT_SIZE = 2; // FIXME: tie in to blob and ring size
1685 #define MARGIN (HIGHLIGHTED_PT_SIZE * 2 + 1)
1686 void GfxCore::RefreshLine(const Point *a, const Point *b, const Point *c)
1688 #ifdef __WXMSW__
1689 (void)a;
1690 (void)b;
1691 (void)c;
1692 // FIXME: We get odd redraw artifacts if we just update the line, and
1693 // redrawing the whole scene doesn't actually seem to be measurably
1694 // slower. That may not be true with software rendering though...
1695 ForceRefresh();
1696 #else
1697 // Best of all might be to copy the window contents before we draw the
1698 // line, then replace each time we redraw.
1700 // Calculate the minimum rectangle which includes the old and new
1701 // measuring lines to minimise the redraw time
1702 int l = INT_MAX, r = INT_MIN, u = INT_MIN, d = INT_MAX;
1703 double X, Y, Z;
1704 if (a) {
1705 if (!Transform(*a, &X, &Y, &Z)) {
1706 printf("oops\n");
1707 } else {
1708 int x = int(X);
1709 int y = GetYSize() - 1 - int(Y);
1710 l = x;
1711 r = x;
1712 u = y;
1713 d = y;
1716 if (b) {
1717 if (!Transform(*b, &X, &Y, &Z)) {
1718 printf("oops\n");
1719 } else {
1720 int x = int(X);
1721 int y = GetYSize() - 1 - int(Y);
1722 l = min(l, x);
1723 r = max(r, x);
1724 u = max(u, y);
1725 d = min(d, y);
1728 if (c) {
1729 if (!Transform(*c, &X, &Y, &Z)) {
1730 printf("oops\n");
1731 } else {
1732 int x = int(X);
1733 int y = GetYSize() - 1 - int(Y);
1734 l = min(l, x);
1735 r = max(r, x);
1736 u = max(u, y);
1737 d = min(d, y);
1740 l -= MARGIN;
1741 r += MARGIN;
1742 u += MARGIN;
1743 d -= MARGIN;
1744 RefreshRect(wxRect(l, d, r - l, u - d), false);
1745 #endif
1748 void GfxCore::SetHereFromTree(const LabelInfo * p)
1750 SetHere(p);
1751 m_Parent->ShowInfo(m_here, m_there);
1754 void GfxCore::SetHere(const LabelInfo *p)
1756 if (p == m_here) return;
1757 bool line_active = MeasuringLineActive();
1758 const LabelInfo * old = m_here;
1759 m_here = p;
1760 if (line_active || MeasuringLineActive())
1761 RefreshLine(old, m_there, m_here);
1764 void GfxCore::SetThere(const LabelInfo * p)
1766 if (p == m_there) return;
1767 const LabelInfo * old = m_there;
1768 m_there = p;
1769 RefreshLine(m_here, old, m_there);
1772 void GfxCore::CreateHitTestGrid()
1774 if (!m_PointGrid) {
1775 // Initialise hit-test grid.
1776 m_PointGrid = new list<LabelInfo*>[HITTEST_SIZE * HITTEST_SIZE];
1777 } else {
1778 // Clear hit-test grid.
1779 for (int i = 0; i < HITTEST_SIZE * HITTEST_SIZE; i++) {
1780 m_PointGrid[i].clear();
1784 // Fill the grid.
1785 list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
1786 list<LabelInfo*>::const_iterator end = m_Parent->GetLabelsEnd();
1787 while (pos != end) {
1788 LabelInfo* label = *pos++;
1790 if (!((m_Surface && label->IsSurface()) ||
1791 (m_Legs && label->IsUnderground()) ||
1792 (!label->IsSurface() && !label->IsUnderground()))) {
1793 // if this station isn't to be displayed, skip to the next
1794 // (last case is for stns with no legs attached)
1795 continue;
1798 // Calculate screen coordinates.
1799 double cx, cy, cz;
1800 Transform(*label, &cx, &cy, &cz);
1801 if (cx < 0 || cx >= GetXSize()) continue;
1802 if (cy < 0 || cy >= GetYSize()) continue;
1804 cy = GetYSize() - cy;
1806 // On-screen, so add to hit-test grid...
1807 int grid_x = int(cx * HITTEST_SIZE / (GetXSize() + 1));
1808 int grid_y = int(cy * HITTEST_SIZE / (GetYSize() + 1));
1810 m_PointGrid[grid_x + grid_y * HITTEST_SIZE].push_back(label);
1813 m_HitTestGridValid = true;
1817 // Methods for controlling the orientation of the survey
1820 void GfxCore::TurnCave(Double angle)
1822 // Turn the cave around its z-axis by a given angle.
1824 m_PanAngle += angle;
1825 // Wrap to range [0, 360):
1826 m_PanAngle = fmod(m_PanAngle, 360.0);
1827 if (m_PanAngle < 0.0) {
1828 m_PanAngle += 360.0;
1831 m_HitTestGridValid = false;
1832 if (m_here && m_here == &temp_here) SetHere();
1834 SetRotation(m_PanAngle, m_TiltAngle);
1837 void GfxCore::TurnCaveTo(Double angle)
1839 if (m_Rotating) {
1840 // If we're rotating, jump to the specified angle.
1841 TurnCave(angle - m_PanAngle);
1842 SetPanBase();
1843 return;
1846 int new_switching_to = ((int)angle) / 90 + NORTH;
1847 if (new_switching_to == m_SwitchingTo) {
1848 // A second order to switch takes us there right away
1849 TurnCave(angle - m_PanAngle);
1850 m_SwitchingTo = 0;
1851 ForceRefresh();
1852 } else {
1853 SetPanBase();
1854 m_SwitchingTo = new_switching_to;
1858 void GfxCore::TiltCave(Double tilt_angle)
1860 // Tilt the cave by a given angle.
1861 if (m_TiltAngle + tilt_angle > 90.0) {
1862 m_TiltAngle = 90.0;
1863 } else if (m_TiltAngle + tilt_angle < -90.0) {
1864 m_TiltAngle = -90.0;
1865 } else {
1866 m_TiltAngle += tilt_angle;
1869 m_HitTestGridValid = false;
1870 if (m_here && m_here == &temp_here) SetHere();
1872 SetRotation(m_PanAngle, m_TiltAngle);
1875 void GfxCore::TranslateCave(int dx, int dy)
1877 AddTranslationScreenCoordinates(dx, dy);
1878 m_HitTestGridValid = false;
1880 if (m_here && m_here == &temp_here) SetHere();
1882 ForceRefresh();
1885 void GfxCore::DragFinished()
1887 m_MouseOutsideCompass = m_MouseOutsideElev = false;
1888 ForceRefresh();
1891 void GfxCore::ClearCoords()
1893 m_Parent->ClearCoords();
1896 void GfxCore::SetCoords(wxPoint point)
1898 // We can't work out 2D coordinates from a perspective view, and it
1899 // doesn't really make sense to show coordinates while we're animating.
1900 if (GetPerspective() || Animating()) return;
1902 // Update the coordinate or altitude display, given the (x, y) position in
1903 // window coordinates. The relevant display is updated depending on
1904 // whether we're in plan or elevation view.
1906 double cx, cy, cz;
1908 SetDataTransform();
1909 ReverseTransform(point.x, GetYSize() - 1 - point.y, &cx, &cy, &cz);
1911 if (ShowingPlan()) {
1912 m_Parent->SetCoords(cx + m_Parent->GetOffset().GetX(),
1913 cy + m_Parent->GetOffset().GetY(),
1914 m_there);
1915 } else if (ShowingElevation()) {
1916 m_Parent->SetAltitude(cz + m_Parent->GetOffset().GetZ(),
1917 m_there);
1918 } else {
1919 m_Parent->ClearCoords();
1923 int GfxCore::GetCompassWidth() const
1925 static int result = 0;
1926 if (result == 0) {
1927 result = INDICATOR_BOX_SIZE;
1928 int width;
1929 const wxString & msg = wmsg(/*Facing*/203);
1930 GetTextExtent(msg, &width, NULL);
1931 if (width > result) result = width;
1933 return result;
1936 int GfxCore::GetClinoWidth() const
1938 static int result = 0;
1939 if (result == 0) {
1940 result = INDICATOR_BOX_SIZE;
1941 int width;
1942 const wxString & msg1 = wmsg(/*Plan*/432);
1943 GetTextExtent(msg1, &width, NULL);
1944 if (width > result) result = width;
1945 const wxString & msg2 = wmsg(/*Kiwi Plan*/433);
1946 GetTextExtent(msg2, &width, NULL);
1947 if (width > result) result = width;
1948 const wxString & msg3 = wmsg(/*Elevation*/118);
1949 GetTextExtent(msg3, &width, NULL);
1950 if (width > result) result = width;
1952 return result;
1955 int GfxCore::GetCompassXPosition() const
1957 // Return the x-coordinate of the centre of the compass in window
1958 // coordinates.
1959 return GetXSize() - INDICATOR_OFFSET_X - GetCompassWidth() / 2;
1962 int GfxCore::GetClinoXPosition() const
1964 // Return the x-coordinate of the centre of the compass in window
1965 // coordinates.
1966 return GetXSize() - GetClinoOffset() - GetClinoWidth() / 2;
1969 int GfxCore::GetIndicatorYPosition() const
1971 // Return the y-coordinate of the centre of the indicators in window
1972 // coordinates.
1973 return GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE / 2;
1976 int GfxCore::GetIndicatorRadius() const
1978 // Return the radius of each indicator.
1979 return (INDICATOR_BOX_SIZE - INDICATOR_MARGIN * 2) / 2;
1982 bool GfxCore::PointWithinCompass(wxPoint point) const
1984 // Determine whether a point (in window coordinates) lies within the
1985 // compass.
1986 if (!ShowingCompass()) return false;
1988 glaCoord dx = point.x - GetCompassXPosition();
1989 glaCoord dy = point.y - GetIndicatorYPosition();
1990 glaCoord radius = GetIndicatorRadius();
1992 return (dx * dx + dy * dy <= radius * radius);
1995 bool GfxCore::PointWithinClino(wxPoint point) const
1997 // Determine whether a point (in window coordinates) lies within the clino.
1998 if (!ShowingClino()) return false;
2000 glaCoord dx = point.x - GetClinoXPosition();
2001 glaCoord dy = point.y - GetIndicatorYPosition();
2002 glaCoord radius = GetIndicatorRadius();
2004 return (dx * dx + dy * dy <= radius * radius);
2007 bool GfxCore::PointWithinScaleBar(wxPoint point) const
2009 // Determine whether a point (in window coordinates) lies within the scale
2010 // bar.
2011 if (!ShowingScaleBar()) return false;
2013 return (point.x >= SCALE_BAR_OFFSET_X &&
2014 point.x <= SCALE_BAR_OFFSET_X + m_ScaleBarWidth &&
2015 point.y <= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT &&
2016 point.y >= GetYSize() - SCALE_BAR_OFFSET_Y - SCALE_BAR_HEIGHT*2);
2019 bool GfxCore::PointWithinColourKey(wxPoint point) const
2021 // Determine whether a point (in window coordinates) lies within the key.
2022 point.x -= GetXSize() - KEY_OFFSET_X;
2023 point.y = KEY_OFFSET_Y - point.y;
2024 return (point.x >= key_lowerleft[m_ColourBy].x && point.x <= 0 &&
2025 point.y >= key_lowerleft[m_ColourBy].y && point.y <= 0);
2028 void GfxCore::SetCompassFromPoint(wxPoint point)
2030 // Given a point in window coordinates, set the heading of the survey. If
2031 // the point is outside the compass, it snaps to 45 degree intervals;
2032 // otherwise it operates as normal.
2034 wxCoord dx = point.x - GetCompassXPosition();
2035 wxCoord dy = point.y - GetIndicatorYPosition();
2036 wxCoord radius = GetIndicatorRadius();
2038 double angle = deg(atan2(double(dx), double(dy))) - 180.0;
2039 if (dx * dx + dy * dy <= radius * radius) {
2040 TurnCave(angle - m_PanAngle);
2041 m_MouseOutsideCompass = false;
2042 } else {
2043 TurnCave(int(angle / 45.0) * 45.0 - m_PanAngle);
2044 m_MouseOutsideCompass = true;
2047 ForceRefresh();
2050 void GfxCore::SetClinoFromPoint(wxPoint point)
2052 // Given a point in window coordinates, set the elevation of the survey.
2053 // If the point is outside the clino, it snaps to 90 degree intervals;
2054 // otherwise it operates as normal.
2056 glaCoord dx = point.x - GetClinoXPosition();
2057 glaCoord dy = point.y - GetIndicatorYPosition();
2058 glaCoord radius = GetIndicatorRadius();
2060 if (dx >= 0 && dx * dx + dy * dy <= radius * radius) {
2061 TiltCave(-deg(atan2(double(dy), double(dx))) - m_TiltAngle);
2062 m_MouseOutsideElev = false;
2063 } else if (dy >= INDICATOR_MARGIN) {
2064 TiltCave(-90.0 - m_TiltAngle);
2065 m_MouseOutsideElev = true;
2066 } else if (dy <= -INDICATOR_MARGIN) {
2067 TiltCave(90.0 - m_TiltAngle);
2068 m_MouseOutsideElev = true;
2069 } else {
2070 TiltCave(-m_TiltAngle);
2071 m_MouseOutsideElev = true;
2074 ForceRefresh();
2077 void GfxCore::SetScaleBarFromOffset(wxCoord dx)
2079 // Set the scale of the survey, given an offset as to how much the mouse has
2080 // been dragged over the scalebar since the last scale change.
2082 SetScale((m_ScaleBarWidth + dx) * m_Scale / m_ScaleBarWidth);
2083 ForceRefresh();
2086 void GfxCore::RedrawIndicators()
2088 // Redraw the compass and clino indicators.
2090 int total_width = GetCompassWidth() + INDICATOR_GAP + GetClinoWidth();
2091 RefreshRect(wxRect(GetXSize() - INDICATOR_OFFSET_X - total_width,
2092 GetYSize() - INDICATOR_OFFSET_Y - INDICATOR_BOX_SIZE,
2093 total_width,
2094 INDICATOR_BOX_SIZE), false);
2097 void GfxCore::StartRotation()
2099 // Start the survey rotating.
2101 if (m_SwitchingTo >= NORTH)
2102 m_SwitchingTo = 0;
2103 m_Rotating = true;
2104 SetPanBase();
2107 void GfxCore::ToggleRotation()
2109 // Toggle the survey rotation on/off.
2111 if (m_Rotating) {
2112 StopRotation();
2113 } else {
2114 StartRotation();
2118 void GfxCore::StopRotation()
2120 // Stop the survey rotating.
2122 m_Rotating = false;
2123 ForceRefresh();
2126 bool GfxCore::IsExtendedElevation() const
2128 return m_Parent->IsExtendedElevation();
2131 void GfxCore::ReverseRotation()
2133 // Reverse the direction of rotation.
2135 m_RotationStep = -m_RotationStep;
2136 if (m_Rotating)
2137 SetPanBase();
2140 void GfxCore::RotateSlower(bool accel)
2142 // Decrease the speed of rotation, optionally by an increased amount.
2143 if (fabs(m_RotationStep) == 1.0)
2144 return;
2146 m_RotationStep *= accel ? (1 / 1.44) : (1 / 1.2);
2148 if (fabs(m_RotationStep) < 1.0) {
2149 m_RotationStep = (m_RotationStep > 0 ? 1.0 : -1.0);
2151 if (m_Rotating)
2152 SetPanBase();
2155 void GfxCore::RotateFaster(bool accel)
2157 // Increase the speed of rotation, optionally by an increased amount.
2158 if (fabs(m_RotationStep) == 180.0)
2159 return;
2161 m_RotationStep *= accel ? 1.44 : 1.2;
2162 if (fabs(m_RotationStep) > 180.0) {
2163 m_RotationStep = (m_RotationStep > 0 ? 180.0 : -180.0);
2165 if (m_Rotating)
2166 SetPanBase();
2169 void GfxCore::SwitchToElevation()
2171 // Perform an animated switch to elevation view.
2173 if (m_SwitchingTo != ELEVATION) {
2174 SetTiltBase();
2175 m_SwitchingTo = ELEVATION;
2176 } else {
2177 // A second order to switch takes us there right away
2178 TiltCave(-m_TiltAngle);
2179 m_SwitchingTo = 0;
2180 ForceRefresh();
2184 void GfxCore::SwitchToPlan()
2186 // Perform an animated switch to plan view.
2188 if (m_SwitchingTo != PLAN) {
2189 SetTiltBase();
2190 m_SwitchingTo = PLAN;
2191 } else {
2192 // A second order to switch takes us there right away
2193 TiltCave(-90.0 - m_TiltAngle);
2194 m_SwitchingTo = 0;
2195 ForceRefresh();
2199 void GfxCore::SetViewTo(Double xmin, Double xmax, Double ymin, Double ymax, Double zmin, Double zmax)
2202 SetTranslation(-Vector3((xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2));
2203 Double scale = HUGE_VAL;
2204 const Vector3 ext = m_Parent->GetExtent();
2205 if (xmax > xmin) {
2206 Double s = ext.GetX() / (xmax - xmin);
2207 if (s < scale) scale = s;
2209 if (ymax > ymin) {
2210 Double s = ext.GetY() / (ymax - ymin);
2211 if (s < scale) scale = s;
2213 if (!ShowingPlan() && zmax > zmin) {
2214 Double s = ext.GetZ() / (zmax - zmin);
2215 if (s < scale) scale = s;
2217 if (scale != HUGE_VAL) SetScale(scale);
2218 ForceRefresh();
2221 bool GfxCore::CanRaiseViewpoint() const
2223 // Determine if the survey can be viewed from a higher angle of elevation.
2225 return GetPerspective() ? (m_TiltAngle < 90.0) : (m_TiltAngle > -90.0);
2228 bool GfxCore::CanLowerViewpoint() const
2230 // Determine if the survey can be viewed from a lower angle of elevation.
2232 return GetPerspective() ? (m_TiltAngle > -90.0) : (m_TiltAngle < 90.0);
2235 bool GfxCore::HasDepth() const
2237 return m_Parent->GetDepthExtent() == 0.0;
2240 bool GfxCore::HasErrorInformation() const
2242 return m_Parent->HasErrorInformation();
2245 bool GfxCore::HasDateInformation() const
2247 return m_Parent->GetDateMin() >= 0;
2250 bool GfxCore::ShowingPlan() const
2252 // Determine if the survey is in plan view.
2254 return (m_TiltAngle == -90.0);
2257 bool GfxCore::ShowingElevation() const
2259 // Determine if the survey is in elevation view.
2261 return (m_TiltAngle == 0.0);
2264 bool GfxCore::ShowingMeasuringLine() const
2266 // Determine if the measuring line is being shown. Only check if "there"
2267 // is valid, since that means the measuring line anchor is out.
2269 return m_there;
2272 void GfxCore::ToggleFlag(bool* flag, int update)
2274 *flag = !*flag;
2275 if (update == UPDATE_BLOBS) {
2276 UpdateBlobs();
2277 } else if (update == UPDATE_BLOBS_AND_CROSSES) {
2278 UpdateBlobs();
2279 InvalidateList(LIST_CROSSES);
2280 m_HitTestGridValid = false;
2282 ForceRefresh();
2285 int GfxCore::GetNumEntrances() const
2287 return m_Parent->GetNumEntrances();
2290 int GfxCore::GetNumFixedPts() const
2292 return m_Parent->GetNumFixedPts();
2295 int GfxCore::GetNumExportedPts() const
2297 return m_Parent->GetNumExportedPts();
2300 void GfxCore::ToggleTerrain()
2302 if (!m_Terrain && !dem) {
2303 // OnOpenTerrain() calls us if a file is selected.
2304 wxCommandEvent dummy;
2305 m_Parent->OnOpenTerrain(dummy);
2306 return;
2308 ToggleFlag(&m_Terrain);
2311 void GfxCore::ToggleFatFinger()
2313 if (sqrd_measure_threshold == sqrd(MEASURE_THRESHOLD)) {
2314 sqrd_measure_threshold = sqrd(5 * MEASURE_THRESHOLD);
2315 wxMessageBox(wxT("Fat finger enabled"), wxT("Aven Debug"), wxOK | wxICON_INFORMATION);
2316 } else {
2317 sqrd_measure_threshold = sqrd(MEASURE_THRESHOLD);
2318 wxMessageBox(wxT("Fat finger disabled"), wxT("Aven Debug"), wxOK | wxICON_INFORMATION);
2322 void GfxCore::ClearTreeSelection()
2324 m_Parent->ClearTreeSelection();
2327 void GfxCore::CentreOn(const Point &p)
2329 SetTranslation(-p);
2330 m_HitTestGridValid = false;
2332 ForceRefresh();
2335 void GfxCore::ForceRefresh()
2337 Refresh(false);
2340 void GfxCore::GenerateList(unsigned int l)
2342 assert(m_HaveData);
2344 switch (l) {
2345 case LIST_COMPASS:
2346 DrawCompass();
2347 break;
2348 case LIST_CLINO:
2349 DrawClino();
2350 break;
2351 case LIST_CLINO_BACK:
2352 DrawClinoBack();
2353 break;
2354 case LIST_SCALE_BAR:
2355 DrawScaleBar();
2356 break;
2357 case LIST_DEPTH_KEY:
2358 DrawDepthKey();
2359 break;
2360 case LIST_DATE_KEY:
2361 DrawDateKey();
2362 break;
2363 case LIST_ERROR_KEY:
2364 DrawErrorKey();
2365 break;
2366 case LIST_GRADIENT_KEY:
2367 DrawGradientKey();
2368 break;
2369 case LIST_LENGTH_KEY:
2370 DrawLengthKey();
2371 break;
2372 case LIST_UNDERGROUND_LEGS:
2373 GenerateDisplayList(false);
2374 break;
2375 case LIST_TUBES:
2376 GenerateDisplayListTubes();
2377 break;
2378 case LIST_SURFACE_LEGS:
2379 GenerateDisplayList(true);
2380 break;
2381 case LIST_BLOBS:
2382 GenerateBlobsDisplayList();
2383 break;
2384 case LIST_CROSSES: {
2385 BeginCrosses();
2386 SetColour(col_LIGHT_GREY);
2387 list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2388 while (pos != m_Parent->GetLabelsEnd()) {
2389 const LabelInfo* label = *pos++;
2391 if ((m_Surface && label->IsSurface()) ||
2392 (m_Legs && label->IsUnderground()) ||
2393 (!label->IsSurface() && !label->IsUnderground())) {
2394 // Check if this station should be displayed
2395 // (last case is for stns with no legs attached)
2396 DrawCross(label->GetX(), label->GetY(), label->GetZ());
2399 EndCrosses();
2400 break;
2402 case LIST_GRID:
2403 DrawGrid();
2404 break;
2405 case LIST_SHADOW:
2406 GenerateDisplayListShadow();
2407 break;
2408 case LIST_TERRAIN:
2409 DrawTerrain();
2410 break;
2411 default:
2412 assert(false);
2413 break;
2417 void GfxCore::ToggleSmoothShading()
2419 GLACanvas::ToggleSmoothShading();
2420 InvalidateList(LIST_TUBES);
2421 ForceRefresh();
2424 void GfxCore::GenerateDisplayList(bool surface)
2426 unsigned surf_or_not = surface ? img_FLAG_SURFACE : 0;
2427 // Generate the display list for the surface or underground legs.
2428 for (int f = 0; f != 8; ++f) {
2429 if ((f & img_FLAG_SURFACE) != surf_or_not) continue;
2430 const unsigned SHOW_DASHED_AND_FADED = unsigned(-1);
2431 unsigned style = SHOW_NORMAL;
2432 if ((f & img_FLAG_SPLAY) && m_Splays != SHOW_NORMAL) {
2433 style = m_Splays;
2434 } else if (f & img_FLAG_DUPLICATE) {
2435 style = m_Dupes;
2437 if (f & img_FLAG_SURFACE) {
2438 if (style == SHOW_FADED) {
2439 style = SHOW_DASHED_AND_FADED;
2440 } else {
2441 style = SHOW_DASHED;
2445 switch (style) {
2446 case SHOW_HIDE:
2447 continue;
2448 case SHOW_FADED:
2449 SetAlpha(0.4);
2450 break;
2451 case SHOW_DASHED:
2452 EnableDashedLines();
2453 break;
2454 case SHOW_DASHED_AND_FADED:
2455 SetAlpha(0.4);
2456 EnableDashedLines();
2457 break;
2460 void (GfxCore::* add_poly)(const traverse&);
2461 if (surface) {
2462 if (m_ColourBy == COLOUR_BY_ERROR) {
2463 add_poly = &GfxCore::AddPolylineError;
2464 } else {
2465 add_poly = &GfxCore::AddPolyline;
2467 } else {
2468 add_poly = AddPoly;
2471 list<traverse>::const_iterator trav = m_Parent->traverses_begin(f);
2472 list<traverse>::const_iterator tend = m_Parent->traverses_end(f);
2473 while (trav != tend) {
2474 (this->*add_poly)(*trav);
2475 ++trav;
2478 switch (style) {
2479 case SHOW_FADED:
2480 SetAlpha(1.0);
2481 break;
2482 case SHOW_DASHED:
2483 DisableDashedLines();
2484 break;
2485 case SHOW_DASHED_AND_FADED:
2486 DisableDashedLines();
2487 SetAlpha(1.0);
2488 break;
2493 void GfxCore::GenerateDisplayListTubes()
2495 // Generate the display list for the tubes.
2496 list<vector<XSect> >::iterator trav = m_Parent->tubes_begin();
2497 list<vector<XSect> >::iterator tend = m_Parent->tubes_end();
2498 while (trav != tend) {
2499 SkinPassage(*trav);
2500 ++trav;
2504 void GfxCore::GenerateDisplayListShadow()
2506 SetColour(col_BLACK);
2507 for (int f = 0; f != 8; ++f) {
2508 // Only include underground legs in the shadow.
2509 if ((f & img_FLAG_SURFACE) != 0) continue;
2510 list<traverse>::const_iterator trav = m_Parent->traverses_begin(f);
2511 list<traverse>::const_iterator tend = m_Parent->traverses_end(f);
2512 while (trav != tend) {
2513 AddPolylineShadow(*trav);
2514 ++trav;
2519 void
2520 GfxCore::parse_hgt_filename(const wxString & lc_name)
2522 char * leaf = leaf_from_fnm(lc_name.utf8_str());
2523 const char * p = leaf;
2524 char * q;
2525 char dirn = *p++;
2526 o_y = strtoul(p, &q, 10);
2527 p = q;
2528 if (dirn == 's')
2529 o_y = -o_y;
2530 ++o_y;
2531 dirn = *p++;
2532 o_x = strtoul(p, &q, 10);
2533 if (dirn == 'w')
2534 o_x = -o_x;
2535 bigendian = true;
2536 nodata_value = -32768;
2537 osfree(leaf);
2540 size_t
2541 GfxCore::parse_hdr(wxInputStream & is, unsigned long & skipbytes)
2543 // ESRI docs say NBITS defaults to 8.
2544 unsigned long nbits = 8;
2545 // ESRI docs say NBANDS defaults to 1.
2546 unsigned long nbands = 1;
2547 unsigned long bandrowbytes = 0;
2548 unsigned long totalrowbytes = 0;
2549 // ESRI docs say ULXMAP defaults to 0.
2550 o_x = 0.0;
2551 // ESRI docs say ULYMAP defaults to NROWS - 1.
2552 o_y = HUGE_VAL;
2553 // ESRI docs say XDIM and YDIM default to 1.
2554 step_x = step_y = 1.0;
2555 while (!is.Eof()) {
2556 wxString line;
2557 int ch;
2558 while ((ch = is.GetC()) != wxEOF) {
2559 if (ch == '\n' || ch == '\r') break;
2560 line += wxChar(ch);
2562 #define CHECK(X, COND) \
2563 } else if (line.StartsWith(wxT(X " "))) { \
2564 size_t v = line.find_first_not_of(wxT(' '), sizeof(X)); \
2565 if (v == line.npos || !(COND)) { \
2566 err += wxT("Unexpected value for " X); \
2568 wxString err;
2569 if (false) {
2570 // I = little-endian; M = big-endian
2571 CHECK("BYTEORDER", (bigendian = (line[v] == 'M')) || line[v] == 'I')
2572 // ESRI docs say LAYOUT defaults to BIL if not specified.
2573 CHECK("LAYOUT", line.substr(v) == wxT("BIL"))
2574 CHECK("NROWS", line.substr(v).ToCULong(&dem_height))
2575 CHECK("NCOLS", line.substr(v).ToCULong(&dem_width))
2576 // ESRI docs say NBANDS defaults to 1 if not specified.
2577 CHECK("NBANDS", line.substr(v).ToCULong(&nbands) && nbands == 1)
2578 CHECK("NBITS", line.substr(v).ToCULong(&nbits) && nbits == 16)
2579 CHECK("BANDROWBYTES", line.substr(v).ToCULong(&bandrowbytes))
2580 CHECK("TOTALROWBYTES", line.substr(v).ToCULong(&totalrowbytes))
2581 // PIXELTYPE is a GDAL extension, so may not be present.
2582 CHECK("PIXELTYPE", line.substr(v) == wxT("SIGNEDINT"))
2583 CHECK("ULXMAP", line.substr(v).ToCDouble(&o_x))
2584 CHECK("ULYMAP", line.substr(v).ToCDouble(&o_y))
2585 CHECK("XDIM", line.substr(v).ToCDouble(&step_x))
2586 CHECK("YDIM", line.substr(v).ToCDouble(&step_y))
2587 CHECK("NODATA", line.substr(v).ToCLong(&nodata_value))
2588 CHECK("SKIPBYTES", line.substr(v).ToCULong(&skipbytes))
2590 if (!err.empty()) {
2591 wxMessageBox(err);
2594 if (o_y == HUGE_VAL) {
2595 o_y = dem_height - 1;
2597 if (bandrowbytes != 0) {
2598 if (nbits * dem_width != bandrowbytes * 8) {
2599 wxMessageBox("BANDROWBYTES setting indicates unused bits after each band - not currently supported");
2602 if (totalrowbytes != 0) {
2603 // This is the ESRI default for BIL, for BIP it would be
2604 // nbands * bandrowbytes.
2605 if (nbands * nbits * dem_width != totalrowbytes * 8) {
2606 wxMessageBox("TOTALROWBYTES setting indicates unused bits after "
2607 "each row - not currently supported");
2610 return ((nbits * dem_width + 7) / 8) * dem_height;
2613 bool
2614 GfxCore::read_bil(wxInputStream & is, size_t size, unsigned long skipbytes)
2616 bool know_size = true;
2617 if (!size) {
2618 // If the stream doesn't know its size, GetSize() returns 0.
2619 size = is.GetSize();
2620 if (!size) {
2621 size = DEFAULT_HGT_SIZE;
2622 know_size = false;
2625 dem = new unsigned short[size / 2];
2626 if (skipbytes) {
2627 if (is.SeekI(skipbytes, wxFromStart) == ::wxInvalidOffset) {
2628 while (skipbytes) {
2629 unsigned long to_read = skipbytes;
2630 if (size < to_read) to_read = size;
2631 is.Read(reinterpret_cast<char *>(dem), to_read);
2632 size_t c = is.LastRead();
2633 if (c == 0) {
2634 wxMessageBox(wxT("Failed to skip terrain data header"));
2635 break;
2637 skipbytes -= c;
2642 #if wxCHECK_VERSION(2,9,5)
2643 if (!is.ReadAll(dem, size)) {
2644 if (know_size) {
2645 // FIXME: On __WXMSW__ currently we fail to
2646 // read any data from files in zips.
2647 delete [] dem;
2648 dem = NULL;
2649 wxMessageBox(wxT("Failed to read terrain data"));
2650 return false;
2652 size = is.LastRead();
2654 #else
2655 char * p = reinterpret_cast<char *>(dem);
2656 while (size) {
2657 is.Read(p, size);
2658 size_t c = is.LastRead();
2659 if (c == 0) {
2660 if (!know_size) {
2661 size = DEFAULT_HGT_SIZE - size;
2662 if (size)
2663 break;
2665 delete [] dem;
2666 dem = NULL;
2667 wxMessageBox(wxT("Failed to read terrain data"));
2668 return false;
2670 p += c;
2671 size -= c;
2673 #endif
2675 if (dem_width == 0 && dem_height == 0) {
2676 dem_width = dem_height = sqrt(size / 2);
2677 if (dem_width * dem_height * 2 != size) {
2678 delete [] dem;
2679 dem = NULL;
2680 wxMessageBox(wxT("HGT format data doesn't form a square"));
2681 return false;
2683 step_x = step_y = 1.0 / dem_width;
2686 return true;
2689 bool GfxCore::LoadDEM(const wxString & file)
2691 if (m_Parent->m_cs_proj.empty()) {
2692 wxMessageBox(wxT("No coordinate system specified in survey data"));
2693 return false;
2696 delete [] dem;
2697 dem = NULL;
2699 size_t size = 0;
2700 // Default is to not skip any bytes.
2701 unsigned long skipbytes = 0;
2702 // For .hgt files, default to using filesize to determine.
2703 dem_width = dem_height = 0;
2704 // ESRI say "The default byte order is the same as that of the host machine
2705 // executing the software", but that's stupid so we default to
2706 // little-endian.
2707 bigendian = false;
2709 wxFileInputStream fs(file);
2710 if (!fs.IsOk()) {
2711 wxMessageBox(wxT("Failed to open DEM file"));
2712 return false;
2715 const wxString & lc_file = file.Lower();
2716 if (lc_file.EndsWith(wxT(".hgt"))) {
2717 parse_hgt_filename(lc_file);
2718 read_bil(fs, size, skipbytes);
2719 } else if (lc_file.EndsWith(wxT(".bil"))) {
2720 wxString hdr_file = file;
2721 hdr_file.replace(file.size() - 4, 4, wxT(".hdr"));
2722 wxFileInputStream hdr_is(hdr_file);
2723 if (!hdr_is.IsOk()) {
2724 wxMessageBox(wxT("Failed to open HDR file '") + hdr_file + wxT("'"));
2725 return false;
2727 size = parse_hdr(hdr_is, skipbytes);
2728 read_bil(fs, size, skipbytes);
2729 } else if (lc_file.EndsWith(wxT(".zip"))) {
2730 wxZipEntry * ze_data = NULL;
2731 wxZipInputStream zs(fs);
2732 wxZipEntry * ze;
2733 while ((ze = zs.GetNextEntry()) != NULL) {
2734 if (!ze->IsDir()) {
2735 const wxString & lc_name = ze->GetName().Lower();
2736 if (!ze_data && lc_name.EndsWith(wxT(".hgt"))) {
2737 // SRTM .hgt files are raw binary data, with the filename
2738 // encoding the coordinates.
2739 parse_hgt_filename(lc_name);
2740 read_bil(zs, size, skipbytes);
2741 delete ze;
2742 break;
2745 if (!ze_data && lc_name.EndsWith(wxT(".bil"))) {
2746 if (size) {
2747 read_bil(zs, size, skipbytes);
2748 break;
2750 ze_data = ze;
2751 continue;
2754 if (lc_name.EndsWith(wxT(".hdr"))) {
2755 size = parse_hdr(zs, skipbytes);
2756 if (ze_data) {
2757 if (!zs.OpenEntry(*ze_data)) {
2758 wxMessageBox(wxT("Couldn't read DEM data from .zip file"));
2759 break;
2761 read_bil(zs, size, skipbytes);
2763 } else if (lc_name.EndsWith(wxT(".prj"))) {
2764 //FIXME: check this matches the datum string we use
2765 //Projection GEOGRAPHIC
2766 //Datum WGS84
2767 //Zunits METERS
2768 //Units DD
2769 //Spheroid WGS84
2770 //Xshift 0.0000000000
2771 //Yshift 0.0000000000
2772 //Parameters
2775 delete ze;
2777 delete ze_data;
2780 if (!dem) {
2781 return false;
2784 InvalidateList(LIST_TERRAIN);
2785 ForceRefresh();
2786 return true;
2789 void GfxCore::DrawTerrainTriangle(const Vector3 & a, const Vector3 & b, const Vector3 & c)
2791 Vector3 n = (b - a) * (c - a);
2792 n.normalise();
2793 Double factor = dot(n, light) * .95 + .05;
2794 SetColour(col_WHITE, factor);
2795 PlaceVertex(a);
2796 PlaceVertex(b);
2797 PlaceVertex(c);
2798 ++n_tris;
2801 // Like wxBusyCursor, but you can cancel it early.
2802 class AvenBusyCursor {
2803 bool active;
2805 public:
2806 AvenBusyCursor() : active(true) {
2807 wxBeginBusyCursor();
2810 void stop() {
2811 if (active) {
2812 active = false;
2813 wxEndBusyCursor();
2817 ~AvenBusyCursor() {
2818 stop();
2822 void GfxCore::DrawTerrain()
2824 if (!dem) return;
2826 AvenBusyCursor hourglass;
2828 // Draw terrain to twice the extent, or at least 1km.
2829 double r_sqrd = sqrd(max(m_Parent->GetExtent().magnitude(), 1000.0));
2830 #define WGS84_DATUM_STRING "+proj=longlat +ellps=WGS84 +datum=WGS84"
2831 static projPJ pj_in = pj_init_plus(WGS84_DATUM_STRING);
2832 if (!pj_in) {
2833 ToggleTerrain();
2834 delete [] dem;
2835 dem = NULL;
2836 hourglass.stop();
2837 error(/*Failed to initialise input coordinate system “%s”*/287, WGS84_DATUM_STRING);
2838 return;
2840 static projPJ pj_out = pj_init_plus(m_Parent->m_cs_proj.c_str());
2841 if (!pj_out) {
2842 ToggleTerrain();
2843 delete [] dem;
2844 dem = NULL;
2845 hourglass.stop();
2846 error(/*Failed to initialise output coordinate system “%s”*/288, (const char *)m_Parent->m_cs_proj.c_str());
2847 return;
2849 n_tris = 0;
2850 SetAlpha(0.3);
2851 BeginTriangles();
2852 const Vector3 & off = m_Parent->GetOffset();
2853 vector<Vector3> prevcol(dem_height + 1);
2854 for (size_t x = 0; x < dem_width; ++x) {
2855 double X_ = (o_x + x * step_x) * DEG_TO_RAD;
2856 Vector3 prev;
2857 for (size_t y = 0; y < dem_height; ++y) {
2858 unsigned short elev = dem[x + y * dem_width];
2859 #ifdef WORDS_BIGENDIAN
2860 const bool MACHINE_BIGENDIAN = true;
2861 #else
2862 const bool MACHINE_BIGENDIAN = false;
2863 #endif
2864 if (bigendian != MACHINE_BIGENDIAN) {
2865 #if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408)
2866 elev = __builtin_bswap16(elev);
2867 #else
2868 elev = (elev >> 8) | (elev << 8);
2869 #endif
2871 double Z = (short)elev;
2872 Vector3 pt;
2873 if (Z == nodata_value) {
2874 pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
2875 } else {
2876 double X = X_;
2877 double Y = (o_y - y * step_y) * DEG_TO_RAD;
2878 pj_transform(pj_in, pj_out, 1, 1, &X, &Y, &Z);
2879 pt = Vector3(X, Y, Z) - off;
2880 double dist_2 = sqrd(pt.GetX()) + sqrd(pt.GetY());
2881 if (dist_2 > r_sqrd) {
2882 pt = Vector3(DBL_MAX, DBL_MAX, DBL_MAX);
2885 if (x > 0 && y > 0) {
2886 const Vector3 & a = prevcol[y - 1];
2887 const Vector3 & b = prevcol[y];
2888 // If all points are valid, split the quadrilateral into
2889 // triangles along the shorter 3D diagonal, which typically
2890 // looks better:
2892 // ----->
2893 // prev---a x prev---a
2894 // | |P /| |\ S|
2895 // y | | / | or | \ |
2896 // V | / | | \ |
2897 // |/ Q| |R \|
2898 // b----pt b----pt
2900 // FORWARD BACKWARD
2901 enum { NONE = 0, P = 1, Q = 2, R = 4, S = 8, ALL = P|Q|R|S };
2902 int valid =
2903 ((prev.GetZ() != DBL_MAX)) |
2904 ((a.GetZ() != DBL_MAX) << 1) |
2905 ((b.GetZ() != DBL_MAX) << 2) |
2906 ((pt.GetZ() != DBL_MAX) << 3);
2907 static const int tris_map[16] = {
2908 NONE, // nothing valid
2909 NONE, // prev
2910 NONE, // a
2911 NONE, // a, prev
2912 NONE, // b
2913 NONE, // b, prev
2914 NONE, // b, a
2915 P, // b, a, prev
2916 NONE, // pt
2917 NONE, // pt, prev
2918 NONE, // pt, a
2919 S, // pt, a, prev
2920 NONE, // pt, b
2921 R, // pt, b, prev
2922 Q, // pt, b, a
2923 ALL, // pt, b, a, prev
2925 int tris = tris_map[valid];
2926 if (tris == ALL) {
2927 // All points valid.
2928 if ((a - b).magnitude() < (prev - pt).magnitude()) {
2929 tris = P | Q;
2930 } else {
2931 tris = R | S;
2934 if (tris & P)
2935 DrawTerrainTriangle(a, prev, b);
2936 if (tris & Q)
2937 DrawTerrainTriangle(a, b, pt);
2938 if (tris & R)
2939 DrawTerrainTriangle(pt, prev, b);
2940 if (tris & S)
2941 DrawTerrainTriangle(a, prev, pt);
2943 prev = prevcol[y];
2944 prevcol[y].assign(pt);
2947 EndTriangles();
2948 SetAlpha(1.0);
2949 if (n_tris == 0) {
2950 ToggleTerrain();
2951 delete [] dem;
2952 dem = NULL;
2953 hourglass.stop();
2954 /* TRANSLATORS: Aven shows a circle of terrain covering the area
2955 * of the survey plus a bit, but the terrain data file didn't
2956 * contain any data inside that circle.
2958 error(/*No terrain data near area of survey*/161);
2962 // Plot blobs.
2963 void GfxCore::GenerateBlobsDisplayList()
2965 if (!(m_Entrances || m_FixedPts || m_ExportedPts ||
2966 m_Parent->GetNumHighlightedPts()))
2967 return;
2969 // Plot blobs.
2970 gla_colour prev_col = col_BLACK; // not a colour used for blobs
2971 list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
2972 BeginBlobs();
2973 while (pos != m_Parent->GetLabelsEnd()) {
2974 const LabelInfo* label = *pos++;
2976 // When more than one flag is set on a point:
2977 // search results take priority over entrance highlighting
2978 // which takes priority over fixed point
2979 // highlighting, which in turn takes priority over exported
2980 // point highlighting.
2982 if (!((m_Surface && label->IsSurface()) ||
2983 (m_Legs && label->IsUnderground()) ||
2984 (!label->IsSurface() && !label->IsUnderground()))) {
2985 // if this station isn't to be displayed, skip to the next
2986 // (last case is for stns with no legs attached)
2987 continue;
2990 gla_colour col;
2992 if (label->IsHighLighted()) {
2993 col = col_YELLOW;
2994 } else if (m_Entrances && label->IsEntrance()) {
2995 col = col_GREEN;
2996 } else if (m_FixedPts && label->IsFixedPt()) {
2997 col = col_RED;
2998 } else if (m_ExportedPts && label->IsExportedPt()) {
2999 col = col_TURQUOISE;
3000 } else {
3001 continue;
3004 // Stations are sorted by blob type, so colour changes are infrequent.
3005 if (col != prev_col) {
3006 SetColour(col);
3007 prev_col = col;
3009 DrawBlob(label->GetX(), label->GetY(), label->GetZ());
3011 EndBlobs();
3014 void GfxCore::DrawIndicators()
3016 // Draw colour key.
3017 if (m_ColourKey) {
3018 drawing_list key_list = LIST_LIMIT_;
3019 switch (m_ColourBy) {
3020 case COLOUR_BY_DEPTH:
3021 key_list = LIST_DEPTH_KEY; break;
3022 case COLOUR_BY_DATE:
3023 key_list = LIST_DATE_KEY; break;
3024 case COLOUR_BY_ERROR:
3025 key_list = LIST_ERROR_KEY; break;
3026 case COLOUR_BY_GRADIENT:
3027 key_list = LIST_GRADIENT_KEY; break;
3028 case COLOUR_BY_LENGTH:
3029 key_list = LIST_LENGTH_KEY; break;
3031 if (key_list != LIST_LIMIT_) {
3032 DrawList2D(key_list, GetXSize() - KEY_OFFSET_X,
3033 GetYSize() - KEY_OFFSET_Y, 0);
3037 // Draw compass or elevation/heading indicators.
3038 if (m_Compass || m_Clino) {
3039 if (!m_Parent->IsExtendedElevation()) Draw2dIndicators();
3042 // Draw scalebar.
3043 if (m_Scalebar) {
3044 DrawList2D(LIST_SCALE_BAR, 0, 0, 0);
3048 void GfxCore::PlaceVertexWithColour(const Vector3 & v,
3049 glaTexCoord tex_x, glaTexCoord tex_y,
3050 Double factor)
3052 SetColour(col_WHITE, factor);
3053 PlaceVertex(v, tex_x, tex_y);
3056 void GfxCore::SetDepthColour(Double z, Double factor) {
3057 // Set the drawing colour based on the altitude.
3058 Double z_ext = m_Parent->GetDepthExtent();
3060 z -= m_Parent->GetDepthMin();
3061 // points arising from tubes may be slightly outside the limits...
3062 if (z < 0) z = 0;
3063 if (z > z_ext) z = z_ext;
3065 if (z == 0) {
3066 SetColour(GetPen(0), factor);
3067 return;
3070 assert(z_ext > 0.0);
3071 Double how_far = z / z_ext;
3072 assert(how_far >= 0.0);
3073 assert(how_far <= 1.0);
3075 int band = int(floor(how_far * (GetNumColourBands() - 1)));
3076 GLAPen pen1 = GetPen(band);
3077 if (band < GetNumColourBands() - 1) {
3078 const GLAPen& pen2 = GetPen(band + 1);
3080 Double interval = z_ext / (GetNumColourBands() - 1);
3081 Double into_band = z / interval - band;
3083 // printf("%g z_offset=%g interval=%g band=%d\n", into_band,
3084 // z_offset, interval, band);
3085 // FIXME: why do we need to clamp here? Is it because the walls can
3086 // extend further up/down than the centre-line?
3087 if (into_band < 0.0) into_band = 0.0;
3088 if (into_band > 1.0) into_band = 1.0;
3089 assert(into_band >= 0.0);
3090 assert(into_band <= 1.0);
3092 pen1.Interpolate(pen2, into_band);
3094 SetColour(pen1, factor);
3097 void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v, Double factor)
3099 SetDepthColour(v.GetZ(), factor);
3100 PlaceVertex(v);
3103 void GfxCore::PlaceVertexWithDepthColour(const Vector3 &v,
3104 glaTexCoord tex_x, glaTexCoord tex_y,
3105 Double factor)
3107 SetDepthColour(v.GetZ(), factor);
3108 PlaceVertex(v, tex_x, tex_y);
3111 void GfxCore::SplitLineAcrossBands(int band, int band2,
3112 const Vector3 &p, const Vector3 &q,
3113 Double factor)
3115 const int step = (band < band2) ? 1 : -1;
3116 for (int i = band; i != band2; i += step) {
3117 const Double z = GetDepthBoundaryBetweenBands(i, i + step);
3119 // Find the intersection point of the line p -> q
3120 // with the plane parallel to the xy-plane with z-axis intersection z.
3121 assert(q.GetZ() - p.GetZ() != 0.0);
3123 const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
3124 // assert(0.0 <= t && t <= 1.0); FIXME: rounding problems!
3126 const Double x = p.GetX() + t * (q.GetX() - p.GetX());
3127 const Double y = p.GetY() + t * (q.GetY() - p.GetY());
3129 PlaceVertexWithDepthColour(Vector3(x, y, z), factor);
3133 void GfxCore::SplitPolyAcrossBands(vector<vector<Split> >& splits,
3134 int band, int band2,
3135 const Vector3 &p, const Vector3 &q,
3136 glaTexCoord ptx, glaTexCoord pty,
3137 glaTexCoord w, glaTexCoord h)
3139 const int step = (band < band2) ? 1 : -1;
3140 for (int i = band; i != band2; i += step) {
3141 const Double z = GetDepthBoundaryBetweenBands(i, i + step);
3143 // Find the intersection point of the line p -> q
3144 // with the plane parallel to the xy-plane with z-axis intersection z.
3145 assert(q.GetZ() - p.GetZ() != 0.0);
3147 const Double t = (z - p.GetZ()) / (q.GetZ() - p.GetZ());
3148 // assert(0.0 <= t && t <= 1.0); FIXME: rounding problems!
3150 const Double x = p.GetX() + t * (q.GetX() - p.GetX());
3151 const Double y = p.GetY() + t * (q.GetY() - p.GetY());
3152 glaTexCoord tx = ptx, ty = pty;
3153 if (w) tx += t * w;
3154 if (h) ty += t * h;
3156 splits[i].push_back(Split(Vector3(x, y, z), tx, ty));
3157 splits[i + step].push_back(Split(Vector3(x, y, z), tx, ty));
3161 int GfxCore::GetDepthColour(Double z) const
3163 // Return the (0-based) depth colour band index for a z-coordinate.
3164 Double z_ext = m_Parent->GetDepthExtent();
3165 z -= m_Parent->GetDepthMin();
3166 // We seem to get rounding differences causing z to sometimes be slightly
3167 // less than GetDepthMin() here, and it can certainly be true for passage
3168 // tubes, so just clamp the value to 0.
3169 if (z <= 0) return 0;
3170 // We seem to get rounding differences causing z to sometimes exceed z_ext
3171 // by a small amount here (see: https://trac.survex.com/ticket/26) and it
3172 // can certainly be true for passage tubes, so just clamp the value.
3173 if (z >= z_ext) return GetNumColourBands() - 1;
3174 return int(z / z_ext * (GetNumColourBands() - 1));
3177 Double GfxCore::GetDepthBoundaryBetweenBands(int a, int b) const
3179 // Return the z-coordinate of the depth colour boundary between
3180 // two adjacent depth colour bands (specified by 0-based indices).
3182 assert((a == b - 1) || (a == b + 1));
3183 if (GetNumColourBands() == 1) return 0;
3185 int band = (a > b) ? a : b; // boundary N lies on the bottom of band N.
3186 Double z_ext = m_Parent->GetDepthExtent();
3187 return (z_ext * band / (GetNumColourBands() - 1)) + m_Parent->GetDepthMin();
3190 void GfxCore::AddPolyline(const traverse & centreline)
3192 BeginPolyline();
3193 SetColour(col_WHITE);
3194 vector<PointInfo>::const_iterator i = centreline.begin();
3195 PlaceVertex(*i);
3196 ++i;
3197 while (i != centreline.end()) {
3198 PlaceVertex(*i);
3199 ++i;
3201 EndPolyline();
3204 void GfxCore::AddPolylineShadow(const traverse & centreline)
3206 BeginPolyline();
3207 const double z = -0.5 * m_Parent->GetZExtent();
3208 vector<PointInfo>::const_iterator i = centreline.begin();
3209 PlaceVertex(i->GetX(), i->GetY(), z);
3210 ++i;
3211 while (i != centreline.end()) {
3212 PlaceVertex(i->GetX(), i->GetY(), z);
3213 ++i;
3215 EndPolyline();
3218 void GfxCore::AddPolylineDepth(const traverse & centreline)
3220 BeginPolyline();
3221 vector<PointInfo>::const_iterator i, prev_i;
3222 i = centreline.begin();
3223 int band0 = GetDepthColour(i->GetZ());
3224 PlaceVertexWithDepthColour(*i);
3225 prev_i = i;
3226 ++i;
3227 while (i != centreline.end()) {
3228 int band = GetDepthColour(i->GetZ());
3229 if (band != band0) {
3230 SplitLineAcrossBands(band0, band, *prev_i, *i);
3231 band0 = band;
3233 PlaceVertexWithDepthColour(*i);
3234 prev_i = i;
3235 ++i;
3237 EndPolyline();
3240 void GfxCore::AddQuadrilateral(const Vector3 &a, const Vector3 &b,
3241 const Vector3 &c, const Vector3 &d)
3243 Vector3 normal = (a - c) * (d - b);
3244 normal.normalise();
3245 Double factor = dot(normal, light) * .3 + .7;
3246 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3247 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3248 // FIXME: should plot triangles instead to avoid rendering glitches.
3249 BeginQuadrilaterals();
3250 PlaceVertexWithColour(a, 0, 0, factor);
3251 PlaceVertexWithColour(b, w, 0, factor);
3252 PlaceVertexWithColour(c, w, h, factor);
3253 PlaceVertexWithColour(d, 0, h, factor);
3254 EndQuadrilaterals();
3257 void GfxCore::AddQuadrilateralDepth(const Vector3 &a, const Vector3 &b,
3258 const Vector3 &c, const Vector3 &d)
3260 Vector3 normal = (a - c) * (d - b);
3261 normal.normalise();
3262 Double factor = dot(normal, light) * .3 + .7;
3263 int a_band, b_band, c_band, d_band;
3264 a_band = GetDepthColour(a.GetZ());
3265 a_band = min(max(a_band, 0), GetNumColourBands());
3266 b_band = GetDepthColour(b.GetZ());
3267 b_band = min(max(b_band, 0), GetNumColourBands());
3268 c_band = GetDepthColour(c.GetZ());
3269 c_band = min(max(c_band, 0), GetNumColourBands());
3270 d_band = GetDepthColour(d.GetZ());
3271 d_band = min(max(d_band, 0), GetNumColourBands());
3272 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3273 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3274 int min_band = min(min(a_band, b_band), min(c_band, d_band));
3275 int max_band = max(max(a_band, b_band), max(c_band, d_band));
3276 if (min_band == max_band) {
3277 // Simple case - the polygon is entirely within one band.
3278 BeginPolygon();
3279 //// PlaceNormal(normal);
3280 PlaceVertexWithDepthColour(a, 0, 0, factor);
3281 PlaceVertexWithDepthColour(b, w, 0, factor);
3282 PlaceVertexWithDepthColour(c, w, h, factor);
3283 PlaceVertexWithDepthColour(d, 0, h, factor);
3284 EndPolygon();
3285 } else {
3286 // We need to make a separate polygon for each depth band...
3287 vector<vector<Split> > splits;
3288 splits.resize(max_band + 1);
3289 splits[a_band].push_back(Split(a, 0, 0));
3290 if (a_band != b_band) {
3291 SplitPolyAcrossBands(splits, a_band, b_band, a, b, 0, 0, w, 0);
3293 splits[b_band].push_back(Split(b, w, 0));
3294 if (b_band != c_band) {
3295 SplitPolyAcrossBands(splits, b_band, c_band, b, c, w, 0, 0, h);
3297 splits[c_band].push_back(Split(c, w, h));
3298 if (c_band != d_band) {
3299 SplitPolyAcrossBands(splits, c_band, d_band, c, d, w, h, -w, 0);
3301 splits[d_band].push_back(Split(d, 0, h));
3302 if (d_band != a_band) {
3303 SplitPolyAcrossBands(splits, d_band, a_band, d, a, 0, h, 0, -h);
3305 for (int band = min_band; band <= max_band; ++band) {
3306 BeginPolygon();
3307 for (auto&& item : splits[band]) {
3308 PlaceVertexWithDepthColour(item.vec, item.tx, item.ty, factor);
3310 EndPolygon();
3315 void GfxCore::SetColourFromDate(int date, Double factor)
3317 // Set the drawing colour based on a date.
3319 if (date == -1) {
3320 // Undated.
3321 SetColour(NODATA_COLOUR, factor);
3322 return;
3325 int date_offset = date - m_Parent->GetDateMin();
3326 if (date_offset == 0) {
3327 // Earliest date - handle as a special case for the single date case.
3328 SetColour(GetPen(0), factor);
3329 return;
3332 int date_ext = m_Parent->GetDateExtent();
3333 Double how_far = (Double)date_offset / date_ext;
3334 assert(how_far >= 0.0);
3335 assert(how_far <= 1.0);
3336 SetColourFrom01(how_far, factor);
3339 void GfxCore::AddPolylineDate(const traverse & centreline)
3341 BeginPolyline();
3342 vector<PointInfo>::const_iterator i, prev_i;
3343 i = centreline.begin();
3344 int date = i->GetDate();
3345 SetColourFromDate(date, 1.0);
3346 PlaceVertex(*i);
3347 prev_i = i;
3348 while (++i != centreline.end()) {
3349 int newdate = i->GetDate();
3350 if (newdate != date) {
3351 EndPolyline();
3352 BeginPolyline();
3353 date = newdate;
3354 SetColourFromDate(date, 1.0);
3355 PlaceVertex(*prev_i);
3357 PlaceVertex(*i);
3358 prev_i = i;
3360 EndPolyline();
3363 static int static_date_hack; // FIXME
3365 void GfxCore::AddQuadrilateralDate(const Vector3 &a, const Vector3 &b,
3366 const Vector3 &c, const Vector3 &d)
3368 Vector3 normal = (a - c) * (d - b);
3369 normal.normalise();
3370 Double factor = dot(normal, light) * .3 + .7;
3371 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3372 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3373 // FIXME: should plot triangles instead to avoid rendering glitches.
3374 BeginQuadrilaterals();
3375 //// PlaceNormal(normal);
3376 SetColourFromDate(static_date_hack, factor);
3377 PlaceVertex(a, 0, 0);
3378 PlaceVertex(b, w, 0);
3379 PlaceVertex(c, w, h);
3380 PlaceVertex(d, 0, h);
3381 EndQuadrilaterals();
3384 static double static_E_hack; // FIXME
3386 void GfxCore::SetColourFromError(double E, Double factor)
3388 // Set the drawing colour based on an error value.
3390 if (E < 0) {
3391 SetColour(NODATA_COLOUR, factor);
3392 return;
3395 Double how_far = E / MAX_ERROR;
3396 assert(how_far >= 0.0);
3397 if (how_far > 1.0) how_far = 1.0;
3398 SetColourFrom01(how_far, factor);
3401 void GfxCore::AddQuadrilateralError(const Vector3 &a, const Vector3 &b,
3402 const Vector3 &c, const Vector3 &d)
3404 Vector3 normal = (a - c) * (d - b);
3405 normal.normalise();
3406 Double factor = dot(normal, light) * .3 + .7;
3407 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3408 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3409 // FIXME: should plot triangles instead to avoid rendering glitches.
3410 BeginQuadrilaterals();
3411 //// PlaceNormal(normal);
3412 SetColourFromError(static_E_hack, factor);
3413 PlaceVertex(a, 0, 0);
3414 PlaceVertex(b, w, 0);
3415 PlaceVertex(c, w, h);
3416 PlaceVertex(d, 0, h);
3417 EndQuadrilaterals();
3420 void GfxCore::AddPolylineError(const traverse & centreline)
3422 BeginPolyline();
3423 SetColourFromError(centreline.E, 1.0);
3424 vector<PointInfo>::const_iterator i;
3425 for(i = centreline.begin(); i != centreline.end(); ++i) {
3426 PlaceVertex(*i);
3428 EndPolyline();
3431 // gradient is in *radians*.
3432 void GfxCore::SetColourFromGradient(double gradient, Double factor)
3434 // Set the drawing colour based on the gradient of the leg.
3436 const Double GRADIENT_MAX = M_PI_2;
3437 gradient = fabs(gradient);
3438 Double how_far = gradient / GRADIENT_MAX;
3439 SetColourFrom01(how_far, factor);
3442 void GfxCore::AddPolylineGradient(const traverse & centreline)
3444 vector<PointInfo>::const_iterator i, prev_i;
3445 i = centreline.begin();
3446 prev_i = i;
3447 while (++i != centreline.end()) {
3448 BeginPolyline();
3449 SetColourFromGradient((*i - *prev_i).gradient(), 1.0);
3450 PlaceVertex(*prev_i);
3451 PlaceVertex(*i);
3452 prev_i = i;
3453 EndPolyline();
3457 static double static_gradient_hack; // FIXME
3459 void GfxCore::AddQuadrilateralGradient(const Vector3 &a, const Vector3 &b,
3460 const Vector3 &c, const Vector3 &d)
3462 Vector3 normal = (a - c) * (d - b);
3463 normal.normalise();
3464 Double factor = dot(normal, light) * .3 + .7;
3465 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3466 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3467 // FIXME: should plot triangles instead to avoid rendering glitches.
3468 BeginQuadrilaterals();
3469 //// PlaceNormal(normal);
3470 SetColourFromGradient(static_gradient_hack, factor);
3471 PlaceVertex(a, 0, 0);
3472 PlaceVertex(b, w, 0);
3473 PlaceVertex(c, w, h);
3474 PlaceVertex(d, 0, h);
3475 EndQuadrilaterals();
3478 void GfxCore::SetColourFromLength(double length, Double factor)
3480 // Set the drawing colour based on log(length_of_leg).
3482 Double log_len = log10(length);
3483 Double how_far = log_len / LOG_LEN_MAX;
3484 how_far = max(how_far, 0.0);
3485 how_far = min(how_far, 1.0);
3486 SetColourFrom01(how_far, factor);
3489 void GfxCore::SetColourFrom01(double how_far, Double factor)
3491 double b;
3492 double into_band = modf(how_far * (GetNumColourBands() - 1), &b);
3493 int band(b);
3494 GLAPen pen1 = GetPen(band);
3495 // With 24bit colour, interpolating by less than this can have no effect.
3496 if (into_band >= 1.0 / 512.0) {
3497 const GLAPen& pen2 = GetPen(band + 1);
3498 pen1.Interpolate(pen2, into_band);
3500 SetColour(pen1, factor);
3503 void GfxCore::AddPolylineLength(const traverse & centreline)
3505 vector<PointInfo>::const_iterator i, prev_i;
3506 i = centreline.begin();
3507 prev_i = i;
3508 while (++i != centreline.end()) {
3509 BeginPolyline();
3510 SetColourFromLength((*i - *prev_i).magnitude(), 1.0);
3511 PlaceVertex(*prev_i);
3512 PlaceVertex(*i);
3513 prev_i = i;
3514 EndPolyline();
3518 static double static_length_hack; // FIXME
3520 void GfxCore::AddQuadrilateralLength(const Vector3 &a, const Vector3 &b,
3521 const Vector3 &c, const Vector3 &d)
3523 Vector3 normal = (a - c) * (d - b);
3524 normal.normalise();
3525 Double factor = dot(normal, light) * .3 + .7;
3526 glaTexCoord w(((b - a).magnitude() + (d - c).magnitude()) * .5);
3527 glaTexCoord h(((b - c).magnitude() + (d - a).magnitude()) * .5);
3528 // FIXME: should plot triangles instead to avoid rendering glitches.
3529 BeginQuadrilaterals();
3530 //// PlaceNormal(normal);
3531 SetColourFromLength(static_length_hack, factor);
3532 PlaceVertex(a, 0, 0);
3533 PlaceVertex(b, w, 0);
3534 PlaceVertex(c, w, h);
3535 PlaceVertex(d, 0, h);
3536 EndQuadrilaterals();
3539 void
3540 GfxCore::SkinPassage(vector<XSect> & centreline, bool draw)
3542 assert(centreline.size() > 1);
3543 Vector3 U[4];
3544 XSect prev_pt_v;
3545 Vector3 last_right(1.0, 0.0, 0.0);
3547 // FIXME: it's not simple to set the colour of a tube based on error...
3548 // static_E_hack = something...
3549 vector<XSect>::iterator i = centreline.begin();
3550 vector<XSect>::size_type segment = 0;
3551 while (i != centreline.end()) {
3552 // get the coordinates of this vertex
3553 XSect & pt_v = *i++;
3555 bool cover_end = false;
3557 Vector3 right, up;
3559 const Vector3 up_v(0.0, 0.0, 1.0);
3561 if (segment == 0) {
3562 assert(i != centreline.end());
3563 // first segment
3565 // get the coordinates of the next vertex
3566 const XSect & next_pt_v = *i;
3568 // calculate vector from this pt to the next one
3569 Vector3 leg_v = next_pt_v - pt_v;
3571 // obtain a vector in the LRUD plane
3572 right = leg_v * up_v;
3573 if (right.magnitude() == 0) {
3574 right = last_right;
3575 // Obtain a second vector in the LRUD plane,
3576 // perpendicular to the first.
3577 //up = right * leg_v;
3578 up = up_v;
3579 } else {
3580 last_right = right;
3581 up = up_v;
3584 cover_end = true;
3585 static_date_hack = next_pt_v.GetDate();
3586 } else if (segment + 1 == centreline.size()) {
3587 // last segment
3589 // Calculate vector from the previous pt to this one.
3590 Vector3 leg_v = pt_v - prev_pt_v;
3592 // Obtain a horizontal vector in the LRUD plane.
3593 right = leg_v * up_v;
3594 if (right.magnitude() == 0) {
3595 right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
3596 // Obtain a second vector in the LRUD plane,
3597 // perpendicular to the first.
3598 //up = right * leg_v;
3599 up = up_v;
3600 } else {
3601 last_right = right;
3602 up = up_v;
3605 cover_end = true;
3606 static_date_hack = pt_v.GetDate();
3607 } else {
3608 assert(i != centreline.end());
3609 // Intermediate segment.
3611 // Get the coordinates of the next vertex.
3612 const XSect & next_pt_v = *i;
3614 // Calculate vectors from this vertex to the
3615 // next vertex, and from the previous vertex to
3616 // this one.
3617 Vector3 leg1_v = pt_v - prev_pt_v;
3618 Vector3 leg2_v = next_pt_v - pt_v;
3620 // Obtain horizontal vectors perpendicular to
3621 // both legs, then normalise and average to get
3622 // a horizontal bisector.
3623 Vector3 r1 = leg1_v * up_v;
3624 Vector3 r2 = leg2_v * up_v;
3625 r1.normalise();
3626 r2.normalise();
3627 right = r1 + r2;
3628 if (right.magnitude() == 0) {
3629 // This is the "mid-pitch" case...
3630 right = last_right;
3632 if (r1.magnitude() == 0) {
3633 up = up_v;
3635 // Rotate pitch section to minimise the
3636 // "tortional stress" - FIXME: use
3637 // triangles instead of rectangles?
3638 int shift = 0;
3639 Double maxdotp = 0;
3641 // Scale to unit vectors in the LRUD plane.
3642 right.normalise();
3643 up.normalise();
3644 Vector3 vec = up - right;
3645 for (int orient = 0; orient <= 3; ++orient) {
3646 Vector3 tmp = U[orient] - prev_pt_v;
3647 tmp.normalise();
3648 Double dotp = dot(vec, tmp);
3649 if (dotp > maxdotp) {
3650 maxdotp = dotp;
3651 shift = orient;
3654 if (shift) {
3655 if (shift != 2) {
3656 Vector3 temp(U[0]);
3657 U[0] = U[shift];
3658 U[shift] = U[2];
3659 U[2] = U[shift ^ 2];
3660 U[shift ^ 2] = temp;
3661 } else {
3662 swap(U[0], U[2]);
3663 swap(U[1], U[3]);
3666 #if 0
3667 // Check that the above code actually permuted
3668 // the vertices correctly.
3669 shift = 0;
3670 maxdotp = 0;
3671 for (int j = 0; j <= 3; ++j) {
3672 Vector3 tmp = U[j] - prev_pt_v;
3673 tmp.normalise();
3674 Double dotp = dot(vec, tmp);
3675 if (dotp > maxdotp) {
3676 maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
3677 shift = j;
3680 if (shift) {
3681 printf("New shift = %d!\n", shift);
3682 shift = 0;
3683 maxdotp = 0;
3684 for (int j = 0; j <= 3; ++j) {
3685 Vector3 tmp = U[j] - prev_pt_v;
3686 tmp.normalise();
3687 Double dotp = dot(vec, tmp);
3688 printf(" %d : %.8f\n", j, dotp);
3691 #endif
3692 } else {
3693 up = up_v;
3695 last_right = right;
3696 static_date_hack = pt_v.GetDate();
3699 // Scale to unit vectors in the LRUD plane.
3700 right.normalise();
3701 up.normalise();
3703 Double l = fabs(pt_v.GetL());
3704 Double r = fabs(pt_v.GetR());
3705 Double u = fabs(pt_v.GetU());
3706 Double d = fabs(pt_v.GetD());
3708 // Produce coordinates of the corners of the LRUD "plane".
3709 Vector3 v[4];
3710 v[0] = pt_v - right * l + up * u;
3711 v[1] = pt_v + right * r + up * u;
3712 v[2] = pt_v + right * r - up * d;
3713 v[3] = pt_v - right * l - up * d;
3715 if (draw) {
3716 const Vector3 & delta = pt_v - prev_pt_v;
3717 static_length_hack = delta.magnitude();
3718 static_gradient_hack = delta.gradient();
3719 if (segment > 0) {
3720 (this->*AddQuad)(v[0], v[1], U[1], U[0]);
3721 (this->*AddQuad)(v[2], v[3], U[3], U[2]);
3722 (this->*AddQuad)(v[1], v[2], U[2], U[1]);
3723 (this->*AddQuad)(v[3], v[0], U[0], U[3]);
3726 if (cover_end) {
3727 if (segment == 0) {
3728 (this->*AddQuad)(v[0], v[1], v[2], v[3]);
3729 } else {
3730 (this->*AddQuad)(v[3], v[2], v[1], v[0]);
3735 prev_pt_v = pt_v;
3736 U[0] = v[0];
3737 U[1] = v[1];
3738 U[2] = v[2];
3739 U[3] = v[3];
3741 pt_v.set_right_bearing(deg(atan2(right.GetY(), right.GetX())));
3743 ++segment;
3747 void GfxCore::FullScreenMode()
3749 m_Parent->ViewFullScreen();
3752 bool GfxCore::IsFullScreen() const
3754 return m_Parent->IsFullScreen();
3757 bool GfxCore::FullScreenModeShowingMenus() const
3759 return m_Parent->FullScreenModeShowingMenus();
3762 void GfxCore::FullScreenModeShowMenus(bool show)
3764 m_Parent->FullScreenModeShowMenus(show);
3767 void
3768 GfxCore::MoveViewer(double forward, double up, double right)
3770 double cT = cos(rad(m_TiltAngle));
3771 double sT = sin(rad(m_TiltAngle));
3772 double cP = cos(rad(m_PanAngle));
3773 double sP = sin(rad(m_PanAngle));
3774 Vector3 v_forward(cT * sP, cT * cP, sT);
3775 Vector3 v_up(sT * sP, sT * cP, -cT);
3776 Vector3 v_right(-cP, sP, 0);
3777 assert(fabs(dot(v_forward, v_up)) < 1e-6);
3778 assert(fabs(dot(v_forward, v_right)) < 1e-6);
3779 assert(fabs(dot(v_right, v_up)) < 1e-6);
3780 Vector3 move = v_forward * forward + v_up * up + v_right * right;
3781 AddTranslation(-move);
3782 // Show current position.
3783 m_Parent->SetCoords(m_Parent->GetOffset() - GetTranslation());
3784 ForceRefresh();
3787 PresentationMark GfxCore::GetView() const
3789 return PresentationMark(GetTranslation() + m_Parent->GetOffset(),
3790 m_PanAngle, -m_TiltAngle, m_Scale);
3793 void GfxCore::SetView(const PresentationMark & p)
3795 m_SwitchingTo = 0;
3796 SetTranslation(p - m_Parent->GetOffset());
3797 m_PanAngle = p.angle;
3798 m_TiltAngle = -p.tilt_angle; // FIXME: nasty reversed sense (and above)
3799 SetRotation(m_PanAngle, m_TiltAngle);
3800 SetScale(p.scale);
3801 ForceRefresh();
3804 void GfxCore::PlayPres(double speed, bool change_speed) {
3805 if (!change_speed || presentation_mode == 0) {
3806 if (speed == 0.0) {
3807 presentation_mode = 0;
3808 return;
3810 presentation_mode = PLAYING;
3811 next_mark = m_Parent->GetPresMark(MARK_FIRST);
3812 SetView(next_mark);
3813 next_mark_time = 0; // There already!
3814 this_mark_total = 0;
3815 pres_reverse = (speed < 0);
3818 if (change_speed) pres_speed = speed;
3820 if (speed != 0.0) {
3821 bool new_pres_reverse = (speed < 0);
3822 if (new_pres_reverse != pres_reverse) {
3823 pres_reverse = new_pres_reverse;
3824 if (pres_reverse) {
3825 next_mark = m_Parent->GetPresMark(MARK_PREV);
3826 } else {
3827 next_mark = m_Parent->GetPresMark(MARK_NEXT);
3829 swap(this_mark_total, next_mark_time);
3834 void GfxCore::SetColourBy(int colour_by) {
3835 m_ColourBy = colour_by;
3836 switch (colour_by) {
3837 case COLOUR_BY_DEPTH:
3838 AddQuad = &GfxCore::AddQuadrilateralDepth;
3839 AddPoly = &GfxCore::AddPolylineDepth;
3840 break;
3841 case COLOUR_BY_DATE:
3842 AddQuad = &GfxCore::AddQuadrilateralDate;
3843 AddPoly = &GfxCore::AddPolylineDate;
3844 break;
3845 case COLOUR_BY_ERROR:
3846 AddQuad = &GfxCore::AddQuadrilateralError;
3847 AddPoly = &GfxCore::AddPolylineError;
3848 break;
3849 case COLOUR_BY_GRADIENT:
3850 AddQuad = &GfxCore::AddQuadrilateralGradient;
3851 AddPoly = &GfxCore::AddPolylineGradient;
3852 break;
3853 case COLOUR_BY_LENGTH:
3854 AddQuad = &GfxCore::AddQuadrilateralLength;
3855 AddPoly = &GfxCore::AddPolylineLength;
3856 break;
3857 default: // case COLOUR_BY_NONE:
3858 AddQuad = &GfxCore::AddQuadrilateral;
3859 AddPoly = &GfxCore::AddPolyline;
3860 break;
3863 InvalidateList(LIST_UNDERGROUND_LEGS);
3864 InvalidateList(LIST_SURFACE_LEGS);
3865 InvalidateList(LIST_TUBES);
3867 ForceRefresh();
3870 bool GfxCore::ExportMovie(const wxString & fnm)
3872 FILE* fh = wxFopen(fnm.fn_str(), wxT("wb"));
3873 if (fh == NULL) {
3874 wxGetApp().ReportError(wxString::Format(wmsg(/*Failed to open output file “%s”*/47), fnm.c_str()));
3875 return false;
3878 wxString ext;
3879 wxFileName::SplitPath(fnm, NULL, NULL, NULL, &ext, wxPATH_NATIVE);
3881 int width;
3882 int height;
3883 GetSize(&width, &height);
3884 // Round up to next multiple of 2 (required by ffmpeg).
3885 width += (width & 1);
3886 height += (height & 1);
3888 movie = new MovieMaker();
3890 // movie takes ownership of fh.
3891 if (!movie->Open(fh, ext.utf8_str(), width, height)) {
3892 wxGetApp().ReportError(wxString(movie->get_error_string(), wxConvUTF8));
3893 delete movie;
3894 movie = NULL;
3895 return false;
3898 PlayPres(1);
3899 return true;
3902 void
3903 GfxCore::OnPrint(const wxString &filename, const wxString &title,
3904 const wxString &datestamp, time_t datestamp_numeric,
3905 const wxString &cs_proj,
3906 bool close_after_print)
3908 svxPrintDlg * p;
3909 p = new svxPrintDlg(m_Parent, filename, title, cs_proj,
3910 datestamp, datestamp_numeric,
3911 m_PanAngle, m_TiltAngle,
3912 m_Names, m_Crosses, m_Legs, m_Surface, m_Splays,
3913 m_Tubes, m_Entrances, m_FixedPts, m_ExportedPts,
3914 true, close_after_print);
3915 p->Show(true);
3918 void
3919 GfxCore::OnExport(const wxString &filename, const wxString &title,
3920 const wxString &datestamp, time_t datestamp_numeric,
3921 const wxString &cs_proj)
3923 // Fill in "right_bearing" for each cross-section.
3924 list<vector<XSect> >::iterator trav = m_Parent->tubes_begin();
3925 list<vector<XSect> >::iterator tend = m_Parent->tubes_end();
3926 while (trav != tend) {
3927 SkinPassage(*trav, false);
3928 ++trav;
3931 svxPrintDlg * p;
3932 p = new svxPrintDlg(m_Parent, filename, title, cs_proj,
3933 datestamp, datestamp_numeric,
3934 m_PanAngle, m_TiltAngle,
3935 m_Names, m_Crosses, m_Legs, m_Surface, m_Splays,
3936 m_Tubes, m_Entrances, m_FixedPts, m_ExportedPts,
3937 false);
3938 p->Show(true);
3941 static wxCursor
3942 make_cursor(const unsigned char * bits, const unsigned char * mask,
3943 int hotx, int hoty)
3945 #if defined __WXGTK__ && !defined __WXGTK3__
3946 // Use this code for GTK < 3 only - it doesn't work properly with GTK3
3947 // (reported and should be fixed in wxWidgets 3.0.4 and 3.1.1, see:
3948 // https://trac.wxwidgets.org/ticket/17916)
3949 return wxCursor((const char *)bits, 32, 32, hotx, hoty,
3950 (const char *)mask, wxBLACK, wxWHITE);
3951 #else
3952 # ifdef __WXMAC__
3953 // The default Mac cursor is black with a white edge, so
3954 // invert our custom cursors to match.
3955 char b[128];
3956 for (int i = 0; i < 128; ++i)
3957 b[i] = bits[i] ^ 0xff;
3958 # else
3959 const char * b = reinterpret_cast<const char *>(bits);
3960 # endif
3961 wxBitmap cursor_bitmap(b, 32, 32);
3962 wxBitmap mask_bitmap(reinterpret_cast<const char *>(mask), 32, 32);
3963 cursor_bitmap.SetMask(new wxMask(mask_bitmap, *wxWHITE));
3964 wxImage cursor_image = cursor_bitmap.ConvertToImage();
3965 cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, hotx);
3966 cursor_image.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, hoty);
3967 return wxCursor(cursor_image);
3968 #endif
3971 const
3972 #include "hand.xbm"
3973 const
3974 #include "handmask.xbm"
3976 const
3977 #include "brotate.xbm"
3978 const
3979 #include "brotatemask.xbm"
3981 const
3982 #include "vrotate.xbm"
3983 const
3984 #include "vrotatemask.xbm"
3986 const
3987 #include "rotate.xbm"
3988 const
3989 #include "rotatemask.xbm"
3991 const
3992 #include "rotatezoom.xbm"
3993 const
3994 #include "rotatezoommask.xbm"
3996 void
3997 GfxCore::UpdateCursor(GfxCore::cursor new_cursor)
3999 // Check if we're already showing that cursor.
4000 if (current_cursor == new_cursor) return;
4002 current_cursor = new_cursor;
4003 switch (current_cursor) {
4004 case GfxCore::CURSOR_DEFAULT:
4005 GLACanvas::SetCursor(wxNullCursor);
4006 break;
4007 case GfxCore::CURSOR_POINTING_HAND:
4008 GLACanvas::SetCursor(wxCursor(wxCURSOR_HAND));
4009 break;
4010 case GfxCore::CURSOR_DRAGGING_HAND:
4011 GLACanvas::SetCursor(make_cursor(hand_bits, handmask_bits, 12, 18));
4012 break;
4013 case GfxCore::CURSOR_HORIZONTAL_RESIZE:
4014 GLACanvas::SetCursor(wxCursor(wxCURSOR_SIZEWE));
4015 break;
4016 case GfxCore::CURSOR_ROTATE_HORIZONTALLY:
4017 GLACanvas::SetCursor(make_cursor(rotate_bits, rotatemask_bits, 15, 15));
4018 break;
4019 case GfxCore::CURSOR_ROTATE_VERTICALLY:
4020 GLACanvas::SetCursor(make_cursor(vrotate_bits, vrotatemask_bits, 15, 15));
4021 break;
4022 case GfxCore::CURSOR_ROTATE_EITHER_WAY:
4023 GLACanvas::SetCursor(make_cursor(brotate_bits, brotatemask_bits, 15, 15));
4024 break;
4025 case GfxCore::CURSOR_ZOOM:
4026 GLACanvas::SetCursor(wxCursor(wxCURSOR_MAGNIFIER));
4027 break;
4028 case GfxCore::CURSOR_ZOOM_ROTATE:
4029 GLACanvas::SetCursor(make_cursor(rotatezoom_bits, rotatezoommask_bits, 15, 15));
4030 break;
4034 bool GfxCore::MeasuringLineActive() const
4036 if (Animating()) return false;
4037 return HereIsReal() || m_there;
4040 bool GfxCore::HandleRClick(wxPoint point)
4042 if (PointWithinCompass(point)) {
4043 // Pop up menu.
4044 wxMenu menu;
4045 /* TRANSLATORS: View *looking* North */
4046 menu.Append(menu_ORIENT_MOVE_NORTH, wmsg(/*View &North*/240));
4047 /* TRANSLATORS: View *looking* East */
4048 menu.Append(menu_ORIENT_MOVE_EAST, wmsg(/*View &East*/241));
4049 /* TRANSLATORS: View *looking* South */
4050 menu.Append(menu_ORIENT_MOVE_SOUTH, wmsg(/*View &South*/242));
4051 /* TRANSLATORS: View *looking* West */
4052 menu.Append(menu_ORIENT_MOVE_WEST, wmsg(/*View &West*/243));
4053 menu.AppendSeparator();
4054 /* TRANSLATORS: Menu item which turns off the "north arrow" in aven. */
4055 menu.AppendCheckItem(menu_IND_COMPASS, wmsg(/*&Hide Compass*/387));
4056 /* TRANSLATORS: tickable menu item in View menu.
4058 * Degrees are the angular measurement where there are 360 in a full
4059 * circle. */
4060 menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4061 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
4062 PopupMenu(&menu);
4063 return true;
4066 if (PointWithinClino(point)) {
4067 // Pop up menu.
4068 wxMenu menu;
4069 menu.Append(menu_ORIENT_PLAN, wmsg(/*&Plan View*/248));
4070 menu.Append(menu_ORIENT_ELEVATION, wmsg(/*Ele&vation*/249));
4071 menu.AppendSeparator();
4072 /* TRANSLATORS: Menu item which turns off the tilt indicator in aven. */
4073 menu.AppendCheckItem(menu_IND_CLINO, wmsg(/*&Hide Clino*/384));
4074 /* TRANSLATORS: tickable menu item in View menu.
4076 * Degrees are the angular measurement where there are 360 in a full
4077 * circle. */
4078 menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4079 /* TRANSLATORS: tickable menu item in View menu.
4081 * Show the tilt of the survey as a percentage gradient (100% = 45
4082 * degrees = 50 grad). */
4083 menu.AppendCheckItem(menu_CTL_PERCENT, wmsg(/*&Percent*/430));
4084 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
4085 PopupMenu(&menu);
4086 return true;
4089 if (PointWithinScaleBar(point)) {
4090 // Pop up menu.
4091 wxMenu menu;
4092 /* TRANSLATORS: Menu item which turns off the scale bar in aven. */
4093 menu.AppendCheckItem(menu_IND_SCALE_BAR, wmsg(/*&Hide scale bar*/385));
4094 /* TRANSLATORS: tickable menu item in View menu.
4096 * "Metric" here means metres, km, etc (rather than feet, miles, etc)
4098 menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
4099 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
4100 PopupMenu(&menu);
4101 return true;
4104 if (PointWithinColourKey(point)) {
4105 // Pop up menu.
4106 wxMenu menu;
4107 menu.AppendCheckItem(menu_COLOUR_BY_DEPTH, wmsg(/*Colour by &Depth*/292));
4108 menu.AppendCheckItem(menu_COLOUR_BY_DATE, wmsg(/*Colour by D&ate*/293));
4109 menu.AppendCheckItem(menu_COLOUR_BY_ERROR, wmsg(/*Colour by &Error*/289));
4110 menu.AppendCheckItem(menu_COLOUR_BY_GRADIENT, wmsg(/*Colour by &Gradient*/85));
4111 menu.AppendCheckItem(menu_COLOUR_BY_LENGTH, wmsg(/*Colour by &Length*/82));
4112 menu.AppendSeparator();
4113 /* TRANSLATORS: Menu item which turns off the colour key.
4114 * The "Colour Key" is the thing in aven showing which colour
4115 * corresponds to which depth, date, survey closure error, etc. */
4116 menu.AppendCheckItem(menu_IND_COLOUR_KEY, wmsg(/*&Hide colour key*/386));
4117 if (m_ColourBy == COLOUR_BY_DEPTH || m_ColourBy == COLOUR_BY_LENGTH)
4118 menu.AppendCheckItem(menu_CTL_METRIC, wmsg(/*&Metric*/342));
4119 else if (m_ColourBy == COLOUR_BY_GRADIENT)
4120 menu.AppendCheckItem(menu_CTL_DEGREES, wmsg(/*&Degrees*/343));
4121 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&wxEvtHandler::ProcessEvent, NULL, m_Parent->GetEventHandler());
4122 PopupMenu(&menu);
4123 return true;
4126 return false;
4129 void GfxCore::SetZoomBox(wxPoint p1, wxPoint p2, bool centred, bool aspect)
4131 if (centred) {
4132 p1.x = p2.x + (p1.x - p2.x) * 2;
4133 p1.y = p2.y + (p1.y - p2.y) * 2;
4135 if (aspect) {
4136 #if 0 // FIXME: This needs more work.
4137 int sx = GetXSize();
4138 int sy = GetYSize();
4139 int dx = p1.x - p2.x;
4140 int dy = p1.y - p2.y;
4141 int dy_new = dx * sy / sx;
4142 if (abs(dy_new) >= abs(dy)) {
4143 p1.y += (dy_new - dy) / 2;
4144 p2.y -= (dy_new - dy) / 2;
4145 } else {
4146 int dx_new = dy * sx / sy;
4147 p1.x += (dx_new - dx) / 2;
4148 p2.x -= (dx_new - dx) / 2;
4150 #endif
4152 zoombox.set(p1, p2);
4153 ForceRefresh();
4156 void GfxCore::ZoomBoxGo()
4158 if (!zoombox.active()) return;
4160 int width = GetXSize();
4161 int height = GetYSize();
4163 TranslateCave(-0.5 * (zoombox.x1 + zoombox.x2 - width),
4164 -0.5 * (zoombox.y1 + zoombox.y2 - height));
4165 int box_w = abs(zoombox.x1 - zoombox.x2);
4166 int box_h = abs(zoombox.y1 - zoombox.y2);
4168 double factor = min(double(width) / box_w, double(height) / box_h);
4170 zoombox.unset();
4172 SetScale(GetScale() * factor);