Наша следующая задача – вынести (убрать) построение модели за пределы 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.