Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MorsGames
GitHub Repository: MorsGames/sm64plus
Path: blob/master/tools/skyconv.c
7854 views
1
/* skybox generator */
2
3
#define _GNU_SOURCE
4
#include <assert.h>
5
#include <string.h>
6
#include <stdint.h>
7
#include <stdlib.h>
8
#include <limits.h>
9
#include <stdio.h>
10
#include <stdbool.h>
11
#include <math.h>
12
13
#include "n64graphics.h"
14
#include "utils.h"
15
16
#define SKYCONV_ENCODING ENCODING_U8
17
18
typedef struct {
19
rgba *px;
20
bool useless;
21
unsigned int pos;
22
} TextureTile;
23
24
typedef enum {
25
InvalidType = -1,
26
Skybox,
27
Cake,
28
CakeEU,
29
ImageType_MAX
30
} ImageType;
31
32
typedef enum {
33
InvalidMode = -1,
34
Combine,
35
Split
36
} OperationMode;
37
38
typedef struct {
39
int imageWidth, imageHeight;
40
int tileWidth, tileHeight;
41
int numCols, numRows;
42
bool wrapX;
43
bool optimizePositions;
44
} ImageProps;
45
46
static const ImageProps IMAGE_PROPERTIES[ImageType_MAX][2] = {
47
[Skybox] = {
48
{248, 248, 31, 31, 8, 8, true, true},
49
{256, 256, 32, 32, 8, 8, true, true},
50
},
51
[Cake] = {
52
{316, 228, 79, 19, 4, 12, false, false},
53
{320, 240, 80, 20, 4, 12, false, false},
54
},
55
[CakeEU] = {
56
{320, 224, 64, 32, 5, 7, false, false},
57
{320, 224, 64, 32, 5, 7, false, false},
58
},
59
};
60
61
typedef struct {
62
int cols, rows;
63
} TableDimension;
64
65
static const TableDimension TABLE_DIMENSIONS[ImageType_MAX] = {
66
[Skybox] = {8, 10},
67
[Cake] = {4, 12},
68
[CakeEU] = {5, 7},
69
};
70
71
TextureTile *tiles;
72
ImageType type = InvalidType;
73
OperationMode mode = InvalidMode;
74
char *programName;
75
char *input, *output;
76
char *writeDir;
77
char skyboxName[256];
78
bool expanded = false;
79
bool writeTiles;
80
bool storeNamesOnly = false;
81
82
static void allocate_tiles() {
83
const ImageProps props = IMAGE_PROPERTIES[type][true];
84
int tileWidth = props.tileWidth;
85
int tileHeight = props.tileHeight;
86
int numRows = props.numRows;
87
int numCols = props.numCols;
88
89
int tileSize = tileWidth * tileHeight * sizeof(rgba);
90
int totalSize = numRows * numCols * tileSize;
91
tiles = calloc(1, numRows * numCols * sizeof(TextureTile));
92
rgba *tileData = calloc(1, totalSize);
93
for (int row = 0; row < numRows; ++row) {
94
for (int col = 0; col < numCols; ++col) {
95
tiles[row * numCols + col].px = (tileData + (row * numCols + col) * (tileWidth * tileHeight));
96
}
97
}
98
}
99
100
static void free_tiles() {
101
free(tiles->px);
102
free(tiles);
103
}
104
105
static void split_tile(int col, int row, rgba *image, bool expanded) {
106
const ImageProps props = IMAGE_PROPERTIES[type][expanded];
107
int tileWidth = props.tileWidth;
108
int tileHeight = props.tileHeight;
109
int imageWidth = props.imageWidth;
110
int numCols = props.numCols;
111
112
int expandedWidth = IMAGE_PROPERTIES[type][true].tileWidth;
113
114
for (int y = 0; y < tileHeight; y++) {
115
for (int x = 0; x < tileWidth; x++) {
116
int ny = row * tileHeight + y;
117
int nx = col * tileWidth + x;
118
tiles[row * numCols + col].px[y * expandedWidth + x] = image[(ny * imageWidth + nx)];
119
}
120
}
121
}
122
123
static void expand_tiles(ImageType imageType) {
124
const ImageProps props = IMAGE_PROPERTIES[imageType][true];
125
int numRows = props.numRows;
126
int numCols = props.numCols;
127
int tileWidth = props.tileWidth;
128
int tileHeight = props.tileHeight;
129
130
// If the image type wraps,
131
// Copy each tile's left edge to the previous tile's right edge
132
// Each tile's height is still tileHeight - 1
133
if (props.wrapX) {
134
for (int row = 0; row < numRows; ++row) {
135
for (int col = 0; col < numCols; ++col) {
136
int nextCol = (col + 1) % numCols;
137
for (int y = 0; y < (tileHeight - 1); ++y) {
138
tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
139
}
140
}
141
}
142
} else {
143
// Don't wrap, copy the second to last column instead.
144
for (int row = 0; row < numRows; ++row) {
145
for (int col = 0; col < numCols - 1; ++col) {
146
int nextCol = (col + 1) % numCols;
147
for (int y = 0; y < (tileHeight - 1); ++y) {
148
tiles[row * numCols + col].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + nextCol].px[y * tileWidth];
149
}
150
}
151
for (int y = 0; y < (tileHeight - 1); ++y) {
152
tiles[row * numCols + (numCols - 1)].px[(tileWidth - 1) + y * tileWidth] = tiles[row * numCols + (numCols - 1)].px[(tileWidth - 2) + y * tileWidth];
153
}
154
155
}
156
}
157
158
// Copy each tile's top edge to the previous tile's bottom edge, EXCEPT for the bottom row, which
159
// just duplicates its second-to-last row
160
for (int row = 0; row < numRows; ++row) {
161
if (row < numRows - 1) {
162
for (int col = 0; col < numCols; ++col) {
163
int nextRow = (row + 1) % numRows;
164
for (int x = 0; x < tileWidth; ++x) {
165
tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[nextRow * numCols + col].px[x];
166
}
167
}
168
}
169
// For the last row of tiles, duplicate each one's second to last row
170
else {
171
for (int col = 0; col < numCols; ++col) {
172
for (int x = 0; x < tileWidth; ++x) {
173
tiles[row * numCols + col].px[x + (tileHeight - 1) * tileWidth] = tiles[row * numCols + col].px[x + (tileHeight - 2) * tileWidth];
174
}
175
}
176
}
177
}
178
}
179
180
static void init_tiles(rgba *image, bool expanded) {
181
const ImageProps props = IMAGE_PROPERTIES[type][expanded];
182
183
for (int row = 0; row < props.numRows; row++) {
184
for (int col = 0; col < props.numCols; col++) {
185
split_tile(col, row, image, expanded);
186
}
187
}
188
189
// Expand the tiles to their full size
190
if (!expanded) {
191
expand_tiles(type);
192
}
193
}
194
195
static void assign_tile_positions() {
196
const ImageProps props = IMAGE_PROPERTIES[type][true];
197
const size_t TILE_SIZE = props.tileWidth * props.tileHeight * sizeof(rgba);
198
199
unsigned int newPos = 0;
200
for (int i = 0; i < props.numRows * props.numCols; i++) {
201
if (props.optimizePositions) {
202
for (int j = 0; j < i; j++) {
203
if (!tiles[j].useless && memcmp(tiles[j].px, tiles[i].px, TILE_SIZE) == 0) {
204
tiles[i].useless = 1;
205
tiles[i].pos = j;
206
break;
207
}
208
}
209
}
210
211
if (!tiles[i].useless) {
212
tiles[i].pos = newPos;
213
newPos++;
214
}
215
}
216
}
217
218
// Provide a replacement for realpath on Windows
219
#ifdef _WIN32
220
#define realpath(path, resolved_path) _fullpath(resolved_path, path, PATH_MAX)
221
#endif
222
223
/* write pngs to disc */
224
void write_tiles() {
225
const ImageProps props = IMAGE_PROPERTIES[type][true];
226
char buffer[PATH_MAX];
227
228
if (realpath(writeDir, buffer) == NULL) {
229
fprintf(stderr, "err: Could not find find img dir %s", writeDir);
230
exit(EXIT_FAILURE);
231
}
232
233
strcat(buffer, "/");
234
235
switch(type) {
236
case Skybox:
237
strcat(buffer, skyboxName);
238
break;
239
case Cake:
240
strcat(buffer, "cake");
241
break;
242
case CakeEU:
243
strcat(buffer, "cake_eu");
244
break;
245
default:
246
exit(EXIT_FAILURE);
247
break;
248
}
249
250
int dirLength = strlen(buffer);
251
char *filename = buffer + dirLength;
252
for (int i = 0; i < props.numRows * props.numCols; i++) {
253
if (!tiles[i].useless) {
254
*filename = 0;
255
snprintf(filename, PATH_MAX, ".%d.rgba16.png", tiles[i].pos);
256
rgba2png(buffer, tiles[i].px, props.tileWidth, props.tileHeight);
257
}
258
}
259
}
260
261
static unsigned int get_index(TextureTile *t, unsigned int i) {
262
if (t[i].useless) {
263
i = t[i].pos;
264
}
265
return t[i].pos;
266
}
267
268
static void print_raw_data(FILE *cFile, TextureTile *tile) {
269
ImageProps props = IMAGE_PROPERTIES[type][true];
270
uint8_t *raw = malloc(props.tileWidth * props.tileHeight * 2);
271
int size = rgba2raw(raw, tile->px, props.tileWidth, props.tileHeight, 16);
272
fprint_write_output(cFile, SKYCONV_ENCODING, raw, size);
273
free(raw);
274
}
275
276
static void write_skybox_c() { /* write c data to disc */
277
const ImageProps props = IMAGE_PROPERTIES[type][true];
278
279
char fBuffer[PATH_MAX] = "";
280
FILE *cFile;
281
282
if (realpath(output, fBuffer) == NULL) {
283
fprintf(stderr, "err: Could not find find src dir %s", output);
284
exit(EXIT_FAILURE);
285
}
286
287
sprintf(fBuffer, "%s/%s_skybox.c", output, skyboxName);
288
cFile = fopen(fBuffer, "w"); /* reset file */
289
290
/* setup C file */
291
292
if (cFile == NULL) {
293
fprintf(stderr, "err: Could not open %s\n", fBuffer);
294
}
295
296
fprintf(cFile, "#include \"types.h\"\n\n#include \"make_const_nonconst.h\"\n\n");
297
298
for (int i = 0; i < props.numRows * props.numCols; i++) {
299
if (!tiles[i].useless) {
300
if (storeNamesOnly) {
301
fprintf(
302
cFile,
303
"ALIGNED8 static const Texture %s_skybox_texture_%05X[] = "
304
"\"textures/skybox_tiles/%s.%d.rgba16\";\n\n",
305
skyboxName, tiles[i].pos, skyboxName, tiles[i].pos
306
);
307
} else {
308
fprintf(cFile, "ALIGNED8 static const Texture %s_skybox_texture_%05X[] = {\n", skyboxName, tiles[i].pos);
309
print_raw_data(cFile, &tiles[i]);
310
fputs("};\n\n", cFile);
311
}
312
}
313
}
314
315
fprintf(cFile, "const Texture *const %s_skybox_ptrlist[] = {\n", skyboxName);
316
317
for (int row = 0; row < 8; row++) {
318
for (int col = 0; col < 10; col++) {
319
fprintf(cFile, "%s_skybox_texture_%05X,\n", skyboxName, get_index(tiles, row * 8 + (col % 8)));
320
}
321
}
322
323
fputs("};\n\n", cFile);
324
fclose(cFile);
325
}
326
327
static void write_cake_c() {
328
char buffer[PATH_MAX] = "";
329
if (realpath(output, buffer) == NULL) {
330
fprintf(stderr, "err: Could not find find src dir %s", output);
331
exit(EXIT_FAILURE);
332
}
333
334
if (type == CakeEU) {
335
strcat(buffer, "/cake_eu.inc.c");
336
}
337
else {
338
strcat(buffer, "/cake.inc.c");
339
}
340
341
FILE *cFile = fopen(buffer, "w");
342
343
const char *euSuffx = "";
344
if (type == CakeEU) {
345
euSuffx = "eu_";
346
}
347
348
int numTiles = TABLE_DIMENSIONS[type].cols * TABLE_DIMENSIONS[type].rows;
349
for (int i = 0; i < numTiles; ++i) {
350
if (storeNamesOnly) {
351
fprintf(
352
cFile,
353
"ALIGNED8 static const Texture cake_end_texture_%s%d[] = "
354
"\"textures/skybox_tiles/cake%s.%d.rgba16\";\n\n",
355
euSuffx, i, *euSuffx ? "_eu" : "", tiles[i].pos
356
);
357
} else {
358
fprintf(cFile, "ALIGNED8 static const Texture cake_end_texture_%s%d[] = {\n", euSuffx, i);
359
print_raw_data(cFile, &tiles[i]);
360
fputs("};\n\n", cFile);
361
}
362
}
363
fclose(cFile);
364
}
365
366
// input: the skybox tiles + the table = up to 64 32x32 images (rgba16) + 80 pointers (u32)
367
// some pointers point to duplicate entries
368
void combine_skybox(const char *input, const char *output) {
369
enum { W = 10, H = 8, W2 = 8 };
370
371
FILE *file = fopen(input, "rb");
372
if (!file) goto fail;
373
if (fseek(file, 0, SEEK_END)) goto fail;
374
375
ssize_t fileSize = ftell(file);
376
if (fileSize < 8*10*4) goto fail;
377
rewind(file);
378
379
size_t tableIndex = fileSize - 8*10*4;
380
if (tableIndex % (32*32*2) != 0) goto fail;
381
382
// there are at most 64 tiles before the table
383
rgba *tiles[8*8];
384
size_t tileIndex = 0;
385
for (size_t pos = 0; pos < tableIndex; pos += 32*32*2) {
386
uint8_t buf[32*32*2];
387
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
388
tiles[tileIndex] = raw2rgba(buf, 32, 32, 16);
389
tileIndex++;
390
}
391
392
uint32_t table[W*H];
393
if (fread(table, sizeof(table), 1, file) != 1) goto fail;
394
395
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
396
reverse_endian((unsigned char *) table, W*H*4);
397
#endif
398
399
uint32_t base = table[0];
400
for (int i = 0; i < W*H; i++) {
401
table[i] -= base;
402
}
403
404
// Convert the 256x256 skybox to an editable 248x248 image by skipping the duplicated rows and columns
405
// every 32nd column is a repeat of the 33rd, and
406
// every 32nd row is a repeat of the 33rd, EXCEPT for the last row, but that only matters when
407
// expanding the tiles
408
rgba combined[31*H * 31*W2];
409
for (int i = 0; i < H; i++) {
410
for (int j = 0; j < W2; j++) {
411
int index = table[i*W+j] / 0x800;
412
for (int y = 0; y < 31; y++) {
413
for (int x = 0; x < 31; x++) {
414
combined[(i*31 + y) * (31*W2) + (j*31 + x)] = tiles[index][y*32 + x];
415
}
416
}
417
}
418
}
419
if (!rgba2png(output, combined, 31*W2, 31*H)) {
420
fprintf(stderr, "Failed to write skybox image.\n");
421
exit(1);
422
}
423
return;
424
fail:
425
fprintf(stderr, "Failed to read skybox binary.\n");
426
exit(1);
427
}
428
429
void combine_cakeimg(const char *input, const char *output, bool eu) {
430
int W, H, SMALLH, SMALLW;
431
if (eu) {
432
W = 5;
433
H = 7;
434
SMALLH = 32;
435
SMALLW = 64;
436
} else {
437
W = 4;
438
H = 12;
439
SMALLH = 20;
440
SMALLW = 80;
441
}
442
443
FILE *file = fopen(input, "rb");
444
if (!file) goto fail;
445
446
rgba *combined;
447
if (!eu) {
448
combined = malloc((SMALLH-1)*H * (SMALLW-1)*W * sizeof(rgba));
449
for (int i = 0; i < H; i++) {
450
for (int j = 0; j < W; j++) {
451
//Read the full tile
452
uint8_t buf[SMALLH * SMALLW * 2];
453
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
454
rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
455
456
//Only write the unique parts of each tile
457
for (int y = 0; y < SMALLH - 1; y++) {
458
for (int x = 0; x < SMALLW - 1; x++) {
459
combined[(i*(SMALLH-1) + y) * (SMALLW-1)*W + (j*(SMALLW-1) + x)] = tile[y*(SMALLW) + x];
460
}
461
}
462
}
463
}
464
if (!rgba2png(output, combined, (SMALLW-1)*W, (SMALLH-1)*H)) {
465
fprintf(stderr, "Failed to write cake image.\n");
466
exit(1);
467
}
468
}
469
else {
470
combined = malloc(SMALLH*H * SMALLW*W * sizeof(rgba));
471
for (int i = 0; i < H; i++) {
472
for (int j = 0; j < W; j++) {
473
uint8_t buf[SMALLH * SMALLW * 2];
474
if (fread(buf, sizeof(buf), 1, file) != 1) goto fail;
475
rgba *tile = raw2rgba(buf, SMALLH, SMALLW, 16);
476
for (int y = 0; y < SMALLH; y++) {
477
for (int x = 0; x < SMALLW; x++) {
478
combined[(i*SMALLH + y) * SMALLW*W + (j*SMALLW + x)] = tile[y*SMALLW + x];
479
}
480
}
481
}
482
}
483
if (!rgba2png(output, combined, SMALLW*W, SMALLH*H)) {
484
fprintf(stderr, "Failed to write cake image.\n");
485
exit(1);
486
}
487
}
488
return;
489
fail:
490
fprintf(stderr, "Failed to read cake binary.\n");
491
exit(1);
492
}
493
494
// Modified from n64split
495
static void usage() {
496
fprintf(stderr,
497
"Usage: %s --type sky|cake|cake_eu {--combine INPUT OUTPUT | --split INPUT OUTPUT}\n"
498
"\n"
499
"Optional arguments:\n"
500
" --write-tiles OUTDIR Also create the individual tiles' PNG files\n"
501
" --store-names Store texture file names instead of actual data\n", programName);
502
}
503
504
// Modified from n64split
505
static int parse_arguments(int argc, char *argv[]) {
506
for (int i = 1; i < argc; ++i) {
507
if (strcmp(argv[i], "--combine") == 0) {
508
if (++i >= argc || mode != InvalidMode) {
509
goto invalid;
510
}
511
512
mode = Combine;
513
input = argv[i];
514
if (++i >= argc) {
515
goto invalid;
516
}
517
518
output = argv[i];
519
}
520
521
if (strcmp(argv[i], "--split") == 0) {
522
if (++i >= argc || mode != InvalidMode) {
523
goto invalid;
524
}
525
526
mode = Split;
527
input = argv[i];
528
if (++i >= argc) {
529
goto invalid;
530
}
531
532
output = argv[i];
533
}
534
535
if (strcmp(argv[i], "--type") == 0) {
536
if (++i >= argc || type != InvalidType) {
537
goto invalid;
538
}
539
540
if (strcmp(argv[i], "sky") == 0) {
541
type = Skybox;
542
} else if(strcmp(argv[i], "cake-eu") == 0) {
543
type = CakeEU;
544
} else if(strcmp(argv[i], "cake") == 0) {
545
type = Cake;
546
}
547
}
548
549
if (strcmp(argv[i], "--write-tiles") == 0) {
550
if (++i >= argc || argv[i][0] == '-') {
551
goto invalid;
552
}
553
554
writeTiles = true;
555
writeDir = argv[i];
556
}
557
558
if (strcmp(argv[i], "--store-names") == 0) {
559
storeNamesOnly = true;
560
}
561
}
562
563
return 1;
564
invalid:
565
usage();
566
return 0;
567
}
568
569
bool imageMatchesDimensions(int width, int height) {
570
bool matchesDimensions = false;
571
for (int expand = false; expand <= true; ++expand) {
572
if (width == IMAGE_PROPERTIES[type][expand].imageWidth &&
573
height == IMAGE_PROPERTIES[type][expand].imageHeight) {
574
matchesDimensions = true;
575
expanded = expand;
576
break;
577
}
578
}
579
if (!matchesDimensions) {
580
if (type != CakeEU) {
581
fprintf(stderr, "err: That type of image must be either %d x %d or %d x %d. Yours is %d x %d.\n",
582
IMAGE_PROPERTIES[type][false].imageWidth, IMAGE_PROPERTIES[type][false].imageHeight,
583
IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
584
width, height);
585
}
586
else {
587
fprintf(stderr, "err: That type of image must be %d x %d. Yours is %d x %d.\n",
588
IMAGE_PROPERTIES[type][true].imageWidth, IMAGE_PROPERTIES[type][true].imageHeight,
589
width, height);
590
591
}
592
return false;
593
}
594
595
if (type == CakeEU) {
596
expanded = true;
597
}
598
599
return true;
600
}
601
602
int main(int argc, char *argv[]) {
603
if (parse_arguments(argc, argv) == false) {
604
return EXIT_FAILURE;
605
}
606
607
if (type == Skybox && mode == Split) {
608
// Extract the skybox's name (ie: bbh, bidw) from the input png
609
char *base = basename(input);
610
strcpy(skyboxName, base);
611
char *extension = strrchr(skyboxName, '.');
612
if (extension) *extension = '\0';
613
}
614
615
switch (mode) {
616
case Combine:
617
switch (type) {
618
case Skybox:
619
combine_skybox(input, output);
620
break;
621
case Cake:
622
combine_cakeimg(input, output, 0);
623
break;
624
case CakeEU:
625
combine_cakeimg(input, output, 1);
626
break;
627
default:
628
usage();
629
return EXIT_FAILURE;
630
break;
631
}
632
break;
633
634
case Split: {
635
int width, height;
636
rgba *image = png2rgba(input, &width, &height);
637
if (image == NULL) {
638
fprintf(stderr, "err: Could not load image %s\n", argv[1]);
639
return EXIT_FAILURE;
640
}
641
642
if (!imageMatchesDimensions(width, height)) {
643
return EXIT_FAILURE;
644
}
645
646
allocate_tiles();
647
648
init_tiles(image, expanded);
649
switch (type) {
650
case Skybox:
651
assign_tile_positions();
652
write_skybox_c();
653
break;
654
case Cake:
655
case CakeEU:
656
assign_tile_positions();
657
write_cake_c();
658
break;
659
default:
660
fprintf(stderr, "err: Unknown image type.\n");
661
return EXIT_FAILURE;
662
break;
663
}
664
665
if (writeTiles) {
666
write_tiles();
667
}
668
free_tiles();
669
free(image);
670
} break;
671
default:
672
usage();
673
return EXIT_FAILURE;
674
break;
675
}
676
677
678
return EXIT_SUCCESS;
679
}
680