1
//------------------------------------------------------------------------
2
// BLOCKMAP : Generate the blockmap
3
//------------------------------------------------------------------------
5
// GL-Friendly Node Builder (C) 2000-2007 Andrew Apted
7
// Based on 'BSP 2.3' by Colin Reed, Lee Killough and others.
9
// This program is free software; you can redistribute it and/or
10
// modify it under the terms of the GNU General Public License
11
// as published by the Free Software Foundation; either version 2
12
// of the License, or (at your option) any later version.
14
// This program is distributed in the hope that it will be useful,
15
// but WITHOUT ANY WARRANTY; without even the implied warranty of
16
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
// GNU General Public License for more details.
19
//------------------------------------------------------------------------
41
#define DEBUG_BLOCKMAP 0
44
static int block_x, block_y;
45
static int block_w, block_h;
46
static int block_count;
48
static int block_mid_x = 0;
49
static int block_mid_y = 0;
51
static uint16_g ** block_lines;
53
static uint16_g *block_ptrs;
54
static uint16_g *block_dups;
56
static int block_compression;
57
static int block_overflowed;
59
#define DUMMY_DUP 0xFFFF
65
void GetBlockmapBounds(int *x, int *y, int *w, int *h)
67
*x = block_x; *y = block_y;
68
*w = block_w; *h = block_h;
72
// CheckLinedefInsideBox
74
int CheckLinedefInsideBox(int xmin, int ymin, int xmax, int ymax,
75
int x1, int y1, int x2, int y2)
87
x1 = x1 + (int) ((x2-x1) * (double)(ymax-y1) / (double)(y2-y1));
99
x1 = x1 + (int) ((x2-x1) * (double)(ymin-y1) / (double)(y2-y1));
111
y1 = y1 + (int) ((y2-y1) * (double)(xmax-x1) / (double)(x2-x1));
123
y1 = y1 + (int) ((y2-y1) * (double)(xmin-x1) / (double)(x2-x1));
135
/* swap end points */
136
tmp=x1; x1=x2; x2=tmp;
137
tmp=y1; y1=y2; y2=tmp;
140
/* linedef touches block */
145
/* ----- create blockmap ------------------------------------ */
152
#define BK_QUANTUM 32
154
static void BlockAdd(int blk_num, int line_index)
156
uint16_g *cur = block_lines[blk_num];
159
PrintDebug("Block %d has line %d\n", blk_num, line_index);
162
if (blk_num < 0 || blk_num >= block_count)
163
InternalError("BlockAdd: bad block number %d", blk_num);
167
// create empty block
168
block_lines[blk_num] = cur = UtilCalloc(BK_QUANTUM *
171
cur[BK_MAX] = BK_QUANTUM;
172
cur[BK_XOR] = 0x1234;
175
if (BK_FIRST + cur[BK_NUM] == cur[BK_MAX])
177
// no more room, so allocate some more...
178
cur[BK_MAX] += BK_QUANTUM;
180
block_lines[blk_num] = cur = UtilRealloc(cur, cur[BK_MAX] *
184
// compute new checksum
185
cur[BK_XOR] = ((cur[BK_XOR] << 4) | (cur[BK_XOR] >> 12)) ^ line_index;
187
cur[BK_FIRST + cur[BK_NUM]] = UINT16(line_index);
191
static void BlockAddLine(linedef_t *L)
193
int x1 = (int) L->start->x;
194
int y1 = (int) L->start->y;
195
int x2 = (int) L->end->x;
196
int y2 = (int) L->end->y;
198
int bx1 = (MIN(x1,x2) - block_x) / 128;
199
int by1 = (MIN(y1,y2) - block_y) / 128;
200
int bx2 = (MAX(x1,x2) - block_x) / 128;
201
int by2 = (MAX(y1,y2) - block_y) / 128;
204
int line_index = L->index;
207
PrintDebug("BlockAddLine: %d (%d,%d) -> (%d,%d)\n", line_index,
211
// handle truncated blockmaps
212
if (bx1 < 0) bx1 = 0;
213
if (by1 < 0) by1 = 0;
214
if (bx2 >= block_w) bx2 = block_w - 1;
215
if (by2 >= block_h) by2 = block_h - 1;
217
if (bx2 < bx1 || by2 < by1)
220
// handle simple case #1: completely horizontal
223
for (bx=bx1; bx <= bx2; bx++)
225
int blk_num = by1 * block_w + bx;
226
BlockAdd(blk_num, line_index);
231
// handle simple case #2: completely vertical
234
for (by=by1; by <= by2; by++)
236
int blk_num = by * block_w + bx1;
237
BlockAdd(blk_num, line_index);
242
// handle the rest (diagonals)
244
for (by=by1; by <= by2; by++)
245
for (bx=bx1; bx <= bx2; bx++)
247
int blk_num = by * block_w + bx;
249
int minx = block_x + bx * 128;
250
int miny = block_y + by * 128;
251
int maxx = minx + 127;
252
int maxy = miny + 127;
254
if (CheckLinedefInsideBox(minx, miny, maxx, maxy, x1, y1, x2, y2))
256
BlockAdd(blk_num, line_index);
261
static void CreateBlockmap(void)
265
block_lines = UtilCalloc(block_count * sizeof(uint16_g *));
269
for (i=0; i < num_linedefs; i++)
271
linedef_t *L = LookupLinedef(i);
273
// ignore zero-length lines
282
static int BlockCompare(const void *p1, const void *p2)
284
int blk_num1 = ((const uint16_g *) p1)[0];
285
int blk_num2 = ((const uint16_g *) p2)[0];
287
const uint16_g *A = block_lines[blk_num1];
288
const uint16_g *B = block_lines[blk_num2];
293
if (A == NULL) return -1;
294
if (B == NULL) return +1;
296
if (A[BK_NUM] != B[BK_NUM])
298
return A[BK_NUM] - B[BK_NUM];
301
if (A[BK_XOR] != B[BK_XOR])
303
return A[BK_XOR] - B[BK_XOR];
306
return memcmp(A+BK_FIRST, B+BK_FIRST, A[BK_NUM] * sizeof(uint16_g));
309
static void CompressBlockmap(void)
315
int orig_size, new_size;
317
block_ptrs = UtilCalloc(block_count * sizeof(uint16_g));
318
block_dups = UtilCalloc(block_count * sizeof(uint16_g));
322
// sort duplicate-detecting array. After the sort, all duplicates
323
// will be next to each other. The duplicate array gives the order
324
// of the blocklists in the BLOCKMAP lump.
326
for (i=0; i < block_count; i++)
329
qsort(block_dups, block_count, sizeof(uint16_g), BlockCompare);
331
// scan duplicate array and build up offset array
333
cur_offset = 4 + block_count + 2;
335
orig_size = 4 + block_count;
336
new_size = cur_offset;
340
for (i=0; i < block_count; i++)
342
int blk_num = block_dups[i];
346
if (block_lines[blk_num] == NULL)
348
block_ptrs[blk_num] = 4 + block_count;
349
block_dups[i] = DUMMY_DUP;
355
count = 2 + block_lines[blk_num][BK_NUM];
357
// duplicate ? Only the very last one of a sequence of duplicates
358
// will update the current offset value.
360
if (i+1 < block_count &&
361
BlockCompare(block_dups + i, block_dups + i+1) == 0)
363
block_ptrs[blk_num] = cur_offset;
364
block_dups[i] = DUMMY_DUP;
366
// free the memory of the duplicated block
367
UtilFree(block_lines[blk_num]);
368
block_lines[blk_num] = NULL;
376
// OK, this block is either the last of a series of duplicates, or
379
block_ptrs[blk_num] = cur_offset;
387
if (cur_offset > 65535)
389
MarkSoftFailure(LIMIT_BLOCKMAP);
390
block_overflowed = TRUE;
395
PrintDebug("Blockmap: Last ptr = %d duplicates = %d\n",
396
cur_offset, dup_count);
399
block_compression = (orig_size - new_size) * 100 / orig_size;
401
// there's a tiny chance of new_size > orig_size
402
if (block_compression < 0)
403
block_compression = 0;
407
static void WriteBlockmap(void)
411
raw_blockmap_header_t header;
413
lump_t *lump = CreateLevelLump("BLOCKMAP");
415
uint16_g null_block[2] = { 0x0000, 0xFFFF };
416
uint16_g m_zero = 0x0000;
417
uint16_g m_neg1 = 0xFFFF;
419
// leave empty if the blockmap overflowed
420
if (block_overflowed)
424
header.x_origin = UINT16(block_x);
425
header.y_origin = UINT16(block_y);
426
header.x_blocks = UINT16(block_w);
427
header.y_blocks = UINT16(block_h);
429
AppendLevelLump(lump, &header, sizeof(header));
432
for (i=0; i < block_count; i++)
434
uint16_g ptr = UINT16(block_ptrs[i]);
437
InternalError("WriteBlockmap: offset %d not set.", i);
439
AppendLevelLump(lump, &ptr, sizeof(uint16_g));
442
// add the null block which _all_ empty blocks will use
443
AppendLevelLump(lump, null_block, sizeof(null_block));
445
// handle each block list
446
for (i=0; i < block_count; i++)
448
int blk_num = block_dups[i];
451
// ignore duplicate or empty blocks
452
if (blk_num == DUMMY_DUP)
455
blk = block_lines[blk_num];
458
InternalError("WriteBlockmap: block %d is NULL !", i);
460
AppendLevelLump(lump, &m_zero, sizeof(uint16_g));
461
AppendLevelLump(lump, blk + BK_FIRST, blk[BK_NUM] * sizeof(uint16_g));
462
AppendLevelLump(lump, &m_neg1, sizeof(uint16_g));
467
static void FreeBlockmap(void)
471
for (i=0; i < block_count; i++)
474
UtilFree(block_lines[i]);
477
UtilFree(block_lines);
478
UtilFree(block_ptrs);
479
UtilFree(block_dups);
483
/* ----- top level funcs ------------------------------------ */
485
static void FindBlockmapLimits(bbox_t *bbox)
492
bbox->minx = bbox->miny = SHRT_MAX;
493
bbox->maxx = bbox->maxy = SHRT_MIN;
495
for (i=0; i < num_linedefs; i++)
497
linedef_t *L = LookupLinedef(i);
501
float_g x1 = L->start->x;
502
float_g y1 = L->start->y;
503
float_g x2 = L->end->x;
504
float_g y2 = L->end->y;
506
int lx = (int)floor(MIN(x1, x2));
507
int ly = (int)floor(MIN(y1, y2));
508
int hx = (int)ceil(MAX(x1, x2));
509
int hy = (int)ceil(MAX(y1, y2));
511
if (lx < bbox->minx) bbox->minx = lx;
512
if (ly < bbox->miny) bbox->miny = ly;
513
if (hx > bbox->maxx) bbox->maxx = hx;
514
if (hy > bbox->maxy) bbox->maxy = hy;
516
// compute middle of cluster (roughly, so we don't overflow)
517
mid_x += (lx + hx) / 32;
518
mid_y += (ly + hy) / 32;
522
if (num_linedefs > 0)
524
block_mid_x = (mid_x / num_linedefs) * 16;
525
block_mid_y = (mid_y / num_linedefs) * 16;
529
PrintDebug("Blockmap lines centered at (%d,%d)\n", block_mid_x, block_mid_y);
536
static void TruncateBlockmap(void)
538
while (block_w * block_h > cur_info->block_limit)
540
block_w -= block_w / 8;
541
block_h -= block_h / 8;
544
block_count = block_w * block_h;
546
PrintMiniWarn("Blockmap TOO LARGE! Truncated to %dx%d blocks\n",
549
MarkSoftFailure(LIMIT_BMAP_TRUNC);
551
/* center the truncated blockmap */
552
block_x = block_mid_x - block_w * 64;
553
block_y = block_mid_y - block_h * 64;
556
PrintDebug("New blockmap origin: (%d,%d)\n", block_x, block_y);
563
void InitBlockmap(void)
567
/* find limits of linedefs, and store as map limits */
568
FindBlockmapLimits(&map_bbox);
570
PrintVerbose("Map goes from (%d,%d) to (%d,%d)\n",
571
map_bbox.minx, map_bbox.miny, map_bbox.maxx, map_bbox.maxy);
573
block_x = map_bbox.minx - (map_bbox.minx & 0x7);
574
block_y = map_bbox.miny - (map_bbox.miny & 0x7);
576
block_w = ((map_bbox.maxx - block_x) / 128) + 1;
577
block_h = ((map_bbox.maxy - block_y) / 128) + 1;
578
block_count = block_w * block_h;
585
void PutBlockmap(void)
587
block_overflowed = FALSE;
589
// truncate blockmap if too large. We're limiting the number of
590
// blocks to around 16000 (user changeable), this leaves about 48K
591
// of shorts for the actual line lists.
593
if (block_count > cur_info->block_limit)
596
// initial phase: create internal blockmap containing the index of
597
// all lines in each block.
601
// -AJA- second phase: compress the blockmap. We do this by sorting
602
// the blocks, which is a typical way to detect duplicates in
607
// final phase: write it out in the correct format
611
if (block_overflowed)
612
PrintVerbose("Blockmap overflowed (lump will be empty)\n");
614
PrintVerbose("Completed blockmap, size %dx%d (compression: %d%%)\n",
615
block_w, block_h, block_compression);