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
Глава 28. Загрузчик моделей / Model Loader – Игра в Написание Игры

Глава 28. Загрузчик моделей / Model Loader

Наша следующая задача – вынести (убрать) построение модели за пределы TheGame класса.

Нам нужно создать какой-то текстовый описатель (text descriptor) модели, XML формат кажется вполне подходящим. Мы будем держать описания моделей за пределами экзешника, в каталоге /dt. Еще нам понадобится класс, который будет их загружать, читать, обрабатывать и строить соответствующие 3D модели в памяти.

У нас уже есть FileLoader класс. На его основе мы напишем класс, который сможет парсировать XML txt файлы. Назовем его XMLParser. Это будет часть движка (engine).

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


2. Под xEngine добавим header file XMLParser.h

Location: C:\CPP\engine

Код:

#pragma once
#include "FileLoader.h"

class XMLparser : public FileLoader
{
public:
	char* readFrom;
	std::string currentTag = "";
	int tagLength = 0;
	std::string tagName = "";
	bool closedTag = false; // > or />
public:
	XMLparser(std::string filePath) : FileLoader(filePath) { removeComments(this); readFrom = pData; };
	static int removeComments(XMLparser* pXP);
	static int processSource(XMLparser* pXP);
	int nextTag() { return nextTag(this); }; //returns 0 if no more tags, 1 - tag extractedb
	static bool nextTag(XMLparser* pXP); //returns 0 if no more tags, 1 - tag extractedb
	static int nameEndsAt(std::string varName, std::string tag);
	virtual int processTag() { return 1; };
	static std::string buildFullPath(XMLparser* pXP, std::string filePath);

	static bool varExists(std::string varName, std::string tag);
	static std::string getStringValue(std::string varName, std::string tag);
	static int setCharsValue(char* pChars, int charsLength, std::string varName, std::string tag);
	static int setIntValue(int* pInt, std::string varName, std::string tag);
	static int setFloatValue(float* pFloat, std::string varName, std::string tag);
	static int setIntArray(int* pInts, int arrayLength, std::string varName, std::string tag);
	static int setFloatArray(float* pFloats, int arrayLength, std::string varName, std::string tag);
	static int setUintColorValue(unsigned int* pInt, std::string varName, std::string tag);
	static int setIntBoolValue(int* pInt, std::string varName, std::string tag);
};

Заметьте, он наследует класс FileLoader.


3. Под xEngine добавим новый C++ file XMLParser.cpp

Location: C:\CPP\engine

Код:

#include "XMLparser.h"
#include "platform.h"
#include "utils.h"
#include "MyColor.h"
#include <vector>

extern std::string filesRoot;

int XMLparser::removeComments(XMLparser* pXP) {
	//find all occurances of "/*"
	std::vector<char*> commentsStarts;
	// /* comments */
	char* scanFrom = pXP->pData;
	while (1) {
		char* commentStarts = strstr(scanFrom, "/*");
		if (commentStarts == NULL)
			break;
		commentsStarts.push_back(commentStarts);
		scanFrom = commentStarts + 2;
	}
	//here we have a list of /* comments */
	while(commentsStarts.size() > 0){
		//get last comment
		char* commentStarts = commentsStarts.back();
		commentsStarts.pop_back();
		char* commentEnds = strstr(commentStarts, "*/");
		int commentLength = (int)(commentEnds - commentStarts) + 2;
		//shift text left
		myStrcpy_s(commentStarts, pXP->dataSize - (int)(commentStarts - pXP->pData), commentStarts + commentLength);
		pXP->dataSize -= commentLength;
//mylog("======\n%s----------\n", pXP->pData);
	}
	// //line comments
	scanFrom = pXP->pData;
	while (1) {
		char* commentStarts = strstr(scanFrom, "//");
		if (commentStarts == NULL)
			break;
		char* commentEnds = strstr(commentStarts, "\n");
		int commentLength = (int)(commentEnds - commentStarts) + 1;
		//shift text left
		myStrcpy_s(commentStarts, pXP->dataSize - (int)(commentStarts - pXP->pData), commentStarts + commentLength);
		pXP->dataSize -= commentLength;
		scanFrom = commentStarts;
//mylog("======\n%s----------\n", pXP->pData);
	}
	// <!-- comments -->
	scanFrom = pXP->pData;
	while (1) {
		char* commentStarts = strstr(scanFrom, "<!--");
		if (commentStarts == NULL)
			break;
		commentsStarts.push_back(commentStarts);
		scanFrom = commentStarts + 4;
	}
	//here we have a list of <!-- comments -->
	while (commentsStarts.size() > 0) {
		//get last comment
		char* commentStarts = commentsStarts.back();
		commentsStarts.pop_back();
		char* commentEnds = strstr(commentStarts, "-->");
		int commentLength = (int)(commentEnds - commentStarts) + 3;
		//shift text left
		myStrcpy_s(commentStarts, pXP->dataSize - (int)(commentStarts - pXP->pData), commentStarts + commentLength);
		pXP->dataSize -= commentLength;
//mylog("======\n%s----------\n", pXP->pData);
	}
	return 1;
}
bool XMLparser::nextTag(XMLparser* pXP) {
	//returns 0 if no more tags, 1 - tag extracted
	char* tagStarts = strstr(pXP->readFrom, "<");
	if (tagStarts == NULL)
		return false;
	pXP->readFrom++;
	char* tagEnds = strstr(pXP->readFrom, ">");
	if (tagEnds == NULL)
		return false;
	pXP->readFrom = tagEnds + 1;
	pXP->tagLength = (int)(tagEnds - tagStarts) + 1;
	pXP->currentTag.assign(tagStarts, pXP->tagLength);
	return true;
} 
int XMLparser::nameEndsAt(std::string varName, std::string tag) {
	int scanFrom = 0;
	int nameLength = varName.length();
	std::string optsBefore = "< ";
	std::string optsAfter = " =/>\n";
	while (1) {
		int varStartsAt = tag.find(varName, scanFrom);
		if (varStartsAt == std::string::npos)
			return -1;
		scanFrom = varStartsAt + nameLength;
		char charBefore = tag.at(varStartsAt - 1);
		if (optsBefore.find(charBefore) == std::string::npos)
			continue;
		char charAfter = tag.at(scanFrom);
		if (optsAfter.find(charAfter) == std::string::npos)
			continue;
		return scanFrom;
	}
}
int XMLparser::processSource(XMLparser* pXP) {
	while (pXP->nextTag()) {
		//extract tagName
		int nameStart = pXP->currentTag.find_first_not_of(" <");
		int nameEnd = pXP->currentTag.find_first_of(" =/>\n", nameStart+1);
		pXP->tagName = pXP->currentTag.substr(nameStart, (nameEnd - nameStart));
		//open/closed tag
		char lastChar = pXP->currentTag.at(pXP->tagLength - 2);
		pXP->closedTag = (lastChar == '/');
//mylog("[%s] [%s] closed=%d nameStart=%d nameEnd=%d\n", pXP->currentTag.c_str(), pXP->tagName.c_str(), pXP->closedTag, nameStart, nameEnd);
		pXP->processTag();
	}
	return 1;
}
std::string XMLparser::buildFullPath(XMLparser* pXP, std::string filePath) {
	if (filePath.at(0) != '/')
		filePath = pXP->inAppFolder + filePath;
	return (filesRoot + filePath);
}

std::string XMLparser::getStringValue(std::string varName, std::string tag) {
	//returns std::string
	int valueStartsAt = nameEndsAt(varName, tag);
	if (valueStartsAt < 0)
		return ""; //var not found
	valueStartsAt = tag.find_first_not_of(" ", valueStartsAt);
	char c = tag.at(valueStartsAt);
	if (c != '=')
		return ""; //var exists, but value not set
	valueStartsAt = tag.find_first_not_of(" ", valueStartsAt + 1);
	c = tag.at(valueStartsAt);
	std::string optsQuote = "\"'";
	int valueEndsAt = 0;
	if (optsQuote.find(c) != std::string::npos) {
		//the value is in quotes
		valueStartsAt++;
		valueEndsAt = tag.find(c, valueStartsAt);
	}
	else { //value not quoted
		valueEndsAt = tag.find_first_of(" />\n", valueStartsAt);
	}
	return tag.substr(valueStartsAt, valueEndsAt - valueStartsAt);
}

bool XMLparser::varExists(std::string varName, std::string tag) {
	int valueStartsAt = nameEndsAt(varName, tag);
	if (valueStartsAt < 0)
		return false; //var not found
	return true;
}
int XMLparser::setCharsValue(char* pChars, int charsLength, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string val = getStringValue(varName, tag);
	myStrcpy_s(pChars, charsLength, (char*)val.c_str());
	return 1;
}
int XMLparser::setIntValue(int* pInt, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string val = getStringValue(varName, tag);
	*pInt = stoi(val);
	return 1;
}
int XMLparser::setFloatValue(float* pFloat, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string val = getStringValue(varName, tag);
	*pFloat = stof(val);
	return 1;
}
int XMLparser::setFloatArray(float* pFloats, int arrayLength, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string valuesString = getStringValue(varName, tag);
	std::vector<std::string> valuesVector = splitString(valuesString, ",");
	int valsN = valuesVector.size();
	if (valsN != arrayLength) {
		mylog("ERROR in XMLparser::getFloatArray, %s, %s\n", varName.c_str(), tag.c_str());
		return -1;
	}
	for (int i = 0; i < valsN; i++) {
		if (valuesVector.at(i).compare("x") != 0)
			pFloats[i] = stof(valuesVector.at(i));
	}
	return 1;
}int XMLparser::setIntArray(int* pInts, int arrayLength, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string valuesString = getStringValue(varName, tag);
	std::vector<std::string> valuesVector = splitString(valuesString, ",");
	int valsN = valuesVector.size();
	if (valsN != arrayLength) {
		mylog("ERROR in XMLparser::getIntArray, %s, %s\n", varName.c_str(), tag.c_str());
		return -1;
	}
	for (int i = 0; i < valsN; i++) {
		if(valuesVector.at(i).compare("x") != 0)
			pInts[i] = stoi(valuesVector.at(i));
	}
	return 1;
}
int XMLparser::setUintColorValue(unsigned int* pInt, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	MyColor clr;
	std::string val = getStringValue(varName, tag);
	if (val.at(0) == '#') {
		//the value is in HTML HEX format (like #ff0000)
		int r = std::stoi(val.substr(1, 2), nullptr, 16);
		int g = std::stoi(val.substr(3, 2), nullptr, 16);
		int b = std::stoi(val.substr(5, 2), nullptr, 16);
		int a = 255;
		if (val.size() > 7)
			a = std::stoi(val.substr(7, 2), nullptr, 16);
		clr.setRGBA(r, g, b, a);
	}
	else if (val.find(",") > 0) {
		//the value is an array of ints (?)
		std::vector<std::string> valuesVector = splitString(val, ",");
		int r = std::stoi(valuesVector[0]);
		int g = std::stoi(valuesVector[1]);
		int b = std::stoi(valuesVector[2]);
		int a = 255;
		if (valuesVector.size() > 3)
			a = std::stoi(valuesVector[3]);
		clr.setRGBA(r, g, b, a);
	}
	else {
		mylog("ERROR in XMLparser::setUintColorValue: unhandled Color format %s\n",tag.c_str());
		return -1;
	}
	*pInt = clr.getUint32();
	return 1;
}
int XMLparser::setIntBoolValue(int* pInt, std::string varName, std::string tag) {
	if (!varExists(varName, tag))
		return 0; //var not found
	std::string val = getStringValue(varName, tag);
	if (val.compare("") == 0) *pInt = 1;
	else if (val.compare("1") == 0) *pInt = 1;
	else if (val.compare("0") == 0) *pInt = 0;
	else if (val.compare("yes") == 0) *pInt = 1;
	else if (val.compare("no") == 0) *pInt = 0;
	else if (val.compare("true") == 0) *pInt = 1;
	else if (val.compare("false") == 0) *pInt = 0;
	else {
		mylog("ERROR in XMLparser::setIntBoolValue, %s=%s in %s\n",varName.c_str(),val.c_str(),tag.c_str());
		return -1;
	}
	return 1;
}

Класс XMLParser загрузит заданный xml/txt файл, уберет комменты, потом просканирует его таг-за-тагом и для каждого из них вызовет виртуальную функцию processTag() на исполнение. Эта функция виртуальная потому что сам XMLParser не знает что делать с этими тагами. Будет знать наследующий класс ModelLoader, у которого будет своя имплементация функции processTag().

Также в классе XMLParser есть набор функций для чтения тагов, их переменных и их значений.


Теперь на основе XMLParser, мы создадим класс ModelLoader, который будет читать заданный XML descriptor и исполнять его инструкции в ModelBuilder-е. Этот класс будет принадлежать modeler-у.

4. Под modeler добавим новый header file ModelLoader.h

Location: C:\CPP\engine\modeler

Код:

#pragma once
#include "XMLparser.h"
#include "ModelBuilder.h"

class ModelLoader : public XMLparser
{
public:
	ModelBuilder* pModelBuilder = NULL;
	bool ownModelBuilder = false;
	std::vector<GameSubj*>* pSubjsVector = NULL;
public:
	ModelLoader(std::vector<GameSubj*>* pSubjsVector0, int subjN, ModelBuilder* pMB, std::string filePath) : XMLparser(filePath) {
		pSubjsVector = pSubjsVector0;
		if (pMB != NULL) {
			ownModelBuilder = false;
			pModelBuilder = pMB;
		}
		else {
			ownModelBuilder = true;
			pModelBuilder = new ModelBuilder();
		}
		pModelBuilder->useSubjN(subjN);
	};
	virtual ~ModelLoader() {
		if (!ownModelBuilder)
			return;
		pModelBuilder->buildDrawJobs(*pSubjsVector);
		delete pModelBuilder;
	};
	static int fillProps_vs(VirtualShape* pVS, std::string tagStr); //virtual shape
	static int processTag_a(ModelLoader* pML); //apply
	static int setValueFromIntHashMap(int* pInt, std::map<std::string, int> intHashMap, std::string varName, std::string tagStr);
	static int setTexture(ModelLoader* pML, int* pInt, std::string txName);
	static int setMaterialTextures(ModelLoader* pML, Material* pMT);
	static int fillProps_mt(Material* pMT, std::string tagStr, ModelLoader* pML); //Material
	int processTag() { return processTag(this); };
	static int processTag(ModelLoader* pML);
	static int loadModel(std::vector<GameSubj*>* pSubjsVector0, std::string sourceFile, std::string subjClass);
};

Заметьте, он наследует XMLParser класс.


5. Под modeler добавим новый C++ file ModelLoader.cpp

Location: C:\CPP\engine\modeler

Код:

#include "ModelLoader.h"
#include "platform.h"
#include "TheGame.h"
#include "DrawJob.h"
#include "Texture.h"
#include "utils.h"

extern TheGame theGame;

int ModelLoader::loadModel(std::vector<GameSubj*>* pSubjsVector0, std::string sourceFile, std::string subjClass) {
	//returns element's (Subj) number or -1
	int subjN = pSubjsVector0->size();
	GameSubj* pGS = theGame.newGameSubj(subjClass);
	pSubjsVector0->push_back(pGS);
	//pGS->djStartN = DrawJob::drawJobs.size();
	ModelLoader* pML = new ModelLoader(pSubjsVector0, subjN, NULL, sourceFile);
	processSource(pML);
	delete pML;
	//pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
	return subjN;
}
int ModelLoader::processTag_a(ModelLoader* pML) {
	//apply
	ModelBuilder* pMB = pML->pModelBuilder;
	std::string tagStr = pML->currentTag;
	pMB->lockGroup(pMB);

	std::vector<std::string> applyTosVector = splitString(pML->getStringValue("a", tagStr), ",");
	Material* pMT = pMB->materialsList.at(pMB->usingMaterialN);
	int texN = pMT->uTex1mask;
	if (texN < 0)
		texN = pMT->uTex0;
	float xywh[4] = { 0,0,1,1 };
	setFloatArray(xywh, 4, "xywh", tagStr);
	std::string flipStr = getStringValue("flip", tagStr);
	TexCoords tc;
	tc.set(texN, xywh[0], xywh[1], xywh[2], xywh[3], flipStr);

	setFloatArray(xywh, 4, "xywh2nm", tagStr);
	flipStr = getStringValue("flip2nm", tagStr);
	TexCoords tc2nm;
	tc2nm.set(pMT->uTex2nm, xywh[0], xywh[1], xywh[2], xywh[3], flipStr);

	for (int aN = 0; aN < (int)applyTosVector.size(); aN++) {
		pMB->buildFace(pMB, applyTosVector.at(aN), pMB->pCurrentVShape, &tc, &tc2nm);
	}
	//mylog("vertsN=%d\n",pMB->vertices.size());

	pMB->releaseGroup(pMB);
	return 1;
}
int ModelLoader::setValueFromIntHashMap(int* pInt, std::map<std::string, int> intHashMap, std::string varName, std::string tagStr) {
	if (!varExists(varName, tagStr))
		return 0;
	std::string str0 = getStringValue(varName, tagStr);
	if (intHashMap.find(str0) == intHashMap.end()) {
		mylog("ERROR in ModelLoader::setValueFromIntMap, %s not found, %s\n", varName.c_str(), tagStr.c_str());
		return -1;
	}
	*pInt = intHashMap[getStringValue(varName, tagStr)];
	return 1;
}
int ModelLoader::setTexture(ModelLoader* pML, int* pInt, std::string txName) {
	ModelBuilder* pMB = pML->pModelBuilder;
	std::string varName = txName + "_use";
	if (setValueFromIntHashMap(pInt, pMB->texturesHashMap, varName, pML->currentTag) == 0) {
		//the texture is not in hash table
		varName = txName + "_src";
		if (varExists(varName, pML->currentTag)) {
			std::string txFile = getStringValue(varName, pML->currentTag);
			varName = txName + "_ckey";
			unsigned int intCkey = 0;
			setUintColorValue(&intCkey, varName, pML->currentTag);
			*pInt = Texture::loadTexture(buildFullPath(pML, txFile), intCkey);
		}
	}
	return 1;
}
int ModelLoader::setMaterialTextures(ModelLoader* pML, Material* pMT) {
	setTexture(pML, &pMT->uTex0, "uTex0");
	setTexture(pML, &pMT->uTex1mask, "uTex1mask");
	setTexture(pML, &pMT->uTex2nm, "uTex2nm");
	setTexture(pML, &pMT->uTex3, "uTex3");
	return 1;
}
int ModelLoader::fillProps_mt(Material* pMT, std::string tagStr, ModelLoader* pML) {
	setCharsValue(pMT->shaderType, 20, "mt_type", tagStr);
	setMaterialTextures(pML, pMT);
	//color
	if (varExists("uColor", tagStr)) {
		unsigned int uintColor = 0;
		setUintColorValue(&uintColor, "uColor", tagStr);
		pMT->uColor.setUint32(uintColor);
	}
	//mylog("mt.uTex0=%d, mt.uTex1mask=%d\n", mt.uTex0, mt.uTex1mask);
	if (varExists("primitiveType", tagStr)) {
		std::string str0 = getStringValue("primitiveType", tagStr);
		if (str0.compare("GL_POINTS") == 0) pMT->primitiveType = GL_POINTS;
		else if (str0.compare("GL_LINES") == 0) pMT->primitiveType = GL_LINES;
		else if (str0.compare("GL_LINE_STRIP") == 0) pMT->primitiveType = GL_LINE_STRIP;
		else if (str0.compare("GL_LINE_LOOP") == 0) pMT->primitiveType = GL_LINE_LOOP;
		else if (str0.compare("GL_TRIANGLE_STRIP") == 0) pMT->primitiveType = GL_TRIANGLE_STRIP;
		else if (str0.compare("GL_TRIANGLE_FAN") == 0) pMT->primitiveType = GL_TRIANGLE_FAN;
		else pMT->primitiveType = GL_TRIANGLES;
	}
	setIntValue(&pMT->uTex1alphaChannelN, "uTex1alphaChannelN", tagStr);
	setIntValue(&pMT->uTex0translateChannelN, "uTex0translateChannelN", tagStr);
	setIntBoolValue(&pMT->uAlphaBlending, "uAlphaBlending", tagStr);
	setFloatValue(&pMT->uAlphaFactor, "uAlphaFactor", tagStr);
	setFloatValue(&pMT->uAmbient, "uAmbient", tagStr);
	setFloatValue(&pMT->uSpecularIntencity, "uSpecularIntencity", tagStr);
	setFloatValue(&pMT->uSpecularMinDot, "uSpecularMinDot", tagStr);
	setFloatValue(&pMT->uSpecularPowerOf, "uSpecularPowerOf", tagStr);

	pML->pModelBuilder->useMaterial(pMT);
	return 1;
}
int ModelLoader::processTag(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	if (pML->tagName.compare("texture_as") == 0) {
		//saves texture N in texturesMap under given name
		std::string keyName = getStringValue("texture_as", pML->currentTag);
		if (pMB->texturesHashMap.find(keyName) != pMB->texturesHashMap.end())
			return pMB->texturesHashMap[keyName];
		else { //add new
			std::string txFile = getStringValue("src", pML->currentTag);
			unsigned int intCkey = 0;
			setUintColorValue(&intCkey, "ckey", pML->currentTag);
			int txN = Texture::loadTexture(buildFullPath(pML, txFile), intCkey);
			pMB->texturesHashMap[keyName] = txN;
			//mylog("%s=%d\n", keyName.c_str(), pMB->texturesMap[keyName]);
			return txN;
		}
	}
	if (pML->tagName.find("mt_") == 0) {
		//sets current material
		ModelBuilder* pMB = pML->pModelBuilder;
		if (!pML->closedTag) {
			//save previous material in stack
			if (pMB->usingMaterialN >= 0)
				pMB->materialsStack.push_back(pMB->usingMaterialN);
		}
		Material mt;
		return fillProps_mt(&mt, pML->currentTag, pML);
	}
	if (pML->tagName.find("/mt_") == 0) {
		//restore previous material
		if (pMB->materialsStack.size() > 0) {
			pMB->usingMaterialN = pMB->materialsStack.back();
			pMB->materialsStack.pop_back();
		}
		return 1;
	}
	if (pML->tagName.compare("vs") == 0) {
		//sets virtual shape
		ModelBuilder* pMB = pML->pModelBuilder;
		if (pML->closedTag) {
			if (pMB->pCurrentVShape != NULL)
				delete pMB->pCurrentVShape;
		}
		else { //open tag
			//save previous vshape in stack
			if (pMB->pCurrentVShape != NULL)
				pMB->vShapesStack.push_back(pMB->pCurrentVShape);
		}
		pMB->pCurrentVShape = new VirtualShape();
		fillProps_vs(pMB->pCurrentVShape, pML->currentTag);
		return 1;
	}
	if (pML->tagName.compare("/vs") == 0) {
		//restore previous virtual shape
		if (pMB->vShapesStack.size() > 0) {
			if (pMB->pCurrentVShape != NULL)
				delete(pMB->pCurrentVShape);
			pMB->pCurrentVShape = pMB->vShapesStack.back();
			pMB->vShapesStack.pop_back();
		}
		return 1;
	}
	if (pML->tagName.compare("group") == 0) {
		std::string notAllowed[] = { "pxyz","axyz","align","headTo" };
		int notAllowedLn = sizeof(notAllowed) / sizeof(notAllowed[0]);
		for (int i = 0; i < notAllowedLn; i++)
			if (varExists(notAllowed[i], pML->currentTag)) {
				mylog("ERROR in ModelLoader::processTag: use %s in </group>: %s\n", notAllowed[i].c_str(), pML->currentTag.c_str());
				return -1;
			}
		pMB->lockGroup(pMB);
		return 1;
	}
	//mylog("%s, %s /group?=%d\n",pML->currentTag.c_str(), pML->tagName.c_str(), (pML->tagName.compare("/group") == 0));
	if (pML->tagName.compare("/group") == 0) {
		pMB->releaseGroup(pMB);
		return 1;
	}
	if (pML->tagName.compare("a") == 0)
		return processTag_a(pML); //apply 

	mylog("ERROR in ModelLoader::processTag, unhandled tag %s, file %s\n", pML->currentTag.c_str(), pML->fullPath.c_str());
	return -1;
}
int ModelLoader::fillProps_vs(VirtualShape* pVS, std::string tagStr) {
	//sets virtual shape
	setCharsValue(pVS->shapeType, 20, "vs", tagStr);
	setFloatArray(pVS->whl, 3, "whl", tagStr);
	//extensions
	float ext;
	if (varExists("ext", tagStr)) {
		setFloatValue(&ext, "ext", tagStr);
		pVS->setExt(ext);
	}
	if (varExists("extX", tagStr)) {
		setFloatValue(&ext, "extX", tagStr);
		pVS->setExtX(ext);
	}
	if (varExists("extY", tagStr)) {
		setFloatValue(&ext, "extY", tagStr);
		pVS->setExtY(ext);
	}
	if (varExists("extZ", tagStr)) {
		setFloatValue(&ext, "extZ", tagStr);
		pVS->setExtZ(ext);
	}
	setFloatValue(&pVS->extU, "extU", tagStr);
	setFloatValue(&pVS->extD, "extD", tagStr);
	setFloatValue(&pVS->extL, "extL", tagStr);
	setFloatValue(&pVS->extR, "extR", tagStr);
	setFloatValue(&pVS->extF, "extF", tagStr);
	setFloatValue(&pVS->extB, "extB", tagStr);
	//sections
	setIntValue(&pVS->sectionsR, "sectR", tagStr);
	setIntValue(&pVS->sections[0], "sectX", tagStr);
	setIntValue(&pVS->sections[1], "sectY", tagStr);
	setIntValue(&pVS->sections[2], "sectZ", tagStr);

	//mylog("pVS->shapeType=%s whl=%fx%fx%f\n", pVS->shapeType, pVS->whl[0], pVS->whl[1], pVS->whl[2]);
	return 1;
}


Также надо слегка изменить ModelBuilder1base.

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

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

class ModelBuilder1base
{
public:
	std::vector<Vertex01*> vertices;
	std::vector<Triangle01*> triangles;
	std::vector<int> subjNumbersList;
	int usingSubjN = -1;

	std::vector<Group01*> groupsStack;
	Group01* pCurrentGroup = NULL;

	std::vector<VirtualShape*> vShapesStack;
	VirtualShape* pCurrentVShape = NULL;

	std::vector<Material*> materialsList;
	int usingMaterialN = -1;
	std::vector<int> materialsStack;

	std::map<std::string, int> texturesHashMap;
public:
	virtual ~ModelBuilder1base();
	int useSubjN(int subjN) { return useSubjN(this, subjN); };
	static int useSubjN(ModelBuilder1base* pMB, int subjN);
	int useMaterial(Material* pMT) { return useMaterial(this, pMT); };
	static int useMaterial(ModelBuilder1base* pMB, Material* pMT);
	static void lockGroup(ModelBuilder1base* pMB);
	static void releaseGroup(ModelBuilder1base* pMB);
	static int addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz);
	static int add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n);
	static int addTriangle(ModelBuilder1base* pMB, int n0, int n1, int n2);
	int buildDrawJobs(std::vector<GameSubj*> gameSubjs) { return buildDrawJobs(this, gameSubjs); };
	static int buildDrawJobs(ModelBuilder1base* pMB, std::vector<GameSubj*> gameSubjs);
	static int rearrangeArraysForDrawJob(ModelBuilder1base* 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);
	static int moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ);
};

Важные изменения:

  • texturesHashMap позволит ModelLoader-у обращаться к текстурам по имени.
  • materialsStack позволит переключаться между Material-ами.

7. Заменим ModelBuilder1base.cpp код на:

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

extern float degrees2radians;

ModelBuilder1base::~ModelBuilder1base() {
	//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 ModelBuilder1base::useSubjN(ModelBuilder1base* 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 ModelBuilder1base::useMaterial(ModelBuilder1base* 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 ModelBuilder1base::add2triangles(ModelBuilder1base* 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 ModelBuilder1base::addTriangle(ModelBuilder1base* 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;
}
int ModelBuilder1base::addVertex(ModelBuilder1base* 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 ModelBuilder1base::buildDrawJobs(ModelBuilder1base* 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 ModelBuilder1base::buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalVertsN = useVertices.size();
	if (totalVertsN < 1)
		return 0;
	//if (pMT->uTex2nm >= 0)
	//	calculateTangentSpace(useVertices, useTriangles);
	pMT->pickShaderNumber();
	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_aNormal >= 0) //normal
			memcpy(&vertsBuffer[idx + pDJ->aNormal.offset / floatSize], pVX->aNormal, 3 * floatSize);
		if (pSh->l_aTuv >= 0) //attribute TUV (texture coordinates)
			memcpy(&vertsBuffer[idx + pDJ->aTuv.offset / floatSize], pVX->aTuv, 2 * floatSize);
		if (pSh->l_aTuv2 >= 0) //attribute TUV2 (normal maps)
			memcpy(&vertsBuffer[idx + pDJ->aTuv2.offset / floatSize], pVX->aTuv2, 2 * floatSize);
		if (pSh->l_aTangent >= 0)
			memcpy(&vertsBuffer[idx + pDJ->aTangent.offset / floatSize], pVX->aTangent, 3 * floatSize);
		if (pSh->l_aBinormal >= 0)
			memcpy(&vertsBuffer[idx + pDJ->aBinormal.offset / floatSize], pVX->aBinormal, 3 * 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] = (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 ModelBuilder1base::rearrangeArraysForDrawJob(ModelBuilder1base* 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;
}

int ModelBuilder1base::moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ) {
	//moves and rotates vertex group
	//rotation angles are set in degrees
	mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
	mat4x4_translate(transformMatrix, kX, kY, kZ);
	//rotation order: Z-X-Y
	if (aY != 0) mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * aY);
	if (aX != 0) mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * aX);
	if (aZ != 0) mat4x4_rotate_Z(transformMatrix, transformMatrix, degrees2radians * aZ);

	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);
	}
	return 1;
}
void ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {
	if (pMB->pCurrentGroup != NULL)
		pMB->groupsStack.push_back(pMB->pCurrentGroup);
	pMB->pCurrentGroup = new Group01();
	pMB->pCurrentGroup->fromVertexN = pMB->vertices.size();
	pMB->pCurrentGroup->fromTriangleN = pMB->triangles.size();
}
void ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {
	if (pMB->groupsStack.size() > 0) {
		pMB->pCurrentGroup = pMB->groupsStack.back();
		pMB->groupsStack.pop_back();
	}
	else
		pMB->pCurrentGroup = NULL;
}


Теперь – сама модель.

С таким классным набором шейдеров как у нас, можно подумать и о РЕАЛЬНОЙ модели.

Например:

Она узнаваемая, простая, квадратная, достаточно симпатичная.

Ну и просто подвернулась под-руку.

Я объединил вовлеченные проекции в одну 1024×512 PNG картинку:

  • Голубые изображения справа – это Normal Maps (карты нормалей). Они понадобятся нам позже.

8. Модель будет принадлежать Проекту, не движку (engine). Значит, в Windows File Explorer-е внутри

C:\CPP\a997modeler\dt

добавим новый под-каталог

C:\CPP\a997modeler\dt\models\misc\marlboro01.

Скачаем PNG файл здесь

и сохраним его в C:\CPP\a997modeler\dt\models\misc\marlboro01.


Теперь – описатель / model descriptor.

9. Скопируем нижеприведенный код в Текстовый редактор и сохраним в/как

C:\CPP\a997modeler\dt\models\misc\marlboro01\root01.txt

<texture_as="tx0" src="marlboro03small.png"/>
<mt_type="phong" uTex0_use="tx0" />
<vs="box_tank" whl="53,83,21" ext=1 sectR=1 />
<a="front v,back v" xywh="2,1,323,495"/>
<a="right all" xywh="327,1,128,495"/>
<a="left all" xywh="457,1,128,495"/>
<a="top" xywh="588,1,323,133"/>
<a="bottom" xywh="587,136,324,134"/>

Надеюсь, понятно что он делает. На всякий случай:

  • Строка 1: Загружает Textur-у. “tx0” – просто имя, котое будет использоваться в Материалах.
  • Строка 2: Задает Material с текстурой tx0: текстурированный Phong. Все последующие команды “a” будут использовать этот материал.
  • Строка 3: Задает Виртуальную Форму (Virtual Shape). Размер пачки – 55x85x23 mm. Значения в “whl” (width/height/length, ширина/высота/длинна) слегка меньше, потому что есть еще и 1 mm расширения/extensions во все стороны. “sectR=1” – число радиальных секций для расширений.
  • Остальное: Задает проекции на каждую сторону. “xywh” – текстурные координаты проекции (x, y, width, height).
  • Функционал ModelLoader-а определен как раз этим набором команд.


Создание модели вTheGame.cpp сейчас занимает 51 строку (строки с 22 по 72). А будет всего 4.

10. Заменим 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"
#include "TexCoords.h"
#include "ModelLoader.h"

extern std::string filesRoot;
extern float degrees2radians;

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

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

    int subjN = ModelLoader::loadModel(&gameSubjs, "/dt/models/misc/marlboro01/root01.txt", "");
    GameSubj* pGS = gameSubjs.at(subjN);
    pGS->name.assign("box1");
    pGS->ownSpeed.setDegrees(0, 2, 0);
    //pGS->ownCoords.setDegrees(0, -90, 0);

    //===== set up camera
    mainCamera.ownCoords.setDegrees(15, 180, 0); //set camera angles/orientation
    mainCamera.viewRangeDg = 30;
    mainCamera.stageSize[0] = 80;
    mainCamera.stageSize[1] = 120;
    memcpy(mainCamera.lookAtPoint, pGS->ownCoords.pos, sizeof(float) * 3);
    mainCamera.onScreenResize();

    //===== set up light
    v3set(dirToMainLight, -1, 1, 1);
    vec3_norm(dirToMainLight, dirToMainLight);

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

    //glClearColor(0.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    //calculate halfVector
    float dirToCamera[4] = { 0,0,-1,0 }; //-z
    mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);

    float uHalfVector[4] = { 0,0,0,0 };
    for (int i = 0; i < 3; i++)
        uHalfVector[i] = (dirToCamera[i] + dirToMainLight[i]) / 2;
    vec3_norm(uHalfVector, uHalfVector);

    mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;
    //mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 100.f, 500.f);
    float nearClip = mainCamera.focusDistance - 50;
    float farClip = mainCamera.focusDistance + 50;
    if (nearClip < 0) nearClip = 0;
    mat4x4_perspective(mProjection, mainCamera.viewRangeDg * degrees2radians, screenAspectRatio, nearClip, farClip);
    mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);
    mViewProjection[1][3] = 0; //keystone effect

    //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, mViewProjection, pGS->ownModelMatrix);
        //build Model-View (rotation) matrix for normals
        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS->ownCoords.getRotationMatrix());
        //convert to 3x3 matrix
        float mMV3x3[3][3];
        for (int y = 0; y < 3; y++)
            for (int x = 0; x < 3; x++)
                mMV3x3[y][x] = mMV4x4[y][x];
        //render subject
        for (int i = 0; i < pGS->djTotalN; i++) {
            DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
            pDJ->execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);
        }
    }
    //synchronization
    while (1) {
        long long int currentMillis = getSystemMillis();
        long long int millisSinceLastFrame = currentMillis - lastFrameMillis;
        if (millisSinceLastFrame >= millisPerFrame) {
            lastFrameMillis = currentMillis;
            break;
        }
    }
    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;
    screenAspectRatio = (float)width / height;
    glViewport(0, 0, width, height);
    mainCamera.onScreenResize();
    mylog(" screen size %d x %d\n", width, height);
    return 1;
}
int TheGame::run() {
    getReady();
    while (!bExitGame) {
        drawFrame();
    }
    cleanUp();
    return 1;
}
GameSubj* TheGame::newGameSubj(std::string subjClass) {
    return (new GameSubj());
}

Другие изменения:

  • Добавлена функция newGameSubj(..), которую ModelLoader будет вызывать для создания новых GameSubjs.
  • stageSise изменен на 80×120
  • nearClip и farClip тоже слегка другие (исходя их размеров пачки)
  • гашение эффекта трапеции (keystone effect handling, строка 64)


11. В TheGame.h объявление новой функции, newGameSubj(..) .

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

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

class TheGame
{
public:
	int screenSize[2];
	float screenAspectRatio = 1;
	//synchronization
	long long int lastFrameMillis = 0;
	int targetFPS = 30;
	int millisPerFrame = 1000 / targetFPS;

	bool bExitGame;
	Camera mainCamera;
	float dirToMainLight[4] = { 1,1,1,0 };

	//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);
	static GameSubj* newGameSubj(std::string subjClass);
};


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


Android

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


14. Под xEngine добавим Existing Item

из C:\CPP\engine.

  • XMLparser.cpp
  • XMLparser.h

Add


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

из C:\CPP\engine\modeler

  • ModelLoader.cpp
  • ModelLoader.h

Add


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

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

Хорогшо.

VS top menu -> Debug -> Stop Debugging.


Leave a Reply

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