Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Doom для терминала без стрельбы и монстров. 3D Raycasting на ascii.

Дата публикации: 05-07-2026 07:42:09

Попросил нейронную сеть deepai.org написать рейкастинг 3D с библиотекой ncurses для Linux. Чтобы...

Основное содержимое страницы с новостью.

Попросил нейронную сеть deepai.org написать рейкастинг 3D с библиотекой ncurses для Linux. Чтобы можно было
ходить на стрелочки. Чтобы стены были отрисованы символами. Справилась.

Первый вариант сделала сеть с эффектом рыбьего глаза, но вот второй подоспел. Т.е. в терминале Линукса, прямо в tty,
можно походить в 3D комнате, типа лабиринта. Необычно.
Но мне нравится, что в 130 строк уложился код. Если не считать main, то сам алгоритм raycasting всего строк 50-60.
У меня комбинаторные некоторые за 200 переваливали. Вот за этой красивой сложностью линейной перспективы, такой, я бы сказал, относительно простой код. Хотя на мой гуманитарный взгляд - там, где появляются синусы и косинусы с числом PI, то там уже не все совсем очевидно.
В алгоритме рейкастинга, на мой взгляд, много всего: FOV - поле зрения, POV - точка зрения. Это все надо учесть. Рассчитать высоту стен на основе расстояния до них в общем не очень мудрено. Но в целом представление линейной перспективы на дискретной системе и довольно простым способом - вещь интересная. Изобретателям надо отдать должное. Хотя тут в общем вся наука поучаствовала, скорее всего. От художников до топографов. Но вот сам код:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*Модель 3D Raycasting на C89 с библиотекой ncurses. Написана нейронной сетью.
Стены с помощью символов
Компиляция и запуск gcc -o ray14 ray14.c -lncurses -ltinfo  -lm; ./ ray14
Убран эффект рыбьего глаза*/
 
#include <ncurses.h>
#include <math.h>
#include <stdlib.h>
 
#define MAP_WIDTH 16
#define MAP_HEIGHT 16
#define FOV (M_PI / 3.0) // 60 градусов
#define MAX_DEPTH 16
 
// Карта: 0 - пусто, 1 - стена
int worldMap[MAP_HEIGHT][MAP_WIDTH] = {
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
 
double playerX = 3.0, playerY = 3.0; // Начальные координаты игрока
double playerAngle = 0.0;
 
void draw(int width, int height) {
    for (int x = 0; x < width; x++) {
        // Вычисляем угол для каждого луча
        double rayAngle = (playerAngle - FOV / 2.0) + ((double)x / (double)width) * FOV;
        double distanceToWall = 0;
        int hit = 0;
 
        double eyeX = cos(rayAngle);
        double eyeY = sin(rayAngle);
 
        while (!hit && distanceToWall < MAX_DEPTH) {
            distanceToWall += 0.1;
            int testX = (int)(playerX + eyeX * distanceToWall);
            int testY = (int)(playerY + eyeY * distanceToWall);
 
            // Проверка выхода за границы
            if (testX < 0 || testX >= MAP_WIDTH || testY < 0 || testY >= MAP_HEIGHT) {
                hit = 1;
                distanceToWall = MAX_DEPTH;
            } else {
                if (worldMap[testY][testX] == 1) {
                    hit = 1;
                }
            }
        }
 
        // new: Корректировка расстояния для устранения эффекта рыбьего глаза (new)
        double angleDifference = rayAngle - playerAngle; // new: расчет разницы углов (new)
        if (angleDifference > M_PI) angleDifference -= 2 * M_PI; // new: приведение к диапазону (-π, π) (new)
        if (angleDifference < -M_PI) angleDifference += 2 * M_PI; // new
 
        distanceToWall = distanceToWall * cos(angleDifference); // new: исправление расстояния (new)
 
        // Рассчитаем высоту стен
        int ceiling = (int)((double)(height / 2.0) - height / distanceToWall);
        int floor = height - ceiling;
 
        for (int y = 0; y < height; y++) {
            if (y < ceiling) {
                mvaddch(y, x, ' ');
            } else if (y > ceiling && y <= floor) {
                // Можно добавить тень
                if (distanceToWall <= MAX_DEPTH / 4.0) {
                    mvaddch(y, x, '#'); // Ближе - темнее
                } else if (distanceToWall < MAX_DEPTH / 2.0) {
                    mvaddch(y, x, '+');
                } else {
                    mvaddch(y, x, '.');
                }
            } else {
                mvaddch(y, x, ' ');
            }
        }
    }
    refresh();
}
int main() {
    initscr();
    noecho();
    curs_set(FALSE);
    keypad(stdscr, TRUE);
    nodelay(stdscr, TRUE); // Не блокировать getch
 
    int width, height;
    getmaxyx(stdscr, height, width);
 
    while (1) {
        // Обработка ввода
        int ch = getch();
        if (ch == 'q') break; // Выход
        if (ch == KEY_UP) {
            // Вперёд
            double newX = playerX + cos(playerAngle) * 0.1;
            double newY = playerY + sin(playerAngle) * 0.1;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        }
        if (ch == KEY_DOWN) {
            // Назад
            double newX = playerX - cos(playerAngle) * 0.1;
            double newY = playerY - sin(playerAngle) * 0.1;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        }
        if (ch == KEY_LEFT) {
            playerAngle -= 0.1;
        }
        if (ch == KEY_RIGHT) {
            playerAngle += 0.1;
        }
 
        draw(width, height);
    }
 
    endwin();
    return 0;
}

P.S.
Вообще для себя заметил пока алгоритм для себя не объяснил, то и понять код довольно сложно. В рейкастинге надо держать в голове, что 1 итерация внешнего цикла - это 1 брошенный луч до одного деления стенки. И основаная задача высчитать расстояние от наблюдателя до этого деления стенки (интегрирование вообще напоминает), а на основе расстояния потом отрисовать его высоту (потолок и пол) и тут уже синусы с косинусами и числом пи.
 

Подоспел вариант с миникартой и направлением взгляда:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*Модель 3D Raycasting на C89 с библиотекой ncurses. Написана нейронной сетью.
Стены с помощью символов
Компиляция и запуск gcc -o ray14 ray14.c -lncurses -ltinfo  -lm; ./ray14
Убран эффект рыбьего глаза*/
#include <ncurses.h>
#include <math.h>
#include <stdlib.h>
 
#define MAP_WIDTH 16
#define MAP_HEIGHT 16
#define FOV (M_PI / 3.0) // 60 градусов
#define MAX_DEPTH 16
 
// Карта: 0 - пусто, 1 - стена
int worldMap[MAP_HEIGHT][MAP_WIDTH] = {
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1},
    {1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
 
double playerX = 3.0, playerY = 3.0; // Начальные координаты игрока
double playerAngle = 0.0;
 
// Функция для отрисовки мини-карты
void drawMiniMap(int mapX, int mapY, int mapSize) {
    for (int y = 0; y < MAP_HEIGHT; y++) {
        for (int x = 0; x < MAP_WIDTH; x++) {
            char ch = (worldMap[y][x] == 1) ? '#' : ' ';
            mvaddch(mapY + y, mapX + x, ch);
        }
    }
    // Отметка положения игрока
    mvaddch(mapY + (int)playerY, mapX + (int)playerX, '@');
 
    // Добавляем линию направления взгляда
    double dirX = cos(playerAngle);
    double dirY = sin(playerAngle);
    int lineXEnd = (int)(playerX + dirX * 3);
    int lineYEnd = (int)(playerY + dirY * 3);
 
    // Рисуем линию направления (используем Bresenham или простую)
    int x1 = (int)playerX;
    int y1 = (int)playerY;
    int x2 = lineXEnd;
    int y2 = lineYEnd;
 
    int dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
    int dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; 
    int err = dx + dy, e2;
 
    while (1) {
        mvaddch(mapY + y1, mapX + x1, '*'); // или другой символ
        if (x1 == x2 && y1 == y2) break;
        e2 = 2 * err;
        if (e2 >= dy) { err += dy; x1 += sx; }
        if (e2 <= dx) { err += dx; y1 += sy; }
    }
}
 
void draw(int width, int height) {
    for (int x = 0; x < width; x++) {
        // Исправленная строка
        double rayAngle = (playerAngle - FOV / 2.0) + ((double)x / (double)width) * FOV;
        double distanceToWall = 0;
        int hit = 0;
 
        double eyeX = cos(rayAngle);
        double eyeY = sin(rayAngle);
 
        while (!hit && distanceToWall < MAX_DEPTH) {
            distanceToWall += 0.1;
            int testX = (int)(playerX + eyeX * distanceToWall);
            int testY = (int)(playerY + eyeY * distanceToWall);
 
            if (testX < 0 || testX >= MAP_WIDTH || testY < 0 || testY >= MAP_HEIGHT) {
                hit = 1;
                distanceToWall = MAX_DEPTH;
            } else {
                if (worldMap[testY][testX] == 1) {
                    hit = 1;
                }
            }
        }
 
        double angleDifference = rayAngle - playerAngle;
        if (angleDifference > M_PI) angleDifference -= 2 * M_PI;
        if (angleDifference < -M_PI) angleDifference += 2 * M_PI;
 
        distanceToWall = distanceToWall * cos(angleDifference);
 
        int ceiling = (int)((double)(height / 2.0) - height / distanceToWall);
        int floor = height - ceiling;
 
        for (int y = 0; y < height; y++) {
            if (y < ceiling) {
                mvaddch(y, x, ' ');
            } else if (y > ceiling && y <= floor) {
                if (distanceToWall <= MAX_DEPTH / 4.0) {
                    mvaddch(y, x, '#');
                } else if (distanceToWall < MAX_DEPTH / 2.0) {
                    mvaddch(y, x, '+');
                } else {
                    mvaddch(y, x, '.');
                }
            } else {
                mvaddch(y, x, ' ');
            }
        }
    }
    refresh();
}
 
int main() {
    initscr();
    noecho();
    curs_set(FALSE);
    keypad(stdscr, TRUE);
    nodelay(stdscr, TRUE);
 
    int width, height;
    getmaxyx(stdscr, height, width);
 
    // Позиция мини-карты в левом нижнем углу
    int miniMapSizeX = 16;
    int miniMapSizeY = 16;
    int miniMapPosX = 1;
    int miniMapPosY = height - miniMapSizeY - 1; // снизу
 
    while (1) {
        int ch = getch();
        if (ch == 'q') break;
        if (ch == KEY_UP) {
            double newX = playerX + cos(playerAngle) * 0.1;
            double newY = playerY + sin(playerAngle) * 0.1;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        }
        if (ch == KEY_DOWN) {
            double newX = playerX - cos(playerAngle) * 0.1;
            double newY = playerY - sin(playerAngle) * 0.1;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        }
        if (ch == KEY_LEFT) {
            playerAngle -= 0.1;
        }
        if (ch == KEY_RIGHT) {
            playerAngle += 0.1;
        }
 
        draw(width, height);
        drawMiniMap(miniMapPosX, miniMapPosY, miniMapSizeX);
    }
 
    endwin();
    return 0;
}

Точно на C89 без ncurses:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*Модель 3D Raycasting на C(89). Написана нейронной сетью.
Стены с помощью символов
Компиляция и запуск gcc -o ray ray.c   -lm; ./ray
* или gcc -std=c89  -o ray ray.c   -lm; ./ray
Убран эффект рыбьего глаза*/
#include <stdio.h>
#include <math.h>
#include <string.h>
 
/*Размеры карты и экрана*/
#define MAP_WIDTH 16
#define MAP_HEIGHT 16
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 24
#define M_PI 3.14159265358979323846
#define FOV (M_PI / 3.0)
#define MAX_DEPTH 16.0
 
/*Карта*/
int worldMap[MAP_HEIGHT][MAP_WIDTH] = {
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
 
double playerX = 3.0;  /*Начальные координаты игрока*/
double playerY = 3.0;
double playerAngle = 0.0;
 
void draw() {
    int y, x, testX, testY, hit, ceiling, floor;
    double angleDifference;
        double eyeX, eyeY, rayAngle, distanceToWall;
    printf("\033[2J");
    printf("\033[H");
    fflush(stdout);
 
    for (y = 0; y < SCREEN_HEIGHT; y++) {
        for (x = 0; x < SCREEN_WIDTH; x++) {
              /*Вычисляем угол для каждого луча*/
            rayAngle = (playerAngle - FOV / 2.0) + ((double)x / (double)SCREEN_WIDTH) * FOV;
            distanceToWall = 0.0;
             hit = 0;
 
            eyeX = cos(rayAngle);
            eyeY = sin(rayAngle);
 
            while (!hit && distanceToWall < MAX_DEPTH) {
                distanceToWall += 0.1;  /* шаг по лучу*/
                 testX = (int)(playerX + eyeX * distanceToWall); /*переменная луча по X*/
                 testY = (int)(playerY + eyeY * distanceToWall); /*по Y*/
 
                if (testX < 0 || testX >= MAP_WIDTH || testY < 0 || testY >= MAP_HEIGHT) {
                    hit = 1;  /*луч вышел за границы карты, считаем, что столкнулся с границей*/
                    distanceToWall = MAX_DEPTH;
                } else {
                    if (worldMap[testY][testX] == 1) {
                        hit = 1; /*столкновение со стеной*/
                    }
                }
            }
 
            /*Корректировка расстояния для устранения эффекта рыбьего глаза*/
             angleDifference = rayAngle - playerAngle;
            if (angleDifference > M_PI) angleDifference -= 2 * M_PI;
            if (angleDifference < -M_PI) angleDifference += 2 * M_PI;
 
            ceiling = (int)((SCREEN_HEIGHT / 2.0) - SCREEN_HEIGHT / distanceToWall);
            floor = SCREEN_HEIGHT - ceiling;
 
            if (y < ceiling) {
                printf(" "); /*рисуем пробел - небо*/
            } else if (y > ceiling && y <= floor) {
                if (distanceToWall <= MAX_DEPTH / 4.0) {
                    printf("#"); /*рисуем стены разной жирности*/
                } else if (distanceToWall < MAX_DEPTH / 2.0) {
                    printf("+");
                } else {
                    printf(".");
                }
            } else {
                printf(" "); /*рисуем пол*/
            }
        }
        printf("\n");
    }
}
 
int main() {
    char command[10];
    double newX,  newY;
    char c;
 
    while (1) {
        draw();
        printf("Введите команду (W/A/S/D для движения, 'exit' для выхода): ");
        if (!fgets(command, sizeof(command), stdin)) break;
 
        if (strncmp(command, "exit", 4) == 0) {
            break; /*Выход из программы*/
        }
 
        c = command[0];
        if (c == 'w' || c == 'W') {
            /*Вперед*/
             newX = playerX + cos(playerAngle) * 0.2;
             newY = playerY + sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 's' || c == 'S') {
            /*Назад*/
            newX = playerX - cos(playerAngle) * 0.2;
            newY = playerY - sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 'a' || c == 'A') {
            /*Поворот налево*/
            playerAngle -= 0.1;
        } else if (c == 'd' || c == 'D') {
            /*Поворот направо*/
            playerAngle += 0.1;
        }
    }
    return 0;
}

В стиле матрицы:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*Модель 3D Raycasting на C(89). Написана нейронной сетью. Отредактирована by dcc0.
Стены с помощью символов
Компиляция и запуск gcc -o ray ray.c   -lm; ./ray
* или gcc -std=c89  -o ray ray.c   -lm; ./ray
Убран эффект рыбьего глаза*/
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
/*Размеры карты и экрана*/
#define MAP_WIDTH 16
#define MAP_HEIGHT 16
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 24
#define M_PI 3.14159265358979323846
#define FOV (M_PI / 3.0)
#define MAX_DEPTH 16.0
 
/*Карта*/
int worldMap[MAP_HEIGHT][MAP_WIDTH] = {
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
 
double playerX = 3.0;  /*Начальные координаты игрока*/
double playerY = 3.0;
double playerAngle = 0.0;
 
void draw() {
    int y, x, RayX, RayY, hit, ceiling, floor, rrandd;
    double angleDifference;
    double eyeX, eyeY, rayAngle, distanceToWall;
    printf("\033[2J"); //Очистка экрана
    printf("\033[H"); //И сброс курсора в начало
    fflush(stdout);
 
    for (y = 0; y < SCREEN_HEIGHT; y++) {
        for (x = 0; x < SCREEN_WIDTH; x++) {
              /*Вычисляем угол для каждого луча*/
            rayAngle = (playerAngle - FOV / 2.0) + ((double)x / (double)SCREEN_WIDTH) * FOV;
            distanceToWall = 0.0;
             hit = 0;
             /*Можно удалить */
            //eyeX = cos(rayAngle);
            //eyeY = sin(rayAngle);
            while (!hit && distanceToWall < MAX_DEPTH) {
                distanceToWall += 0.1;  /* шаг по лучу*/
                // testX = (int)(playerX + eyeX * distanceToWall); /*переменная луча по X*/
                 //testY = (int)(playerY + eyeY * distanceToWall); /*по Y*/
                RayX = (int)(playerX + cos(rayAngle) * distanceToWall); /*переменная луча по X*/
                RayY = (int)(playerY + sin(rayAngle) * distanceToWall); /*по Y*/
 
                if (RayX < 0 || RayX >= MAP_WIDTH || RayY < 0 || RayY >= MAP_HEIGHT) {
                    hit = 1;  /*луч вышел за границы карты, считаем, что столкнулся с границей*/
                    distanceToWall = MAX_DEPTH;
                } else {
                    if (worldMap[RayY][RayX] == 1) {
                        hit = 1; /*столкновение со стеной*/
                    }
                }
            }
 
            /*Корректировка расстояния для устранения эффекта рыбьего глаза*/
             angleDifference = rayAngle - playerAngle;
            if (angleDifference > M_PI) angleDifference -= 2 * M_PI;
            if (angleDifference < -M_PI) angleDifference += 2 * M_PI;
 
            ceiling = (int)((SCREEN_HEIGHT / 2.0) - SCREEN_HEIGHT / distanceToWall);
            floor = SCREEN_HEIGHT - ceiling;
 
             srand(time(NULL));
             rrandd = (rand() %10);
 
            if (y < ceiling) {
                printf(" "); /*рисуем пробел - небо*/
            } else if (y > ceiling && y <= floor) {
                if (distanceToWall <= MAX_DEPTH / 4.0) {
                    printf("\033[0;32m");
                    printf("%d",  rrandd ); /*рисуем стены разной жирности*/
                } else if (distanceToWall < MAX_DEPTH / 2.0) {
                    printf("+");
                } else {
                    printf(".");
                }
            } else {
                printf("\033[0;32m");
                printf(" "); /*рисуем пол*/
            }
        }
        printf("\n");
    }
}
 
int main() {
    char command[10];
    double newX,  newY;
    char c;
//Управление
    while (1) {
        //На каждое движение перерисовываем видимую область
        draw();
        printf("Введите  (W/A/S/D + enter, 'exit'  - выход): ");
        if (!fgets(command, sizeof(command), stdin)) break;
 
        if (strncmp(command, "exit", 4) == 0) {
            break; /*Выход из программы*/
        }
 
        c = command[0];
        if (c == 'w' || c == 'W') {
            /*Вперед*/
             newX = playerX + cos(playerAngle) * 0.2;
             newY = playerY + sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 's' || c == 'S') {
            /*Назад*/
            newX = playerX - cos(playerAngle) * 0.2;
            newY = playerY - sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 'a' || c == 'A') {
            /*Поворот налево*/
            playerAngle -= 0.1;
        } else if (c == 'd' || c == 'D') {
            /*Поворот направо*/
            playerAngle += 0.1;
        }
    }
    return 0;
}

На PHP:
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<?php
session_start();
 
// Размеры карты и параметры
define('MAP_WIDTH', 16);
define('MAP_HEIGHT', 16);
define('SCREEN_WIDTH', 60);
define('SCREEN_HEIGHT', 24);
define('FOV', M_PI / 3.0);
define('MAX_DEPTH', 16.0);
 
// Карта
$worldMap = [
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1],
    [1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,1],
    [1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
];
 
// Инициализация или получение состояния игрока
if (!isset($_SESSION['playerX'])) {
    $_SESSION['playerX'] = 3.0;
    $_SESSION['playerY'] = 3.0;
    $_SESSION['playerAngle'] = 0.0;
}
 
// Обработка команд
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $command = $_POST['command'] ?? '';
 
    $playerX = &$_SESSION['playerX'];
    $playerY = &$_SESSION['playerY'];
    $playerAngle = &$_SESSION['playerAngle'];
 
    $newX = $playerX;
    $newY = $playerY;
 
    switch ($command) {
        case 'W':
        case 'w':
            $newX = $playerX + cos($playerAngle) * 0.2;
            $newY = $playerY + sin($playerAngle) * 0.2;
            if ($worldMap[(int)$newY][(int)$newX] === 0) {
                $playerX = $newX;
                $playerY = $newY;
            }
            break;
        case 'S':
        case 's':
            $newX = $playerX - cos($playerAngle) * 0.2;
            $newY = $playerY - sin($playerAngle) * 0.2;
            if ($worldMap[(int)$newY][(int)$newX] === 0) {
                $playerX = $newX;
                $playerY = $newY;
            }
            break;
        case 'A':
        case 'a':
            $playerAngle -= 0.1;
            break;
        case 'D':
        case 'd':
            $playerAngle += 0.1;
            break;
    }
}
 
// Функция рисования
function draw() {
    global $worldMap, $_SESSION;
 
    $playerX = $_SESSION['playerX'];
    $playerY = $_SESSION['playerY'];
    $playerAngle = $_SESSION['playerAngle'];
 
    $output = "3d"; // Очистка терминала
    for ($y = 0; $y < SCREEN_HEIGHT; $y++) {
        for ($x = 0; $x < SCREEN_WIDTH; $x++) {
            $rayAngle = ($playerAngle - FOV / 2) + (($x / SCREEN_WIDTH) * FOV);
            $distanceToWall = 0.0;
            $hit = false;
 
            while (!$hit && $distanceToWall < MAX_DEPTH) {
                $distanceToWall += 0.1;
                $rayX = (int)($playerX + cos($rayAngle) * $distanceToWall);
                $rayY = (int)($playerY + sin($rayAngle) * $distanceToWall);
 
                if ($rayX < 0 || $rayX >= MAP_WIDTH || $rayY < 0 || $rayY >= MAP_HEIGHT) {
                    $hit = true;
                    $distanceToWall = MAX_DEPTH;
                } else {
                    if ($worldMap[$rayY][$rayX] == 1) {
                        $hit = true;
                    }
                }
            }
 
            $angleDiff = $rayAngle - $playerAngle;
            if ($angleDiff > M_PI) $angleDiff -= 2 * M_PI;
            if ($angleDiff < -M_PI) $angleDiff += 2 * M_PI;
 
            $ceiling = (int)((SCREEN_HEIGHT / 2.0) - SCREEN_HEIGHT / $distanceToWall);
            $floor = SCREEN_HEIGHT - $ceiling;
 
            if ($y < $ceiling) {
                $output .= " ";
            } elseif ($y > $ceiling && $y <= $floor) {
                if ($distanceToWall <= MAX_DEPTH / 4.0) {
                    $output .= "#";
                } elseif ($distanceToWall < MAX_DEPTH / 2.0) {
                    $output .= "+";
                } else {
                    $output .= ".";
                }
            } else {
                $output .= " ";
            }
        }
        $output .= "\n";
    }
    return $output;
}
 
?>
 
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>3D Raycasting</title>
</head>
<body style="font-family: monospace; white-space: pre;">
<h2>3D Raycasting на PHP</h2>
<pre>
<?php echo draw(); ?>
</pre>
<form method="post">
    <button name="command" value="W">Вперед (W)</button>
    <button name="command" value="S">Назад (S)</button>
    <button name="command" value="A">Поворот налево (A)</button>
    <button name="command" value="D">Поворот направо (D)</button>
    <button type="submit" name="command" value="exit">Выход</button>
</form>
</body>
</html>
Ещё один вариант подредактированный. В нем заменил инкремент/декремент угла игрока на
предпосчитанную матрицу, Чтобы не значение при вращении не накапливалось:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*Модель 3D Raycasting на C(89). Написана нейронной сетью. Отредактирована by dcc0.
Стены с помощью символов
Компиляция и запуск gcc -o ray ray.c   -lm; ./ray
* Поворот исправлен на матрицу. Формула поиска угла луча упрощена.
* или gcc -std=c89  -o ray ray.c   -lm; ./ray
Убран эффект рыбьего глаза*/
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
/*Размеры карты и экрана*/
#define MAP_WIDTH 16
#define MAP_HEIGHT 16
#define SCREEN_WIDTH 60
#define SCREEN_HEIGHT 24
#define M_PI 3.14159265358979323846
#define FOV (M_PI / 3.0)
#define MAX_DEPTH 16.0
 
/*Карта*/
int worldMap[MAP_HEIGHT][MAP_WIDTH] = {
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,1},
    {1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//Матрица для поворота наблюдателя вокруг себя 
double matrix_angle[33] = {-8, -7.5, -7, -6.5, -6, -5.5, -5, -4.5, -4, -3.5, -3, -2.5, -2,
-1.5, -1, -0.5, 0.0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8};
 
double playerX = 3.0;  /*Начальные координаты игрока*/
double playerY = 3.0;
//double playerAngle = 0.0;
 
 
void draw(double playerAngle) {
    int y, x, RayX, RayY, hit, ceiling, floor, rrandd;
    double angleDifference;
    double eyeX, eyeY, rayAngle, distanceToWall;
    printf("\033[2J"); //Очистка экрана
    printf("\033[H"); //И сброс курсора в начало
    fflush(stdout);
  
 
    for (y = 0; y < SCREEN_HEIGHT; y++) {
        for (x = 0; x < SCREEN_WIDTH; x++) {
              /*Вычисляем угол для каждого луча*/
            //rayAngle = (playerAngle - FOV / 2.0) + ((double)x / (double)SCREEN_WIDTH) * FOV;
            //Угол игрока минус FOV/2 (30 градусов влево от точки взгляда игрока)
            //  + x умн. на 1 градус в радианах = 0.0174532925 
            //Из FOV (= 60 градусов) вычитаем 30 градусов в радианах для левой части от POV (Point of view - точка обзора)
            //Если не вычитать 0.52359878, то получим только половину обзора
            //Но если убрать, все равно будет работать, будто закрыли один глаз
            //Нам надо найти 60 углов для луча, но не от точки взгдяла, а от точки взгляд - 30градусов (в радианх)
            //а это и есть минус 52359878
            //Так тоже можно, но тогда углы будут от точки взгляда, левая часть из обзора пропадет
            //rayAngle = (playerAngle) + ((double)x * 0.0174532925);
            rayAngle = (playerAngle - 0.52359878) + ((double)x * 0.0174532925);
            distanceToWall = 0.0;
             hit = 0;
             /*Можно удалить */
            //eyeX = cos(rayAngle);
            //eyeY = sin(rayAngle);
            //Тут луч начианет двигаться
            while (!hit && distanceToWall < MAX_DEPTH) {
                distanceToWall += 0.1;  /* шаг по лучу*/
                // testX = (int)(playerX + eyeX * distanceToWall); /*переменная луча по X*/
                 //testY = (int)(playerY + eyeY * distanceToWall); /*по Y*/
                //Округлим до целого. Это и есть луч. 
                RayX = (int)(playerX + cos(rayAngle) * distanceToWall); /*переменная луча по X*/
                RayY = (int)(playerY + sin(rayAngle) * distanceToWall); /*по Y*/
                //Можно закомментировать. При такой карте не сработает.
                //if (RayX < 0 || RayX >= MAP_WIDTH || RayY < 0 || RayY >= MAP_HEIGHT) {
                  //  hit = 1;  /*луч вышел за границы карты, считаем, что столкнулся с границей*/
                   // distanceToWall = MAX_DEPTH;
                //} else {
                //Луч всегда упрется в стену
                    if (worldMap[RayY][RayX] == 1) {
                        hit = 1; /*столкновение со стеной*/
                    }
                //}
            }
            // new: Корректировка расстояния для устранения эффекта рыбьего глаза (new)
        angleDifference = rayAngle - playerAngle; // new: расчет разницы углов (new)
        if (angleDifference > M_PI) angleDifference -= 2 * M_PI; // new: приведение к диапазону (-π, π) (new)
        if (angleDifference < -M_PI) angleDifference += 2 * M_PI; // new
        distanceToWall = distanceToWall * cos(angleDifference); // new: исправление расстояния (new)
 
            //Делим на двое высоту находим точку до которой рисовать потолок пробелом 
            ceiling = (int)((SCREEN_HEIGHT / 2.0) - SCREEN_HEIGHT / distanceToWall);
            //откуда начнется пол, тоже пробелами
            floor = SCREEN_HEIGHT - ceiling;
 
            if (y < ceiling) {
                printf(" "); /*рисуем пробел - небо*/
            } else if (y > ceiling && y <= floor) {
                if (distanceToWall <= MAX_DEPTH / 4.0) {
                    printf("#"); /*рисуем стены разной жирности*/
                } else if (distanceToWall < MAX_DEPTH / 2.0) {
                    printf("+");
                } else {
                    printf(".");
                }
            } else {
 
                printf(" "); /*рисуем пол*/
 
            }
 
        }
 
        printf("\n");
 
    }
  printf("\nУгол поворта: %g\n", playerAngle);
 
}
 
int main() {
    char command[10];
    double newX,  newY;
    char c;
    int ma = 16;
    //Начальный угол игрока = 0.0
    double playerAngle = matrix_angle[16];
//Управление
    while (1) {
        
        //На каждое движение перерисовываем видимую область
        draw(playerAngle);
        printf("Введите  (W/A/S/D + enter, 'exit'  - выход): ");
        if (!fgets(command, sizeof(command), stdin)) break;
 
        if (strncmp(command, "exit", 4) == 0) {
            break; /*Выход из программы*/
        }
 
        c = command[0];
        if (c == 'w' || c == 'W') {
            /*Вперед*/
             newX = playerX + cos(playerAngle) * 0.2;
             newY = playerY + sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 's' || c == 'S') {
            /*Назад*/
            newX = playerX - cos(playerAngle) * 0.2;
            newY = playerY - sin(playerAngle) * 0.2;
            if (worldMap[(int)newY][(int)newX] == 0) {
                playerX = newX;
                playerY = newY;
            }
        } else if (c == 'a' || c == 'A') {
            /*Поворот налево*/
            ma--;
           if (ma < 0) ma = 32;
            if (ma > 32) ma = 0;
            playerAngle = matrix_angle[ma];
            //playerAngle -= 0.1;
        } else if (c == 'd' || c == 'D') {
            /*Поворот направо*/
            ma++;
            if (ma < 0) ma = 32;
            if (ma > 32) ma = 0;
             playerAngle = matrix_angle[ma];
            //playerAngle += 0.1;
        }
           
    }
 
    return 0;
}

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Учебный мини "цифровой двойник" = (Сервер = Java + Flask_питон )+(3D = Гугловскаая 4T nvideo + google colab) +( WMS = Odoo на Docker)0707-07-2026
2GitDOS: Когда я решил скрестить MS-DOS, GitHub и Claude5708-07-2026
3Нейросеть трансформер на PDP 11/40 7801-07-2026
4Правильный запуск AI дорвея в 2026?0328-06-2026
5🤯 Ты ещё не используешь нейросети? А они уже генерируют ...7804-07-2026
6😨Над легендарным движком id Tech теперь, похоже, работает всего один ...-5609-07-2026
7HOWTO: как установить и настроить собственный ИИ на игровом ПК5721-05-2023
8Когда контекстное окно кончается, а проект — нет5723-06-2026
9Делаем недорогой лазерный 3D-сканер своими руками0514-12-2011
10Сравнение отзывчивости ввода в X11 и Wayland0714-07-2026

Классификация: Наука. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 5. Источник: www.cyberforum.ru.