Ray casting engine implemented on the mBuino platform using the ST7735 LCD controller.
Ray casting engine written to test performance of the LCD_ST7735 library I wrote as a learning exercise on the mbed platform.
Diff: Raycaster.cpp
- Revision:
- 0:303768292f44
- Child:
- 1:fdbc2be25831
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Raycaster.cpp Sun Sep 21 22:04:22 2014 +0000 @@ -0,0 +1,236 @@ +#include "mbed.h" +#include "common.h" +#include "LCD_ST7735.h" +#include "Raycaster.h" + +Raycaster::Raycaster(int left, int top, int width, int height, + int viewerDistance, int viewerHeight, + const uint8_t *pMap, int mapWidth, int mapHeight, + const uint16_t *pPalette) : + _display(P0_15, // backlight + P0_10, // reset + P0_18, // ds + P0_21, // mosi + P0_22, // miso + P1_15, // clk + P0_19),// cs + _pMap(pMap), + _mapWidth(mapWidth), + _mapHeight(mapHeight), + _pPalette(pPalette), + _left(left), + _width(width), + _top(top), + _height(height) +{ + _display.setOrientation(LCD_ST7735::Rotate90, false); + _display.clearScreen(); + + _right = left + width; + _halfWidth = width >> 1; + _horizCenter = left + _halfWidth; + + _bottom = top + height; + _halfHeight = height >> 1; + _vertCenter = top + _halfHeight; + + _viewerDistance = viewerDistance; + _viewerHeight = viewerHeight; + + _viewVolume = 60 * PI / 180; + _halfViewVolume = _viewVolume / 2; + + _ainc = _viewVolume / _width; + + _viewDistanceTimesHeight = _viewerDistance * _viewerHeight; + _heightRatio = (_viewerDistance << CELL_SIZE_SHIFT); + _pSlivers = new Sliver[_width >> 1]; +} + +void Raycaster::setCellPosition(int x, int y) +{ + _playerX = x << CELL_SIZE_SHIFT; + _playerY = y << CELL_SIZE_SHIFT; + _playerViewAngle = 0; +} + +void Raycaster::rotate(float radians) +{ + _playerViewAngle += radians; + if (_playerViewAngle > PI2) _playerViewAngle -= PI2; + if (_playerViewAngle < 0) _playerViewAngle += PI2; +} + +void Raycaster::move(int distance) +{ + int mx; + int my; + + // Calculate the change in x and y coordinates + float dx = sin(_playerViewAngle) * distance; + float dy = cos(_playerViewAngle) * -distance; + + // Check for collisions with walls + float nx = _playerX + (dx * 4); + float ny = _playerY + (dy * 4); + + // Check for wall in the x direction and move if open + mx = ((int)nx) >> CELL_SIZE_SHIFT; + my = ((int)_playerY) >> CELL_SIZE_SHIFT; + if (_pMap[mx + (my * _mapWidth)] == 0) + { + _playerX += dx; + } + + // Check for wall in the y direction and move if open + mx = ((int)_playerX) >> CELL_SIZE_SHIFT; + my = ((int)ny) >> CELL_SIZE_SHIFT; + if (_pMap[mx + (my * _mapWidth)] == 0) + { + _playerY += dy; + } +} + +void Raycaster::renderFrame() +{ + int xd, yd; + int grid_x, grid_y; + float xcross_x, xcross_y; + float ycross_x, ycross_y; + int xmaze, ymaze; + float distance; + int tmcolumn; + uint8_t cellValue; + + int xview = FLOAT2INT(_playerX); + int yview = FLOAT2INT(_playerY); + + float columnAngle = _halfViewVolume; + float ainc2 = _ainc * 2; + Sliver *pSliver = _pSlivers; + for (int column = 0; column < _width; column += 2, columnAngle -= ainc2, pSliver++) + { + float radians = _playerViewAngle - columnAngle; + + int xdiff = FLOAT2INT(CELL_SIZE * sin(radians)); + int ydiff = FLOAT2INT(-CELL_SIZE * cos(radians)); + + if (xdiff == 0) xdiff = 1; + + float slope = (float)ydiff / xdiff; + if (slope == 0.0f) slope = 0.001f; + + int x = xview; + int y = yview; + + for(;;) + { + if (xdiff > 0) + { + grid_x = ((int)x & CELL_BIT_MASK) + CELL_SIZE; + } + else + { + grid_x = ((int)x & CELL_BIT_MASK) - 1; + } + + if (ydiff > 0) + { + grid_y = ((int)y & CELL_BIT_MASK) + CELL_SIZE; + } + else + { + grid_y = ((int)y & CELL_BIT_MASK) - 1; + } + + xcross_x = grid_x; + xcross_y = y + slope * (grid_x - x); + + ycross_x = x + (grid_y - y) / slope; + ycross_y = grid_y; + + xd = xcross_x - x; + yd = xcross_y - y; + int xdist = (xd * xd) + (yd * yd); + + xd = ycross_x - x; + yd = ycross_y - y; + int ydist = (xd * xd) + (yd * yd); + + if (xdist < ydist) + { + xmaze = (int)xcross_x >> CELL_SIZE_SHIFT; + ymaze = (int)xcross_y >> CELL_SIZE_SHIFT; + + x = xcross_x; + y = xcross_y; + + tmcolumn = (int)y & CELL_SIZE_MASK; + } + else + { + xmaze = (int)ycross_x >> CELL_SIZE_SHIFT; + ymaze = (int)ycross_y >> CELL_SIZE_SHIFT; + + x = ycross_x; + y = ycross_y; + + tmcolumn = (int)x & CELL_SIZE_MASK; + } + + cellValue = _pMap[xmaze + (ymaze * _mapWidth)]; + if (cellValue != 0) break; + } + + xd = x - xview; + yd = y - yview; + distance = sqrt(((xd * xd) + (yd * yd)) * cos(columnAngle)); + if (distance == 0) distance = 1; + + int height = _heightRatio / distance; + if (height == 0) height = 1; + + int bot = _viewDistanceTimesHeight / distance + _vertCenter; + int top = bot - height; + + int t = tmcolumn; + int dheight = height; + int iheight = CELL_SIZE; + float yratio = (float)CELL_SIZE / height; + + if (top < _top) + { + int clipBy = _top - top; + int clipRatio = (int)(clipBy * yratio); + dheight-= clipBy; + t += clipRatio << CELL_SIZE_SHIFT; + iheight -= clipRatio; + top = _top; + } + + if (bot >= _bottom) + { + int clipBy = bot - _bottom; + dheight -= clipBy; + iheight -= (int)(clipBy * yratio); + bot = _bottom; + } + + uint16_t color = 0xf800; + + color = _pPalette[cellValue]; + + pSliver->top = top; + pSliver->bottom = bot; + pSliver->color = color; + } + + int x = _left; + pSliver = _pSlivers; + for(int column = 0; column < _width; column += 2, x += 2, pSliver++) + { + _display.fillRect(x, _top, x + 1, pSliver->top, *_pPalette); + _display.fillRect(x, pSliver->top, x + 1, pSliver->bottom, pSliver->color); + _display.fillRect(x, pSliver->bottom, x + 1, _bottom, *_pPalette); + } +} \ No newline at end of file