// Voir README.md pour license précise, par Fcalva 2023-2024 et est sous GPLv3 #include #include #include #include #include #include #include #include #include #include #include "C3D/fixed.h" #include "C3D/moteur.h" #include "C3D/map.h" #include "C3D/game.h" #include "C3D/config.h" // moteur.c : // ici se trouvent tout ce qui concerne les graphismes, mouvement et collisions // //1D zbuf for sprites fixed_t zbuf[viewport_w]; void move(RcGame *game) { extern int frame_time; fixed_t moveSpeed = fmul(fix(frame_time/1000), 0x148); //frame_time * fix(carrés/seconde/1000) là carrés/seconde = 5 fixed_t rotSpeed = fmul(fix(frame_time/1000), 0x83); //frame_time * fix(radians/seconde/1000) là radians/seconde = 2 fixed_t c_rotSpeed = fix(cos(f2float(rotSpeed))); fixed_t s_rotSpeed = fix(sin(f2float(rotSpeed))); uint8_t *map_test = game->current_map->dat; V2d *plane = &game->player.plane; V2d *dir = &game->player.dir; V2d *pos = &game->player.pos; int map_w = game->current_map->w; int map_h = game->current_map->h; fixed_t oldDirX; fixed_t oldPlaneX; int xtemp1; int ytemp1; int xtemp2; int ytemp2; if (keydown(KEY_UP)) { xtemp1 = ffloor(pos->x + fmul(dir->x, moveSpeed)); ytemp1 = ffloor(pos->y); xtemp2 = ffloor(pos->x); ytemp2 = ffloor(pos->y + fmul(dir->y, moveSpeed)); if(!map_test[xtemp1*map_w+ytemp1]) pos->x += fmul(dir->x, moveSpeed); if(!map_test[xtemp2*map_w+ytemp2]) pos->y += fmul(dir->y, moveSpeed); } //move backwards if no wall behind you if (keydown(KEY_DOWN)) { xtemp1 = ffloor(pos->x - fmul(dir->x, moveSpeed)); ytemp1 = ffloor(pos->y); xtemp2 = ffloor(pos->x); ytemp2 = ffloor(pos->y - fmul(dir->y, moveSpeed)); if(!map_test[xtemp1*map_w+ytemp1]) pos->x -= fmul(dir->x, moveSpeed); if(!map_test[xtemp2*map_w+ytemp2]) pos->y -= fmul(dir->y, moveSpeed); } //rotate to the right if (keydown(KEY_RIGHT)) { //both camera direction and camera plane must be rotated oldDirX = dir->x; dir->x = (fmul(dir->x, c_rotSpeed)+1) - (fmul(dir->y, -s_rotSpeed)+1); dir->y = (fmul(oldDirX, -s_rotSpeed)+1) + (fmul(dir->y, c_rotSpeed)+1); oldPlaneX = plane->x; plane->x = (fmul(plane->x, c_rotSpeed)+1) - (fmul(plane->y, -s_rotSpeed)+1); plane->y = (fmul(oldPlaneX, -s_rotSpeed)+1) + (fmul(plane->y, c_rotSpeed)+1); } //rotate to the left if (keydown(KEY_LEFT)) { //both camera direction and camera plane must be rotated oldDirX = dir->x; dir->x = (fmul(dir->x, c_rotSpeed)-1) - (fmul(dir->y, s_rotSpeed)-1); dir->y = (fmul(oldDirX, s_rotSpeed)+1) + (fmul(dir->y, c_rotSpeed)+1); oldPlaneX = plane->x; plane->x = (fmul(plane->x, c_rotSpeed)-1) - (fmul(plane->y, s_rotSpeed) - 1); plane->y = (fmul(oldPlaneX, s_rotSpeed)+1) + (fmul(plane->y, c_rotSpeed) + 1); } if (dir->x > 0xFFFF) dir->x = 0xFFFF; if (dir->y > 0xFFFF) dir->y = 0xFFFF; if (dir->x < -0xFFFF) dir->x = -0xFFFF; if (dir->y < -0xFFFF) dir->y = -0xFFFF; } void load_map(){ } //Fonction fournie par le grand Lephé :D inline int __attribute__((always_inline)) get_pixel(bopti_image_t const *img, int u, int v) { int layers = image_layer_count(img->profile); int columns = (img->width + 31) >> 5; uint32_t const *layer_ptr = (void *)img->data; layer_ptr += (v * columns + (u >> 5)) * layers; uint32_t mask = 0x80000000u >> (u & 31); int light = (layer_ptr[0] & mask) != 0; int dark = (layer_ptr[1] & mask) != 0; int alpha = 0; if(layers > 2){ alpha = !(layer_ptr[2] & mask); } return (alpha << 2) | (dark << 1) | light; } uint32_t *light, *dark; //Version simplifiée de gpixel dans gint/src/gray/gpixel.c //Tout le crédit revient à Lephé inline void __attribute__((always_inline)) cust_dpixel(int x, int y, color_t color){ if(y >= viewport_h || y < 0) return; int offset = (y << 2) + (x >> 5); uint32_t mask = 1 << (~x & 31); switch(color){ case C_WHITE: light[offset] &= ~mask; dark [offset] &= ~mask; break; case C_LIGHT: light[offset] |= mask; dark [offset] &= ~mask; break; case C_DARK: light[offset] &= ~mask; dark [offset] |= mask; break; case C_BLACK: light[offset] |= mask; dark [offset] |= mask; break; case C_NONE: break; default: break; } } void dscale_bopti(bopti_image_t *tex, fixed_t scale_x, fixed_t scale_y, int x, int y){ dgray_getvram(&light,&dark); fixed_t screen_y = fix(y); fixed_t tex_y = 0; do{ fixed_t oldy = screen_y; if(screen_y < 0 || screen_y+scale_x > viewport_h) return; do{ fixed_t screen_x = fix(x); fixed_t tex_x = 0; do{ fixed_t oldx = screen_x; int tpix = get_pixel(tex, tex_x, tex_y); if(screen_x < 0 || screen_x+scale_x > viewport_w) return; do{ cust_dpixel(ffloor(oldx), ffloor(oldy), tpix); oldx += 0xFFFF; } while(oldx < screen_x+scale_x); screen_x += scale_x; tex_x++; } while(tex_x < tex->width); oldy += 0xFFFF; } while(oldy < screen_y+scale_y); screen_y += scale_y; tex_y++; } while(tex_y < tex->height); } inline void __attribute__((always_inline)) draw_stripe(bopti_image_t *tex, int texSampleY, int linePos, fixed_t texSize, int texX, int x){ fixed_t screenPos = fix(linePos); for(int texPos = texSampleY; texPos < TSIZE; ++texPos){ if(screenPos >= 0){ fixed_t oldPos = screenPos; int tpix = get_pixel(tex, texX, texPos); do{ oldPos += 0xFFFF; cust_dpixel(x, fround(oldPos), tpix); } while(oldPos < screenPos+texSize); } screenPos += texSize; } } void draw_walls( #if debug EngineTimers *timers, #endif RcGame *game ){ fixed_t posX = game->player.pos.x; fixed_t posY = game->player.pos.y; fixed_t dirX = game->player.dir.x; fixed_t dirY = game->player.dir.y; fixed_t planeX = game->player.plane.x; fixed_t planeY = game->player.plane.y; extern bopti_image_t *tex_index[TINDEX_S]; uint8_t *map_test = game->current_map->dat; int map_w = game->current_map->w; int map_h = game->current_map->h; fixed_t cameraX; fixed_t rayDirX; fixed_t rayDirY; fixed_t sideDistX;//length of ray from current position to next x or y-side fixed_t sideDistY; fixed_t deltaDistX; fixed_t deltaDistY; fixed_t perpWallDist; int x; int mapX; int mapY; int stepX; //what direction to step in x or y-direction (either +1 or -1) int stepY; int side = 0; //was a NS or a EW wall hit? int lineHeight; int texX; int texSample; int texSampleY; dgray_getvram(&light,&dark); //int v_offset = 0; fixed_t h_offset = 0; //struct image_linear_map temp; for(x = 0; x < viewport_w; x++) { #if debug prof_enter(timers->raycast_time); #endif //calculate ray position and direction cameraX = fdiv(fix(x*2), fix(viewport_w)) - 0xFFFF + h_offset; //x-coordinate in camera space rayDirX = dirX + fmul(planeX, cameraX); rayDirY = dirY + fmul(planeY, cameraX); //which box of the map we're in mapX = ffloor(posX); mapY = ffloor(posY); // length of ray from one x or y-side to next x or y-side // these are derived as: // deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX)) // deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY)) // which can be simplified to abs(|rayDir| / rayDirX) and abs(|rayDir| / rayDirY) // where |rayDir| is the length of the vector (rayDirX, rayDirY). Its length, // unlike (dirX, dirY) is not 1, however this does not matter, only the // ratio between deltaDistX and deltaDistY matters, due to the way the DDA // stepping further below works. So the values can be computed as below. // Division through zero is prevented, even though technically that's not // needed in C++ with IEEE 754 floating point values. //Fcalva : It is with fp32s ! rayDirX = rayDirX == 0 ? 1 : rayDirX; rayDirY = rayDirY == 0 ? 1 : rayDirY; deltaDistX = abs(fdiv(0xFFFF, rayDirX)); deltaDistY = abs(fdiv(0xFFFF, rayDirY)); if(deltaDistX > max_dist) deltaDistX = max_dist; if(deltaDistY > max_dist) deltaDistY = max_dist; //calculate step and initial sideDist if (rayDirX < 0) { stepX = -1; sideDistX = fmul(posX - fix(mapX), deltaDistX); } else { stepX = 1; sideDistX = fmul( fix(mapX + 1) - posX, deltaDistX); } if (rayDirY == 0) { stepY = 0; sideDistY = 0; } else if (rayDirY < 0) { stepY = -1; sideDistY = fmul(posY - fix(mapY), deltaDistY); } else { stepY = 1; sideDistY = fmul( fix(mapY + 1) - posY, deltaDistY); } //perform DDA while(true) { //Check if the ray is out of range/bounds if (sideDistX >= max_dist || sideDistY >= max_dist || mapX < 0 || mapY < 0 || mapY >= map_w || mapX >= map_h) { break; } //Otherwise check if ray has hit a wall if (map_test[mapX*map_w+mapY] > 0) { break; } //jump to next map square, either in x-direction, or in y-direction if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } } //Calculate distance projected on camera direction. This is the shortest distance from the point where the wall is //hit to the camera plane. Euclidean to center camera point would give fisheye effect! //This can be computed as (mapX - posX + (1 - stepX) / 2) / rayDirX for side == 0, or same formula with Y //for size == 1, but can be simplified to the code below thanks to how sideDist and deltaDist are computed: //because they were left scaled to |rayDir|. sideDist is the entire length of the ray above after the multiple //steps, but we subtract deltaDist once because one step more into the wall was taken above. if (side == 0) perpWallDist = (sideDistX - deltaDistX); else perpWallDist = (sideDistY - deltaDistY); zbuf[x] = perpWallDist; #if debug prof_leave(timers->raycast_time); prof_enter(timers->draw_time); #endif //texturing calculations //calculate value of wallX fixed_t wallX; //where exactly the wall was hit if (side == 0) wallX = posY + fmul(perpWallDist, rayDirY); else wallX = posX + fmul(perpWallDist, rayDirX); wallX -= fix(ffloor(wallX)); //x coordinate on the texture texX = fmul(wallX, TSIZE); texX %= TSIZE; if(side == 0 && rayDirX > 0) texX = 64 - texX - 1; if(side == 1 && rayDirY < 0) texX = 64 - texX - 1; lineHeight = ffloor(fdiv(fix(viewport_h), perpWallDist)); //Taille en px de la ligne if (lineHeight < 1) lineHeight = 1; //if (lineHeight > viewport_h) lineHeight = viewport_h - 1; fixed_t texSize = fix(lineHeight) / TSIZE; //taille proportionelle de la ligne a la tex if (texSize < fix(1.0/(float)TSIZE)) texSize = fix(1.0/(float)TSIZE); if (texSize > fix((float)viewport_h/(float)TSIZE)) { texSample = fceil(fdiv(fix(viewport_h), texSize)); texSampleY = TSIZE/2 - texSample/2; } else { texSample = TSIZE; texSampleY = 0; } int linePos = viewport_h/2 - lineHeight/2 + game->flags.v_offset; bopti_image_t *tex = tex_index[map_test[mapX*map_w+mapY]]; //dline(x, linePos, x, linePos + lineHeight, 1); draw_stripe(tex, texSampleY, linePos, texSize, texX, x); #if debug prof_leave(timers->draw_time); #endif } }