6 // Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2024 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
29 #include "img_hosted.h"
37 const static int img2aven_tab
[] = {
44 flags
&= (sizeof(img2aven_tab
) / sizeof(img2aven_tab
[0]) - 1);
45 return img2aven_tab
[flags
];
48 int Model::Load(const wxString
& file
, const wxString
& prefix
)
50 // Load the processed survey data.
51 img
* survey
= img_read_stream_survey(wxFopen(file
, wxT("rb")),
56 return img_error2msg(img_error());
59 m_IsExtendedElevation
= survey
->is_extended_elevation
;
61 // Create a list of all the leg vertices, counting them and finding the
62 // extent of the survey at the same time.
67 m_HasUndergroundLegs
= false;
70 m_HasSurfaceLegs
= false;
71 m_HasErrorInformation
= false;
73 // FIXME: discard existing presentation? ask user about saving if we do!
75 // Delete any existing list entries.
78 double xmin
= DBL_MAX
;
79 double xmax
= -DBL_MAX
;
80 double ymin
= DBL_MAX
;
81 double ymax
= -DBL_MAX
;
82 double zmin
= DBL_MAX
;
83 double zmax
= -DBL_MAX
;
86 double depthmax
= -DBL_MAX
;
90 complete_dateinfo
= true;
92 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
97 // Ultimately we probably want different types (subclasses perhaps?) for
98 // underground and surface data, so we don't need to store LRUD for surface
100 traverse
* current_traverse
= NULL
;
101 vector
<XSect
> * current_tube
= NULL
;
103 map
<wxString
, LabelInfo
*> labelmap
;
104 list
<LabelInfo
*>::const_iterator last_mapped_label
= m_Labels
.begin();
107 img_point prev_pt
= {0,0,0};
108 bool current_polyline_is_surface
= false;
109 int current_flags
= 0;
110 int current_style
= 0;
111 string current_label
;
112 bool pending_move
= false;
113 // When legs within a traverse have different surface/splay/duplicate
114 // flags, we split it into contiguous traverses of each flag combination,
115 // but we need to track these so we can assign the error statistics to all
116 // of them. So we keep counts of how many of each combination we've
117 // generated for the current traverse.
118 size_t n_traverses
[8];
119 memset(n_traverses
, 0, sizeof(n_traverses
));
122 if (++items
% 200 == 0) {
123 long pos
= ftell(survey
->fh
);
124 int progress
= int((double(pos
) / double(file_size
)) * 100.0);
125 // SetProgress(progress);
130 result
= img_read_item(survey
, &pt
);
133 memset(n_traverses
, 0, sizeof(n_traverses
));
139 // Update survey extents.
140 if (pt
.x
< xmin
) xmin
= pt
.x
;
141 if (pt
.x
> xmax
) xmax
= pt
.x
;
142 if (pt
.y
< ymin
) ymin
= pt
.y
;
143 if (pt
.y
> ymax
) ymax
= pt
.y
;
144 if (pt
.z
< zmin
) zmin
= pt
.z
;
145 if (pt
.z
> zmax
) zmax
= pt
.z
;
147 int date
= survey
->days1
;
149 date
+= (survey
->days2
- date
) / 2;
150 if (date
< m_DateMin
) m_DateMin
= date
;
151 if (date
> datemax
) datemax
= date
;
153 complete_dateinfo
= false;
156 int flags
= survey
->flags
&
157 (img_FLAG_SURFACE
|img_FLAG_SPLAY
|img_FLAG_DUPLICATE
);
158 bool is_surface
= (flags
& img_FLAG_SURFACE
);
159 bool is_splay
= (flags
& img_FLAG_SPLAY
);
160 bool is_dupe
= (flags
& img_FLAG_DUPLICATE
);
163 if (pt
.z
< m_DepthMin
) m_DepthMin
= pt
.z
;
164 if (pt
.z
> depthmax
) depthmax
= pt
.z
;
171 current_flags
!= flags
||
172 current_label
!= survey
->label
||
173 current_style
!= survey
->style
) {
174 if (!current_polyline_is_surface
&& current_traverse
) {
175 //FixLRUD(*current_traverse);
178 ++n_traverses
[flags
];
179 // Start new traverse (surface or underground).
181 m_HasSurfaceLegs
= true;
183 m_HasUndergroundLegs
= true;
184 // The previous point was at a surface->ug transition.
185 if (current_polyline_is_surface
) {
186 if (prev_pt
.z
< m_DepthMin
) m_DepthMin
= prev_pt
.z
;
187 if (prev_pt
.z
> depthmax
) depthmax
= prev_pt
.z
;
190 traverses
[flags
].push_back(traverse(survey
->label
));
191 current_traverse
= &traverses
[flags
].back();
192 current_traverse
->flags
= survey
->flags
;
193 current_traverse
->style
= survey
->style
;
195 current_polyline_is_surface
= is_surface
;
196 current_flags
= flags
;
197 current_label
= survey
->label
;
198 current_style
= survey
->style
;
201 // Update survey extents. We only need to do this if
202 // there's a pending move, since for a surface <->
203 // underground transition, we'll already have handled
205 if (prev_pt
.x
< xmin
) xmin
= prev_pt
.x
;
206 if (prev_pt
.x
> xmax
) xmax
= prev_pt
.x
;
207 if (prev_pt
.y
< ymin
) ymin
= prev_pt
.y
;
208 if (prev_pt
.y
> ymax
) ymax
= prev_pt
.y
;
209 if (prev_pt
.z
< zmin
) zmin
= prev_pt
.z
;
210 if (prev_pt
.z
> zmax
) zmax
= prev_pt
.z
;
213 current_traverse
->push_back(PointInfo(prev_pt
));
216 current_traverse
->push_back(PointInfo(pt
, date
));
219 pending_move
= false;
224 wxString
s(survey
->label
, wxConvUTF8
);
226 // If label isn't valid UTF-8 then this conversion will
227 // give an empty string. In this case, assume that the
228 // label is CP1252 (the Microsoft superset of ISO8859-1).
229 static wxCSConv
ConvCP1252(wxFONTENCODING_CP1252
);
230 s
= wxString(survey
->label
, ConvCP1252
);
232 // Or if that doesn't work (ConvCP1252 doesn't like
233 // strings with some bytes in) let's just go for
235 s
= wxString(survey
->label
, wxConvISO8859_1
);
238 int flags
= img2aven(survey
->flags
);
239 LabelInfo
* label
= new LabelInfo(pt
, s
, flags
);
240 if (label
->IsEntrance()) {
243 if (label
->IsFixedPt()) {
246 if (label
->IsExportedPt()) {
249 m_Labels
.push_back(label
);
255 // Start new current_tube.
256 tubes
.push_back(vector
<XSect
>());
257 current_tube
= &tubes
.back();
261 wxString
label(survey
->label
, wxConvUTF8
);
262 map
<wxString
, LabelInfo
*>::const_iterator p
;
263 p
= labelmap
.find(label
);
264 if (p
!= labelmap
.end()) {
267 // Initialise labelmap lazily - we may have no
269 list
<LabelInfo
*>::const_iterator i
;
270 if (labelmap
.empty()) {
271 i
= m_Labels
.begin();
273 i
= last_mapped_label
;
276 while (i
!= m_Labels
.end() && (*i
)->GetText() != label
) {
277 labelmap
[(*i
)->GetText()] = *i
;
280 last_mapped_label
= i
;
281 if (i
== m_Labels
.end()) {
282 // Unattached cross-section - ignore for now.
283 printf("unattached cross-section\n");
284 if (current_tube
->size() <= 1)
285 tubes
.resize(tubes
.size() - 1);
287 if (!m_Labels
.empty())
292 labelmap
[label
] = lab
;
295 int date
= survey
->days1
;
297 date
+= (survey
->days2
- date
) / 2;
298 if (date
< m_DateMin
) m_DateMin
= date
;
299 if (date
> datemax
) datemax
= date
;
302 current_tube
->emplace_back(lab
, date
, survey
->l
, survey
->r
, survey
->u
, survey
->d
);
307 // Finish off current_tube.
308 // If there's only one cross-section in the tube, just
309 // discard it for now. FIXME: we should handle this
310 // when we come to skinning the tubes.
311 if (current_tube
&& current_tube
->size() <= 1)
312 tubes
.resize(tubes
.size() - 1);
316 case img_ERROR_INFO
: {
317 if (survey
->E
== 0.0) {
318 // Currently cavern doesn't spot all articulating traverses
319 // so we assume that any traverse with no error isn't part
320 // of a loop. FIXME: fix cavern!
323 m_HasErrorInformation
= true;
324 for (size_t f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
325 list
<traverse
>::reverse_iterator t
= traverses
[f
].rbegin();
326 size_t n
= n_traverses
[f
];
329 assert(t
!= traverses
[f
].rend());
330 t
->n_legs
= survey
->n_legs
;
331 t
->length
= survey
->length
;
332 t
->errors
[traverse::ERROR_3D
] = survey
->E
;
333 t
->errors
[traverse::ERROR_H
] = survey
->H
;
334 t
->errors
[traverse::ERROR_V
] = survey
->V
;
345 // FIXME: Do we need to reset all these? - Olly
347 m_NumExportedPts
= 0;
349 m_HasUndergroundLegs
= false;
351 m_HasSurfaceLegs
= false;
355 return img_error2msg(img_error());
361 } while (result
!= img_STOP
);
363 if (!current_polyline_is_surface
&& current_traverse
) {
364 //FixLRUD(*current_traverse);
367 // Finish off current_tube.
368 // If there's only one cross-section in the tube, just
369 // discard it for now. FIXME: we should handle this
370 // when we come to skinning the tubes.
371 if (current_tube
&& current_tube
->size() <= 1)
372 tubes
.resize(tubes
.size() - 1);
374 m_separator
= survey
->separator
;
375 m_Title
= wxString(survey
->title
, wxConvUTF8
);
376 m_DateStamp_numeric
= survey
->datestamp_numeric
;
378 m_cs_proj
= wxString(survey
->cs
, wxConvUTF8
);
380 m_cs_proj
= wxString();
382 if (strcmp(survey
->datestamp
, "?") == 0) {
383 /* TRANSLATORS: used a processed survey with no processing date/time info */
384 m_DateStamp
= wmsg(/*Date and time not available.*/108);
385 } else if (survey
->datestamp
[0] == '@') {
386 const struct tm
* tm
= localtime(&m_DateStamp_numeric
);
388 /* TRANSLATORS: This is the date format string used to timestamp .3d
389 * files internally. Probably best to keep it the same for all
391 strftime(buf
, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm
);
392 m_DateStamp
= wxString(buf
, wxConvUTF8
);
394 if (m_DateStamp
.empty()) {
395 m_DateStamp
= wxString(survey
->datestamp
, wxConvUTF8
);
399 // Check we've actually loaded some legs or stations!
400 if (!m_HasUndergroundLegs
&& !m_HasSurfaceLegs
&& m_Labels
.empty()) {
401 return (/*No survey data in 3d file “%s”*/202);
404 if (traverses
[0].empty() &&
405 traverses
[1].empty() &&
406 traverses
[2].empty() &&
407 traverses
[3].empty() &&
408 traverses
[4].empty() &&
409 traverses
[5].empty() &&
410 traverses
[6].empty() &&
411 traverses
[7].empty()) {
412 // No legs, so get survey extents from stations
413 list
<LabelInfo
*>::const_iterator i
;
414 for (i
= m_Labels
.begin(); i
!= m_Labels
.end(); ++i
) {
415 if ((*i
)->GetX() < xmin
) xmin
= (*i
)->GetX();
416 if ((*i
)->GetX() > xmax
) xmax
= (*i
)->GetX();
417 if ((*i
)->GetY() < ymin
) ymin
= (*i
)->GetY();
418 if ((*i
)->GetY() > ymax
) ymax
= (*i
)->GetY();
419 if ((*i
)->GetZ() < zmin
) zmin
= (*i
)->GetZ();
420 if ((*i
)->GetZ() > zmax
) zmax
= (*i
)->GetZ();
424 m_Ext
.assign(xmax
- xmin
, ymax
- ymin
, zmax
- zmin
);
426 if (datemax
< m_DateMin
) m_DateMin
= datemax
;
427 m_DateExt
= datemax
- m_DateMin
;
429 // Centre the dataset around the origin.
430 CentreDataset(Vector3(xmin
, ymin
, zmin
));
432 if (depthmax
< m_DepthMin
) {
436 m_DepthExt
= depthmax
- m_DepthMin
;
437 m_DepthMin
-= GetOffset().GetZ();
441 printf("time to load = %.3f\n", (double)timer
.Time());
447 void Model::CentreDataset(const Vector3
& vmin
)
449 // Centre the dataset around the origin.
451 m_Offset
= vmin
+ (m_Ext
* 0.5);
453 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
454 list
<traverse
>::iterator t
= traverses
[f
].begin();
455 while (t
!= traverses
[f
].end()) {
456 assert(t
->size() > 1);
457 vector
<PointInfo
>::iterator pos
= t
->begin();
458 while (pos
!= t
->end()) {
459 Point
& point
= *pos
++;
466 list
<LabelInfo
*>::iterator lpos
= m_Labels
.begin();
467 while (lpos
!= m_Labels
.end()) {
468 Point
& point
= **lpos
++;
474 Model::do_prepare_tubes() const
476 // Fill in "right_bearing" for each cross-section.
477 for (auto&& tube
: tubes
) {
478 assert(tube
.size() > 1);
480 XSect
* prev_pt_v
= NULL
;
481 Vector3
last_right(1.0, 0.0, 0.0);
483 vector
<XSect
>::iterator i
= tube
.begin();
484 vector
<XSect
>::size_type segment
= 0;
485 while (i
!= tube
.end()) {
486 // get the coordinates of this vertex
489 bool cover_end
= false;
493 const Vector3
up_v(0.0, 0.0, 1.0);
496 assert(i
!= tube
.end());
499 // get the coordinates of the next vertex
500 const XSect
& next_pt_v
= *i
;
502 // calculate vector from this pt to the next one
503 Vector3 leg_v
= next_pt_v
- pt_v
;
505 // obtain a vector in the LRUD plane
506 right
= leg_v
* up_v
;
507 if (right
.magnitude() == 0) {
509 // Obtain a second vector in the LRUD plane,
510 // perpendicular to the first.
511 //up = right * leg_v;
519 } else if (segment
+ 1 == tube
.size()) {
522 // Calculate vector from the previous pt to this one.
523 Vector3 leg_v
= pt_v
- *prev_pt_v
;
525 // Obtain a horizontal vector in the LRUD plane.
526 right
= leg_v
* up_v
;
527 if (right
.magnitude() == 0) {
528 right
= Vector3(last_right
.GetX(), last_right
.GetY(), 0.0);
529 // Obtain a second vector in the LRUD plane,
530 // perpendicular to the first.
531 //up = right * leg_v;
540 assert(i
!= tube
.end());
541 // Intermediate segment.
543 // Get the coordinates of the next vertex.
544 const XSect
& next_pt_v
= *i
;
546 // Calculate vectors from this vertex to the
547 // next vertex, and from the previous vertex to
549 Vector3 leg1_v
= pt_v
- *prev_pt_v
;
550 Vector3 leg2_v
= next_pt_v
- pt_v
;
552 // Obtain horizontal vectors perpendicular to
553 // both legs, then normalise and average to get
554 // a horizontal bisector.
555 Vector3 r1
= leg1_v
* up_v
;
556 Vector3 r2
= leg2_v
* up_v
;
560 if (right
.magnitude() == 0) {
561 // This is the "mid-pitch" case...
564 if (r1
.magnitude() == 0) {
567 // Rotate pitch section to minimise the
568 // "torsional stress" - FIXME: use
569 // triangles instead of rectangles?
573 // Scale to unit vectors in the LRUD plane.
576 Vector3 vec
= up
- right
;
577 for (int orient
= 0; orient
<= 3; ++orient
) {
578 Vector3 tmp
= U
[orient
] - prev_pt_v
->GetPoint();
580 double dotp
= dot(vec
, tmp
);
581 if (dotp
> maxdotp
) {
599 // Check that the above code actually permuted
600 // the vertices correctly.
603 for (int j
= 0; j
<= 3; ++j
) {
604 Vector3 tmp
= U
[j
] - *prev_pt_v
;
606 double dotp
= dot(vec
, tmp
);
607 if (dotp
> maxdotp
) {
608 maxdotp
= dotp
+ 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
613 printf("New shift = %d!\n", shift
);
616 for (int j
= 0; j
<= 3; ++j
) {
617 Vector3 tmp
= U
[j
] - *prev_pt_v
;
619 double dotp
= dot(vec
, tmp
);
620 printf(" %d : %.8f\n", j
, dotp
);
630 // Scale to unit vectors in the LRUD plane.
634 double l
= fabs(pt_v
.GetL());
635 double r
= fabs(pt_v
.GetR());
636 double u
= fabs(pt_v
.GetU());
637 double d
= fabs(pt_v
.GetD());
639 // Produce coordinates of the corners of the LRUD "plane".
641 v
[0] = pt_v
.GetPoint() - right
* l
+ up
* u
;
642 v
[1] = pt_v
.GetPoint() + right
* r
+ up
* u
;
643 v
[2] = pt_v
.GetPoint() + right
* r
- up
* d
;
644 v
[3] = pt_v
.GetPoint() - right
* l
- up
* d
;
652 // FIXME: Store rather than recomputing on each draw?
655 pt_v
.set_right_bearing(deg(atan2(right
.GetX(), right
.GetY())));
663 SurveyFilter::add(const wxString
& name
)
665 auto it
= filters
.lower_bound(name
);
666 if (it
!= filters
.end()) {
667 // name is already present in the filter.
671 // Check if a survey prefixing name is visible.
672 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
) {
673 redundant_filters
.insert(name
);
677 while (it
!= filters
.begin()) {
679 const wxString
& s
= *it
;
680 if (s
.size() <= name
.size()) break;
681 if (s
.StartsWith(name
) && s
[name
.size()] == separator
) {
682 redundant_filters
.insert(s
);
683 it
= filters
.erase(it
);
686 filters
.insert(name
);
690 SurveyFilter::remove(const wxString
& name
)
692 if (filters
.erase(name
) == 0) {
693 redundant_filters
.erase(name
);
696 if (redundant_filters
.empty()) {
699 auto it
= redundant_filters
.upper_bound(name
);
700 while (it
!= redundant_filters
.begin()) {
702 // Check if a survey prefixed by name should be made visible.
703 const wxString
& s
= *it
;
704 if (s
.size() <= name
.size()) {
707 if (!(s
.StartsWith(name
) && s
[name
.size()] == separator
))
710 it
= redundant_filters
.erase(it
);
715 SurveyFilter::SetSeparator(wxChar separator_
)
717 if (separator_
== separator
) return;
719 separator
= separator_
;
721 if (filters
.empty()) {
725 // Move aside all the filters already set and re-add() them so they get
726 // split into redundant_filters appropriately.
727 std::set
<wxString
, std::greater
<wxString
>> old_filters
;
728 std::set
<wxString
, std::greater
<wxString
>> old_redundant_filters
;
729 swap(filters
, old_filters
);
730 swap(redundant_filters
, old_redundant_filters
);
731 for (auto& s
: old_filters
) {
734 for (auto& s
: old_redundant_filters
) {
740 SurveyFilter::CheckVisible(const wxString
& name
) const
742 auto it
= filters
.lower_bound(name
);
743 if (it
== filters
.end()) {
744 // There's no filter <= name so name is excluded.
751 // Check if a survey prefixing name is visible.
752 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
)