- AI Research Engineer (NLP Specialist) in WRTN-Technologies
- Former Research Scientist(NLP) in Riiid
- Former NLP Engineer in TUNiB
- Bachelor of German Langauge and Literature in Yonsei University
- Large Language Models
- Data-centric AI
Porting Lode's Computer Graphics Tutorial - Raycasting to C and Minilibx for 42 Subject Cub3D
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* 05_sprite_raycast.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: hakang <[email protected]> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2020/06/29 20:28:54 by yohlee #+# #+# */
/* Updated: 2020/07/27 13:57:18 by hakang ### ########.fr */
/* */
/* ************************************************************************** */
#include "mlx/mlx.h"
#include "key_macos.h"
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define X_EVENT_KEY_PRESS 2
# define X_EVENT_KEY_RELEASE 3
#define X_EVENT_KEY_EXIT 17
#define texWidth 64
#define texHeight 64
#define mapWidth 24
#define mapHeight 24
#define width 640
#define height 480
#define numSprites 19
typedef struct s_img
{
void *img;
int *data;
int size_l;
int bpp;
int endian;
int img_width;
int img_height;
} t_img;
struct Sprite
{
double x;
double y;
int texture;
};
struct Sprite sprite[numSprites] =
{
{20.5, 11.5, 10}, //green light in front of playerstart
//green lights in every room
{18.5,4.5, 10},
{10.0,4.5, 10},
{10.0,12.5,10},
{3.5, 6.5, 10},
{3.5, 20.5,10},
{3.5, 14.5,10},
{14.5,20.5,10},
//row of pillars in front of wall: fisheye test
{18.5, 10.5, 9},
{18.5, 11.5, 9},
{18.5, 12.5, 9},
//some barrels around the map
{21.5, 1.5, 8},
{15.5, 1.5, 8},
{16.0, 1.8, 8},
{16.2, 1.2, 8},
{3.5, 2.5, 8},
{9.5, 15.5, 8},
{10.0, 15.1,8},
{10.5, 15.8,8},
};
int spriteOrder[numSprites];
double spriteDistance[numSprites];
typedef struct s_info
{
double posX;
double posY;
double dirX;
double dirY;
double planeX;
double planeY;
void *mlx;
void *win;
int key_a;
int key_w;
int key_s;
int key_d;
int key_esc;
t_img img;
int buf[height][width];
double zBuffer[width];
int **texture;
double moveSpeed;
double rotSpeed;
} t_info;
typedef struct s_pair
{
double first;
int second;
} t_pair;
void key_update(t_info *info);
static int compare(const void *first, const void *second)
{
if (*(int *)first > *(int *)second)
return (1);
else if (*(int *)first < *(int *)second)
return (-1);
else
return (0);
}
void sort_order(t_pair *orders, int amount)
{
t_pair tmp;
for (int i = 0; i < amount; i++)
{
for (int j = 0; j < amount - 1; j++)
{
if (orders[j].first > orders[j + 1].first)
{
tmp.first = orders[j].first;
tmp.second = orders[j].second;
orders[j].first = orders[j + 1].first;
orders[j].second = orders[j + 1].second;
orders[j + 1].first = tmp.first;
orders[j + 1].second = tmp.second;
}
}
}
}
void sortSprites(int *order, double *dist, int amount)
{
t_pair *sprites;
//std::vector<std::pair<double, int>> sprites(amount);
sprites = (t_pair*)malloc(sizeof(t_pair) * amount);
for (int i = 0; i < amount; i++)
{
sprites[i].first = dist[i];
sprites[i].second = order[i];
}
sort_order(sprites, amount);
//std::sort(sprites.begin(), sprites.end());
for (int i = 0; i < amount; i++)
{
dist[i] = sprites[amount - i - 1].first;
order[i] = sprites[amount - i - 1].second;
}
free(sprites);
}
int worldMap[mapWidth][mapHeight] =
{
{8,8,8,8,8,8,8,8,8,8,8,4,4,6,4,4,6,4,6,4,4,4,6,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6},
{8,0,3,3,0,0,0,0,0,8,8,4,0,0,0,0,0,0,0,0,0,0,0,4},
{8,0,0,0,0,0,0,0,0,0,8,4,0,0,0,0,0,6,6,6,0,6,4,6},
{8,8,8,8,0,8,8,8,8,8,8,4,4,4,4,4,4,6,0,0,0,0,0,6},
{7,7,7,7,0,7,7,7,7,0,8,0,8,0,8,0,8,4,0,4,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,0,0,0,0,0,6},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,0,0,0,0,4},
{7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,6,0,6,0,6,0,6},
{7,7,0,0,0,0,0,0,7,8,0,8,0,8,0,8,8,6,4,6,0,6,6,6},
{7,7,7,7,0,7,7,7,7,8,8,4,0,6,8,4,8,3,3,3,0,3,3,3},
{2,2,2,2,0,2,2,2,2,4,6,4,0,0,6,0,6,3,0,0,0,0,0,3},
{2,2,0,0,0,0,0,2,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{2,0,0,0,0,0,0,0,2,4,0,0,0,0,0,0,4,3,0,0,0,0,0,3},
{1,0,0,0,0,0,0,0,1,4,4,4,4,4,6,0,6,3,3,0,0,0,3,3},
{2,0,0,0,0,0,0,0,2,2,2,1,2,2,2,6,6,0,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5},
{2,0,0,0,0,0,0,0,2,0,0,0,0,0,2,5,0,5,0,5,0,5,0,5},
{2,2,0,0,0,0,0,2,2,2,0,0,0,2,2,0,5,0,5,0,0,0,5,5},
{2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,5,5,5,5,5,5,5,5,5}
};
void draw(t_info *info)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
info->img.data[y * width + x] = info->buf[y][x];
}
}
mlx_put_image_to_window(info->mlx, info->win, info->img.img, 0, 0);
}
void calc(t_info *info)
{
//FLOOR CASTING
for(int y = height / 2 + 1; y < height; ++y)
{
// rayDir for leftmost ray (x = 0) and rightmost ray (x = w)
float rayDirX0 = info->dirX - info->planeX;
float rayDirY0 = info->dirY - info->planeY;
float rayDirX1 = info->dirX + info->planeX;
float rayDirY1 = info->dirY + info->planeY;
// Current y position compared to the center of the screen (the horizon)
int p = y - height / 2;
// Vertical position of the camera.
float posZ = 0.5 * height;
// Horizontal distance from the camera to the floor for the current row.
// 0.5 is the z position exactly in the middle between floor and ceiling.
float rowDistance = posZ / p;
// calculate the real world step vector we have to add for each x (parallel to camera plane)
// adding step by step avoids multiplications with a weight in the inner loop
float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / width;
float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / width;
// real world coordinates of the leftmost column. This will be updated as we step to the right.
float floorX = info->posX + rowDistance * rayDirX0;
float floorY = info->posY + rowDistance * rayDirY0;
for(int x = 0; x < width; ++x)
{
// the cell coord is simply got from the integer parts of floorX and floorY
int cellX = (int)(floorX);
int cellY = (int)(floorY);
// get the texture coordinate from the fractional part
int tx = (int)(texWidth * (floorX - cellX)) & (texWidth - 1);
int ty = (int)(texHeight * (floorY - cellY)) & (texHeight - 1);
floorX += floorStepX;
floorY += floorStepY;
// choose texture and draw the pixel
int checkerBoardPattern = (int)(cellX + cellY) & 1;
int floorTexture;
if(checkerBoardPattern == 0) floorTexture = 3;
else floorTexture = 4;
int ceilingTexture = 6;
int color;
// floor
color = info->texture[floorTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
info->buf[y][x] = color;
//ceiling (symmetrical, at height - y - 1 instead of y)
color = info->texture[ceilingTexture][texWidth * ty + tx];
color = (color >> 1) & 8355711; // make a bit darker
info->buf[height - y - 1][x] = color;
}
}
// WALL CASTING
for(int x = 0; x < width; x++)
{
//calculate ray position and direction
double cameraX = 2 * x / (double)width - 1; //x-coordinate in camera space
double rayDirX = info->dirX + info->planeX * cameraX;
double rayDirY = info->dirY + info->planeY * cameraX;
//which box of the map we're in
int mapX = (int)info->posX;
int mapY = (int)info->posY;
//length of ray from current position to next x or y-side
double sideDistX;
double sideDistY;
//length of ray from one x or y-side to next x or y-side
double deltaDistX = fabs(1 / rayDirX);
double deltaDistY = fabs(1 / rayDirY);
double perpWallDist;
//what direction to step in x or y-direction (either +1 or -1)
int stepX;
int stepY;
int hit = 0; //was there a wall hit?
int side; //was a NS or a EW wall hit?
//calculate step and initial sideDist
if(rayDirX < 0)
{
stepX = -1;
sideDistX = (info->posX - mapX) * deltaDistX;
}
else
{
stepX = 1;
sideDistX = (mapX + 1.0 - info->posX) * deltaDistX;
}
if(rayDirY < 0)
{
stepY = -1;
sideDistY = (info->posY - mapY) * deltaDistY;
}
else
{
stepY = 1;
sideDistY = (mapY + 1.0 - info->posY) * deltaDistY;
}
//perform DDA
while (hit == 0)
{
//jump to next map square, OR in x-direction, OR in y-direction
if(sideDistX < sideDistY)
{
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
}
else
{
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if(worldMap[mapX][mapY] > 0) hit = 1;
}
//Calculate distance of perpendicular ray (Euclidean distance will give fisheye effect!)
if(side == 0) perpWallDist = (mapX - info->posX + (1 - stepX) / 2) / rayDirX;
else perpWallDist = (mapY - info->posY + (1 - stepY) / 2) / rayDirY;
//Calculate height of line to draw on screen
int lineHeight = (int)(height / perpWallDist);
//calculate lowest and highest pixel to fill in current stripe
int drawStart = -lineHeight / 2 + height / 2;
if(drawStart < 0) drawStart = 0;
int drawEnd = lineHeight / 2 + height / 2;
if(drawEnd >= height) drawEnd = height - 1;
//texturing calculations
int texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//calculate value of wallX
double wallX; //where exactly the wall was hit
if (side == 0) wallX = info->posY + perpWallDist * rayDirY;
else wallX = info->posX + perpWallDist * rayDirX;
wallX -= floor((wallX));
//x coordinate on the texture
int texX = (int)(wallX * (double)texWidth);
if(side == 0 && rayDirX > 0) texX = texWidth - texX - 1;
if(side == 1 && rayDirY < 0) texX = texWidth - texX - 1;
// TODO: an integer-only bresenham or DDA like algorithm could make the texture coordinate stepping faster
// How much to increase the texture coordinate per screen pixel
double step = 1.0 * texHeight / lineHeight;
// Starting texture coordinate
double texPos = (drawStart - height / 2 + lineHeight / 2) * step;
for(int y = drawStart; y < drawEnd; y++)
{
// Cast the texture coordinate to integer, and mask with (texHeight - 1) in case of overflow
int texY = (int)texPos & (texHeight - 1);
texPos += step;
int color = info->texture[texNum][texHeight * texY + texX];
//make color darker for y-sides: R, G and B byte each divided through two with a "shift" and an "and"
if(side == 1) color = (color >> 1) & 8355711;
info->buf[y][x] = color;
}
//SET THE ZBUFFER FOR THE SPRITE CASTING
info->zBuffer[x] = perpWallDist; //perpendicular distance is used
}
//SPRITE CASTING
//sort sprites from far to close
for(int i = 0; i < numSprites; i++)
{
spriteOrder[i] = i;
spriteDistance[i] = ((info->posX - sprite[i].x) * (info->posX - sprite[i].x) + (info->posY - sprite[i].y) * (info->posY - sprite[i].y)); //sqrt not taken, unneeded
}
sortSprites(spriteOrder, spriteDistance, numSprites);
//after sorting the sprites, do the projection and draw them
for(int i = 0; i < numSprites; i++)
{
//translate sprite position to relative to camera
double spriteX = sprite[spriteOrder[i]].x - info->posX;
double spriteY = sprite[spriteOrder[i]].y - info->posY;
//transform sprite with the inverse camera matrix
// [ planeX dirX ] -1 [ dirY -dirX ]
// [ ] = 1/(planeX*dirY-dirX*planeY) * [ ]
// [ planeY dirY ] [ -planeY planeX ]
double invDet = 1.0 / (info->planeX * info->dirY - info->dirX * info->planeY); //required for correct matrix multiplication
double transformX = invDet * (info->dirY * spriteX - info->dirX * spriteY);
double transformY = invDet * (-info->planeY * spriteX + info->planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D, the distance of sprite to player, matching sqrt(spriteDistance[i])
int spriteScreenX = (int)((width / 2) * (1 + transformX / transformY));
//parameters for scaling and moving the sprites
#define uDiv 1
#define vDiv 1
#define vMove 0.0
int vMoveScreen = (int)(vMove / transformY);
//calculate height of the sprite on screen
int spriteHeight = (int)fabs((height / transformY) / vDiv); //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
int drawStartY = -spriteHeight / 2 + height / 2 + vMoveScreen;
if(drawStartY < 0) drawStartY = 0;
int drawEndY = spriteHeight / 2 + height / 2 + vMoveScreen;
if(drawEndY >= height) drawEndY = height - 1;
//calculate width of the sprite
int spriteWidth = (int)fabs((height / transformY) / uDiv);
int drawStartX = -spriteWidth / 2 + spriteScreenX;
if(drawStartX < 0) drawStartX = 0;
int drawEndX = spriteWidth / 2 + spriteScreenX;
if(drawEndX >= width) drawEndX = width - 1;
//loop through every vertical stripe of the sprite on screen
for(int stripe = drawStartX; stripe < drawEndX; stripe++)
{
int texX = (int)((256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth / spriteWidth) / 256);
//the conditions in the if are:
//1) it's in front of camera plane so you don't see things behind you
//2) it's on the screen (left)
//3) it's on the screen (right)
//4) ZBuffer, with perpendicular distance
if(transformY > 0 && stripe > 0 && stripe < width && transformY < info->zBuffer[stripe])
for(int y = drawStartY; y < drawEndY; y++) //for every pixel of the current stripe
{
int d = (y-vMoveScreen) * 256 - height * 128 + spriteHeight * 128; //256 and 128 factors to avoid floats
int texY = ((d * texHeight) / spriteHeight) / 256;
int color = info->texture[sprite[spriteOrder[i]].texture][texWidth * texY + texX]; //get current color from the texture
if((color & 0x00FFFFFF) != 0) info->buf[y][stripe] = color; //paint pixel if it isn't black, black is the invisible color
}
}
}
}
int main_loop(t_info *info)
{
calc(info);
draw(info);
key_update(info);
return (0);
}
void key_update(t_info *info)
{
if (info->key_w)
{
if (!worldMap[(int)(info->posX + info->dirX * info->moveSpeed)][(int)(info->posY)])
info->posX += info->dirX * info->moveSpeed;
if (!worldMap[(int)(info->posX)][(int)(info->posY + info->dirY * info->moveSpeed)])
info->posY += info->dirY * info->moveSpeed;
}
//move backwards if no wall behind you
if (info->key_s)
{
if (!worldMap[(int)(info->posX - info->dirX * info->moveSpeed)][(int)(info->posY)])
info->posX -= info->dirX * info->moveSpeed;
if (!worldMap[(int)(info->posX)][(int)(info->posY - info->dirY * info->moveSpeed)])
info->posY -= info->dirY * info->moveSpeed;
}
//rotate to the right
if (info->key_d)
{
//both camera direction and camera plane must be rotated
double oldDirX = info->dirX;
info->dirX = info->dirX * cos(-info->rotSpeed) - info->dirY * sin(-info->rotSpeed);
info->dirY = oldDirX * sin(-info->rotSpeed) + info->dirY * cos(-info->rotSpeed);
double oldPlaneX = info->planeX;
info->planeX = info->planeX * cos(-info->rotSpeed) - info->planeY * sin(-info->rotSpeed);
info->planeY = oldPlaneX * sin(-info->rotSpeed) + info->planeY * cos(-info->rotSpeed);
}
//rotate to the left
if (info->key_a)
{
//both camera direction and camera plane must be rotated
double oldDirX = info->dirX;
info->dirX = info->dirX * cos(info->rotSpeed) - info->dirY * sin(info->rotSpeed);
info->dirY = oldDirX * sin(info->rotSpeed) + info->dirY * cos(info->rotSpeed);
double oldPlaneX = info->planeX;
info->planeX = info->planeX * cos(info->rotSpeed) - info->planeY * sin(info->rotSpeed);
info->planeY = oldPlaneX * sin(info->rotSpeed) + info->planeY * cos(info->rotSpeed);
}
if (info->key_esc)
exit(0);
}
int key_press(int key, t_info *info)
{
if (key == K_ESC)
exit(0);
else if (key == K_W)
info->key_w = 1;
else if (key == K_A)
info->key_a = 1;
else if (key == K_S)
info->key_s = 1;
else if (key == K_D)
info->key_d = 1;
return (0);
}
int key_release(int key, t_info *info)
{
if (key == K_ESC)
exit(0);
else if (key == K_W)
info->key_w = 0;
else if (key == K_A)
info->key_a = 0;
else if (key == K_S)
info->key_s = 0;
else if (key == K_D)
info->key_d = 0;
return (0);
}
void load_image(t_info *info, int *texture, char *path, t_img *img)
{
img->img = mlx_xpm_file_to_image(info->mlx, path, &img->img_width, &img->img_height);
img->data = (int *)mlx_get_data_addr(img->img, &img->bpp, &img->size_l, &img->endian);
for (int y = 0; y < img->img_height; y++)
{
for (int x = 0; x < img->img_width; x++)
{
texture[img->img_width * y + x] = img->data[img->img_width * y + x];
}
}
mlx_destroy_image(info->mlx, img->img);
}
void load_texture(t_info *info)
{
t_img img;
load_image(info, info->texture[0], "textures/eagle.xpm", &img);
load_image(info, info->texture[1], "textures/redbrick.xpm", &img);
load_image(info, info->texture[2], "textures/purplestone.xpm", &img);
load_image(info, info->texture[3], "textures/greystone.xpm", &img);
load_image(info, info->texture[4], "textures/bluestone.xpm", &img);
load_image(info, info->texture[5], "textures/mossy.xpm", &img);
load_image(info, info->texture[6], "textures/wood.xpm", &img);
load_image(info, info->texture[7], "textures/colorstone.xpm", &img);
load_image(info, info->texture[8], "textures/barrel.xpm", &img);
load_image(info, info->texture[9], "textures/pillar.xpm", &img);
load_image(info, info->texture[10], "textures/greenlight.xpm", &img);
}
int main(void)
{
t_info info;
info.mlx = mlx_init();
info.posX = 22.0;
info.posY = 11.5;
info.dirX = -1.0;
info.dirY = 0.0;
info.planeX = 0.0;
info.planeY = 0.66;
info.key_a = 0;
info.key_w = 0;
info.key_s = 0;
info.key_d = 0;
info.key_esc = 0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
info.buf[i][j] = 0;
}
}
if (!(info.texture = (int **)malloc(sizeof(int *) * 11)))
return (-1);
for (int i = 0; i < 11; i++)
{
if (!(info.texture[i] = (int *)malloc(sizeof(int) * (texHeight * texWidth))))
return (-1);
}
for (int i = 0; i < 11; i++)
{
for (int j = 0; j < texHeight * texWidth; j++)
{
info.texture[i][j] = 0;
}
}
load_texture(&info);
info.moveSpeed = 0.05;
info.rotSpeed = 0.05;
info.win = mlx_new_window(info.mlx, width, height, "mlx");
info.img.img = mlx_new_image(info.mlx, width, height);
info.img.data = (int *)mlx_get_data_addr(info.img.img, &info.img.bpp, &info.img.size_l, &info.img.endian);
mlx_loop_hook(info.mlx, &main_loop, &info);
mlx_hook(info.win, X_EVENT_KEY_PRESS, 0, &key_press, &info);
mlx_hook(info.win, X_EVENT_KEY_RELEASE, 0, &key_release, &info);
mlx_loop(info.mlx);
}
Line 45 in 3273a9d
if (info->sprite[i].distance > info->sprite[i].distance)
를
if (info->sprite[j].distance > info->sprite[i].distance)
로 변경을 하는 게 맞아보입니다. 다만, 근본적인 sprite 소트 오류는 발견을 아직 못했네요. ;(
241: int texNum = worldMap[mapX][mapY];에서 -1을 해주어야 정확한 텍스쳐 선택이 될 것 같습니다.
-1을 해주지 않아 texture buf 인덱스 참조에서 오류가 발생하는 경우가 생깁니다!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.