1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "twine/scene/grid.h"
24 #include "common/endian.h"
25 #include "common/memstream.h"
26 #include "common/textconsole.h"
27 #include "twine/debugger/debug_grid.h"
28 #include "twine/menu/interface.h"
29 #include "twine/parser/blocklibrary.h"
30 #include "twine/renderer/redraw.h"
31 #include "twine/renderer/renderer.h"
32 #include "twine/renderer/screens.h"
33 #include "twine/resources/resources.h"
34 #include "twine/scene/actor.h"
35 #include "twine/scene/collision.h"
36 #include "twine/scene/scene.h"
37 #include "twine/twine.h"
38
39 #define CELLING_GRIDS_START_INDEX 120
40
41 namespace TwinE {
42
Grid(TwinEEngine * engine)43 Grid::Grid(TwinEEngine *engine) : _engine(engine) {
44 _blockBufferSize = GRID_SIZE_X * GRID_SIZE_Z * GRID_SIZE_Y * sizeof(BlockEntry);
45 _blockBuffer = (uint8 *)malloc(_blockBufferSize);
46 }
47
~Grid()48 Grid::~Grid() {
49 free(_blockBuffer);
50 for (int32 i = 0; i < ARRAYSIZE(_brickMaskTable); i++) {
51 free(_brickMaskTable[i]);
52 }
53 for (int32 i = 0; i < ARRAYSIZE(_brickTable); i++) {
54 free(_brickTable[i]);
55 }
56 free(_currentGrid);
57 free(_brickInfoBuffer);
58 free(_bricksDataBuffer);
59 }
60
init(int32 w,int32 h)61 void Grid::init(int32 w, int32 h) {
62 const int32 numbrickentries = (1 + (w + 24) / 24);
63 const size_t brickDataBufferSize = numbrickentries * MAXBRICKS * sizeof(BrickEntry);
64 _bricksDataBuffer = (BrickEntry *)malloc(brickDataBufferSize);
65 _brickInfoBufferSize = numbrickentries * sizeof(int16);
66 _brickInfoBuffer = (int16 *)malloc(_brickInfoBufferSize);
67 }
68
copyGridMask(int32 index,int32 x,int32 y,const Graphics::ManagedSurface & buffer)69 void Grid::copyGridMask(int32 index, int32 x, int32 y, const Graphics::ManagedSurface &buffer) {
70 uint8 *ptr = _brickMaskTable[index];
71
72 int32 left = x + *(ptr + 2);
73 int32 top = y + *(ptr + 3);
74 int32 right = *ptr + left - 1;
75 int32 bottom = *(ptr + 1) + top - 1;
76
77 if (left > _engine->_interface->_clip.right || right < _engine->_interface->_clip.left || bottom < _engine->_interface->_clip.top || top > _engine->_interface->_clip.bottom) {
78 return;
79 }
80
81 ptr += 4;
82
83 int32 absX = left;
84 int32 absY = top;
85
86 int32 vSize = (bottom - top) + 1;
87
88 if (vSize <= 0) {
89 return;
90 }
91
92 int32 offset = -((right - left) - _engine->width()) - 1;
93
94 right++;
95 bottom++;
96
97 // if line on top aren't in the blitting area...
98 if (absY < _engine->_interface->_clip.top) {
99 int numOfLineToRemove = _engine->_interface->_clip.top - absY;
100
101 vSize -= numOfLineToRemove;
102 if (vSize <= 0) {
103 return;
104 }
105
106 absY += numOfLineToRemove;
107
108 do {
109 int lineDataSize;
110
111 lineDataSize = *(ptr++);
112 ptr += lineDataSize;
113 } while (--numOfLineToRemove);
114 }
115
116 // reduce the vSize to remove lines on bottom
117 if (absY + vSize - 1 > _engine->_interface->_clip.bottom) {
118 vSize = _engine->_interface->_clip.bottom - absY + 1;
119 if (vSize <= 0) {
120 return;
121 }
122 }
123
124 uint8 *outPtr = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(left, absY);
125 const uint8 *inPtr = (const uint8 *)buffer.getBasePtr(left, absY);
126
127 do {
128 int32 height = *(ptr++);
129
130 do {
131 int32 width = *(ptr++); // skip size
132 outPtr += width;
133 inPtr += width;
134
135 absX += width;
136
137 height--;
138 if (!height) {
139 break;
140 }
141
142 width = *(ptr++); // copy size
143
144 for (int32 j = 0; j < width; j++) {
145 if (absX >= _engine->_interface->_clip.left && absX <= _engine->_interface->_clip.right) {
146 *outPtr = *inPtr;
147 }
148
149 absX++;
150 outPtr++;
151 inPtr++;
152 }
153 } while (--height);
154
155 absX = left;
156
157 outPtr += offset;
158 inPtr += offset;
159 } while (--vSize);
160 }
161
getBrickEntry(int32 j,int32 i) const162 const BrickEntry* Grid::getBrickEntry(int32 j, int32 i) const {
163 return &_bricksDataBuffer[j * MAXBRICKS + i];
164 }
165
drawOverModelActor(int32 x,int32 y,int32 z)166 void Grid::drawOverModelActor(int32 x, int32 y, int32 z) {
167 const int32 copyBlockPhysLeft = ((_engine->_interface->_clip.left + 24) / 24) - 1;
168 const int32 copyBlockPhysRight = ((_engine->_interface->_clip.right + 24) / 24);
169
170 for (int32 j = copyBlockPhysLeft; j <= copyBlockPhysRight; j++) {
171 for (int32 i = 0; i < _brickInfoBuffer[j]; i++) {
172 const BrickEntry *currBrickEntry = getBrickEntry(j, i);
173
174 if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
175 if (currBrickEntry->x + currBrickEntry->z > z + x) {
176 copyGridMask(currBrickEntry->index, (j * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
177 }
178 }
179 }
180 }
181 }
182
drawOverSpriteActor(int32 x,int32 y,int32 z)183 void Grid::drawOverSpriteActor(int32 x, int32 y, int32 z) {
184 const int32 copyBlockPhysLeft = ((_engine->_interface->_clip.left + 24) / 24) - 1;
185 const int32 copyBlockPhysRight = (_engine->_interface->_clip.right + 24) / 24;
186
187 for (int32 j = copyBlockPhysLeft; j <= copyBlockPhysRight; j++) {
188 for (int32 i = 0; i < _brickInfoBuffer[j]; i++) {
189 const BrickEntry *currBrickEntry = getBrickEntry(j, i);
190
191 if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
192 if (currBrickEntry->x == x && currBrickEntry->z == z) {
193 copyGridMask(currBrickEntry->index, (j * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
194 }
195
196 if (currBrickEntry->x > x || currBrickEntry->z > z) {
197 copyGridMask(currBrickEntry->index, (j * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
198 }
199 }
200 }
201 }
202 }
203
processGridMask(const uint8 * buffer,uint8 * ptr)204 void Grid::processGridMask(const uint8 *buffer, uint8 *ptr) {
205 const uint8 width = *buffer++;
206 uint8 height = *buffer++;
207 const uint8 offsetX = *buffer++;
208 const uint8 offsetY = *buffer++;
209 const int32 maxY = offsetY + height;
210
211 *ptr++ = width;
212 *ptr++ = height;
213 *ptr++ = offsetX;
214 *ptr++ = offsetY;
215
216 uint8 *targetPtrPos = ptr;
217
218 for (int32 y = offsetY; y < maxY; ++y) {
219 uint8 numOfBlock = 0;
220 uint8 opaquePixels = 0;
221 uint8 *numOfBlockTargetPtr = targetPtrPos;
222
223 targetPtrPos++;
224
225 const uint8 numRuns = *buffer++;
226
227 // the first time isn't skip. the skip size is 0 in that case
228 if (bits(*buffer, 6, 2) != 0) {
229 *targetPtrPos++ = 0;
230 numOfBlock++;
231 }
232
233 for (uint8 run = 0; run < numRuns; ++run) {
234 const uint8 runSpec = *buffer++;
235 const uint8 runLength = bits(runSpec, 0, 6) + 1;
236 const uint8 type = bits(runSpec, 6, 2);
237 if (type == 2) {
238 opaquePixels += runLength;
239 buffer++;
240 } else if (type == 1) {
241 opaquePixels += runLength;
242 buffer += runLength;
243 } else { // skip (type 3)
244 if (opaquePixels) {
245 *targetPtrPos++ = opaquePixels; // write down the number of pixel passed so far
246 numOfBlock++;
247 opaquePixels = 0;
248 }
249 *targetPtrPos++ = runLength; //write skip
250 numOfBlock++;
251 }
252 }
253
254 if (opaquePixels) {
255 *targetPtrPos++ = opaquePixels;
256 numOfBlock++;
257
258 opaquePixels = 0;
259 }
260
261 *numOfBlockTargetPtr = numOfBlock;
262 }
263 }
264
createGridMask()265 void Grid::createGridMask() {
266 for (int32 b = 0; b < NUM_BRICKS; b++) {
267 if (!_brickUsageTable[b]) {
268 continue;
269 }
270 if (_brickMaskTable[b]) {
271 free(_brickMaskTable[b]);
272 }
273 _brickMaskTable[b] = (uint8 *)malloc(_brickSizeTable[b]);
274 processGridMask(_brickTable[b], _brickMaskTable[b]);
275 }
276 }
277
getSpriteSize(int32 offset,int32 * width,int32 * height,const uint8 * spritePtr)278 void Grid::getSpriteSize(int32 offset, int32 *width, int32 *height, const uint8 *spritePtr) {
279 spritePtr += READ_LE_INT32(spritePtr + offset * 4);
280
281 *width = *spritePtr;
282 *height = *(spritePtr + 1);
283 }
284
loadGridBricks()285 void Grid::loadGridBricks() {
286 uint32 firstBrick = 60000;
287 uint32 lastBrick = 0;
288 uint32 currentBllEntryIdx = 1;
289
290 memset(_brickSizeTable, 0, sizeof(_brickSizeTable));
291 memset(_brickUsageTable, 0, sizeof(_brickUsageTable));
292
293 // get block libraries usage bits
294 const uint8 *ptrToBllBits = _currentGrid + (_currentGridSize - 32);
295
296 // for all bits under the 32bytes (256bits)
297 for (uint32 i = 1; i < 256; i++) {
298 const uint8 currentBitByte = *(ptrToBllBits + (i / 8));
299 const uint8 currentBitMask = 1 << (7 - (i & 7));
300
301 if (currentBitByte & currentBitMask) {
302 const BlockData *currentBllPtr = getBlockLibrary(currentBllEntryIdx);
303 for (const BlockDataEntry &entry : currentBllPtr->entries) {
304 uint16 brickIdx = entry.brickIdx;
305 if (!brickIdx) {
306 continue;
307 }
308 brickIdx--;
309 if (brickIdx <= firstBrick) {
310 firstBrick = brickIdx;
311 }
312
313 if (brickIdx > lastBrick) {
314 lastBrick = brickIdx;
315 }
316
317 _brickUsageTable[brickIdx] = 1;
318 }
319 }
320 ++currentBllEntryIdx;
321 }
322
323 for (uint32 i = firstBrick; i <= lastBrick; i++) {
324 if (!_brickUsageTable[i]) {
325 free(_brickTable[i]);
326 _brickTable[i] = nullptr;
327 continue;
328 }
329 _brickSizeTable[i] = HQR::getAllocEntry(&_brickTable[i], Resources::HQR_LBA_BRK_FILE, i);
330 if (_brickSizeTable[i] == 0) {
331 warning("Failed to load isometric brick index %i", i);
332 }
333 }
334 }
335
createGridColumn(const uint8 * gridEntry,uint32 gridEntrySize,uint8 * dest,uint32 destSize)336 void Grid::createGridColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) {
337 Common::MemoryReadStream stream(gridEntry, gridEntrySize);
338 Common::MemoryWriteStream outstream(dest, destSize);
339 int32 brickCount = stream.readByte();
340
341 do {
342 const int32 flag = stream.readByte();
343 const int32 blockCount = bits(flag, 0, 6) + 1;
344 const int32 type = bits(flag, 6, 2);
345 if (type == 0) {
346 for (int32 i = 0; i < blockCount; i++) {
347 outstream.writeUint16LE(0);
348 }
349 } else if (type == 1) {
350 for (int32 i = 0; i < blockCount; i++) {
351 outstream.writeUint16LE(stream.readUint16LE());
352 }
353 } else {
354 const uint16 gridIdx = stream.readUint16LE();
355 for (int32 i = 0; i < blockCount; i++) {
356 outstream.writeUint16LE(gridIdx);
357 }
358 }
359 assert(!outstream.err());
360 assert(!stream.err());
361 } while (--brickCount);
362 }
363
createCellingGridColumn(const uint8 * gridEntry,uint32 gridEntrySize,uint8 * dest,uint32 destSize)364 void Grid::createCellingGridColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) {
365 Common::MemoryReadStream stream(gridEntry, gridEntrySize);
366 Common::SeekableMemoryWriteStream outstream(dest, destSize);
367 int32 brickCount = stream.readByte();
368
369 do {
370 const int32 flag = stream.readByte();
371 const int32 blockCount = bits(flag, 0, 6) + 1;
372 const int32 type = bits(flag, 6, 2);
373
374 if (type == 0) {
375 for (int32 i = 0; i < blockCount; i++) {
376 outstream.seek(outstream.pos() + 2);
377 }
378 } else if (type == 1) {
379 for (int32 i = 0; i < blockCount; i++) {
380 outstream.writeUint16LE(stream.readUint16LE());
381 }
382 } else {
383 const uint16 gridIdx = stream.readUint16LE();
384 for (int32 i = 0; i < blockCount; i++) {
385 outstream.writeUint16LE(gridIdx);
386 }
387 }
388 assert(!outstream.err());
389 assert(!stream.err());
390 } while (--brickCount);
391 }
392
createGridMap()393 void Grid::createGridMap() {
394 int32 blockOffset = 0;
395
396 for (int32 z = 0; z < GRID_SIZE_Z; z++) {
397 const int32 gridIdx = z * GRID_SIZE_X;
398
399 for (int32 x = 0; x < GRID_SIZE_X; x++) {
400 const int32 gridOffset = READ_LE_UINT16(_currentGrid + 2 * (x + gridIdx));
401 createGridColumn(_currentGrid + gridOffset, _currentGridSize - gridOffset, _blockBuffer + blockOffset, _blockBufferSize - blockOffset);
402 blockOffset += 2 * GRID_SIZE_Y;
403 }
404 }
405 }
406
createCellingGridMap(const uint8 * gridPtr,int32 gridPtrSize)407 void Grid::createCellingGridMap(const uint8 *gridPtr, int32 gridPtrSize) {
408 int32 currGridOffset = 0;
409 int32 blockOffset = 0;
410
411 for (int32 z = 0; z < GRID_SIZE_Z; z++) {
412 const uint8 *tempGridPtr = gridPtr + currGridOffset;
413
414 for (int32 x = 0; x < GRID_SIZE_X; x++) {
415 const int gridOffset = READ_LE_UINT16(tempGridPtr);
416 tempGridPtr += 2;
417 createCellingGridColumn(gridPtr + gridOffset, gridPtrSize - gridOffset, _blockBuffer + blockOffset, _blockBufferSize - blockOffset);
418 blockOffset += 2 * GRID_SIZE_Y;
419 }
420 currGridOffset += GRID_SIZE_X + GRID_SIZE_Z;
421 }
422 }
423
initGrid(int32 index)424 bool Grid::initGrid(int32 index) {
425 // load grids from file
426 _currentGridSize = HQR::getAllocEntry(&_currentGrid, Resources::HQR_LBA_GRI_FILE, index);
427 if (_currentGridSize == 0) {
428 warning("Failed to load grid index: %i", index);
429 return false;
430 }
431
432 // load layouts from file
433 if (!_currentBlockLibrary.loadFromHQR(Resources::HQR_LBA_BLL_FILE, index, _engine->isLBA1())) {
434 warning("Failed to load block library index: %i", index);
435 return false;
436 }
437
438 loadGridBricks();
439
440 createGridMask();
441
442 createGridMap();
443
444 return true;
445 }
446
initCellingGrid(int32 index)447 bool Grid::initCellingGrid(int32 index) {
448 uint8 *gridPtr = nullptr;
449
450 // load grids from file
451 const int realIndex = index + CELLING_GRIDS_START_INDEX;
452 const int32 gridSize = HQR::getAllocEntry(&gridPtr, Resources::HQR_LBA_GRI_FILE, realIndex);
453 if (gridSize == 0) {
454 warning("Failed to load grid index %i", realIndex);
455 return false;
456 }
457
458 createCellingGridMap(gridPtr, gridSize);
459 free(gridPtr);
460 _engine->_redraw->_reqBgRedraw = true;
461 return true;
462 }
463
drawBrick(int32 index,int32 posX,int32 posY)464 bool Grid::drawBrick(int32 index, int32 posX, int32 posY) {
465 return drawBrickSprite(index, posX, posY, _brickTable[index], false);
466 }
467
drawSprite(int32 index,int32 posX,int32 posY,const uint8 * ptr)468 bool Grid::drawSprite(int32 index, int32 posX, int32 posY, const uint8 *ptr) {
469 ptr = ptr + READ_LE_INT32(ptr + index * 4);
470 return drawBrickSprite(index, posX, posY, ptr, true);
471 }
472
drawSprite(int32 posX,int32 posY,const SpriteData & ptr,int spriteIndex)473 bool Grid::drawSprite(int32 posX, int32 posY, const SpriteData &ptr, int spriteIndex) {
474 const int32 left = posX + ptr.offsetX(spriteIndex);
475 if (left > _engine->_interface->_clip.right) {
476 return false;
477 }
478 const int32 right = ptr.surface(spriteIndex).w + left;
479 if (right < _engine->_interface->_clip.left) {
480 return false;
481 }
482 const int32 top = posY + ptr.offsetY(spriteIndex);
483 if (top > _engine->_interface->_clip.bottom) {
484 return false;
485 }
486 const int32 bottom = ptr.surface(spriteIndex).h + top;
487 if (bottom < _engine->_interface->_clip.top) {
488 return false;
489 }
490
491 const Common::Point pos(left, top);
492 _engine->_frontVideoBuffer.transBlitFrom(ptr.surface(spriteIndex), pos);
493 return true;
494 }
495
496 // WARNING: Rewrite this function to have better performance
drawBrickSprite(int32 index,int32 posX,int32 posY,const uint8 * ptr,bool isSprite)497 bool Grid::drawBrickSprite(int32 index, int32 posX, int32 posY, const uint8 *ptr, bool isSprite) {
498 if (!_engine->_interface->_clip.isValidRect()) {
499 return false;
500 }
501
502 const int32 left = posX + *(ptr + 2);
503 if (left > _engine->_interface->_clip.right) {
504 return false;
505 }
506 const int32 right = *ptr + left;
507 if (right < _engine->_interface->_clip.left) {
508 return false;
509 }
510 const int32 top = posY + *(ptr + 3);
511 if (top > _engine->_interface->_clip.bottom) {
512 return false;
513 }
514 const int32 bottom = (int32)*(ptr + 1) + top;
515 if (bottom < _engine->_interface->_clip.top) {
516 return false;
517 }
518 const int32 maxY = MIN(bottom, (int32)_engine->_interface->_clip.bottom);
519
520 ptr += 4;
521
522 int32 x = left;
523
524 //if (left >= textWindowLeft-2 && top >= textWindowTop-2 && right <= textWindowRight-2 && bottom <= textWindowBottom-2) // crop
525 {
526 for (int32 y = top; y < maxY; ++y) {
527 const uint8 rleAmount = *ptr++;
528 for (int32 run = 0; run < rleAmount; ++run) {
529 const uint8 rleMask = *ptr++;
530 const uint8 iterations = bits(rleMask, 0, 6) + 1;
531 const uint8 type = bits(rleMask, 6, 2);
532 if (type == 0) {
533 x += iterations;
534 continue;
535 }
536 if (y < _engine->_interface->_clip.top || x >= _engine->_interface->_clip.right || x + iterations < _engine->_interface->_clip.left) {
537 if (type == 1) {
538 ptr += iterations;
539 } else {
540 ++ptr;
541 }
542 x += iterations;
543 continue;
544 }
545 if (type == 1) {
546 uint8 *out = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(x, y);
547 for (uint8 i = 0; i < iterations; i++) {
548 if (x >= _engine->_interface->_clip.left && x < _engine->_interface->_clip.right) {
549 *out = *ptr;
550 }
551
552 ++out;
553 ++x;
554 ++ptr;
555 }
556 } else {
557 const uint8 pixel = *ptr++;
558 uint8 *out = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(x, y);
559 for (uint8 i = 0; i < iterations; i++) {
560 if (x >= _engine->_interface->_clip.left && x < _engine->_interface->_clip.right) {
561 *out = pixel;
562 }
563
564 ++out;
565 ++x;
566 }
567 }
568 }
569 x = left;
570 }
571 }
572
573 Common::Rect rect(left, top, right, bottom);
574 _engine->_frontVideoBuffer.addDirtyRect(rect);
575
576 return true;
577 }
578
getBlockBufferGround(const IVec3 & pos,int32 & ground)579 const uint8 *Grid::getBlockBufferGround(const IVec3 &pos, int32 &ground) {
580 const IVec3 &collision = updateCollisionCoordinates(pos.x, pos.y, pos.z);
581 const uint8 *ptr = _blockBuffer
582 + collision.y * sizeof(int16)
583 + collision.x * GRID_SIZE_Y * sizeof(int16)
584 + collision.z * GRID_SIZE_X * GRID_SIZE_Y * sizeof(int16);
585
586 int32 collisionY = collision.y;
587 while (collisionY) {
588 if (READ_LE_INT16(ptr)) { // found the ground
589 break;
590 }
591 collisionY--;
592 ptr -= sizeof(int16);
593 }
594
595 _engine->_collision->_collision.y = collisionY;
596 ground = (int16)((collisionY + 1) * BRICK_HEIGHT);
597
598 return ptr;
599 }
600
getBlockPointer(int32 blockIdx,int32 brickIdx) const601 const BlockDataEntry* Grid::getBlockPointer(int32 blockIdx, int32 brickIdx) const {
602 const BlockData *blockPtr = getBlockLibrary(blockIdx);
603 return &blockPtr->entries[brickIdx];
604 }
605
getBlockLibrary(int32 blockIdx) const606 const BlockData *Grid::getBlockLibrary(int32 blockIdx) const {
607 return _currentBlockLibrary.getLayout(blockIdx - 1);
608 }
609
getBrickPos(int32 x,int32 y,int32 z)610 void Grid::getBrickPos(int32 x, int32 y, int32 z) {
611 _brickPixelPosX = (x - z) * 24 + _engine->width() / 2 - GRID_SIZE_X / 2;
612 _brickPixelPosY = ((x + z) * 12) - (y * 15) + _engine->height() / 2 - GRID_SIZE_Y;
613 }
614
drawColumnGrid(int32 blockIdx,int32 brickBlockIdx,int32 x,int32 y,int32 z)615 void Grid::drawColumnGrid(int32 blockIdx, int32 brickBlockIdx, int32 x, int32 y, int32 z) {
616 const BlockDataEntry *blockPtr = getBlockPointer(blockIdx, brickBlockIdx);
617 const uint8 brickShape = blockPtr->brickShape;
618 const uint8 brickSound = blockPtr->brickType;
619 const uint16 brickIdx = blockPtr->brickIdx;
620 if (!brickIdx) {
621 return;
622 }
623
624 getBrickPos(x - _newCamera.x, y - _newCamera.y, z - _newCamera.z);
625
626 if (_brickPixelPosX < -24) {
627 return;
628 }
629 if (_brickPixelPosX >= _engine->width()) {
630 return;
631 }
632 if (_brickPixelPosY < -38) {
633 return;
634 }
635 if (_brickPixelPosY >= _engine->height()) {
636 return;
637 }
638
639 // draw the background brick
640 drawBrick(brickIdx - 1, _brickPixelPosX, _brickPixelPosY);
641
642 int32 brickBuffIdx = (_brickPixelPosX + 24) / 24;
643
644 if (_brickInfoBuffer[brickBuffIdx] >= MAXBRICKS) {
645 warning("GRID: brick buffer exceeded");
646 return;
647 }
648
649 BrickEntry *currBrickEntry = &_bricksDataBuffer[brickBuffIdx * MAXBRICKS + _brickInfoBuffer[brickBuffIdx]];
650
651 currBrickEntry->x = x;
652 currBrickEntry->y = y;
653 currBrickEntry->z = z;
654 currBrickEntry->posX = _brickPixelPosX;
655 currBrickEntry->posY = _brickPixelPosY;
656 currBrickEntry->index = brickIdx - 1;
657 currBrickEntry->shape = brickShape;
658 currBrickEntry->sound = brickSound;
659
660 _brickInfoBuffer[brickBuffIdx]++;
661 }
662
redrawGrid()663 void Grid::redrawGrid() {
664 _camera.x = _newCamera.x * BRICK_SIZE;
665 _camera.y = _newCamera.y * BRICK_HEIGHT;
666 _camera.z = _newCamera.z * BRICK_SIZE;
667
668 memset(_brickInfoBuffer, 0, _brickInfoBufferSize);
669
670 if (!_engine->_scene->_enableGridTileRendering) {
671 return;
672 }
673
674 for (int32 z = 0; z < GRID_SIZE_Z; z++) {
675 for (int32 x = 0; x < GRID_SIZE_X; x++) {
676 for (int32 y = 0; y < GRID_SIZE_Y; y++) {
677 const BlockEntry entry = getBlockEntry(x, y, z);
678 if (entry.blockIdx) {
679 drawColumnGrid(entry.blockIdx, entry.brickBlockIdx, x, y, z);
680 }
681 }
682 }
683 }
684 }
685
getBlockEntry(int32 x,int32 y,int32 z) const686 BlockEntry Grid::getBlockEntry(int32 x, int32 y, int32 z) const {
687 const uint8 *blockBufferPtr = _blockBuffer;
688 blockBufferPtr += x * GRID_SIZE_Y * 2;
689 blockBufferPtr += y * 2;
690 blockBufferPtr += (z * GRID_SIZE_X * 2) * GRID_SIZE_Y;
691
692 BlockEntry entry;
693 entry.blockIdx = *blockBufferPtr;
694 entry.brickBlockIdx = *(blockBufferPtr + 1);
695 return entry;
696 }
697
getBrickShape(int32 x,int32 y,int32 z)698 ShapeType Grid::getBrickShape(int32 x, int32 y, int32 z) {
699 const IVec3 &collision = updateCollisionCoordinates(x, y, z);
700
701 if (collision.x < 0 || collision.x >= GRID_SIZE_X) {
702 return ShapeType::kNone;
703 }
704
705 if (collision.y <= -1) {
706 return ShapeType::kSolid;
707 }
708
709 if (collision.y < 0 || collision.y >= GRID_SIZE_Y || collision.z < 0 || collision.z >= GRID_SIZE_Z) {
710 return ShapeType::kNone;
711 }
712
713 const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
714 if (entry.blockIdx) {
715 const BlockDataEntry *blockPtr = getBlockPointer(entry.blockIdx, entry.brickBlockIdx);
716 return (ShapeType)blockPtr->brickShape;
717 }
718 return (ShapeType)entry.brickBlockIdx;
719 }
720
updateCollisionCoordinates(int32 x,int32 y,int32 z)721 const IVec3 &Grid::updateCollisionCoordinates(int32 x, int32 y, int32 z) {
722 _engine->_collision->_collision.x = (x + BRICK_HEIGHT) / BRICK_SIZE;
723 _engine->_collision->_collision.y = y / BRICK_HEIGHT;
724 _engine->_collision->_collision.z = (z + BRICK_HEIGHT) / BRICK_SIZE;
725 return _engine->_collision->_collision;
726 }
727
getBrickShapeFull(int32 x,int32 y,int32 z,int32 y2)728 ShapeType Grid::getBrickShapeFull(int32 x, int32 y, int32 z, int32 y2) {
729 const IVec3 &collision = updateCollisionCoordinates(x, y, z);
730
731 if (collision.x < 0 || collision.x >= GRID_SIZE_X) {
732 return ShapeType::kNone;
733 }
734
735 if (collision.y <= -1) {
736 return ShapeType::kSolid;
737 }
738
739 if (collision.y < 0 || collision.y >= GRID_SIZE_Y || collision.z < 0 || collision.z >= GRID_SIZE_Z) {
740 return ShapeType::kNone;
741 }
742
743 uint8 *blockBufferPtr = _blockBuffer;
744 blockBufferPtr += collision.x * GRID_SIZE_Y * 2;
745 blockBufferPtr += collision.y * 2;
746 blockBufferPtr += (collision.z * GRID_SIZE_X * 2) * GRID_SIZE_Y;
747
748 uint8 blockIdx = *blockBufferPtr;
749
750 if (blockIdx) {
751 const uint8 tmpBrickIdx = *(blockBufferPtr + 1);
752 const BlockDataEntry *blockPtr = getBlockPointer(blockIdx, tmpBrickIdx);
753 const ShapeType brickShape = (ShapeType)blockPtr->brickShape;
754
755 const int32 newY = (y2 + (BRICK_HEIGHT - 1)) / BRICK_HEIGHT;
756 int32 currY = collision.y;
757
758 for (int32 i = 0; i < newY; i++) {
759 if (currY >= GRID_SIZE_Y) {
760 return brickShape;
761 }
762
763 blockBufferPtr += 2;
764 currY++;
765
766 if (READ_LE_INT16(blockBufferPtr) != 0) {
767 return ShapeType::kSolid;
768 }
769 }
770
771 return brickShape;
772 }
773 const ShapeType brickShape = (ShapeType) * (blockBufferPtr + 1);
774
775 const int32 newY = (y2 + (BRICK_HEIGHT - 1)) / BRICK_HEIGHT;
776 int32 currY = collision.y;
777
778 for (int32 i = 0; i < newY; i++) {
779 if (currY >= GRID_SIZE_Y) {
780 return brickShape;
781 }
782
783 blockBufferPtr += 2;
784 currY++;
785
786 if (READ_LE_INT16(blockBufferPtr) != 0) {
787 return ShapeType::kSolid;
788 }
789 }
790
791 return ShapeType::kNone;
792 }
793
getBrickSoundType(int32 x,int32 y,int32 z)794 uint8 Grid::getBrickSoundType(int32 x, int32 y, int32 z) {
795 const IVec3 &collision = updateCollisionCoordinates(x, y, z);
796
797 if (collision.x < 0 || collision.x >= GRID_SIZE_X) {
798 return 0; // none
799 }
800
801 if (collision.y <= -1) {
802 return 1; // solid
803 }
804
805 if (collision.y < 0 || collision.y >= GRID_SIZE_Y || collision.z < 0 || collision.z >= GRID_SIZE_Z) {
806 return 0; // none
807 }
808
809 const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
810 if (entry.blockIdx) {
811 const BlockDataEntry *blockPtr = getBlockPointer(entry.blockIdx, entry.brickBlockIdx);
812 return blockPtr->brickType;
813 }
814
815 return 0xF0U;
816 }
817
centerOnActor(const ActorStruct * actor)818 void Grid::centerOnActor(const ActorStruct* actor) {
819 _newCamera.x = (actor->_pos.x + BRICK_HEIGHT) / BRICK_SIZE;
820 _newCamera.y = (actor->_pos.y + BRICK_HEIGHT) / BRICK_HEIGHT;
821 _newCamera.z = (actor->_pos.z + BRICK_HEIGHT) / BRICK_SIZE;
822 _engine->_redraw->_reqBgRedraw = true;
823 }
824
centerScreenOnActor()825 void Grid::centerScreenOnActor() {
826 if (_engine->_disableScreenRecenter) {
827 return;
828 }
829 if (_engine->_debugGrid->_useFreeCamera) {
830 return;
831 }
832
833 ActorStruct *actor = _engine->_scene->getActor(_engine->_scene->_currentlyFollowedActor);
834 _engine->_renderer->projectPositionOnScreen(actor->_pos.x - (_newCamera.x * BRICK_SIZE),
835 actor->_pos.y - (_newCamera.y * BRICK_HEIGHT),
836 actor->_pos.z - (_newCamera.z * BRICK_SIZE));
837 // TODO: these border values should get scaled for higher resolutions
838 if (_engine->_renderer->_projPos.x < 80 || _engine->_renderer->_projPos.x >= _engine->width() - 60 || _engine->_renderer->_projPos.y < 80 || _engine->_renderer->_projPos.y >= _engine->height() - 50) {
839 _newCamera.x = ((actor->_pos.x + BRICK_HEIGHT) / BRICK_SIZE) + (((actor->_pos.x + BRICK_HEIGHT) / BRICK_SIZE) - _newCamera.x) / 2;
840 _newCamera.y = actor->_pos.y / BRICK_HEIGHT;
841 _newCamera.z = ((actor->_pos.z + BRICK_HEIGHT) / BRICK_SIZE) + (((actor->_pos.z + BRICK_HEIGHT) / BRICK_SIZE) - _newCamera.z) / 2;
842
843 if (_newCamera.x >= GRID_SIZE_X) {
844 _newCamera.x = GRID_SIZE_X - 1;
845 }
846
847 if (_newCamera.z >= GRID_SIZE_Z) {
848 _newCamera.z = GRID_SIZE_Z - 1;
849 }
850
851 _engine->_redraw->_reqBgRedraw = true;
852 }
853 }
854
855 } // namespace TwinE
856