Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the antispam-bee domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /home/ruwritingagame/public_html/wp-includes/functions.php on line 6121
Глава 14. 3D координаты и Game Subjects – Игра в Написание Игры

Глава 14. 3D координаты и Game Subjects

3D subject-ам понадобятся 3D координаты включая углы ориентации, или углы Эйлера – тангаж, рысканье, вращение (pitch, yaw, roll) в авиационной терминологии или курс, дифферент, крен (heading, attitude, bank) в морской. Мы начнем с углов Эйлера в градусах и в радианах и матрицы вращений (rotation Matrix). Назовем этот класс Coords.

Windows

1. Запускаем VS. Открываем C:\CPP\a998engine\p_windows\p_windows.sln.


2. Под xEngine добавим новый header файл Coords.h

Location: C:\CPP\engine

Код:

#pragma once
#include "linmath.h"

class Coords
{
private:
	float eulerDg[3] = { 0,0,0 }; //Euler angles (yaw, pitch, and roll) in degrees
	float eulerRd[3] = { 0,0,0 }; //Euler angles in radians
	mat4x4 rotationMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
	//mat4x4 rotationMatrix = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
public:
	float pos[4] = { 0,0,0,0 }; //x,y,z position + 4-th element for compatibility with 3D 4x4 matrices math
public:
	void setDegrees(float ax, float ay, float az) { setDegrees(this, ax, ay, az); };
	static void setDegrees(Coords* pC, float ax, float ay, float az);
	float getRd(int i) { return eulerRd[i]; }; //get angle in radians
	//float getDg(int i) { return eulerDg[i]; }; //get angle in degrees
	void setPosition(float kx, float ky, float kz) { setPosition(this, kx, ky, kz); };
	static void setPosition(Coords* pC, float kx, float ky, float kz);
	mat4x4* getRotationMatrix() { return &rotationMatrix; };
	void setRotationMatrix(mat4x4 m) { setRotationMatrix(this, m); };
	static void setRotationMatrix(Coords* pC, mat4x4 m);
	static void eulerRdToMatrix(mat4x4 rotationMatrix, float* eulerRd);
	static void matrixToEulerRd(float* eulerRd, mat4x4 m);
};


3. Под xEngine добавим новый C++ файл Coords.cpp

Location: C:\CPP\engine

Код:

#include "Coords.h"
#include "platform.h"
#include <string>

float PI = 3.141592f;
float degrees2radians = PI / 180.0f;
float radians2degrees = 180.0f / PI;

void Coords::setDegrees(Coords* pC, float ax, float ay, float az) {
	if (pC->eulerDg[0] == ax && pC->eulerDg[1] == ay && pC->eulerDg[2] == az)
		return;
	pC->eulerDg[0] = ax;
	pC->eulerDg[1] = ay;
	pC->eulerDg[2] = az;
	//convert to radians
	pC->eulerRd[0] = pC->eulerDg[0] * degrees2radians;
	pC->eulerRd[1] = pC->eulerDg[1] * degrees2radians;
	pC->eulerRd[2] = pC->eulerDg[2] * degrees2radians;
	//re-build rotation matrix
	eulerRdToMatrix(pC->rotationMatrix, pC->eulerRd);
}
void Coords::eulerRdToMatrix(mat4x4 rotationMatrix, float* eulerRd){
	//builds rotation matrix from Euler angles (in radians)
	mat4x4_identity(rotationMatrix);
	//rotation order: Z-X-Y
	float a = eulerRd[1];
	if (a != 0)
		mat4x4_rotate_Y(rotationMatrix, rotationMatrix, a);
	a = eulerRd[0];
	if (a != 0)
		mat4x4_rotate_X(rotationMatrix, rotationMatrix, a);
	a = eulerRd[2];
	if (a != 0)
		mat4x4_rotate_Z(rotationMatrix, rotationMatrix, a);
}
void Coords::setPosition(Coords* pC, float kx, float ky, float kz) {
	pC->pos[0] = kx;
	pC->pos[1] = ky;
	pC->pos[2] = kz;
}
void Coords::setRotationMatrix(Coords* pC, mat4x4 m) {
	memcpy(pC->rotationMatrix, m, sizeof(pC->rotationMatrix));
	//update Euler angles
	matrixToEulerRd(pC->eulerRd, pC->rotationMatrix);

	pC->eulerDg[0] = pC->eulerRd[0] * radians2degrees;
	pC->eulerDg[1] = pC->eulerRd[1] * radians2degrees;
	pC->eulerDg[2] = pC->eulerRd[2] * radians2degrees;
}
void Coords::matrixToEulerRd(float* eulerRd, mat4x4 m) {
	//calculates Euler angles (in radians) from matrix
	float yaw, pitch, roll;

	if (m[1][2] > 0.998 || m[1][2] < -0.998) { // singularity at south or north pole
		yaw = atan2f(-m[2][0], m[0][0]);
		roll = 0;
	}
	else {
		yaw = atan2f(-m[0][2], m[2][2]);
		roll = atan2f(-m[1][0], m[1][1]);
	}
	pitch = asinf(m[1][2]);

	eulerRd[0] = pitch;
	eulerRd[1] = yaw;
	eulerRd[2] = roll;
}


В GameSubj класс для начала включим 3Д координаты, 3Д скорость (в данный момент нас в основном интересуют угловые скорости), modelMatrix для рендринга, и ссылку на вовлеченные DrawJobs (это 2 переменные: djStartN и djTotalN)

4. Под xEngine добавим новый header файл GameSubj.h

Location: C:\CPP\engine

Код:

#pragma once
#include "Coords.h"
#include "Material.h"
#include <string>

class GameSubj
{
public:
	std::string name;
	Coords ownCoords;
	Coords ownSpeed;
	float scale[3] = { 1,1,1 };
	int djStartN = 0; //first DJ N in DJs array (DrawJob::drawJobs)
	int djTotalN = 0; //number of DJs
	mat4x4 ownModelMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };

	Material* pAltMaterial = NULL;

public:
	virtual ~GameSubj();
	void buildModelMatrix() { buildModelMatrix(this); };
	static void buildModelMatrix(GameSubj* pGS);
	virtual int moveSubj() { return moveSubj(this); };
	static int moveSubj(GameSubj* pGS);
};


5. Под xEngine добавим новый C++ файл GameSubj.cpp

Location: C:\CPP\engine

Код:

#include "GameSubj.h"
#include "platform.h"

GameSubj::~GameSubj() {
    if (pAltMaterial != NULL)
        delete pAltMaterial;
}
void GameSubj::buildModelMatrix(GameSubj* pGS) {
    mat4x4_translate(pGS->ownModelMatrix, pGS->ownCoords.pos[0], pGS->ownCoords.pos[1], pGS->ownCoords.pos[2]);
    //rotation order: Z-X-Y
    mat4x4_mul(pGS->ownModelMatrix, pGS->ownModelMatrix, *(pGS->ownCoords.getRotationMatrix()));

    if (pGS->scale[0] != 1 || pGS->scale[1] != 1 || pGS->scale[2] != 1)
        mat4x4_scale_aniso(pGS->ownModelMatrix, pGS->ownModelMatrix, pGS->scale[0], pGS->scale[1], pGS->scale[2]);
}
int GameSubj::moveSubj(GameSubj* pGS) {
    if (pGS->ownSpeed.getRd(0) != 0 || pGS->ownSpeed.getRd(1) != 0 || pGS->ownSpeed.getRd(2) != 0) {
        //apply angle speed
        mat4x4 newRotationMatrix;
        mat4x4_mul(newRotationMatrix, *(pGS->ownCoords.getRotationMatrix()), *(pGS->ownSpeed.getRotationMatrix()));
        pGS->ownCoords.setRotationMatrix(newRotationMatrix);
    }
    return 1;
}

У нас тут пока 2 функции :

  • buildModelMatrix() которая строит ownModelMatrix из 3Д координат для рендринга и
  • moveSubj() которая (на данный момент) просто изменяет 3Д матрицу субъекта в зависимости от заданных угловых скоростей и вызывает пересчет углов Эйлера из полученной матрицы. Это функции setRotationMatrix() и matrixToEulerRd().

Нужно добавить в TheGame.h массив (vector) 3D gameSubjs.

6. Заменим TheGame.h код на:

#pragma once
#include <vector>
#include "GameSubj.h"

class TheGame
{
public:
	int screenSize[2];
	float screenRatio;
	bool bExitGame;

	//static arrays (vectors) of active GameSubjs
	static std::vector<GameSubj*> gameSubjs;
public:
	int run();
	int getReady();
	int drawFrame();
	int cleanUp();
	int onScreenResize(int width, int height);
};


В качесиве простой 3Д модели вTheGame.cpp мы возьмем наш текстурированный прямоугольник. Команда glEnable(GL_CULL_FACE) проинструктирует OpenGL рендрить только ту сторону, которая обращена к нам лицом, и игнорировать обратную (изнаночную) сторону. OpenGL рассматривает поверхность как “обращенную к нам лицом” если вершины идут против часовой стрелки (counter-clockwise order).

В функции getReady() мы создадим 1GameSubj с двумя VBO и двумя соответствующими DrawJobs.

Функция drawFrame() будет сканировать массив gameSubjs (состоящий из 1 субъекта), для каждого (пока одного) субъекта, вызывать его функцию pGS->moveSubj() которая повернет его координаты на скорости вращения, потом построит MVP матрицу для рендринга и исполнит вовлеченные DrawJobs.

7. Откроем TheGame.cpp и заменим код на:

#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"

extern std::string filesRoot;

//static array (vector) for loaded gameSubjs
std::vector<GameSubj*> TheGame::gameSubjs;

static const struct
{
    float x, y, z, tu, tv;
} frontVertices[4] =
{
    { -0.5f,  0.5f, 0.f, 0.f, 0.f }, //top-left
    { -0.5f, -0.5f, 0.f, 0.f, 1.f }, //bottom-left
    {  0.5f,  0.5f, 0.f, 1.f, 0.f }, //top-right
    {  0.5f, -0.5f, 0.f, 1.f, 1.f }  //bottom-right
};

int TheGame::getReady() {
    bExitGame = false;
    Shader::loadShaders();
    glEnable(GL_CULL_FACE);

    GameSubj* pGS = new GameSubj();
    gameSubjs.push_back(pGS);
    pGS->djStartN = DrawJob::drawJobs.size();

    pGS->name.assign("img1");
    //pGS->ownCoords.setPosition(-50, 50, 0);
    pGS->ownSpeed.setDegrees(1, 2, 3);
    pGS->scale[0] = 400;
    pGS->scale[1] = pGS->scale[0] / 2;

    //face DrawJob
    //build VBO
    unsigned int VBOid = DrawJob::newBufferId();
    glBindBuffer(GL_ARRAY_BUFFER, VBOid);
    glBufferData(GL_ARRAY_BUFFER, sizeof(frontVertices), frontVertices, GL_STATIC_DRAW);
    int stride = sizeof(float) * 5;
    //add DrawJob
    DrawJob* pDJ = new DrawJob();
    pDJ->pointsN = 4; //number of vertices
    //define material
    pDJ->mt.shaderN = Shader::spN_flat_tex;
    pDJ->mt.primitiveType = GL_TRIANGLE_STRIP;
    pDJ->mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/sample_img.png");
    //attributes references
    AttribRef* pAR;
    pAR = &pDJ->aPos; pAR->offset = 0;                 pAR->glVBOid = VBOid; pAR->stride = stride;
    pAR = &pDJ->aTuv; pAR->offset = sizeof(float) * 3; pAR->glVBOid = VBOid; pAR->stride = stride;
    //create and fill vertex attributes array (VAO)
    pDJ->buildVAO();
    pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;

    return 1;
}
int TheGame::drawFrame() {
    myPollEvents();

    //glClearColor(0.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    mat4x4 mProjection, mMVP;
    mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 200.f, -200.f);

    //scan subjects
    int subjsN = gameSubjs.size();
    for (int subjN = 0; subjN < subjsN; subjN++) {
        GameSubj* pGS = gameSubjs.at(subjN);
        //behavior - apply rotation speed
        pGS->moveSubj();
        //prepare subject for rendering
        pGS->buildModelMatrix(pGS);
        //build MVP matrix for given subject
        mat4x4_mul(mMVP, mProjection, pGS->ownModelMatrix);
        //render subject
        for (int i = 0; i < pGS->djTotalN; i++) {
            DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
            pDJ->execute((float*)mMVP, NULL);
        }
    }
    mySwapBuffers();
    return 1;
}
int TheGame::cleanUp() {
    Texture::cleanUp();
    Shader::cleanUp();
    DrawJob::cleanUp();
    return 1;
}
int TheGame::onScreenResize(int width, int height) {
    if (screenSize[0] == width && screenSize[1] == height)
        return 0;
    screenSize[0] = width;
    screenSize[1] = height;
    screenRatio = (float)width / (float)height;
    glViewport(0, 0, width, height);
    mylog(" screen size %d x %d\n", width, height);
    return 1;
}
int TheGame::run() {
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    return 1;
}


8. Компиляция и запуск:

Картинка мягко вращается по всем трем осям, значит наши 3D функции работают правильно.


Кватернионы / Quaternions

Хотя они последнее время и “в тренде”, я так и не вижу в чем они могут заменить матрицы, даже в сложных 3D вычислениях по трем осям. Просто альтернативная форма представления 3D вращений. Или я что-то упустил? Поправьте меня если неправ.


Android

9. Пере-запускаем VS. Открываем C:\CPP\a998engine\p_android\p_android.sln.

Под xEngine добавим Existing Item,

из C:\CPP\engine,

Выбираем

  • Coords.cpp
  • Coords.h
  • GameSubj.cpp
  • GameSubj.h

Add



10. Включаем, разблокируем, подключаем, разрешаем.

Компиляция и запуск. Норм.


Leave a Reply

Your email address will not be published. Required fields are marked *