Глава 17. Modeler. Часть 2

Теперь вернемся к modeler-у. Пришло время собрать новые классы вместе.

Windows

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


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

Location – C:\CPP\engine\modeler

Код:

#pragma once
#include <string>
#include <vector>
#include "Vertex01.h"
#include "Triangle01.h"
#include "VirtualShape.h"
#include "Group01.h"
#include "Material.h"
#include "GameSubj.h"

class ModelBuilder
{
public:
	std::vector<Vertex01*> vertices;
	std::vector<Triangle01*> triangles;
	std::vector<VirtualShape*> vShapesStack;
	std::vector<Group01*> groupsStack;
	std::vector<Material*> materialsList;
	std::vector<int> subjNumbersList;
	int usingSubjN = -1;
	int usingMaterialN = -1;
	Group01* pCurrentGroup = NULL;
public:
	virtual ~ModelBuilder();
	int useSubjN(int subjN) { return useSubjN(this, subjN); };
	static int useSubjN(ModelBuilder* pMB, int subjN);
	int useMaterial(Material* pMT) { return useMaterial(this, pMT); };
	static int useMaterial(ModelBuilder* pMB, Material* pMT);
	int buildBoxFace(std::string applyTo, VirtualShape* pVS) { return buildBoxFace(this, applyTo, pVS); };
	static int buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);
	static void startGroup(ModelBuilder* pMB);
	static void endGroup(ModelBuilder* pMB);
	static int addVertex(ModelBuilder* pMB, float kx, float ky, float kz, float nx, float ny, float nz);
	static int add2triangles(ModelBuilder* pMB, int nNW, int nNE, int nSW, int nSE, int n);
	static int addTriangle(ModelBuilder* pMB, int n0, int n1, int n2);
	int buildDrawJobs(std::vector<GameSubj*> gameSubjs) { return buildDrawJobs(this, gameSubjs); };
	static int buildDrawJobs(ModelBuilder* pMB, std::vector<GameSubj*> gameSubjs);
	static int rearrangeArraysForDrawJob(ModelBuilder* pMB, std::vector<Vertex01*> allVertices, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles);
	static int buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles);
};

В этом примере мы создадим красный кубик с 1 синей стороной (правой). Для этого мы создадим 1 GameSubject, 2 материала, “box” virtual shape, потом на нее добавим 6 “проекций/faces”. Затем будет нужно перевести созданные массивы вертексов и треугольников индексов в DrawJobs. Вышеупомянутый набор функций определен именно этими задачами.


Теперь – имплементация:

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

Location – C:\CPP\engine\modeler

Код:

#include "ModelBuilder.h"
#include "platform.h"
#include "utils.h"
#include "DrawJob.h"
#include "Shader.h"

extern float degrees2radians;

ModelBuilder::~ModelBuilder() {
	//clear all vectors
	int itemsN = vertices.size();
	for (int i = 0; i < itemsN; i++)
		delete vertices.at(i);
	vertices.clear();

	itemsN = triangles.size();
	for (int i = 0; i < itemsN; i++)
		delete triangles.at(i);
	triangles.clear();

	itemsN = vShapesStack.size();
	for (int i = 0; i < itemsN; i++)
		delete vShapesStack.at(i);
	vShapesStack.clear();

	itemsN = groupsStack.size();
	for (int i = 0; i < itemsN; i++)
		delete groupsStack.at(i);
	groupsStack.clear();

	itemsN = materialsList.size();
	for (int i = 0; i < itemsN; i++)
		delete materialsList.at(i);
	materialsList.clear();

	subjNumbersList.clear();
}
int ModelBuilder::useSubjN(ModelBuilder* pMB, int subjN) {
	pMB->usingSubjN = subjN;
	int itemsN = pMB->subjNumbersList.size();
	bool newN = true;
	if (itemsN > 0)
		for (int i = 0; i < itemsN; i++)
			if (pMB->subjNumbersList.at(i) == subjN) {
				newN = false;
				break;
			}
	if (newN)
		pMB->subjNumbersList.push_back(subjN);
	return subjN;
}
int ModelBuilder::useMaterial(ModelBuilder* pMB, Material* pMT) {
	int itemsN = pMB->materialsList.size();
	if (itemsN > 0)
		for (int i = 0; i < itemsN; i++)
			if (memcmp(pMB->materialsList.at(i), pMT, sizeof(Material)) == 0) {
				pMB->usingMaterialN = i;
				return i;
			}
	//if here - add new material to the list
	pMB->usingMaterialN = itemsN;
	//create a copy of new Material and add to the list
	Material* pMTnew = new Material(*pMT);
	pMB->materialsList.push_back(pMTnew);
	return itemsN;
}
int ModelBuilder::buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
	//this code is for simple box
	VirtualShape vs; //face VS, 
	mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
	//rotate desirable side to face us. 
	if (applyTo.find("front") == 0) {
		//Side <front> is facing us as is.
		vs.whl[0] = pVS->whl[0];
		vs.whl[1] = pVS->whl[1];
		//define how to move/place generated face back to the VirtualShape
		//just shift closer to us by length/2
		mat4x4_translate(transformMatrix, 0, 0, pVS->whl[2] / 2);
	}
	else if (applyTo.find("back") == 0) {
		vs.whl[0] = pVS->whl[0];
		vs.whl[1] = pVS->whl[1];
		//rotate 180 degrees around Y and shift farther from us by half-length
		mat4x4_translate(transformMatrix, 0, 0, -pVS->whl[2] / 2);
		mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 180);
	}
	else if (applyTo.find("left") == 0) {
		vs.whl[0] = pVS->whl[2]; //width = original length
		vs.whl[1] = pVS->whl[1];
		//rotate -90 degrees around Y (CW) and shift half-width to the left
		mat4x4_translate(transformMatrix, -pVS->whl[0] / 2, 0, 0);
		mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 90);
	}
	else if (applyTo.find("right") == 0) {
		vs.whl[0] = pVS->whl[2]; //width = original length
		vs.whl[1] = pVS->whl[1];
		//rotate +90 degrees around Y (CCW) and shift half-width to the right
		mat4x4_translate(transformMatrix, pVS->whl[0] / 2, 0, 0);
		mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 90);
	}
	else if (applyTo.find("top") == 0) {
		vs.whl[0] = pVS->whl[0];
		vs.whl[1] = pVS->whl[2]; //height = original length
		//rotate -90 degrees around X (CW) and 180 around Y, and shift half-height up
		mat4x4_translate(transformMatrix, 0, pVS->whl[1] / 2, 0);
		mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 180);
		mat4x4_rotate_X(transformMatrix, transformMatrix, -degrees2radians * 90);
	}
	else if (applyTo.find("bottom") == 0) {
		vs.whl[0] = pVS->whl[0];
		vs.whl[1] = pVS->whl[2]; //height = original length
		//rotate 90 around X (CCW) and shift half-height down
		mat4x4_translate(transformMatrix, 0, -pVS->whl[1] / 2, 0);
		mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * 90);
	}
	startGroup(pMB);
	//create vertices
	int sectionsX = 1;
	int sectionsY = 1;
	int pointsX = sectionsX + 1;
	int pointsY = sectionsY + 1;
	float stepX = vs.whl[0] / sectionsX;
	float stepY = vs.whl[1] / sectionsY;
	float kY = vs.whl[1] / 2;
	for (int iy = 0; iy < pointsY; iy++) {
		float kX = -vs.whl[0] / 2;
		for (int ix = 0; ix < pointsX; ix++) {
			int nSE = addVertex(pMB, kX, kY, 0, 0, 0, 1); //vertex number on south-east
			if (iy > 0 && ix > 0) {
				//add 2 triangles
				int nSW = nSE - 1; //vertex number south-west
				int nNE = nSE - pointsX; //north-east
				int nNW = nSW - pointsX; //north-west
				add2triangles(pMB, nNW, nNE, nSW, nSE, iy + ix);
			}
			kX += stepX;
		}
		kY -= stepY;
	}
	//move face to it's place (apply transform matrix)
	int vertsN = pMB->vertices.size();
	for (int i = pMB->pCurrentGroup->fromVertexN; i < vertsN; i++) {
		Vertex01* pVX = pMB->vertices.at(i);
		mat4x4_mul_vec4plus(pVX->aPos, transformMatrix, pVX->aPos, 1);
		mat4x4_mul_vec4plus(pVX->aNormal, transformMatrix, pVX->aNormal, 0);
	}
	endGroup(pMB);
	return 1;
}
int ModelBuilder::add2triangles(ModelBuilder* pMB, int nNW, int nNE, int nSW, int nSE, int n) {
	//indexes: NorthWest, NorthEast, SouthWest,SouthEast
	if (n % 2 == 0) { //even number
		addTriangle(pMB, nNW, nSW, nNE);
		addTriangle(pMB, nNE, nSW, nSE);
	}
	else { //odd number
		addTriangle(pMB, nNW, nSE, nNE);
		addTriangle(pMB, nNW, nSW, nSE);
	}
	return pMB->triangles.size() - 1;
}
int ModelBuilder::addTriangle(ModelBuilder* pMB, int i0, int i1, int i2) {
	Triangle01* pTR = new Triangle01();
	pMB->triangles.push_back(pTR);
	pTR->idx[0] = i0;
	pTR->idx[1] = i1;
	pTR->idx[2] = i2;
	pTR->subjN = pMB->usingSubjN;
	pTR->materialN = pMB->usingMaterialN;
	return pMB->triangles.size() - 1;
}
void ModelBuilder::startGroup(ModelBuilder* pMB) {
	pMB->pCurrentGroup = new Group01();
	pMB->groupsStack.push_back(pMB->pCurrentGroup);
	pMB->pCurrentGroup->fromVertexN = pMB->vertices.size();
	pMB->pCurrentGroup->fromTriangleN = pMB->triangles.size();
}
void ModelBuilder::endGroup(ModelBuilder* pMB) {
	delete pMB->pCurrentGroup;
	pMB->pCurrentGroup = pMB->groupsStack.back();
	pMB->groupsStack.pop_back();
}
int ModelBuilder::addVertex(ModelBuilder* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {
	Vertex01* pVX = new Vertex01();
	pMB->vertices.push_back(pVX);
	pVX->aPos[0] = kx;
	pVX->aPos[1] = ky;
	pVX->aPos[2] = kz;
	//normal
	pVX->aNormal[0] = nx;
	pVX->aNormal[1] = ny;
	pVX->aNormal[2] = nz;
	pVX->subjN = pMB->usingSubjN;
	pVX->materialN = pMB->usingMaterialN;
	return pMB->vertices.size() - 1;
}
int ModelBuilder::buildDrawJobs(ModelBuilder* pMB, std::vector<GameSubj*> gameSubjs) {
	int totalSubjsN = pMB->subjNumbersList.size();
	if (totalSubjsN < 1) {
		pMB->subjNumbersList.push_back(-1);
		totalSubjsN = 1;
	}
	int totalMaterialsN = pMB->materialsList.size();
	if (totalSubjsN < 2 && totalMaterialsN < 2) {
		//simple single DrawJob
		Material* pMT = pMB->materialsList.at(0);
		GameSubj* pGS = NULL;
		int gsN = pMB->subjNumbersList.at(0);
		if (gsN >= 0)
			pGS = gameSubjs.at(gsN);
		if (pGS != NULL)
			pGS->djStartN = DrawJob::drawJobs.size();
		buildSingleDrawJob(pMT, pMB->vertices, pMB->triangles);
		if (pGS != NULL)
			pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
		return 1;
	}
	int totalVertsN = pMB->vertices.size();
	int totalTrianglesN = pMB->triangles.size();
	//clear flags
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pVX = pMB->vertices.at(vN);
		pVX->flag = 0;
	}
	for (int tN = 0; tN < totalTrianglesN; tN++) {
		Triangle01* pTR = pMB->triangles.at(tN);
		pTR->flag = 0;
	}
	int addedDJs = 0;
	for (int sN = 0; sN < totalSubjsN; sN++) {
		GameSubj* pGS = NULL;
		int gsN = pMB->subjNumbersList.at(sN);
		if (gsN >= 0)
			pGS = gameSubjs.at(gsN);
		if (pGS != NULL)
			pGS->djStartN = DrawJob::drawJobs.size();
		for (int mtN = 0; mtN < totalMaterialsN; mtN++) {
			Material* pMT = pMB->materialsList.at(mtN);
			std::vector<Vertex01*> useVertices;
			std::vector<Triangle01*> useTriangles;
			for (int vN = 0; vN < totalVertsN; vN++) {
				Vertex01* pVX = pMB->vertices.at(vN);
				if (pVX->flag != 0)
					continue;
				if (pVX->subjN != gsN)
					continue;
				if (pVX->materialN != mtN)
					continue;
				//if here - make a copy
				Vertex01* pVX2 = new Vertex01(*pVX);
				useVertices.push_back(pVX2);
				pVX2->altN = vN;
				pVX->flag = 1;
				if (pVX->endOfSequence > 0) {
					rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
					buildSingleDrawJob(pMT, useVertices, useTriangles);
					addedDJs++;
					//clear and proceed to next sequence
					int useVerticesN = useVertices.size();
					for (int i = 0; i < useVerticesN; i++)
						delete useVertices.at(i);
					useVertices.clear();
				}
			}
			int useVerticesN = useVertices.size();
			if (useVerticesN < 1)
				continue; //to next material
			//pick triangles
			for (int tN = 0; tN < totalTrianglesN; tN++) {
				Triangle01* pTR = pMB->triangles.at(tN);
				if (pTR->flag != 0)
					continue;
				if (pTR->subjN != gsN)
					continue;
				if (pTR->materialN != mtN)
					continue;
				//if here - make a copy
				Triangle01* pTR2 = new Triangle01(*pTR);
				useTriangles.push_back(pTR2);
				pTR->flag = 1;
			}
			rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
			buildSingleDrawJob(pMT, useVertices, useTriangles);
			addedDJs++;
			//clear all for next material
			for (int i = 0; i < useVerticesN; i++)
				delete useVertices.at(i);
			useVertices.clear();
			int useTrianglesN = useTriangles.size();
			for (int i = 0; i < useTrianglesN; i++)
				delete useTriangles.at(i);
			useTriangles.clear();
		}
		if (pGS != NULL)
			pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
	}
	return addedDJs;
}
int ModelBuilder::buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalVertsN = useVertices.size();
	if (totalVertsN < 1)
		return 1;
	DrawJob* pDJ = new DrawJob();
	//copy material to DJ
	memcpy(&pDJ->mt, pMT, sizeof(Material));
	//calculate VBO element size (stride) and variables offsets in VBO
	int VBOid = DrawJob::newBufferId();
	int stride = 0;
	pDJ->setDesirableOffsets(&stride, pDJ->mt.shaderN, VBOid);
	//create an array for VBO
	int bufferSize = totalVertsN * stride;
	float* vertsBuffer = new float[bufferSize];
	//fill vertsBuffer
	Shader* pSh = Shader::shaders.at(pDJ->mt.shaderN);
	int floatSize = sizeof(float);
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pVX = useVertices.at(vN);
		int idx = vN * stride / floatSize;
		//pick data from vertex and move to the buffer
		memcpy(&vertsBuffer[idx + pDJ->aPos.offset / floatSize], pVX->aPos, 3 * floatSize);
		if (pSh->l_aTuv >= 0) //attribute TUV (texture coordinates)
			memcpy(&vertsBuffer[idx + pDJ->aTuv.offset / floatSize], pVX->aTuv, 2 * floatSize);
	}
	//buffer is ready, create VBO
	glBindBuffer(GL_ARRAY_BUFFER, VBOid);
	glBufferData(GL_ARRAY_BUFFER, bufferSize * floatSize, vertsBuffer, GL_STATIC_DRAW);
	delete[] vertsBuffer;
	pDJ->pointsN = totalVertsN;

	int totalTrianglesN = useTriangles.size();
	if (totalTrianglesN > 0) {
		//create EBO
		int totalIndexesN = totalTrianglesN * 3;
		//create buffer
		GLushort* indexBuffer = new GLushort[totalIndexesN];
		for (int tN = 0; tN < totalTrianglesN; tN++) {
			Triangle01* pTR = useTriangles[tN];
			int idx = tN * 3;
			indexBuffer[idx + 0] = (GLushort)pTR->idx[0];
			indexBuffer[idx + 1] = (GLushort)pTR->idx[1];
			indexBuffer[idx + 2] = (GLushort)pTR->idx[2];
		}
		//buffer is ready, create IBO
		pDJ->glEBOid = DrawJob::newBufferId();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexesN * sizeof(GLushort), indexBuffer, GL_STATIC_DRAW);
		delete[] indexBuffer;
		pDJ->pointsN = totalIndexesN;
	}
	//create and fill vertex attributes array (VAO)
	pDJ->buildVAO();
	return 1;
}
int ModelBuilder::rearrangeArraysForDrawJob(ModelBuilder* pMB, std::vector<Vertex01*> allVertices, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalTrianglesN = useTriangles.size();
	if (totalTrianglesN < 1)
		return 0;
	int totalVerticesN = useVertices.size();
	//save new vertices order in original vertices array
	//since triangles indices refer to original vertices order
	for (int i = 0; i < totalVerticesN; i++) {
		Vertex01* pVX1 = useVertices.at(i);
		Vertex01* pVX0 = allVertices.at(pVX1->altN);
		pVX0->altN = i;
	}
	//replace triangle original indices by new numbers saved in original vertices altN
	for (int tN = 0; tN < totalTrianglesN; tN++) {
		Triangle01* pTR = useTriangles.at(tN);
		for (int i = 0; i < 3; i++) {
			Vertex01* pVX0 = allVertices.at(pTR->idx[i]);
			pTR->idx[i] = pVX0->altN;
		}
	}
	return 1;
}


Теперь у нас есть все для создания нашей первой 3D модели. В этом примере, напомню, это будет красный кубик с 1 синей стороной.

В TheGame.cpp встроенные массивы вершин и индексов больше не нужны. Вместо этого мы создадим их программно.

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

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

extern std::string filesRoot;

std::vector<GameSubj*> TheGame::gameSubjs;

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

    //=== create box ========================
    GameSubj* pGS = new GameSubj();
    gameSubjs.push_back(pGS);

    pGS->name.assign("box1");
    pGS->ownCoords.setPosition(0, 0, 0);
    pGS->ownCoords.setDegrees(0, 0, 0);
    pGS->ownSpeed.setDegrees(1.5, -2, 1);

    ModelBuilder* pMB = new ModelBuilder();
    pMB->useSubjN(gameSubjs.size() - 1);

    //define VirtualShape
    VirtualShape vs;
    vs.type.assign("box");
    vs.whl[0] = 100;
    vs.whl[1] = 200;
    vs.whl[2] = 400;

    Material mt;
    //define material - flat red
    mt.shaderN = Shader::spN_flat_ucolor;
    mt.primitiveType = GL_TRIANGLES;
    mt.uColor.setRGBA(255, 0, 0,255); //red
    pMB->useMaterial(&mt);

    pMB->buildBoxFace("front", &vs);
    pMB->buildBoxFace("back", &vs);
    pMB->buildBoxFace("top", &vs);
    pMB->buildBoxFace("bottom", &vs);
    pMB->buildBoxFace("left", &vs);

    mt.uColor.setRGBA(0, 0, 255,255); pMB->useMaterial(&mt); //blue
    pMB->buildBoxFace("right", &vs);

    pMB->buildDrawJobs(gameSubjs);

    delete pMB;

    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, 400.f, -400.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() {
    int itemsN = gameSubjs.size();
    //delete all UISubjs
    for (int i = 0; i < itemsN; i++) {
        GameSubj* pGS = gameSubjs.at(i);
        delete pGS;
    }
    gameSubjs.clear();
    //clear all other classes
    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;
}

Посмотрим на этот код повнимательнее. Все действие происходит в функции TheGame::getReady() (с 20-ой строки).

Во-первых, мы создаем GameSubj и задаем начальную позицию и скорости вращения (строки с 19 по 26).

В строке 28 создаем инстанс ModelBuilder-а, который и будет строить массивы вертексов и треугольников, и потом – DrawJobs.

Следующая строка (29), pMB->useSubjN, инструктирует ModelBuilder для какого GameSubj это все (в будущем это позволит строить несколько под-моделей одновременно).

Строки с 32 по 36 задают VirtualShape, чтобы последующие команды знали что мы строим,

Строки с 40 по 43 определяют Material.

Команда pMB->useMaterial(&mt) (строка 43) инструктирует ModelBuilder какой номер материала использовать для вновь создаваемых вертексов и треугольников. Откройте Vertex01.h или Triangle01.h, увидите там ссылки на subjN и materialN.

В строке 45 мы начинаем заполнять массивы вертексов и треугольников, это команда buildBoxFace(). Давайте взглянем на нее повнимательнее.

Откроем ModelBuilder.cpp, строка 67. Идея – создать простой 2D прямоугольник лицом к нам, и затем развернуть и сдвинуть его на его окончательное место. Размеры этого прямоугольника (ширину и высоту) мы берем из заданной VirtualShape (параметр pVS) и сохраняем их в local VirtualShape vs. Это строки с 72 по 115. Также в этом коде мы строим transformMatrix которая потом установит созданный прямоугольник на его место.

Например, для стороны “front”, строки с 72 по 79. Эта сторона – лицом к нам и так, разворачивать ее не надо, достаточно просто сдвинуть по оси Z ближе к нам, в направлении +Z. “+Z” – потому что у OpenGL право-рукая система координат (right-handed coordinate system):

Строка 116, startGroup(). Мы уже готовы создать набор реальных вертексов и треугольников, потом нам надо будет их переместить. Концепция группы (“group”) позволит переместить только то что надо, не задевая ранее созданные вертексы. startGroup() запоминает стартовые позиции в массивах для будущих вертексов и треугольников.

Строки с 118 по 139 – где мы реально создаем вертексы и треугольники. Этот код позволяет строить не только простые 4-х-вершинные прямоугольники, но и “сеть”.

Затем, строки с 141 по 146 – перемещение вертексов на окончательные позиции.

Теперь вернемся к TheGame.cpp.

Обратите внимание, строка 51: мы изменяем текущий Material (меняем цвет на синий), и вызываем pMB->useMaterial(&mt). Эта команда проверяет, есть ли уже такой материал в materialsList, если нет – то создает новый и добавляет его в список. И устанавливает соответствующий номер материалв в переменную usingMaterialN. Таким образом, у всех последующих вертексов будет новый materialN.

В итоге, после 52-ой строки у нас создалось 24 вертекса и 12 треугольников (6 сторон, 36 индексов), все с subjN=0 и с двумя разными номерами материалов (первые 5 сторон – с материалом #0, и 6-ая сторона – с материалом #1).

Теперь главная функция, pMB->buildDrawJobs(), строка 54. Эта функция берет массивы вертексов и треугольников, извлекает из них под-массивы для каждого subj/material и строит соответствующие DrawJobs. Это строка 197 в ModelBuilder.cpp.

После извлечения под-массива для конкретной DrawJob она перенумеровывает вертексы и индексы и строит соответствующие VBO, EBO и VAO.

Строка 56 – не забываем удалять созданные объекты во избежание утечки памяти (memory leaks).


Надеюсь, теперь все более-или-менее понятно. Давайте попробуем что у нас получилось.

5. Компиляция и запуск. Результат:


Теперь

Android

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


7. Под xEngine добавляем New Filter, имя – modeler


8. Под modeler добавим Existing Item

из C:\CPP\engine\modeler

Выбираем там все 6 файлов,

Add.


9. Добавляем новый path в project Properties:

Right-click на p_android.NativeActivity project, открываем project Properties, All Configurations / Active (ARM64), C/C++ -> General -> Additional Include Directories -> Edit,

добавляем новую строку, идем в C:\CPP\engine\modeler,

Select Folder, Ok, Apply, Ok.


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

Re-build solution, запуск. Отлично.


Leave a Reply

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