File: UniverseGenerator.cpp

package info (click to toggle)
freeorion 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 194,940 kB
  • sloc: cpp: 186,508; python: 40,969; ansic: 1,164; xml: 719; makefile: 32; sh: 7
file content (880 lines) | stat: -rw-r--r-- 39,006 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
#include "UniverseGenerator.h"

#include "../util/Random.h"
#include "../util/i18n.h"
#include "../util/MultiplayerCommon.h"
#include "../util/GameRules.h"
#include "../util/AppInterface.h"
#include "../Empire/Empire.h"
#include "../Empire/EmpireManager.h"

#include "../universe/Planet.h"
#include "../universe/System.h"
#include "../universe/Species.h"

#include "../util/ScopedTimer.h"

#include <boost/container/flat_map.hpp>

#include <limits>

namespace {
    DeclareThreadSafeLogger(effects);
}

//////////////////////////////////////////
//  Universe Setup Functions            //
//////////////////////////////////////////

namespace Delauney {
    /** simple 2D point.  would have used array of systems, but System
      * class has limits on the range of positions that would interfere
      * with the triangulation algorithm (need a single large covering
      * triangle that overlaps all actual points being triangulated) */
    class DTPoint {
    public:
        DTPoint() = default;
        DTPoint(double x_, double y_) :
            x(x_),
            y(y_)
        {}
        double x = 0.0;
        double y = 0.0;
    };

    /* simple class for an integer that has an associated "sorting value",
     * so the integer can be stored in a list sorted by something other than
     * the value of the integer */
    class SortValInt {
    public:
        SortValInt(int num_, double sort_val_) :
            num(num_),
            sortVal(sort_val_)
        {}
        int     num;
        double  sortVal;
    };


    /** list of three interger array indices, and some additional info about
      * the triangle that the corresponding points make up, such as the
      * circumcentre and radius, and a function to find if another point is in
      * the circumcircle */
    class DTTriangle {
    public:
        DTTriangle() = default;
        DTTriangle(int vert1, int vert2, int vert3,
                   const std::vector<Delauney::DTPoint>& points);

        ///< determines whether a specified point is within the circumcircle of the triangle
        bool PointInCircumCircle(const Delauney::DTPoint& p);
        const std::array<int, 3>& Verts() {return verts;}

    private:
        std::array<int, 3> verts{0, 0, 0};   ///< indices of vertices of triangle
        Delauney::DTPoint  centre{0.0, 0.0}; ///< location of circumcentre of triangle
        double             radius2{0.0};     ///< radius of circumcircle squared
    };

    DTTriangle::DTTriangle(int vert1, int vert2, int vert3,
                           const std::vector<Delauney::DTPoint>& points)
    {
        if (vert1 == vert2 || vert1 == vert3 || vert2 == vert3)
            throw std::runtime_error("Attempted to create Triangle with two of the same vertex indices.");

        // record indices of vertices of triangle
        verts = {vert1, vert2, vert3};

        // extract position info for vertices
        const double& x1 = points[vert1].x;
        const double& x2 = points[vert2].x;
        const double& x3 = points[vert3].x;
        const double& y1 = points[vert1].y;
        const double& y2 = points[vert2].y;
        const double& y3 = points[vert3].y;

        // calculate circumcircle and circumcentre of triangle
        double a, Sx, Sy, b;

        a = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);

        Sx = 0.5 * ((x1 * x1 + y1 * y1) * (y2 - y3) +
                    (x2 * x2 + y2 * y2) * (y3 - y1) +
                    (x3 * x3 + y3 * y3) * (y1 - y2));

        Sy = -0.5* ((x1 * x1 + y1 * y1) * (x2 - x3) +
                    (x2 * x2 + y2 * y2) * (x3 - x1) +
                    (x3 * x3 + y3 * y3) * (x1 - x2));

        b =        ((x1 * x1 + y1 * y1) * (x2 * y3 - x3 * y2) +
                    (x2 * x2 + y2 * y2) * (x3 * y1 - x1 * y3) +
                    (x3 * x3 + y3 * y3) * (x1 * y2 - x2 * y1));

        // make sure nothing funky's going on...
        if (std::abs(a) < 0.01)
            throw std::runtime_error("Attempted to find circumcircle for a triangle with vertices in a line.");

        // finish!
        centre.x = Sx / a;
        centre.y = Sy / a;
        radius2 = (Sx*Sx + Sy*Sy)/(a*a) + b/a;
    };

    bool DTTriangle::PointInCircumCircle(const Delauney::DTPoint& p) {
        double vectX = p.x - centre.x;
        double vectY = p.y - centre.y;
        return (vectX*vectX + vectY*vectY < radius2);
    };



    /** Runs a Delauney Triangulation on a set of 2D points corresponding
      * to the locations of the systems in \a systems_vec */
    std::list<Delauney::DTTriangle> DelauneyTriangulate(
        const std::vector<std::shared_ptr<const System>>& systems_vec, double universe_width)
    {
        // ensure a useful list of systems was passed...
        if (systems_vec.empty()) {
            ErrorLogger() << "Attempted to run Delauney Triangulation on empty array of systems";
            return std::list<Delauney::DTTriangle>();
        }


        // extract systems positions from system objects.
        std::vector<Delauney::DTPoint> points_vec;
        points_vec.reserve(systems_vec.size() + 3);
        for (auto& system : systems_vec)
            points_vec.push_back({system->X(), system->Y()});

        // entries in points_vec correspond to entries in \a systems_vec
        // so that the index of an item in systems_vec will have a
        // corresponding points at that index in points_vec


        // add points for covering triangle. the point positions should be big
        // enough to form a triangle that encloses all the points in points_vec
        // (or at least one whose circumcircle covers all points)
        points_vec.push_back({-1.0, -1.0});
        points_vec.push_back({2.0 * (universe_width + 1.0), -1.0});
        points_vec.push_back({-1.0, 2.0 * (universe_width + 1.0)});


        // initialize triangle_list.
        // add last three points into the first triangle, the "covering triangle"
        std::list<Delauney::DTTriangle> triangle_list;
        int num_points_in_vec = points_vec.size();
        triangle_list.push_front({num_points_in_vec - 1, num_points_in_vec - 2,
                                  num_points_in_vec - 3, points_vec});

        if (points_vec.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
            ErrorLogger() << "Attempted to run Delauney Triangulation on " << points_vec.size()
                          << " points.  The limit is " << std::numeric_limits<int>::max();
            return std::list<Delauney::DTTriangle>();
        }

        // loop through points generated from systems, excluding the final
        // 3 points added for the covering triangle
        for (std::size_t n = 0; n < points_vec.size() - 3; n++) {
            // list of indices in vector of points extracted from removed
            // triangles that need to be retriangulated
            std::list<Delauney::SortValInt> point_idx_list;

            const auto& cur_point = points_vec.at(n);

            // check each triangle to see if the current point lies in its
            // circumcircle.  if so, delete the triangle and add its vertices to a list
            auto cur_tri_it = triangle_list.begin();
            while (cur_tri_it != triangle_list.end()) {
                // get current triangle
                Delauney::DTTriangle& tri = *cur_tri_it;

                // check if point to be added to triangulation is within the
                // circumcircle for the current triangle
                if (!tri.PointInCircumCircle(cur_point)) {
                    // point not in circumcircle for this triangle.
                    // go next triangle in list
                    ++cur_tri_it;
                    continue;
                }

                // point is in circumcircle for this triangle.
                // insert the triangle's vertices' indices into the
                // list.  add in sorted position based on angle of direction
                // to current point n being inserted.  don't add if doing so
                // would duplicate an index already in the list
                for (int tri_vert_idx : tri.Verts()) {

                    // get sorting value to order points clockwise
                    // circumferentially around point n

                    // vector from point n to current point
                    double vx = points_vec[tri_vert_idx].x - points_vec[n].x;
                    double vy = points_vec[tri_vert_idx].y - points_vec[n].y;
                    double mag = std::sqrt(vx*vx + vy*vy);
                    // normalize
                    vx /= mag;
                    vy /= mag;

                    // dot product with (0, 1) is vy, magnitude of cross
                    // product is vx this gives a range of "sort value"
                    // from -2 to 2, around the circle
                    double sort_value = (vx >= 0) ? (vy + 1) : (-vy - 1);

                    // iterate through list, finding insert spot and
                    // verifying uniqueness (or add if list is empty)
                    auto idx_list_it = point_idx_list.begin();
                    if (idx_list_it == point_idx_list.end()) {
                        // list is empty
                        point_idx_list.push_back({tri_vert_idx, sort_value});

                    } else {
                        while (idx_list_it != point_idx_list.end()) {
                            if (idx_list_it->num == tri_vert_idx)
                                break;
                            if (idx_list_it->sortVal > sort_value) {
                                point_idx_list.insert(idx_list_it, {tri_vert_idx, sort_value});
                                break;
                            }
                            ++idx_list_it;
                        }
                        if (idx_list_it == point_idx_list.end()) {
                            // point wasn't added, so should go at end
                            point_idx_list.push_back({tri_vert_idx, sort_value});
                        }
                    }
                } // end for c

                // remove current triangle from list of triangles
                cur_tri_it = triangle_list.erase(cur_tri_it);
            } // end while


            // add triangle for last and first points and n
            triangle_list.push_front(
                {static_cast<int>(n), (point_idx_list.front()).num, (point_idx_list.back()).num, points_vec});


            // go through list of points, making new triangles out of them
            auto idx_list_it = point_idx_list.begin();
            int num = idx_list_it->num;
            ++idx_list_it;
            while (idx_list_it != point_idx_list.end()) {
                int num2 = num;
                num = idx_list_it->num;

                triangle_list.push_front({static_cast<int>(n), num2, num, points_vec});

                ++idx_list_it;
            } // end while

        } // end for

        DebugLogger() << "DelauneyTriangulate generated list of "
                      << triangle_list.size() << " triangles";

        return triangle_list;
    }
}

namespace {
    int IntSetMapSizeCount(const std::map<int, std::set<int>>& in) {
        int retval{0};
        for (const auto& entry : in)
            retval += entry.second.size();
        return retval;
    }

    /** Used by GenerateStarlanes.  Determines if two systems are connected by
      * maxLaneJumps or less edges on graph. */
    bool ConnectedWithin(int system1, int system2, int maxLaneJumps,
                         const std::map<int, std::set<int>>& system_lanes)
    {
        // check for simple cases for quick termination
        if (system1 == system2) return true; // system is always connected to itself
        if (0 == maxLaneJumps) return false; // no system is connected to any other system by less than 1 jump

        if (!system_lanes.contains(system1)) return false;     // start system not in lanes map
        if (!system_lanes.contains(system2)) return false;     // destination system not in lanes map

        if (system_lanes.at(system1).empty()) return false; // no lanes out of start system
        if (system_lanes.at(system2).empty()) return false; // no lanes out of destination system

        // list of indices of systems that are accessible from previously visited systems.
        // when a new system is found to be accessible, it is added to the back of the
        // list.  the list is iterated through from front to back to find systems
        // to examine
        std::vector<int> accessibleSystemsList;
        accessibleSystemsList.reserve(system_lanes.size());

        // Map from system_id to the number of starlane jumps away from system1
        // that sytsem is. This indicates the depth of the search, which allows
        // the serch to terminate after searching to the depth of maxLaneJumps
        // without finding system2
        boost::container::flat_map<int, int> accessibleSystemsMap;
        accessibleSystemsMap.reserve(system_lanes.size());
        for (const auto sys : system_lanes | range_keys)
            accessibleSystemsMap.emplace_hint(accessibleSystemsMap.end(), sys, -1);

        static constexpr int mapped_type_max = std::numeric_limits<decltype(accessibleSystemsMap)::mapped_type>::max();
        maxLaneJumps = std::min(maxLaneJumps, mapped_type_max);

        // add starting system to list and set of accessible systems
        accessibleSystemsList.push_back(system1);
        accessibleSystemsMap[system1] = 0;


        // loop through visited systems
        for (std::size_t idx = 0; idx < accessibleSystemsList.size(); ++idx) {
            auto cur_sys_id = accessibleSystemsList[idx];
            // check that iteration hasn't reached maxLaneJumps levels deep, which would
            // mean that system2 isn't within maxLaneJumps starlane jumps of system1
            auto curDepth = accessibleSystemsMap[cur_sys_id];
            if (curDepth >= maxLaneJumps) return false;

            // get set of starlanes for this system
            for (auto curLaneDest : system_lanes.at(cur_sys_id)) {
                // check for goal
                if (curLaneDest == system2) return true;

                auto& cur_lane_dest_depth = accessibleSystemsMap[curLaneDest];
                if (cur_lane_dest_depth < 0) {
                    // this lane was not yet considered, so update its depth in map and add to list ot consider
                    accessibleSystemsList.push_back(curLaneDest);
                    cur_lane_dest_depth = curDepth + 1;
                }
            }
        }
        return false; // default
    }

    /** Removes lanes from passed graph that are angularly too close to
      * each other. */
    void CullAngularlyTooCloseLanes(double max_lane_uvect_dot_product,
                                    std::map<int, std::set<int>>& system_lanes,
                                    const std::map<int, std::shared_ptr<const System>>& systems)
    {
        // 2 component vector and vect + magnitude typedefs
        typedef std::pair<std::pair<double, double>, double> VectAndMagTypeQQ;

        std::set<std::pair<int, int>> lanesToRemoveSet;  // start and end stars of lanes to be removed in final step...

        // make sure data is consistent
        if (system_lanes.size() != systems.size()) {
            ErrorLogger() << "CullAngularlyTooCloseLanes got different size vectors of lane sets and systems.  Doing nothing.";
            return;
        }

        if (systems.size() < 3) return;  // nothing worth doing for less than three systems

        //DebugLogger() << "Culling Too Close Angularly Lanes";

        // loop through systems
        for (const auto& entry : systems) {
            // can't have pairs of lanes departing from a system if that system
            // has less than two lanes
            if (system_lanes.at(entry.first).size() < 2)
                continue;

            // get position of current system (for use in calculated vectors)
            auto startX = entry.second->X();
            auto startY = entry.second->Y();
            auto cur_sys_id = entry.first;

            /** componenets of vectors of lanes of current system, indexed by destination system number */
            std::map<int, VectAndMagTypeQQ> laneVectsMap;

            // get unit vectors for all lanes of this system
            auto laneSetIter1 = system_lanes[cur_sys_id].begin();
            while (laneSetIter1 != system_lanes[cur_sys_id].end()) {
                // get destination for this lane
                auto dest1 = *laneSetIter1;
                // get vector to this lane destination
                auto vectX1 = systems.at(dest1)->X() - startX;
                auto vectY1 = systems.at(dest1)->Y() - startY;
                // normalize
                auto mag1 = std::sqrt(vectX1 * vectX1 + vectY1 * vectY1);
                vectX1 /= mag1;
                vectY1 /= mag1;

                // store lane in map of lane vectors
                laneVectsMap.emplace(dest1, VectAndMagTypeQQ{{vectX1, vectY1}, mag1});

                ++laneSetIter1;
            }

            // iterate through lanes of cur_sys_id
            laneSetIter1 = system_lanes[cur_sys_id].begin();
            ++laneSetIter1;  // start at second, since iterators are used in pairs, and starting both at the first wouldn't be a valid pair
            while (laneSetIter1 != system_lanes[cur_sys_id].end()) {
                // get destination of current starlane
                const auto& dest1 = *laneSetIter1;

                auto lane1{(cur_sys_id < dest1) ?
                    std::pair{cur_sys_id, dest1} : std::pair{dest1, cur_sys_id}};

                // check if this lane has already been added to the set of lanes to remove
                if (lanesToRemoveSet.contains(lane1)) {
                    ++laneSetIter1;
                    continue;
                }

                // extract data on starlane vector...
                auto laneVectsMapIter = laneVectsMap.find(dest1);
                assert(laneVectsMapIter != laneVectsMap.end());
                const auto& [temp_vec, mag1] = laneVectsMapIter->second;
                const auto& [vectX1, vectY1] = temp_vec;

                // iterate through other lanes of cur_sys_id, in order
                // to get all possible pairs of lanes
                auto laneSetIter2 = system_lanes[cur_sys_id].begin();
                while (laneSetIter2 != laneSetIter1) {
                    auto dest2 = *laneSetIter2;

                    std::pair<int, int> lane2;
                    if (cur_sys_id < dest2)
                        lane2 = {cur_sys_id, dest2};
                    else
                        lane2 = {dest2, cur_sys_id};

                    // check if this lane has already been added to the
                    // set of lanes to remove
                    if (lanesToRemoveSet.contains(lane2)) {
                        ++laneSetIter2;
                        continue;
                    }

                    // extract data on starlane vector...
                    laneVectsMapIter = laneVectsMap.find(dest2);
                    assert(laneVectsMapIter != laneVectsMap.end());
                    const auto& [temp_vect2, mag2] = laneVectsMapIter->second;
                    const auto& [vectX2, vectY2] = temp_vect2;

                    // find dot product
                    auto dotProd = vectX1 * vectX2 + vectY1 * vectY2;

                    // if dotProd is big enough, then lanes are too close angularly
                    // thus one needs to be removed.
                    if (dotProd > max_lane_uvect_dot_product) {
                        // preferentially remove the longer lane
                        if (mag1 > mag2) {
                            lanesToRemoveSet.insert(lane1);
                            break;  // don't need to check any more lanes against lane1, since lane1 has been removed
                        } else {
                            lanesToRemoveSet.insert(lane2);
                        }
                    }

                    ++laneSetIter2;
                }

                ++laneSetIter1;
            }
        }

        // iterate through set of lanes to remove, and remove them in turn...
        auto lanes_to_remove_it = lanesToRemoveSet.begin();
        auto lanes_to_remove_end = lanesToRemoveSet.end();
        while (lanes_to_remove_it != lanes_to_remove_end) {
            const auto& [l_start, l_end] = *lanes_to_remove_it;

            system_lanes[l_start].erase(l_end);
            system_lanes[l_end].erase(l_start);

            // check that removing lane hasn't disconnected systems
            if (!ConnectedWithin(l_start, l_end, systems.size(), system_lanes)) {
                // they aren't connected... reconnect them
                system_lanes[l_start].insert(l_end);
                system_lanes[l_end].insert(l_start);
            }

            ++lanes_to_remove_it;
        }
    }

    /** Removes lanes from passed graph that are too long. */
    void CullTooLongLanes(double max_lane_length,
                          std::map<int, std::set<int>>& system_lanes,
                          const std::map<int, std::shared_ptr<const System>>& systems)
    {
        DebugLogger() << "CullTooLongLanes max lane length: " << max_lane_length
                      << "  potential lanes: " << IntSetMapSizeCount(system_lanes)
                      << "  systems: " << systems.size();

        ScopedTimer timer("CullTooLongLanes", std::chrono::milliseconds{1});

        // map, indexed by lane length, of start and end stars of lanes to be removed
        std::multimap<double, std::pair<int, int>, std::greater<double>> lanes_to_remove;

        // make sure data is consistent
        if (system_lanes.size() != systems.size())
            return;
        // nothing worth doing for less than two systems (no lanes!)
        if (systems.size() < 2)
            return;

        // get squared max lane lenth, so as to eliminate the need to take square roots of lane lenths...
        const double max_lane_length2 = max_lane_length*max_lane_length;
        std::size_t total_lanes_count = 0;

        // loop through systems and lanes to find long lanes
        for (const auto& [cur_sys_id, cur_system] : systems) {
            // get position of current system (for use in calculating vector)
            double startX = cur_system->X();
            double startY = cur_system->Y();

            // iterate through all lanes in system, checking lengths and
            // marking to be removed if necessary
            for (const auto dest_sys_id : system_lanes[cur_sys_id]) {
                total_lanes_count++;

                // convert start and end into ordered pair to represent lane
                std::pair<int, int> lane;
                if (cur_sys_id < dest_sys_id)
                    lane = {cur_sys_id, dest_sys_id};
                else
                    lane = {dest_sys_id, cur_sys_id};

                // get vector to this lane destination
                const auto& dest_system = systems.at(dest_sys_id);
                auto vectX = dest_system->X() - startX;
                auto vectY = dest_system->Y() - startY;

                // compare magnitude of vector to max allowed
                double lane_length2 = vectX*vectX + vectY*vectY;
                if (lane_length2 > max_lane_length2) {
                    // lane is too long!  mark it to be removed
                    TraceLogger() << "CullTooLongLanes wants to remove lane of length "
                                  << std::sqrt(lane_length2)
                                  << " between systems with ids: "
                                  << cur_sys_id << " and " << dest_sys_id;
                    lanes_to_remove.emplace(lane_length2, lane);
                }
            }
        }

        DebugLogger() << "CullTooLongLanes identified " << lanes_to_remove.size()
                      << " long lanes to possibly remove of " << total_lanes_count << " lanes total";

        std::size_t total_checked_lanes = 0;
        std::size_t removable_lanes = 0;

        // TODO: Consider re-implementing this as a minimal spanning tree
        //       and immediately remove any lane not in that tree, and
        //       thus avoid having to check connectivity for each
        //       potential lane removal

        // Iterate through set of lanes to remove, and remove them in turn.
        // Lanes are sorted by length, so are processed longest-first
        for (const auto& [dist, lane] : lanes_to_remove) {
            if (!system_lanes[lane.first].contains(lane.second) ||
                !system_lanes[lane.second].contains(lane.first))
            { continue; }

            // remove lane
            system_lanes[lane.first].erase(lane.second);
            system_lanes[lane.second].erase(lane.first);

            total_checked_lanes++;

            // if removing lane has disconnected systems, reconnect them
            if (!ConnectedWithin(lane.first, lane.second, systems.size(), system_lanes)) {
                system_lanes[lane.first].insert(lane.second);
                system_lanes[lane.second].insert(lane.first);
                TraceLogger() << "CullTooLongLanes can't remove lane between systems with ids: "
                              << lane.first << " and " << lane.second
                              << " because they would then be disconnected (more than "
                              << systems.size() << " jumps apart)";
            } else {
                removable_lanes++;
                TraceLogger() << "CullTooLongLanes removing lane between systems with ids: "
                              << lane.first << " and " << lane.second;
            }
        }

        DebugLogger() << "CullTooLongLanes left with " << IntSetMapSizeCount(system_lanes)
                      << " lanes.  " << total_checked_lanes << " were checked and "
                      << removable_lanes << " were removable and "
                      << total_checked_lanes - removable_lanes << " were not removable";
    }
}

void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length,
                       Universe& universe, const EmpireManager& empires)
{
    DebugLogger() << "GenerateStarlanes  max jumps b/w sys: " << max_jumps_between_systems
                  << "  max lane length: " << max_starlane_length;

    std::vector<int> triangle_vertices;  // indices of stars that form vertices of a triangle

    // array of set to store final, included starlanes for each star
    std::map<int, std::set<int>> system_lanes;

    // array of set to store possible starlanes for each star,
    // as extracted form triangulation
    std::map<int, std::set<int>> potential_system_lanes;

    // get systems
    auto sys_rng = std::as_const(universe.Objects()).all<System>();
    std::vector<std::shared_ptr<const System>> sys_vec{sys_rng.begin(), sys_rng.end()};
    std::map<int, std::shared_ptr<const System>> sys_map;
    std::transform(sys_rng.begin(), sys_rng.end(), std::inserter(sys_map, sys_map.end()),
                   [](const std::shared_ptr<const System>& p) { return std::pair{p->ID(), p}; });

    // generate lanes
    if (GetGameRules().Get<bool>("RULE_STARLANES_EVERYWHERE")) {
        // if the lanes everywhere rules is true, add starlanes to every star
        // to every other star...
        for (const auto& sys1 : sys_vec) {
            for (const auto& sys2 : sys_vec) {
                if (sys1->ID() == sys2->ID())
                    continue;
                potential_system_lanes[sys1->ID()].insert(sys2->ID());
            }
        }
        DebugLogger() << "Generated " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes between all system pairs";
        CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map);
        DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling";
        system_lanes = potential_system_lanes;

    } else {
        // pass systems to Delauney Triangulation routine, getting array of triangles back
        auto triangle_list = Delauney::DelauneyTriangulate(sys_vec, universe.UniverseWidth());
        if (triangle_list.empty()) {
            ErrorLogger() << "Got blank list of triangles from Triangulation.";
            return;
        }

        // extract triangles from list, add edges to sets of potential starlanes
        // for each star (in array)
        while (!triangle_list.empty()) {
            // extract indices for the corners of the triangles, which should
            // correspond to indices in sys_vec, except that there can also be
            // indices up to sys_vec.size() + 2, which correspond to extra points
            // used by the algorithm
            const auto& [s1, s2, s3] = triangle_list.front().Verts();

            if (s1 < 0 || s2 < 0 || s3 < 0) {
                ErrorLogger() << "Got negative vector indices from DelauneyTriangulate!";
                triangle_list.pop_front();
                continue;
            }

            // get system ids for ends of lanes from the sys_vec indices
            int sys1_id = INVALID_OBJECT_ID;
            if (static_cast<std::size_t>(s1) < sys_vec.size())
                sys1_id = sys_vec.at(s1)->ID();
            int sys2_id = INVALID_OBJECT_ID;
            if (static_cast<std::size_t>(s2) < sys_vec.size())
                sys2_id = sys_vec.at(s2)->ID();
            int sys3_id = INVALID_OBJECT_ID;
            if (static_cast<std::size_t>(s3) < sys_vec.size())
                sys3_id = sys_vec.at(s3)->ID();


            // add starlanes to list of potential starlanes for each star,
            // making sure each pair involves only valid indices into sys_vec
            if (sys1_id != INVALID_OBJECT_ID && sys2_id != INVALID_OBJECT_ID) {
                potential_system_lanes[sys1_id].insert(sys2_id);
                potential_system_lanes[sys2_id].insert(sys1_id);
            }
            if (sys2_id != INVALID_OBJECT_ID && sys3_id != INVALID_OBJECT_ID) {
                potential_system_lanes[sys2_id].insert(sys3_id);
                potential_system_lanes[sys3_id].insert(sys2_id);
            }
            if (sys3_id != INVALID_OBJECT_ID && sys1_id != INVALID_OBJECT_ID) {
                potential_system_lanes[sys3_id].insert(sys1_id);
                potential_system_lanes[sys1_id].insert(sys3_id);
            }

            triangle_list.pop_front();
        }

        DebugLogger() << "Extracted " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes from triangulation";
        CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map);
        DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling";
        CullAngularlyTooCloseLanes(0.98, potential_system_lanes, sys_map);
        DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after angular culling";

        system_lanes = potential_system_lanes;

        // attempt removing lanes, but don't do so if it would make the systems
        // the lane connects too far apart
        for (const auto& [sys1_id, sys1_lanes] : potential_system_lanes) {
            for (auto& sys2_id : sys1_lanes) {
                if (sys2_id <= sys1_id)
                    continue; // skip lanes that should already have been checked since lanes exist in both directions

                // try removing lane
                system_lanes[sys1_id].erase(sys2_id);
                system_lanes[sys2_id].erase(sys1_id);

                if (!ConnectedWithin(sys1_id, sys2_id, max_jumps_between_systems, system_lanes)) {
                    // lane removal was a bad idea.  restore it
                    system_lanes[sys1_id].insert(sys2_id);
                    system_lanes[sys2_id].insert(sys1_id);
                }
            }
        }
    }

    // add the starlane to the stars
    for (auto& sys : universe.Objects().all<System>()) {
        const auto& sys_lanes = system_lanes[sys->ID()];
        for (auto& lane_end_id : sys_lanes)
            sys->AddStarlane(lane_end_id);
    }

    DebugLogger() << "Initializing System Graph";
    universe.InitializeSystemGraph(empires);
}

void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map) {
    TraceLogger(effects) << "SetActiveMetersToTargetMaxCurrentValues";
    // check for each pair of meter types.  if both exist, set active
    // meter current value equal to target meter current value.
    for (const auto& object : object_map.all()) {
        for (auto& entry : AssociatedMeterTypes()) {
            if (Meter* meter = object->GetMeter(entry.first)) {
                if (Meter* targetmax_meter = object->GetMeter(entry.second))
                    meter->SetCurrent(targetmax_meter->Current());
            }
        }
    }
}

void SetNativePopulationValues(ObjectMap& object_map) {
    for (const auto& object : object_map.all()) {
        Meter* meter = object->GetMeter(MeterType::METER_POPULATION);
        Meter* targetmax_meter = object->GetMeter(MeterType::METER_TARGET_POPULATION);
        // only applies to unowned planets
        if (meter && targetmax_meter && object->Unowned()) {
            double r = RandZeroToOne();
            double factor = (0.1 < r) ? r : 0.1;
            meter->SetCurrent(targetmax_meter->Current() * factor);
        }
    }
}

bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name,
                        ScriptingContext& context)
{
    // get home planet and system, check if they exist
    auto home_planet = context.ContextObjects().get<Planet>(planet_id);
    if (!home_planet)
        return false;
    auto home_system = context.ContextObjects().get<System>(home_planet->SystemID());
    if (!home_system)
        return false;

    DebugLogger() << "SetEmpireHomeworld: setting system " << home_system->ID()
                  << " (planet " <<  home_planet->ID() << ") to be home system for empire " << empire->EmpireID();

    // get species, check if it exists
    auto species = context.species.GetSpecies(species_name);
    if (!species) {
        ErrorLogger() << "SetEmpireHomeworld: couldn't get species \""
                      << species_name << "\" to set with homeworld id " << home_planet->ID();
        return false;
    }

    // set homeword's planet type to the preferred type for this species
    const auto& spte = species->PlanetEnvironments();
    if (!spte.empty()) {
        // invert map from planet type to environments to map from
        // environments to type, sorted by environment
        std::map<PlanetEnvironment, PlanetType> sept;
        for (const auto& entry : spte)
            sept[entry.second] = entry.first;
        // assuming enum values are ordered in increasing goodness...
        PlanetType preferred_planet_type = sept.rbegin()->second;

        // both the current as well as the original type need to be set to the preferred type
        home_planet->SetType(preferred_planet_type);
        home_planet->SetOriginalType(preferred_planet_type);
        // set planet size according to planet type
        if (preferred_planet_type == PlanetType::PT_ASTEROIDS)
            home_planet->SetSize(PlanetSize::SZ_ASTEROIDS);
        else if (preferred_planet_type == PlanetType::PT_GASGIANT)
            home_planet->SetSize(PlanetSize::SZ_GASGIANT);
        else
            home_planet->SetSize(PlanetSize::SZ_MEDIUM);
    }

    home_planet->Colonize(empire->EmpireID(), species_name, Meter::LARGE_VALUE, context);
    context.species.AddSpeciesHomeworld(std::move(species_name), home_planet->ID());

    empire->SetCapitalID(home_planet->ID(), context.ContextObjects());
    context.Empires().RefreshCapitalIDs();
    empire->AddExploredSystem(home_planet->SystemID(), BEFORE_FIRST_TURN, context.ContextObjects());

    return true;
}

void InitEmpires(const std::map<int, PlayerSetupData>& player_setup_data, EmpireManager& empires) {
    DebugLogger() << "Initializing " << player_setup_data.size() << " empires";

    // copy empire colour table, so that individual colours can be removed after they're used
    auto colors{EmpireColors()};

    // create empire objects and do some basic initilization for each player
    for (auto& [empire_id, psd] : player_setup_data) {
        // use map key for empire ID so that the calling code can get the
        // correct empire for each player in player_setup_data
        if (empire_id == ALL_EMPIRES)
            ErrorLogger() << "InitEmpires empire id (" << empire_id << ") is invalid";

        const auto& player_name =   psd.player_name;
        auto        empire_colour = psd.empire_color;
        bool        authenticated = psd.authenticated;

        // validate or generate empire colour
        // ensure no other empire gets auto-assigned this colour automatically
        auto color_it = std::find(colors.begin(), colors.end(), empire_colour);
        if (color_it != colors.end())
            colors.erase(color_it);

        static constexpr EmpireColor CLR_ZERO{{0, 0, 0, 0}};

        // if no colour already set, do so automatically
        if (empire_colour == CLR_ZERO) {
            if (!colors.empty()) {
                // take next colour from list
                empire_colour = colors[0];
                colors.erase(colors.begin());
            } else {
                // as a last resort, make up a colour
                empire_colour = {{static_cast<uint8_t>(RandInt(0, 255)),
                                  static_cast<uint8_t>(RandInt(0, 255)),
                                  static_cast<uint8_t>(RandInt(0, 255)),
                                  255}};
            }
        }

        // set generic default empire name
        std::string empire_name = UserString("EMPIRE") + std::to_string(empire_id);

        DebugLogger() << "Universe::InitEmpires creating new empire" << " with ID: " << empire_id
                      << " for player: " << player_name << " in team: " << psd.starting_team;

        // create new Empire object through empire manager
        empires.CreateEmpire(empire_id, std::move(empire_name), player_name,
                             empire_colour, authenticated);
    }

    empires.ResetDiplomacy();

    for (auto& [player_id1, psd1] : player_setup_data) {
        if (psd1.starting_team < 0)
            continue;

        for (auto& [player_id2, psd2] : player_setup_data) {
            if (player_id1 == player_id2)
                continue;

            if (psd1.starting_team != psd2.starting_team)
                continue;

            empires.SetDiplomaticStatus(player_id1, player_id2, DiplomaticStatus::DIPLO_ALLIED);
        }
    }
}