// Voir README.md pour license précise, par Fcalva 2023-2024 et est sous GPLv3 #include #include #include #include #include #include #include "C3D/fixed.h" #include "C3D/game.h" #include "C3D/sprites.h" #include "C3D/moteur.h" #include "C3D/map.h" #include "C3D/config.h" #include "C3D/utils.h" extern fixed_t zbuf[viewport_w]; int spritec = 0; Sprite *sprite_index[SINDEX_S]; void add_sprite(Sprite *sprite){ if(spritec >= SINDEX_S) return; sprite_index[spritec++] = sprite; } void clear_sprites(){ spritec = 0; } int remove_sprite(Sprite *sprite){ if(!sprite) return 0; int pos = -1; for(int i = 0; i < spritec; i++){ if(sprite == sprite_index[i]){ pos = i; break; } } if(pos < 0) return 1; memmove(&sprite_index[pos], &sprite_index[pos+1], sizeof(void*) * (spritec-pos)); } //Lodev's sprite projection, translated to use my functions and fixed point void project_sprite(bopti_image_t *tex_index[], Sprite *sprite, RcActor *player){ V2d relpos = sprite->pos; relpos.x -= player->pos.x; relpos.y -= player->pos.y; fixed_t idet = fmul(player->plane.x, player->dir.y) - fmul(player->dir.x, player->plane.y); idet = idet ? fdiv(fix(1), idet):0; fixed_t tx = fmul(idet, fmul(player->dir.y, relpos.x) - fmul(player->dir.x, relpos.y)); fixed_t ty = fmul(idet, fmul(-player->plane.y, relpos.x) + fmul(player->plane.x, relpos.y)); fixed_t txtydiv = ty ? fdiv(tx, ty):0; int screen_x = ffloor(fmul(fix(viewport_w/2),(fix(1) + txtydiv))); int sprite_h = ffloor(fixabs(fdiv(fix(viewport_h),ty))); int sprite_pos_y = -sprite_h / 2 + viewport_h / 2; if(sprite_pos_y < 0) sprite_pos_y = 0; int sprite_pos_x = -sprite_h / 2 + screen_x; int sprite_end_x = sprite_h / 2 + screen_x; if(sprite_end_x > viewport_w) sprite_end_x = viewport_w; int sprite_w = sprite_h; if(sprite_pos_x + sprite_h > viewport_w) sprite_h = viewport_w - sprite_pos_x; if(sprite_pos_y + sprite_w > viewport_h) sprite_w = viewport_h - sprite_pos_y; if(sprite_pos_x + sprite_h < 0 || sprite_pos_x > viewport_w) return; fixed_t sprite_size = fdiv(fix(sprite_w), fix(TSIZE)); for(int x = sprite_pos_x; x < sprite_end_x; x++){ if(ty < fix(0.4) || x < 0 || ty > zbuf[x]) continue; if(x > viewport_w) break; int tex_x = ffloor(fixabs(x-sprite_pos_x) * fdiv(fix(TSIZE), fix(sprite_w))); tex_x %= TSIZE; draw_stripe(tex_index[sprite->tex], 0, sprite_pos_y, sprite_size, tex_x, x); } } struct SpriteDist{ fixed_t dist; int id; }; int sprite_cmpfnc(const void *p1, const void *p2){ return ((struct SpriteDist*)p2)->dist - ((struct SpriteDist*)p1)->dist; } void draw_sprites(bopti_image_t *tex_index[], RcActor *player){ struct SpriteDist dists[SINDEX_S]; memset(dists, 0, sizeof(struct SpriteDist)*SINDEX_S); for(int i = 0; i < spritec; i++){ Sprite *spr = sprite_index[i]; fixed_t d = fmul(player->pos.x - spr->pos.x, player->pos.x - spr->pos.x); d += fmul(player->pos.y - spr->pos.y,player->pos.y - spr->pos.y); dists[i].dist = d; dists[i].id = i; } qsort(dists, SINDEX_S, sizeof(struct SpriteDist), &sprite_cmpfnc); int msprite = spritec > max_sprites ? max_sprites:spritec; for(int i = 0; i < msprite; i++){ struct SpriteDist *sd = &dists[i]; Sprite *spr = sprite_index[sd->id]; if(sd->dist > max_dist || !spr) continue; project_sprite(tex_index, spr, player); } } //Returns distance (-1 if no hit), hit is set to the hit sprite //or NULL if no hit (can be passed NULL) fixed_t raycast(RcGame *game, RcActor *origin, Sprite **hit){ fixed_t posX = origin->pos.x; fixed_t posY = origin->pos.y; fixed_t dirX = origin->dir.x; fixed_t dirY = origin->dir.y; fixed_t planeX = origin->plane.x; fixed_t planeY = origin->plane.y; 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 = viewport_w/2; 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? //calculate ray position and direction cameraX = fdiv(fix(x*2), fix(viewport_w)) - 0xFFFF; //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); } int coll = 0; //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) { coll = 0; break; } //Otherwise check if ray has hit a wall if (map_test[mapX*map_w+mapY] > 0) { coll = 0; break; } for(int i = 0; i < spritec; i++){ Sprite *act = sprite_index[i]; if(ffloor(act->pos.x) == mapX && ffloor(act->pos.y) == mapY){ if(hit) *hit = act; coll = 1; goto outerbreak; } } //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; } continue; outerbreak: break; } //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(coll){ if (side == 0) perpWallDist = (sideDistX - deltaDistX); else perpWallDist = (sideDistY - deltaDistY); } else { perpWallDist = -1; } return perpWallDist; }