Chapter 32. Marked vertices groups

Next I want to "draw" the joint (slit) between the pack and the lid, like a small normal map applied to existing textured mesh's fragment, not to entire mesh and not to VirtualShape as we did before. This will require a few new concepts.

Windows

1. Start VS, open C:\CPP\a997modeler\p_windows\p_windows.sln.


The first new concept - marked vertices/triangles groups, so later we can address certain mesh by the name (mark).

2. Open Group01.h and replace code by:

#pragma once

class Group01
{
public:
	char marks[124] = "";
	int fromVertexN = 0;
	int fromTriangleN = 0;
};


3. Open Vertex01.h and replace code by:

#pragma once

class Vertex01
{
public:
	char marks[124] = "";
	int subjN = -1; //game subject number
	int materialN = -1; //material number
	int flag = 0;
	int endOfSequence = 0; //for sequentional (unindexed) primitives (like GL_LINE_STRIP for example)
	int altN = -1; //vertex' position in alternative array
	//atributes
	float aPos[4] = { 0,0,0,0 }; //position x,y,z + 4-th float for matrix operations
	float aNormal[4] = { 0,0,0,0 }; //normal (surface reflection vector) x,y,z + 4-th float for matrix operations
	float aTuv[2] = { 0,0 }; //2D texture coordinates
	float aTuv2[2] = { 0,0 }; //for normal maps
	//tangent space (for normal maps)
	float aTangent[3] = { 0,0,0 };
	float aBinormal[3] = { 0,0,0 };
};


4. Open Triangle01.h and replace code by:

#pragma once

class Triangle01
{
public:
	char marks[124] = "";
	int subjN = -1; //game subject number
	int materialN = -1; //material number
	int flag = 0;
	int idx[3] = { 0,0,0 }; //3 vertex indices
};


GroupTransform class will hold all info about what we are selecting and what we want to do with this selection.

5. Open GroupTransform.h and replace code by:

#pragma once
#include <stdlib.h>
#include "ModelBuilder1base.h"

class GroupTransform
{
public:
	float shift[4] = { 0,0,0,0 };
	float spin[3] = { 0,0,0 }; //in degrees
	float scale[3] = { 1,1,1 };
	std::string allign = "";
	std::string headZto = "";
	std::string onThe = ""; //left/right/etc.
	//limit to
	Group01* pGroup = NULL; //NULL-all, can also be pCurrentGroup or pLastClosedGroup
	char mark[32] = ""; //must be in <>
	float pMin[3]{ -1000000 ,-1000000 ,-1000000 };
	float pMax[3]{ 1000000 , 1000000 , 1000000 };
public:
	int executeGroupTransform(ModelBuilder1base* pMB) { return executeGroupTransform(pMB, this); };
	static int executeGroupTransform(ModelBuilder1base* pMB, GroupTransform* pGT);
	//set limits
	static void limit2mark(GroupTransform* pGT, std::string mark0);
	static int flagSelection(GroupTransform* pGT, std::vector<Vertex01*>* pVertices, std::vector<Triangle01*>* pTriangles);
	static int cloneFlagged(ModelBuilder1base* pMB,
		std::vector<Vertex01*>* pVxDst, std::vector<Triangle01*>* pTrDst,
		std::vector<Vertex01*>* pVxSrc, std::vector<Triangle01*>* pTrSrc);
	static int refactorTriangles(std::vector<Triangle01*>* pTrDst, int trianglesN0dst, std::vector<Vertex01*>* pVxSrc);
	static int transformFlagged(GroupTransform* pGT, std::vector<Vertex01*>* pVxDst);
	static int transformFlaggedMx(std::vector<Vertex01*>* pVx, mat4x4* pTransformMatrix);

	static int buildTransformMatrix(GroupTransform* pGT, mat4x4* pTransformMatrix);
	static void flagAll(std::vector<Vertex01*>* pVx, std::vector<Triangle01*>* pTr);
	static int buildBoundingBoxFlagged(float* bbMin, float* bbMax, std::vector<Vertex01*>* pVx);
};


6. Open GroupTransform.cpp and replace code by:

#include "GroupTransform.h"
#include "utils.h"

extern float degrees2radians;

int GroupTransform::executeGroupTransform(ModelBuilder1base* pMB, GroupTransform* pGT) {
	//pMB->moveGroupDg(pMB, pGT->spin[0], pGT->spin[1], pGT->spin[2], pGT->shift[0], pGT->shift[1], pGT->shift[2]);
	flagSelection(pGT, &pMB->vertices, &pMB->triangles);
	transformFlagged(pGT, &pMB->vertices);
	return 1;
}
void GroupTransform::limit2mark(GroupTransform* pGT, std::string mark0) {
	std::string outStr;
	outStr.append("<" + mark0 + ">");
	myStrcpy_s(pGT->mark, 32, outStr.c_str());
}
int GroupTransform::flagSelection(GroupTransform* pGT, std::vector<Vertex01*>* pVertices, std::vector<Triangle01*>* pTriangles) {
	if (pVertices != NULL) {
		bool checkSize = false;
		for (int i = 0; i < 3; i++) {
			if (pGT->pMin[i] > -1000000) {
				checkSize = true;
				break;
			}
			if (pGT->pMax[i] < 1000000) {
				checkSize = true;
				break;
			}
		}
		int totalN = pVertices->size();
		for (int vN = 0; vN < totalN; vN++) {
			Vertex01* pV = pVertices->at(vN);
			if (pGT->pGroup != NULL) {
				if (vN < pGT->pGroup->fromVertexN) {
					pV->flag = -1;
					continue;
				}
			}
			if (strcmp(pGT->mark, "") != 0) {
				if (strstr(pV->marks, pGT->mark) == nullptr) {
					pV->flag = -1;
					continue;
				}
			}
			if (checkSize) {
				bool fits = true;
				for (int i = 0; i < 3; i++) {
					if (pV->aPos[i] < pGT->pMin[i]) {
						fits = false;
						break;
					}
					if (pV->aPos[i] > pGT->pMax[i]) {
						fits = false;
						break;
					}
				}
				if (!fits) {
					pV->flag = -1;
					continue;
				}
			}
			pV->flag = 0;
		}
	}
	if (pTriangles != NULL) {
		int totalN = pTriangles->size();
		for (int tN = 0; tN < totalN; tN++) {
			Triangle01* pT = pTriangles->at(tN);
			if (pGT->pGroup != NULL) {
				if (tN < pGT->pGroup->fromTriangleN) {
					pT->flag = -1;
					continue;
				}
			}
			if (strcmp(pGT->mark, "") != 0) {
				if (strstr(pT->marks, pGT->mark) == nullptr) {
					pT->flag = -1;
					continue;
				}
			}
			pT->flag = 0;
		}
	}
	return 1;
}
int GroupTransform::cloneFlagged(ModelBuilder1base* pMB,
	std::vector<Vertex01*>* pVxDst, std::vector<Triangle01*>* pTrDst,
	std::vector<Vertex01*>* pVxSrc, std::vector<Triangle01*>* pTrSrc) {
	int vertsNsrc = pVxSrc->size();
	int trianglesNsrc = pTrSrc->size();
	int vertsN0dst = pVxDst->size();
	int trianglesN0dst = pTrDst->size();
	for (int i = 0; i < vertsNsrc; i++) {
		Vertex01* pV0 = pVxSrc->at(i);
		if (pV0->flag < 0)
			continue;
		pV0->altN = pVxDst->size();
		Vertex01* pV = new Vertex01(*pV0);
		pVxDst->push_back(pV);
		//overwrite marks
		if (pMB != NULL) {
			myStrcpy_s(pV->marks, 124, pMB->pCurrentGroup->marks);
			pV->subjN = pMB->usingSubjN;
		}
		pV->flag = -1;
	}
	for (int i = 0; i < trianglesNsrc; i++) {
		Triangle01* pT0 = pTrSrc->at(i);
		if (pT0->flag < 0)
			continue;
		Triangle01* pT = new Triangle01(*pT0);
		pTrDst->push_back(pT);
		//overwrite marks
		if (pMB != NULL) {
			myStrcpy_s(pT->marks, 124, pMB->pCurrentGroup->marks);
			pT->subjN = pMB->usingSubjN;
		}
		pT->flag = -1;
	}
	refactorTriangles(pTrDst, trianglesN0dst, pVxSrc);
	return 1;
}
int GroupTransform::refactorTriangles(std::vector<Triangle01*>* pTrDst, int trianglesN0dst, std::vector<Vertex01*>* pVxSrc){
	//re-factor triangles idx, adjusting triangles verts #s
	int trianglesNdst = pTrDst->size();
	for (int tN = trianglesN0dst; tN < trianglesNdst; tN++) {
		Triangle01* pT = pTrDst->at(tN);
		for (int i = 0; i < 3; i++) {
			int vN = pT->idx[i];
			Vertex01* pV = pVxSrc->at(vN);
			pT->idx[i] = pV->altN;
		}
	}
	return 1;
}
int GroupTransform::buildTransformMatrix(GroupTransform* pGT, mat4x4* pTransformMatrix) {
	mat4x4_identity(*pTransformMatrix);
	if (!pGT->onThe.empty()) {
		if (pGT->onThe.compare("back") == 0)
			mat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 180);
		else if (pGT->onThe.compare("left") == 0)
			mat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, -degrees2radians * 90);
		else if (pGT->onThe.compare("right") == 0)
			mat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 90);
		else if (pGT->onThe.compare("top") == 0) {
			mat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * 180);
			mat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, -degrees2radians * 90);
		}
		else if (pGT->onThe.compare("bottom") == 0)
			mat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, degrees2radians * 90);
	}
	if(!v3equals(pGT->shift, 0))
		mat4x4_translate_in_place(*pTransformMatrix, pGT->shift[0], pGT->shift[1], pGT->shift[2]);

	//rotation order: Z-X-Y
	if (pGT->spin[1] != 0)
		mat4x4_rotate_Y(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT->spin[1]);
	if (pGT->spin[0] != 0)
		mat4x4_rotate_X(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT->spin[0]);
	if (pGT->spin[2] != 0)
		mat4x4_rotate_Z(*pTransformMatrix, *pTransformMatrix, degrees2radians * pGT->spin[2]);

	if (!v3equals(pGT->scale, 1))
		mat4x4_scale_aniso(*pTransformMatrix, *pTransformMatrix, pGT->scale[0], pGT->scale[1], pGT->scale[2]);
	return 1;
}
int GroupTransform::transformFlagged(GroupTransform* pGT, std::vector<Vertex01*>* pVx) {
	//moves and rotates vertex group
	//rotation angles are set in degrees
	mat4x4 transformMatrix;
	buildTransformMatrix(pGT, &transformMatrix);
	transformFlaggedMx(pVx, &transformMatrix);
	return 1;
}
int GroupTransform::transformFlaggedMx(std::vector<Vertex01*>* pVx, mat4x4* pTransformMatrix) {
	//moves and rotates vertex group
	int vertsN = pVx->size();
	for (int i = 0; i < vertsN; i++) {
		Vertex01* pV = pVx->at(i);
		if (pV->flag < 0)
			continue;
		mat4x4_mul_vec4plus(pV->aPos, *pTransformMatrix, pV->aPos, 1);
		//normal
		mat4x4_mul_vec4plus(pV->aNormal, *pTransformMatrix, pV->aNormal, 0);
		vec3_norm(pV->aNormal, pV->aNormal);
	}
	return 1;
}
void GroupTransform::flagAll(std::vector<Vertex01*>* pVx, std::vector<Triangle01*>* pTr) {
	//set flags
	for (int i = pVx->size() - 1; i >= 0; i--)
		pVx->at(i)->flag = 0;
	for (int i = pTr->size() - 1; i >= 0; i--)
		pTr->at(i)->flag = 0;
}
int GroupTransform::buildBoundingBoxFlagged(float* bbMin, float* bbMax, std::vector<Vertex01*>* pVx) {
	v3copy(bbMin, pVx->at(0)->aPos);
	v3copy(bbMax, pVx->at(0)->aPos);
	//scan all flagged except #0
	for (int vN = pVx->size() - 1; vN > 0; vN--) {
		Vertex01* pV = pVx->at(vN);
		if (pV->flag < 0)
			continue;
		for(int i=0;i<3;i++){
			if (bbMin[i] > pV->aPos[i])
				bbMin[i] = pV->aPos[i];
			if (bbMax[i] < pV->aPos[i])
				bbMax[i] = pV->aPos[i];
		}
	}
	return 1;
}


To test it we will add to root01.txt couple commands.

7. Copy following code to a Text Editor and save it (overwrite) to/as

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

<texture_as="tx0" src="marlboro03small.png" ckey="#00ff00"/>
<mt_type="phong" uTex0_use="tx0" />
<vs="box_tank" whl="53,83,21" ext=1 sectR=1 />
<a="front v" xywh="2,1,323,495"/>
<a="back v"  xywh="2,1,323,495"/>
<a="right all" xywh="327,1,128,495" mark="box_right"/>
<a="left all" xywh="457,1,128,495"/>
<a="top" xywh="588,1,323,133"/>
<a="bottom" xywh="587,136,324,134"/>
//golden prints
<vs="box" whl="55.1,85.1,23.1" />
<texture_as="whitenoise" src="/dt/common/img/whitenoise/wn64_blur3.bmp"/>
<texture_as="gold" src="/dt/common/img/materials/gold02roman.bmp" />
<mt_type="mirror" uAlphaBlending uTex1mask_use="tx0" uTex1alphaChannelN=1 uTex0_use="whitenoise" uTex0translateChannelN=0 uTex3_use="gold" />
//side golden prints
<a="right" xywh="342,12,101,10" whl="x,1.8,18.1" pxyz="x,39.8, -0.3" /> //Please do not litter
<a="right" xywh="339,144,105,89" whl="x,15.35,18.9" pxyz="x,10.3,-0.12" /> //For special offers...
<a="left" xywh="475,15,95,48" whl="x,8.4,17" pxyz="x,36, 0.3" /> //Underage sale...
//front prints
<group>
	//bottom golden print "20 class a..."
	<a="front" xywh="20,498,289,13" whl="47.5,2,x" pxyz="1,-36,x" />
	//blazon/emblem
	<mt_type="mirror" uAlphaBlending uTex2nm_use="tx0" uTex0_use="whitenoise" uTex0translateChannelN=0 uTex3_use="gold" />
	<a="front" xywh2nm="589,415,128,94" whl="20.7,16,x" pxyz="0.3,6.1,x" /> //emblem
	//"Marlboro
	<mt_type="phong" uAlphaBlending uTex2nm_use="tx0" uColor="#1E211E" />
	<a="front" xywh2nm="590,275,301,136" whl="49.2,23.3,x" pxyz="0.21,-18,x" /> //marlboro
</group> 
<clone ay=180 />

<do px=5 all markedAs="box_right" />

  • In line 6 we are marking right side as "box_right".
  • All vertices and triangles for this side will have this mark.
  • In line 32 we are saying to move everything marked as "box_right" to the right (x coordinate) by 5 units.

In order to accept new parameters, XMLParser, ModelBuilder1base and ModelLoader are modified a bit.

8. Open XMLParser.cpp and replace code by:

#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 == 1) {
		float val = stof(valuesVector.at(0));
		for (int i = 0; i < arrayLength; i++)
			pFloats[i] = val;
		return 1;
	}
	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 == 1) {
		int val = stoi(valuesVector.at(0));
		for (int i = 0; i < arrayLength; i++)
			pInts[i] = val;
		return 1;
	}
	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;
}

Modified functions:

  • setFloatArray(..)
  • setIntArray(..)

9. Open ModelBuilder1base.cpp and replace code by:

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

extern float degrees2radians;

ModelBuilder1base::~ModelBuilder1base() {
	releaseGroup(this);

	//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();
	if (pCurrentGroup != NULL)
		delete pCurrentGroup;
	if (pLastClosedGroup != NULL)
		delete pLastClosedGroup;

	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;
	//mark
	if (pMB->pCurrentGroup != NULL)
		if (strcmp(pMB->pCurrentGroup->marks, "") != 0)
			myStrcpy_s(pTR->marks, 124, pMB->pCurrentGroup->marks);
	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;
	//mark
	if (pMB->pCurrentGroup != NULL)
		if (strcmp(pMB->pCurrentGroup->marks, "") != 0)
			myStrcpy_s(pVX->marks, 124, pMB->pCurrentGroup->marks);

	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;
}

int ModelBuilder1base::calculateTangentSpace(std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalVertsN = useVertices.size();
	if (totalVertsN < 1)
		return 0;
	int totalTrianglesN = useTriangles.size();
	//assuming that GL_TRIANGLES
	//clear flags
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pV = useVertices.at(vN);
		pV->flag = 0;
	}
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pVX = useVertices.at(vN);
		if (pVX->flag != 0)
			continue;
		Triangle01* pT = NULL;
		for (int tN = 0; tN < totalTrianglesN; tN++) {
			pT = useTriangles.at(tN);
			bool haveTriangle = false;
			for (int i = 0; i < 3; i++)
				if (pT->idx[i] == vN) {
					haveTriangle = true;
					break;
				}
			if (haveTriangle)
				break;
		}
		Vertex01* pV[3];
		for (int i = 0; i < 3; i++)
			pV[i] = useVertices.at(pT->idx[i]);

		float dPos1[3];
		float dPos2[3];
		float dUV1[2];
		float dUV2[2];
		for (int i = 0; i < 3; i++) {
			dPos1[i] = pV[1]->aPos[i] - pV[0]->aPos[i];
			dPos2[i] = pV[2]->aPos[i] - pV[0]->aPos[i];
		}
		for (int i = 0; i < 2; i++) {
			dUV1[i] = pV[1]->aTuv2[i] - pV[0]->aTuv2[i];
			dUV2[i] = pV[2]->aTuv2[i] - pV[0]->aTuv2[i];
		}

		float tangent[3];
		float binormal[3];
		float divider = dUV1[0] * dUV2[1] - dUV1[1] * dUV2[0];
		if (divider == 0) {
			v3set(tangent, 1, 0, 0);
			v3set(binormal, 0, -1, 0);
		}
		else {
			float r = 1.0f / divider;
			for (int i = 0; i < 3; i++) {
				tangent[i] = (dPos1[i] * dUV2[1] - dPos2[i] * dUV1[1]) * r;
				binormal[i] = -(dPos2[i] * dUV1[0] - dPos1[i] * dUV2[0]) * r;
			}
			vec3_norm(tangent, tangent);
			vec3_norm(binormal, binormal);
		}
		//add to all 3 vertices
		for (int n = 0; n < 3; n++) {
			if (pV[n]->flag > 0)
				continue;
			v3copy(pV[n]->aTangent, tangent);
			v3copy(pV[n]->aBinormal, binormal);
			pV[n]->flag = 1;
		}
	}
	//normalize tangent and binormal around normal
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pV = useVertices.at(vN);
		float v3out[3];
		//tangent
		vec3_mul_cross(v3out, pV->aNormal, pV->aBinormal);
		if (v3dotProduct(pV->aTangent, v3out) < 0)
			v3inverse(v3out);
		v3copy(pV->aTangent, v3out);
		//binormal
		vec3_mul_cross(v3out, pV->aNormal, pV->aTangent);
		if (v3dotProduct(pV->aBinormal, v3out) < 0)
			v3inverse(v3out);
		v3copy(pV->aBinormal, v3out);
	}
	return 1;
}
void ModelBuilder1base::lockGroup(ModelBuilder1base* pMB) {
	Group01* pPrevGroup = pMB->pCurrentGroup;
	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();
	//marks
	if(pPrevGroup != NULL)
		if (strcmp(pPrevGroup->marks, "") != 0)
			myStrcpy_s(pMB->pCurrentGroup->marks, 124, pPrevGroup->marks);
}
void ModelBuilder1base::releaseGroup(ModelBuilder1base* pMB) {
	if (pMB->pLastClosedGroup != NULL)
		delete pMB->pLastClosedGroup;
	pMB->pLastClosedGroup = pMB->pCurrentGroup;

	if (pMB->groupsStack.size() > 0) {
		pMB->pCurrentGroup = pMB->groupsStack.back();
		pMB->groupsStack.pop_back();
	}
	else
		pMB->pCurrentGroup = NULL;
}

Modified functions::

  • lockGroup(..)
  • addVertex(..)
  • addTriangle(..)

10. Open ModelLoader.h and replace code by:

#pragma once
#include "XMLparser.h"
#include "ModelBuilder.h"
#include "GroupTransform.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->lockGroup(pModelBuilder);
		}
		pModelBuilder->useSubjN(subjN);
	};
	virtual ~ModelLoader() {
		if (!ownModelBuilder)
			return;
		pModelBuilder->buildDrawJobs(*pSubjsVector);
		delete pModelBuilder;
	};
	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_vs(VirtualShape* pVS, std::string tagStr); //virtual shape
	static int fillProps_mt(Material* pMT, std::string tagStr, ModelLoader* pML); //Material
	static int fillProps_gt(GroupTransform* pGS, ModelBuilder* pMB, std::string tagStr);
	int processTag() { return processTag(this); };
	static int processTag(ModelLoader* pML);
	static int loadModel(std::vector<GameSubj*>* pSubjsVector0, std::string sourceFile, std::string subjClass);
	static int processTag_clone(ModelLoader* pML);
	static int addMark(char* marks, std::string newMark);
	static int processTag_do(ModelLoader* pML);
};

New functions:

  • addMark(..)
  • processTag_do(..)

11. Open ModelLoader.cpp and replace code by:

#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::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);
		//mark
		if (varExists("mark", pML->currentTag))
			addMark(pMB->pCurrentGroup->marks, getStringValue("mark", pML->currentTag));
		return 1;
	}
	if (pML->tagName.compare("/group") == 0) {
		GroupTransform gt;
		fillProps_gt(&gt, pMB, pML->currentTag);
		gt.executeGroupTransform(pMB);

		pMB->releaseGroup(pMB);
		return 1;
	}
	if (pML->tagName.compare("a") == 0)
		return processTag_a(pML); //apply 
	if (pML->tagName.compare("clone") == 0)
		return processTag_clone(pML);
	if (pML->tagName.compare("/clone") == 0)
		return processTag_clone(pML);
	if (pML->tagName.compare("do") == 0)
		return processTag_do(pML);

	//mylog("%s, %s /group?=%d\n",pML->currentTag.c_str(), pML->tagName.c_str(), (pML->tagName.compare("/group") == 0));
	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;
}
int ModelLoader::processTag_a(ModelLoader* pML) {
	//apply
	ModelBuilder* pMB = pML->pModelBuilder;
	std::string tagStr = pML->currentTag;
	pMB->lockGroup(pMB);
	//mark
	if (varExists("mark", tagStr))
		addMark(pMB->pCurrentGroup->marks, getStringValue("mark", tagStr));

	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);

	//adjusted VirtualShape
	VirtualShape* pVS_a = new VirtualShape(*pMB->pCurrentVShape);
	fillProps_vs(pVS_a, tagStr);

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

	GroupTransform GT_a;
	fillProps_gt(&GT_a, pMB, tagStr);
	GT_a.executeGroupTransform(pMB);

	pMB->releaseGroup(pMB);
	return 1;
}
int ModelLoader::processTag_clone(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	if (pML->tagName.compare("clone") == 0) {
		//mark what to clone
		GroupTransform gt;
		gt.pGroup = pMB->pLastClosedGroup;
		gt.flagSelection(&gt, &pMB->vertices, &pMB->triangles);

		//cloning
		pMB->lockGroup(pMB);
		gt.cloneFlagged(pMB, &pMB->vertices, &pMB->triangles, &pMB->vertices, &pMB->triangles);
	}
	GroupTransform gt;
	fillProps_gt(&gt, pMB, pML->currentTag);
	gt.executeGroupTransform(pMB);

	if (pML->tagName.compare("/clone") == 0 || pML->closedTag) {
		pMB->releaseGroup(pMB);
	}
	return 1;
}
int ModelLoader::addMark(char* marks, std::string newMark) {
	if (newMark.empty())
		return 0;
	std::string allMarks;
	allMarks.assign(marks);
	allMarks.append("<"+ newMark+">");
	myStrcpy_s(marks,124, allMarks.c_str());
	return 1;
}

int ModelLoader::fillProps_gt(GroupTransform* pGT, ModelBuilder* pMB, std::string tagStr) {
	pGT->pGroup = pMB->pCurrentGroup;
	//position
	setFloatArray(pGT->shift, 3, "pxyz", tagStr);
	setFloatValue(&pGT->shift[0], "px", tagStr);
	setFloatValue(&pGT->shift[1], "py", tagStr);
	setFloatValue(&pGT->shift[2], "pz", tagStr);
	//angles
	setFloatArray(pGT->spin, 3, "axyz", tagStr);
	setFloatValue(&pGT->spin[0], "ax", tagStr);
	setFloatValue(&pGT->spin[1], "ay", tagStr);
	setFloatValue(&pGT->spin[2], "az", tagStr);
	//scale
	setFloatArray(pGT->scale, 3, "scale", tagStr);

	pGT->onThe = getStringValue("onThe", tagStr);
	pGT->allign = getStringValue("allign", tagStr);
	pGT->headZto = getStringValue("headZto", tagStr);
	//limit to
	if(varExists("all",tagStr))
		pGT->pGroup = NULL;
	if (varExists("lastClosedGroup", tagStr))
		pGT->pGroup = pMB->pLastClosedGroup;
	if (varExists("markedAs", tagStr))
		pGT->limit2mark(pGT, getStringValue("markedAs", tagStr));
	setFloatArray(pGT->pMin, 3, "xyzMin", tagStr);
	setFloatArray(pGT->pMax, 3, "xyzMax", tagStr);
	return 1;
}
int ModelLoader::processTag_do(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	GroupTransform gt;
	fillProps_gt(&gt, pMB, pML->currentTag);
	gt.flagSelection(&gt, &pMB->vertices, &pMB->triangles);
	gt.transformFlagged(&gt, &pMB->vertices);
	return 1;
}

Also modified:

  • fillProps_gt(..)
  • processTag_clone(..)

So, we wanted to mark right side and shift it out to the right by 5 units.

12. Build and run. Result:

Shifted. However, gilded letters which were left behind are overlapping shifted right side. That's because we still didn't care about depth buffer. Closer objects should cover more distant ones. For that we need to enable depth buffer testing.


13. Open TheGame.cpp and replace code by:

#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);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDepthMask(GL_TRUE);

    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 | GL_DEPTH_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());
}

See highlighted lines. Added:

  • glEnable(GL_DEPTH_TEST);
  • glDepthFunc(GL_LEQUAL);
  • glDepthMask(GL_TRUE);
  • glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

14. Build and run. Result:

Now - correct, depth buffer works.

So, we are able to mark sub-meshes and to modify them, the goal is achieved.


Now - on

Android

To my big surprise depth buffer on Android didn't work! Turned out that it just wasn't included in GL initialization in main.cpp.

15. Close and re-start VS. Open C:\CPP\a997modeler\p_android\p_android.sln.


16. Open main.cpp and replace code by:

#include "platform.h"
#include "TheGame.h"

#include <string>
#include <vector>

#include <sys/stat.h>	//mkdir for Android

std::string filesRoot;

TheGame theGame;

struct android_app* androidApp;

ASensorManager* sensorManager;
const ASensor* accelerometerSensor;
ASensorEventQueue* sensorEventQueue;

EGLDisplay androidDisplay;
EGLSurface androidSurface;
EGLContext androidContext;

/**
* Initialize an EGL context for the current display.
*/
static int engine_init_display(struct engine* engine) {
	// initialize OpenGL ES and EGL

	/*
	* Here specify the attributes of the desired configuration.
	* Below, we select an EGLConfig with at least 8 bits per color
	* component compatible with on-screen windows
	*/
	const EGLint attribs[] = {
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_BLUE_SIZE, 8,
		EGL_GREEN_SIZE, 8,
		EGL_RED_SIZE, 8,
		EGL_DEPTH_SIZE, 16,
		EGL_NONE
	};
	EGLint format;
	EGLint numConfigs;
	EGLConfig config;
	EGLSurface surface;
	EGLContext context;

	EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

	eglInitialize(display, 0, 0);

	/* Here, the application chooses the configuration it desires. In this
	* sample, we have a very simplified selection process, where we pick
	* the first EGLConfig that matches our criteria */
	eglChooseConfig(display, attribs, &config, 1, &numConfigs);

	/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
	* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
	* As soon as we picked a EGLConfig, we can safely reconfigure the
	* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
	eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);

	ANativeWindow_setBuffersGeometry(androidApp->window, 0, 0, format);

	surface = eglCreateWindowSurface(display, config, androidApp->window, NULL);

	EGLint contextAttribs[] =
	{
		EGL_CONTEXT_CLIENT_VERSION, 3,
		EGL_NONE
	};
	context = eglCreateContext(display, config, NULL, contextAttribs);


	if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
		mylog("ERROR: Unable to eglMakeCurrent");
		return -1;
	}

	androidDisplay = display;
	androidContext = context;
	androidSurface = surface;

	return 0;
}

/**
* Tear down the EGL context currently associated with the display.
*/
static void engine_term_display() {

	if (androidDisplay != EGL_NO_DISPLAY) {
		eglMakeCurrent(androidDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		if (androidContext != EGL_NO_CONTEXT) {
			eglDestroyContext(androidDisplay, androidContext);
		}
		if (androidSurface != EGL_NO_SURFACE) {
			eglDestroySurface(androidDisplay, androidSurface);
		}
		eglTerminate(androidDisplay);
	}
	androidDisplay = EGL_NO_DISPLAY;
	androidContext = EGL_NO_CONTEXT;
	androidSurface = EGL_NO_SURFACE;
}

/**
* Process the next input event.
*/
static int32_t engine_handle_input(struct android_app* app, AInputEvent* event) {
	if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
		//engine->state.x = AMotionEvent_getX(event, 0);
		//engine->state.y = AMotionEvent_getY(event, 0);
		return 1;
	}
	return 0;
}

/**
* Process the next main command.
*/
static void engine_handle_cmd(struct android_app* app, int32_t cmd) {
	struct engine* engine = (struct engine*)app->userData;
	switch (cmd) {
	case APP_CMD_INIT_WINDOW:
		// The window is being shown, get it ready.
		if (androidApp->window != NULL) {
			engine_init_display(engine);
			//engine_draw_frame(engine);
		}
		break;
	case APP_CMD_TERM_WINDOW:
		// The window is being hidden or closed, clean it up.
		engine_term_display();
		break;
	case APP_CMD_GAINED_FOCUS:
		// When our app gains focus, we start monitoring the accelerometer.
		if (accelerometerSensor != NULL) {
			ASensorEventQueue_enableSensor(sensorEventQueue,
				accelerometerSensor);
			// We'd like to get 60 events per second (in microseconds).
			ASensorEventQueue_setEventRate(sensorEventQueue,
				accelerometerSensor, (1000L / 60) * 1000);
		}
		break;
	case APP_CMD_LOST_FOCUS:
		// When our app loses focus, we stop monitoring the accelerometer.
		// This is to avoid consuming battery while not being used.
		if (accelerometerSensor != NULL) {
			ASensorEventQueue_disableSensor(sensorEventQueue,
				accelerometerSensor);
		}
		// Also stop animating.
		//engine_draw_frame(engine);
		break;
	}
}
static std::vector<std::string> list_assets(android_app* app, const char* asset_path)
{ //by Marcel Smit, stolen from https://github.com/android/ndk-samples/issues/603
	std::vector<std::string> result;

	JNIEnv* env = nullptr;
	app->activity->vm->AttachCurrentThread(&env, nullptr);

	auto context_object = app->activity->clazz;
	auto getAssets_method = env->GetMethodID(env->GetObjectClass(context_object), "getAssets", "()Landroid/content/res/AssetManager;");
	auto assetManager_object = env->CallObjectMethod(context_object, getAssets_method);
	auto list_method = env->GetMethodID(env->GetObjectClass(assetManager_object), "list", "(Ljava/lang/String;)[Ljava/lang/String;");

	jstring path_object = env->NewStringUTF(asset_path);
	auto files_object = (jobjectArray)env->CallObjectMethod(assetManager_object, list_method, path_object);
	env->DeleteLocalRef(path_object);
	auto length = env->GetArrayLength(files_object);

	for (int i = 0; i < length; i++)
	{
		jstring jstr = (jstring)env->GetObjectArrayElement(files_object, i);
		const char* filename = env->GetStringUTFChars(jstr, nullptr);
		if (filename != nullptr)
		{
			result.push_back(filename);
			env->ReleaseStringUTFChars(jstr, filename);
		}
		env->DeleteLocalRef(jstr);
	}
	app->activity->vm->DetachCurrentThread();
	return result;
}

int updateAssets() {
	//get APK apkLastUpdateTime timestamp
	JNIEnv* env = nullptr;
	androidApp->activity->vm->AttachCurrentThread(&env, nullptr);
	jobject context_object = androidApp->activity->clazz;
	jmethodID getPackageNameMid_method = env->GetMethodID(env->GetObjectClass(context_object), "getPackageName", "()Ljava/lang/String;");
	jstring packageName = (jstring)env->CallObjectMethod(context_object, getPackageNameMid_method);
	jmethodID getPackageManager_method = env->GetMethodID(env->GetObjectClass(context_object), "getPackageManager", "()Landroid/content/pm/PackageManager;");
	jobject packageManager_object = env->CallObjectMethod(context_object, getPackageManager_method);
	jmethodID getPackageInfo_method = env->GetMethodID(env->GetObjectClass(packageManager_object), "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
	jobject packageInfo_object = env->CallObjectMethod(packageManager_object, getPackageInfo_method, packageName, 0x0);
	jfieldID updateTimeFid = env->GetFieldID(env->GetObjectClass(packageInfo_object), "lastUpdateTime", "J");
	long int apkLastUpdateTime = env->GetLongField(packageInfo_object, updateTimeFid);
	// APK updateTime timestamp retrieved
	// compare with saved timestamp
	std::string updateTimeFilePath = filesRoot + "/dt/apk_update_time.bin";
	FILE* inFile = fopen(updateTimeFilePath.c_str(), "r");
	if (inFile != NULL)
	{
		long int savedUpdateTime;
		fread(&savedUpdateTime, 1, sizeof(savedUpdateTime), inFile);
		fclose(inFile);
		if (savedUpdateTime == apkLastUpdateTime) {
			mylog("Assets are up to date.\n");
			return 0;
		}
	}
	// if here - need to update assets
	AAssetManager* am = androidApp->activity->assetManager;
	int buffSize = 1000000; //guess, should be enough?
	char* buff = new char[buffSize];

	std::vector<std::string> dirsToCheck; //list of assets folders to check
	dirsToCheck.push_back("dt"); //root folder
	while (dirsToCheck.size() > 0) {
		//open last element from directories vector
		std::string dirPath = dirsToCheck.back();
		dirsToCheck.pop_back(); //delete last element
		//mylog("Scanning directory <%s>\n", dirPath.c_str());
		//make sure folder exists on local drive
		std::string outPath = filesRoot + "/" + dirPath; // .c_str();
		struct stat info;
		int statRC = stat(outPath.c_str(), &info);
		if (statRC == 0)
			mylog("%s folder exists.\n", outPath.c_str());
		else {
			// mylog("Try to create %s\n", outPath.c_str());
			int status = mkdir(outPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
			if (status == 0)
				mylog("%s folder added.\n", outPath.c_str());
			else {
				mylog("ERROR creating, status=%d, errno: %s.\n", status, std::strerror(errno));
			}
		}
		//get folder's content
		std::vector<std::string> dirItems = list_assets(androidApp, dirPath.c_str());
		int itemsN = dirItems.size();
		//scan directory items
		for (int i = 0; i < itemsN; i++) {
			std::string itemPath = dirPath + "/" + dirItems.at(i).c_str();
			//mylog("New item: <%s> - ", itemPath.c_str());
			//try to open it to see if it's a file
			AAsset* asset = AAssetManager_open(am, itemPath.c_str(), AASSET_MODE_UNKNOWN);
			if (asset != NULL) {
				long size = AAsset_getLength(asset);
				//mylog("It's a file, size = %d - ", size);
				if (size > buffSize) {
					mylog("ERROR in main.cpp->updateAssets(): File %s is too big, skipped.\n", itemPath.c_str());
				}
				else {
					AAsset_read(asset, buff, size);
					outPath = filesRoot + "/" + itemPath;
					FILE* outFile = fopen(outPath.c_str(), "w+");
					if (outFile != NULL)
					{
						fwrite(buff, 1, size, outFile);
						fflush(outFile);
						fclose(outFile);
						mylog("%s saved\n", outPath.c_str());
					}
					else
						mylog("ERROR in main.cpp->updateAssets(): Can't create file %s\n", itemPath.c_str());
				}
				AAsset_close(asset);
			}
			else {
				dirsToCheck.push_back(itemPath);
				//mylog("It's a folder, add to folders list to check.\n");
			}
		}
		dirItems.clear();
	}
	delete[] buff;
	// save updateTime
	FILE* outFile = fopen(updateTimeFilePath.c_str(), "w+");
	if (outFile != NULL)
	{
		fwrite(&apkLastUpdateTime, 1, sizeof(apkLastUpdateTime), outFile);
		fflush(outFile);
		fclose(outFile);
	}
	else
		mylog("ERROR creating %s\n", updateTimeFilePath.c_str());
	return 1;
}
/**
* This is the main entry point of a native application that is using
* android_native_app_glue.  It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main(struct android_app* state) {

	//state->userData = &engine;
	state->onAppCmd = engine_handle_cmd;
	state->onInputEvent = engine_handle_input;
	androidApp = state;

	// Prepare to monitor accelerometer
	sensorManager = ASensorManager_getInstance();
	accelerometerSensor = ASensorManager_getDefaultSensor(sensorManager,
		ASENSOR_TYPE_ACCELEROMETER);
	sensorEventQueue = ASensorManager_createEventQueue(sensorManager,
		state->looper, LOOPER_ID_USER, NULL, NULL);

	// Read all pending events.
	int ident;
	int events;
	struct android_poll_source* source;
	//wait for display
	while (androidDisplay == NULL) {
		// No display yet.
		//std::this_thread::sleep_for(std::chrono::seconds(1));
		//mylog("No display yet\n");
		//wait for event
		while ((ident = ALooper_pollAll(0, NULL, &events,
			(void**)&source)) >= 0) {
			// Process this event.
			if (source != NULL) {
				source->process(state, source);
			}
		}
	}

	EGLint w, h;
	eglQuerySurface(androidDisplay, androidSurface, EGL_WIDTH, &w);
	eglQuerySurface(androidDisplay, androidSurface, EGL_HEIGHT, &h);
	theGame.onScreenResize(w, h);

	//retrieving files root
	filesRoot.assign(androidApp->activity->internalDataPath);
	mylog("filesRoot = %s\n", filesRoot.c_str());

	updateAssets();

	theGame.run();

	engine_term_display();
}

Just 1 missed line... (line 39).


17. Switch on, unlock, plug in, allow.

Build and run. Now - works.

VS top menu -> Debug -> Stop Debugging


Leave a Reply

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