From 6e33223035b96a054bbf2bbb62feaac26df95745 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Wed, 2 Sep 2026 14:38:11 +0200 Subject: [PATCH] Limit movement based on fps Signed-off-by: Hans Kokx --- raycaster.c | 350 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 321 insertions(+), 29 deletions(-) diff --git a/raycaster.c b/raycaster.c index 464813d..61e7b32 100644 --- a/raycaster.c +++ b/raycaster.c @@ -46,17 +46,46 @@ typedef struct } ButtonKeys; ButtonKeys Keys; +// * ---------------- HELPERS -------------------------------------------------- + // Function to check if a number is a power of 2 int isPowerOfTwo(int n) { return (n > 0) && ((n & (n - 1)) == 0); } -// * Player position (playerX, playerY), delta (playerDeltaX, playerDeltaY), and angle (pa) +float degToRad(float a) { return a * M_PI / 180.0; } + +float FixAng(float a) +{ + if (a > 359) + { + a -= 360; + } + + if (a < 0) + { + a += 360; + } + + return a; +} + +// * Returns the distance between the player and the ray's endpoint +// That's the hypotenuse, so we can use the Pythagorean theorem. +float distance(float ax, float ay, float bx, float by, float ang) +{ + // SOH - CAH - TOA + return (sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay))); +} + +// * ---------------- PLAYER --------------------------------------------------- + +// Player position (playerX, playerY), delta (playerDeltaX, playerDeltaY), and angle (pa) float playerX, playerY, playerDeltaX, playerDeltaY, playerAngle; -// * Draw the player on the screen -void drawPlayer() +// Draw the player on the map +void drawPlayer2D() { // Draws the player square glColor3f(1, 1, 0); @@ -66,13 +95,271 @@ void drawPlayer() glEnd(); // Draws the player direction - glLineWidth(3); + glLineWidth(4); glBegin(GL_LINES); glVertex2i(playerX, playerY); glVertex2i(playerX + playerDeltaX * 5, playerY + playerDeltaY * 5); glEnd(); } +// * ---------------- Draw Rays and Walls -------------------------------------- + +void drawRays2D() +{ + int r, mx, my, mapPoint, dof; + + // Ensure mapSize is a power of 2, otherwise default or handle error + if (!isPowerOfTwo(mapSize)) + { + fprintf(stderr, "Error: mapSize must be a power of 2 (e.g., 16, 32, 64, 128).\n"); + exit(1); + } + + int shiftSize = (int)(log2f((float)mapSize)); + + glColor3f(0, 1, 1); + glBegin(GL_QUADS); + glVertex2i(526, 0); + glVertex2i(1006, 0); + glVertex2i(1006, 160); + glEnd(); + + glColor3f(0, 0, 1); + glBegin(GL_QUADS); + glVertex2i(526, 160); + glVertex2i(1006, 160); + glVertex2i(1006, 320); + glEnd(); + + // Ray x, y, angle + float rayX, rayY, rayAngle; + + float xo, yo; + + // Final distance + float disT; + + // Let's initialize the ray angle n degrees from the player's facing position + // ra = ray angle; pa = player angle; DR = degree radian; n = degrees to use + rayAngle = playerAngle - DR * 30; + // rayAngle = FixAng(playerAngle+30); + + // Make sure to limit the next angle + if (rayAngle < 0) + { + rayAngle += 2 * PI; + } + if (rayAngle > 2 * PI) + { + rayAngle -= 2 * PI; + } + + // * Cast n rays + for (r = 0; r < 60; r++) + { + // * --- Check Horizontal Lines --- + dof = 0; + + // We need to get the shortest ray cast in either the vertical or horizontal + // distance. So, we'll set a default that's way higher than we'll ever + // calculate, and save the horizontal ray's X and Y position. + float distanceHor = 1000000, hx = playerX, hy = playerY; + + // Negative inverse of tangent + float aTan = -1.0f / tanf(rayAngle); + + // Check if the ray is looking up or down. + // If it's less than π (e.g., 180°), we're looking down. + + // ? Looking up (ray angle > π) + if (rayAngle > PI) + { + // Round the ray's Y position to the nearest 64th value. + // We can do this by bit-shifting down by `shiftSize`, then bit-shifting back up by + // `shiftSize`. Then, we subtract a small amount for accuracy. + rayY = (((int)playerY >> shiftSize) << shiftSize) - 0.0001f; + + // The ray's X value is the distance between the player and the ray's Y + // position, times the inverse of tangent, plus the player's X position. + rayX = (playerY - rayY) * aTan + playerX; + + // Now that we have the ray's first grid intersection position, we need + // the next X and Y offset for the second grid intersection point. + yo = -mapSize; + xo = -yo * aTan; + } + + // ? Looking down + if (rayAngle < PI) + { + // This is almost exactly the same as looking up, but we add one grid + // unit, instead of subtracting a tiny amount. + rayY = (((int)playerY >> shiftSize) << shiftSize) + mapSize; + + rayX = (playerY - rayY) * aTan + playerX; + + // This is almost exactly the same as looking up, but we add instead of + // subtract. + yo = mapSize; + xo = -yo * aTan; + } + + // ? Looking straight left or right + if (rayAngle == 0.0f || rayAngle == PI) + { + // It's impossible to ever hit a horizontal line + rayX = playerX; + rayY = playerY; + } + + int mv = 0, mh = 0; + + // ? We don't want to get caught in an infinite loop, so use the + // depth-of-field to limit our check to a max of 8. + while (dof < 8) + { + // We know the coordinates where the ray will hit the wall, but we need + // to know where that is in the map array. + // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift + // 6 places), and use that to find the position in the map's array. + mx = (int)(rayX) >> shiftSize; + my = (int)(rayY) >> shiftSize; + mapPoint = my * mapX + mx; + + // If the map position is less than the array's size, then we can check it + // inside the map. If the value is 1, there's a wall. If we hit a wall, + // we're done! + if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) + { + // We hit a wall! + + mh = map[mapPoint]; + + // Save the values and calculate the distance from the player + hx = rayX; + hy = rayY; + distanceHor = distance(playerX, playerY, hx, hy, rayAngle); + + dof = 8; // Break the loop - no need to calculate more. + } + else + { + // If we didn't hit a wall, we check the next horizontal line by adding + // the x and y offset. + rayX += xo; + rayY += yo; + dof += 1; + } + } + + // * --- Check Vertical Lines --- + dof = 0; + float distanceVert = 1000000, vx = playerX, vy = playerY; + + // Negative of tangent + float nTan = -tanf(rayAngle); + + // ? Looking left + if (rayAngle > PI2 && rayAngle < PI3) + { + rayX = (((int)playerX >> shiftSize) << shiftSize) - 0.0001f; + rayY = (playerX - rayX) * nTan + playerY; + xo = -mapSize; + yo = -xo * nTan; + } + + // ? Looking right + if (rayAngle < PI2 || rayAngle > PI3) + { + rayX = (((int)playerX >> shiftSize) << shiftSize) + mapSize; + rayY = (playerX - rayX) * nTan + playerY; + xo = mapSize; + yo = -xo * nTan; + } + + // ? Looking straight up or down + if (rayAngle == 0.0f || rayAngle == PI) + { + rayX = playerX; + rayY = playerY; + } + + while (dof < 8) + { + // We know the coordinates where the ray will hit the wall, but we need + // to know where that is in the map array. + // Let's take the ray hit position (rx,ry) and divide it by 64 (bit-shift + // 6 places), and use that to find the position in the map's array. + mx = (int)(rayX) >> shiftSize; + my = (int)(rayY) >> shiftSize; + mapPoint = my * mapX + mx; + + // If the map position is less than the array's size, then we can check it + // inside the map. If the value is 1, there's a wall. If we hit a wall, + // we're done! + if (mapPoint > 0 && mapPoint < mapX * mapY && map[mapPoint] > 0) + { + // We hit a wall! + + mv = map[mapPoint]; + + // Save the values and calculate the distance from the player + vx = rayX; + vy = rayY; + distanceVert = distance(playerX, playerY, vx, vy, rayAngle); + + dof = 8; // Break the loop - no need to calculate more. + } + else + { + // If we didn't hit a wall, we check the next horizontal line by adding + // the x and y offset. + rayX += xo; + rayY += yo; + dof += 1; + } + } + + // * Compare shortest horizontal and vertical line, then use the shortest + // This is what actually determines our ray that hits a wall! + + // Vertical wall hit + if (distanceVert < distanceHor) + { + rayX = vx; + rayY = vy; + disT = distanceVert; + } + + // Horizontal wall hit + if (distanceHor < distanceVert) + { + rayX = hx; + rayY = hy; + disT = distanceHor; + } + + // * Draw the ray + glColor3f(0, 1, 1); + glLineWidth(3); + glBegin(GL_LINES); + glVertex2i(playerX, playerY); + glVertex2i(rayX, rayY); + glEnd(); + + // * Increase the ray angle to draw the next ray + rayAngle += DR; + if (rayAngle < 0) + { + rayAngle += 2 * PI; + } + if (rayAngle > 2 * PI) + { + rayAngle -= 2 * PI; + } + } +} + // * Draws the top-down map with white alls and black floors void drawMap2D() { @@ -108,14 +395,6 @@ void drawMap2D() } } -// * Returns the distance between the player and the ray's endpoint -// That's the hypotenuse, so we can use the Pythagorean theorem. -float dist(float ax, float ay, float bx, float by, float ang) -{ - // SOH - CAH - TOA - return (sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay))); -} - // * The actual raycaster void drawRays3D() { @@ -237,7 +516,7 @@ void drawRays3D() // Save the values and calculate the distance from the player hx = rayX; hy = rayY; - distanceHor = dist(playerX, playerY, hx, hy, rayAngle); + distanceHor = distance(playerX, playerY, hx, hy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } @@ -305,7 +584,7 @@ void drawRays3D() // Save the values and calculate the distance from the player vx = rayX; vy = rayY; - distanceVert = dist(playerX, playerY, vx, vy, rayAngle); + distanceVert = distance(playerX, playerY, vx, vy, rayAngle); dof = 8; // Break the loop - no need to calculate more. } @@ -352,13 +631,6 @@ void drawRays3D() } } - // * Draw the ray - glLineWidth(3); - glBegin(GL_LINES); - glVertex2i(playerX, playerY); - glVertex2i(rayX, rayY); - glEnd(); - // * --- Draw 3D Walls --- // Correct the fish-eye effect @@ -462,9 +734,26 @@ void drawFloor() glEnd(); } +// * ---------------- MAIN ----------------------------------------------------- + +float frame1, frame2, fps; + // * Main rendering void display() { + // Frames per second + frame2 = glutGet(GLUT_ELAPSED_TIME); + + if (frame1 < 0) + { + frame1 = 10; + } + + fps = (frame2 - frame1); + frame1 = glutGet(GLUT_ELAPSED_TIME); + + float movementMultiplier = 0.05 * fps; + // * Handle keyboard inputs // ? Move left if (Keys.a == 1) @@ -475,7 +764,7 @@ void display() // 0° -> 360° ≈ 0 -> ~6.28 // Change player angle slightly (using radians) - playerAngle -= 0.1; + playerAngle -= 0.1 * movementMultiplier; // If player angle drops below 0, let's reset back to 2π if (playerAngle < 0) @@ -492,7 +781,7 @@ void display() // ? Move right if (Keys.d == 1) { - playerAngle += 0.1; + playerAngle += 0.1 * movementMultiplier; if (playerAngle > 2 * PI) { @@ -508,24 +797,27 @@ void display() { // We change the X and Y values based on our delta, which is calculated // during our rotation. - playerX += playerDeltaX; - playerY += playerDeltaY; + playerX += playerDeltaX * movementMultiplier; + playerY += playerDeltaY * movementMultiplier; } // ? Move backwards if (Keys.s == 1) { - playerX -= playerDeltaX; - playerY -= playerDeltaY; + playerX -= playerDeltaX * movementMultiplier; + playerY -= playerDeltaY * movementMultiplier; } glutPostRedisplay(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + drawMap2D(); + drawRays2D(); + drawPlayer2D(); + drawCeiling(); drawFloor(); - drawMap2D(); drawRays3D(); - drawPlayer(); glutSwapBuffers(); }