4 // Core drawing code for Aven.
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
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
33 #include "aventreectrl.h"
42 #include "guicontrol.h"
43 #include "moviemaker.h"
45 #include <wx/confbase.h>
46 #include <wx/wfstream.h>
48 #include <wx/zipstrm.h>
52 const unsigned long DEFAULT_HGT_DIM
= 3601;
53 const unsigned long DEFAULT_HGT_SIZE
= sqrd(DEFAULT_HGT_DIM
) * 2;
55 // Values for m_SwitchingTo
63 // Any error value higher than this is clamped to this.
64 #define MAX_ERROR 12.0
66 // Any length greater than pow(10, LOG_LEN_MAX) will be clamped to this.
67 const Double LOG_LEN_MAX
= 1.5;
69 // How many bins per letter height to use when working out non-overlapping
71 const unsigned int QUANTISE_FACTOR
= 2;
75 static const int INDICATOR_BOX_SIZE
= 60;
76 static const int INDICATOR_GAP
= 2;
77 static const int INDICATOR_MARGIN
= 5;
78 static const int INDICATOR_OFFSET_X
= 15;
79 static const int INDICATOR_OFFSET_Y
= 15;
80 static const int INDICATOR_RADIUS
= INDICATOR_BOX_SIZE
/ 2 - INDICATOR_MARGIN
;
81 static const int KEY_OFFSET_X
= 10;
82 static const int KEY_OFFSET_Y
= 10;
83 static const int KEY_EXTRA_LEFT_MARGIN
= 2;
84 static const int KEY_BLOCK_WIDTH
= 20;
85 static const int KEY_BLOCK_HEIGHT
= 16;
86 static const int TICK_LENGTH
= 4;
87 static const int SCALE_BAR_OFFSET_X
= 15;
88 static const int SCALE_BAR_OFFSET_Y
= 12;
89 static const int SCALE_BAR_HEIGHT
= 12;
91 static const gla_colour TEXT_COLOUR
= col_GREEN
;
92 static const gla_colour HERE_COLOUR
= col_WHITE
;
93 static const gla_colour NAME_COLOUR
= col_GREEN
;
94 static const gla_colour SEL_COLOUR
= col_WHITE
;
95 // Used with colour by date for legs without date information and with colour
96 // by error for legs not in a loop.
97 static const gla_colour NODATA_COLOUR
= col_LIGHT_GREY_2
;
99 // Number of entries across and down the hit-test grid:
100 #define HITTEST_SIZE 20
102 // How close the pointer needs to be to a station to be considered:
103 #define MEASURE_THRESHOLD 7
105 // vector for lighting angle
106 static const Vector3
light(.577, .577, .577);
108 BEGIN_EVENT_TABLE(GfxCore
, GLACanvas
)
109 EVT_PAINT(GfxCore::OnPaint
)
110 EVT_LEFT_DOWN(GfxCore::OnLButtonDown
)
111 EVT_LEFT_UP(GfxCore::OnLButtonUp
)
112 EVT_MIDDLE_DOWN(GfxCore::OnMButtonDown
)
113 EVT_MIDDLE_UP(GfxCore::OnMButtonUp
)
114 EVT_RIGHT_DOWN(GfxCore::OnRButtonDown
)
115 EVT_RIGHT_UP(GfxCore::OnRButtonUp
)
116 EVT_MOUSEWHEEL(GfxCore::OnMouseWheel
)
117 EVT_MOTION(GfxCore::OnMouseMove
)
118 EVT_LEAVE_WINDOW(GfxCore::OnLeaveWindow
)
119 EVT_SIZE(GfxCore::OnSize
)
120 EVT_IDLE(GfxCore::OnIdle
)
121 EVT_CHAR(GfxCore::OnKeyPress
)
124 GfxCore::GfxCore(MainFrm
* parent
, wxWindow
* parent_win
, GUIControl
* control
) :
125 GLACanvas(parent_win
, 100),
132 m_DoneFirstShow(false),
140 m_Splays(SHOW_FADED
),
141 m_Dupes(SHOW_DASHED
),
145 m_OverlappingNames(false),
149 m_ColourBy(COLOUR_BY_DEPTH
),
152 m_MouseOutsideCompass(false),
153 m_MouseOutsideElev(false),
157 m_ExportedPts(false),
159 m_BoundingBox(false),
164 m_HitTestDebug(false),
165 m_RenderStats(false),
167 m_HitTestGridValid(false),
170 presentation_mode(0),
174 current_cursor(GfxCore::CURSOR_DEFAULT
),
175 sqrd_measure_threshold(sqrd(MEASURE_THRESHOLD
)),
180 AddQuad
= &GfxCore::AddQuadrilateralDepth
;
181 AddPoly
= &GfxCore::AddPolylineDepth
;
182 wxConfigBase::Get()->Read(wxT("metric"), &m_Metric
, true);
183 wxConfigBase::Get()->Read(wxT("degrees"), &m_Degrees
, true);
184 wxConfigBase::Get()->Read(wxT("percent"), &m_Percent
, false);
186 for (int pen
= 0; pen
< NUM_COLOUR_BANDS
+ 1; ++pen
) {
187 m_Pens
[pen
].SetColour(REDS
[pen
] / 255.0,
199 delete[] m_PointGrid
;
202 void GfxCore::TryToFreeArrays()
204 // Free up any memory allocated for arrays.
205 delete[] m_LabelGrid
;
210 // Initialisation methods
213 void GfxCore::Initialise(bool same_file
)
215 // Initialise the view from the parent holding the survey data.
219 m_DoneFirstShow
= false;
221 m_HitTestGridValid
= false;
225 m_MouseOutsideCompass
= m_MouseOutsideElev
= false;
228 // Apply default parameters unless reloading the same file.
234 // Clear any cached OpenGL lists which depend on the data.
235 InvalidateList(LIST_SCALE_BAR
);
236 InvalidateList(LIST_DEPTH_KEY
);
237 InvalidateList(LIST_DATE_KEY
);
238 InvalidateList(LIST_ERROR_KEY
);
239 InvalidateList(LIST_GRADIENT_KEY
);
240 InvalidateList(LIST_LENGTH_KEY
);
241 InvalidateList(LIST_STYLE_KEY
);
242 InvalidateList(LIST_UNDERGROUND_LEGS
);
243 InvalidateList(LIST_TUBES
);
244 InvalidateList(LIST_SURFACE_LEGS
);
245 InvalidateList(LIST_BLOBS
);
246 InvalidateList(LIST_CROSSES
);
247 InvalidateList(LIST_GRID
);
248 InvalidateList(LIST_SHADOW
);
249 InvalidateList(LIST_TERRAIN
);
251 // Set diameter of the viewing volume.
252 auto ext
= m_Parent
->GetExtent();
253 double cave_diameter
= sqrt(sqrd(ext
.GetX()) +
257 // Allow for terrain.
258 double diameter
= max(1000.0 * 2, cave_diameter
* 2);
261 SetVolumeDiameter(diameter
);
263 // Set initial scale based on the size of the cave.
264 initial_scale
= diameter
/ cave_diameter
;
265 SetScale(initial_scale
);
267 // Adjust the position when restricting the view to a subsurvey (or
268 // expanding the view to show the whole survey).
269 AddTranslation(m_Parent
->GetOffset() - offsets
);
271 // Try to keep the same scale, allowing for the
272 // cave having grown (or shrunk).
273 double rescale
= GetVolumeDiameter() / diameter
;
274 SetVolumeDiameter(diameter
);
275 SetScale(GetScale() / rescale
); // ?
276 initial_scale
= initial_scale
* rescale
;
279 offsets
= m_Parent
->GetOffset();
284 void GfxCore::FirstShow()
286 GLACanvas::FirstShow();
288 const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR
);
289 list
<LabelInfo
*>::iterator pos
= m_Parent
->GetLabelsNC();
290 while (pos
!= m_Parent
->GetLabelsNCEnd()) {
291 LabelInfo
* label
= *pos
++;
292 // Calculate and set the label width for use when plotting
293 // none-overlapping labels.
295 GLACanvas::GetTextExtent(label
->GetText(), &ext_x
, NULL
);
296 label
->set_width(unsigned(ext_x
) / quantise
+ 1);
299 m_DoneFirstShow
= true;
303 // Recalculating methods
306 void GfxCore::SetScale(Double scale
)
310 } else if (scale
> GetVolumeDiameter()) {
311 scale
= GetVolumeDiameter();
315 m_HitTestGridValid
= false;
316 if (m_here
&& m_here
== &temp_here
) SetHere();
318 GLACanvas::SetScale(scale
);
321 bool GfxCore::HasUndergroundLegs() const
323 return m_Parent
->HasUndergroundLegs();
326 bool GfxCore::HasSplays() const
328 return m_Parent
->HasSplays();
331 bool GfxCore::HasDupes() const
333 return m_Parent
->HasDupes();
336 bool GfxCore::HasSurfaceLegs() const
338 return m_Parent
->HasSurfaceLegs();
341 bool GfxCore::HasTubes() const
343 return m_Parent
->HasTubes();
346 void GfxCore::UpdateBlobs()
348 InvalidateList(LIST_BLOBS
);
355 void GfxCore::OnLeaveWindow(wxMouseEvent
&) {
360 void GfxCore::OnIdle(wxIdleEvent
& event
)
362 // Handle an idle event.
365 // If still animating, we want more idle events.
369 // If we're idle, don't show a bogus FPS next time we render.
374 void GfxCore::OnPaint(wxPaintEvent
&)
376 // Redraw the window.
378 // Get a graphics context.
382 // Make sure we're initialised.
383 bool first_time
= !m_DoneFirstShow
;
390 // Clear the background.
393 // Set up model transformation matrix.
396 if (m_Legs
|| m_Tubes
) {
398 EnableSmoothPolygons(true); // FIXME: allow false for wireframe view
399 DrawList(LIST_TUBES
);
400 DisableSmoothPolygons();
403 // Draw the underground legs. Do this last so that anti-aliasing
404 // works over polygons.
405 SetColour(col_GREEN
);
406 DrawList(LIST_UNDERGROUND_LEGS
);
410 // Draw the surface legs.
411 DrawList(LIST_SURFACE_LEGS
);
415 DrawShadowedBoundingBox();
422 DrawList(LIST_BLOBS
);
425 DrawList(LIST_CROSSES
);
429 // Disable texturing while drawing terrain.
430 bool texturing
= GetTextured();
431 if (texturing
) GLACanvas::ToggleTextured();
433 // This is needed if blobs and/or crosses are drawn using lines -
434 // otherwise the terrain doesn't appear when they are enabled.
437 // We don't want to be able to see the terrain through itself, so
438 // do a "Z-prepass" - plot the terrain once only updating the
439 // Z-buffer, then again with Z-clipping only plotting where the
440 // depth matches the value in the Z-buffer.
441 DrawListZPrepass(LIST_TERRAIN
);
443 if (texturing
) GLACanvas::ToggleTextured();
446 SetIndicatorTransform();
448 // Draw station names.
449 if (m_Names
/*&& !m_Control->MouseDown() && !Animating()*/) {
450 SetColour(NAME_COLOUR
);
452 if (m_OverlappingNames
) {
459 if (!highlighted_survey
.empty()) {
463 if (m_HitTestDebug
) {
464 // Show the hit test grid bucket sizes...
465 SetColour(m_HitTestGridValid
? col_LIGHT_GREY
: col_DARK_GREY
);
467 for (int i
= 0; i
!= HITTEST_SIZE
; ++i
) {
468 int x
= (GetXSize() + 1) * i
/ HITTEST_SIZE
+ 2;
469 for (int j
= 0; j
!= HITTEST_SIZE
; ++j
) {
470 int square
= i
+ j
* HITTEST_SIZE
;
471 unsigned long bucket_size
= m_PointGrid
[square
].size();
473 int y
= (GetYSize() + 1) * (HITTEST_SIZE
- 1 - j
) / HITTEST_SIZE
;
474 DrawIndicatorText(x
, y
, wxString::Format(wxT("%lu"), bucket_size
));
482 for (int i
= 0; i
!= HITTEST_SIZE
; ++i
) {
483 int x
= (GetXSize() + 1) * i
/ HITTEST_SIZE
;
484 PlaceIndicatorVertex(x
, 0);
485 PlaceIndicatorVertex(x
, GetYSize());
487 for (int j
= 0; j
!= HITTEST_SIZE
; ++j
) {
488 int y
= (GetYSize() + 1) * (HITTEST_SIZE
- 1 - j
) / HITTEST_SIZE
;
489 PlaceIndicatorVertex(0, y
);
490 PlaceIndicatorVertex(GetXSize(), y
);
493 DisableDashedLines();
496 long now
= timer
.Time();
498 // Show stats about rendering.
499 SetColour(col_TURQUOISE
);
500 int y
= GetYSize() - GetFontSize();
501 if (last_time
!= 0.0) {
502 // timer.Time() measure in milliseconds.
503 double fps
= 1000.0 / (now
- last_time
);
504 DrawIndicatorText(1, y
, wxString::Format(wxT("FPS:% 5.1f"), fps
));
507 DrawIndicatorText(1, y
, wxString::Format(wxT("▲:%lu"), (unsigned long)n_tris
));
513 // There's no advantage in generating an OpenGL list for the
514 // indicators since they change with almost every redraw (and
515 // sometimes several times between redraws). This way we avoid
516 // the need to track when to update the indicator OpenGL list,
517 // and also avoid indicator update bugs when we don't quite get this
521 if (zoombox
.active()) {
522 SetColour(SEL_COLOUR
);
525 glaCoord Y
= GetYSize();
526 PlaceIndicatorVertex(zoombox
.x1
, Y
- zoombox
.y1
);
527 PlaceIndicatorVertex(zoombox
.x1
, Y
- zoombox
.y2
);
528 PlaceIndicatorVertex(zoombox
.x2
, Y
- zoombox
.y2
);
529 PlaceIndicatorVertex(zoombox
.x2
, Y
- zoombox
.y1
);
530 PlaceIndicatorVertex(zoombox
.x1
, Y
- zoombox
.y1
);
532 DisableDashedLines();
533 } else if (MeasuringLineActive()) {
534 // Draw "here" and "there".
536 SetColour(HERE_COLOUR
);
539 Transform(*m_here
, &hx
, &hy
, &dummy
);
540 if (m_here
!= &temp_here
) DrawRing(hx
, hy
);
545 Transform(*m_there
, &tx
, &ty
, &dummy
);
548 PlaceIndicatorVertex(hx
, hy
);
549 PlaceIndicatorVertex(tx
, ty
);
560 dc
.SetBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWFRAME
));
565 void GfxCore::DrawBoundingBox()
567 const Vector3 v
= 0.5 * m_Parent
->GetExtent();
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 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());
586 PlaceVertex(-v
.GetX(), -v
.GetY(), v
.GetZ());
587 PlaceVertex(-v
.GetX(), -v
.GetY(), -v
.GetZ());
588 PlaceVertex(-v
.GetX(), v
.GetY(), v
.GetZ());
589 PlaceVertex(-v
.GetX(), v
.GetY(), -v
.GetZ());
590 PlaceVertex(v
.GetX(), v
.GetY(), v
.GetZ());
591 PlaceVertex(v
.GetX(), v
.GetY(), -v
.GetZ());
592 PlaceVertex(v
.GetX(), -v
.GetY(), v
.GetZ());
593 PlaceVertex(v
.GetX(), -v
.GetY(), -v
.GetZ());
595 DisableDashedLines();
598 void GfxCore::DrawShadowedBoundingBox()
600 const Vector3 v
= 0.5 * m_Parent
->GetExtent();
605 SetColour(col_DARK_GREY
);
606 BeginQuadrilaterals();
607 PlaceVertex(-v
.GetX(), -v
.GetY(), -v
.GetZ());
608 PlaceVertex(-v
.GetX(), v
.GetY(), -v
.GetZ());
609 PlaceVertex(v
.GetX(), v
.GetY(), -v
.GetZ());
610 PlaceVertex(v
.GetX(), -v
.GetY(), -v
.GetZ());
612 PolygonOffset(false);
614 DrawList(LIST_SHADOW
);
617 void GfxCore::DrawGrid()
622 // Calculate the extent of the survey, in metres across the screen plane.
623 Double m_across_screen
= SurveyUnitsAcrossViewport();
624 // Calculate the length of the scale bar in metres.
625 //--move this elsewhere
626 Double size_snap
= pow(10.0, floor(log10(0.75 * m_across_screen
)));
627 Double t
= m_across_screen
* 0.75 / size_snap
;
635 Double grid_size
= size_snap
* 0.1;
636 Double edge
= grid_size
* 2.0;
637 auto ext
= m_Parent
->GetExtent();
638 Double grid_z
= -ext
.GetZ() * 0.5 - grid_size
;
639 Double left
= -ext
.GetX() * 0.5 - edge
;
640 Double right
= ext
.GetX() * 0.5 + edge
;
641 Double bottom
= -ext
.GetY() * 0.5 - edge
;
642 Double top
= ext
.GetY() * 0.5 + edge
;
643 int count_x
= (int) ceil((right
- left
) / grid_size
);
644 int count_y
= (int) ceil((top
- bottom
) / grid_size
);
645 Double actual_right
= left
+ count_x
*grid_size
;
646 Double actual_top
= bottom
+ count_y
*grid_size
;
650 for (int xc
= 0; xc
<= count_x
; xc
++) {
651 Double x
= left
+ xc
*grid_size
;
653 PlaceVertex(x
, bottom
, grid_z
);
654 PlaceVertex(x
, actual_top
, grid_z
);
657 for (int yc
= 0; yc
<= count_y
; yc
++) {
658 Double y
= bottom
+ yc
*grid_size
;
659 PlaceVertex(left
, y
, grid_z
);
660 PlaceVertex(actual_right
, y
, grid_z
);
666 int GfxCore::GetClinoOffset() const
668 int result
= INDICATOR_OFFSET_X
;
670 result
+= 6 + GetCompassWidth() + INDICATOR_GAP
;
675 void GfxCore::DrawTick(int angle_cw
)
677 const Double theta
= rad(angle_cw
);
678 const wxCoord length1
= INDICATOR_RADIUS
;
679 const wxCoord length0
= length1
+ TICK_LENGTH
;
680 wxCoord x0
= wxCoord(length0
* sin(theta
));
681 wxCoord y0
= wxCoord(length0
* cos(theta
));
682 wxCoord x1
= wxCoord(length1
* sin(theta
));
683 wxCoord y1
= wxCoord(length1
* cos(theta
));
685 PlaceIndicatorVertex(x0
, y0
);
686 PlaceIndicatorVertex(x1
, y1
);
689 void GfxCore::DrawArrow(gla_colour col1
, gla_colour col2
) {
690 Vector3
p1(0, INDICATOR_RADIUS
, 0);
691 Vector3
p2(INDICATOR_RADIUS
/2, INDICATOR_RADIUS
*-.866025404, 0); // 150deg
692 Vector3
p3(-INDICATOR_RADIUS
/2, INDICATOR_RADIUS
*-.866025404, 0); // 210deg
695 DrawTriangle(col_LIGHT_GREY
, col1
, p2
, p1
, pc
);
696 DrawTriangle(col_LIGHT_GREY
, col2
, p3
, p1
, pc
);
699 void GfxCore::DrawCompass() {
702 for (int angle
= 315; angle
> 0; angle
-= 45) {
705 SetColour(col_GREEN
);
709 // Compass background.
710 DrawCircle(col_LIGHT_GREY_2
, col_GREY
, 0, 0, INDICATOR_RADIUS
);
713 DrawArrow(col_INDICATOR_1
, col_INDICATOR_2
);
716 // Draw the non-rotating background to the clino.
717 void GfxCore::DrawClinoBack() {
719 for (int angle
= 0; angle
<= 180; angle
+= 90) {
724 PlaceIndicatorVertex(0, INDICATOR_RADIUS
);
725 PlaceIndicatorVertex(0, -INDICATOR_RADIUS
);
726 PlaceIndicatorVertex(0, 0);
727 PlaceIndicatorVertex(INDICATOR_RADIUS
, 0);
732 void GfxCore::DrawClino() {
734 SetColour(col_GREEN
);
740 DrawSemicircle(col_LIGHT_GREY_2
, col_GREY
, 0, 0, INDICATOR_RADIUS
, 0);
743 DrawArrow(col_INDICATOR_2
, col_INDICATOR_1
);
746 void GfxCore::Draw2dIndicators()
748 // Draw the compass and elevation indicators.
750 const int centre_y
= INDICATOR_BOX_SIZE
/ 2 + INDICATOR_OFFSET_Y
;
752 const int comp_centre_x
= GetCompassXPosition();
754 if (m_Compass
&& !m_Parent
->IsExtendedElevation()) {
755 // If the user is dragging the compass with the pointer outside the
756 // compass, we snap to 45 degree multiples, and the ticks go white.
757 SetColour(m_MouseOutsideCompass
? col_WHITE
: col_LIGHT_GREY_2
);
758 DrawList2D(LIST_COMPASS
, comp_centre_x
, centre_y
, -m_PanAngle
);
761 const int elev_centre_x
= GetClinoXPosition();
764 // If the user is dragging the clino with the pointer outside the
765 // clino, we snap to 90 degree multiples, and the ticks go white.
766 SetColour(m_MouseOutsideElev
? col_WHITE
: col_LIGHT_GREY_2
);
767 DrawList2D(LIST_CLINO_BACK
, elev_centre_x
, centre_y
, 0);
768 DrawList2D(LIST_CLINO
, elev_centre_x
, centre_y
, 90 - m_TiltAngle
);
771 SetColour(TEXT_COLOUR
);
773 static int triple_zero_width
= 0;
774 static int height
= 0;
775 if (!triple_zero_width
) {
776 GetTextExtent(wxT("000"), &triple_zero_width
, &height
);
778 const int y_off
= INDICATOR_OFFSET_Y
+ INDICATOR_BOX_SIZE
+ height
/ 2;
780 if (m_Compass
&& !m_Parent
->IsExtendedElevation()) {
785 value
= int(m_PanAngle
);
786 /* TRANSLATORS: degree symbol - probably should be translated to
790 value
= int(m_PanAngle
* 200.0 / 180.0);
791 /* TRANSLATORS: symbol for grad (400 grad = 360 degrees = full
795 str
.Printf(wxT("%03d"), value
);
796 str
+= wmsg(brg_unit
);
797 DrawIndicatorText(comp_centre_x
- triple_zero_width
/ 2, y_off
, str
);
799 // TRANSLATORS: Used in aven above the compass indicator at the lower
800 // right of the display, with a bearing below "Facing". This indicates the
801 // direction the viewer is "facing" in.
803 // Try to keep this translation short - ideally at most 10 characters -
804 // as otherwise the compass and clino will be moved further apart to
806 str
= wmsg(/*Facing*/203);
808 GetTextExtent(str
, &w
, NULL
);
809 DrawIndicatorText(comp_centre_x
- w
/ 2, y_off
+ height
, str
);
813 if (m_TiltAngle
== -90.0) {
814 // TRANSLATORS: Label used for "clino" in Aven when the view is
815 // from directly above.
817 // Try to keep this translation short - ideally at most 10
818 // characters - as otherwise the compass and clino will be moved
819 // further apart to make room. */
820 wxString str
= wmsg(/*Plan*/432);
821 static int width
= 0;
823 GetTextExtent(str
, &width
, NULL
);
825 int x
= elev_centre_x
- width
/ 2;
826 DrawIndicatorText(x
, y_off
+ height
/ 2, str
);
827 } else if (m_TiltAngle
== 90.0) {
828 // TRANSLATORS: Label used for "clino" in Aven when the view is
829 // from directly below.
831 // Try to keep this translation short - ideally at most 10
832 // characters - as otherwise the compass and clino will be moved
833 // further apart to make room. */
834 wxString str
= wmsg(/*Kiwi Plan*/433);
835 static int width
= 0;
837 GetTextExtent(str
, &width
, NULL
);
839 int x
= elev_centre_x
- width
/ 2;
840 DrawIndicatorText(x
, y_off
+ height
/ 2, str
);
847 static int zero_width
= 0;
849 GetTextExtent(wxT("0"), &zero_width
, NULL
);
852 if (m_TiltAngle
> 89.99) {
854 } else if (m_TiltAngle
< -89.99) {
857 angle
= int(100 * tan(rad(m_TiltAngle
)));
859 if (angle
> 99999 || angle
< -99999) {
860 str
= angle
> 0 ? wxT("+") : wxT("-");
861 /* TRANSLATORS: infinity symbol - used for the percentage gradient on
862 * vertical angles. */
863 str
+= wmsg(/*∞*/431);
865 str
= angle
? wxString::Format(wxT("%+03d"), angle
) : wxT("0");
867 /* TRANSLATORS: symbol for percentage gradient (100% = 45
868 * degrees = 50 grad). */
870 } else if (m_Degrees
) {
871 static int zero_zero_width
= 0;
872 if (!zero_zero_width
) {
873 GetTextExtent(wxT("00"), &zero_zero_width
, NULL
);
875 width
= zero_zero_width
;
876 angle
= int(m_TiltAngle
);
877 str
= angle
? wxString::Format(wxT("%+03d"), angle
) : wxT("00");
880 width
= triple_zero_width
;
881 angle
= int(m_TiltAngle
* 200.0 / 180.0);
882 str
= angle
? wxString::Format(wxT("%+04d"), angle
) : wxT("000");
887 if (unit
== /*%*/96) {
888 // Right align % since the width changes so much.
889 GetTextExtent(str
, &sign_offset
, NULL
);
890 sign_offset
-= width
;
891 } else if (angle
< 0) {
892 // Adjust horizontal position so the left of the first digit is
893 // always in the same place.
894 static int minus_width
= 0;
896 GetTextExtent(wxT("-"), &minus_width
, NULL
);
898 sign_offset
= minus_width
;
899 } else if (angle
> 0) {
900 // Adjust horizontal position so the left of the first digit is
901 // always in the same place.
902 static int plus_width
= 0;
904 GetTextExtent(wxT("+"), &plus_width
, NULL
);
906 sign_offset
= plus_width
;
910 DrawIndicatorText(elev_centre_x
- sign_offset
- width
/ 2, y_off
, str
);
912 // TRANSLATORS: Label used for "clino" in Aven when the view is
913 // neither from directly above nor from directly below. It is
914 // also used in the dialog for editing a marked position in a
917 // Try to keep this translation short - ideally at most 10
918 // characters - as otherwise the compass and clino will be moved
919 // further apart to make room. */
920 str
= wmsg(/*Elevation*/118);
921 static int elevation_width
= 0;
922 if (!elevation_width
) {
923 GetTextExtent(str
, &elevation_width
, NULL
);
925 int x
= elev_centre_x
- elevation_width
/ 2;
926 DrawIndicatorText(x
, y_off
+ height
, str
);
931 void GfxCore::NattyDrawNames()
933 // Draw station names, without overlapping.
935 const unsigned int quantise(GetFontSize() / QUANTISE_FACTOR
);
936 const unsigned int quantised_x
= GetXSize() / quantise
;
937 const unsigned int quantised_y
= GetYSize() / quantise
;
938 const size_t buffer_size
= quantised_x
* quantised_y
;
940 if (!m_LabelGrid
) m_LabelGrid
= new char[buffer_size
];
942 memset((void*) m_LabelGrid
, 0, buffer_size
);
944 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
945 list
<LabelInfo
*>::const_iterator label
= m_Parent
->GetLabels();
946 for ( ; label
!= m_Parent
->GetLabelsEnd(); ++label
) {
947 if (m_Splays
== SHOW_HIDE
&& (*label
)->IsSplayEnd())
950 if (!((m_Surface
&& (*label
)->IsSurface()) ||
951 (m_Legs
&& (*label
)->IsUnderground()) ||
952 (!(*label
)->IsSurface() && !(*label
)->IsUnderground()))) {
953 // if this station isn't to be displayed, skip to the next
954 // (last case is for stns with no legs attached)
957 if (filter
&& !filter
->CheckVisible((*label
)->GetText()))
962 Transform(**label
, &x
, &y
, &z
);
963 // Check if the label is behind us (in perspective view).
964 if (z
<= 0.0 || z
>= 1.0) continue;
966 // Apply a small shift so that translating the view doesn't make which
967 // labels are displayed change as the resulting twinkling effect is
970 Transform(Vector3(), &tx
, &ty
, &tz
);
971 tx
-= floor(tx
/ quantise
) * quantise
;
972 ty
-= floor(ty
/ quantise
) * quantise
;
975 if (tx
< 0) continue;
978 if (ty
< 0) continue;
980 unsigned int iy
= unsigned(ty
) / quantise
;
981 if (iy
>= quantised_y
) continue;
982 unsigned int width
= (*label
)->get_width();
983 unsigned int ix
= unsigned(tx
) / quantise
;
984 if (ix
+ width
>= quantised_x
) continue;
986 char * test
= m_LabelGrid
+ ix
+ iy
* quantised_x
;
987 if (memchr(test
, 1, width
)) continue;
990 y
-= GetFontSize() / 2;
991 DrawIndicatorText((int)x
, (int)y
, (*label
)->GetText());
993 if (iy
> QUANTISE_FACTOR
) iy
= QUANTISE_FACTOR
;
994 test
-= quantised_x
* iy
;
996 while (--iy
&& test
< m_LabelGrid
+ buffer_size
) {
997 memset(test
, 1, width
);
1003 void GfxCore::SimpleDrawNames()
1005 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
1006 // Draw all station names, without worrying about overlaps
1007 list
<LabelInfo
*>::const_iterator label
= m_Parent
->GetLabels();
1008 for ( ; label
!= m_Parent
->GetLabelsEnd(); ++label
) {
1009 if (m_Splays
== SHOW_HIDE
&& (*label
)->IsSplayEnd())
1012 if (!((m_Surface
&& (*label
)->IsSurface()) ||
1013 (m_Legs
&& (*label
)->IsUnderground()) ||
1014 (!(*label
)->IsSurface() && !(*label
)->IsUnderground()))) {
1015 // if this station isn't to be displayed, skip to the next
1016 // (last case is for stns with no legs attached)
1019 if (filter
&& !filter
->CheckVisible((*label
)->GetText()))
1023 Transform(**label
, &x
, &y
, &z
);
1025 // Check if the label is behind us (in perspective view).
1026 if (z
<= 0) continue;
1029 y
-= GetFontSize() / 2;
1030 DrawIndicatorText((int)x
, (int)y
, (*label
)->GetText());
1034 void GfxCore::DrawColourKey(int num_bands
, const wxString
& other
, const wxString
& units
)
1036 int total_block_height
=
1037 KEY_BLOCK_HEIGHT
* (num_bands
== 1 ? num_bands
: num_bands
- 1);
1038 if (!other
.empty()) total_block_height
+= KEY_BLOCK_HEIGHT
* 2;
1039 if (!units
.empty()) total_block_height
+= KEY_BLOCK_HEIGHT
;
1041 const int bottom
= -total_block_height
;
1044 if (!other
.empty()) GetTextExtent(other
, &size
, NULL
);
1046 for (band
= 0; band
< num_bands
; ++band
) {
1048 GetTextExtent(key_legends
[band
], &x
, NULL
);
1049 if (x
> size
) size
= x
;
1052 int left
= -KEY_BLOCK_WIDTH
- size
;
1054 key_lowerleft
[m_ColourBy
].x
= left
- KEY_EXTRA_LEFT_MARGIN
;
1055 key_lowerleft
[m_ColourBy
].y
= bottom
;
1056 switch (m_ColourBy
) {
1057 case COLOUR_BY_ERROR
:
1058 case COLOUR_BY_H_ERROR
:
1059 case COLOUR_BY_V_ERROR
:
1060 key_lowerleft
[COLOUR_BY_ERROR
] = key_lowerleft
[m_ColourBy
];
1061 key_lowerleft
[COLOUR_BY_H_ERROR
] = key_lowerleft
[m_ColourBy
];
1062 key_lowerleft
[COLOUR_BY_V_ERROR
] = key_lowerleft
[m_ColourBy
];
1066 if (!units
.empty()) y
+= KEY_BLOCK_HEIGHT
;
1068 if (!other
.empty()) {
1069 DrawRectangle(NODATA_COLOUR
, col_BLACK
,
1071 KEY_BLOCK_WIDTH
, KEY_BLOCK_HEIGHT
);
1072 y
+= KEY_BLOCK_HEIGHT
* 2;
1076 if (num_bands
== 1) {
1077 DrawShadedRectangle(GetPen(0), GetPen(0), left
, y
,
1078 KEY_BLOCK_WIDTH
, KEY_BLOCK_HEIGHT
);
1079 y
+= KEY_BLOCK_HEIGHT
;
1081 for (band
= 0; band
< num_bands
- 1; ++band
) {
1082 DrawShadedRectangle(GetPen(band
), GetPen(band
+ 1), left
, y
,
1083 KEY_BLOCK_WIDTH
, KEY_BLOCK_HEIGHT
);
1084 y
+= KEY_BLOCK_HEIGHT
;
1088 SetColour(col_BLACK
);
1090 PlaceIndicatorVertex(left
, y
);
1091 PlaceIndicatorVertex(left
+ KEY_BLOCK_WIDTH
, y
);
1092 PlaceIndicatorVertex(left
+ KEY_BLOCK_WIDTH
, start
);
1093 PlaceIndicatorVertex(left
, start
);
1094 PlaceIndicatorVertex(left
, y
);
1097 SetColour(TEXT_COLOUR
);
1100 if (!units
.empty()) {
1101 GetTextExtent(units
, &size
, NULL
);
1102 DrawIndicatorText(left
+ (KEY_BLOCK_WIDTH
- size
) / 2, y
, units
);
1103 y
+= KEY_BLOCK_HEIGHT
;
1105 y
-= GetFontSize() / 2;
1106 left
+= KEY_BLOCK_WIDTH
+ 5;
1108 if (!other
.empty()) {
1109 y
+= KEY_BLOCK_HEIGHT
/ 2;
1110 DrawIndicatorText(left
, y
, other
);
1111 y
+= KEY_BLOCK_HEIGHT
* 2 - KEY_BLOCK_HEIGHT
/ 2;
1114 if (num_bands
== 1) {
1115 y
+= KEY_BLOCK_HEIGHT
/ 2;
1116 DrawIndicatorText(left
, y
, key_legends
[0]);
1118 for (band
= 0; band
< num_bands
; ++band
) {
1119 DrawIndicatorText(left
, y
, key_legends
[band
]);
1120 y
+= KEY_BLOCK_HEIGHT
;
1125 void GfxCore::DrawDepthKey()
1127 Double z_ext
= m_Parent
->GetDepthExtent();
1131 num_bands
= GetNumColourBands();
1132 Double z_range
= z_ext
;
1133 if (!m_Metric
) z_range
/= METRES_PER_FOOT
;
1134 sf
= max(0, 1 - (int)floor(log10(z_range
)));
1137 Double z_min
= m_Parent
->GetDepthMin() + m_Parent
->GetOffset().GetZ();
1138 for (int band
= 0; band
< num_bands
; ++band
) {
1141 z
+= z_ext
* band
/ (num_bands
- 1);
1144 z
/= METRES_PER_FOOT
;
1146 key_legends
[band
].Printf(wxT("%.*f"), sf
, z
);
1149 DrawColourKey(num_bands
, wxString(), wmsg(m_Metric
? /*m*/424: /*ft*/428));
1152 void GfxCore::DrawDateKey()
1155 if (!HasDateInformation()) {
1158 int date_ext
= m_Parent
->GetDateExtent();
1159 if (date_ext
== 0) {
1162 num_bands
= GetNumColourBands();
1164 for (int band
= 0; band
< num_bands
; ++band
) {
1166 int days
= m_Parent
->GetDateMin();
1168 days
+= date_ext
* band
/ (num_bands
- 1);
1169 ymd_from_days_since_1900(days
, &y
, &m
, &d
);
1170 key_legends
[band
].Printf(wxT("%04d-%02d-%02d"), y
, m
, d
);
1175 if (!m_Parent
->HasCompleteDateInfo()) {
1176 /* TRANSLATORS: Used in the "colour key" for "colour by date" if there
1177 * are surveys without date information. Try to keep this fairly short.
1179 other
= wmsg(/*Undated*/221);
1182 DrawColourKey(num_bands
, other
, wxString());
1185 void GfxCore::DrawErrorKey()
1188 if (HasErrorInformation()) {
1189 // Use fixed colours for each error factor so it's directly visually
1190 // comparable between surveys.
1191 num_bands
= GetNumColourBands();
1192 for (int band
= 0; band
< num_bands
; ++band
) {
1193 double E
= MAX_ERROR
* band
/ (num_bands
- 1);
1194 key_legends
[band
].Printf(wxT("%.2f"), E
);
1200 // Always show the "Not in loop" legend for now (FIXME).
1201 /* TRANSLATORS: Used in the "colour key" for "colour by error" for surveys
1202 * which aren’t part of a loop and so have no error information. Try to keep
1203 * this fairly short. */
1204 DrawColourKey(num_bands
, wmsg(/*Not in loop*/290), wxString());
1207 void GfxCore::DrawGradientKey()
1210 // Use fixed colours for each gradient so it's directly visually comparable
1212 num_bands
= GetNumColourBands();
1213 wxString units
= wmsg(m_Degrees
? /*°*/344 : /*ᵍ*/345);
1214 for (int band
= 0; band
< num_bands
; ++band
) {
1215 double gradient
= double(band
) / (num_bands
- 1);
1221 key_legends
[band
].Printf(wxT("%.f%s"), gradient
, units
);
1224 DrawColourKey(num_bands
, wxString(), wxString());
1227 void GfxCore::DrawLengthKey()
1230 // Use fixed colours for each length so it's directly visually comparable
1232 num_bands
= GetNumColourBands();
1233 for (int band
= 0; band
< num_bands
; ++band
) {
1234 double len
= pow(10, LOG_LEN_MAX
* band
/ (num_bands
- 1));
1236 len
/= METRES_PER_FOOT
;
1238 key_legends
[band
].Printf(wxT("%.1f"), len
);
1241 DrawColourKey(num_bands
, wxString(), wmsg(m_Metric
? /*m*/424: /*ft*/428));
1244 static const gla_colour style_colours
[] = {
1245 NODATA_COLOUR
, // img_STYLE_UNKNOWN
1246 col_GREEN
, // img_STYLE_NORMAL
1247 col_BLUE
, // img_STYLE_DIVING
1248 col_YELLOW
, // img_STYLE_CARTESIAN
1249 col_MAGENTA
, // img_STYLE_CYLPOLAR
1250 col_RED
// img_STYLE_NOSURVEY
1254 static const char* style_names
[] = {
1263 void GfxCore::DrawStyleKey()
1265 int num_bands
= sizeof(style_names
) / sizeof(style_names
[0]);
1266 int total_block_height
= KEY_BLOCK_HEIGHT
* (2 * num_bands
- 1);
1268 const int bottom
= -total_block_height
;
1271 for (int band
= 0; band
< num_bands
; ++band
) {
1273 GetTextExtent(style_names
[band
], &x
, NULL
);
1274 if (x
> size
) size
= x
;
1277 int left
= -KEY_BLOCK_WIDTH
- size
;
1279 key_lowerleft
[m_ColourBy
].x
= left
- KEY_EXTRA_LEFT_MARGIN
;
1280 key_lowerleft
[m_ColourBy
].y
= bottom
;
1283 for (int band
= 0; band
< num_bands
; ++band
) {
1284 DrawRectangle(style_colours
[band
], col_BLACK
,
1286 KEY_BLOCK_WIDTH
, KEY_BLOCK_HEIGHT
);
1287 y
+= KEY_BLOCK_HEIGHT
* 2;
1290 SetColour(TEXT_COLOUR
);
1293 left
+= KEY_BLOCK_WIDTH
+ 5;
1295 for (int band
= 0; band
< num_bands
; ++band
) {
1296 DrawIndicatorText(left
, y
, style_names
[band
]);
1297 y
+= KEY_BLOCK_HEIGHT
* 2;
1301 void GfxCore::DrawScaleBar()
1303 // Calculate how many metres of survey are currently displayed across the
1305 Double across_screen
= SurveyUnitsAcrossViewport();
1307 double f
= double(GetClinoXPosition() - INDICATOR_BOX_SIZE
/ 2 - SCALE_BAR_OFFSET_X
) / GetXSize();
1310 } else if (f
< 0.5) {
1311 // Stop it getting squeezed to nothing.
1312 // FIXME: In this case we should probably move the compass and clino up
1313 // to make room rather than letting stuff overlap.
1317 // Convert to imperial measurements if required.
1318 Double multiplier
= 1.0;
1320 across_screen
/= METRES_PER_FOOT
;
1321 multiplier
= METRES_PER_FOOT
;
1322 if (across_screen
>= 5280.0 / f
) {
1323 across_screen
/= 5280.0;
1324 multiplier
*= 5280.0;
1328 // Calculate the length of the scale bar.
1329 Double size_snap
= pow(10.0, floor(log10(f
* across_screen
)));
1330 Double t
= across_screen
* f
/ size_snap
;
1333 } else if (t
>= 2.0) {
1337 if (!m_Metric
) size_snap
*= multiplier
;
1339 // Actual size of the thing in pixels:
1340 int size
= int((size_snap
/ SurveyUnitsAcrossViewport()) * GetXSize());
1341 m_ScaleBarWidth
= size
;
1344 const int end_y
= SCALE_BAR_OFFSET_Y
+ SCALE_BAR_HEIGHT
;
1345 int interval
= size
/ 10;
1347 gla_colour col
= col_WHITE
;
1348 for (int ix
= 0; ix
< 10; ix
++) {
1349 int x
= SCALE_BAR_OFFSET_X
+ int(ix
* ((Double
) size
/ 10.0));
1351 DrawRectangle(col
, col
, x
, end_y
, interval
+ 2, SCALE_BAR_HEIGHT
);
1353 col
= (col
== col_WHITE
) ? col_GREY
: col_WHITE
;
1360 Double km
= size_snap
* 1e-3;
1363 /* TRANSLATORS: abbreviation for "kilometres" (unit of length),
1366 * If there should be a space between the number and this, include
1367 * one in the translation. */
1369 } else if (size_snap
>= 1.0) {
1370 /* TRANSLATORS: abbreviation for "metres" (unit of length), used
1373 * If there should be a space between the number and this, include
1374 * one in the translation. */
1378 /* TRANSLATORS: abbreviation for "centimetres" (unit of length),
1381 * If there should be a space between the number and this, include
1382 * one in the translation. */
1386 size_snap
/= METRES_PER_FOOT
;
1387 Double miles
= size_snap
/ 5280.0;
1390 if (size_snap
>= 2.0) {
1391 /* TRANSLATORS: abbreviation for "miles" (unit of length,
1392 * plural), used e.g. "2 miles".
1394 * If there should be a space between the number and this,
1395 * include one in the translation. */
1396 units
= /* miles*/426;
1398 /* TRANSLATORS: abbreviation for "mile" (unit of length,
1399 * singular), used e.g. "1 mile".
1401 * If there should be a space between the number and this,
1402 * include one in the translation. */
1403 units
= /* mile*/427;
1405 } else if (size_snap
>= 1.0) {
1406 /* TRANSLATORS: abbreviation for "feet" (unit of length), used e.g.
1409 * If there should be a space between the number and this, include
1410 * one in the translation. */
1414 /* TRANSLATORS: abbreviation for "inches" (unit of length), used
1417 * If there should be a space between the number and this, include
1418 * one in the translation. */
1422 if (size_snap
>= 1.0) {
1423 str
.Printf(wxT("%.f%s"), size_snap
, wmsg(units
).c_str());
1425 int sf
= -(int)floor(log10(size_snap
));
1426 str
.Printf(wxT("%.*f%s"), sf
, size_snap
, wmsg(units
).c_str());
1429 int text_width
, text_height
;
1430 GetTextExtent(str
, &text_width
, &text_height
);
1431 const int text_y
= end_y
- text_height
+ 1;
1432 SetColour(TEXT_COLOUR
);
1433 DrawIndicatorText(SCALE_BAR_OFFSET_X
, text_y
, wxT("0"));
1434 DrawIndicatorText(SCALE_BAR_OFFSET_X
+ size
- text_width
, text_y
, str
);
1437 bool GfxCore::CheckHitTestGrid(const wxPoint
& point
, bool centre
)
1439 if (Animating()) return false;
1441 if (point
.x
< 0 || point
.x
>= GetXSize() ||
1442 point
.y
< 0 || point
.y
>= GetYSize()) {
1448 if (!m_HitTestGridValid
) CreateHitTestGrid();
1450 int grid_x
= point
.x
* HITTEST_SIZE
/ (GetXSize() + 1);
1451 int grid_y
= point
.y
* HITTEST_SIZE
/ (GetYSize() + 1);
1453 LabelInfo
*best
= NULL
;
1454 int dist_sqrd
= sqrd_measure_threshold
;
1455 int square
= grid_x
+ grid_y
* HITTEST_SIZE
;
1456 list
<LabelInfo
*>::iterator iter
= m_PointGrid
[square
].begin();
1458 while (iter
!= m_PointGrid
[square
].end()) {
1459 LabelInfo
*pt
= *iter
++;
1463 Transform(*pt
, &cx
, &cy
, &cz
);
1465 cy
= GetYSize() - cy
;
1467 int dx
= point
.x
- int(cx
);
1469 if (ds
>= dist_sqrd
) continue;
1470 int dy
= point
.y
- int(cy
);
1473 if (ds
>= dist_sqrd
) continue;
1482 m_Parent
->ShowInfo(best
, m_there
);
1484 // FIXME: allow Ctrl-Click to not set there or something?
1486 WarpPointer(GetXSize() / 2, GetYSize() / 2);
1488 m_Parent
->SelectTreeItem(best
);
1491 // Left-clicking not on a survey cancels the measuring line.
1493 ClearTreeSelection();
1495 m_Parent
->ShowInfo(best
, m_there
);
1497 ReverseTransform(point
.x
, GetYSize() - point
.y
, &x
, &y
, &z
);
1498 temp_here
.assign(Vector3(x
, y
, z
));
1499 SetHere(&temp_here
);
1506 void GfxCore::OnSize(wxSizeEvent
& event
)
1508 // Handle a change in window size.
1509 wxSize size
= event
.GetSize();
1511 if (size
.GetWidth() <= 0 || size
.GetHeight() <= 0) {
1512 // Before things are fully initialised, we sometimes get a bogus
1513 // resize message...
1514 // FIXME have changes in MainFrm cured this? It still happens with
1515 // 1.0.32 and wxGTK 2.5.2 (load a file from the command line).
1516 // With 1.1.6 and wxGTK 2.4.2 we only get negative sizes if MainFrm
1517 // is resized such that the GfxCore window isn't visible.
1518 //printf("OnSize(%d,%d)\n", size.GetWidth(), size.GetHeight());
1524 if (m_DoneFirstShow
) {
1527 m_HitTestGridValid
= false;
1533 void GfxCore::DefaultParameters()
1535 // Set default viewing parameters.
1538 if (!m_Parent
->HasUndergroundLegs()) {
1539 if (m_Parent
->HasSurfaceLegs()) {
1540 // If there are surface legs, but no underground legs, turn
1541 // surface surveys on.
1544 // If there are no legs (e.g. after loading a .pos file), turn
1551 if (m_Parent
->IsExtendedElevation()) {
1554 m_TiltAngle
= -90.0;
1557 SetRotation(m_PanAngle
, m_TiltAngle
);
1558 SetTranslation(Vector3());
1560 m_RotationStep
= 30.0;
1563 m_Entrances
= false;
1565 m_ExportedPts
= false;
1567 m_BoundingBox
= false;
1569 if (GetPerspective()) TogglePerspective();
1571 // Set the initial scale.
1572 SetScale(initial_scale
);
1575 void GfxCore::Defaults()
1577 // Restore default scale, rotation and translation parameters.
1578 DefaultParameters();
1580 // Invalidate all the cached lists.
1581 GLACanvas::FirstShow();
1586 void GfxCore::Animate()
1588 // Don't show pointer coordinates while animating.
1589 // FIXME : only do this when we *START* animating! Use a static copy
1590 // of the value of "Animating()" last time we were here to track this?
1591 // MainFrm now checks if we're trying to clear already cleared labels
1592 // and just returns, but it might be simpler to check here!
1594 m_Parent
->ShowInfo();
1598 ReadPixels(movie
->GetWidth(), movie
->GetHeight(), movie
->GetBuffer());
1599 if (!movie
->AddFrame()) {
1600 wxGetApp().ReportError(wxString(movie
->get_error_string(), wxConvUTF8
));
1603 presentation_mode
= 0;
1606 t
= 1000 / 25; // 25 frames per second
1608 static long t_prev
= 0;
1610 // Avoid redrawing twice in the same frame.
1611 long delta_t
= (t_prev
== 0 ? 1000 / MAX_FRAMERATE
: t
- t_prev
);
1612 if (delta_t
< 1000 / MAX_FRAMERATE
)
1615 if (presentation_mode
== PLAYING
&& pres_speed
!= 0.0)
1619 if (presentation_mode
== PLAYING
&& pres_speed
!= 0.0) {
1620 // FIXME: It would probably be better to work relative to the time we
1621 // passed the last mark, but that's complicated by the speed
1622 // potentially changing (or even the direction of playback reversing)
1623 // at any point during playback.
1624 Double tick
= t
* 0.001 * fabs(pres_speed
);
1625 while (tick
>= next_mark_time
) {
1626 tick
-= next_mark_time
;
1627 this_mark_total
= 0;
1628 PresentationMark prev_mark
= next_mark
;
1629 if (prev_mark
.angle
< 0) prev_mark
.angle
+= 360.0;
1630 else if (prev_mark
.angle
>= 360.0) prev_mark
.angle
-= 360.0;
1632 next_mark
= m_Parent
->GetPresMark(MARK_PREV
);
1634 next_mark
= m_Parent
->GetPresMark(MARK_NEXT
);
1635 if (!next_mark
.is_valid()) {
1637 presentation_mode
= 0;
1638 if (movie
&& !movie
->Close()) {
1639 wxGetApp().ReportError(wxString(movie
->get_error_string(), wxConvUTF8
));
1646 double tmp
= (pres_reverse
? prev_mark
.time
: next_mark
.time
);
1648 next_mark_time
= tmp
;
1650 double d
= (next_mark
- prev_mark
).magnitude();
1651 // FIXME: should ignore component of d which is unseen in
1652 // non-perspective mode?
1653 next_mark_time
= sqrd(d
/ 30.0);
1654 double a
= next_mark
.angle
- prev_mark
.angle
;
1656 next_mark
.angle
-= 360.0;
1658 } else if (a
< -180.0) {
1659 next_mark
.angle
+= 360.0;
1664 next_mark_time
+= sqrd(a
/ 60.0);
1665 double ta
= fabs(next_mark
.tilt_angle
- prev_mark
.tilt_angle
);
1666 next_mark_time
+= sqrd(ta
/ 60.0);
1667 double s
= fabs(log(next_mark
.scale
) - log(prev_mark
.scale
));
1668 next_mark_time
+= sqrd(s
/ 2.0);
1669 next_mark_time
= sqrt(next_mark_time
);
1670 // was: next_mark_time = max(max(d / 30, s / 2), max(a, ta) / 60);
1671 //printf("*** %.6f from (\nd: %.6f\ns: %.6f\na: %.6f\nt: %.6f )\n",
1672 // next_mark_time, d/30.0, s/2.0, a/60.0, ta/60.0);
1673 if (tmp
< 0) next_mark_time
/= -tmp
;
1677 if (presentation_mode
) {
1678 // Advance position towards next_mark
1679 double p
= tick
/ next_mark_time
;
1681 PresentationMark here
= GetView();
1682 if (next_mark
.angle
< 0) {
1683 if (here
.angle
>= next_mark
.angle
+ 360.0)
1684 here
.angle
-= 360.0;
1685 } else if (next_mark
.angle
>= 360.0) {
1686 if (here
.angle
<= next_mark
.angle
- 360.0)
1687 here
.angle
+= 360.0;
1689 here
.assign(q
* here
+ p
* next_mark
);
1690 here
.angle
= q
* here
.angle
+ p
* next_mark
.angle
;
1691 if (here
.angle
< 0) here
.angle
+= 360.0;
1692 else if (here
.angle
>= 360.0) here
.angle
-= 360.0;
1693 here
.tilt_angle
= q
* here
.tilt_angle
+ p
* next_mark
.tilt_angle
;
1694 here
.scale
= exp(q
* log(here
.scale
) + p
* log(next_mark
.scale
));
1696 this_mark_total
+= tick
;
1697 next_mark_time
-= tick
;
1706 Double step
= base_pan
+ (t
- base_pan_time
) * 1e-3 * m_RotationStep
- m_PanAngle
;
1710 if (m_SwitchingTo
== PLAN
) {
1711 // When switching to plan view...
1712 Double step
= base_tilt
- (t
- base_tilt_time
) * 1e-3 * 90.0 - m_TiltAngle
;
1714 if (m_TiltAngle
== -90.0) {
1717 } else if (m_SwitchingTo
== ELEVATION
) {
1718 // When switching to elevation view...
1720 if (m_TiltAngle
> 0.0) {
1721 step
= base_tilt
- (t
- base_tilt_time
) * 1e-3 * 90.0 - m_TiltAngle
;
1723 step
= base_tilt
+ (t
- base_tilt_time
) * 1e-3 * 90.0 - m_TiltAngle
;
1725 if (fabs(step
) >= fabs(m_TiltAngle
)) {
1727 step
= -m_TiltAngle
;
1730 } else if (m_SwitchingTo
) {
1731 // Rotate the shortest way around to the destination angle. If we're
1732 // 180 off, we favour turning anticlockwise, as auto-rotation does by
1734 Double target
= (m_SwitchingTo
- NORTH
) * 90;
1735 Double diff
= target
- m_PanAngle
;
1736 diff
= fmod(diff
, 360);
1739 else if (diff
> 180)
1741 if (m_RotationStep
< 0 && diff
== 180.0)
1743 Double step
= base_pan
- m_PanAngle
;
1744 Double delta
= (t
- base_pan_time
) * 1e-3 * fabs(m_RotationStep
);
1750 step
= fmod(step
, 360);
1753 else if (step
> 180)
1755 if (fabs(step
) >= fabs(diff
)) {
1765 // How much to allow around the box - this is because of the ring shape
1766 // at one end of the line.
1767 static const int HIGHLIGHTED_PT_SIZE
= 2; // FIXME: tie in to blob and ring size
1768 #define MARGIN (HIGHLIGHTED_PT_SIZE * 2 + 1)
1769 void GfxCore::RefreshLine(const Point
*a
, const Point
*b
, const Point
*c
)
1775 // FIXME: We get odd redraw artifacts if we just update the line, and
1776 // redrawing the whole scene doesn't actually seem to be measurably
1777 // slower. That may not be true with software rendering though...
1780 // Best of all might be to copy the window contents before we draw the
1781 // line, then replace each time we redraw.
1783 // Calculate the minimum rectangle which includes the old and new
1784 // measuring lines to minimise the redraw time
1785 int l
= INT_MAX
, r
= INT_MIN
, u
= INT_MIN
, d
= INT_MAX
;
1788 if (!Transform(*a
, &X
, &Y
, &Z
)) {
1792 int y
= GetYSize() - 1 - int(Y
);
1800 if (!Transform(*b
, &X
, &Y
, &Z
)) {
1804 int y
= GetYSize() - 1 - int(Y
);
1812 if (!Transform(*c
, &X
, &Y
, &Z
)) {
1816 int y
= GetYSize() - 1 - int(Y
);
1827 RefreshRect(wxRect(l
, d
, r
- l
, u
- d
), false);
1831 void GfxCore::HighlightSurvey()
1833 SurveyFilter filter
;
1834 filter
.add(highlighted_survey
);
1835 filter
.SetSeparator(m_Parent
->GetSeparator());
1837 double x_min
= HUGE_VAL
, x_max
= -HUGE_VAL
;
1838 double y_min
= HUGE_VAL
, y_max
= -HUGE_VAL
;
1839 double xpy_min
= HUGE_VAL
, xpy_max
= -HUGE_VAL
;
1840 double xmy_min
= HUGE_VAL
, xmy_max
= -HUGE_VAL
;
1841 list
<LabelInfo
*>::const_iterator pos
= m_Parent
->GetLabels();
1842 double x_tot
= 0, y_tot
= 0;
1844 while (pos
!= m_Parent
->GetLabelsEnd()) {
1845 const LabelInfo
* label
= *pos
++;
1846 if (!filter
.CheckVisible(label
->GetText()))
1850 Transform(*label
, &x
, &y
, &z
);
1851 if (x
< x_min
) x_min
= x
;
1852 if (x
> x_max
) x_max
= x
;
1853 if (y
< y_min
) y_min
= y
;
1854 if (y
> y_max
) y_max
= y
;
1856 if (xpy
< xpy_min
) xpy_min
= xpy
;
1857 if (xpy
> xpy_max
) xpy_max
= xpy
;
1859 if (xmy
< xmy_min
) xmy_min
= xmy
;
1860 if (xmy
> xmy_max
) xmy_max
= xmy
;
1865 for (int f
= 0; f
!= 8; ++f
) {
1866 list
<traverse
>::const_iterator trav
= m_Parent
->traverses_begin(f
, &filter
);
1867 list
<traverse
>::const_iterator tend
= m_Parent
->traverses_end(f
);
1868 while (trav
!= tend
) {
1869 for (auto&& p
: *trav
) {
1871 Transform(p
, &x
, &y
, &z
);
1872 if (x
< x_min
) x_min
= x
;
1873 if (x
> x_max
) x_max
= x
;
1874 if (y
< y_min
) y_min
= y
;
1875 if (y
> y_max
) y_max
= y
;
1877 if (xpy
< xpy_min
) xpy_min
= xpy
;
1878 if (xpy
> xpy_max
) xpy_max
= xpy
;
1880 if (xmy
< xmy_min
) xmy_min
= xmy
;
1881 if (xmy
> xmy_max
) xmy_max
= xmy
;
1886 trav
= m_Parent
->traverses_next(f
, &filter
, trav
);
1892 // Minimum margin around survey.
1893 const double M
= 4.0;
1894 // X/Y component when M measured diagonally.
1895 const double D
= M
* sqrt(2.0) / 2.0;
1897 SetColour(col_WHITE
);
1899 PlaceIndicatorVertex(xmy_max
+ y_min
, y_min
- M
);
1900 PlaceIndicatorVertex(xmy_max
+ y_min
+ D
, y_min
- D
);
1901 PlaceIndicatorVertex(x_max
+ D
, x_max
- xmy_max
- D
);
1902 PlaceIndicatorVertex(x_max
+ M
, x_max
- xmy_max
);
1903 PlaceIndicatorVertex(x_max
+ M
, xpy_max
- x_max
);
1904 PlaceIndicatorVertex(x_max
+ D
, xpy_max
- x_max
+ D
);
1905 PlaceIndicatorVertex(xpy_max
- y_max
+ D
, y_max
+ D
);
1906 PlaceIndicatorVertex(xpy_max
- y_max
, y_max
+ M
);
1907 PlaceIndicatorVertex(xmy_min
+ y_max
, y_max
+ M
);
1908 PlaceIndicatorVertex(xmy_min
+ y_max
- D
, y_max
+ D
);
1909 PlaceIndicatorVertex(x_min
- D
, x_min
- xmy_min
+ D
);
1910 PlaceIndicatorVertex(x_min
- M
, x_min
- xmy_min
);
1911 PlaceIndicatorVertex(x_min
- M
, xpy_min
- x_min
);
1912 PlaceIndicatorVertex(x_min
- D
, xpy_min
- x_min
- D
);
1913 PlaceIndicatorVertex(xpy_min
- y_min
- D
, y_min
- D
);
1914 PlaceIndicatorVertex(xpy_min
- y_min
, y_min
- M
);
1918 void GfxCore::ZoomToSurvey(const wxString
& survey
) {
1919 SurveyFilter filter
;
1921 filter
.SetSeparator(m_Parent
->GetSeparator());
1923 Double xmin
= DBL_MAX
;
1924 Double xmax
= -DBL_MAX
;
1925 Double ymin
= DBL_MAX
;
1926 Double ymax
= -DBL_MAX
;
1927 Double zmin
= DBL_MAX
;
1928 Double zmax
= -DBL_MAX
;
1930 list
<LabelInfo
*>::const_iterator pos
= m_Parent
->GetLabels();
1931 while (pos
!= m_Parent
->GetLabelsEnd()) {
1932 LabelInfo
* label
= *pos
++;
1934 if (!filter
.CheckVisible(label
->GetText()))
1937 if (label
->GetX() < xmin
) xmin
= label
->GetX();
1938 if (label
->GetX() > xmax
) xmax
= label
->GetX();
1939 if (label
->GetY() < ymin
) ymin
= label
->GetY();
1940 if (label
->GetY() > ymax
) ymax
= label
->GetY();
1941 if (label
->GetZ() < zmin
) zmin
= label
->GetZ();
1942 if (label
->GetZ() > zmax
) zmax
= label
->GetZ();
1945 SetViewTo(xmin
, xmax
, ymin
, ymax
, zmin
, zmax
);
1948 void GfxCore::SetHereFromTree(const LabelInfo
* p
)
1951 m_Parent
->ShowInfo(m_here
, m_there
);
1952 SetHereSurvey(wxString());
1955 void GfxCore::SetHere(const LabelInfo
*p
)
1957 if (p
== m_here
) return;
1958 bool line_active
= MeasuringLineActive();
1959 const LabelInfo
* old
= m_here
;
1961 if (line_active
|| MeasuringLineActive())
1962 RefreshLine(old
, m_there
, m_here
);
1965 void GfxCore::SetThere(const LabelInfo
* p
)
1967 if (p
== m_there
) return;
1968 const LabelInfo
* old
= m_there
;
1970 RefreshLine(m_here
, old
, m_there
);
1973 void GfxCore::CreateHitTestGrid()
1976 // Initialise hit-test grid.
1977 m_PointGrid
= new list
<LabelInfo
*>[HITTEST_SIZE
* HITTEST_SIZE
];
1979 // Clear hit-test grid.
1980 for (int i
= 0; i
< HITTEST_SIZE
* HITTEST_SIZE
; i
++) {
1981 m_PointGrid
[i
].clear();
1985 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
1987 list
<LabelInfo
*>::const_iterator pos
= m_Parent
->GetLabels();
1988 list
<LabelInfo
*>::const_iterator end
= m_Parent
->GetLabelsEnd();
1989 while (pos
!= end
) {
1990 LabelInfo
* label
= *pos
++;
1992 if (m_Splays
== SHOW_HIDE
&& label
->IsSplayEnd())
1995 if (!((m_Surface
&& label
->IsSurface()) ||
1996 (m_Legs
&& label
->IsUnderground()) ||
1997 (!label
->IsSurface() && !label
->IsUnderground()))) {
1998 // if this station isn't to be displayed, skip to the next
1999 // (last case is for stns with no legs attached)
2003 if (filter
&& !filter
->CheckVisible(label
->GetText()))
2006 // Calculate screen coordinates.
2008 Transform(*label
, &cx
, &cy
, &cz
);
2009 if (cx
< 0 || cx
>= GetXSize()) continue;
2010 if (cy
< 0 || cy
>= GetYSize()) continue;
2012 cy
= GetYSize() - cy
;
2014 // On-screen, so add to hit-test grid...
2015 int grid_x
= int(cx
* HITTEST_SIZE
/ (GetXSize() + 1));
2016 int grid_y
= int(cy
* HITTEST_SIZE
/ (GetYSize() + 1));
2018 m_PointGrid
[grid_x
+ grid_y
* HITTEST_SIZE
].push_back(label
);
2021 m_HitTestGridValid
= true;
2025 // Methods for controlling the orientation of the survey
2028 void GfxCore::TurnCave(Double angle
)
2030 // Turn the cave around its z-axis by a given angle.
2032 m_PanAngle
+= angle
;
2033 // Wrap to range [0, 360):
2034 m_PanAngle
= fmod(m_PanAngle
, 360.0);
2035 if (m_PanAngle
< 0.0) {
2036 m_PanAngle
+= 360.0;
2039 m_HitTestGridValid
= false;
2040 if (m_here
&& m_here
== &temp_here
) SetHere();
2042 SetRotation(m_PanAngle
, m_TiltAngle
);
2045 void GfxCore::TurnCaveTo(Double angle
)
2048 // If we're rotating, jump to the specified angle.
2049 TurnCave(angle
- m_PanAngle
);
2054 int new_switching_to
= ((int)angle
) / 90 + NORTH
;
2055 if (new_switching_to
== m_SwitchingTo
) {
2056 // A second order to switch takes us there right away
2057 TurnCave(angle
- m_PanAngle
);
2062 m_SwitchingTo
= new_switching_to
;
2066 void GfxCore::TiltCave(Double tilt_angle
)
2068 // Tilt the cave by a given angle.
2069 if (m_TiltAngle
+ tilt_angle
> 90.0) {
2071 } else if (m_TiltAngle
+ tilt_angle
< -90.0) {
2072 m_TiltAngle
= -90.0;
2074 m_TiltAngle
+= tilt_angle
;
2077 m_HitTestGridValid
= false;
2078 if (m_here
&& m_here
== &temp_here
) SetHere();
2080 SetRotation(m_PanAngle
, m_TiltAngle
);
2083 void GfxCore::TranslateCave(int dx
, int dy
)
2085 AddTranslationScreenCoordinates(dx
, dy
);
2086 m_HitTestGridValid
= false;
2088 if (m_here
&& m_here
== &temp_here
) SetHere();
2093 void GfxCore::DragFinished()
2095 m_MouseOutsideCompass
= m_MouseOutsideElev
= false;
2099 void GfxCore::ClearCoords()
2101 m_Parent
->ClearCoords();
2104 void GfxCore::SetCoords(wxPoint point
)
2106 // We can't work out 2D coordinates from a perspective view, and it
2107 // doesn't really make sense to show coordinates while we're animating.
2108 if (GetPerspective() || Animating()) return;
2110 // Update the coordinate or altitude display, given the (x, y) position in
2111 // window coordinates. The relevant display is updated depending on
2112 // whether we're in plan or elevation view.
2117 ReverseTransform(point
.x
, GetYSize() - 1 - point
.y
, &cx
, &cy
, &cz
);
2119 if (ShowingPlan()) {
2120 m_Parent
->SetCoords(cx
+ m_Parent
->GetOffset().GetX(),
2121 cy
+ m_Parent
->GetOffset().GetY(),
2123 } else if (ShowingElevation()) {
2124 m_Parent
->SetAltitude(cz
+ m_Parent
->GetOffset().GetZ(),
2127 m_Parent
->ClearCoords();
2131 int GfxCore::GetCompassWidth() const
2133 static int result
= 0;
2135 result
= INDICATOR_BOX_SIZE
;
2137 const wxString
& msg
= wmsg(/*Facing*/203);
2138 GetTextExtent(msg
, &width
, NULL
);
2139 if (width
> result
) result
= width
;
2144 int GfxCore::GetClinoWidth() const
2146 static int result
= 0;
2148 result
= INDICATOR_BOX_SIZE
;
2150 const wxString
& msg1
= wmsg(/*Plan*/432);
2151 GetTextExtent(msg1
, &width
, NULL
);
2152 if (width
> result
) result
= width
;
2153 const wxString
& msg2
= wmsg(/*Kiwi Plan*/433);
2154 GetTextExtent(msg2
, &width
, NULL
);
2155 if (width
> result
) result
= width
;
2156 const wxString
& msg3
= wmsg(/*Elevation*/118);
2157 GetTextExtent(msg3
, &width
, NULL
);
2158 if (width
> result
) result
= width
;
2163 int GfxCore::GetCompassXPosition() const
2165 // Return the x-coordinate of the centre of the compass in window
2167 return GetXSize() - INDICATOR_OFFSET_X
- GetCompassWidth() / 2;
2170 int GfxCore::GetClinoXPosition() const
2172 // Return the x-coordinate of the centre of the compass in window
2174 return GetXSize() - GetClinoOffset() - GetClinoWidth() / 2;
2177 int GfxCore::GetIndicatorYPosition() const
2179 // Return the y-coordinate of the centre of the indicators in window
2181 return GetYSize() - INDICATOR_OFFSET_Y
- INDICATOR_BOX_SIZE
/ 2;
2184 int GfxCore::GetIndicatorRadius() const
2186 // Return the radius of each indicator.
2187 return (INDICATOR_BOX_SIZE
- INDICATOR_MARGIN
* 2) / 2;
2190 bool GfxCore::PointWithinCompass(wxPoint point
) const
2192 // Determine whether a point (in window coordinates) lies within the
2194 if (!ShowingCompass()) return false;
2196 glaCoord dx
= point
.x
- GetCompassXPosition();
2197 glaCoord dy
= point
.y
- GetIndicatorYPosition();
2198 glaCoord radius
= GetIndicatorRadius();
2200 return (dx
* dx
+ dy
* dy
<= radius
* radius
);
2203 bool GfxCore::PointWithinClino(wxPoint point
) const
2205 // Determine whether a point (in window coordinates) lies within the clino.
2206 if (!ShowingClino()) return false;
2208 glaCoord dx
= point
.x
- GetClinoXPosition();
2209 glaCoord dy
= point
.y
- GetIndicatorYPosition();
2210 glaCoord radius
= GetIndicatorRadius();
2212 return (dx
* dx
+ dy
* dy
<= radius
* radius
);
2215 bool GfxCore::PointWithinScaleBar(wxPoint point
) const
2217 // Determine whether a point (in window coordinates) lies within the scale
2219 if (!ShowingScaleBar()) return false;
2221 return (point
.x
>= SCALE_BAR_OFFSET_X
&&
2222 point
.x
<= SCALE_BAR_OFFSET_X
+ m_ScaleBarWidth
&&
2223 point
.y
<= GetYSize() - SCALE_BAR_OFFSET_Y
- SCALE_BAR_HEIGHT
&&
2224 point
.y
>= GetYSize() - SCALE_BAR_OFFSET_Y
- SCALE_BAR_HEIGHT
*2);
2227 bool GfxCore::PointWithinColourKey(wxPoint point
) const
2229 // Determine whether a point (in window coordinates) lies within the key.
2230 point
.x
-= GetXSize() - KEY_OFFSET_X
;
2231 point
.y
= KEY_OFFSET_Y
- point
.y
;
2232 return (point
.x
>= key_lowerleft
[m_ColourBy
].x
&& point
.x
<= 0 &&
2233 point
.y
>= key_lowerleft
[m_ColourBy
].y
&& point
.y
<= 0);
2236 void GfxCore::SetCompassFromPoint(wxPoint point
)
2238 // Given a point in window coordinates, set the heading of the survey. If
2239 // the point is outside the compass, it snaps to 45 degree intervals;
2240 // otherwise it operates as normal.
2242 wxCoord dx
= point
.x
- GetCompassXPosition();
2243 wxCoord dy
= point
.y
- GetIndicatorYPosition();
2244 wxCoord radius
= GetIndicatorRadius();
2246 double angle
= deg(atan2(double(dx
), double(dy
))) - 180.0;
2247 if (dx
* dx
+ dy
* dy
<= radius
* radius
) {
2248 TurnCave(angle
- m_PanAngle
);
2249 m_MouseOutsideCompass
= false;
2251 TurnCave(int(angle
/ 45.0) * 45.0 - m_PanAngle
);
2252 m_MouseOutsideCompass
= true;
2258 void GfxCore::SetClinoFromPoint(wxPoint point
)
2260 // Given a point in window coordinates, set the elevation of the survey.
2261 // If the point is outside the clino, it snaps to 90 degree intervals;
2262 // otherwise it operates as normal.
2264 glaCoord dx
= point
.x
- GetClinoXPosition();
2265 glaCoord dy
= point
.y
- GetIndicatorYPosition();
2266 glaCoord radius
= GetIndicatorRadius();
2268 if (dx
>= 0 && dx
* dx
+ dy
* dy
<= radius
* radius
) {
2269 TiltCave(-deg(atan2(double(dy
), double(dx
))) - m_TiltAngle
);
2270 m_MouseOutsideElev
= false;
2271 } else if (dy
>= INDICATOR_MARGIN
) {
2272 TiltCave(-90.0 - m_TiltAngle
);
2273 m_MouseOutsideElev
= true;
2274 } else if (dy
<= -INDICATOR_MARGIN
) {
2275 TiltCave(90.0 - m_TiltAngle
);
2276 m_MouseOutsideElev
= true;
2278 TiltCave(-m_TiltAngle
);
2279 m_MouseOutsideElev
= true;
2285 void GfxCore::SetScaleBarFromOffset(wxCoord dx
)
2287 // Set the scale of the survey, given an offset as to how much the mouse has
2288 // been dragged over the scalebar since the last scale change.
2290 SetScale((m_ScaleBarWidth
+ dx
) * m_Scale
/ m_ScaleBarWidth
);
2294 void GfxCore::RedrawIndicators()
2296 // Redraw the compass and clino indicators.
2298 int total_width
= GetCompassWidth() + INDICATOR_GAP
+ GetClinoWidth();
2299 RefreshRect(wxRect(GetXSize() - INDICATOR_OFFSET_X
- total_width
,
2300 GetYSize() - INDICATOR_OFFSET_Y
- INDICATOR_BOX_SIZE
,
2302 INDICATOR_BOX_SIZE
), false);
2305 void GfxCore::StartRotation()
2307 // Start the survey rotating.
2309 if (m_SwitchingTo
>= NORTH
)
2315 void GfxCore::ToggleRotation()
2317 // Toggle the survey rotation on/off.
2326 void GfxCore::StopRotation()
2328 // Stop the survey rotating.
2334 bool GfxCore::IsExtendedElevation() const
2336 return m_Parent
->IsExtendedElevation();
2339 void GfxCore::ReverseRotation()
2341 // Reverse the direction of rotation.
2343 m_RotationStep
= -m_RotationStep
;
2348 void GfxCore::RotateSlower(bool accel
)
2350 // Decrease the speed of rotation, optionally by an increased amount.
2351 if (fabs(m_RotationStep
) == 1.0)
2354 m_RotationStep
*= accel
? (1 / 1.44) : (1 / 1.2);
2356 if (fabs(m_RotationStep
) < 1.0) {
2357 m_RotationStep
= (m_RotationStep
> 0 ? 1.0 : -1.0);
2363 void GfxCore::RotateFaster(bool accel
)
2365 // Increase the speed of rotation, optionally by an increased amount.
2366 if (fabs(m_RotationStep
) == 180.0)
2369 m_RotationStep
*= accel
? 1.44 : 1.2;
2370 if (fabs(m_RotationStep
) > 180.0) {
2371 m_RotationStep
= (m_RotationStep
> 0 ? 180.0 : -180.0);
2377 void GfxCore::SwitchToElevation()
2379 // Perform an animated switch to elevation view.
2381 if (m_SwitchingTo
!= ELEVATION
) {
2383 m_SwitchingTo
= ELEVATION
;
2385 // A second order to switch takes us there right away
2386 TiltCave(-m_TiltAngle
);
2392 void GfxCore::SwitchToPlan()
2394 // Perform an animated switch to plan view.
2396 if (m_SwitchingTo
!= PLAN
) {
2398 m_SwitchingTo
= PLAN
;
2400 // A second order to switch takes us there right away
2401 TiltCave(-90.0 - m_TiltAngle
);
2407 void GfxCore::SetViewTo(Double xmin
, Double xmax
, Double ymin
, Double ymax
, Double zmin
, Double zmax
)
2410 SetTranslation(-Vector3((xmin
+ xmax
) / 2, (ymin
+ ymax
) / 2, (zmin
+ zmax
) / 2));
2411 Double scale
= HUGE_VAL
;
2412 const Vector3 ext
= m_Parent
->GetExtent();
2414 Double s
= ext
.GetX() / (xmax
- xmin
);
2415 if (s
< scale
) scale
= s
;
2418 Double s
= ext
.GetY() / (ymax
- ymin
);
2419 if (s
< scale
) scale
= s
;
2421 if (!ShowingPlan() && zmax
> zmin
) {
2422 Double s
= ext
.GetZ() / (zmax
- zmin
);
2423 if (s
< scale
) scale
= s
;
2425 if (scale
!= HUGE_VAL
) SetScale(scale
);
2429 bool GfxCore::CanRaiseViewpoint() const
2431 // Determine if the survey can be viewed from a higher angle of elevation.
2433 return GetPerspective() ? (m_TiltAngle
< 90.0) : (m_TiltAngle
> -90.0);
2436 bool GfxCore::CanLowerViewpoint() const
2438 // Determine if the survey can be viewed from a lower angle of elevation.
2440 return GetPerspective() ? (m_TiltAngle
> -90.0) : (m_TiltAngle
< 90.0);
2443 bool GfxCore::HasDepth() const
2445 return m_Parent
->GetDepthExtent() == 0.0;
2448 bool GfxCore::HasErrorInformation() const
2450 return m_Parent
->HasErrorInformation();
2453 bool GfxCore::HasDateInformation() const
2455 return m_Parent
->GetDateMin() >= 0;
2458 bool GfxCore::ShowingPlan() const
2460 // Determine if the survey is in plan view.
2462 return (m_TiltAngle
== -90.0);
2465 bool GfxCore::ShowingElevation() const
2467 // Determine if the survey is in elevation view.
2469 return (m_TiltAngle
== 0.0);
2472 bool GfxCore::ShowingMeasuringLine() const
2474 // Determine if the measuring line is being shown. Only check if "there"
2475 // is valid, since that means the measuring line anchor is out.
2480 void GfxCore::ToggleFlag(bool* flag
, int update
)
2483 if (update
== UPDATE_BLOBS
) {
2485 } else if (update
== UPDATE_BLOBS_AND_CROSSES
) {
2487 InvalidateList(LIST_CROSSES
);
2488 m_HitTestGridValid
= false;
2493 int GfxCore::GetNumEntrances() const
2495 return m_Parent
->GetNumEntrances();
2498 int GfxCore::GetNumFixedPts() const
2500 return m_Parent
->GetNumFixedPts();
2503 int GfxCore::GetNumExportedPts() const
2505 return m_Parent
->GetNumExportedPts();
2508 void GfxCore::ToggleTerrain()
2510 if (!m_Terrain
&& !dem
) {
2511 // OnOpenTerrain() calls us if a file is selected.
2512 wxCommandEvent dummy
;
2513 m_Parent
->OnOpenTerrain(dummy
);
2516 ToggleFlag(&m_Terrain
);
2519 void GfxCore::ToggleFatFinger()
2521 if (sqrd_measure_threshold
== sqrd(MEASURE_THRESHOLD
)) {
2522 sqrd_measure_threshold
= sqrd(5 * MEASURE_THRESHOLD
);
2523 wxMessageBox(wxT("Fat finger enabled"), wxT("Aven Debug"), wxOK
| wxICON_INFORMATION
);
2525 sqrd_measure_threshold
= sqrd(MEASURE_THRESHOLD
);
2526 wxMessageBox(wxT("Fat finger disabled"), wxT("Aven Debug"), wxOK
| wxICON_INFORMATION
);
2530 void GfxCore::ClearTreeSelection()
2532 m_Parent
->ClearTreeSelection();
2535 void GfxCore::CentreOn(const Point
&p
)
2538 m_HitTestGridValid
= false;
2543 void GfxCore::ForceRefresh()
2548 void GfxCore::GenerateList(unsigned int l
)
2559 case LIST_CLINO_BACK
:
2562 case LIST_SCALE_BAR
:
2565 case LIST_DEPTH_KEY
:
2571 case LIST_ERROR_KEY
:
2574 case LIST_GRADIENT_KEY
:
2577 case LIST_LENGTH_KEY
:
2580 case LIST_STYLE_KEY
:
2583 case LIST_UNDERGROUND_LEGS
:
2584 GenerateDisplayList(false);
2587 GenerateDisplayListTubes();
2589 case LIST_SURFACE_LEGS
:
2590 GenerateDisplayList(true);
2593 GenerateBlobsDisplayList();
2595 case LIST_CROSSES
: {
2597 SetColour(col_LIGHT_GREY
);
2598 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
2599 list
<LabelInfo
*>::const_iterator pos
= m_Parent
->GetLabels();
2600 while (pos
!= m_Parent
->GetLabelsEnd()) {
2601 const LabelInfo
* label
= *pos
++;
2603 if (m_Splays
== SHOW_HIDE
&& label
->IsSplayEnd())
2606 if ((m_Surface
&& label
->IsSurface()) ||
2607 (m_Legs
&& label
->IsUnderground()) ||
2608 (!label
->IsSurface() && !label
->IsUnderground())) {
2609 // Check if this station should be displayed
2610 // (last case above is for stns with no legs attached)
2611 if (filter
&& !filter
->CheckVisible(label
->GetText()))
2613 DrawCross(label
->GetX(), label
->GetY(), label
->GetZ());
2623 GenerateDisplayListShadow();
2634 void GfxCore::ToggleSmoothShading()
2636 GLACanvas::ToggleSmoothShading();
2637 InvalidateList(LIST_TUBES
);
2641 void GfxCore::GenerateDisplayList(bool surface
)
2643 unsigned surf_or_not
= surface
? img_FLAG_SURFACE
: 0;
2644 // Generate the display list for the surface or underground legs.
2645 for (int f
= 0; f
!= 8; ++f
) {
2646 if ((f
& img_FLAG_SURFACE
) != surf_or_not
) continue;
2647 const unsigned SHOW_DASHED_AND_FADED
= unsigned(-1);
2648 unsigned style
= SHOW_NORMAL
;
2649 if ((f
& img_FLAG_SPLAY
) && m_Splays
!= SHOW_NORMAL
) {
2651 } else if (f
& img_FLAG_DUPLICATE
) {
2654 if (f
& img_FLAG_SURFACE
) {
2655 if (style
== SHOW_FADED
) {
2656 style
= SHOW_DASHED_AND_FADED
;
2658 style
= SHOW_DASHED
;
2669 EnableDashedLines();
2671 case SHOW_DASHED_AND_FADED
:
2673 EnableDashedLines();
2677 void (GfxCore::* add_poly
)(const traverse
&);
2679 switch (m_ColourBy
) {
2680 case COLOUR_BY_ERROR
:
2681 case COLOUR_BY_H_ERROR
:
2682 case COLOUR_BY_V_ERROR
:
2683 add_poly
= &GfxCore::AddPolylineError
;
2685 case COLOUR_BY_STYLE
:
2686 add_poly
= &GfxCore::AddPolylineStyle
;
2689 add_poly
= &GfxCore::AddPolyline
;
2695 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
2696 list
<traverse
>::const_iterator trav
= m_Parent
->traverses_begin(f
, filter
);
2697 list
<traverse
>::const_iterator tend
= m_Parent
->traverses_end(f
);
2698 while (trav
!= tend
) {
2699 (this->*add_poly
)(*trav
);
2700 trav
= m_Parent
->traverses_next(f
, filter
, trav
);
2708 DisableDashedLines();
2710 case SHOW_DASHED_AND_FADED
:
2711 DisableDashedLines();
2718 void GfxCore::GenerateDisplayListTubes()
2720 // Generate the display list for the tubes.
2721 list
<vector
<XSect
>>::iterator trav
= m_Parent
->tubes_begin();
2722 list
<vector
<XSect
>>::iterator tend
= m_Parent
->tubes_end();
2723 while (trav
!= tend
) {
2729 void GfxCore::GenerateDisplayListShadow()
2731 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
2732 SetColour(col_BLACK
);
2733 for (int f
= 0; f
!= 8; ++f
) {
2734 // Only include underground legs in the shadow.
2735 if ((f
& img_FLAG_SURFACE
) != 0) continue;
2736 list
<traverse
>::const_iterator trav
= m_Parent
->traverses_begin(f
, filter
);
2737 list
<traverse
>::const_iterator tend
= m_Parent
->traverses_end(f
);
2738 while (trav
!= tend
) {
2739 AddPolylineShadow(*trav
);
2740 trav
= m_Parent
->traverses_next(f
, filter
, trav
);
2746 GfxCore::parse_hgt_filename(const wxString
& lc_name
)
2748 char * leaf
= leaf_from_fnm(lc_name
.utf8_str());
2749 const char * p
= leaf
;
2752 o_y
= strtoul(p
, &q
, 10);
2758 o_x
= strtoul(p
, &q
, 10);
2762 nodata_value
= -32768;
2767 GfxCore::parse_hdr(wxInputStream
& is
, unsigned long & skipbytes
)
2769 // ESRI docs say NBITS defaults to 8.
2770 unsigned long nbits
= 8;
2771 // ESRI docs say NBANDS defaults to 1.
2772 unsigned long nbands
= 1;
2773 unsigned long bandrowbytes
= 0;
2774 unsigned long totalrowbytes
= 0;
2775 // ESRI docs say ULXMAP defaults to 0.
2777 // ESRI docs say ULYMAP defaults to NROWS - 1.
2779 // ESRI docs say XDIM and YDIM default to 1.
2780 step_x
= step_y
= 1.0;
2784 while ((ch
= is
.GetC()) != wxEOF
) {
2785 if (ch
== '\n' || ch
== '\r') break;
2788 #define CHECK(X, COND) \
2789 } else if (line.StartsWith(wxT(X " "))) { \
2790 size_t v = line.find_first_not_of(wxT(' '), sizeof(X)); \
2791 if (v == line.npos || !(COND)) { \
2792 err += wxT("Unexpected value for " X); \
2796 // I = little-endian; M = big-endian
2797 CHECK("BYTEORDER", (bigendian
= (line
[v
] == 'M')) || line
[v
] == 'I')
2798 // ESRI docs say LAYOUT defaults to BIL if not specified.
2799 CHECK("LAYOUT", line
.substr(v
) == wxT("BIL"))
2800 CHECK("NROWS", line
.substr(v
).ToCULong(&dem_height
))
2801 CHECK("NCOLS", line
.substr(v
).ToCULong(&dem_width
))
2802 // ESRI docs say NBANDS defaults to 1 if not specified.
2803 CHECK("NBANDS", line
.substr(v
).ToCULong(&nbands
) && nbands
== 1)
2804 CHECK("NBITS", line
.substr(v
).ToCULong(&nbits
) && nbits
== 16)
2805 CHECK("BANDROWBYTES", line
.substr(v
).ToCULong(&bandrowbytes
))
2806 CHECK("TOTALROWBYTES", line
.substr(v
).ToCULong(&totalrowbytes
))
2807 // PIXELTYPE is a GDAL extension, so may not be present.
2808 CHECK("PIXELTYPE", line
.substr(v
) == wxT("SIGNEDINT"))
2809 CHECK("ULXMAP", line
.substr(v
).ToCDouble(&o_x
))
2810 CHECK("ULYMAP", line
.substr(v
).ToCDouble(&o_y
))
2811 CHECK("XDIM", line
.substr(v
).ToCDouble(&step_x
))
2812 CHECK("YDIM", line
.substr(v
).ToCDouble(&step_y
))
2813 CHECK("NODATA", line
.substr(v
).ToCLong(&nodata_value
))
2814 CHECK("SKIPBYTES", line
.substr(v
).ToCULong(&skipbytes
))
2820 if (o_y
== HUGE_VAL
) {
2821 o_y
= dem_height
- 1;
2823 if (bandrowbytes
!= 0) {
2824 if (nbits
* dem_width
!= bandrowbytes
* 8) {
2825 wxMessageBox("BANDROWBYTES setting indicates unused bits after each band - not currently supported");
2828 if (totalrowbytes
!= 0) {
2829 // This is the ESRI default for BIL, for BIP it would be
2830 // nbands * bandrowbytes.
2831 if (nbands
* nbits
* dem_width
!= totalrowbytes
* 8) {
2832 wxMessageBox("TOTALROWBYTES setting indicates unused bits after "
2833 "each row - not currently supported");
2836 return ((nbits
* dem_width
+ 7) / 8) * dem_height
;
2840 GfxCore::read_bil(wxInputStream
& is
, size_t size
, unsigned long skipbytes
)
2842 bool know_size
= true;
2844 // If the stream doesn't know its size, GetSize() returns 0.
2845 size
= is
.GetSize();
2847 size
= DEFAULT_HGT_SIZE
;
2851 dem
= new unsigned short[size
/ 2];
2853 if (is
.SeekI(skipbytes
, wxFromStart
) == ::wxInvalidOffset
) {
2855 unsigned long to_read
= skipbytes
;
2856 if (size
< to_read
) to_read
= size
;
2857 is
.Read(reinterpret_cast<char *>(dem
), to_read
);
2858 size_t c
= is
.LastRead();
2860 wxMessageBox(wxT("Failed to skip terrain data header"));
2868 if (!is
.ReadAll(dem
, size
)) {
2870 // FIXME: On __WXMSW__ currently we fail to
2871 // read any data from files in zips.
2874 wxMessageBox(wxT("Failed to read terrain data"));
2877 size
= is
.LastRead();
2880 if (dem_width
== 0 && dem_height
== 0) {
2881 dem_width
= dem_height
= sqrt(size
/ 2);
2882 if (dem_width
* dem_height
* 2 != size
) {
2885 wxMessageBox(wxT("HGT format data doesn't form a square"));
2888 step_x
= step_y
= 1.0 / dem_width
;
2894 bool GfxCore::LoadDEM(const wxString
& file
)
2896 if (m_Parent
->GetCSProj().empty()) {
2897 wxMessageBox(wxT("No coordinate system specified in survey data"));
2905 // Default is to not skip any bytes.
2906 unsigned long skipbytes
= 0;
2907 // For .hgt files, default to using filesize to determine.
2908 dem_width
= dem_height
= 0;
2909 // ESRI say "The default byte order is the same as that of the host machine
2910 // executing the software", but that's stupid so we default to
2914 wxFileInputStream
fs(file
);
2916 wxMessageBox(wxT("Failed to open DEM file"));
2920 const wxString
& lc_file
= file
.Lower();
2921 if (lc_file
.EndsWith(wxT(".hgt"))) {
2922 parse_hgt_filename(lc_file
);
2923 read_bil(fs
, size
, skipbytes
);
2924 } else if (lc_file
.EndsWith(wxT(".bil"))) {
2925 wxString hdr_file
= file
;
2926 hdr_file
.replace(file
.size() - 4, 4, wxT(".hdr"));
2927 wxFileInputStream
hdr_is(hdr_file
);
2928 if (!hdr_is
.IsOk()) {
2929 wxMessageBox(wxT("Failed to open HDR file '") + hdr_file
+ wxT("'"));
2932 size
= parse_hdr(hdr_is
, skipbytes
);
2933 read_bil(fs
, size
, skipbytes
);
2934 } else if (lc_file
.EndsWith(wxT(".zip"))) {
2935 wxZipEntry
* ze_data
= NULL
;
2936 wxZipInputStream
zs(fs
);
2938 while ((ze
= zs
.GetNextEntry()) != NULL
) {
2940 const wxString
& lc_name
= ze
->GetName().Lower();
2941 if (!ze_data
&& lc_name
.EndsWith(wxT(".hgt"))) {
2942 // SRTM .hgt files are raw binary data, with the filename
2943 // encoding the coordinates.
2944 parse_hgt_filename(lc_name
);
2945 read_bil(zs
, size
, skipbytes
);
2950 if (!ze_data
&& lc_name
.EndsWith(wxT(".bil"))) {
2952 read_bil(zs
, size
, skipbytes
);
2959 if (lc_name
.EndsWith(wxT(".hdr"))) {
2960 size
= parse_hdr(zs
, skipbytes
);
2962 if (!zs
.OpenEntry(*ze_data
)) {
2963 wxMessageBox(wxT("Couldn't read DEM data from .zip file"));
2966 read_bil(zs
, size
, skipbytes
);
2968 } else if (lc_name
.EndsWith(wxT(".prj"))) {
2969 //FIXME: check this matches the datum string we use
2970 //Projection GEOGRAPHIC
2975 //Xshift 0.0000000000
2976 //Yshift 0.0000000000
2989 InvalidateList(LIST_TERRAIN
);
2994 void GfxCore::DrawTerrainTriangle(const Vector3
& a
, const Vector3
& b
, const Vector3
& c
)
2996 Vector3 n
= (b
- a
) * (c
- a
);
2998 Double factor
= dot(n
, light
) * .95 + .05;
2999 SetColour(col_WHITE
, factor
);
3006 // Like wxBusyCursor, but you can cancel it early.
3007 class AvenBusyCursor
{
3011 AvenBusyCursor() : active(true) {
3012 wxBeginBusyCursor();
3027 static void discarding_proj_logger(void *, int, const char *) { }
3029 void GfxCore::DrawTerrain()
3033 AvenBusyCursor hourglass
;
3035 // Draw terrain to twice the extent, or at least 1km.
3036 double r_sqrd
= sqrd(max(m_Parent
->GetExtent().magnitude(), 1000.0));
3038 /* Prevent stderr spew from PROJ. */
3039 proj_log_func(PJ_DEFAULT_CTX
, nullptr, discarding_proj_logger
);
3041 #define WGS84_DATUM_STRING "EPSG:4326"
3043 PJ
* pj
= proj_create_crs_to_crs(PJ_DEFAULT_CTX
,
3045 m_Parent
->GetCSProj().c_str(),
3049 // Normalise the output order so x is longitude and y latitude - by default
3050 // new PROJ has them switched for EPSG:4326 which just seems confusing.
3051 PJ
* pj_norm
= proj_normalize_for_visualization(PJ_DEFAULT_CTX
, pj
);
3061 error(/*Failed to initialise output coordinate system “%s”*/288, (const char *)m_Parent
->GetCSProj().c_str());
3067 const Vector3
& off
= m_Parent
->GetOffset();
3068 vector
<Vector3
> prevcol(dem_height
+ 1);
3069 for (size_t x
= 0; x
< dem_width
; ++x
) {
3070 PJ_COORD coord
= {o_x
+ x
* step_x
, 0.0, 0.0, HUGE_VAL
};
3072 for (size_t y
= 0; y
< dem_height
; ++y
) {
3073 unsigned short elev
= dem
[x
+ y
* dem_width
];
3074 #ifdef WORDS_BIGENDIAN
3075 const bool MACHINE_BIGENDIAN
= true;
3077 const bool MACHINE_BIGENDIAN
= false;
3079 if (bigendian
!= MACHINE_BIGENDIAN
) {
3080 #if defined __GNUC__ && (__GNUC__ * 100 + __GNUC_MINOR__ >= 408)
3081 elev
= __builtin_bswap16(elev
);
3083 elev
= (elev
>> 8) | (elev
<< 8);
3086 double Z
= (short)elev
;
3088 if (Z
== nodata_value
) {
3089 pt
= Vector3(DBL_MAX
, DBL_MAX
, DBL_MAX
);
3091 coord
.xyzt
.y
= o_y
- y
* step_y
;
3093 PJ_COORD r
= proj_trans(pj
, PJ_FWD
, coord
);
3094 if (r
.xyzt
.x
== HUGE_VAL
||
3095 r
.xyzt
.y
== HUGE_VAL
||
3096 r
.xyzt
.z
== HUGE_VAL
) {
3097 pt
= Vector3(DBL_MAX
, DBL_MAX
, DBL_MAX
);
3100 pt
= Vector3(r
.xyzt
.x
, r
.xyzt
.y
, r
.xyzt
.z
) - off
;
3101 double dist_2
= sqrd(pt
.GetX()) + sqrd(pt
.GetY());
3102 if (dist_2
> r_sqrd
) {
3103 pt
= Vector3(DBL_MAX
, DBL_MAX
, DBL_MAX
);
3107 if (x
> 0 && y
> 0) {
3108 const Vector3
& a
= prevcol
[y
- 1];
3109 const Vector3
& b
= prevcol
[y
];
3110 // If all points are valid, split the quadrilateral into
3111 // triangles along the shorter 3D diagonal, which typically
3115 // prev---a x prev---a
3117 // y | | / | or | \ |
3123 enum { NONE
= 0, P
= 1, Q
= 2, R
= 4, S
= 8, ALL
= P
|Q
|R
|S
};
3125 ((prev
.GetZ() != DBL_MAX
)) |
3126 ((a
.GetZ() != DBL_MAX
) << 1) |
3127 ((b
.GetZ() != DBL_MAX
) << 2) |
3128 ((pt
.GetZ() != DBL_MAX
) << 3);
3129 static const int tris_map
[16] = {
3130 NONE
, // nothing valid
3145 ALL
, // pt, b, a, prev
3147 int tris
= tris_map
[valid
];
3149 // All points valid.
3150 if ((a
- b
).magnitude() < (prev
- pt
).magnitude()) {
3157 DrawTerrainTriangle(a
, prev
, b
);
3159 DrawTerrainTriangle(a
, b
, pt
);
3161 DrawTerrainTriangle(pt
, prev
, b
);
3163 DrawTerrainTriangle(a
, prev
, pt
);
3166 prevcol
[y
].assign(pt
);
3176 /* TRANSLATORS: Aven shows a circle of terrain covering the area
3177 * of the survey plus a bit, but the terrain data file didn't
3178 * contain any data inside that circle.
3180 error(/*No terrain data near area of survey*/161);
3187 void GfxCore::GenerateBlobsDisplayList()
3189 if (!(m_Entrances
|| m_FixedPts
|| m_ExportedPts
||
3190 m_Parent
->GetNumHighlightedPts()))
3194 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
3195 gla_colour prev_col
= col_BLACK
; // not a colour used for blobs
3196 list
<LabelInfo
*>::const_iterator pos
= m_Parent
->GetLabels();
3198 while (pos
!= m_Parent
->GetLabelsEnd()) {
3199 const LabelInfo
* label
= *pos
++;
3201 // When more than one flag is set on a point:
3202 // search results take priority over entrance highlighting
3203 // which takes priority over fixed point
3204 // highlighting, which in turn takes priority over exported
3205 // point highlighting.
3207 if (m_Splays
== SHOW_HIDE
&& label
->IsSplayEnd())
3210 if (!((m_Surface
&& label
->IsSurface()) ||
3211 (m_Legs
&& label
->IsUnderground()) ||
3212 (!label
->IsSurface() && !label
->IsUnderground()))) {
3213 // if this station isn't to be displayed, skip to the next
3214 // (last case is for stns with no legs attached)
3217 if (filter
&& !filter
->CheckVisible(label
->GetText()))
3222 if (label
->IsHighLighted()) {
3224 } else if (m_Entrances
&& label
->IsEntrance()) {
3226 } else if (m_FixedPts
&& label
->IsFixedPt()) {
3228 } else if (m_ExportedPts
&& label
->IsExportedPt()) {
3229 col
= col_TURQUOISE
;
3234 // Stations are sorted by blob type, so colour changes are infrequent.
3235 if (col
!= prev_col
) {
3239 DrawBlob(label
->GetX(), label
->GetY(), label
->GetZ());
3244 void GfxCore::DrawIndicators()
3248 drawing_list key_list
= LIST_LIMIT_
;
3249 switch (m_ColourBy
) {
3250 case COLOUR_BY_DEPTH
:
3251 key_list
= LIST_DEPTH_KEY
; break;
3252 case COLOUR_BY_DATE
:
3253 key_list
= LIST_DATE_KEY
; break;
3254 case COLOUR_BY_ERROR
:
3255 case COLOUR_BY_H_ERROR
:
3256 case COLOUR_BY_V_ERROR
:
3257 key_list
= LIST_ERROR_KEY
; break;
3258 case COLOUR_BY_GRADIENT
:
3259 key_list
= LIST_GRADIENT_KEY
; break;
3260 case COLOUR_BY_LENGTH
:
3261 key_list
= LIST_LENGTH_KEY
; break;
3262 #if 0 // FIXME Key for survey colours?
3263 case COLOUR_BY_SURVEY
:
3264 key_list
= LIST_SURVEY_KEY
; break;
3266 case COLOUR_BY_STYLE
:
3267 key_list
= LIST_STYLE_KEY
; break;
3269 if (key_list
!= LIST_LIMIT_
) {
3270 DrawList2D(key_list
, GetXSize() - KEY_OFFSET_X
,
3271 GetYSize() - KEY_OFFSET_Y
, 0);
3275 // Draw compass or elevation/heading indicators.
3276 if (m_Compass
|| m_Clino
) {
3277 if (!m_Parent
->IsExtendedElevation()) Draw2dIndicators();
3281 if (m_Scalebar
&& !GetPerspective()) {
3282 DrawList2D(LIST_SCALE_BAR
, 0, 0, 0);
3286 void GfxCore::PlaceVertexWithColour(const Vector3
& v
,
3287 glaTexCoord tex_x
, glaTexCoord tex_y
,
3290 SetColour(col_WHITE
, factor
);
3291 PlaceVertex(v
, tex_x
, tex_y
);
3294 void GfxCore::SetDepthColour(Double z
, Double factor
) {
3295 // Set the drawing colour based on the altitude.
3296 Double z_ext
= m_Parent
->GetDepthExtent();
3298 z
-= m_Parent
->GetDepthMin();
3299 // points arising from tubes may be slightly outside the limits...
3301 if (z
> z_ext
) z
= z_ext
;
3304 SetColour(GetPen(0), factor
);
3308 assert(z_ext
> 0.0);
3309 Double how_far
= z
/ z_ext
;
3310 assert(how_far
>= 0.0);
3311 assert(how_far
<= 1.0);
3313 int band
= int(floor(how_far
* (GetNumColourBands() - 1)));
3314 GLAPen pen1
= GetPen(band
);
3315 if (band
< GetNumColourBands() - 1) {
3316 const GLAPen
& pen2
= GetPen(band
+ 1);
3318 Double interval
= z_ext
/ (GetNumColourBands() - 1);
3319 Double into_band
= z
/ interval
- band
;
3321 // printf("%g z_offset=%g interval=%g band=%d\n", into_band,
3322 // z_offset, interval, band);
3323 // FIXME: why do we need to clamp here? Is it because the walls can
3324 // extend further up/down than the centre-line?
3325 if (into_band
< 0.0) into_band
= 0.0;
3326 if (into_band
> 1.0) into_band
= 1.0;
3327 assert(into_band
>= 0.0);
3328 assert(into_band
<= 1.0);
3330 pen1
.Interpolate(pen2
, into_band
);
3332 SetColour(pen1
, factor
);
3335 void GfxCore::PlaceVertexWithDepthColour(const Vector3
&v
, Double factor
)
3337 SetDepthColour(v
.GetZ(), factor
);
3341 void GfxCore::PlaceVertexWithDepthColour(const Vector3
&v
,
3342 glaTexCoord tex_x
, glaTexCoord tex_y
,
3345 SetDepthColour(v
.GetZ(), factor
);
3346 PlaceVertex(v
, tex_x
, tex_y
);
3349 void GfxCore::SplitLineAcrossBands(int band
, int band2
,
3350 const Vector3
&p
, const Vector3
&q
,
3353 const int step
= (band
< band2
) ? 1 : -1;
3354 for (int i
= band
; i
!= band2
; i
+= step
) {
3355 const Double z
= GetDepthBoundaryBetweenBands(i
, i
+ step
);
3357 // Find the intersection point of the line p -> q
3358 // with the plane parallel to the xy-plane with z-axis intersection z.
3359 assert(q
.GetZ() - p
.GetZ() != 0.0);
3361 const Double t
= (z
- p
.GetZ()) / (q
.GetZ() - p
.GetZ());
3362 // assert(0.0 <= t && t <= 1.0); FIXME: rounding problems!
3364 const Double x
= p
.GetX() + t
* (q
.GetX() - p
.GetX());
3365 const Double y
= p
.GetY() + t
* (q
.GetY() - p
.GetY());
3367 PlaceVertexWithDepthColour(Vector3(x
, y
, z
), factor
);
3371 void GfxCore::SplitPolyAcrossBands(vector
<vector
<Split
>>& splits
,
3372 int band
, int band2
,
3373 const Vector3
&p
, const Vector3
&q
,
3374 glaTexCoord ptx
, glaTexCoord pty
,
3375 glaTexCoord w
, glaTexCoord h
)
3377 const int step
= (band
< band2
) ? 1 : -1;
3378 for (int i
= band
; i
!= band2
; i
+= step
) {
3379 const Double z
= GetDepthBoundaryBetweenBands(i
, i
+ step
);
3381 // Find the intersection point of the line p -> q
3382 // with the plane parallel to the xy-plane with z-axis intersection z.
3383 assert(q
.GetZ() - p
.GetZ() != 0.0);
3385 const Double t
= (z
- p
.GetZ()) / (q
.GetZ() - p
.GetZ());
3386 // assert(0.0 <= t && t <= 1.0); FIXME: rounding problems!
3388 const Double x
= p
.GetX() + t
* (q
.GetX() - p
.GetX());
3389 const Double y
= p
.GetY() + t
* (q
.GetY() - p
.GetY());
3390 glaTexCoord tx
= ptx
, ty
= pty
;
3394 splits
[i
].push_back(Split(Vector3(x
, y
, z
), tx
, ty
));
3395 splits
[i
+ step
].push_back(Split(Vector3(x
, y
, z
), tx
, ty
));
3399 int GfxCore::GetDepthColour(Double z
) const
3401 // Return the (0-based) depth colour band index for a z-coordinate.
3402 Double z_ext
= m_Parent
->GetDepthExtent();
3403 z
-= m_Parent
->GetDepthMin();
3404 // We seem to get rounding differences causing z to sometimes be slightly
3405 // less than GetDepthMin() here, and it can certainly be true for passage
3406 // tubes, so just clamp the value to 0.
3407 if (z
<= 0) return 0;
3408 // We seem to get rounding differences causing z to sometimes exceed z_ext
3409 // by a small amount here (see: https://trac.survex.com/ticket/26) and it
3410 // can certainly be true for passage tubes, so just clamp the value.
3411 if (z
>= z_ext
) return GetNumColourBands() - 1;
3412 return int(z
/ z_ext
* (GetNumColourBands() - 1));
3415 Double
GfxCore::GetDepthBoundaryBetweenBands(int a
, int b
) const
3417 // Return the z-coordinate of the depth colour boundary between
3418 // two adjacent depth colour bands (specified by 0-based indices).
3420 assert((a
== b
- 1) || (a
== b
+ 1));
3421 if (GetNumColourBands() == 1) return 0;
3423 int band
= (a
> b
) ? a
: b
; // boundary N lies on the bottom of band N.
3424 Double z_ext
= m_Parent
->GetDepthExtent();
3425 return (z_ext
* band
/ (GetNumColourBands() - 1)) + m_Parent
->GetDepthMin();
3428 void GfxCore::AddPolyline(const traverse
& centreline
)
3431 SetColour(col_WHITE
);
3432 vector
<PointInfo
>::const_iterator i
= centreline
.begin();
3435 while (i
!= centreline
.end()) {
3442 void GfxCore::AddPolylineShadow(const traverse
& centreline
)
3445 const double z
= -0.5 * m_Parent
->GetExtent().GetZ();
3446 vector
<PointInfo
>::const_iterator i
= centreline
.begin();
3447 PlaceVertex(i
->GetX(), i
->GetY(), z
);
3449 while (i
!= centreline
.end()) {
3450 PlaceVertex(i
->GetX(), i
->GetY(), z
);
3456 void GfxCore::AddPolylineDepth(const traverse
& centreline
)
3459 vector
<PointInfo
>::const_iterator i
, prev_i
;
3460 i
= centreline
.begin();
3461 int band0
= GetDepthColour(i
->GetZ());
3462 PlaceVertexWithDepthColour(*i
);
3465 while (i
!= centreline
.end()) {
3466 int band
= GetDepthColour(i
->GetZ());
3467 if (band
!= band0
) {
3468 SplitLineAcrossBands(band0
, band
, *prev_i
, *i
);
3471 PlaceVertexWithDepthColour(*i
);
3478 void GfxCore::AddQuadrilateral(const Vector3
&a
, const Vector3
&b
,
3479 const Vector3
&c
, const Vector3
&d
)
3481 Vector3 normal
= (a
- c
) * (d
- b
);
3483 Double factor
= dot(normal
, light
) * .3 + .7;
3484 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3485 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3486 // FIXME: should plot triangles instead to avoid rendering glitches.
3487 BeginQuadrilaterals();
3488 PlaceVertexWithColour(a
, 0, 0, factor
);
3489 PlaceVertexWithColour(b
, w
, 0, factor
);
3490 PlaceVertexWithColour(c
, w
, h
, factor
);
3491 PlaceVertexWithColour(d
, 0, h
, factor
);
3492 EndQuadrilaterals();
3495 void GfxCore::AddQuadrilateralDepth(const Vector3
&a
, const Vector3
&b
,
3496 const Vector3
&c
, const Vector3
&d
)
3498 Vector3 normal
= (a
- c
) * (d
- b
);
3500 Double factor
= dot(normal
, light
) * .3 + .7;
3501 int a_band
, b_band
, c_band
, d_band
;
3502 a_band
= GetDepthColour(a
.GetZ());
3503 a_band
= min(max(a_band
, 0), GetNumColourBands());
3504 b_band
= GetDepthColour(b
.GetZ());
3505 b_band
= min(max(b_band
, 0), GetNumColourBands());
3506 c_band
= GetDepthColour(c
.GetZ());
3507 c_band
= min(max(c_band
, 0), GetNumColourBands());
3508 d_band
= GetDepthColour(d
.GetZ());
3509 d_band
= min(max(d_band
, 0), GetNumColourBands());
3510 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3511 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3512 int min_band
= min(min(a_band
, b_band
), min(c_band
, d_band
));
3513 int max_band
= max(max(a_band
, b_band
), max(c_band
, d_band
));
3514 if (min_band
== max_band
) {
3515 // Simple case - the polygon is entirely within one band.
3517 //// PlaceNormal(normal);
3518 PlaceVertexWithDepthColour(a
, 0, 0, factor
);
3519 PlaceVertexWithDepthColour(b
, w
, 0, factor
);
3520 PlaceVertexWithDepthColour(c
, w
, h
, factor
);
3521 PlaceVertexWithDepthColour(d
, 0, h
, factor
);
3524 // We need to make a separate polygon for each depth band...
3525 vector
<vector
<Split
>> splits
;
3526 splits
.resize(max_band
+ 1);
3527 splits
[a_band
].push_back(Split(a
, 0, 0));
3528 if (a_band
!= b_band
) {
3529 SplitPolyAcrossBands(splits
, a_band
, b_band
, a
, b
, 0, 0, w
, 0);
3531 splits
[b_band
].push_back(Split(b
, w
, 0));
3532 if (b_band
!= c_band
) {
3533 SplitPolyAcrossBands(splits
, b_band
, c_band
, b
, c
, w
, 0, 0, h
);
3535 splits
[c_band
].push_back(Split(c
, w
, h
));
3536 if (c_band
!= d_band
) {
3537 SplitPolyAcrossBands(splits
, c_band
, d_band
, c
, d
, w
, h
, -w
, 0);
3539 splits
[d_band
].push_back(Split(d
, 0, h
));
3540 if (d_band
!= a_band
) {
3541 SplitPolyAcrossBands(splits
, d_band
, a_band
, d
, a
, 0, h
, 0, -h
);
3543 for (int band
= min_band
; band
<= max_band
; ++band
) {
3545 for (auto&& item
: splits
[band
]) {
3546 PlaceVertexWithDepthColour(item
.vec
, item
.tx
, item
.ty
, factor
);
3553 void GfxCore::SetColourFromDate(int date
, Double factor
)
3555 // Set the drawing colour based on a date.
3559 SetColour(NODATA_COLOUR
, factor
);
3563 int date_offset
= date
- m_Parent
->GetDateMin();
3564 if (date_offset
== 0) {
3565 // Earliest date - handle as a special case for the single date case.
3566 SetColour(GetPen(0), factor
);
3570 int date_ext
= m_Parent
->GetDateExtent();
3571 Double how_far
= (Double
)date_offset
/ date_ext
;
3572 assert(how_far
>= 0.0);
3573 assert(how_far
<= 1.0);
3574 SetColourFrom01(how_far
, factor
);
3577 void GfxCore::AddPolylineDate(const traverse
& centreline
)
3580 auto i
= centreline
.begin();
3581 int date
= i
->GetDate();
3582 SetColourFromDate(date
, 1.0);
3584 while (++i
!= centreline
.end()) {
3585 int newdate
= i
->GetDate();
3586 if (newdate
!= date
) {
3588 SetColourFromDate(date
, 1.0);
3595 static int static_date_hack
; // FIXME
3597 void GfxCore::AddQuadrilateralDate(const Vector3
&a
, const Vector3
&b
,
3598 const Vector3
&c
, const Vector3
&d
)
3600 Vector3 normal
= (a
- c
) * (d
- b
);
3602 Double factor
= dot(normal
, light
) * .3 + .7;
3603 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3604 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3605 // FIXME: should plot triangles instead to avoid rendering glitches.
3606 BeginQuadrilaterals();
3607 //// PlaceNormal(normal);
3608 SetColourFromDate(static_date_hack
, factor
);
3609 PlaceVertex(a
, 0, 0);
3610 PlaceVertex(b
, w
, 0);
3611 PlaceVertex(c
, w
, h
);
3612 PlaceVertex(d
, 0, h
);
3613 EndQuadrilaterals();
3616 static double static_E_hack
; // FIXME
3618 void GfxCore::SetColourFromError(double E
, Double factor
)
3620 // Set the drawing colour based on an error value.
3623 SetColour(NODATA_COLOUR
, factor
);
3627 Double how_far
= E
/ MAX_ERROR
;
3628 assert(how_far
>= 0.0);
3629 if (how_far
> 1.0) how_far
= 1.0;
3630 SetColourFrom01(how_far
, factor
);
3633 void GfxCore::AddQuadrilateralError(const Vector3
&a
, const Vector3
&b
,
3634 const Vector3
&c
, const Vector3
&d
)
3636 Vector3 normal
= (a
- c
) * (d
- b
);
3638 Double factor
= dot(normal
, light
) * .3 + .7;
3639 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3640 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3641 // FIXME: should plot triangles instead to avoid rendering glitches.
3642 BeginQuadrilaterals();
3643 //// PlaceNormal(normal);
3644 SetColourFromError(static_E_hack
, factor
);
3645 PlaceVertex(a
, 0, 0);
3646 PlaceVertex(b
, w
, 0);
3647 PlaceVertex(c
, w
, h
);
3648 PlaceVertex(d
, 0, h
);
3649 EndQuadrilaterals();
3652 void GfxCore::AddPolylineError(const traverse
& centreline
)
3655 SetColourFromError(centreline
.errors
[error_type
], 1.0);
3656 for (auto i
= centreline
.begin(); i
!= centreline
.end(); ++i
) {
3662 // gradient is in *radians*.
3663 void GfxCore::SetColourFromGradient(double gradient
, Double factor
)
3665 // Set the drawing colour based on the gradient of the leg.
3667 const Double GRADIENT_MAX
= M_PI_2
;
3668 gradient
= fabs(gradient
);
3669 Double how_far
= gradient
/ GRADIENT_MAX
;
3670 SetColourFrom01(how_far
, factor
);
3673 void GfxCore::AddPolylineGradient(const traverse
& centreline
)
3676 auto i
= centreline
.begin();
3679 while (++i
!= centreline
.end()) {
3680 SetColourFromGradient((*i
- *prev_i
).gradient(), 1.0);
3687 static double static_gradient_hack
; // FIXME
3689 void GfxCore::AddQuadrilateralGradient(const Vector3
&a
, const Vector3
&b
,
3690 const Vector3
&c
, const Vector3
&d
)
3692 Vector3 normal
= (a
- c
) * (d
- b
);
3694 Double factor
= dot(normal
, light
) * .3 + .7;
3695 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3696 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3697 // FIXME: should plot triangles instead to avoid rendering glitches.
3698 BeginQuadrilaterals();
3699 //// PlaceNormal(normal);
3700 SetColourFromGradient(static_gradient_hack
, factor
);
3701 PlaceVertex(a
, 0, 0);
3702 PlaceVertex(b
, w
, 0);
3703 PlaceVertex(c
, w
, h
);
3704 PlaceVertex(d
, 0, h
);
3705 EndQuadrilaterals();
3708 void GfxCore::SetColourFromLength(double length
, Double factor
)
3710 // Set the drawing colour based on log(length_of_leg).
3712 Double log_len
= log10(length
);
3713 Double how_far
= log_len
/ LOG_LEN_MAX
;
3714 how_far
= max(how_far
, 0.0);
3715 how_far
= min(how_far
, 1.0);
3716 SetColourFrom01(how_far
, factor
);
3719 void GfxCore::SetColourFromSurvey(const wxString
& survey
)
3721 // Set the drawing colour based on hash of name.
3722 int hash
= hash_string(survey
.utf8_str());
3723 wxImage::HSVValue
hsv((hash
& 0xff) / 256.0, (((hash
>> 8) & 0x7f) | 0x80) / 256.0, 0.9);
3724 wxImage::RGBValue rgb
= wxImage::HSVtoRGB(hsv
);
3726 pen
.SetColour(rgb
.red
/ 256.0, rgb
.green
/ 256.0, rgb
.blue
/ 256.0);
3730 void GfxCore::SetColourFromSurveyStation(const wxString
& name
, Double factor
)
3732 // Set the drawing colour based on hash of survey name.
3733 const char* p
= name
.utf8_str();
3734 const char* q
= strrchr(p
, m_Parent
->GetSeparator());
3735 size_t len
= q
? (q
- p
) : strlen(p
);
3736 int hash
= hash_data(p
, len
);
3737 wxImage::HSVValue
hsv((hash
& 0xff) / 256.0, (((hash
>> 8) & 0x7f) | 0x80) / 256.0, 0.9);
3738 wxImage::RGBValue rgb
= wxImage::HSVtoRGB(hsv
);
3740 pen
.SetColour(rgb
.red
/ 256.0, rgb
.green
/ 256.0, rgb
.blue
/ 256.0);
3741 SetColour(pen
, factor
);
3744 void GfxCore::SetColourFrom01(double how_far
, Double factor
)
3747 double into_band
= modf(how_far
* (GetNumColourBands() - 1), &b
);
3749 GLAPen pen1
= GetPen(band
);
3750 // With 24bit colour, interpolating by less than this can have no effect.
3751 if (into_band
>= 1.0 / 512.0) {
3752 const GLAPen
& pen2
= GetPen(band
+ 1);
3753 pen1
.Interpolate(pen2
, into_band
);
3755 SetColour(pen1
, factor
);
3758 void GfxCore::AddPolylineLength(const traverse
& centreline
)
3761 auto i
= centreline
.begin();
3764 while (++i
!= centreline
.end()) {
3765 SetColourFromLength((*i
- *prev_i
).magnitude(), 1.0);
3772 static double static_length_hack
; // FIXME
3774 void GfxCore::AddQuadrilateralLength(const Vector3
&a
, const Vector3
&b
,
3775 const Vector3
&c
, const Vector3
&d
)
3777 Vector3 normal
= (a
- c
) * (d
- b
);
3779 Double factor
= dot(normal
, light
) * .3 + .7;
3780 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3781 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3782 // FIXME: should plot triangles instead to avoid rendering glitches.
3783 BeginQuadrilaterals();
3784 //// PlaceNormal(normal);
3785 SetColourFromLength(static_length_hack
, factor
);
3786 PlaceVertex(a
, 0, 0);
3787 PlaceVertex(b
, w
, 0);
3788 PlaceVertex(c
, w
, h
);
3789 PlaceVertex(d
, 0, h
);
3790 EndQuadrilaterals();
3793 void GfxCore::AddPolylineSurvey(const traverse
& centreline
)
3796 SetColourFromSurvey(centreline
.name
);
3797 for (auto i
= centreline
.begin(); i
!= centreline
.end(); ++i
) {
3803 static const wxString
* static_survey_hack
;
3805 void GfxCore::AddQuadrilateralSurvey(const Vector3
&a
, const Vector3
&b
,
3806 const Vector3
&c
, const Vector3
&d
)
3808 Vector3 normal
= (a
- c
) * (d
- b
);
3810 Double factor
= dot(normal
, light
) * .3 + .7;
3811 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3812 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3813 // FIXME: should plot triangles instead to avoid rendering glitches.
3814 BeginQuadrilaterals();
3815 //// PlaceNormal(normal);
3816 SetColourFromSurveyStation(*static_survey_hack
, factor
);
3817 PlaceVertex(a
, 0, 0);
3818 PlaceVertex(b
, w
, 0);
3819 PlaceVertex(c
, w
, h
);
3820 PlaceVertex(d
, 0, h
);
3821 EndQuadrilaterals();
3824 void GfxCore::AddPolylineStyle(const traverse
& centreline
)
3827 SetColour(style_colours
[centreline
.style
+ 1]);
3828 // SetColourFromStyle(centreline.style);
3829 for (auto i
= centreline
.begin(); i
!= centreline
.end(); ++i
) {
3836 static int static_style_hack
;
3838 void GfxCore::AddQuadrilateralStyle(const Vector3
&a
, const Vector3
&b
,
3839 const Vector3
&c
, const Vector3
&d
)
3841 Vector3 normal
= (a
- c
) * (d
- b
);
3843 Double factor
= dot(normal
, light
) * .3 + .7;
3844 glaTexCoord
w(((b
- a
).magnitude() + (d
- c
).magnitude()) * .5);
3845 glaTexCoord
h(((b
- c
).magnitude() + (d
- a
).magnitude()) * .5);
3846 // FIXME: should plot triangles instead to avoid rendering glitches.
3847 BeginQuadrilaterals();
3848 //// PlaceNormal(normal);
3849 SetColourFromSurveyStation(*static_survey_hack
, factor
);
3850 PlaceVertex(a
, 0, 0);
3851 PlaceVertex(b
, w
, 0);
3852 PlaceVertex(c
, w
, h
);
3853 PlaceVertex(d
, 0, h
);
3854 EndQuadrilaterals();
3859 GfxCore::SkinPassage(vector
<XSect
> & centreline
)
3861 const SurveyFilter
* filter
= m_Parent
->GetTreeFilter();
3862 assert(centreline
.size() > 1);
3864 XSect
* prev_pt_v
= NULL
;
3865 Vector3
last_right(1.0, 0.0, 0.0);
3867 // FIXME: it's not simple to set the colour of a tube based on error...
3868 // static_E_hack = something...
3869 vector
<XSect
>::iterator i
= centreline
.begin();
3870 vector
<XSect
>::size_type segment
= 0;
3871 while (i
!= centreline
.end()) {
3872 // get the coordinates of this vertex
3873 XSect
& pt_v
= *i
++;
3875 bool cover_end
= false;
3879 const Vector3
up_v(0.0, 0.0, 1.0);
3881 static_survey_hack
= &(pt_v
.GetLabel());
3883 assert(i
!= centreline
.end());
3886 // get the coordinates of the next vertex
3887 const XSect
& next_pt_v
= *i
;
3889 // calculate vector from this pt to the next one
3890 Vector3 leg_v
= next_pt_v
- pt_v
;
3892 // obtain a vector in the LRUD plane
3893 right
= leg_v
* up_v
;
3894 if (right
.magnitude() == 0) {
3896 // Obtain a second vector in the LRUD plane,
3897 // perpendicular to the first.
3898 //up = right * leg_v;
3906 static_date_hack
= next_pt_v
.GetDate();
3907 } else if (segment
+ 1 == centreline
.size()) {
3910 // Calculate vector from the previous pt to this one.
3911 Vector3 leg_v
= pt_v
- *prev_pt_v
;
3913 // Obtain a horizontal vector in the LRUD plane.
3914 right
= leg_v
* up_v
;
3915 if (right
.magnitude() == 0) {
3916 right
= Vector3(last_right
.GetX(), last_right
.GetY(), 0.0);
3917 // Obtain a second vector in the LRUD plane,
3918 // perpendicular to the first.
3919 //up = right * leg_v;
3927 static_date_hack
= pt_v
.GetDate();
3929 assert(i
!= centreline
.end());
3930 // Intermediate segment.
3932 // Get the coordinates of the next vertex.
3933 const XSect
& next_pt_v
= *i
;
3935 // Calculate vectors from this vertex to the
3936 // next vertex, and from the previous vertex to
3938 Vector3 leg1_v
= pt_v
- *prev_pt_v
;
3939 Vector3 leg2_v
= next_pt_v
- pt_v
;
3941 // Obtain horizontal vectors perpendicular to
3942 // both legs, then normalise and average to get
3943 // a horizontal bisector.
3944 Vector3 r1
= leg1_v
* up_v
;
3945 Vector3 r2
= leg2_v
* up_v
;
3949 if (right
.magnitude() == 0) {
3950 // This is the "mid-pitch" case...
3953 if (r1
.magnitude() == 0) {
3956 // Rotate pitch section to minimise the
3957 // "torsional stress" - FIXME: use
3958 // triangles instead of rectangles?
3962 // Scale to unit vectors in the LRUD plane.
3965 Vector3 vec
= up
- right
;
3966 for (int orient
= 0; orient
<= 3; ++orient
) {
3967 Vector3 tmp
= U
[orient
] - prev_pt_v
->GetPoint();
3969 Double dotp
= dot(vec
, tmp
);
3970 if (dotp
> maxdotp
) {
3980 U
[2] = U
[shift
^ 2];
3981 U
[shift
^ 2] = temp
;
3988 // Check that the above code actually permuted
3989 // the vertices correctly.
3992 for (int j
= 0; j
<= 3; ++j
) {
3993 Vector3 tmp
= U
[j
] - *prev_pt_v
;
3995 Double dotp
= dot(vec
, tmp
);
3996 if (dotp
> maxdotp
) {
3997 maxdotp
= dotp
+ 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
4002 printf("New shift = %d!\n", shift
);
4005 for (int j
= 0; j
<= 3; ++j
) {
4006 Vector3 tmp
= U
[j
] - *prev_pt_v
;
4008 Double dotp
= dot(vec
, tmp
);
4009 printf(" %d : %.8f\n", j
, dotp
);
4017 static_date_hack
= pt_v
.GetDate();
4020 // Scale to unit vectors in the LRUD plane.
4024 Double l
= fabs(pt_v
.GetL());
4025 Double r
= fabs(pt_v
.GetR());
4026 Double u
= fabs(pt_v
.GetU());
4027 Double d
= fabs(pt_v
.GetD());
4029 // Produce coordinates of the corners of the LRUD "plane".
4031 v
[0] = pt_v
.GetPoint() - right
* l
+ up
* u
;
4032 v
[1] = pt_v
.GetPoint() + right
* r
+ up
* u
;
4033 v
[2] = pt_v
.GetPoint() + right
* r
- up
* d
;
4034 v
[3] = pt_v
.GetPoint() - right
* l
- up
* d
;
4037 if (!filter
|| (filter
->CheckVisible(pt_v
.GetLabel()) &&
4038 filter
->CheckVisible(prev_pt_v
->GetLabel()))) {
4039 const Vector3
& delta
= pt_v
- *prev_pt_v
;
4040 static_length_hack
= delta
.magnitude();
4041 static_gradient_hack
= delta
.gradient();
4042 (this->*AddQuad
)(v
[0], v
[1], U
[1], U
[0]);
4043 (this->*AddQuad
)(v
[2], v
[3], U
[3], U
[2]);
4044 (this->*AddQuad
)(v
[1], v
[2], U
[2], U
[1]);
4045 (this->*AddQuad
)(v
[3], v
[0], U
[0], U
[3]);
4050 if (!filter
|| filter
->CheckVisible(pt_v
.GetLabel())) {
4052 (this->*AddQuad
)(v
[0], v
[1], v
[2], v
[3]);
4054 (this->*AddQuad
)(v
[3], v
[2], v
[1], v
[0]);
4069 void GfxCore::FullScreenMode()
4071 m_Parent
->ViewFullScreen();
4074 bool GfxCore::IsFullScreen() const
4076 return m_Parent
->IsFullScreen();
4079 bool GfxCore::FullScreenModeShowingMenus() const
4081 return m_Parent
->FullScreenModeShowingMenus();
4084 void GfxCore::FullScreenModeShowMenus(bool show
)
4086 m_Parent
->FullScreenModeShowMenus(show
);
4090 GfxCore::MoveViewer(double forward
, double up
, double right
)
4092 double cT
= cos(rad(m_TiltAngle
));
4093 double sT
= sin(rad(m_TiltAngle
));
4094 double cP
= cos(rad(m_PanAngle
));
4095 double sP
= sin(rad(m_PanAngle
));
4096 Vector3
v_forward(cT
* sP
, cT
* cP
, sT
);
4097 Vector3
v_up(sT
* sP
, sT
* cP
, -cT
);
4098 Vector3
v_right(-cP
, sP
, 0);
4099 assert(fabs(dot(v_forward
, v_up
)) < 1e-6);
4100 assert(fabs(dot(v_forward
, v_right
)) < 1e-6);
4101 assert(fabs(dot(v_right
, v_up
)) < 1e-6);
4102 Vector3 move
= v_forward
* forward
+ v_up
* up
+ v_right
* right
;
4103 AddTranslation(-move
);
4104 // Show current position.
4105 m_Parent
->SetCoords(m_Parent
->GetOffset() - GetTranslation());
4109 PresentationMark
GfxCore::GetView() const
4111 return PresentationMark(GetTranslation() + m_Parent
->GetOffset(),
4112 m_PanAngle
, -m_TiltAngle
, m_Scale
);
4115 void GfxCore::SetView(const PresentationMark
& p
)
4118 SetTranslation(p
- m_Parent
->GetOffset());
4119 m_PanAngle
= p
.angle
;
4120 m_TiltAngle
= -p
.tilt_angle
; // FIXME: nasty reversed sense (and above)
4121 SetRotation(m_PanAngle
, m_TiltAngle
);
4126 void GfxCore::PlayPres(double speed
, bool change_speed
) {
4127 if (!change_speed
|| presentation_mode
== 0) {
4129 presentation_mode
= 0;
4132 presentation_mode
= PLAYING
;
4133 next_mark
= m_Parent
->GetPresMark(MARK_FIRST
);
4135 next_mark_time
= 0; // There already!
4136 this_mark_total
= 0;
4137 pres_reverse
= (speed
< 0);
4140 if (change_speed
) pres_speed
= speed
;
4143 bool new_pres_reverse
= (speed
< 0);
4144 if (new_pres_reverse
!= pres_reverse
) {
4145 pres_reverse
= new_pres_reverse
;
4147 next_mark
= m_Parent
->GetPresMark(MARK_PREV
);
4149 next_mark
= m_Parent
->GetPresMark(MARK_NEXT
);
4151 swap(this_mark_total
, next_mark_time
);
4156 void GfxCore::SetColourBy(int colour_by
) {
4157 m_ColourBy
= colour_by
;
4158 switch (colour_by
) {
4159 case COLOUR_BY_DEPTH
:
4160 AddQuad
= &GfxCore::AddQuadrilateralDepth
;
4161 AddPoly
= &GfxCore::AddPolylineDepth
;
4163 case COLOUR_BY_DATE
:
4164 AddQuad
= &GfxCore::AddQuadrilateralDate
;
4165 AddPoly
= &GfxCore::AddPolylineDate
;
4167 case COLOUR_BY_ERROR
:
4168 case COLOUR_BY_H_ERROR
:
4169 case COLOUR_BY_V_ERROR
:
4170 AddQuad
= &GfxCore::AddQuadrilateralError
;
4171 AddPoly
= &GfxCore::AddPolylineError
;
4173 case COLOUR_BY_GRADIENT
:
4174 AddQuad
= &GfxCore::AddQuadrilateralGradient
;
4175 AddPoly
= &GfxCore::AddPolylineGradient
;
4177 case COLOUR_BY_LENGTH
:
4178 AddQuad
= &GfxCore::AddQuadrilateralLength
;
4179 AddPoly
= &GfxCore::AddPolylineLength
;
4181 case COLOUR_BY_SURVEY
:
4182 AddQuad
= &GfxCore::AddQuadrilateralSurvey
;
4183 AddPoly
= &GfxCore::AddPolylineSurvey
;
4185 case COLOUR_BY_STYLE
:
4186 // FIXME: support quad colouring by style
4187 AddQuad
= &GfxCore::AddQuadrilateral
;
4188 AddPoly
= &GfxCore::AddPolylineStyle
;
4190 default: // case COLOUR_BY_NONE:
4191 AddQuad
= &GfxCore::AddQuadrilateral
;
4192 AddPoly
= &GfxCore::AddPolyline
;
4196 switch (colour_by
) {
4197 case COLOUR_BY_ERROR
:
4198 error_type
= traverse::ERROR_3D
;
4200 case COLOUR_BY_H_ERROR
:
4201 error_type
= traverse::ERROR_H
;
4203 case COLOUR_BY_V_ERROR
:
4204 error_type
= traverse::ERROR_V
;
4208 InvalidateList(LIST_UNDERGROUND_LEGS
);
4209 InvalidateList(LIST_SURFACE_LEGS
);
4210 InvalidateList(LIST_TUBES
);
4215 bool GfxCore::ExportMovie(const wxString
& fnm
)
4217 FILE* fh
= wxFopen(fnm
.fn_str(), wxT("wb"));
4219 wxGetApp().ReportError(wxString::Format(wmsg(/*Failed to open output file “%s”*/47), fnm
.c_str()));
4224 wxFileName::SplitPath(fnm
, NULL
, NULL
, NULL
, &ext
, wxPATH_NATIVE
);
4228 GetSize(&width
, &height
);
4229 // Round up to next multiple of 2 (required by ffmpeg).
4230 width
+= (width
& 1);
4231 height
+= (height
& 1);
4233 movie
= new MovieMaker();
4235 // movie takes ownership of fh.
4236 if (!movie
->Open(fh
, ext
.utf8_str(), width
, height
)) {
4237 wxGetApp().ReportError(wxString(movie
->get_error_string(), wxConvUTF8
));
4248 GfxCore::OnPrint(const wxString
&filename
, const wxString
&title
,
4249 const wxString
&datestamp
,
4250 bool close_after_print
)
4253 p
= new svxPrintDlg(m_Parent
, filename
, title
, datestamp
,
4254 m_PanAngle
, m_TiltAngle
,
4255 m_Names
, m_Crosses
, m_Legs
, m_Surface
, m_Splays
,
4256 m_Tubes
, m_Entrances
, m_FixedPts
, m_ExportedPts
,
4257 true, close_after_print
);
4262 GfxCore::OnExport(const wxString
&filename
, const wxString
&title
,
4263 const wxString
&datestamp
)
4266 p
= new svxPrintDlg(m_Parent
, filename
, title
, datestamp
,
4267 m_PanAngle
, m_TiltAngle
,
4268 m_Names
, m_Crosses
, m_Legs
, m_Surface
, m_Splays
,
4269 m_Tubes
, m_Entrances
, m_FixedPts
, m_ExportedPts
,
4275 make_cursor(const unsigned char * bits
, const unsigned char * mask
,
4278 #if defined __WXGTK__ && !defined __WXGTK3__
4279 // Use this code for GTK < 3 only - it doesn't work properly with GTK3
4280 // (reported and should be fixed in wxWidgets 3.0.4 and 3.1.1, see:
4281 // https://trac.wxwidgets.org/ticket/17916)
4282 return wxCursor((const char *)bits
, 32, 32, hotx
, hoty
,
4283 (const char *)mask
, wxBLACK
, wxWHITE
);
4286 // The default Mac cursor is black with a white edge, so
4287 // invert our custom cursors to match.
4289 for (int i
= 0; i
< 128; ++i
)
4290 b
[i
] = bits
[i
] ^ 0xff;
4292 const char * b
= reinterpret_cast<const char *>(bits
);
4294 wxBitmap
cursor_bitmap(b
, 32, 32);
4295 wxBitmap
mask_bitmap(reinterpret_cast<const char *>(mask
), 32, 32);
4296 cursor_bitmap
.SetMask(new wxMask(mask_bitmap
, *wxWHITE
));
4297 wxImage cursor_image
= cursor_bitmap
.ConvertToImage();
4298 cursor_image
.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X
, hotx
);
4299 cursor_image
.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y
, hoty
);
4300 return wxCursor(cursor_image
);
4307 #include "handmask.xbm"
4310 #include "brotate.xbm"
4312 #include "brotatemask.xbm"
4315 #include "vrotate.xbm"
4317 #include "vrotatemask.xbm"
4320 #include "rotate.xbm"
4322 #include "rotatemask.xbm"
4325 #include "rotatezoom.xbm"
4327 #include "rotatezoommask.xbm"
4330 GfxCore::UpdateCursor(GfxCore::cursor new_cursor
)
4332 // Check if we're already showing that cursor.
4333 if (current_cursor
== new_cursor
) return;
4335 current_cursor
= new_cursor
;
4336 switch (current_cursor
) {
4337 case GfxCore::CURSOR_DEFAULT
:
4338 GLACanvas::SetCursor(wxNullCursor
);
4340 case GfxCore::CURSOR_POINTING_HAND
:
4341 GLACanvas::SetCursor(wxCursor(wxCURSOR_HAND
));
4343 case GfxCore::CURSOR_DRAGGING_HAND
:
4344 GLACanvas::SetCursor(make_cursor(hand_bits
, handmask_bits
, 12, 18));
4346 case GfxCore::CURSOR_HORIZONTAL_RESIZE
:
4347 GLACanvas::SetCursor(wxCursor(wxCURSOR_SIZEWE
));
4349 case GfxCore::CURSOR_ROTATE_HORIZONTALLY
:
4350 GLACanvas::SetCursor(make_cursor(rotate_bits
, rotatemask_bits
, 15, 15));
4352 case GfxCore::CURSOR_ROTATE_VERTICALLY
:
4353 GLACanvas::SetCursor(make_cursor(vrotate_bits
, vrotatemask_bits
, 15, 15));
4355 case GfxCore::CURSOR_ROTATE_EITHER_WAY
:
4356 GLACanvas::SetCursor(make_cursor(brotate_bits
, brotatemask_bits
, 15, 15));
4358 case GfxCore::CURSOR_ZOOM
:
4359 GLACanvas::SetCursor(wxCursor(wxCURSOR_MAGNIFIER
));
4361 case GfxCore::CURSOR_ZOOM_ROTATE
:
4362 GLACanvas::SetCursor(make_cursor(rotatezoom_bits
, rotatezoommask_bits
, 15, 15));
4367 bool GfxCore::MeasuringLineActive() const
4369 if (Animating()) return false;
4370 return HereIsReal() || m_there
;
4373 bool GfxCore::HandleRClick(wxPoint point
)
4375 if (PointWithinCompass(point
)) {
4378 /* TRANSLATORS: View *looking* North */
4379 menu
.Append(menu_ORIENT_MOVE_NORTH
, wmsg(/*View &North*/240));
4380 /* TRANSLATORS: View *looking* East */
4381 menu
.Append(menu_ORIENT_MOVE_EAST
, wmsg(/*View &East*/241));
4382 /* TRANSLATORS: View *looking* South */
4383 menu
.Append(menu_ORIENT_MOVE_SOUTH
, wmsg(/*View &South*/242));
4384 /* TRANSLATORS: View *looking* West */
4385 menu
.Append(menu_ORIENT_MOVE_WEST
, wmsg(/*View &West*/243));
4386 menu
.AppendSeparator();
4387 /* TRANSLATORS: Menu item which turns off the "north arrow" in aven. */
4388 menu
.AppendCheckItem(menu_IND_COMPASS
, wmsg(/*&Hide Compass*/387));
4389 /* TRANSLATORS: tickable menu item in View menu.
4391 * Degrees are the angular measurement where there are 360 in a full
4393 menu
.AppendCheckItem(menu_CTL_DEGREES
, wmsg(/*&Degrees*/343));
4394 menu
.Bind(wxEVT_COMMAND_MENU_SELECTED
,
4396 m_Parent
->GetEventHandler()->ProcessEvent(e
);
4402 if (PointWithinClino(point
)) {
4405 menu
.Append(menu_ORIENT_PLAN
, wmsg(/*&Plan View*/248));
4406 menu
.Append(menu_ORIENT_ELEVATION
, wmsg(/*Ele&vation*/249));
4407 menu
.AppendSeparator();
4408 /* TRANSLATORS: Menu item which turns off the tilt indicator in aven. */
4409 menu
.AppendCheckItem(menu_IND_CLINO
, wmsg(/*&Hide Clino*/384));
4410 /* TRANSLATORS: tickable menu item in View menu.
4412 * Degrees are the angular measurement where there are 360 in a full
4414 menu
.AppendCheckItem(menu_CTL_DEGREES
, wmsg(/*&Degrees*/343));
4415 /* TRANSLATORS: tickable menu item in View menu.
4417 * Show the tilt of the survey as a percentage gradient (100% = 45
4418 * degrees = 50 grad). */
4419 menu
.AppendCheckItem(menu_CTL_PERCENT
, wmsg(/*&Percent*/430));
4420 menu
.Bind(wxEVT_COMMAND_MENU_SELECTED
,
4422 m_Parent
->GetEventHandler()->ProcessEvent(e
);
4428 if (PointWithinScaleBar(point
)) {
4431 /* TRANSLATORS: Menu item which turns off the scale bar in aven. */
4432 menu
.AppendCheckItem(menu_IND_SCALE_BAR
, wmsg(/*&Hide scale bar*/385));
4433 /* TRANSLATORS: tickable menu item in View menu.
4435 * "Metric" here means metres, km, etc (rather than feet, miles, etc)
4437 menu
.AppendCheckItem(menu_CTL_METRIC
, wmsg(/*&Metric*/342));
4438 menu
.Bind(wxEVT_COMMAND_MENU_SELECTED
,
4440 m_Parent
->GetEventHandler()->ProcessEvent(e
);
4446 if (PointWithinColourKey(point
)) {
4449 menu
.AppendCheckItem(menu_COLOUR_BY_DEPTH
, wmsg(/*Colour by &Depth*/292));
4450 menu
.AppendCheckItem(menu_COLOUR_BY_DATE
, wmsg(/*Colour by D&ate*/293));
4451 menu
.AppendCheckItem(menu_COLOUR_BY_ERROR
, wmsg(/*Colour by &Error*/289));
4452 menu
.AppendCheckItem(menu_COLOUR_BY_H_ERROR
, wmsg(/*Colour by &Horizontal Error*/480));
4453 menu
.AppendCheckItem(menu_COLOUR_BY_V_ERROR
, wmsg(/*Colour by &Vertical Error*/481));
4454 menu
.AppendCheckItem(menu_COLOUR_BY_GRADIENT
, wmsg(/*Colour by &Gradient*/85));
4455 menu
.AppendCheckItem(menu_COLOUR_BY_LENGTH
, wmsg(/*Colour by &Length*/82));
4456 menu
.AppendCheckItem(menu_COLOUR_BY_SURVEY
, wmsg(/*Colour by &Survey*/448));
4457 menu
.AppendCheckItem(menu_COLOUR_BY_STYLE
, wmsg(/*Colour by St&yle*/482));
4458 menu
.AppendSeparator();
4459 /* TRANSLATORS: Menu item which turns off the colour key.
4460 * The "Colour Key" is the thing in aven showing which colour
4461 * corresponds to which depth, date, survey closure error, etc. */
4462 menu
.AppendCheckItem(menu_IND_COLOUR_KEY
, wmsg(/*&Hide colour key*/386));
4463 if (m_ColourBy
== COLOUR_BY_DEPTH
|| m_ColourBy
== COLOUR_BY_LENGTH
)
4464 menu
.AppendCheckItem(menu_CTL_METRIC
, wmsg(/*&Metric*/342));
4465 else if (m_ColourBy
== COLOUR_BY_GRADIENT
)
4466 menu
.AppendCheckItem(menu_CTL_DEGREES
, wmsg(/*&Degrees*/343));
4467 menu
.Bind(wxEVT_COMMAND_MENU_SELECTED
,
4469 m_Parent
->GetEventHandler()->ProcessEvent(e
);
4478 void GfxCore::SetZoomBox(wxPoint p1
, wxPoint p2
, bool centred
, bool aspect
)
4481 p1
.x
= p2
.x
+ (p1
.x
- p2
.x
) * 2;
4482 p1
.y
= p2
.y
+ (p1
.y
- p2
.y
) * 2;
4485 #if 0 // FIXME: This needs more work.
4486 int sx
= GetXSize();
4487 int sy
= GetYSize();
4488 int dx
= p1
.x
- p2
.x
;
4489 int dy
= p1
.y
- p2
.y
;
4490 int dy_new
= dx
* sy
/ sx
;
4491 if (abs(dy_new
) >= abs(dy
)) {
4492 p1
.y
+= (dy_new
- dy
) / 2;
4493 p2
.y
-= (dy_new
- dy
) / 2;
4495 int dx_new
= dy
* sx
/ sy
;
4496 p1
.x
+= (dx_new
- dx
) / 2;
4497 p2
.x
-= (dx_new
- dx
) / 2;
4501 zoombox
.set(p1
, p2
);
4505 void GfxCore::ZoomBoxGo()
4507 if (!zoombox
.active()) return;
4509 int width
= GetXSize();
4510 int height
= GetYSize();
4512 TranslateCave(-0.5 * (zoombox
.x1
+ zoombox
.x2
- width
),
4513 -0.5 * (zoombox
.y1
+ zoombox
.y2
- height
));
4514 int box_w
= abs(zoombox
.x1
- zoombox
.x2
);
4515 int box_h
= abs(zoombox
.y1
- zoombox
.y2
);
4517 double factor
= min(double(width
) / box_w
, double(height
) / box_h
);
4521 SetScale(GetScale() * factor
);