-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.c
More file actions
1884 lines (1459 loc) · 56.7 KB
/
Copy pathmain.c
File metadata and controls
1884 lines (1459 loc) · 56.7 KB
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
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright (C) 2025 Liam Ralph
https://github.com/liam-ralph
This program, including this file, is licensed under the
GNU General Public License v3.0 (GNU GPLv3), with one exception.
See LICENSE or this project's source for more information.
Project Source: https://github.com/liam-ralph/biomegen
result.png, the output of this program, is licensed under The Unlicense.
See LICENSE_PNG or this project's source for more information.
BiomeGen, a terminal application for generating png maps.
*/
// Includes
#define _POSIX_C_SOURCE 199309L // Needed for CLOCK_REALTIME
#include <limits.h>
#include <math.h>
#include <png.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
// Definitions
#define ANSI_GREEN "\033[38;5;2m"
#define ANSI_BLUE "\033[38;5;4m"
#define ANSI_RESET "\033[0m"
// Structs
typedef struct {
short x;
short y;
char type;
/*
I = Ice
s = Shallow Water
W = Water
d = Deep Water
R = Rock
D = Desert
J = Jungle
F = Forest
P = Plains
T = Taiga
S = Snow
w = Water Forced
L = Land
l = Land Origin
*/
} Dot;
typedef struct Node {
int coord[2];
int index;
struct Node *left;
struct Node *right;
} Node;
// General Functions
// (Alphabetical order)
/**
* Get a sanitized integer input from the user between MIN and MAX,
* both inclusive.
*/
int get_int(const int min, const int max) {
int result;
while (true) {
int scanf_return = scanf("%d", &result);
while (getchar() != '\n');
if (scanf_return != 1 || result < min || result > max) {
printf("Input must be an integer between %d and %d (both inclusive).\n", min, max);
} else {
break;
}
}
return result;
}
/**
* Return the sum of a list of integers.
*/
int sum_list_int(const int list[], const int list_len) {
int sum = 0;
for (int i = 0; i < list_len; i++) {
sum += list[i];
}
return sum;
}
/**
* Return the sum of a list of floats.
*/
float sum_list_float(const float list[], const int list_len) {
float sum = 0;
for (int i = 0; i < list_len; i++) {
sum += list[i];
}
return sum;
}
void quicksort_recursive(int *coords, const int low, const int high, const int width) {
if (low < high) {
/*
Pivot value is the value at coords[high]. Every value less than pivot
will be to the left of wherever pivot ends up. Remember: a coord is
{x, y, index (of equivalent dot in dots)}. The array isn't 2D as it
requires malloc.
*/
int pivot = coords[high * 3 + 1] * width + coords[high * 3];
// Move Smaller Elements to the Left
int i = low - 1;
for (int ii = low; ii <= high - 1; ii++) {
if (coords[ii * 3 + 1] * width + coords[ii * 3] < pivot) {
i++;
const int temp[3] = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
coords[i * 3] = coords[ii * 3];
coords[i * 3 + 1] = coords[ii * 3 + 1];
coords[i * 3 + 2] = coords[ii * 3 + 2];
coords[ii * 3] = temp[0];
coords[ii * 3 + 1] = temp[1];
coords[ii * 3 + 2] = temp[2];
}
}
// Move Pivot to After Smaller Elements
i++;
const int temp[3] = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
coords[i * 3] = coords[high * 3];
coords[i * 3 + 1] = coords[high * 3 + 1];
coords[i * 3 + 2] = coords[high * 3 + 2];
coords[high * 3] = temp[0];
coords[high * 3 + 1] = temp[1];
coords[high * 3 + 2] = temp[2];
// Decide Whether Recursion is Needed
/*
Cases:
i == med_index, the median is in the correct spot, no more sorting
i > med_index, median is in left section, left section needs sorting
i < med_index, median in right section, sort right section
*/
quicksort_recursive(coords, low, i - 1, width);
quicksort_recursive(coords, i + 1, high, width);
}
}
// KDTree Functions
/**
* Sort the given COORDS until the median index is correctly sorted. In other
* words, median index will be correct, everything before median index will be
* less than the value at median index, and everything after will be greater.
* AXIS determines which value of a coordinate is its value (x or y) to sort
* based on. HIGH and LOW determine the sorting bounds for a given level of
* recursion.
*/
void median_sort_recursive(
int *coords, const int low, const int high, const int axis, const int med_index
) {
if (low < high) {
/*
Pivot value is the value at coords[high]. Every value less than pivot
will be to the left of wherever pivot ends up. Remember: a coord is
{x, y, index (of equivalent dot in dots)}. The array isn't 2D as it
requires malloc.
*/
int pivot = coords[high * 3 + axis];
// Move Smaller Elements to the Left
int i = low - 1;
for (int ii = low; ii <= high - 1; ii++) {
if (coords[ii * 3 + axis] < pivot) {
i++;
const int temp[3] = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
coords[i * 3] = coords[ii * 3];
coords[i * 3 + 1] = coords[ii * 3 + 1];
coords[i * 3 + 2] = coords[ii * 3 + 2];
coords[ii * 3] = temp[0];
coords[ii * 3 + 1] = temp[1];
coords[ii * 3 + 2] = temp[2];
}
}
// Move Pivot to After Smaller Elements
i++;
const int temp[3] = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
coords[i * 3] = coords[high * 3];
coords[i * 3 + 1] = coords[high * 3 + 1];
coords[i * 3 + 2] = coords[high * 3 + 2];
coords[high * 3] = temp[0];
coords[high * 3 + 1] = temp[1];
coords[high * 3 + 2] = temp[2];
// Decide Whether Recursion is Needed
/*
Cases:
i == med_index, the median is in the correct spot, no more sorting
i > med_index, median is in left section, left section needs sorting
i < med_index, median in right section, sort right section
*/
if (i > med_index) {
median_sort_recursive(coords, low, i - 1, axis, med_index);
} else if (i < med_index) {
median_sort_recursive(coords, i + 1, high, axis, med_index);
}
}
}
/**
* Build a KDTree from COORDS, and return the root node. Ensures the KDTree is
* built with the lowest possible depth for maximum efficiency when querying the
* tree. Every recursion creates one dot and calls this function to insert its
* children from an array of possible dots. DEPTH should be 0. COORDS should be
* of length NUM_COORDS * 3.
*/
Node *build_recursive(int *coords, const int num_coords, const int depth) {
const int med_pos = num_coords / 2;
// Sort Coords Around Median Position
/*
Median coord will be in correct place, everything less will be to the left,
everything else to the right.
*/
median_sort_recursive(coords, 0, num_coords - 1, depth % 2, med_pos);
// Add Median Node to Tree
// Every recursion adds one node
Node *node = malloc(sizeof(Node));
node->coord[0] = coords[med_pos * 3];
node->coord[1] = coords[med_pos * 3 + 1];
node->index = coords[med_pos * 3 + 2];
node->left = NULL;
node->right = NULL;
// Decide whether node will have a left child
const int num_coords_left = med_pos;
if (num_coords_left > 0) {
int *coords_left = malloc(num_coords_left * 3 * sizeof(int));
for (int i = 0; i < med_pos; i++) {
coords_left[i * 3] = coords[i * 3];
coords_left[i * 3 + 1] = coords[i * 3 + 1];
coords_left[i * 3 + 2] = coords[i * 3 + 2];
}
node->left = build_recursive(coords_left, num_coords_left, depth + 1);
free(coords_left);
// Decide whether node will have a right child
const int num_coords_right = num_coords - med_pos - 1;
if (num_coords_right > 0) {
int *coords_right = malloc(num_coords_right * 3 * sizeof(int));
for (int i = 0; i < num_coords_right; i++) {
const int index = i + med_pos + 1;
coords_right[i * 3] = coords[index * 3];
coords_right[i * 3 + 1] = coords[index * 3 + 1];
coords_right[i * 3 + 2] = coords[index * 3 + 2];
}
node->right = build_recursive(coords_right, num_coords_right, depth + 1);
free(coords_right);
}
}
// Return node to place it within the tree
return node;
}
/**
* Query the KDTree to modify MIN_DIST, the distance to the nearest node. When
* INDEX_PTR is not null, it stores the index of the nearest node, which
* corresponds to the index of the dot it was created from. Calls itself
* recursively to query children of NODE. DEPTH should be 0 when NODE is a root
* node.
*/
void query_recursive(
Node *node, const int coord[2], const int depth, int *index_ptr, int *min_dist_ptr
) {
// Calculate Distance
const int diff_x = node->coord[0] - coord[0];
const int diff_y = node->coord[1] - coord[1];
// Distance is squared for efficiency
const int dist = diff_x * diff_x + diff_y * diff_y;
// Update Minimum Distance and Index Pointers
if (dist < *min_dist_ptr) {
*min_dist_ptr = dist;
if (index_ptr != NULL) {
*index_ptr = node->index;
} else if (dist < 15 * 15) {
return; // Shortcut specifically for water biome generation
}
}
// Decide Whether Recursion is Needed
const int axis = depth % 2;
const int dist_line = node->coord[axis] - coord[axis];
const int dist_sq = dist_line * dist_line;
// Whether distance to splitting line is less than max_dist
if (node->left != NULL && (dist_sq < *min_dist_ptr || coord[axis] < node->coord[axis])) {
/*
Recursion only if coord is close enough to dividing line, or coord would
be inside bounds of left child
*/
query_recursive(node->left, coord, depth + 1, index_ptr, min_dist_ptr);
}
if (node->right != NULL && (dist_sq < *min_dist_ptr || coord[axis] >= node->coord[axis])) {
query_recursive(node->right, coord, depth + 1, index_ptr, min_dist_ptr);
}
}
/**
* Query the KDTree to modify DISTS, the distances of the nearest DISTS_LEN
* points to COORD. Recursively navigates down the KDTree, editing DISTS and
* the value at MAX_DIST_PTR whenever it finds a node whose distance is less
* than the value at MAX_DIST_PTR. All distances are squared for efficiency.
* DEPTH should be 0 when NODE is a root node.
*/
void query_dist_recursive(
Node *node, const int coord[2], const int depth,
int dists[], const int dists_len
) {
// Calculate Distance
const int diff_x = node->coord[0] - coord[0];
const int diff_y = node->coord[1] - coord[1];
const int dist = diff_x * diff_x + diff_y * diff_y; // Squared distance
// Update Distances List
if (dist < dists[dists_len - 1] && dist != 0) {
for (int i = dists_len - 1; i >= 0; i--) {
if (i == 0 || dist >= dists[i - 1]) {
// Found insertion position, insert and break
dists[i] = dist;
break;
}
// After insertion position, shift element
dists[i] = dists[i - 1];
}
}
// Decide Whether Recursion is Needed
const int axis = depth % 2;
const int dist_line = node->coord[axis] - coord[axis];
const int dist_sq = dist_line * dist_line;
// Whether distance to splitting line is less than max_dist
if (node->left != NULL && (dist_sq < dists[dists_len - 1] || coord[axis] < node->coord[axis])) {
/*
Recursion only if coord is close enough to dividing line, or coord would
be inside bounds of left child
*/
query_dist_recursive(node->left, coord, depth + 1, dists, dists_len);
}
if (
node->right != NULL && (dist_sq < dists[dists_len - 1] || coord[axis] >= node->coord[axis])
) {
query_dist_recursive(node->right, coord, depth + 1, dists, dists_len);
}
}
/**
* Recursively free NODE and its children.
*/
void free_recursive(Node *node) {
if (node->left != NULL) {
free_recursive(node->left);
}
if (node->right != NULL) {
free_recursive(node->right);
}
free(node);
node = NULL;
}
// Multiprocessing Functions
// (Order of use)
/**
* Set the process's title to "biogen-" + TYPE. If NUM >= 0, then NUM, padded to
* a length of 2 (not including null character) with leading zeros will be added
* as well. The length of TYPE + NUM (as a string) must be at max 9 characters
* (including null character).
*/
void set_process_title(const char type[], const int num) {
// Example: type is "worker", num is 6: "biogen-worker6"
// Concatenate Type
char string[16] = "biogen-";
strncat(string, type, 9);
// Concatenate Num
if (0 <= num && num < 100) {
char num_str[3];
snprintf(num_str, 3, "%02d", num);
strncat(string, num_str, 3);
}
// Set Process Title
#ifdef __linux__
prctl(PR_SET_NAME, string, 0, 0, 0);
#elif BSD || __Apple__
setproctitle(string);
#endif
}
/**
* Track the progress of map generation, and show the progress in the terminal.
* START_TIME is the time at the start of map generation in main(), and the
* other inputs are all shared memory used to track section progress and times.
* Not used in automated inputs mode.
*/
void track_progress(
struct timespec start_time,
_Atomic int *section_progress, int *section_progress_total, float *section_times
) {
char section_names[7][20] = {
"Setup", "Section Generation", "Section Assignment", "Coastline Smoothing",
"Biome Generation", "Image Generation", "Finish"
};
float section_weights[7] = {0.03, 0.01, 0.01, 0.14, 0.04, 0.24, 0.53};
// Used for overall progress bar (e.g. Setup takes ~3% of total time)
while (true) {
// Clear Screen
system("clear");
// Section Progress
struct timespec time_now;
clock_gettime(CLOCK_REALTIME, &time_now);
float time_diff = (float)(time_now.tv_sec - start_time.tv_sec) +
(time_now.tv_nsec - start_time.tv_nsec) / 1000000000.0;
float total_progress = 0.0;
for (int i = 0; i < 7; i++) {
// Calculate Section Progress
float progress_section =
atomic_load(§ion_progress[i]) / (float)section_progress_total[i];
total_progress += progress_section * section_weights[i];
// Check if Section Complete
char color[10];
float section_time;
if (section_times[i] != 0.0) {
strncpy(color, ANSI_GREEN, 10);
section_time = section_times[i]; // Section complete
} else {
strncpy(color, ANSI_BLUE, 10);
if (i == 0 || section_times[i - 1] != 0.0) {
section_time = time_diff - sum_list_float(section_times, 7);
// Section in progress
} else {
section_time = 0.0; // Section hasn't started
}
}
// Print Output for Section Progress
printf(
"%s[%d/7] %-20s%8.2f%% %s",
color, i + 1, section_names[i], progress_section * 100, ANSI_GREEN
); // "[1/7] Setup 100.00% "
int green_bars = (int)round(20 * progress_section);
for (int ii = 0; ii < green_bars; ii++) {
printf("█");
}
printf("%s", ANSI_BLUE);
for (int ii = 0; ii < 20 - green_bars; ii++) {
printf("█");
}
char formatted_time[32];
snprintf(
formatted_time, 32, "%2d:%08.5f", (int)section_time / 60, fmod(section_time, 60.0f)
);
printf("%s%s\n", ANSI_RESET, formatted_time);
}
// Total Progress
char color[10];
float total_time;
if (section_times[7] != 0.0) {
strncpy(color, ANSI_GREEN, 10);
total_time = section_times[7];
} else {
strncpy(color, ANSI_BLUE, 10);
total_time = (float)(time_now.tv_sec - start_time.tv_sec) +
(time_now.tv_nsec - start_time.tv_nsec) / 1000000000.0;
}
printf("%s Total Progress %8.2f%% %s", color, total_progress * 100, ANSI_GREEN);
int green_bars = (int)round(20 * total_progress);
for (int i = 0; i < green_bars; i++) {
printf("█");
}
printf("%s", ANSI_BLUE);
for (int i = 0; i < 20 - green_bars; i++) {
printf("█");
}
char formatted_time[32];
snprintf(formatted_time, 32, "%2d:%08.5f", (int)total_time / 60, fmod(total_time, 60.0f));
printf("%s%s\n", ANSI_RESET, formatted_time);
// Check Exit Status
if (strcmp(color, ANSI_GREEN) == 0) {
break; // All sections done, exit tracking process
}
// Sleep 0.1 Seconds
struct timespec sleep_time;
sleep_time.tv_sec = 0;
sleep_time.tv_nsec = 100000000;
nanosleep(&sleep_time, &sleep_time);
}
}
/**
* Assign sections of the map. Land and water are randomly assigned based on a
* dot's distance from the nearest land origin dot.
*/
void assign_sections(
const int map_resolution, const float island_size,
const int start_index, const int end_index, const int *reg_dots,
Node *origin_tree_root, Dot *dots, _Atomic int *section_progress
) {
srand(time(NULL) + getpid());
int min_dist;
for (int i = start_index; i < end_index; i++) {
// Non-water dots are not included
// Calculate Maximum Distance
if (i != start_index && reg_dots[i * 3 + 1] == reg_dots[(i - 1) * 3 + 1]) {
int min_dist_sq = (int)sqrt(min_dist) + 1 + reg_dots[i * 3] - reg_dots[(i - 1) * 3];
min_dist = min_dist_sq * min_dist_sq;
} else {
min_dist = INT_MAX;
}
// Find Distance to Nearest Origin Dot
// min and dist are squared, sqrt is not done until later
int min_index = 0;
int coord[2] = {reg_dots[i * 3], reg_dots[i * 3 + 1]};
query_recursive(origin_tree_root, coord, 0, &min_index, &min_dist);
// Calculate Chance
float dist = sqrt(min_dist) / sqrt(map_resolution);
float threshold = ((float)(min_index % 20) / 19.0f * 1.5f + 0.25f) * island_size;
int chance = (dist <= threshold) ? 9 : 1;
if (rand() % 10 < chance) {
dots[reg_dots[i * 3 + 2]].type = 'L'; // Land
}
atomic_fetch_add(§ion_progress[2], 1);
}
}
/**
* Smooth map coastlines for a more realistic, aesthetically pleasing map.
* Reassigns land and water dots based on the average distance of the nearest
* COASTLINE_SMOOTHING dots of the same and opposite types.
*/
void smooth_coastlines(
const int coastline_smoothing,
const int *land_dots, const int land_start, const int land_end, Node *land_tree_root,
const int *water_dots, const int water_start, const int water_end, Node *water_tree_root,
const int num_dots, const int num_land_dots, const int num_water_dots,
Dot *dots, _Atomic int *section_progress
) {
int dists_same[coastline_smoothing];
int dists_opp[coastline_smoothing];
// Coastline Smoothing for Land Dots
for (int i = land_start; i < land_end; i++) {
int dot_coord[2] = {land_dots[i * 3], land_dots[i * 3 + 1]};
// Calculate Maximum Distances
bool same_y = (i != land_start && land_dots[i * 3 + 1] == land_dots[(i - 1) * 3 + 1]);
if (same_y) {
const int prev_dot_dist = land_dots[i * 3] - land_dots[(i - 1) * 3];
int min_dist_same = (int)sqrt(dists_same[coastline_smoothing - 1]) + 1 + prev_dot_dist;
// + 1 needed for floating-point errors (ceil didn't work)
min_dist_same *= min_dist_same;
int min_dist_opp = (int)sqrt(dists_opp[coastline_smoothing - 1]) + 1 + prev_dot_dist;
min_dist_opp *= min_dist_opp;
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_same[ii] = min_dist_same;
dists_opp[ii] = min_dist_opp;
}
} else {
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_same[ii] = INT_MAX;
}
}
// Get Nearest Distances for Each Type
long sum_same = 0;
long sum_opp = 0;
query_dist_recursive(land_tree_root, dot_coord, 0, dists_same, coastline_smoothing);
for (int ii = 0; ii < coastline_smoothing; ii++) {
sum_same += dists_same[ii];
}
if (!same_y) {
const int max = (sum_same < INT_MAX) ? sum_same : INT_MAX;
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_opp[ii] = max;
}
}
query_dist_recursive(water_tree_root, dot_coord, 0, dists_opp, coastline_smoothing);
for (int ii = 0; ii < coastline_smoothing; ii++) {
sum_opp += dists_opp[ii];
}
// Change Dot Type if Closer to Opposite Type
if (sum_same > sum_opp) {
dots[land_dots[i * 3 + 2]].type = 'W';
}
atomic_fetch_add(§ion_progress[3], 1);
}
// Coastline Smoothing for Water Dots
for (int i = water_start; i < water_end; i++) {
int dot_coord[2] = {water_dots[i * 3], water_dots[i * 3 + 1]};
// Calculate Maximum Distances
bool same_y = (i != water_start && water_dots[i * 3 + 1] == water_dots[(i - 1) * 3 + 1]);
if (same_y) {
const int prev_dot_dist = water_dots[i * 3] - water_dots[(i - 1) * 3];
int min_dist_same = (int)sqrt(dists_same[coastline_smoothing - 1]) + 1 + prev_dot_dist;
min_dist_same *= min_dist_same;
int min_dist_opp = (int)sqrt(dists_opp[coastline_smoothing - 1]) + 1 + prev_dot_dist;
min_dist_opp *= min_dist_opp;
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_same[ii] = min_dist_same;
dists_opp[ii] = min_dist_opp;
}
} else {
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_same[ii] = INT_MAX;
}
}
// Get Nearest Distances for Each Type
long sum_same = 0;
long sum_opp = 0;
query_dist_recursive(water_tree_root, dot_coord, 0, dists_same, coastline_smoothing);
for (int ii = 0; ii < coastline_smoothing; ii++) {
sum_same += dists_same[ii];
}
if (!same_y) {
const int max = (sum_same < INT_MAX) ? sum_same : INT_MAX;
for (int ii = 0; ii < coastline_smoothing; ii++) {
dists_opp[ii] = max;
}
}
query_dist_recursive(land_tree_root, dot_coord, 0, dists_opp, coastline_smoothing);
for (int ii = 0; ii < coastline_smoothing; ii++) {
sum_opp += dists_opp[ii];
}
// Change Dot Type if Closer to Opposite Type
if (sum_same > sum_opp) {
dots[water_dots[i * 3 + 2]].type = 'L';
}
atomic_fetch_add(§ion_progress[3], 1);
}
}
/**
* Generate water biomes for DOTS between START_INDEX and END_INDEX. Water
* biomes are generated based on a dot's distance to the equator and the
* distance to the nearest land dot.
*/
void generate_biomes_water(
const int start_index, const int end_index, const int *water_dots, Node *land_tree_root,
const int height, const int num_dots, Dot *dots, _Atomic int *section_progress
) {
int land_dist;
for (int i = start_index; i < end_index; i++) {
// Calculate Distance to Equator
const float equator_dist =
fabs((float)water_dots[i * 3 + 1] - height / 2.0) / height * 20.0;
// Calculate Maximum Land Distance
if (i != start_index && water_dots[i * 3 + 1] == water_dots[(i - 1) * 3 + 1]) {
int min_dist_sq =
(int)sqrt(land_dist) + 1 + water_dots[i * 3] - water_dots[(i - 1) * 3];
land_dist = min_dist_sq * min_dist_sq;
} else {
land_dist = INT_MAX;
}
// Calculate Distance to Land
const int coord[2] = {water_dots[i * 3], water_dots[i * 3 + 1]};
query_recursive(land_tree_root, coord, 0, NULL, &land_dist);
// Set Water Biome
char dot_type = 'W';
if ( // Remember: all land distances are squared for efficiency
(land_dist < 35 * 35 && equator_dist > 9) ||
(land_dist < 25 * 25 && equator_dist > 8) ||
(land_dist < 15 * 15 && equator_dist > 7)
) {
dot_type = 'I';
} else if (land_dist < 18 * 18) {
dot_type = 's';
} else if (land_dist >= 35 * 35) {
dot_type = 'd';
}
dots[water_dots[i * 3 + 2]].type = dot_type;
atomic_fetch_add(§ion_progress[4], 1);
}
}
/**
* Generate land biomes for DOTS between START_INDEX and END_INDEX. Land biomes
* are generated base on the nearest dot in BIOME_ORIGIN_INDEXES, the list of
* dots whose biomes are already before this function.
*/
void generate_biomes_land(
const int start_index, const int end_index, const int *land_dots,
Node *origin_tree_root, const int biome_origin_indexes[],
const int num_dots, Dot *dots, _Atomic int *section_progress
) {
int min_dist;
for (int i = start_index; i < end_index; i++) {
// Calculate Maxminum Distance
if (i != start_index && land_dots[i * 3 + 1] == land_dots[(i - 1) * 3 + 1]) {
int min_dist_sq = (int)sqrt(min_dist) + 1 + land_dots[i * 3] - land_dots[(i - 1) * 3];
min_dist = min_dist_sq * min_dist_sq;
} else {
min_dist = INT_MAX;
}
// Find Nearest Biome Origin Dot
int origin_index = 0;
const int coord[2] = {land_dots[i * 3], land_dots[i * 3 + 1]};
query_recursive(origin_tree_root, coord, 0, &origin_index, &min_dist);
// Set Dot Type
dots[land_dots[i * 3 + 2]].type = dots[origin_index].type;
atomic_fetch_add(§ion_progress[4], 1);
}
}
/**
* Generate a section of the IMAGE_INDEXES, which contains the index in DOTS of
* the nearest dot to each pixel. Also count the number of pixels of each type
* for TYPE_COUNTS, to be used in statistics at the end of the main program.
*/
void generate_image(
const int start_height, const int end_height, const int width, Node *tree_root,
const int num_dots, const Dot *dots,
int *image_indexes, int *type_counts, _Atomic int *section_progress
) {
// Dot type counts for statistics, not used in image generation
int local_type_counts[11] = {0};
const char types[11] = {'I', 's', 'W', 'd', 'R', 'D', 'J', 'F', 'P', 'T', 'S'};
// Generate Image
for (int y = start_height; y < end_height; y++) {
int min_dist = INT_MAX;
for (int x = 0; x < width; x++) {
int nearest_index = 0;
if (min_dist != INT_MAX) {
const int min_dist_sq = (int)sqrt(min_dist) + 2;
/*
max_dist = dist(previous x, previous nearest dot) + dist(previous x, current x)
= previous max_dist + 1
+1 for floating point errors (ceil didn't work)
*/
min_dist = min_dist_sq * min_dist_sq;
}
// Find Nearest Dot
const int coord[2] = {x, y};
query_recursive(tree_root, coord, 0, &nearest_index, &min_dist);
// Add to Image Indexes and Local Type Counts
image_indexes[y * width + x] = nearest_index;
for (int i = 0; i < 11; i++) {
if (dots[nearest_index].type == types[i]) {
local_type_counts[i]++;
break;
}
}
}
// Only update for each row of pixels
atomic_fetch_add(§ion_progress[5], 1);
}
// Update Shared Type Counts
for (int i = 0; i < 11; i++) {
type_counts[i] += local_type_counts[i];
}
}
// Main Function
/**
* Main Function. ARGC and ARGV used for automated inputs mode.
*/
int main(int argc, char *argv[]) {
// Set Main Process Title
set_process_title("main", -1);
// Get Inputs
bool auto_mode;
char output_file[229];
int width, height, map_resolution, island_abundance, coastline_smoothing, processes;
float island_size;
if (argc == 1) {
// Manual Inputs Mode
auto_mode = false;
strncpy(output_file, "result.png", 11);
// Get Program Version
char file_line[29];
FILE *fptr = fopen("README.md", "r");
fgets(file_line, 29, fptr);
fgets(file_line, 29, fptr);
fgets(file_line, 29, fptr);
fclose(fptr);
char version[12];
strncpy(version, file_line + 12, 12);
version[strlen(version) - 1] = '\0';
// Copyright, license notice, etc.
system("clear");
printf(
"Welcome to BiomeGen v%s\n"
"Copyright (C) 2025 Liam Ralph\n"
"https://github.com/liam-ralph\n"
"This project is licensed under the GNU General Public License v3.0,\n"
"except for result.png, this program's output, licensed under The Unlicense.\n"
"\033[38;5;1mWARNING: In some terminals, the refreshing progress screen\n"
"may flash, which could cause problems for people with epilepsy.%s\n"
"Press ENTER to begin.\n", version, ANSI_RESET
);
/*
I do not know if the flashing lights this program sometimes makes could reasonably cause
epilepsy or not, but I put this just in case. You can increase sleep_time.tv_nsec to help
with this.
*/
getchar();
system("clear");