Chapter 38. Hierarchical models

Now we are ready for more complex models, consisting of several elements with individual behavior. For example - car, where body will be a parent (root) element, and wheels - child elements. For both, body and wheel, we'll need separate model descriptors and separate classes, which will handle their behaviors.

1. In Windows File Explorer open our root (CPP) folder, make a copy of a997modeler and rename it (copy) to a996car


2. Under C:\CPP\a996car\dt\models add sub-folder

C:\CPP\a996car\dt\models\cars\01


For simplicity, in this example, our models (car body and wheels) will simply be square.

3. Copy following code to a Text Editor and save it to/as

C:\CPP\a996car\dt\models\cars\01\wheel01.txt

//wheel
<mt_type="phong" uColor="#0000ff" />
<vs="box" whl="4,20,20" />
<a="front,back,right,top,bottom" />
<mt_type="phong" uColor="#000000" /> //inner side
<a="left" />


Now root model - car body plus attached wheels.

4. Copy following code to a Text Editor and save it to/as

C:\CPP\a996car\dt\models\cars\01\root01.txt

//body
<mt_type="phong" uColor="#ff0000" />
<vs="box" whl="10,20,70" />
<a="front,back,left,right,top" py=5  />
//wheels
<element="wheel01.txt" class="CarWheel" pxyz="-10,0,30" ay=180 /> //front passenger
<element="wheel01.txt" class="CarWheel" pxyz="10,0,30" /> //front driver
<element="wheel01.txt" class="CarWheel" pxyz="-10,0,-20" ay=180 /> //rear passenger
<element="wheel01.txt" class="CarWheel" pxyz="10,0,-20" /> //rear driver
 

Now - SW part.

5. Start VS. Open C:\CPP\a996car\p_windows\p_windows.sln solution.


Hierarchical concept will require a few changes in GameSubj class:

6. Replace GameSubj.h code by:

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

class GameSubj
{
public:
	std::vector<GameSubj*>* pSubjsSet = NULL; //which vector/set this subj belongs to
	int nInSubjsSet = 0; //subj's number in pSubjsSet
	int rootN = 0; //model's root N
	int d2parent = 0; //shift to parent object
	int d2headTo = 0; //shift to headTo object
	int totalElements = 0; //elements N in the model
	int totalNativeElements = 0; //elements N in the model when initially loaded
	char source[256] = "";
	char className[32] = "";
	Coords absCoords;
	mat4x4 absModelMatrixUnscaled = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
	mat4x4 absModelMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };

	char name[32]="";
	Coords ownCoords;
	Coords ownSpeed;
	float scale[3] = { 1,1,1 };
	int djStartN = 0; //first DJ N in DJs array (DrawJob::drawJobs)
	int djTotalN = 0; //number of DJs
	mat4x4 ownModelMatrixUnscaled = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };

	Material* pAltMaterial = NULL;
public:
	virtual GameSubj* clone() { return new GameSubj(*this); };
	virtual ~GameSubj();
	void buildModelMatrix() { buildModelMatrix(this); };
	static void buildModelMatrix(GameSubj* pGS);
	virtual int moveSubj() { return applySpeeds(this); };
	static int applySpeeds(GameSubj* pGS);
};

Please note:

  • New virtual function clone().
  • A set of new parent/child variables

7. Replace GameSubj.cpp code by:

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

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

    if(pGS->d2parent == 0)
        memcpy(pGS->absModelMatrixUnscaled, pGS->ownModelMatrixUnscaled, sizeof(mat4x4));
    else {
        GameSubj* pParent = pGS->pSubjsSet->at(pGS->nInSubjsSet - pGS->d2parent);
        mat4x4_mul(pGS->absModelMatrixUnscaled, pParent->absModelMatrixUnscaled, pGS->ownModelMatrixUnscaled);
    }
    if (v3equals(pGS->scale, 1))
        memcpy(pGS->absModelMatrix, pGS->absModelMatrixUnscaled, sizeof(mat4x4));
    else
        mat4x4_scale_aniso(pGS->absModelMatrix, pGS->absModelMatrixUnscaled, pGS->scale[0], pGS->scale[1], pGS->scale[2]);

    //update absCoords
    if (pGS->d2parent == 0)
        memcpy(&pGS->absCoords, &pGS->ownCoords, sizeof(Coords));
    else {
        Coords::getPositionFromMatrix(pGS->absCoords.pos, pGS->absModelMatrixUnscaled);
    }
}
int GameSubj::applySpeeds(GameSubj* pGS) {
    bool angleChanged = false;
    for(int i=0;i<3;i++)
        if (pGS->ownSpeed.eulerDg[i] != 0) {
            angleChanged = true;
            pGS->ownCoords.eulerDg[i] += pGS->ownSpeed.eulerDg[i];
            if (pGS->ownCoords.eulerDg[i] > 180.0)
                pGS->ownCoords.eulerDg[i] -= 360.0;
            else if (pGS->ownCoords.eulerDg[i] <= -180.0)
                pGS->ownCoords.eulerDg[i] += 360.0;
        }
    if(angleChanged)
        Coords::eulerDgToMatrix(pGS->ownCoords.rotationMatrix, pGS->ownCoords.eulerDg);
    return 1;
}

Please note:

  • buildModelMatrix(..) now counts parent Coords too.
  • applySpeeds() is modified as well.

Coords class is seriously revised (simplified).

8. Replace Coords.h code by:

#pragma once
#include "linmath.h"

class Coords
{
public:
	float pos[4] = { 0,0,0,0 }; //x,y,z position + 4-th element for compatibility with 3D 4x4 matrices math
	float eulerDg[3] = { 0,0,0 }; //Euler angles (pitch, yaw, roll) in degrees
	mat4x4 rotationMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
public:
	static void getPositionFromMatrix(float* pos, mat4x4 m);
	static void eulerDgToMatrix(mat4x4 rotationMatrix, float* eulerDg);
	static void matrixToEulerDg(float* eulerDg, mat4x4 m);
};


9. Replace Coords.cpp code by:

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

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

void Coords::getPositionFromMatrix(float* pos, mat4x4 m) {
	//extracts position from matrix
	for (int i = 0; i < 3; i++)
		pos[i] = m[i][3];
}
void Coords::eulerDgToMatrix(mat4x4 rotationMatrix, float* eulerDg) {
	//builds rotation matrix from Euler angles (in degreed)
	mat4x4_identity(rotationMatrix);
	//rotation order: Z-X-Y
	float a = eulerDg[1];
	if (a != 0)
		mat4x4_rotate_Y(rotationMatrix, rotationMatrix, a * degrees2radians);
	a = eulerDg[0];
	if (a != 0)
		mat4x4_rotate_X(rotationMatrix, rotationMatrix, a * degrees2radians);
	a = eulerDg[2];
	if (a != 0)
		mat4x4_rotate_Z(rotationMatrix, rotationMatrix, a * degrees2radians);
}
void Coords::matrixToEulerDg(float* eulerDg, mat4x4 m) {
	//calculates Euler angles (in degrees) from matrix
	float yaw, pitch, roll; //in redians

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

	eulerDg[0] = pitch * radians2degrees;
	eulerDg[1] = yaw * radians2degrees;
	eulerDg[2] = roll * radians2degrees;
}


One of affected classes is Camera.

10. Replace Camera.cpp code by:

#include "Camera.h"
#include "TheGame.h"
#include "utils.h"

extern TheGame theGame;
extern float degrees2radians;

float Camera::pickDistance(Camera* pCam) {
	float cotangentA = 1.0f / tanf(degrees2radians * pCam->viewRangeDg / 2);
	float cameraDistanceV = pCam->stageSize[1] / 2 * cotangentA;
	float cameraDistanceH = pCam->stageSize[0] / 2 * cotangentA / theGame.screenAspectRatio;
	pCam->focusDistance = fmax(cameraDistanceV, cameraDistanceH);
	return pCam->focusDistance;
}
void Camera::setCameraPosition(Camera* pCam) {
	v3set(pCam->ownCoords.pos, 0, 0, -pCam->focusDistance);
	mat4x4_mul_vec4plus(pCam->ownCoords.pos, pCam->ownCoords.rotationMatrix, pCam->ownCoords.pos, 1);
	for (int i = 0; i < 3; i++)
		pCam->ownCoords.pos[i] += pCam->lookAtPoint[i];
}
void Camera::buildLookAtMatrix(Camera* pCam) {
	float cameraUp[4] = { 0,1,0,0 }; //y - up
	mat4x4_mul_vec4plus(cameraUp, pCam->ownCoords.rotationMatrix, cameraUp, 0);
	mat4x4_look_at(pCam->lookAtMatrix, pCam->ownCoords.pos, pCam->lookAtPoint, cameraUp);
}
void Camera::onScreenResize(Camera* pCam) {
	pickDistance(pCam);
	setCameraPosition(pCam);
	buildLookAtMatrix(pCam);
}


New class CarWheel will be part of the Project, not of the engine. Let's add it.

11. Under xTheGame filter add New Filter, name - car


12. Under xTheGame/car add new item - header file CarWheel.h

Make sure to change location to C:\CPP\a996car\car

Code:

#pragma once
#include "GameSubj.h"

class CarWheel : public GameSubj
{
public:
	float wheelDiameter = 20;
public:
	virtual GameSubj* clone() { return new CarWheel(*this); };
	virtual int moveSubj() { return moveSubj(this); };
	static int moveSubj(CarWheel* pGS);
};

Please note:

  • It inherits GameSubj class
  • It has own implementation of virtual clone() function, which returns new CarWheel object
  • and own implementation of virtual moveSubj() function

13. Under xTheGame/car add new C++ file CarWheel.cpp

Location: C:\CPP\a996car\car

Code:

#include "CarWheel.h"

int CarWheel::moveSubj(CarWheel* pGS) {
    float wheelRotationSpeedDg = 3;
    if (abs(pGS->ownCoords.eulerDg[1]) > 90)
        wheelRotationSpeedDg = -wheelRotationSpeedDg;
    pGS->ownSpeed.eulerDg[0] = wheelRotationSpeedDg;
    applySpeeds(pGS);
    return 1;
}

  • moveSubj() function simply rotates wheel with a constant radial speed around X axis.

14. Replace TheGame.cpp 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"
#include "car/CarWheel.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/cars/01/root01.txt", "");
    GameSubj* pGS = gameSubjs.at(subjN);
    pGS->ownSpeed.eulerDg[1] = 1;

    //===== set up camera
     v3set(mainCamera.ownCoords.eulerDg, 15, 180, 0); //set camera angles/orientation
    Coords::eulerDgToMatrix(mainCamera.ownCoords.rotationMatrix, mainCamera.ownCoords.eulerDg);

    mainCamera.viewRangeDg = 20;
    mainCamera.stageSize[0] = 90;
    mainCamera.stageSize[1] = 60;
    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);
    glDepthMask(GL_TRUE);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    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 - 55;
    float farClip = mainCamera.focusDistance + 55;
    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->absModelMatrix);
        //build Model-View (rotation) matrix for normals
        mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, pGS->absModelMatrixUnscaled);
        //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];
        //subj's distance from camera
        float cameraSpacePos[4];
        mat4x4_mul_vec4plus(cameraSpacePos, mainCamera.lookAtMatrix, pGS->absCoords.pos, 1);
        float zDistance = abs(cameraSpacePos[2]);
        float cotangentA = 1.0f / tanf(degrees2radians * mainCamera.viewRangeDg / 2.0f);
        float halfScreenVertSizeInUnits = zDistance / cotangentA;
        float sizeUnitPixelsSize = (float)screenSize[1] / 2.0f / halfScreenVertSizeInUnits;
        //render subject
        for (int i = 0; i < pGS->djTotalN; i++) {
            DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
            pDJ->execute((float*)mMVP, *mMV3x3, (float*)pGS->absModelMatrix, dirToMainLight, mainCamera.ownCoords.pos, sizeUnitPixelsSize, 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) {
    GameSubj* pGS = NULL;
    if (subjClass.compare("") == 0)
        pGS = (new GameSubj());
    else if (subjClass.find("Car") == 0) {
        if (subjClass.compare("CarWheel") == 0)
            pGS = (new CarWheel());
    }
    if(pGS == NULL) {
        mylog("ERROR in TheGame::newGameSubj. %s class not found\n", subjClass.c_str());
        return NULL;
    }
    myStrcpy_s(pGS->className, 32, subjClass.c_str());
    return pGS;
}

Please note:

  • New #include: car/CarWheel.h
  • Function newGameSubj(..) handles new classes

Now - reading/processing element tag:

15. Replace ModelLoader.h by:

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

class ModelLoader : public XMLparser
{
public:
	ModelBuilder* pModelBuilder = NULL;
	bool ownModelBuilder = false;
	std::vector<GameSubj*>* pSubjsVector = NULL;
	MaterialAdjust* pMaterialAdjust = NULL;
	int lineStartsAt = -1;
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(pModelBuilder,subjN);
	};
	virtual ~ModelLoader() {
		if (!ownModelBuilder)
			return;
		pModelBuilder->buildDrawJobs(pModelBuilder, 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);
	virtual 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);
	static int processTag_a2mesh(ModelLoader* pML);
	static int processTag_element(ModelLoader* pML);
};


16. Replace ModelLoader.cpp by:

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

extern TheGame theGame; 

int ModelLoader::loadModel(std::vector<GameSubj*>* pSubjsVector0, std::string sourceFile, std::string subjClass) {
	//returns element's (Subj) number or -1
	//first - check if already loaded
	int totalSubjs = pSubjsVector0->size();
	for (int subjN0 = totalSubjs - 1; subjN0 >= 0; subjN0--) {
		GameSubj* pGS0 = pSubjsVector0->at(subjN0);
		if (pGS0 == NULL)
			continue;
		if (strcmp(pGS0->source, sourceFile.c_str()) != 0)
			continue;
		//if here - model was already loaded - copy
		int subjN = pSubjsVector0->size();
		for (int i = 0; i < pGS0->totalNativeElements; i++) {
			GameSubj* pGS = pSubjsVector0->at(subjN0 + i)->clone();
			pGS->nInSubjsSet = pSubjsVector0->size();
			pSubjsVector0->push_back(pGS);
		}
		GameSubj* pGS = pSubjsVector0->at(subjN);
		pGS->totalElements = pGS->totalNativeElements;
		pGS->d2parent = 0;
		pGS->d2headTo = 0;
		//restore original 1st DrawJob
		return subjN;
	}
	//if here - model wasn't loaded before - load
	int subjN = pSubjsVector0->size();
	GameSubj* pGS = theGame.newGameSubj(subjClass);
	pGS->pSubjsSet = pSubjsVector0;
	pGS->nInSubjsSet = subjN;
	myStrcpy_s(pGS->source, 256, sourceFile.c_str());
	pSubjsVector0->push_back(pGS);
	ModelLoader* pML = new ModelLoader(pSubjsVector0, subjN, NULL, sourceFile);
	processSource(pML);
	delete pML;
	pGS->totalNativeElements = pSubjsVector0->size() - subjN;
	pGS->totalElements = pGS->totalNativeElements;
	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;
	bool resetTexture = false;
	std::string varName = txName + "_use";
	if (varExists(varName, pML->currentTag)) {
		if (setValueFromIntHashMap(pInt, pMB->texturesHashMap, varName, pML->currentTag) == 0) {
			mylog("ERROR in ModelLoader::setTexture: texture not in hashMap: %s\n", pML->currentTag.c_str());
			return -1;
		}
		resetTexture = true;
	}
	else{
		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);
			resetTexture = true;
		}
	}
	if(resetTexture)
		return 1;
	return 0; //texture wasn't reset
}
int ModelLoader::setMaterialTextures(ModelLoader* pML, Material* pMT) {
	if (setTexture(pML, &pMT->uTex0, "uTex0") > 0)
		pMT->uColor.clear();
	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);
		pMT->uTex0 = -1;
	}
	//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);
	setFloatValue(&pMT->uAlphaFactor, "uAlphaFactor", tagStr);
	if (pMT->uAlphaFactor < 1)
		pMT->uAlphaBlending = 1;
	setIntBoolValue(&pMT->uAlphaBlending, "uAlphaBlending", tagStr);
	if (pMT->uAlphaBlending > 0)
		pMT->zBufferUpdate = 0;
	setFloatValue(&pMT->uAmbient, "uAmbient", tagStr);
	setFloatValue(&pMT->uSpecularIntencity, "uSpecularIntencity", tagStr);
	setFloatValue(&pMT->uSpecularMinDot, "uSpecularMinDot", tagStr);
	setFloatValue(&pMT->uSpecularPowerOf, "uSpecularPowerOf", tagStr);

	setFloatValue(&pMT->lineWidth, "lineWidth", tagStr);
	setIntBoolValue(&pMT->zBuffer, "zBuffer", tagStr);
	if (pMT->zBuffer < 1)
		pMT->zBufferUpdate = 0;
	setIntBoolValue(&pMT->zBufferUpdate, "zBufferUpdate", tagStr);

	return 1;
}
int ModelLoader::processTag(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	if (pML->tagName.compare("element") == 0)
		return processTag_element(pML);
	if (pML->tagName.compare("/element") == 0) {
		//restore previous useSubjN from stack
		int subjN = pMB->usingSubjsStack.back();
		pMB->usingSubjsStack.pop_back();
		pMB->useSubjN(pMB, subjN);
		return 1;
	}
	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.compare("mt_type") == 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;
		fillProps_mt(&mt, pML->currentTag, pML);
		pMB->usingMaterialN = pMB->getMaterialN(pMB, &mt);
		return 1;
	}
	if (pML->tagName.compare("/mt_type") == 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);
	if (pML->tagName.compare("a2mesh") == 0)
		return processTag_a2mesh(pML);
	if (pML->tagName.compare("mt_adjust") == 0) {
		if (pML->pMaterialAdjust != NULL)
			mylog("ERROR in ModelLoader::processTag %s, pMaterialAdjust is still busy. File: %s\n", pML->currentTag.c_str(), pML->fullPath.c_str());
		pML->pMaterialAdjust = new (MaterialAdjust);
		fillProps_mt(pML->pMaterialAdjust, pML->currentTag, pML);
		pML->pMaterialAdjust->setWhat2adjust(pML->pMaterialAdjust, pML->currentTag);
		return 1;
	}
	if (pML->tagName.compare("/mt_adjust") == 0) {
		if (pML->pMaterialAdjust != NULL) {
			delete pML->pMaterialAdjust;
			pML->pMaterialAdjust = NULL;
		}
		return 1;
	}
	if (pML->tagName.compare("line") == 0) {
		Material mt;
		//save previous material in stack
		if (pMB->usingMaterialN >= 0){
			pMB->materialsStack.push_back(pMB->usingMaterialN);
			memcpy(&mt, pMB->materialsList.at(pMB->usingMaterialN),sizeof(Material));
		}
		mt.primitiveType = GL_LINE_STRIP;
		fillProps_mt(&mt, pML->currentTag, pML);
		pMB->usingMaterialN = pMB->getMaterialN(pMB, &mt);
		//line starts
		pML->lineStartsAt = pMB->vertices.size();
		return 1;
	}
	if (pML->tagName.compare("/line") == 0) {
		pMB->vertices.back()->endOfSequence = 1;
		pML->lineStartsAt = -1;
		//restore previous material
		if (pMB->materialsStack.size() > 0) {
			pMB->usingMaterialN = pMB->materialsStack.back();
			pMB->materialsStack.pop_back();
		}
		return 1;
	}
	if (pML->tagName.compare("p") == 0) {
		//line point
		Vertex01* pV = new Vertex01();
		if (pMB->vertices.size() > pML->lineStartsAt)
			memcpy(pV, pMB->vertices.back(), sizeof(Vertex01));
		pV->subjN = pMB->usingSubjN;
		pV->materialN = pMB->usingMaterialN;
		setFloatArray(pV->aPos, 3, "pxyz", pML->currentTag);
		setFloatValue(&pV->aPos[0], "px", pML->currentTag);
		setFloatValue(&pV->aPos[1], "py", pML->currentTag);
		setFloatValue(&pV->aPos[2], "pz", pML->currentTag);
		float dPos[3] = { 0,0,0 };
		setFloatArray(dPos, 3, "dxyz", pML->currentTag);
		setFloatValue(&dPos[0], "dx", pML->currentTag);
		setFloatValue(&dPos[1], "dy", pML->currentTag);
		setFloatValue(&dPos[2], "dz", pML->currentTag);
		if (!v3equals(dPos, 0))
			for (int i = 0; i < 3; i++)
				pV->aPos[i] += dPos[i];
		pMB->vertices.push_back(pV);
		return 1;
	}
	mylog("ERROR in ModelLoader::processTag, unhandled tag %s, file %s\n", pML->currentTag.c_str(), pML->fullPath.c_str());
	//mylog("======File:\n%s----------\n", pML->pData);
	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 };
	TexCoords* pTC = NULL;
	if (varExists("xywh", tagStr)) {
		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);
		pTC = &tc;
	}
	TexCoords* pTC2nm = NULL;
	if (varExists("xywh2nm", tagStr)) {
		setFloatArray(xywh, 4, "xywh2nm", tagStr);
		std::string flipStr = getStringValue("flip2nm", tagStr);
		TexCoords tc2nm;
		tc2nm.set(pMT->uTex2nm, xywh[0], xywh[1], xywh[2], xywh[3], flipStr);
		pTC2nm = &tc2nm;
	}
	//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), pVS_a, pTC, pTC2nm);
	}
	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);

	if (varExists("sizeD", tagStr)) { //re-size
		float sizeD[3];
		setFloatArray(sizeD, 3, "sizeD", tagStr);
		//bounding box
		pGT->flagSelection(pGT, &pMB->vertices, NULL);
		float bbMin[3];
		float bbMax[3];
		pGT->buildBoundingBoxFlagged(bbMin, bbMax, &pMB->vertices);
		for (int i = 0; i < 3; i++) {
			float size = bbMax[i] - bbMin[i];
			pGT->scale[i] = (size + sizeD[i]) / size;
		}
	}
	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;
}
int ModelLoader::processTag_a2mesh(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	std::string tagStr = pML->currentTag;
	GroupTransform gt;
	fillProps_gt(&gt, pMB, pML->currentTag);
	gt.flagSelection(&gt, &pMB->vertices, &pMB->triangles);
	//clone a copy
	std::vector<Vertex01*> vx1;
	std::vector<Triangle01*> tr1;
	gt.cloneFlagged(NULL, &vx1, &tr1, &pMB->vertices, &pMB->triangles);
	// build transform and inverted martrices
	mat4x4 transformMatrix;
	gt.buildTransformMatrix(&gt, &transformMatrix);
	mat4x4 transformMatrixInverted;
	mat4x4_invert(transformMatrixInverted, transformMatrix);
	//move/rotate cloned
	gt.flagAll(&vx1, &tr1);
	//gt.transformFlagged(&pMB->vertices, &transformMatrixInverted);
	gt.transformFlaggedMx(&vx1, &transformMatrixInverted);

	//gt.cloneFlagged(pMB, &pMB->vertices, &pMB->triangles, &vx1, &tr1);

	float wh[2];
	setFloatArray(wh, 2, "wh", tagStr);
	Polygon frame;
	frame.setRectangle(&frame, wh[0], wh[1]);
	//destination arrays
	std::vector<Vertex01*> vx2;
	std::vector<Triangle01*> tr2;
	Polygon triangle;
	for (int i = tr1.size() - 1; i >= 0; i--) {
		triangle.setTriangle(&triangle, tr1.at(i), &vx1);
		Polygon intersection;
		int pointsN = Polygon::xyIntersection(&intersection, &frame, &triangle);
		if (pointsN > 2) {
			Polygon::buildTriangles(&intersection);
			GroupTransform::flagAll(&intersection.vertices, &intersection.triangles);
			GroupTransform::cloneFlagged(NULL, &vx2, &tr2, &intersection.vertices, &intersection.triangles);
		}
	}
	gt.flagAll(&vx2, &tr2);
	//at this point we have cutted fragment facing us
	int vxTotal = vx2.size();
	int trTotal = tr2.size();
	//apply adjusted material ?
	if (pML->pMaterialAdjust != NULL) {
		//scan vertices to find new (unupdated) material
		int materialNsrc = -1; //which N to replace
		int materialNdst = -1; //replace by N 
		for (int vN = 0; vN < vxTotal; vN++) {
			Vertex01* pV = vx2.at(vN);
			if (pV->flag < 0)
				continue;
			if (materialNsrc == pV->materialN)
				continue;
			//have new material
			materialNsrc = pV->materialN;
			Material mt;
			Material* pMt0 = pMB->materialsList.at(materialNsrc);
			memcpy(&mt, pMt0, sizeof(Material));
			//modify material
			MaterialAdjust::adjust(&mt, pML->pMaterialAdjust);
			materialNdst = pMB->getMaterialN(pMB, &mt);
			if (materialNsrc != materialNdst) {
				//replace mtN in vx and tr arrays
				for (int vN2 = vN; vN2 < vxTotal; vN2++) {
					Vertex01* pV2 = vx2.at(vN2);
					if (pV2->flag < 0)
						continue;
					if (materialNsrc == pV2->materialN)
						pV2->materialN = materialNdst;
				}
				for (int tN2 = 0; tN2 < trTotal; tN2++) {
					Triangle01* pT2 = tr2.at(tN2);
					if (pT2->flag < 0)
						continue;
					if (materialNsrc == pT2->materialN)
						pT2->materialN = materialNdst;
				}
				materialNsrc = materialNdst;
			}
		}
	}
	else { // pML->pMaterialAdjust == NULL, use pMB->usingMaterialN
		for (int vN2 = 0; vN2 < vxTotal; vN2++) {
			Vertex01* pV2 = vx2.at(vN2);
			if (pV2->flag < 0)
				continue;
			pV2->materialN = pMB->usingMaterialN;
		}
		for (int tN2 = 0; tN2 < trTotal; tN2++) {
			Triangle01* pT2 = tr2.at(tN2);
			if (pT2->flag < 0)
				continue;
			pT2->materialN = pMB->usingMaterialN;
		}
	}
	//apply xywh/2nm ?
	if (varExists("xywh", tagStr) || varExists("xywh2nm", tagStr)) {
		Material* pMT = pMB->materialsList.at(vx2.at(0)->materialN);
		float xywh[4] = { 0,0,1,1 };
		TexCoords* pTC = NULL;
		if (varExists("xywh", tagStr)) {
			setFloatArray(xywh, 4, "xywh", tagStr);
			std::string flipStr = getStringValue("flip", tagStr);
			int texN = pMT->uTex1mask;
			if (texN < 0)
				texN = pMT->uTex0;
			TexCoords tc;
			tc.set(texN, xywh[0], xywh[1], xywh[2], xywh[3], flipStr);
			pTC = &tc;
		}
		TexCoords* pTC2nm = NULL;
		if (varExists("xywh2nm", tagStr)) {
			setFloatArray(xywh, 4, "xywh2nm", tagStr);
			std::string flipStr = getStringValue("flip2nm", tagStr);
			TexCoords tc2nm;
			tc2nm.set(pMT->uTex2nm, xywh[0], xywh[1], xywh[2], xywh[3], flipStr);
			pTC2nm = &tc2nm;
		}
		pMB->applyTexture2flagged(&vx2, "front", pTC, false);
		pMB->applyTexture2flagged(&vx2, "front", pTC2nm, true);
	}

	float detachBy =0;
	setFloatValue(&detachBy, "detachBy", tagStr);
	if (detachBy != 0) {
		mat4x4 mx;
		mat4x4_translate(mx, 0, 0, detachBy);
		gt.transformFlaggedMx(&vx2, &mx);
	}
	//move/rotate back
	gt.transformFlaggedMx(&vx2, &transformMatrix);
	//clone back to modelBuilder arrays
	gt.cloneFlagged(pMB, &pMB->vertices, &pMB->triangles, &vx2, &tr2);

	//clear memory
	for (int i = vx1.size() - 1; i >= 0; i--)
		delete vx1.at(i);
	vx1.clear();
	for (int i = tr1.size() - 1; i >= 0; i--)
		delete tr1.at(i);
	tr1.clear();
	for (int i = vx2.size() - 1; i >= 0; i--)
		delete vx2.at(i);
	vx2.clear();
	for (int i = tr2.size() - 1; i >= 0; i--)
		delete tr2.at(i);
	tr2.clear();

	return 1;
}
int ModelLoader::processTag_element(ModelLoader* pML) {
	ModelBuilder* pMB = pML->pModelBuilder;
	std::string tagStr = pML->currentTag;
	std::string sourceFile = getStringValue("element", tagStr);
	std::string subjClass = getStringValue("class", tagStr);
	std::vector<GameSubj*>* pSubjsVector0 = pML->pSubjsVector;
	int subjN = -1;
	if (!sourceFile.empty()) {
		sourceFile = buildFullPath(pML, sourceFile);
		subjN = loadModel(pSubjsVector0, sourceFile, subjClass);
	}
	else { //sourceFile not specified
		subjN = pML->pSubjsVector->size();
		GameSubj* pGS = theGame.newGameSubj(subjClass);
		pGS->pSubjsSet = pSubjsVector0;
		pGS->nInSubjsSet = subjN;
		pML->pSubjsVector->push_back(pGS);
		pGS->totalNativeElements = 1;
		pGS->totalElements = 1;
		if (!pML->closedTag) { //DrawJobs will follow
			pMB->usingSubjsStack.push_back(pMB->usingSubjN);
			pMB->useSubjN(pMB, subjN);
		}
	}
	//keep reading tag
	GameSubj* pGS = pSubjsVector0->at(subjN);
	int rootN = pMB->subjNumbersList.at(0);
	std::string attachTo = getStringValue("attachTo", tagStr);
	if (attachTo.empty()) //attach to root
		pGS->d2parent = subjN - rootN;
	else {
		//find parent by name
		for (int sN = subjN - 1; sN >= rootN; sN--) {
			if (strcmp(pSubjsVector0->at(sN)->name, attachTo.c_str()) == 0) {
				pGS->d2parent = subjN - sN;
				break;
			}
		}
	}
	std::string headTo = getStringValue("headTo", tagStr);
	if (!headTo.empty()) { //find headTo by name
		for (int sN = subjN - 1; sN >= rootN; sN--) {
			if (strcmp(pSubjsVector0->at(sN)->name, headTo.c_str()) == 0) {
				pGS->d2headTo = subjN - sN;
				break;
			}
		}
	}
	float xyz[3]={0,0,0};
	//position
	setFloatArray(xyz, 3, "pxyz", tagStr);
	setFloatValue(&xyz[0], "px", tagStr);
	setFloatValue(&xyz[1], "py", tagStr);
	setFloatValue(&xyz[2], "pz", tagStr);
	v3copy(pGS->ownCoords.pos, xyz);
	//angles
	v3set(xyz, 0,0,0);
	setFloatArray(xyz, 3, "axyz", tagStr);
	setFloatValue(&xyz[0], "ax", tagStr);
	setFloatValue(&xyz[1], "ay", tagStr);
	setFloatValue(&xyz[2], "az", tagStr);
	v3set(pGS->ownCoords.eulerDg, xyz[0], xyz[1], xyz[2]);
	return 1;
}

Please note:

  • When creating GameSubj, we assigning pGS->pSubjsSet = pSubjsVector0;
  • In ModelLoader::loadModel(): if such model was already loaded, we are making a copy, reusing it's DrawJobs.

For handling loading multiple models in ModelBuilder we have new vector/stack std::vector usingSubjsStack;

17. Replace ModelBuilder1base.h by:

#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<int> usingSubjsStack;

	std::vector<Group01*> groupsStack;
	Group01* pCurrentGroup = NULL;
	Group01* pLastClosedGroup = 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();
	static int useSubjN(ModelBuilder1base* pMB, int subjN);
	static int getMaterialN(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);
	static int buildDrawJobs(ModelBuilder1base* pMB, std::vector<GameSubj*>* pGameSubjs);
	static int rearrangeArraysForDrawJob(std::vector<Vertex01*>* pAllVertices, std::vector<Vertex01*>* pUseVertices, std::vector<Triangle01*>* pUseTriangles);
	static int buildSingleDrawJob(Material* pMT, std::vector<Vertex01*>* pVertices, std::vector<Triangle01*>* pTriangles);
	static int moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ);
	static int calculateTangentSpace(std::vector<Vertex01*>* pUseVertices, std::vector<Triangle01*>* pUseTriangles);
	static int finalizeLine(std::vector<Vertex01*>* pVerts, int lineStartsAt = 0, int lineEndsAt = 0);
	static int optimizeMesh(std::vector<Vertex01*>* pVertices, std::vector<Triangle01*>* pTriangles);
};


18. Build and run. Result:


Android

19. Restart VS. Open C:\CPP\a996car\p_android\p_android.sln


20. Under xTheGame filter add New Filter, name - car


21. Under xTheGame/car add existing item

from C:\CPP\a996car\car

  • CarWheel.cpp
  • CarWheel.h

Add


22. In order not to overwrite Marlboro APK on the device,

open AndroidManifest.xml (it's under p_android.Packaging project)

and change package name (line 4) to "com.OurProjectCar":

package="com.OurProjectCar"

and change android:label to "OurProjectCar":

android:label="OurProjectCar"

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

Build and run. Good.


Leave a Reply

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