Copy3DEngine/eng/sprites.c

270 lines
8.1 KiB
C
Raw Permalink Normal View History

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;
}