Форум программистов, компьютерный форум, киберфорум
8Observer8
Войти
Регистрация
Восстановить пароль

3. Переходим на рисование фигур с помощью функции glDrawElements(­­­) - вместо glBegin()/glEnd(). Змейка

Запись от 8Observer8 размещена 17.11.2014 в 16:46
Показов 15420 Комментарии 4
Метки blender, c++

Содержание блога

В предыдущей инструкции мы рисовали с помощью glBegin()/glEnd(). Этот способ устарел. Переходим на новый уровень. Здесь будем рисовать с помощью функции glDrawElements, а формировать объекты с помощью массивов вершин и индексов. Цвет будем задавать с помощью массива цветов. Это более удобный способ. Он позволяет легко накладывать текстуры, указав текстурные координаты (об этом в следующих инструкция)

Рисуем треугольник

- Создаём проект c диалоговым окном (имя проекта "Triangle") по инструкции: Первое оконное приложение на Qt

- Создаём площадку для рисования: Создание проекта с площадкой для рисования

- Нарисуем треугольник на бумаге, или например в Paint'е, и расставим номера вершин с обходом против часовой стрелки:



- Создаём заголовочный файл "Triangle.h". Для этого кликаем правой кнопкой мыши по имени проекта и выбираем: "Add New..." -> в левой колонке выбираем "C++" -> в средней колонке выбираем "C++ Header File" -> нажимаем кнопку "Choose..." -> вводим имя файла: Triangle -> нажимаем "Next" -> "Finish"

- Объявляем три массива: массив вершин, массив индексов и массив цветов. Заполняем в соответствии с рисунком выше. Скопируйте код:

Triangle.h
Кликните здесь для просмотра всего текста
C++
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
#ifndef TRIANGLE_H
#define TRIANGLE_H
 
#include <vector>
 
class Triangle
{
public:
    Triangle()
    {
        init();
    }
 
    void init()
    {
        // 0
        vertices.push_back( 0 );    // X
        vertices.push_back( 1 );    // Y
        vertices.push_back( 0 );    // Z
 
        // 1
        vertices.push_back( -1 );   // X
        vertices.push_back( -1 );   // Y
        vertices.push_back( 0 );    // Z
 
        // 2
        vertices.push_back( 1 );    // X
        vertices.push_back( -1 );   // Y
        vertices.push_back( 0 );    // Z
 
        indices.push_back( 0 );
        indices.push_back( 1 );
        indices.push_back( 2 );
 
        for ( unsigned int i = 0; i < 3; ++i ) {
            colors.push_back( 0 );   // R
            colors.push_back( 1 );   // G
            colors.push_back( 0 );   // B
        }
    }
 
    std::vector<int> vertices;
    std::vector<unsigned int> indices;
    std::vector<float> colors;
};
 
#endif // TRIANGLE_H


- В файле "Scene.h" подключаем файл "Triangle.h". Объявляем функцию drawTriangle(). Создаём объект triangle класса Triangle. Скопируйте код:

Scene.h

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef SCENE_H
#define SCENE_H
 
#include <QGLWidget>
#include "Triangle.h"
 
class Scene : public QGLWidget
{
public:
    Scene( QWidget *parent = 0 );
 
private:
    void initializeGL();
    void paintGL();
    void resizeGL( int w, int h );
 
    void drawTriangle();
 
private:
    Triangle triangle;
};
 
#endif // SCENE_H
- Копируем содержимое файла "Scene.cpp". Читайте комментарии:

Scene.cpp
Кликните здесь для просмотра всего текста
C++
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
#include "Scene.h"
 
Scene::Scene( QWidget *parent ) :
    QGLWidget( parent )
{
 
}
 
void Scene::initializeGL()
{
    // Цвет для очистки буфера изображения - будет просто фон окна
    glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
 
    // Устанавливает режим проверки глубины пикселей
    glEnable( GL_DEPTH_TEST );
 
    // Отключает режим сглаживания цветов
    glShadeModel( GL_FLAT );
 
    // Устанавливаем режим, когда строятся только внешние поверхности
    glEnable( GL_CULL_FACE );
 
    // Активизация массива вершин
    glEnableClientState( GL_VERTEX_ARRAY );
 
    // Активизация массива цветов вершин
    glEnableClientState( GL_COLOR_ARRAY );
}
 
void Scene::paintGL()
{
    // Окно виджета очищается текущим цветом очистки
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 
    drawTriangle();
}
 
void Scene::resizeGL( int w, int h )
{
    // Set Viewport to window dimensions
    glViewport( 0, 0, w, h );
 
    // Reset coordinate system
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
 
    // Establish clipping volume (left, right, bottom, top, near, far)
    glOrtho( -1, 1, -1, 1, 1, -1);
 
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
}
 
void Scene::drawTriangle()
{
    // Указываем, откуда нужно извлечь данные о массиве вершин
    glVertexPointer( 3, GL_INT, 0, triangle.vertices.data() );
 
    // Указываем, откуда нужно извлечь данные о массиве цветов вершин
    glColorPointer( 3, GL_FLOAT, 0, triangle.colors.data() );
 
    // Используя массивы вершин и индексов, строим поверхности
    glDrawElements( GL_TRIANGLES, triangle.indices.size(), GL_UNSIGNED_INT, triangle.indices.data() );
}


- Запускаем приложение. Мы видим треугольник

Рисуем Квадрат

- Мы можем создать новый проект с именем Square по примеру проекта "Рисуем треугольник". Либо мы можем добавить новый класс "Square" в предыдущий проект (в Scene нужно будет добавить функцию drawSquare). Либо мы можем создать копию папки нашего предыдущего проекта и переименовать её в Square (соответственно, все слова "triangle" нужно заменить на "square"). Я выбрал последний вариант

Примечание. Треугольник в OpengGL - это минимальная единица для рисования любой 2D и 3D фигуры (или поверхности). Этот способ рисования позволяет легко накладывать текстуры, задав массив текстурных координат, а об этом в следующих инструкциях

- Рисуем, например в Paint'е, квадрат, который состоит из двух треугольников. Нумеруем вершины, соблюдая обход против часовой стрелки:



- Создаём класс Square. В соответствии с рисунком выше заполняем массивы вершин, индексов и цветов:

Square.h
Кликните здесь для просмотра всего текста
C++
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
#ifndef SQUARE_H
#define SQUARE_H
 
#include <vector>
 
class Square
{
public:
    Square()
    {
        init();
    }
 
    void init()
    {
        // Первый треугольник
        // 0
        vertices.push_back( -1 );    // X
        vertices.push_back( -1 );    // Y
        vertices.push_back( 0 );     // Z
 
        // 1
        vertices.push_back( 1 );     // X
        vertices.push_back( -1 );    // Y
        vertices.push_back( 0 );     // Z
 
        // 2
        vertices.push_back( -1 );    // X
        vertices.push_back( 1 );     // Y
        vertices.push_back( 0 );     // Z
 
        // Второй треугольник
        // 3
        vertices.push_back( -1 );    // X
        vertices.push_back( 1 );     // Y
        vertices.push_back( 0 );     // Z
 
        // 4
        vertices.push_back( 1 );     // X
        vertices.push_back( -1 );    // Y
        vertices.push_back( 0 );     // Z
 
        // 5
        vertices.push_back( 1 );     // X
        vertices.push_back( 1 );     // Y
        vertices.push_back( 0 );     // Z
 
        for ( unsigned int i = 0; i < 6; ++i ) {
            indices.push_back( i );
        }
 
        for ( unsigned int i = 0; i < 6; ++i ) {
            colors.push_back( 0 );   // R
            colors.push_back( 1 );   // G
            colors.push_back( 0 );   // B
        }
    }
 
    std::vector<int> vertices;
    std::vector<unsigned int> indices;
    std::vector<float> colors;
};
 
#endif // SQUARE_H


В классе Scene меняем в тексте программы "triangle" на "square" и запускаем приложение:

Scene.h
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef SCENE_H
#define SCENE_H
 
#include <QGLWidget>
#include "Square.h"
 
class Scene : public QGLWidget
{
public:
    Scene( QWidget *parent = 0 );
 
private:
    void initializeGL();
    void paintGL();
    void resizeGL( int w, int h );
 
    void drawSquare();
 
private:
    Square square;
};
 
#endif // SCENE_H


Scene.cpp
Кликните здесь для просмотра всего текста
C++
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
#include "Scene.h"
 
Scene::Scene( QWidget *parent ) :
    QGLWidget( parent )
{
 
}
 
void Scene::initializeGL()
{
    // Цвет для очистки буфера изображения - будет просто фон окна
    glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
 
    // Устанавливает режим проверки глубины пикселей
    glEnable( GL_DEPTH_TEST );
 
    // Отключает режим сглаживания цветов
    glShadeModel( GL_FLAT );
 
    // Устанавливаем режим, когда строятся только внешние поверхности
    glEnable( GL_CULL_FACE );
 
    // Активизация массива вершин
    glEnableClientState( GL_VERTEX_ARRAY );
 
    // Активизация массива цветов вершин
    glEnableClientState( GL_COLOR_ARRAY );
}
 
void Scene::paintGL()
{
    // Окно виджета очищается текущим цветом очистки
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 
    drawSquare();
}
 
void Scene::resizeGL( int w, int h )
{
    // Set Viewport to window dimensions
    glViewport( 0, 0, w, h );
 
    // Reset coordinate system
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
 
    // Establish clipping volume (left, right, bottom, top, near, far)
    glOrtho( -2, 2, -2, 2, 1, -1);
 
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
}
 
void Scene::drawSquare()
{
    // Указываем, откуда нужно извлечь данные о массиве вершин
    glVertexPointer( 3, GL_INT, 0, square.vertices.data() );
 
    // Указываем, откуда нужно извлечь данные о массиве цветов вершин
    glColorPointer( 3, GL_FLOAT, 0, square.colors.data() );
 
    // Используя массивы вершин и индексов, строим поверхности
    glDrawElements( GL_TRIANGLES, square.indices.size(), GL_UNSIGNED_INT, square.indices.data() );
}


- Если мы хотим увидеть каркас, то есть треугольники без закрашивания, то нужно в initializeGL() добавить эту строку:
C++
1
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
Змейка

Переделал змейку из предыдущей инструкции. Изменения коснулись класса Painter и немного класса Scene. Просто заменил способ рисования glBegin()/glEnd() на glDrawElements В комментариях в коде - то что было:

Painter.h
Кликните здесь для просмотра всего текста
C++
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
#ifndef PAINTER_H
#define PAINTER_H
 
#include <vector>
 
class Painter
{
public:
    Painter();
    void bar( int x1, int y1, int x2, int y2 );
    void circle( int x, int y, int radius );
 
private:
    void setBarVertices( int x1, int y1, int x2, int y2 );
    void setBarIndices();
    void setBarColor( float r, float g, float b );
    std::vector<int> m_barVertices;
    std::vector<unsigned int> m_barIndices;
    std::vector<float> m_barColors;
 
    void setCircleVertices( int x, int y, int radius );
    void setCircleIndices();
    void setCircleColor( float r, float g, float b );
    std::vector<int> m_circleVertices;
    std::vector<unsigned int> m_circleIndices;
    std::vector<float> m_circleColors;
};
 
#endif // PAINTER_H


Painter.cpp
Кликните здесь для просмотра всего текста
C++
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
188
189
190
191
192
193
194
195
196
197
198
199
200
#include "Painter.h"
#include <GL/gl.h>
 
Painter::Painter()
{
    m_barVertices.resize( 6 * 3 );
    m_barIndices.resize( 6 );
    m_barColors.resize( 6 * 3 );
 
    m_circleVertices.resize( 6 * 3 );
    m_circleIndices.resize( 6 );
    m_circleColors.resize( 6 * 3 );
}
 
void Painter::bar( int x1, int y1, int x2, int y2 )
{
    setBarVertices( x1, y1, x2, y2 );
    setBarIndices();
    setBarColor( 0, 1, 0 );
 
    // Указываем, откуда нужно извлечь данные о массиве вершин
    glVertexPointer( 3, GL_INT, 0, m_barVertices.data() );
 
    // Указываем, откуда нужно извлечь данные о массиве цветов вершин
    glColorPointer( 3, GL_FLOAT, 0, m_barColors.data() );
 
    // Используя массивы вершин и индексов, строим поверхности
    glDrawElements( GL_TRIANGLES, m_barIndices.size(), GL_UNSIGNED_INT, m_barIndices.data() );
 
//    glBegin( GL_QUADS );
//    {
//        glVertex2f( x1, y1 );
//        glVertex2f( x2, y1 );
//        glVertex2f( x2, y2 );
//        glVertex2f( x1, y2 );
//    }
//    glEnd();
}
 
void Painter::circle( int x, int y, int radius )
{
    setCircleVertices( x, y, radius );
    setCircleIndices();
    setCircleColor( 1, 0, 0 );
 
    // Указываем, откуда нужно извлечь данные о массиве вершин
    glVertexPointer( 3, GL_INT, 0, m_circleVertices.data() );
 
    // Указываем, откуда нужно извлечь данные о массиве цветов вершин
    glColorPointer( 3, GL_FLOAT, 0, m_circleColors.data() );
 
    // Используя массивы вершин и индексов, строим поверхности
    glDrawElements( GL_TRIANGLES, m_circleIndices.size(), GL_UNSIGNED_INT, m_circleIndices.data() );
 
//    glColor3f( 1, 0, 0 );
//    glBegin( GL_POLYGON );
//    {
//        glVertex2f( x + radius, y );
//        glVertex2f( x, y + radius );
//        glVertex2f( x - radius, y );
//        glVertex2f( x, y - radius );
//    }
//    glEnd();
}
 
void Painter::setBarVertices( int x1, int y1, int x2, int y2 )
{
    // Первый треугольник
    // 0
    m_barVertices[0] = x1;
    m_barVertices[1] = y2;
    m_barVertices[2] = 0;
 
    // 1
    m_barVertices[3] = x2;
    m_barVertices[4] = y2;
    m_barVertices[5] = 0;
 
    // 2
    m_barVertices[6] = x1;
    m_barVertices[7] = y1;
    m_barVertices[8] = 0;
 
    // Второй треугольник
    // 3
    m_barVertices[9] = x1;
    m_barVertices[10] = y1;
    m_barVertices[11] = 0;
 
    // 4
    m_barVertices[12] = x2;
    m_barVertices[13] = y2;
    m_barVertices[14] = 0;
 
    // 5
    m_barVertices[15] = x2;
    m_barVertices[16] = y1;
    m_barVertices[17] = 0;
}
 
void Painter::setBarIndices()
{
    for ( unsigned int i = 0; i < 6; ++i ) {
        m_barIndices[i] = i;
    }
}
 
void Painter::setBarColor( float r, float g, float b )
{
    m_barColors[0] = r;
    m_barColors[1] = g;
    m_barColors[2] = b;
 
    m_barColors[3] = r;
    m_barColors[4] = g;
    m_barColors[5] = b;
 
    m_barColors[6] = r;
    m_barColors[7] = g;
    m_barColors[8] = b;
 
    m_barColors[9] = r;
    m_barColors[10] = g;
    m_barColors[11] = b;
 
    m_barColors[12] = r;
    m_barColors[13] = g;
    m_barColors[14] = b;
 
    m_barColors[15] = r;
    m_barColors[16] = g;
    m_barColors[17] = b;
}
 
void Painter::setCircleVertices( int x, int y, int radius )
{
    // 0
    m_circleVertices[0] = x + radius;
    m_circleVertices[1] = y;
    m_circleVertices[2] = 0;
 
    // 1
    m_circleVertices[3] = x - radius;
    m_circleVertices[4] = y;
    m_circleVertices[5] = 0;
 
    // 2
    m_circleVertices[6] = x;
    m_circleVertices[7] = y + radius;
    m_circleVertices[8] = 0;
 
    // 3
    m_circleVertices[9] = x + radius;
    m_circleVertices[10] = y;
    m_circleVertices[11] = 0;
 
    // 4
    m_circleVertices[12] = x;
    m_circleVertices[13] = y - radius;
    m_circleVertices[14] = 0;
 
    // 5
    m_circleVertices[15] = x - radius;
    m_circleVertices[16] = y;
    m_circleVertices[17] = 0;
}
 
void Painter::setCircleIndices()
{
    for ( unsigned int i = 0; i < m_circleIndices.size(); ++i ) {
        m_circleIndices[i] = i;
    }
}
 
void Painter::setCircleColor( float r, float g, float b )
{
    m_circleColors[0] = r;
    m_circleColors[1] = g;
    m_circleColors[2] = b;
 
    m_circleColors[3] = r;
    m_circleColors[4] = g;
    m_circleColors[5] = b;
 
    m_circleColors[6] = r;
    m_circleColors[7] = g;
    m_circleColors[8] = b;
 
    m_circleColors[9] = r;
    m_circleColors[10] = g;
    m_circleColors[11] = b;
 
    m_circleColors[12] = r;
    m_circleColors[13] = g;
    m_circleColors[14] = b;
 
    m_circleColors[15] = r;
    m_circleColors[16] = g;
    m_circleColors[17] = b;
}


Исполняемый файл для Win7: https://yadi.sk/d/0MZdHSrIcnHxA
Исходники на Qt C++: https://github.com/8Observer8/... awElements

Если мы добавим в Scene::initializeGL() строку

C++
1
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
то увидим каркас:



Продолжение: 4. Простой загрузчик wavefront (.obj) объектов из Blender на C++
Миниатюры
Нажмите на изображение для увеличения
Название: Triangle.png
Просмотров: 1902
Размер:	7.9 Кб
ID:	2862   Нажмите на изображение для увеличения
Название: Square.png
Просмотров: 1990
Размер:	10.4 Кб
ID:	2863  
Изображения
  
Метки blender, c++
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 4
Комментарии
  1. Старый комментарий
    Аватар для HighPredator
    Можете пояснить почему вы считаете рисование через glBegin/glEnd устаревшим способом?
    Запись от HighPredator размещена 18.11.2014 в 09:55 HighPredator вне форума
  2. Старый комментарий
    Часто встречаю подобные фразы на форумах:

    glBegin/glEnd, к сожалению, уже deprecated-функционал
    Я тоже начинал с glBegin, glEnd, glVertex.
    Когда мне понадобилось написать первое OpenGL приложение для Android, я обнаружил, что в OpenGL ES уже нет ни glBegin ни glEnd ни glVertex, оно и к лучшему =)
    Так что лучше сразу думать в терминах glDrawArrays, glDrawElements итд.
    Лично для меня рисование с помощью glDrawElements удовлетворяет всем моим потребностям. Позже перейду на рисование с помощью GLSL. Как освою - напишу инструкцию
    Запись от 8Observer8 размещена 18.11.2014 в 12:24 8Observer8 вне форума
  3. Старый комментарий
    Аватар для programina
    Можете пояснить почему вы считаете рисование
    через glBegin/glEnd устаревшим способом?
    Потому что уже давно есть более эффективные новые аналоги этого г.... мамонта.
    Запись от programina размещена 18.11.2014 в 13:43 programina вне форума
  4. Старый комментарий
    Я изменил рисование в игре "Змейка" из предыдущей инструкции на glDrawElements и добавил в конец инструкции. Можете скачать и поиграть (win7) https://yadi.sk/d/0MZdHSrIcnHxA
    Запись от 8Observer8 размещена 18.11.2014 в 15:40 8Observer8 вне форума
 
Новые блоги и статьи
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru