Invalid display lists on hidpi change
[survex.git] / src / model.cc
blob600365ce51d6904fb25c2ececda4d9bb34f756f8
1 //
2 // model.cc
3 //
4 // Cave survey model.
5 //
6 // Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018,2019 Olly Betts
8 // Copyright (C) 2005 Martin Green
9 //
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include "model.h"
31 #include "img_hosted.h"
32 #include "useful.h"
34 #include <cfloat>
35 #include <map>
37 using namespace std;
39 const static int img2aven_tab[] = {
40 #include "img2aven.h"
43 inline int
44 img2aven(int flags)
46 flags &= (sizeof(img2aven_tab) / sizeof(img2aven_tab[0]) - 1);
47 return img2aven_tab[flags];
50 int Model::Load(const wxString& file, const wxString& prefix)
52 // Load the processed survey data.
53 img* survey = img_read_stream_survey(wxFopen(file, wxT("rb")),
54 fclose,
55 file.c_str(),
56 prefix.utf8_str());
57 if (!survey) {
58 return img_error2msg(img_error());
61 m_IsExtendedElevation = survey->is_extended_elevation;
63 // Create a list of all the leg vertices, counting them and finding the
64 // extent of the survey at the same time.
66 m_NumFixedPts = 0;
67 m_NumExportedPts = 0;
68 m_NumEntrances = 0;
69 m_HasUndergroundLegs = false;
70 m_HasSplays = false;
71 m_HasDupes = false;
72 m_HasSurfaceLegs = false;
73 m_HasErrorInformation = false;
75 // FIXME: discard existing presentation? ask user about saving if we do!
77 // Delete any existing list entries.
78 m_Labels.clear();
80 double xmin = DBL_MAX;
81 double xmax = -DBL_MAX;
82 double ymin = DBL_MAX;
83 double ymax = -DBL_MAX;
84 double zmin = DBL_MAX;
85 double zmax = -DBL_MAX;
87 m_DepthMin = DBL_MAX;
88 double depthmax = -DBL_MAX;
90 m_DateMin = INT_MAX;
91 int datemax = -1;
92 complete_dateinfo = true;
94 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
95 traverses[f].clear();
97 tubes.clear();
99 // Ultimately we probably want different types (subclasses perhaps?) for
100 // underground and surface data, so we don't need to store LRUD for surface
101 // stuff.
102 traverse * current_traverse = NULL;
103 vector<XSect> * current_tube = NULL;
105 map<wxString, LabelInfo *> labelmap;
106 list<LabelInfo*>::const_iterator last_mapped_label = m_Labels.begin();
108 int result;
109 img_point prev_pt = {0,0,0};
110 bool current_polyline_is_surface = false;
111 int current_flags = 0;
112 int current_style = 0;
113 string current_label;
114 bool pending_move = false;
115 // When legs within a traverse have different surface/splay/duplicate
116 // flags, we split it into contiguous traverses of each flag combination,
117 // but we need to track these so we can assign the error statistics to all
118 // of them. So we keep counts of how many of each combination we've
119 // generated for the current traverse.
120 size_t n_traverses[8];
121 memset(n_traverses, 0, sizeof(n_traverses));
122 do {
123 #if 0
124 if (++items % 200 == 0) {
125 long pos = ftell(survey->fh);
126 int progress = int((double(pos) / double(file_size)) * 100.0);
127 // SetProgress(progress);
129 #endif
131 img_point pt;
132 result = img_read_item(survey, &pt);
133 switch (result) {
134 case img_MOVE:
135 memset(n_traverses, 0, sizeof(n_traverses));
136 pending_move = true;
137 prev_pt = pt;
138 break;
140 case img_LINE: {
141 // Update survey extents.
142 if (pt.x < xmin) xmin = pt.x;
143 if (pt.x > xmax) xmax = pt.x;
144 if (pt.y < ymin) ymin = pt.y;
145 if (pt.y > ymax) ymax = pt.y;
146 if (pt.z < zmin) zmin = pt.z;
147 if (pt.z > zmax) zmax = pt.z;
149 int date = survey->days1;
150 if (date != -1) {
151 date += (survey->days2 - date) / 2;
152 if (date < m_DateMin) m_DateMin = date;
153 if (date > datemax) datemax = date;
154 } else {
155 complete_dateinfo = false;
158 int flags = survey->flags &
159 (img_FLAG_SURFACE|img_FLAG_SPLAY|img_FLAG_DUPLICATE);
160 bool is_surface = (flags & img_FLAG_SURFACE);
161 bool is_splay = (flags & img_FLAG_SPLAY);
162 bool is_dupe = (flags & img_FLAG_DUPLICATE);
164 if (!is_surface) {
165 if (pt.z < m_DepthMin) m_DepthMin = pt.z;
166 if (pt.z > depthmax) depthmax = pt.z;
168 if (is_splay)
169 m_HasSplays = true;
170 if (is_dupe)
171 m_HasDupes = true;
172 if (pending_move ||
173 current_flags != flags ||
174 current_label != survey->label ||
175 current_style != survey->style) {
176 if (!current_polyline_is_surface && current_traverse) {
177 //FixLRUD(*current_traverse);
180 ++n_traverses[flags];
181 // Start new traverse (surface or underground).
182 if (is_surface) {
183 m_HasSurfaceLegs = true;
184 } else {
185 m_HasUndergroundLegs = true;
186 // The previous point was at a surface->ug transition.
187 if (current_polyline_is_surface) {
188 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
189 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
192 traverses[flags].push_back(traverse(survey->label));
193 current_traverse = &traverses[flags].back();
194 current_traverse->flags = survey->flags;
195 current_traverse->style = survey->style;
197 current_polyline_is_surface = is_surface;
198 current_flags = flags;
199 current_label = survey->label;
200 current_style = survey->style;
202 if (pending_move) {
203 // Update survey extents. We only need to do this if
204 // there's a pending move, since for a surface <->
205 // underground transition, we'll already have handled
206 // this point.
207 if (prev_pt.x < xmin) xmin = prev_pt.x;
208 if (prev_pt.x > xmax) xmax = prev_pt.x;
209 if (prev_pt.y < ymin) ymin = prev_pt.y;
210 if (prev_pt.y > ymax) ymax = prev_pt.y;
211 if (prev_pt.z < zmin) zmin = prev_pt.z;
212 if (prev_pt.z > zmax) zmax = prev_pt.z;
215 current_traverse->push_back(PointInfo(prev_pt));
218 current_traverse->push_back(PointInfo(pt, date));
220 prev_pt = pt;
221 pending_move = false;
222 break;
225 case img_LABEL: {
226 wxString s(survey->label, wxConvUTF8);
227 if (s.empty()) {
228 // If label isn't valid UTF-8 then this conversion will
229 // give an empty string. In this case, assume that the
230 // label is CP1252 (the Microsoft superset of ISO8859-1).
231 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
232 s = wxString(survey->label, ConvCP1252);
233 if (s.empty()) {
234 // Or if that doesn't work (ConvCP1252 doesn't like
235 // strings with some bytes in) let's just go for
236 // ISO8859-1.
237 s = wxString(survey->label, wxConvISO8859_1);
240 int flags = img2aven(survey->flags);
241 LabelInfo* label = new LabelInfo(pt, s, flags);
242 if (label->IsEntrance()) {
243 m_NumEntrances++;
245 if (label->IsFixedPt()) {
246 m_NumFixedPts++;
248 if (label->IsExportedPt()) {
249 m_NumExportedPts++;
251 m_Labels.push_back(label);
252 break;
255 case img_XSECT: {
256 if (!current_tube) {
257 // Start new current_tube.
258 tubes.push_back(vector<XSect>());
259 current_tube = &tubes.back();
262 LabelInfo * lab;
263 wxString label(survey->label, wxConvUTF8);
264 map<wxString, LabelInfo *>::const_iterator p;
265 p = labelmap.find(label);
266 if (p != labelmap.end()) {
267 lab = p->second;
268 } else {
269 // Initialise labelmap lazily - we may have no
270 // cross-sections.
271 list<LabelInfo*>::const_iterator i;
272 if (labelmap.empty()) {
273 i = m_Labels.begin();
274 } else {
275 i = last_mapped_label;
276 ++i;
278 while (i != m_Labels.end() && (*i)->GetText() != label) {
279 labelmap[(*i)->GetText()] = *i;
280 ++i;
282 last_mapped_label = i;
283 if (i == m_Labels.end()) {
284 // Unattached cross-section - ignore for now.
285 printf("unattached cross-section\n");
286 if (current_tube->size() <= 1)
287 tubes.resize(tubes.size() - 1);
288 current_tube = NULL;
289 if (!m_Labels.empty())
290 --last_mapped_label;
291 break;
293 lab = *i;
294 labelmap[label] = lab;
297 int date = survey->days1;
298 if (date != -1) {
299 date += (survey->days2 - date) / 2;
300 if (date < m_DateMin) m_DateMin = date;
301 if (date > datemax) datemax = date;
304 current_tube->emplace_back(lab, date, survey->l, survey->r, survey->u, survey->d);
305 break;
308 case img_XSECT_END:
309 // Finish off current_tube.
310 // If there's only one cross-section in the tube, just
311 // discard it for now. FIXME: we should handle this
312 // when we come to skinning the tubes.
313 if (current_tube && current_tube->size() <= 1)
314 tubes.resize(tubes.size() - 1);
315 current_tube = NULL;
316 break;
318 case img_ERROR_INFO: {
319 if (survey->E == 0.0) {
320 // Currently cavern doesn't spot all articulating traverses
321 // so we assume that any traverse with no error isn't part
322 // of a loop. FIXME: fix cavern!
323 break;
325 m_HasErrorInformation = true;
326 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
327 list<traverse>::reverse_iterator t = traverses[f].rbegin();
328 size_t n = n_traverses[f];
329 n_traverses[f] = 0;
330 while (n) {
331 assert(t != traverses[f].rend());
332 t->n_legs = survey->n_legs;
333 t->length = survey->length;
334 t->errors[traverse::ERROR_3D] = survey->E;
335 t->errors[traverse::ERROR_H] = survey->H;
336 t->errors[traverse::ERROR_V] = survey->V;
337 --n;
338 ++t;
341 break;
344 case img_BAD: {
345 m_Labels.clear();
347 // FIXME: Do we need to reset all these? - Olly
348 m_NumFixedPts = 0;
349 m_NumExportedPts = 0;
350 m_NumEntrances = 0;
351 m_HasUndergroundLegs = false;
352 m_HasSplays = false;
353 m_HasSurfaceLegs = false;
355 img_close(survey);
357 return img_error2msg(img_error());
360 default:
361 break;
363 } while (result != img_STOP);
365 if (!current_polyline_is_surface && current_traverse) {
366 //FixLRUD(*current_traverse);
369 // Finish off current_tube.
370 // If there's only one cross-section in the tube, just
371 // discard it for now. FIXME: we should handle this
372 // when we come to skinning the tubes.
373 if (current_tube && current_tube->size() <= 1)
374 tubes.resize(tubes.size() - 1);
376 m_separator = survey->separator;
377 m_Title = wxString(survey->title, wxConvUTF8);
378 m_DateStamp_numeric = survey->datestamp_numeric;
379 if (survey->cs) {
380 m_cs_proj = wxString(survey->cs, wxConvUTF8);
381 } else {
382 m_cs_proj = wxString();
384 if (strcmp(survey->datestamp, "?") == 0) {
385 /* TRANSLATORS: used a processed survey with no processing date/time info */
386 m_DateStamp = wmsg(/*Date and time not available.*/108);
387 } else if (survey->datestamp[0] == '@') {
388 const struct tm * tm = localtime(&m_DateStamp_numeric);
389 char buf[256];
390 /* TRANSLATORS: This is the date format string used to timestamp .3d
391 * files internally. Probably best to keep it the same for all
392 * translations. */
393 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
394 m_DateStamp = wxString(buf, wxConvUTF8);
396 if (m_DateStamp.empty()) {
397 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
399 img_close(survey);
401 // Check we've actually loaded some legs or stations!
402 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
403 return (/*No survey data in 3d file “%s”*/202);
406 if (traverses[0].empty() &&
407 traverses[1].empty() &&
408 traverses[2].empty() &&
409 traverses[3].empty() &&
410 traverses[4].empty() &&
411 traverses[5].empty() &&
412 traverses[6].empty() &&
413 traverses[7].empty()) {
414 // No legs, so get survey extents from stations
415 list<LabelInfo*>::const_iterator i;
416 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
417 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
418 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
419 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
420 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
421 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
422 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
426 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
428 if (datemax < m_DateMin) m_DateMin = datemax;
429 m_DateExt = datemax - m_DateMin;
431 // Centre the dataset around the origin.
432 CentreDataset(Vector3(xmin, ymin, zmin));
434 if (depthmax < m_DepthMin) {
435 m_DepthMin = 0;
436 m_DepthExt = 0;
437 } else {
438 m_DepthExt = depthmax - m_DepthMin;
439 m_DepthMin -= GetOffset().GetZ();
442 #if 0
443 printf("time to load = %.3f\n", (double)timer.Time());
444 #endif
446 return 0; // OK
449 void Model::CentreDataset(const Vector3& vmin)
451 // Centre the dataset around the origin.
453 m_Offset = vmin + (m_Ext * 0.5);
455 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
456 list<traverse>::iterator t = traverses[f].begin();
457 while (t != traverses[f].end()) {
458 assert(t->size() > 1);
459 vector<PointInfo>::iterator pos = t->begin();
460 while (pos != t->end()) {
461 Point & point = *pos++;
462 point -= m_Offset;
464 ++t;
468 list<LabelInfo*>::iterator lpos = m_Labels.begin();
469 while (lpos != m_Labels.end()) {
470 Point & point = **lpos++;
471 point -= m_Offset;
475 void
476 Model::do_prepare_tubes() const
478 // Fill in "right_bearing" for each cross-section.
479 for (auto&& tube : tubes) {
480 assert(tube.size() > 1);
481 Vector3 U[4];
482 XSect* prev_pt_v = NULL;
483 Vector3 last_right(1.0, 0.0, 0.0);
485 vector<XSect>::iterator i = tube.begin();
486 vector<XSect>::size_type segment = 0;
487 while (i != tube.end()) {
488 // get the coordinates of this vertex
489 XSect & pt_v = *i++;
491 bool cover_end = false;
493 Vector3 right, up;
495 const Vector3 up_v(0.0, 0.0, 1.0);
497 if (segment == 0) {
498 assert(i != tube.end());
499 // first segment
501 // get the coordinates of the next vertex
502 const XSect & next_pt_v = *i;
504 // calculate vector from this pt to the next one
505 Vector3 leg_v = next_pt_v - pt_v;
507 // obtain a vector in the LRUD plane
508 right = leg_v * up_v;
509 if (right.magnitude() == 0) {
510 right = last_right;
511 // Obtain a second vector in the LRUD plane,
512 // perpendicular to the first.
513 //up = right * leg_v;
514 up = up_v;
515 } else {
516 last_right = right;
517 up = up_v;
520 cover_end = true;
521 } else if (segment + 1 == tube.size()) {
522 // last segment
524 // Calculate vector from the previous pt to this one.
525 Vector3 leg_v = pt_v - *prev_pt_v;
527 // Obtain a horizontal vector in the LRUD plane.
528 right = leg_v * up_v;
529 if (right.magnitude() == 0) {
530 right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
531 // Obtain a second vector in the LRUD plane,
532 // perpendicular to the first.
533 //up = right * leg_v;
534 up = up_v;
535 } else {
536 last_right = right;
537 up = up_v;
540 cover_end = true;
541 } else {
542 assert(i != tube.end());
543 // Intermediate segment.
545 // Get the coordinates of the next vertex.
546 const XSect & next_pt_v = *i;
548 // Calculate vectors from this vertex to the
549 // next vertex, and from the previous vertex to
550 // this one.
551 Vector3 leg1_v = pt_v - *prev_pt_v;
552 Vector3 leg2_v = next_pt_v - pt_v;
554 // Obtain horizontal vectors perpendicular to
555 // both legs, then normalise and average to get
556 // a horizontal bisector.
557 Vector3 r1 = leg1_v * up_v;
558 Vector3 r2 = leg2_v * up_v;
559 r1.normalise();
560 r2.normalise();
561 right = r1 + r2;
562 if (right.magnitude() == 0) {
563 // This is the "mid-pitch" case...
564 right = last_right;
566 if (r1.magnitude() == 0) {
567 up = up_v;
569 // Rotate pitch section to minimise the
570 // "torsional stress" - FIXME: use
571 // triangles instead of rectangles?
572 int shift = 0;
573 double maxdotp = 0;
575 // Scale to unit vectors in the LRUD plane.
576 right.normalise();
577 up.normalise();
578 Vector3 vec = up - right;
579 for (int orient = 0; orient <= 3; ++orient) {
580 Vector3 tmp = U[orient] - prev_pt_v->GetPoint();
581 tmp.normalise();
582 double dotp = dot(vec, tmp);
583 if (dotp > maxdotp) {
584 maxdotp = dotp;
585 shift = orient;
588 if (shift) {
589 if (shift != 2) {
590 Vector3 temp(U[0]);
591 U[0] = U[shift];
592 U[shift] = U[2];
593 U[2] = U[shift ^ 2];
594 U[shift ^ 2] = temp;
595 } else {
596 swap(U[0], U[2]);
597 swap(U[1], U[3]);
600 #if 0
601 // Check that the above code actually permuted
602 // the vertices correctly.
603 shift = 0;
604 maxdotp = 0;
605 for (int j = 0; j <= 3; ++j) {
606 Vector3 tmp = U[j] - *prev_pt_v;
607 tmp.normalise();
608 double dotp = dot(vec, tmp);
609 if (dotp > maxdotp) {
610 maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
611 shift = j;
614 if (shift) {
615 printf("New shift = %d!\n", shift);
616 shift = 0;
617 maxdotp = 0;
618 for (int j = 0; j <= 3; ++j) {
619 Vector3 tmp = U[j] - *prev_pt_v;
620 tmp.normalise();
621 double dotp = dot(vec, tmp);
622 printf(" %d : %.8f\n", j, dotp);
625 #endif
626 } else {
627 up = up_v;
629 last_right = right;
632 // Scale to unit vectors in the LRUD plane.
633 right.normalise();
634 up.normalise();
636 double l = fabs(pt_v.GetL());
637 double r = fabs(pt_v.GetR());
638 double u = fabs(pt_v.GetU());
639 double d = fabs(pt_v.GetD());
641 // Produce coordinates of the corners of the LRUD "plane".
642 Vector3 v[4];
643 v[0] = pt_v.GetPoint() - right * l + up * u;
644 v[1] = pt_v.GetPoint() + right * r + up * u;
645 v[2] = pt_v.GetPoint() + right * r - up * d;
646 v[3] = pt_v.GetPoint() - right * l - up * d;
648 prev_pt_v = &pt_v;
649 U[0] = v[0];
650 U[1] = v[1];
651 U[2] = v[2];
652 U[3] = v[3];
654 // FIXME: Store rather than recomputing on each draw?
655 (void)cover_end;
657 pt_v.set_right_bearing(deg(atan2(right.GetX(), right.GetY())));
659 ++segment;
664 void
665 SurveyFilter::add(const wxString& name)
667 auto it = filters.lower_bound(name);
668 if (it != filters.end()) {
669 // It's invalid to add a survey which is already present.
670 assert(*it != name);
671 // Check if a survey prefixing name is visible.
672 if (name.StartsWith(*it) && name[it->size()] == separator) {
673 redundant_filters.insert(name);
674 return;
677 while (it != filters.begin()) {
678 --it;
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);
689 void
690 SurveyFilter::remove(const wxString& name)
692 if (filters.erase(name) == 0) {
693 redundant_filters.erase(name);
694 return;
696 if (redundant_filters.empty()) {
697 return;
699 auto it = redundant_filters.upper_bound(name);
700 while (it != redundant_filters.begin()) {
701 --it;
702 // Check if a survey prefixed by name should be made visible.
703 const wxString& s = *it;
704 if (s.size() <= name.size()) {
705 break;
707 if (!(s.StartsWith(name) && s[name.size()] == separator))
708 break;
709 filters.insert(s);
710 it = redundant_filters.erase(it);
714 void
715 SurveyFilter::SetSeparator(wxChar separator_)
717 if (separator_ == separator) return;
719 separator = separator_;
721 if (filters.empty()) {
722 return;
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 : filters) {
732 add(s);
734 for (auto& s : redundant_filters) {
735 add(s);
739 bool
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.
745 return false;
747 if (*it == name) {
748 // Exact match.
749 return true;
751 // Check if a survey prefixing name is visible.
752 if (name.StartsWith(*it) && name[it->size()] == separator)
753 return true;
754 return false;