2024-10-05 00:56:13 +02:00
|
|
|
// Voir README.md pour license précise, par Fcalva 2023-2024 et est sous GPLv3
|
2024-10-05 21:48:42 +02:00
|
|
|
|
|
|
|
#include <gint/display.h>
|
|
|
|
#include <gint/keyboard.h>
|
2024-10-22 12:27:16 +02:00
|
|
|
#include <gint/gray.h>
|
2024-10-08 13:16:05 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2024-10-25 23:05:09 +02:00
|
|
|
#include <stddef.h>
|
2024-10-05 21:48:42 +02:00
|
|
|
|
2024-10-22 12:27:16 +02:00
|
|
|
#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"
|
2024-10-05 21:48:42 +02:00
|
|
|
|
|
|
|
extern fixed_t zbuf[viewport_w];
|
|
|
|
|
2024-10-25 23:05:09 +02:00
|
|
|
int spritec = 0;
|
2024-10-05 21:48:42 +02:00
|
|
|
|
|
|
|
Sprite *sprite_index[SINDEX_S];
|
|
|
|
|
|
|
|
void add_sprite(Sprite *sprite){
|
2024-10-08 13:16:05 +02:00
|
|
|
if(spritec >= SINDEX_S)
|
|
|
|
return;
|
|
|
|
sprite_index[spritec++] = sprite;
|
2024-10-05 21:48:42 +02:00
|
|
|
}
|
|
|
|
|
2024-10-08 13:16:05 +02:00
|
|
|
void clear_sprites(){
|
|
|
|
spritec = 0;
|
2024-10-05 21:48:42 +02:00
|
|
|
}
|
|
|
|
|
2024-10-27 23:52:11 +01:00
|
|
|
int remove_sprite(Sprite *sprite){
|
2024-10-25 23:05:09 +02:00
|
|
|
if(!sprite)
|
2024-10-27 23:52:11 +01:00
|
|
|
return 0;
|
2024-10-25 23:05:09 +02:00
|
|
|
int pos = -1;
|
|
|
|
for(int i = 0; i < spritec; i++){
|
|
|
|
if(sprite == sprite_index[i]){
|
|
|
|
pos = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(pos < 0)
|
2024-10-27 23:52:11 +01:00
|
|
|
return 1;
|
2024-10-25 23:05:09 +02:00
|
|
|
memmove(&sprite_index[pos], &sprite_index[pos+1], sizeof(void*) * (spritec-pos));
|
2024-10-05 21:48:42 +02:00
|
|
|
}
|
|
|
|
|
2024-10-22 12:27:16 +02:00
|
|
|
//Lodev's sprite projection, translated to use my functions and fixed point
|
2024-10-05 21:48:42 +02:00
|
|
|
|
|
|
|
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++){
|
2024-10-25 23:05:09 +02:00
|
|
|
if(ty < fix(0.4) || x < 0 || ty > zbuf[x])
|
2024-10-05 21:48:42 +02:00
|
|
|
continue;
|
|
|
|
if(x > viewport_w)
|
|
|
|
break;
|
2024-10-22 12:27:16 +02:00
|
|
|
int tex_x = ffloor(fixabs(x-sprite_pos_x) * fdiv(fix(TSIZE), fix(sprite_w)));
|
2024-10-05 21:48:42 +02:00
|
|
|
tex_x %= TSIZE;
|
|
|
|
|
2024-10-22 12:27:16 +02:00
|
|
|
draw_stripe(tex_index[sprite->tex], 0, sprite_pos_y, sprite_size, tex_x, x);
|
2024-10-05 21:48:42 +02:00
|
|
|
}
|
|
|
|
}
|
2024-10-08 13:16:05 +02:00
|
|
|
|
|
|
|
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);
|
2024-10-25 23:05:09 +02:00
|
|
|
|
2024-10-08 13:16:05 +02:00
|
|
|
for(int i = 0; i < spritec; i++){
|
|
|
|
Sprite *spr = sprite_index[i];
|
2024-10-25 23:05:09 +02:00
|
|
|
fixed_t d = fmul(player->pos.x - spr->pos.x, player->pos.x - spr->pos.x);
|
2024-10-08 13:16:05 +02:00
|
|
|
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);
|
|
|
|
|
2024-10-25 23:05:09 +02:00
|
|
|
int msprite = spritec > max_sprites ? max_sprites:spritec;
|
|
|
|
for(int i = 0; i < msprite; i++){
|
2024-10-08 13:16:05 +02:00
|
|
|
struct SpriteDist *sd = &dists[i];
|
|
|
|
Sprite *spr = sprite_index[sd->id];
|
2024-10-25 23:05:09 +02:00
|
|
|
|
|
|
|
if(sd->dist > max_dist || !spr)
|
2024-10-08 13:16:05 +02:00
|
|
|
continue;
|
|
|
|
project_sprite(tex_index, spr, player);
|
|
|
|
}
|
|
|
|
}
|
2024-10-22 12:27:16 +02:00
|
|
|
|
|
|
|
//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;
|
2024-10-25 23:05:09 +02:00
|
|
|
int map_w = game->current_map->w;
|
|
|
|
int map_h = game->current_map->h;
|
2024-10-22 12:27:16 +02:00
|
|
|
|
|
|
|
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
|
2024-10-25 23:05:09 +02:00
|
|
|
cameraX = fdiv(fix(x*2), fix(viewport_w)) - 0xFFFF; //x-coordinate in camera space
|
2024-10-22 12:27:16 +02:00
|
|
|
|
|
|
|
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
|
2024-10-27 23:52:11 +01:00
|
|
|
if (sideDistX >= max_dist || sideDistY >= max_dist || mapX <= 0 || mapY <= 0 || mapY >= map_w || mapX >= map_h) {
|
2024-10-22 12:27:16 +02:00
|
|
|
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;
|
|
|
|
}
|