2 RomanH:
RomanHOpenGL и DirectX пока рано изучать
Изучить OpenGL на начальном уровне -- несложно, особенно если использовать библиотеки вроде GLUT или SDL, которые сильно упрощают создание окна и инициализацию OpenGL, избавляя программиста от возни с низко-уровневыми деталями.
RomanHХочется самому понять и построить видовую матрицу для перемещения FPS камеры.
Нужно понять что такое мировая, видовая, проекционная матрица на простом примере.
И как перемножая координаты вектора на матрицу происходят изменения в координатах вектора.
Вот, написал простой пример, который поможет разобраться с матрицами в 3D-графике. Правда, компилятора C# под рукой не было, поэтому написал на C++. Портирование C++/GDI -> C#/GDI+ должно быть механическим, так как используются только базовые возможности C++. Ну или можно не заморачиваться на портирование, а разобраться с матрицами не отходя от C++.
gdi_3d.cpp:
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. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419. 420. 421. 422. 423. 424. 425. 426. 427. 428. 429. 430. 431. 432. 433. 434. 435. 436. 437. 438. 439. 440. 441. 442. 443. 444. 445. 446. 447. 448. 449. 450. 451. 452. 453. 454. 455. 456. 457. 458. 459. 460. 461. 462. 463. 464. 465. 466. 467. 468. 469. 470. 471. 472. 473. 474. 475. 476. 477. 478. 479. 480. 481. 482. 483. 484. 485. 486. 487. 488. 489. 490. 491. 492. 493. 494. 495. 496. 497. 498. 499. 500. 501. 502. 503. 504. 505. 506. 507. 508. 509. 510. 511. 512.
#include <assert.h>
#include <math.h>
#include <float.h>
#define NOMINMAX
#include <windows.h>
const float _undef_float = -FLT_MAX;
// градусы -> радианы
inline float rad_from_deg(float angle) {
return static_cast<float>(3.141592653589793 / 180) * angle;
}
inline int round(float x) {
return static_cast<int>(floor(x + 0.5F));
}
struct Vec_3 {
float x, y, z;
};
inline Vec_3 v3(float x, float y, float z) {
Vec_3 res = {x, y, z};
return res;
}
inline Vec_3 normalize(Vec_3 v) {
float len = sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
assert(len != 0);
return v3(v.x / len, v.y / len, v.z / len);
}
// векторное произведение (cross product)
// может быть записано как определитель (determinant):
// |i j k |
// |ax ay az|
// |bx by bz|
inline Vec_3 cross(Vec_3 a, Vec_3 b) {
return v3(
/*x:*/ a.y * b.z - b.y * a.z,
/*y:*/ a.z * b.x - b.z * a.x,
/*z:*/ a.x * b.y - b.x * a.y
);
}
struct Matrix {
float elems[4][4];
};
// единичная матрица, см. glLoadIdentity
const Matrix _identity = {{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}};
// перемножает 2 матрицы
Matrix* mul(Matrix* res, const Matrix* a, const Matrix* b) {
assert(res != a);
assert(res != b);
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
float e = 0;
for (int i = 0; i < 4; i++) {
e += a->elems[row][i] * b->elems[i][col];
}
res->elems[row][col] = e;
}
}
return res;
}
// перемножает 3 матрицы
Matrix* mul(Matrix* res, const Matrix* a, const Matrix* b, const Matrix* c) {
Matrix t;
return mul(res, mul(&t, a, b), c);
}
// параллельный перенос, см. glTranslate
Matrix* translate(Matrix* res, Vec_3 v) {
Matrix m = {{
{1, 0, 0, v.x},
{0, 1, 0, v.y},
{0, 0, 1, v.z},
{0, 0, 0, 1 },
}};
*res = m;
return res;
}
// поворот вокруг оси z, см. glRotate (умеет вокруг любой оси)
Matrix* rotate_z(Matrix* res, float angle) {
float c = cos(rad_from_deg(angle));
float s = sin(rad_from_deg(angle));
Matrix m = {{
{c, -s, 0, 0},
{s, c, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}};
*res = m;
return res;
}
// мировые координаты -> камерные координаты, см. gluLookAt
// cam_pos, target_pos - в мировых координатах
Matrix* look_at(Matrix* res, Vec_3 cam_pos, Vec_3 target_pos) {
Matrix m1;
translate(&m1, v3(-cam_pos.x, -cam_pos.y, -cam_pos.z));
// оси камеры в мировых координатах
Vec_3 dir = normalize(v3(target_pos.x - cam_pos.x, target_pos.y - cam_pos.y, target_pos.z - cam_pos.z));
Vec_3 right = normalize(v3(target_pos.y - cam_pos.y, -(target_pos.x - cam_pos.x), 0));
Vec_3 up = cross(right, dir);
Matrix m2 = {{
{right.x, right.y, right.z, 0},
{up.x, up.y, up.z, 0},
{-dir.x, -dir.y, -dir.z, 0},
{0, 0, 0, 1},
}};
return mul(res, &m2, &m1);
}
//----------------
bool _running = false;
void init_window();
void term_window();
int main() {
init_window();
_running = true;
// главный цикл
while (_running) {
MSG m;
GetMessage(&m, /*hWnd:*/NULL, /*wMsgFilterMin:*/0, /*wMsgFilterMax:*/0);
TranslateMessage(&m);
DispatchMessage(&m);
}
term_window();
return 0;
}
const char* const _window_class_name = "gdi_3d";
ATOM _window_class_id = 0;
HWND _window_han = NULL;
// размеры клиентской области окна (client area)
int _window_client_width = -1;
int _window_client_height = -1;
LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void init_window() {
HINSTANCE exe_han = GetModuleHandle(NULL);
// регистрируем оконный класс
assert(_window_class_id == 0);
WNDCLASSEX wc_params;
wc_params.cbSize = sizeof(WNDCLASSEX);
wc_params.style = CS_HREDRAW | CS_VREDRAW; // полная перерисовка клиентской области окна при изменении размеров
wc_params.lpfnWndProc = &window_proc;
wc_params.cbClsExtra = 0;
wc_params.cbWndExtra = 0;
wc_params.hInstance = exe_han;
wc_params.hIcon = NULL;
wc_params.hCursor = LoadCursor(NULL, IDC_ARROW);
wc_params.hbrBackground = NULL; // фон будем рисовать сами
wc_params.lpszMenuName = NULL;
wc_params.lpszClassName = _window_class_name;
wc_params.hIconSm = NULL;
_window_class_id = RegisterClassEx(&wc_params);
// создаём окно
assert(_window_han == NULL);
_window_han = CreateWindowEx(
/*dwExStyle:*/0,
/*lpClassName:*/_window_class_name,
/*lpWindowName:*/"GDI 3D", // заголовок окна
/*dwStyle:*/WS_OVERLAPPEDWINDOW | WS_VISIBLE,
/*x:*/CW_USEDEFAULT,
/*y:*/CW_USEDEFAULT,
/*nWidth:*/CW_USEDEFAULT,
/*nHeight:*/CW_USEDEFAULT,
/*hWndParent:*/NULL,
/*hMenu:*/NULL,
/*hInstance:*/exe_han,
/*lpParam:*/NULL
);
}
void term_window() {
if (_window_han != NULL) {
DestroyWindow(_window_han);
_window_han = NULL;
}
if (_window_class_id != 0) {
HINSTANCE exe_han = GetModuleHandle(NULL);
UnregisterClass(_window_class_name, exe_han);
_window_class_id = 0;
}
}
void request_paint() {
assert(_window_han != NULL);
// помечаем всю клиентскую область окна как требующую перерисовки
InvalidateRect(_window_han, /*lpRect:*/NULL, /*bErase:*/false);
// в ближайшее время система пришлёт нашему окну сообщение WM_PAINT
}
// сферические координаты камеры
float _cam_azim = 270;
float _cam_height = 40;
float _cam_dist = 1000;
enum Proj_Type {_proj_type_perspective, _proj_type_parallel};
Proj_Type _proj_type = _proj_type_perspective;
Vec_3 _car_pos = {0, 0, 0};
float _car_dir = 0;
void key_down(int key) {
switch (key) {
// управление камерой
case VK_LEFT:
_cam_azim -= 10;
request_paint();
break;
case VK_RIGHT:
_cam_azim += 10;
request_paint();
break;
case VK_DOWN: {
float h = _cam_height - 10;
if (h > -90) {
_cam_height = h;
request_paint();
}
break;
}
case VK_UP: {
float h = _cam_height + 10;
if (h < 90) {
_cam_height = h;
request_paint();
}
break;
}
case VK_ADD: {
float d = _cam_dist - 10;
if (d > 0) {
_cam_dist = d;
request_paint();
}
break;
}
case VK_SUBTRACT:
_cam_dist += 10;
request_paint();
break;
case 'P':
// меняем тип проекции
switch (_proj_type) {
case _proj_type_perspective: _proj_type = _proj_type_parallel; break;
case _proj_type_parallel: _proj_type = _proj_type_perspective; break;
default: assert(false);
}
request_paint();
break;
// управление машиной
case 'A':
_car_dir += 10;
request_paint();
break;
case 'D':
_car_dir -= 10;
request_paint();
break;
case 'W':
_car_pos.x += 10 * cos(rad_from_deg(_car_dir));
_car_pos.y += 10 * sin(rad_from_deg(_car_dir));
request_paint();
break;
case 'S':
_car_pos.x -= 10 * cos(rad_from_deg(_car_dir));
_car_pos.y -= 10 * sin(rad_from_deg(_car_dir));
request_paint();
break;
}
}
bool _double_buffered = true;
HDC _gdi_context = NULL;
void render_scene();
// оконная процедура (указывается при регистрации оконного класса)
LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CLOSE:
// завершаем главный цикл
_running = false;
return 0;
case WM_SIZE:
_window_client_width = LOWORD(lParam);
_window_client_height = HIWORD(lParam);
return 0;
case WM_KEYDOWN:
key_down(/*key:*/wParam);
return 0;
case WM_ERASEBKGND:
// здесь ничего не делаем, фон рисуется в функции render_scene
return false; // не нарисовали фон
case WM_PAINT: {
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
if (_double_buffered) {
int x = ps.rcPaint.left;
int y = ps.rcPaint.top;
int w = ps.rcPaint.right - ps.rcPaint.left;
int h = ps.rcPaint.bottom - ps.rcPaint.top;
if ((w > 0) && (h > 0)) {
// рисуем сцену в back buffer
HBITMAP back_buf = CreateCompatibleBitmap(ps.hdc, w, h);
assert(_gdi_context == NULL);
_gdi_context = CreateCompatibleDC(ps.hdc);
SelectObject(_gdi_context, back_buf);
SetViewportOrgEx(_gdi_context, -x, -y, NULL);
render_scene();
// копируем содержимое back buffer-а в окно
BitBlt(ps.hdc, x, y, w, h, _gdi_context, x, y, SRCCOPY);
DeleteDC(_gdi_context);
_gdi_context = NULL;
DeleteObject(back_buf);
}
} else {
// рисуем сцену прямо в окно
assert(_gdi_context == NULL);
_gdi_context = ps.hdc;
render_scene();
_gdi_context = NULL;
}
EndPaint(hwnd, &ps);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
Matrix _world_transform = _identity; // локальные координаты объекта -> мировые координаты
Matrix _view_transform = _identity; // мировые координаты -> камерные координаты
Matrix _proj_transform = _identity; // камерные координаты -> координаты для отсечения
void update_proj_transform() {
if ((_window_client_width <= 0) || (_window_client_height <= 0)) return;
switch (_proj_type) {
case _proj_type_perspective: { // см. gluPerspective
const float max_fov = 90; // максимальный угол обзора (field of view)
float h; // тангенс половины угла обзора по горизонтали
float v; // тангенс половины угла обзора по вертикали
if (_window_client_width > _window_client_height) {
h = tan(rad_from_deg(0.5F * max_fov));
v = _window_client_height * h / _window_client_width;
} else {
v = tan(rad_from_deg(0.5F * max_fov));
h = _window_client_width * v / _window_client_height;
}
Matrix m = {{
{1 / h, 0, 0, 0},
{0, 1 / v, 0, 0},
{0, 0, 1, 0}, // эта строка будет другая при использовании OpenGL
{0, 0, -1, 0},
}};
_proj_transform = m;
break;
}
case _proj_type_parallel: {
const float max_size = 2000; // максимальный размер проекции (в мировых единицах)
float h; // половина размера проекции по горизонтали
float v; // половина размера проекции по вертикали
if (_window_client_width > _window_client_height) {
h = 0.5F * max_size;
v = _window_client_height * h / _window_client_width;
} else {
v = 0.5F * max_size;
h = _window_client_width * v / _window_client_height;
}
Matrix m = {{
{1 / h, 0, 0, 0},
{0, 1 / v, 0, 0},
{0, 0, 1, 0}, // эта строка будет другая при использовании OpenGL
{0, 0, 0, 1},
}};
_proj_transform = m;
break;
}
default: assert(false);
}
}
void update_view_transform() {
// сферические координаты -> декартовы координаты
Vec_3 cam_pos;
cam_pos.x = _car_pos.x + _cam_dist * cos(rad_from_deg(_cam_height)) * cos(rad_from_deg(_cam_azim));
cam_pos.y = _car_pos.y + _cam_dist * cos(rad_from_deg(_cam_height)) * sin(rad_from_deg(_cam_azim));
cam_pos.z = _car_pos.z + _cam_dist * sin(rad_from_deg(_cam_height));
look_at(&_view_transform, cam_pos, /*target_pos:*/_car_pos);
}
struct Edge;
struct Model {
const Vec_3* verts;
int verts_count;
const Edge* edges;
int edges_count;
};
struct Edge {
int vert_1_index;
int vert_2_index;
};
#include "models.cpp"
void render_model(const Model* model);
void render_scene() {
assert(_gdi_context != NULL);
// рисуем фон
RECT r = {0, 0, _window_client_width, _window_client_height};
HBRUSH b = static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
FillRect(_gdi_context, &r, b);
update_proj_transform();
update_view_transform();
_world_transform = _identity;
render_model(&_grid_model);
Matrix t1, t2;
mul(&_world_transform, translate(&t2, _car_pos), rotate_z(&t1, _car_dir));
render_model(&_car_model);
}
void render_model(const Model* model) {
assert(_gdi_context != NULL);
// собираем все преобразования координат в одно
Matrix m;
mul(&m, &_proj_transform, &_view_transform, &_world_transform);
// проецируем все вершины
struct Transformed_Vert {
float x, y; // в пикселах
};
const Vec_3* verts = model->verts;
int verts_count = model->verts_count;
Transformed_Vert* transformed_verts = new Transformed_Vert[verts_count];
for (int i = 0; i < verts_count; i++) {
const Vec_3* v = &verts[i]; // в локальных координатах объекта
Transformed_Vert* tv = &transformed_verts[i];
// вычисляем координаты для отсечения (clip coordinates)
float xc = m.elems[0][0] * v->x + m.elems[0][1] * v->y + m.elems[0][2] * v->z + m.elems[0][3];
float yc = m.elems[1][0] * v->x + m.elems[1][1] * v->y + m.elems[1][2] * v->z + m.elems[1][3];
#if 0
float zc = m.elems[2][0] * v->x + m.elems[2][1] * v->y + m.elems[2][2] * v->z + m.elems[2][3];
#endif
float wc = m.elems[3][0] * v->x + m.elems[3][1] * v->y + m.elems[3][2] * v->z + m.elems[3][3];
if (wc > 0) {
// вычисляем нормализованные координаты устройства (normalized device coordinates)
float xn = xc / wc;
float yn = yc / wc;
// во viewport-е видны точки, у которых -1 <= xn <= 1 и -1 <= yn <= 1
tv->x = (0.5F + 0.5F * xn) * _window_client_width;
tv->y = (0.5F - 0.5F * yn) * _window_client_height; // в GDI, ось y идёт вниз
} else {
tv->x = _undef_float;
tv->y = _undef_float;
}
}
// рисуем рёбра
HPEN p = static_cast<HPEN>(GetStockObject(WHITE_PEN));
SelectObject(_gdi_context, p);
const Edge* edges = model->edges;
int edges_count = model->edges_count;
for (int i = 0; i < edges_count; i++) {
const Edge* e = &edges[i];
const Transformed_Vert* tv1 = &transformed_verts[e->vert_1_index];
const Transformed_Vert* tv2 = &transformed_verts[e->vert_2_index];
if ((tv1->x == _undef_float) || (tv2->x == _undef_float)) continue;
// насколько я знаю, GDI+ поддерживает координаты типа float, поэтому там округлять не надо
MoveToEx(_gdi_context, round(tv1->x), round(tv1->y), NULL);
LineTo(_gdi_context, round(tv2->x), round(tv2->y));
}
delete[] transformed_verts;
}
models.cpp:
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. 201. 202. 203. 204. 205. 206. 207. 208. 209. 210. 211. 212. 213. 214. 215. 216. 217. 218. 219. 220. 221. 222. 223. 224. 225. 226. 227. 228. 229. 230. 231. 232. 233. 234. 235. 236. 237. 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251. 252. 253. 254. 255. 256. 257. 258. 259. 260. 261. 262. 263. 264. 265. 266. 267. 268. 269. 270. 271. 272. 273. 274. 275. 276. 277. 278. 279. 280. 281. 282. 283. 284. 285. 286. 287. 288. 289. 290. 291. 292. 293. 294. 295. 296. 297. 298. 299. 300. 301. 302. 303. 304. 305. 306. 307. 308. 309. 310. 311. 312. 313. 314. 315. 316. 317. 318. 319. 320. 321. 322. 323. 324. 325. 326. 327. 328. 329. 330. 331. 332. 333. 334. 335. 336. 337. 338. 339. 340. 341. 342. 343. 344. 345. 346. 347. 348. 349. 350. 351. 352. 353. 354. 355. 356. 357. 358. 359. 360. 361. 362. 363. 364. 365. 366. 367. 368. 369. 370. 371. 372. 373. 374. 375. 376. 377. 378. 379. 380. 381. 382. 383. 384. 385. 386. 387. 388. 389. 390. 391. 392. 393. 394. 395. 396. 397. 398. 399. 400. 401. 402. 403. 404. 405. 406. 407. 408. 409. 410. 411. 412. 413. 414. 415. 416. 417. 418. 419. 420. 421. 422. 423. 424. 425. 426. 427. 428. 429. 430. 431. 432. 433. 434. 435. 436. 437. 438. 439. 440. 441. 442. 443. 444. 445. 446. 447. 448. 449. 450. 451. 452. 453. 454. 455. 456. 457. 458. 459. 460. 461. 462. 463. 464. 465. 466. 467. 468. 469. 470. 471. 472. 473. 474. 475. 476. 477. 478. 479. 480. 481. 482. 483. 484. 485. 486. 487. 488. 489. 490. 491. 492. 493. 494. 495. 496. 497. 498. 499. 500. 501. 502. 503. 504. 505. 506. 507. 508. 509. 510. 511. 512. 513. 514. 515. 516. 517. 518. 519. 520. 521. 522. 523. 524. 525. 526. 527. 528. 529. 530. 531. 532. 533. 534. 535. 536. 537. 538. 539. 540. 541. 542. 543. 544. 545. 546. 547. 548. 549. 550. 551. 552. 553. 554. 555. 556. 557. 558. 559. 560. 561. 562. 563. 564. 565. 566. 567. 568. 569. 570. 571. 572. 573. 574. 575. 576. 577. 578. 579. 580. 581. 582. 583. 584. 585. 586. 587. 588. 589. 590. 591. 592. 593. 594. 595. 596. 597. 598. 599. 600. 601. 602. 603. 604. 605. 606. 607. 608. 609. 610. 611. 612. 613. 614. 615. 616. 617. 618. 619. 620. 621. 622. 623. 624. 625. 626. 627. 628. 629. 630. 631. 632. 633. 634. 635. 636. 637. 638. 639. 640. 641. 642. 643. 644. 645. 646. 647. 648. 649. 650. 651. 652. 653. 654. 655. 656. 657. 658. 659. 660. 661. 662. 663. 664. 665. 666. 667. 668. 669. 670. 671. 672. 673. 674. 675. 676. 677. 678. 679. 680. 681. 682. 683. 684. 685. 686. 687. 688. 689. 690. 691. 692. 693. 694. 695. 696. 697. 698. 699. 700. 701. 702. 703. 704. 705. 706. 707. 708. 709. 710. 711. 712. 713. 714. 715. 716. 717. 718. 719. 720. 721. 722. 723. 724. 725. 726. 727. 728. 729. 730. 731. 732. 733. 734. 735. 736. 737. 738. 739. 740. 741. 742.
const Vec_3 _grid_verts[121] = {
{-2000.0, -2000.0, 0.0},
{-1600.0, -2000.0, 0.0},
{-1200.0, -2000.0, 0.0},
{-800.0, -2000.0, 0.0},
{-400.0, -2000.0, 0.0},
{0.0, -2000.0, 0.0},
{400.0, -2000.0, 0.0},
{800.0, -2000.0, 0.0},
{1200.0, -2000.0, 0.0},
{1600.0, -2000.0, 0.0},
{2000.0, -2000.0, 0.0},
{-2000.0, -1600.0, 0.0},
{-1600.0, -1600.0, 0.0},
{-1200.0, -1600.0, 0.0},
{-800.0, -1600.0, 0.0},
{-400.0, -1600.0, 0.0},
{0.0, -1600.0, 0.0},
{400.0, -1600.0, 0.0},
{800.0, -1600.0, 0.0},
{1200.0, -1600.0, 0.0},
{1600.0, -1600.0, 0.0},
{2000.0, -1600.0, 0.0},
{-2000.0, -1200.0, 0.0},
{-1600.0, -1200.0, 0.0},
{-1200.0, -1200.0, 0.0},
{-800.0, -1200.0, 0.0},
{-400.0, -1200.0, 0.0},
{0.0, -1200.0, 0.0},
{400.0, -1200.0, 0.0},
{800.0, -1200.0, 0.0},
{1200.0, -1200.0, 0.0},
{1600.0, -1200.0, 0.0},
{2000.0, -1200.0, 0.0},
{-2000.0, -800.0, 0.0},
{-1600.0, -800.0, 0.0},
{-1200.0, -800.0, 0.0},
{-800.0, -800.0, 0.0},
{-400.0, -800.0, 0.0},
{0.0, -800.0, 0.0},
{400.0, -800.0, 0.0},
{800.0, -800.0, 0.0},
{1200.0, -800.0, 0.0},
{1600.0, -800.0, 0.0},
{2000.0, -800.0, 0.0},
{-2000.0, -400.0, 0.0},
{-1600.0, -400.0, 0.0},
{-1200.0, -400.0, 0.0},
{-800.0, -400.0, 0.0},
{-400.0, -400.0, 0.0},
{0.0, -400.0, 0.0},
{400.0, -400.0, 0.0},
{800.0, -400.0, 0.0},
{1200.0, -400.0, 0.0},
{1600.0, -400.0, 0.0},
{2000.0, -400.0, 0.0},
{-2000.0, 0.0, 0.0},
{-1600.0, 0.0, 0.0},
{-1200.0, 0.0, 0.0},
{-800.0, 0.0, 0.0},
{-400.0, 0.0, 0.0},
{0.0, 0.0, 0.0},
{400.0, 0.0, 0.0},
{800.0, 0.0, 0.0},
{1200.0, 0.0, 0.0},
{1600.0, 0.0, 0.0},
{2000.0, 0.0, 0.0},
{-2000.0, 400.0, 0.0},
{-1600.0, 400.0, 0.0},
{-1200.0, 400.0, 0.0},
{-800.0, 400.0, 0.0},
{-400.0, 400.0, 0.0},
{0.0, 400.0, 0.0},
{400.0, 400.0, 0.0},
{800.0, 400.0, 0.0},
{1200.0, 400.0, 0.0},
{1600.0, 400.0, 0.0},
{2000.0, 400.0, 0.0},
{-2000.0, 800.0, 0.0},
{-1600.0, 800.0, 0.0},
{-1200.0, 800.0, 0.0},
{-800.0, 800.0, 0.0},
{-400.0, 800.0, 0.0},
{0.0, 800.0, 0.0},
{400.0, 800.0, 0.0},
{800.0, 800.0, 0.0},
{1200.0, 800.0, 0.0},
{1600.0, 800.0, 0.0},
{2000.0, 800.0, 0.0},
{-2000.0, 1200.0, 0.0},
{-1600.0, 1200.0, 0.0},
{-1200.0, 1200.0, 0.0},
{-800.0, 1200.0, 0.0},
{-400.0, 1200.0, 0.0},
{0.0, 1200.0, 0.0},
{400.0, 1200.0, 0.0},
{800.0, 1200.0, 0.0},
{1200.0, 1200.0, 0.0},
{1600.0, 1200.0, 0.0},
{2000.0, 1200.0, 0.0},
{-2000.0, 1600.0, 0.0},
{-1600.0, 1600.0, 0.0},
{-1200.0, 1600.0, 0.0},
{-800.0, 1600.0, 0.0},
{-400.0, 1600.0, 0.0},
{0.0, 1600.0, 0.0},
{400.0, 1600.0, 0.0},
{800.0, 1600.0, 0.0},
{1200.0, 1600.0, 0.0},
{1600.0, 1600.0, 0.0},
{2000.0, 1600.0, 0.0},
{-2000.0, 2000.0, 0.0},
{-1600.0, 2000.0, 0.0},
{-1200.0, 2000.0, 0.0},
{-800.0, 2000.0, 0.0},
{-400.0, 2000.0, 0.0},
{0.0, 2000.0, 0.0},
{400.0, 2000.0, 0.0},
{800.0, 2000.0, 0.0},
{1200.0, 2000.0, 0.0},
{1600.0, 2000.0, 0.0},
{2000.0, 2000.0, 0.0},
};
const Edge _grid_edges[220] = {
{11, 0},
{12, 11},
{1, 12},
{0, 1},
{13, 12},
{2, 13},
{1, 2},
{14, 13},
{3, 14},
{2, 3},
{15, 14},
{4, 15},
{3, 4},
{16, 15},
{5, 16},
{4, 5},
{17, 16},
{6, 17},
{5, 6},
{18, 17},
{7, 18},
{6, 7},
{19, 18},
{8, 19},
{7, 8},
{20, 19},
{9, 20},
{8, 9},
{21, 20},
{10, 21},
{9, 10},
{22, 11},
{23, 22},
{12, 23},
{24, 23},
{13, 24},
{25, 24},
{14, 25},
{26, 25},
{15, 26},
{27, 26},
{16, 27},
{28, 27},
{17, 28},
{29, 28},
{18, 29},
{30, 29},
{19, 30},
{31, 30},
{20, 31},
{32, 31},
{21, 32},
{33, 22},
{34, 33},
{23, 34},
{35, 34},
{24, 35},
{36, 35},
{25, 36},
{37, 36},
{26, 37},
{38, 37},
{27, 38},
{39, 38},
{28, 39},
{40, 39},
{29, 40},
{41, 40},
{30, 41},
{42, 41},
{31, 42},
{43, 42},
{32, 43},
{44, 33},
{45, 44},
{34, 45},
{46, 45},
{35, 46},
{47, 46},
{36, 47},
{48, 47},
{37, 48},
{49, 48},
{38, 49},
{50, 49},
{39, 50},
{51, 50},
{40, 51},
{52, 51},
{41, 52},
{53, 52},
{42, 53},
{54, 53},
{43, 54},
{55, 44},
{56, 55},
{45, 56},
{57, 56},
{46, 57},
{58, 57},
{47, 58},
{59, 58},
{48, 59},
{60, 59},
{49, 60},
{61, 60},
{50, 61},
{62, 61},
{51, 62},
{63, 62},
{52, 63},
{64, 63},
{53, 64},
{65, 64},
{54, 65},
{66, 55},
{67, 66},
{56, 67},
{68, 67},
{57, 68},
{69, 68},
{58, 69},
{70, 69},
{59, 70},
{71, 70},
{60, 71},
{72, 71},
{61, 72},
{73, 72},
{62, 73},
{74, 73},
{63, 74},
{75, 74},
{64, 75},
{76, 75},
{65, 76},
{77, 66},
{78, 77},
{67, 78},
{79, 78},
{68, 79},
{80, 79},
{69, 80},
{81, 80},
{70, 81},
{82, 81},
{71, 82},
{83, 82},
{72, 83},
{84, 83},
{73, 84},
{85, 84},
{74, 85},
{86, 85},
{75, 86},
{87, 86},
{76, 87},
{88, 77},
{89, 88},
{78, 89},
{90, 89},
{79, 90},
{91, 90},
{80, 91},
{92, 91},
{81, 92},
{93, 92},
{82, 93},
{94, 93},
{83, 94},
{95, 94},
{84, 95},
{96, 95},
{85, 96},
{97, 96},
{86, 97},
{98, 97},
{87, 98},
{99, 88},
{100, 99},
{89, 100},
{101, 100},
{90, 101},
{102, 101},
{91, 102},
{103, 102},
{92, 103},
{104, 103},
{93, 104},
{105, 104},
{94, 105},
{106, 105},
{95, 106},
{107, 106},
{96, 107},
{108, 107},
{97, 108},
{109, 108},
{98, 109},
{110, 99},
{111, 110},
{100, 111},
{112, 111},
{101, 112},
{113, 112},
{102, 113},
{114, 113},
{103, 114},
{115, 114},
{104, 115},
{116, 115},
{105, 116},
{117, 116},
{106, 117},
{118, 117},
{107, 118},
{119, 118},
{108, 119},
{120, 119},
{109, 120},
};
const Model _grid_model = {_grid_verts, 121, _grid_edges, 220};
const Vec_3 _car_verts[164] = {
{-200.0, -100.0, 30.0},
{250.0, -100.0, 30.0},
{-200.0, 0.0, 30.0},
{250.0, 0.0, 30.0},
{-200.0, -100.0, 100.0},
{250.0, -100.0, 90.0},
{0.0, -100.0, 170.0},
{0.0, -100.0, 100.0},
{0.0, -90.0, 100.0},
{-190.0, 0.0, 100.0},
{-190.0, -90.0, 100.0},
{0.0, -90.0, 40.0},
{-190.0, -90.0, 40.0},
{100.0, -100.0, 170.0},
{120.0, -100.0, 100.0},
{-117.292, -100.0, 30.0},
{-42.7084, -100.0, 30.0},
{-59.8201, -100.0, 74.5344},
{-100.18, -100.0, 74.5344},
{-42.7084, -80.0, 30.0},
{-117.292, -80.0, 30.0},
{-59.8201, -100.0, 5.46561},
{-100.18, -100.0, 5.4656},
{-100.18, -80.0, 5.4656},
{-59.8201, -80.0, 5.46561},
{-80.0, -80.0, 2.86102e-006},
{-80.0, -100.0, 4.76837e-006},
{-114.534, -80.0, 19.8201},
{-114.534, -100.0, 19.8201},
{-45.4656, -100.0, 19.8201},
{-45.4656, -80.0, 19.8201},
{-80.0, -100.0, 80.0},
{-114.534, -100.0, 60.1799},
{-45.4656, -100.0, 60.1799},
{-120.0, -100.0, 40.0},
{-40.0, -100.0, 40.0},
{-30.0, -90.0, 40.0},
{-30.0, -70.0, 40.0},
{-130.0, -90.0, 40.0},
{-130.0, -70.0, 40.0},
{-36.832, -90.0, 65.2249},
{-123.168, -90.0, 65.2249},
{-36.832, -70.0, 65.2249},
{-123.168, -70.0, 65.2249},
{-54.7751, -90.0, 83.168},
{-54.7751, -70.0, 83.168},
{-105.225, -90.0, 83.168},
{-105.225, -70.0, 83.168},
{-80.0, -90.0, 90.0},
{-80.0, -70.0, 90.0},
{197.292, -100.0, 30.0},
{122.708, -100.0, 30.0},
{180.18, -100.0, 74.5344},
{139.82, -100.0, 74.5344},
{160.0, -100.0, 80.0},
{125.466, -100.0, 60.1799},
{194.534, -100.0, 60.1799},
{120.0, -100.0, 40.0},
{200.0, -100.0, 40.0},
{197.292, -80.0, 30.0},
{122.708, -80.0, 30.0},
{194.534, -100.0, 19.8201},
{125.466, -100.0, 19.8201},
{125.466, -80.0, 19.8201},
{194.534, -80.0, 19.8201},
{160.0, -80.0, 6.67572e-006},
{160.0, -100.0, 4.29153e-006},
{139.82, -80.0, 5.4656},
{139.82, -100.0, 5.4656},
{180.18, -80.0, 5.46562},
{180.18, -100.0, 5.46561},
{0.0, 0.0, 160.0},
{0.0, -90.0, 160.0},
{0.0, -90.0, 110.0},
{10.0, -100.0, 30.0},
{110.0, -100.0, 30.0},
{90.0, -100.0, 160.0},
{10.0, -100.0, 160.0},
{110.0, -100.0, 100.0},
{10.0, -100.0, 100.0},
{-200.0, 100.0, 30.0},
{250.0, 100.0, 30.0},
{-200.0, 100.0, 100.0},
{250.0, 100.0, 90.0},
{-200.0, 0.0, 100.0},
{250.0, 0.0, 90.0},
{0.0, 0.0, 170.0},
{0.0, 100.0, 170.0},
{0.0, 0.0, 40.0},
{0.0, 100.0, 100.0},
{0.0, 90.0, 100.0},
{-190.0, 90.0, 100.0},
{-190.0, 0.0, 40.0},
{0.0, 90.0, 40.0},
{-190.0, 90.0, 40.0},
{100.0, 0.0, 170.0},
{100.0, 100.0, 170.0},
{120.0, 0.0, 100.0},
{120.0, 100.0, 100.0},
{-117.292, 100.0, 30.0},
{-42.7084, 100.0, 30.0},
{-59.8201, 100.0, 74.5344},
{-100.18, 100.0, 74.5344},
{-42.7084, 80.0, 30.0},
{-117.292, 80.0, 30.0},
{-59.8201, 100.0, 5.46561},
{-100.18, 100.0, 5.4656},
{-100.18, 80.0, 5.4656},
{-59.8201, 80.0, 5.46561},
{-80.0, 80.0, 2.86102e-006},
{-80.0, 100.0, 4.76837e-006},
{-114.534, 80.0, 19.8201},
{-114.534, 100.0, 19.8201},
{-45.4656, 100.0, 19.8201},
{-45.4656, 80.0, 19.8201},
{-80.0, 100.0, 80.0},
{-114.534, 100.0, 60.1799},
{-45.4656, 100.0, 60.1799},
{-120.0, 100.0, 40.0},
{-40.0, 100.0, 40.0},
{-30.0, 90.0, 40.0},
{-30.0, 70.0, 40.0},
{-130.0, 90.0, 40.0},
{-130.0, 70.0, 40.0},
{-36.832, 90.0, 65.2249},
{-123.168, 90.0, 65.2249},
{-36.832, 70.0, 65.2249},
{-123.168, 70.0, 65.2249},
{-54.7751, 90.0, 83.168},
{-54.7751, 70.0, 83.168},
{-105.225, 90.0, 83.168},
{-105.225, 70.0, 83.168},
{-80.0, 90.0, 90.0},
{-80.0, 70.0, 90.0},
{197.292, 100.0, 30.0},
{122.708, 100.0, 30.0},
{180.18, 100.0, 74.5344},
{139.82, 100.0, 74.5344},
{160.0, 100.0, 80.0},
{125.466, 100.0, 60.1799},
{194.534, 100.0, 60.1799},
{120.0, 100.0, 40.0},
{200.0, 100.0, 40.0},
{197.292, 80.0, 30.0},
{122.708, 80.0, 30.0},
{194.534, 100.0, 19.8201},
{125.466, 100.0, 19.8201},
{125.466, 80.0, 19.8201},
{194.534, 80.0, 19.8201},
{160.0, 80.0, 6.67572e-006},
{160.0, 100.0, 4.29153e-006},
{139.82, 80.0, 5.4656},
{139.82, 100.0, 5.4656},
{180.18, 80.0, 5.46562},
{180.18, 100.0, 5.46561},
{0.0, 0.0, 110.0},
{0.0, 90.0, 160.0},
{0.0, 90.0, 110.0},
{10.0, 100.0, 30.0},
{110.0, 100.0, 30.0},
{90.0, 100.0, 160.0},
{10.0, 100.0, 160.0},
{110.0, 100.0, 100.0},
{10.0, 100.0, 100.0},
};
const Edge _car_edges[227] = {
{0, 2},
{3, 1},
{1, 50},
{4, 7},
{5, 85},
{84, 4},
{1, 5},
{4, 0},
{6, 13},
{86, 6},
{7, 6},
{88, 11},
{8, 7},
{8, 10},
{10, 9},
{11, 8},
{11, 36},
{12, 92},
{10, 12},
{13, 14},
{95, 13},
{14, 5},
{97, 14},
{15, 0},
{15, 34},
{17, 33},
{18, 31},
{15, 20},
{19, 16},
{20, 19},
{21, 26},
{22, 23},
{23, 25},
{24, 21},
{16, 29},
{15, 28},
{20, 27},
{19, 30},
{25, 24},
{26, 22},
{25, 26},
{27, 23},
{28, 22},
{27, 28},
{29, 21},
{30, 24},
{29, 30},
{31, 17},
{32, 18},
{33, 35},
{34, 32},
{35, 16},
{36, 40},
{36, 37},
{37, 39},
{38, 12},
{38, 39},
{40, 44},
{41, 38},
{37, 42},
{41, 43},
{42, 40},
{43, 39},
{42, 45},
{44, 48},
{45, 49},
{46, 41},
{47, 43},
{48, 46},
{49, 47},
{48, 49},
{44, 45},
{46, 47},
{51, 75},
{51, 57},
{52, 56},
{53, 54},
{54, 52},
{55, 53},
{56, 58},
{57, 55},
{58, 50},
{51, 60},
{59, 50},
{60, 59},
{61, 70},
{62, 63},
{63, 67},
{64, 61},
{50, 61},
{51, 62},
{60, 63},
{59, 64},
{65, 69},
{66, 68},
{67, 65},
{68, 62},
{69, 64},
{70, 66},
{67, 68},
{65, 66},
{69, 70},
{155, 73},
{71, 72},
{73, 72},
{74, 16},
{75, 74},
{74, 79},
{76, 78},
{77, 76},
{78, 75},
{79, 77},
{79, 78},
{2, 80},
{81, 3},
{134, 81},
{89, 82},
{85, 83},
{82, 84},
{83, 81},
{80, 82},
{71, 86},
{96, 87},
{87, 86},
{87, 89},
{93, 88},
{89, 90},
{91, 90},
{9, 91},
{90, 93},
{120, 93},
{92, 94},
{94, 91},
{98, 96},
{96, 95},
{83, 98},
{98, 97},
{80, 99},
{118, 99},
{117, 101},
{115, 102},
{104, 99},
{100, 103},
{103, 104},
{110, 105},
{107, 106},
{109, 107},
{105, 108},
{113, 100},
{112, 99},
{111, 104},
{114, 103},
{108, 109},
{106, 110},
{110, 109},
{107, 111},
{106, 112},
{112, 111},
{105, 113},
{108, 114},
{114, 113},
{101, 115},
{102, 116},
{119, 117},
{116, 118},
{100, 119},
{124, 120},
{121, 120},
{123, 121},
{94, 122},
{123, 122},
{128, 124},
{122, 125},
{126, 121},
{127, 125},
{124, 126},
{123, 127},
{129, 126},
{132, 128},
{133, 129},
{125, 130},
{127, 131},
{130, 132},
{131, 133},
{133, 132},
{129, 128},
{131, 130},
{159, 135},
{141, 135},
{140, 136},
{138, 137},
{136, 138},
{137, 139},
{142, 140},
{139, 141},
{134, 142},
{144, 135},
{134, 143},
{143, 144},
{154, 145},
{147, 146},
{151, 147},
{145, 148},
{145, 134},
{146, 135},
{147, 144},
{148, 143},
{153, 149},
{152, 150},
{149, 151},
{146, 152},
{148, 153},
{150, 154},
{152, 151},
{150, 149},
{154, 153},
{157, 155},
{156, 71},
{156, 157},
{100, 158},
{158, 159},
{163, 158},
{162, 160},
{160, 161},
{159, 162},
{161, 163},
{162, 163},
};
const Model _car_model = {_car_verts, 164, _car_edges, 227};
Компилятору надо скармливать только gdi_3d.cpp, он #include-ит models.cpp (который сгенерирован в результате экспорта из gmax ; могу написать как, если хотите). Так проще всего, чтобы не заморачиваться на чтение моделей из файла.
3D-отсечение (clipping) не делается, чтобы код был проще. То есть если ребро частично позади камеры, то оно отбрасывается целиком. 2D-отсечение делает GDI (рёбра не вылезают за пределы клиентской области окна).
Обработка ошибок отсутствует, чтобы код был проще. По-хорошему, надо проверять, успешно ли отработала WinAPI-шная функция (CreateCompatibleBitmap, например).
Я не заморачивался на exception safety, чтобы код был проще. Также, нельзя выпускать C++-исключение за пределы оконной процедуры (и вообще любого WinAPI-шного callback-а).
Код использует A-функции (например, CreateWindowEx A ), которые работают с текстом в ANSI-кодировке (если Windows настроена на русский язык, то ANSI-кодировка -- это code page 1251). Но сейчас лучше использовать W-функции (например, CreateWindowEx W ), которые работают с текстом в кодировке UTF-16. Именно эта кодировка является родной для современных Windows. Недостаток использования W-функций: программа не будет работать в Windows 9x (95/98/ME). (Этот недостаток можно устранить с помощью MSLU (Microsoft Layer for Unicode), но это уже другая история.)
Короче, это учебный пример, а не образец, как надо писать программы. Что непонятно -- спрашивайте, постараюсь ответить. Результат:
|