/** WIP raycasting demo for Pokitto. Don't forget to compile with -O3! author: Miloslav "drummyfish" Ciz license: CC0 1.0 */ // redefine player's height #define CAMERA_COLL_HEIGHT_BELOW ((3 * UNITS_PER_SQUARE) / 2) #include "general.hpp" #define LEVEL_X_RES 29 #define LEVEL_Y_RES 21 Player player; #define SPRITE_MAX_DISTANCE 5 * UNITS_PER_SQUARE #define JUMP_SPEED 500 char floorColor = 0; Vector2D selectedSquare; ///< Coords of tile selected for editing. /** Represents one terrain change against the implicit terrain. */ class Change { public: Vector2D mCoords; Unit mHeight; uint8_t mColor; }; #define MAX_CHANGES 32 Change changes[MAX_CHANGES]; bool editReleased = false; bool editing = false; uint16_t changeIndex = 0; #define HEIGHT_PROFILE_LENGTH 256 const int8_t heightProfile[] = { 9,9,9,10,10,10,11,11,12,13,13,14,14,14,15,15,15,16,16,16,16,16,15,15,15,14,14, 13,13,12,11,10,10,9,9,9,8,8,8,8,8,8,7,7,7,6,5,4,4,3,3,3,3,3,3,4,5,7,8,10,11,12, 12,13,13,13,14,16,19,20,21,21,22,22,22,23,23,23,23,22,22,21,20,19,18,18,17,17, 17,17,16,16,16,16,16,16,16,15,15,15,14,14,13,12,11,10,8,7,6,5,5,5,4,4,4,4,4,5, 5,5,6,6,7,7,7,8,8,8,8,8,8,8,7,7,7,7,6,6,5,4,4,3,2,2,2,1,1,1,1,2,3,5,6,7,9,13, 13,13,18,18,22,22,22,22,22,22,21,21,20,20,19,16,16,16,14,14,14,15,15,15,15,15, 16,17,17,18,18,18,16,16,16,16,15,15,15,14,14,13,12,8,7,7,7,8,9,11,12,12,13,13, 13,12,11,10,10,10,10,11,11,12,13,13,12,12,11,11,10,10,9,9,9,9,9,9,10,10,11,12, 13,13,14,14,14,14,14,13,13,12,11,11,11,11,10,10,10,10}; const unsigned char imageBackground[] = { 55, 44 // width, height ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xff,0xd2,0xd2,0xd2,0xd2,0xd2,0x69 ,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb ,0xdb,0xff,0xff,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x69,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc,0xbc,0xdb ,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb ,0xdb,0xdb,0xff,0xff,0xff,0xff,0xbc,0xbc,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xff ,0xff,0xbc,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0x3b ,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xff,0xff,0xbc,0xdb,0xdb,0xd2,0xd2 ,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xff,0xff ,0xff,0xff,0xff,0xbc,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b ,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc,0xbc,0xdb,0xdb ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb ,0xdb,0xff,0xff,0xbc,0xbc,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc,0xdb ,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0xdb,0xdb ,0xdb,0xdb,0xff,0xff,0xff,0xff,0xff,0xbc,0xdb,0xdb,0xbc,0xd2,0xd2,0xd2 ,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc ,0xff,0xbc,0xdb,0xdb,0xff,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b ,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xbc,0xff,0xff,0xbc,0xbc,0xff,0xd2 ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xff ,0xbc,0xbc,0xff,0xff,0xbc,0xff,0xff,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xbc,0xdb,0xff,0xff,0xff,0xff ,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xbc,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0x69,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff ,0xbc,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xbc,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xff,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xbc,0xd2 ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xbc,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x69,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xbc ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0xbc,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0x3b,0x3b,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0xbc,0xbc ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b ,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0xff,0xff,0xbc,0xdb,0xbc,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b ,0x3b,0x3b,0xff,0xff,0xbc,0xdb,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0xff,0xbc,0xbc,0xdb ,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x3b,0x3b,0x3b,0x3b,0xff,0xbc,0xbc,0xdb,0xff,0xbc,0xbc,0xdb,0xdb,0xdb ,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xff,0xbc ,0xbc,0xbc,0xff,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xbc,0xbc,0xff,0xbc,0xff,0xff,0xbc,0xdb ,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b ,0xbc,0xdb,0xff,0xff,0xff,0xff,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0xbc,0xdb,0xff,0xff,0xff,0xbc ,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b ,0x3b,0x3b,0xbc,0xdb,0xff,0xff,0xdb,0xbc,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0xbc,0xdb,0xff,0xbc ,0xdb,0xbc,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0xd2,0x69,0x3b,0x3b,0xdb,0xdb,0xff,0xdb,0xdb,0xdb,0xbc,0xdb,0xdb,0xdb ,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xbc,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2 ,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b ,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2 ,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xbc,0xd2,0xd2,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xbc,0xbc,0xd2,0xd2,0xd2 ,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xff,0xff,0xbc,0xbc,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc ,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xbc,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b ,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff ,0xff,0xff,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xff,0xd2,0xd2,0xd2,0xd2 ,0xd2,0x3b,0x3b,0x3b,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb ,0xdb,0xdb,0xff,0xff,0xd2,0xd2,0xd2,0xd2,0xd2,0x69,0x3b,0x3b,0xdb,0xdb ,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xff,0xff,0xd2,0xd2 ,0xd2,0xd2,0xd2,0xd2,0x3b,0x3b }; Unit floorHeightAt(int16_t x, int16_t y) { for (uint16_t i = 0; i < MAX_CHANGES; ++i) if (changes[i].mCoords.x == x && changes[i].mCoords.y == y) return changes[i].mHeight; return (heightProfile[absVal(x) % HEIGHT_PROFILE_LENGTH] + heightProfile[absVal(y + 20) % HEIGHT_PROFILE_LENGTH]) * UNITS_PER_SQUARE; } uint16_t previousColumn = 255; uint16_t backgroundColumn = 0; /** Function for drawing a single pixel (like fragment shader). */ inline void pixelFunc(PixelInfo pixel) { uint8_t c = 0; int16_t intensity = 0; if (pixel.isWall) { c = pixel.hit.square.x != selectedSquare.x || pixel.hit.square.y != selectedSquare.y || (editing && pokitto.frameCount % 2) == 0 ? 50 : 30; intensity = pixel.depth / (UNITS_PER_SQUARE * 3); intensity += pixel.hit.direction % 2 == 0 ? 2 : 0; } else if (pixel.isFloor) { c = floorColor; if (!pixel.isHorizon) intensity = pixel.depth / (UNITS_PER_SQUARE * 3); } else { if (previousColumn == pixel.position.x) { c = imageBackground[2 + backgroundColumn * 22 + min(pixel.position.y,43) / 2]; } else { backgroundColumn = absVal(pixel.position.x / 2 + (110 * player.mCamera.direction) / UNITS_PER_SQUARE) % 55; previousColumn = pixel.position.x; } } if (intensity != 0) c = addIntensity(c,intensity); uint8_t *buf = pokitto.display.screenbuffer; buf += pixel.position.x * SUBSAMPLE; buf += pixel.position.y * SCREEN_WIDTH; for (uint8_t i = 0; i < SUBSAMPLE - 1; ++i) *buf++ = c; *buf = c; } void draw() { RayConstraints c; c.maxHits = 6; c.maxSteps = 20; c.computeTextureCoords = 0; render(player.mCamera,floorHeightAt,0,0,pixelFunc,c); /* pokitto.display.setColor(rgbToIndex(7,7,3)); pokitto.display.setCursor(1,1); pokitto.display.print(player.mCamera.position.x); pokitto.display.print(" "); pokitto.display.print(player.mCamera.position.y); pokitto.display.print(" "); pokitto.display.print(player.mCamera.direction); */ } int main() { initGeneral(); floorColor = rgbToIndex(4,2,0); player.setPositionSquare(4,5); for (uint16_t i = 0; i < MAX_CHANGES; ++i) { changes[i].mCoords.x = 0; changes[i].mCoords.y = 0; changes[i].mHeight = floorHeightAt(0,0); } uint32_t previousTime = 0; uint32_t dt; while (pokitto.isRunning()) { if (pokitto.update()) { draw(); uint32_t timeNow = pokitto.getTime(); dt = timeNow - previousTime; previousTime = timeNow; int16_t horizontalStep = (dt * PLAYER_SPEED * (player.mRunning ? 2 : 1)) / 1000; int16_t rotationStep = (dt * PLAYER_ROTATION_SPEED) / 1000; Vector2D d = angleToDirection(player.mCamera.direction); Vector2D facingOffset; if (player.mCamera.direction > (4 * UNITS_PER_SQUARE / 12) && player.mCamera.direction <= (8 * UNITS_PER_SQUARE / 12)) facingOffset.x = -1; else if (player.mCamera.direction < (2 * UNITS_PER_SQUARE / 12) || player.mCamera.direction >= (10 * UNITS_PER_SQUARE / 12)) facingOffset.x = 1; else facingOffset.x = 0; if (player.mCamera.direction > (UNITS_PER_SQUARE / 12) && player.mCamera.direction <= (5 * UNITS_PER_SQUARE / 12)) facingOffset.y = -1; else if (player.mCamera.direction > (6 * UNITS_PER_SQUARE / 12) && player.mCamera.direction <= (11 * UNITS_PER_SQUARE / 12)) facingOffset.y = 1; else facingOffset.y = 0; selectedSquare.x = divRoundDown(player.mCamera.position.x,UNITS_PER_SQUARE) + facingOffset.x; selectedSquare.y = divRoundDown(player.mCamera.position.y,UNITS_PER_SQUARE) + facingOffset.y; Vector2D moveOffset; moveOffset.x = 0; moveOffset.y = 0; d.x = (d.x * horizontalStep) / UNITS_PER_SQUARE; d.y = (d.y * horizontalStep) / UNITS_PER_SQUARE; if (d.x == 0 && d.y == 0) { d.x = d.x > 0 ? horizontalStep : -1 * horizontalStep; d.y = d.y > 0 ? horizontalStep : -1 * horizontalStep; } bool aButton = pokitto.aBtn(); if (pokitto.cBtn()) { if (editReleased) { editing = !editing; changeIndex = (changeIndex + 1) % MAX_CHANGES; for (uint16_t i = 0; i < MAX_CHANGES; ++i) if (changes[i].mCoords.x == selectedSquare.x && changes[i].mCoords.y == selectedSquare.y) { changeIndex = i; break; } changes[changeIndex].mHeight = floorHeightAt(selectedSquare.x,selectedSquare.y); changes[changeIndex].mCoords.x = selectedSquare.x; changes[changeIndex].mCoords.y = selectedSquare.y; editReleased = false; } } else editReleased = true; if (pokitto.upBtn()) { if (editing) { changes[changeIndex].mHeight += 100; } else if (aButton) player.mCamera.shear = min(player.mCamera.shear + 10,60); else moveOffset = d; } else if (pokitto.downBtn()) { if (editing) { changes[changeIndex].mHeight -= 100; } else if (aButton) player.mCamera.shear = max(player.mCamera.shear - 10,-60); else { moveOffset.x = -1 * d.x; moveOffset.y = -1 * d.y; } } else player.mCamera.shear /= 2; if (!aButton) player.mCamera.shear /= 2; int addition = 0; if (pokitto.rightBtn()) addition = 1; else if (pokitto.leftBtn()) addition = -1; if (aButton) { d = angleToDirection(player.mCamera.direction + UNITS_PER_SQUARE / 4); d.x = (d.x * horizontalStep * addition) / UNITS_PER_SQUARE; d.y = (d.y * horizontalStep * addition) / UNITS_PER_SQUARE; moveOffset = d; } else player.mCamera.direction = wrap(player.mCamera.direction + addition * rotationStep,UNITS_PER_SQUARE); Unit prevHeight = player.mCamera.height; moveCameraWithCollision(&player.mCamera,moveOffset,player.mVericalSpeed, floorHeightAt, 0, 1); Unit heightDiff = player.mCamera.height - prevHeight; if (heightDiff == 0) player.mVericalSpeed = 0; // hit floor/ceiling if (player.mVericalSpeed == 0 && pokitto.bBtn()) { int16_t camX = divRoundDown(player.mCamera.position.x,UNITS_PER_SQUARE); int16_t camY = divRoundDown(player.mCamera.position.y,UNITS_PER_SQUARE); if (player.mCamera.height - CAMERA_COLL_HEIGHT_BELOW - floorHeightAt(camX,camY) < 2) player.mVericalSpeed = JUMP_SPEED; // jump } player.mVericalSpeed -= (dt * GRAVITY_ACCELERATION) / 1000; // gravity } } return 0; }