Глава 31. Карты нормалей / Normal maps

Теперь хочу сделать герб и надпись “Marlboro” на лицевой стороне тиснеными. Для этого понадобятся Карты нормалей / Normal Maps. Это голубые изображения справа:

Чтобы их сгенерировать, я воспользовался сайтом NormalMap-Online.

  • Настройки по умолчанию – для DirectX.
  • Для OpenGL – отметьте Invert R
  • Кликните на левую картинку чтобы загрузить свою.

  • Эта картинка – уже в Вашем каталогне /dt, так что загружать ее не надо.

Логика шейдеров с картами нормалей отличается, она опирается на так называемый Tangent Space вместо обычного Eye Space, так что это будут 2 новых шейдера.

Vertex shader:

1. Копируем нижеприведенный код в Текстовый редактор и сохраняем to/as

C:\CPP\engine\dt\shaders\nm_v.txt

//#version 320 es
precision lowp float;
uniform mat4 uMVP; // transform matrix (Model-View-Projection)
uniform mat3 uMV3x3; // Model-View matrix (for calculating normals into eye space)

in vec3 aPos; // position attribute (x,y,z)
in vec3 aNormal; // normal attribute (x,y,z)

//normal map
in vec3 aTangent;
in vec3 aBinormal;
in vec2 aTuv2; //attribute TUV2 (texture coordinates)
out vec2 vTuv2; //varying TUV2 (pass to fragment shader)
uniform vec3 uVectorToLight;
uniform vec3 uHalfVector;
out vec3 tbnVectorToLight;
out vec3 tbnHalfVector;
#if defined(MIRROR)
	out vec2 vScreenPosition01;
	out mat3 inversedTBN;
#endif
#if defined(USE_TUV0)
	in vec2 aTuv; //attribute TUV (texture coordinates)
	out vec2 vTuv; //varying TUV (pass to fragment shader)
#endif

void main(void) { 
	gl_Position = uMVP * vec4(aPos, 1.0);
#if defined(USE_TUV0)
	vTuv = aTuv;
#endif

	vTuv2 = aTuv2;

	// Transform the normal's orientation into eye space.    
	vec3 N = uMV3x3 * aNormal;
	vec3 T = uMV3x3 * aTangent;
	vec3 B = uMV3x3 * aBinormal;
	//build TBN matrix
	mat3 TBN = mat3(
			T[0],B[0],N[0],
			T[1],B[1],N[1],
			T[2],B[2],N[2]
			);
	tbnVectorToLight = TBN * uVectorToLight;
	tbnHalfVector = TBN * uHalfVector;
#if defined(MIRROR)
	vScreenPosition01[0] =  (gl_Position[0]/gl_Position[3])*0.1;
	vScreenPosition01[1] = -(gl_Position[1]/gl_Position[3])*0.1;
	inversedTBN = inverse(TBN);
#endif
}


Fragment shader:

2. Копируем нижеприведенный код в Текстовый редактор и сохраняем to/as

C:\CPP\engine\dt\shaders\nm_f.txt

//#version 320 es
precision lowp float;
out vec4 FragColor; //output pixel color
uniform float uAlphaFactor; //for semi-transparency
uniform int uAlphaBlending; //for semi-transparency

in vec2 vTuv2;
uniform sampler2D uTex2nm;
in vec3 tbnVectorToLight;
in vec3 tbnHalfVector;

#if defined(USE_TEX0)
	uniform sampler2D uTex0;  //texture id
	uniform sampler2D uTex3;  //translate texture id
	uniform int uTex0translateChannelN;
#else
	uniform vec4 uColor;
#endif
#if defined(USE_TUV0)
	in vec2 vTuv; //varying TUV (passed from vertex shader)
#endif
#if defined(OVERMASK)
	uniform sampler2D uTex1mask;  //texture id
	uniform int uTex1alphaChannelN;
	uniform int uTex1alphaNegative;
#endif
#if defined(MIRROR)
	in vec2 vScreenPosition01;
	in mat3 inversedTBN;
#endif
uniform float uAmbient;
uniform float uSpecularIntencity;
uniform float uSpecularMinDot;
uniform float uSpecularPowerOf;

void main(void) {
	vec4 tbnNormal4 = texture(uTex2nm, vTuv2);
	float alpha = tbnNormal4.a;

	if(alpha < 0.5){
		if(uAlphaBlending > 0){
			if(alpha == 0.0){
				discard;
				return;
			}
		}
		else{ //no AlphaBlending
			discard;
			return;
		}
	}
	//black?
	if(tbnNormal4.b < 0.4){
		FragColor = vec4(0.0,0.0,0.0,alpha);
		return;
	}
	
	vec4 outColor;
#if defined(OVERMASK)
	outColor = texture(uTex1mask, vTuv);
	float alpha2 = outColor[uTex1alphaChannelN];
	if(uTex1alphaNegative > 0)
		alpha2 = 1.0 - alpha2;
	if(alpha2 < 1.0){
		alpha *= alpha2;
		if(alpha < 0.5){
			if(uAlphaBlending > 0){
				if(alpha == 0.0){
					discard;
					return;
				}
			}
			else{ //no AlphaBlending
				discard;
				return;
			}
		}
	}
#endif
	vec3 vNormalNormal = normalize(vec3(tbnNormal4) * 2.0 - 1.0);

#if defined(USE_TEX0)
	#if defined(MIRROR)
		vec3 inversedNormal = normalize(inversedTBN * vNormalNormal);
		vec2 vTuvMirror;
		vTuvMirror[0] =  (vScreenPosition01[0]+inversedNormal[0]*0.4)+0.5;
		vTuvMirror[1] = -(vScreenPosition01[1]+inversedNormal[1]*0.4)+0.5;
		outColor = texture(uTex0, vTuvMirror);
	#else
		outColor = texture(uTex0, vTuv);
	#endif
	if(uTex0translateChannelN >= 0){ //translate channel
		vec2 tuv3;
		tuv3[0] = outColor[uTex0translateChannelN];
		tuv3[1] = 0.0;
		outColor = texture(uTex3, tuv3);
	}
	FragColor = outColor;
#else
	FragColor = uColor;
#endif
	if(FragColor.a != 1.0){
		alpha *= FragColor.a;
		if(alpha < 0.5){
			if(uAlphaBlending > 0){
				if(alpha == 0.0){
					discard;
					return;
				}
			}
			else{ //no AlphaBlending
				discard;
				return;
			}
		}
	}

	if(uAmbient<1.0){
		 // Calculate the dot product of the light vector and vertex normal. If the normal and light vector are
		 // pointing in the same direction then it will get max illumination.
		 float directionalLightIntencity = dot(vNormalNormal, normalize(tbnVectorToLight));
		
		 // count ambient component
		 directionalLightIntencity += uAmbient;
		 if(directionalLightIntencity < uAmbient)
			directionalLightIntencity = uAmbient;

		 // Multiply the color by the lightIntencity illumination level to get final output color.
		 FragColor *= directionalLightIntencity;
	}
	if(uSpecularIntencity>0.0){
		//specular light
		// INTENSITY OF THE SPECULAR LIGHT
		// DOT PRODUCT OF NORMAL VECTOR AND THE HALF VECTOR TO THE POWER OF THE SPECULAR HARDNESS
		float dotProduct = dot(vNormalNormal, normalize(tbnHalfVector));

		if(dotProduct>uSpecularMinDot){
			float specularIntencity = pow(dotProduct, uSpecularPowerOf) * uSpecularIntencity;		
			if(specularIntencity > uSpecularIntencity)
				specularIntencity = uSpecularIntencity;
			FragColor += specularIntencity;
		}
	}
	if(uAlphaFactor != 1.0)
		alpha *= uAlphaFactor;	
	FragColor.a = alpha;
}


Обновленный root01.txt:

3. Копируем нижеприведенный код в Текстовый редактор и сохраняем (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,back v" xywh="2,1,323,495"/>
<a="right all" xywh="327,1,128,495"/>
<a="left all" xywh="457,1,128,495"/>
<a="top" xywh="588,1,323,133"/>
<a="bottom" xywh="587,136,324,134"/>
//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 />

Обратите внимание:

  • Новый материал для герба (строка 23): Вместо маски uTex1mask мы задаем Normal Map uTex2nm_use=”tx0″
  • В таге “a” для герба (строка 24) мы теперь используем НЕ xywh, а xywh2nm (тот же xywh, только для normal map)
  • Material для принта “Marlboro” (строка 26): Phong с uColor и с Normal Map

Теперь программная часть.

Windows

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


Теперь у нас новый набор шейдеров.

5. Заменим Shader.cpp код на:

#include "Shader.h"
#include "platform.h"
#include "utils.h"
#include "FileLoader.h"

extern std::string filesRoot;

//static array (vector) of all loaded shaders
std::vector<Shader*> Shader::shaders;

int Shader::loadShaders() {
    FileLoader* pFLvertex = new FileLoader("/dt/shaders/phong_v.txt");
    FileLoader* pFLfragment = new FileLoader("/dt/shaders/phong_f.txt");
    loadShadersGroup("flat", "FLAT; COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
    loadShadersGroup("phong", "PHONG; COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
    loadShadersGroup("mirror", "PHONG;MIRROR; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
    delete pFLvertex;
    delete pFLfragment;
    //Normal Maps
    pFLvertex = new FileLoader("/dt/shaders/nm_v.txt");
    pFLfragment = new FileLoader("/dt/shaders/nm_f.txt");
    loadShadersGroup("phong", "COLOR | TEXTURE; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
    loadShadersGroup("mirror", "MIRROR; NONE | OVERMASK", pFLvertex->pData, pFLfragment->pData);
    delete pFLvertex;
    delete pFLfragment;
    return 1;
}
int Shader::buildShaderObjectFromFiles(std::string filePathVertexS, std::string filePathFragmentS) {
    //create shader object
    Shader* pSh = new Shader();
    shaders.push_back(pSh);
    pSh->GLid = linkShaderProgramFromFiles((filesRoot + filePathVertexS).c_str(), (filesRoot + filePathFragmentS).c_str());
    //common variables. If not presented, = -1;
    fillLocations(pSh);

    return (shaders.size() - 1);
}
int Shader::fillLocations(Shader* pSh) {
    //common variables. If not presented, = -1;
    //attributes
    pSh->l_aPos = glGetAttribLocation(pSh->GLid, "aPos"); //attribute position (3D coordinates)
    pSh->l_aNormal = glGetAttribLocation(pSh->GLid, "aNormal"); //attribute normal (3D vector)
    pSh->l_aTangent = glGetAttribLocation(pSh->GLid, "aTangent"); //for normal map
    pSh->l_aBinormal = glGetAttribLocation(pSh->GLid, "aBinormal"); //for normal map
    pSh->l_aTuv = glGetAttribLocation(pSh->GLid, "aTuv"); //attribute TUV (texture coordinates)
    pSh->l_aTuv2 = glGetAttribLocation(pSh->GLid, "aTuv2"); //attribute TUV (texture coordinates)
    //uniforms
    pSh->l_uMVP = glGetUniformLocation(pSh->GLid, "uMVP"); // transform matrix (Model-View-Projection)
    pSh->l_uMV3x3 = glGetUniformLocation(pSh->GLid, "uMV3x3"); // Model-View matrix for normals
    pSh->l_uVectorToLight = glGetUniformLocation(pSh->GLid, "uVectorToLight"); // 
    pSh->l_uHalfVector = glGetUniformLocation(pSh->GLid, "uHalfVector"); // required for specular light
    //material's properties
    pSh->l_uColor = glGetUniformLocation(pSh->GLid, "uColor");
    pSh->l_uTex0 = glGetUniformLocation(pSh->GLid, "uTex0"); //texture id
    pSh->l_uTex1mask = glGetUniformLocation(pSh->GLid, "uTex1mask"); //texture id
    pSh->l_uTex2nm = glGetUniformLocation(pSh->GLid, "uTex2nm"); //texture id
    pSh->l_uTex3 = glGetUniformLocation(pSh->GLid, "uTex3"); //texture id
    pSh->l_uTex1alphaChannelN = glGetUniformLocation(pSh->GLid, "uTex1alphaChannelN");
    pSh->l_uTex1alphaNegative = glGetUniformLocation(pSh->GLid, "uTex1alphaNegative");
    pSh->l_uTex0translateChannelN = glGetUniformLocation(pSh->GLid, "uTex0translateChannelN");
    pSh->l_uAlphaFactor = glGetUniformLocation(pSh->GLid, "uAlphaFactor"); // for semi-transparency
    pSh->l_uAlphaBlending = glGetUniformLocation(pSh->GLid, "uAlphaBlending"); // for semi-transparency
    pSh->l_uAmbient = glGetUniformLocation(pSh->GLid, "uAmbient"); // ambient light
    pSh->l_uSpecularIntencity = glGetUniformLocation(pSh->GLid, "uSpecularIntencity"); // 
    pSh->l_uSpecularMinDot = glGetUniformLocation(pSh->GLid, "uSpecularMinDot"); // 
    pSh->l_uSpecularPowerOf = glGetUniformLocation(pSh->GLid, "uSpecularPowerOf"); // 
    return 1;
}
int Shader::cleanUp() {
    int shadersN = shaders.size();
    if (shadersN < 1)
        return -1;
    glUseProgram(0);
    for (int i = 0; i < shadersN; i++) {
        Shader* pSh = shaders.at(i);
        glDeleteProgram(pSh->GLid);
        delete pSh;
    }
    shaders.clear();
    return 1;
}

GLchar infoLog[1024];
int logLength;
int Shader::shaderErrorCheck(int shaderId, std::string ref) {
    //use after glCompileShader()
    if (checkGLerrors(ref) > 0)
        return -1;
    glGetShaderInfoLog(shaderId, 1024, &logLength, infoLog);
    if (logLength == 0)
        return 0;
    mylog("%s shader infoLog:\n%s\n", ref.c_str(), infoLog);
    return -1;
}
int Shader::programErrorCheck(int programId, std::string ref) {
    //use after glLinkProgram()
    if (checkGLerrors(ref) > 0)
        return -1;
    glGetProgramInfoLog(programId, 1024, &logLength, infoLog);
    if (logLength == 0)
        return 0;
    mylog("%s program infoLog:\n%s\n", ref.c_str(), infoLog);
    return -1;
}

int Shader::compileShaderFromFile(const char* filePath, GLenum shaderType) {
    int shaderId = glCreateShader(shaderType);
    FILE* pFile;
    myFopen_s(&pFile, filePath, "rt");
    if (pFile != NULL)
    {
        // obtain file size:
        fseek(pFile, 0, SEEK_END);
        int fSize = ftell(pFile);
        rewind(pFile);
        // size obtained, create buffer
        char* shaderSource = new char[fSize + 1];
        fSize = fread(shaderSource, 1, fSize, pFile);
        shaderSource[fSize] = 0;
        fclose(pFile);
        // source code loaded, compile
        glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
        //myglErrorCheck("glShaderSource");
        glCompileShader(shaderId);
        if (shaderErrorCheck(shaderId, "glCompileShader") < 0)
            return -1;
        delete[] shaderSource;
    }
    else {
        mylog("ERROR loading %s\n", filePath);
        return -1;
    }
    return shaderId;
}
int Shader::linkShaderProgramFromFiles(const char* filePathVertexS, const char* filePathFragmentS) {
    int vertexShaderId = compileShaderFromFile(filePathVertexS, GL_VERTEX_SHADER);
    int fragmentShaderId = compileShaderFromFile(filePathFragmentS, GL_FRAGMENT_SHADER);
    int programId = glCreateProgram();
    glAttachShader(programId, vertexShaderId);
    glAttachShader(programId, fragmentShaderId);
    glLinkProgram(programId);
    if (programErrorCheck(programId, "glLinkProgram") < 0)
        return -1;
    //don't need shaders any longer - detach and delete them
    glDetachShader(programId, vertexShaderId);
    glDetachShader(programId, fragmentShaderId);
    glDeleteShader(vertexShaderId);
    glDeleteShader(fragmentShaderId);
    return programId;
}

int Shader::buildShaderObjectWithDefines(std::string shaderType, std::string definesString, char* sourceVertex, char* sourceFragment) {
    //create shader object
    Shader* pSh = new Shader();
    shaders.push_back(pSh);
    myStrcpy_s(pSh->shaderType, 20, shaderType.c_str());

    pSh->GLid = linkShaderProgramWithDefines(definesString, sourceVertex, sourceFragment);
    //common variables. If not presented, = -1;
    fillLocations(pSh);

    return (shaders.size() - 1);
}
int Shader::linkShaderProgramWithDefines(std::string definesString00, char* sourceVertex, char* sourceFragment) {
    //build extended definesString
    bool bUSE_NORMALS = false;
    bool bUSE_TEX0 = false;
    bool bUSE_TUV0 = false;
    if (definesString00.find(" PHONG\n") != std::string::npos)
        bUSE_NORMALS = true;
    if (definesString00.find(" TEXTURE\n") != std::string::npos) {
        bUSE_TEX0 = true;
        bUSE_TUV0 = true;
    }
    if (definesString00.find(" MIRROR\n") != std::string::npos) {
        bUSE_NORMALS = true;
        bUSE_TEX0 = true;
    }
    if (definesString00.find(" OVERMASK\n") != std::string::npos) {
        bUSE_TUV0 = true;
    }
    std::string definesString;
    definesString.assign("#version 320 es\n");
    definesString.append(definesString00);
    if (bUSE_NORMALS)
        definesString.append("#define USE_NORMALS\n");
    if (bUSE_TEX0)
        definesString.append("#define USE_TEX0\n");
    if (bUSE_TUV0)
        definesString.append("#define USE_TUV0\n");

    int vertexShaderId = compileShaderWithDefines(definesString, sourceVertex, GL_VERTEX_SHADER);
    int fragmentShaderId = compileShaderWithDefines(definesString, sourceFragment, GL_FRAGMENT_SHADER);

    int programId = glCreateProgram();
    glAttachShader(programId, vertexShaderId);
    glAttachShader(programId, fragmentShaderId);
    glLinkProgram(programId);
    if (programErrorCheck(programId, "glLinkProgram") < 0)
        return -1;
    //don't need shaders any longer - detach and delete them
    glDetachShader(programId, vertexShaderId);
    glDetachShader(programId, fragmentShaderId);
    glDeleteShader(vertexShaderId);
    glDeleteShader(fragmentShaderId);
    //mylog("linking program\n%s\n", definesString.c_str());
    return programId;
}
int Shader::compileShaderWithDefines(std::string definesString, char* shaderSource, GLenum shaderType) {
    int shaderId = glCreateShader(shaderType);
    if (definesString.empty())
        glShaderSource(shaderId, 1, (const GLchar**)&shaderSource, NULL);
    else { //2 strings
        const char* sourceStrings[2];
        sourceStrings[0] = definesString.c_str();
        sourceStrings[1] = shaderSource;
        // source code loaded, compile
        glShaderSource(shaderId, 2, (const GLchar**)sourceStrings, NULL);
    }
    //myglErrorCheck("glShaderSource");
    glCompileShader(shaderId);
    if (shaderErrorCheck(shaderId, "glCompileShader") < 0) {
        mylog("ERROR in compileShader,\n%s\n%s\n", definesString.c_str(), shaderSource);
        return -1;
    }
    return shaderId;
}

int Shader::loadShadersGroup(std::string shaderType, std::string optionsString, char* sourceVertex, char* sourceFragment) {
    struct Terms {
        std::vector<std::string> terms;
        int totalN = 0;
        int currentN = 0;
    };
    std::vector<Terms*> terms;
    std::vector<std::string> termGroups = splitString(optionsString, ";");
    int groupsN = termGroups.size();
    for (int groupN = 0; groupN < groupsN; groupN++) {
        Terms* pTerms = new Terms();
        terms.push_back(pTerms);
        pTerms->terms = splitString(termGroups.at(groupN), "|");
        pTerms->totalN = pTerms->terms.size();
    }
    while (1) {
        std::string definesString = "";
        for (int groupN = 0; groupN < groupsN; groupN++) {
            Terms* pTerms = terms.at(groupN);
            std::string term = pTerms->terms.at(pTerms->currentN);
            if (term.compare("NONE") != 0) {
                definesString.append("#define ");
                definesString.append(term);
                definesString.append("\n");
            }
        }
        int shaderObjN = buildShaderObjectWithDefines(shaderType, definesString, sourceVertex, sourceFragment);
        //go to next terms combo
        bool noMoreOptions = false;
        for (int groupN = groupsN - 1; groupN >= 0; groupN--) {
            Terms* pTerms = terms.at(groupN);
            if (pTerms->currentN < pTerms->totalN - 1) {
                pTerms->currentN++;
                break;
            }
            else { // the level exhausted
                pTerms->currentN = 0;
                //proceed to upper level
                if (groupN == 0) {
                    noMoreOptions = true;
                    break;
                }
            }
        }
        if (noMoreOptions)
            break;
    }
    return 1;
}


В VBO для шейдеров с картами нормалей нужно расчитать Tangent Space для каждого вертекса.

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

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

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

	std::vector<Group01*> groupsStack;
	Group01* pCurrentGroup = NULL;
	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();
	int useSubjN(int subjN) { return useSubjN(this, subjN); };
	static int useSubjN(ModelBuilder1base* pMB, int subjN);
	int useMaterial(Material* pMT) { return useMaterial(this, pMT); };
	static int useMaterial(ModelBuilder1base* pMB, Material* pMT);
	static void lockGroup(ModelBuilder1base* pMB);
	static void releaseGroup(ModelBuilder1base* pMB);
	static int addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz);
	static int add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n);
	static int addTriangle(ModelBuilder1base* pMB, int n0, int n1, int n2);
	int buildDrawJobs(std::vector<GameSubj*> gameSubjs) { return buildDrawJobs(this, gameSubjs); };
	static int buildDrawJobs(ModelBuilder1base* pMB, std::vector<GameSubj*> gameSubjs);
	static int rearrangeArraysForDrawJob(ModelBuilder1base* pMB, std::vector<Vertex01*> allVertices, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles);
	static int buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles);
	static int moveGroupDg(ModelBuilder1base* pMB, float aX, float aY, float aZ, float kX, float kY, float kZ);
	static int calculateTangentSpace(std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles);
};


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

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

extern float degrees2radians;

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

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

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

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

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

	subjNumbersList.clear();
}
int ModelBuilder1base::useSubjN(ModelBuilder1base* pMB, int subjN) {
	pMB->usingSubjN = subjN;
	int itemsN = pMB->subjNumbersList.size();
	bool newN = true;
	if (itemsN > 0)
		for (int i = 0; i < itemsN; i++)
			if (pMB->subjNumbersList.at(i) == subjN) {
				newN = false;
				break;
			}
	if (newN)
		pMB->subjNumbersList.push_back(subjN);
	return subjN;
}
int ModelBuilder1base::useMaterial(ModelBuilder1base* pMB, Material* pMT) {
	int itemsN = pMB->materialsList.size();
	if (itemsN > 0)
		for (int i = 0; i < itemsN; i++)
			if (memcmp(pMB->materialsList.at(i), pMT, sizeof(Material)) == 0) {
				pMB->usingMaterialN = i;
				return i;
			}
	//if here - add new material to the list
	pMB->usingMaterialN = itemsN;
	//create a copy of new Material and add to the list
	Material* pMTnew = new Material(*pMT);
	pMB->materialsList.push_back(pMTnew);
	return itemsN;
}
int ModelBuilder1base::add2triangles(ModelBuilder1base* pMB, int nNW, int nNE, int nSW, int nSE, int n) {
	//indexes: NorthWest, NorthEast, SouthWest,SouthEast
	if (n % 2 == 0) { //even number
		addTriangle(pMB, nNW, nSW, nNE);
		addTriangle(pMB, nNE, nSW, nSE);
	}
	else { //odd number
		addTriangle(pMB, nNW, nSE, nNE);
		addTriangle(pMB, nNW, nSW, nSE);
	}
	return pMB->triangles.size() - 1;
}
int ModelBuilder1base::addTriangle(ModelBuilder1base* pMB, int i0, int i1, int i2) {
	Triangle01* pTR = new Triangle01();
	pMB->triangles.push_back(pTR);
	pTR->idx[0] = i0;
	pTR->idx[1] = i1;
	pTR->idx[2] = i2;
	pTR->subjN = pMB->usingSubjN;
	pTR->materialN = pMB->usingMaterialN;
	return pMB->triangles.size() - 1;
}
int ModelBuilder1base::addVertex(ModelBuilder1base* pMB, float kx, float ky, float kz, float nx, float ny, float nz) {
	Vertex01* pVX = new Vertex01();
	pMB->vertices.push_back(pVX);
	pVX->aPos[0] = kx;
	pVX->aPos[1] = ky;
	pVX->aPos[2] = kz;
	//normal
	pVX->aNormal[0] = nx;
	pVX->aNormal[1] = ny;
	pVX->aNormal[2] = nz;
	pVX->subjN = pMB->usingSubjN;
	pVX->materialN = pMB->usingMaterialN;
	return pMB->vertices.size() - 1;
}
int ModelBuilder1base::buildDrawJobs(ModelBuilder1base* pMB, std::vector<GameSubj*> gameSubjs) {
	int totalSubjsN = pMB->subjNumbersList.size();
	if (totalSubjsN < 1) {
		pMB->subjNumbersList.push_back(-1);
		totalSubjsN = 1;
	}
	int totalMaterialsN = pMB->materialsList.size();
	if (totalSubjsN < 2 && totalMaterialsN < 2) {
		//simple single DrawJob
		Material* pMT = pMB->materialsList.at(0);
		GameSubj* pGS = NULL;
		int gsN = pMB->subjNumbersList.at(0);
		if (gsN >= 0)
			pGS = gameSubjs.at(gsN);
		if (pGS != NULL)
			pGS->djStartN = DrawJob::drawJobs.size();
		buildSingleDrawJob(pMT, pMB->vertices, pMB->triangles);
		if (pGS != NULL)
			pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
		return 1;
	}
	int totalVertsN = pMB->vertices.size();
	int totalTrianglesN = pMB->triangles.size();
	//clear flags
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pVX = pMB->vertices.at(vN);
		pVX->flag = 0;
	}
	for (int tN = 0; tN < totalTrianglesN; tN++) {
		Triangle01* pTR = pMB->triangles.at(tN);
		pTR->flag = 0;
	}
	int addedDJs = 0;
	for (int sN = 0; sN < totalSubjsN; sN++) {
		GameSubj* pGS = NULL;
		int gsN = pMB->subjNumbersList.at(sN);
		if (gsN >= 0)
			pGS = gameSubjs.at(gsN);
		if (pGS != NULL)
			pGS->djStartN = DrawJob::drawJobs.size();
		for (int mtN = 0; mtN < totalMaterialsN; mtN++) {
			Material* pMT = pMB->materialsList.at(mtN);
			std::vector<Vertex01*> useVertices;
			std::vector<Triangle01*> useTriangles;
			for (int vN = 0; vN < totalVertsN; vN++) {
				Vertex01* pVX = pMB->vertices.at(vN);
				if (pVX->flag != 0)
					continue;
				if (pVX->subjN != gsN)
					continue;
				if (pVX->materialN != mtN)
					continue;
				//if here - make a copy
				Vertex01* pVX2 = new Vertex01(*pVX);
				useVertices.push_back(pVX2);
				pVX2->altN = vN;
				pVX->flag = 1;
				if (pVX->endOfSequence > 0) {
					rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
					buildSingleDrawJob(pMT, useVertices, useTriangles);
					addedDJs++;
					//clear and proceed to next sequence
					int useVerticesN = useVertices.size();
					for (int i = 0; i < useVerticesN; i++)
						delete useVertices.at(i);
					useVertices.clear();
				}
			}
			int useVerticesN = useVertices.size();
			if (useVerticesN < 1)
				continue; //to next material
			//pick triangles
			for (int tN = 0; tN < totalTrianglesN; tN++) {
				Triangle01* pTR = pMB->triangles.at(tN);
				if (pTR->flag != 0)
					continue;
				if (pTR->subjN != gsN)
					continue;
				if (pTR->materialN != mtN)
					continue;
				//if here - make a copy
				Triangle01* pTR2 = new Triangle01(*pTR);
				useTriangles.push_back(pTR2);
				pTR->flag = 1;
			}
			rearrangeArraysForDrawJob(pMB, pMB->vertices, useVertices, useTriangles);
			buildSingleDrawJob(pMT, useVertices, useTriangles);
			addedDJs++;
			//clear all for next material
			for (int i = 0; i < useVerticesN; i++)
				delete useVertices.at(i);
			useVertices.clear();
			int useTrianglesN = useTriangles.size();
			for (int i = 0; i < useTrianglesN; i++)
				delete useTriangles.at(i);
			useTriangles.clear();
		}
		if (pGS != NULL)
			pGS->djTotalN = DrawJob::drawJobs.size() - pGS->djStartN;
	}
	return addedDJs;
}

int ModelBuilder1base::buildSingleDrawJob(Material* pMT, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalVertsN = useVertices.size();
	if (totalVertsN < 1)
		return 0;
	if (pMT->uTex2nm >= 0)
		calculateTangentSpace(useVertices, useTriangles);
	pMT->pickShaderNumber();
	DrawJob* pDJ = new DrawJob();
	//copy material to DJ
	memcpy(&pDJ->mt, pMT, sizeof(Material));
	//calculate VBO element size (stride) and variables offsets in VBO
	int VBOid = DrawJob::newBufferId();
	int stride = 0;
	pDJ->setDesirableOffsets(&stride, pDJ->mt.shaderN, VBOid);
	//create an array for VBO
	int bufferSize = totalVertsN * stride;
	float* vertsBuffer = new float[bufferSize];
	//fill vertsBuffer
	Shader* pSh = Shader::shaders.at(pDJ->mt.shaderN);
	int floatSize = sizeof(float);
	for (int vN = 0; vN < totalVertsN; vN++) {
		Vertex01* pVX = useVertices.at(vN);
		int idx = vN * stride / floatSize;
		//pick data from vertex and move to the buffer
		memcpy(&vertsBuffer[idx + pDJ->aPos.offset / floatSize], pVX->aPos, 3 * floatSize);
		if (pSh->l_aNormal >= 0) //normal
			memcpy(&vertsBuffer[idx + pDJ->aNormal.offset / floatSize], pVX->aNormal, 3 * floatSize);
		if (pSh->l_aTuv >= 0) //attribute TUV (texture coordinates)
			memcpy(&vertsBuffer[idx + pDJ->aTuv.offset / floatSize], pVX->aTuv, 2 * floatSize);
		if (pSh->l_aTuv2 >= 0) //attribute TUV2 (normal maps)
			memcpy(&vertsBuffer[idx + pDJ->aTuv2.offset / floatSize], pVX->aTuv2, 2 * floatSize);
		if (pSh->l_aTangent >= 0)
			memcpy(&vertsBuffer[idx + pDJ->aTangent.offset / floatSize], pVX->aTangent, 3 * floatSize);
		if (pSh->l_aBinormal >= 0)
			memcpy(&vertsBuffer[idx + pDJ->aBinormal.offset / floatSize], pVX->aBinormal, 3 * floatSize);
	}
	//buffer is ready, create VBO
	glBindBuffer(GL_ARRAY_BUFFER, VBOid);
	glBufferData(GL_ARRAY_BUFFER, bufferSize * floatSize, vertsBuffer, GL_STATIC_DRAW);
	delete[] vertsBuffer;
	pDJ->pointsN = totalVertsN;

	int totalTrianglesN = useTriangles.size();
	if (totalTrianglesN > 0) {
		//create EBO
		int totalIndexesN = totalTrianglesN * 3;
		//create buffer
		GLushort* indexBuffer = new GLushort[totalIndexesN];
		for (int tN = 0; tN < totalTrianglesN; tN++) {
			Triangle01* pTR = useTriangles[tN];
			int idx = tN * 3;
			indexBuffer[idx] = (GLushort)pTR->idx[0];
			indexBuffer[idx + 1] = (GLushort)pTR->idx[1];
			indexBuffer[idx + 2] = (GLushort)pTR->idx[2];
		}
		//buffer is ready, create IBO
		pDJ->glEBOid = DrawJob::newBufferId();
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pDJ->glEBOid);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, totalIndexesN * sizeof(GLushort), indexBuffer, GL_STATIC_DRAW);
		delete[] indexBuffer;
		pDJ->pointsN = totalIndexesN;
	}
	//create and fill vertex attributes array (VAO)
	pDJ->buildVAO();
	return 1;
}

int ModelBuilder1base::rearrangeArraysForDrawJob(ModelBuilder1base* pMB, std::vector<Vertex01*> allVertices, std::vector<Vertex01*> useVertices, std::vector<Triangle01*> useTriangles) {
	int totalTrianglesN = useTriangles.size();
	if (totalTrianglesN < 1)
		return 0;
	int totalVerticesN = useVertices.size();
	//save new vertices order in original vertices array
	//since triangles indices refer to original vertices order
	for (int i = 0; i < totalVerticesN; i++) {
		Vertex01* pVX1 = useVertices.at(i);
		Vertex01* pVX0 = allVertices.at(pVX1->altN);
		pVX0->altN = i;
	}
	//replace triangle original indices by new numbers saved in original vertices altN
	for (int tN = 0; tN < totalTrianglesN; tN++) {
		Triangle01* pTR = useTriangles.at(tN);
		for (int i = 0; i < 3; i++) {
			Vertex01* pVX0 = allVertices.at(pTR->idx[i]);
			pTR->idx[i] = pVX0->altN;
		}
	}
	return 1;
}

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

	int vertsN = pMB->vertices.size();
	for (int i = pMB->pCurrentGroup->fromVertexN; i < vertsN; i++) {
		Vertex01* pVX = pMB->vertices.at(i);
		mat4x4_mul_vec4plus(pVX->aPos, transformMatrix, pVX->aPos, 1);
		mat4x4_mul_vec4plus(pVX->aNormal, transformMatrix, pVX->aNormal, 0);
	}
	return 1;
}

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

  • В функции buildSingleDrawJob(..), если normal map задана (pMT->uTex2nm >= 0), тогда вызываем calculateTangentSpace(..)

Специально для calculateTangentSpace(..) добавлены новые функции v3inverse(..) и v3dotProduct(..)

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

#pragma once
#include <string>
#include <vector>
#include "linmath.h"

int checkGLerrors(std::string ref);
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3);

void v3set(float* vOut, float x, float y, float z);
void v3copy(float* vOut, float* vIn);
float v3pitchRd(float* vIn);
float v3yawRd(float* vIn);
float v3pitchDg(float* vIn);
float v3yawDg(float* vIn);

float v3dotProduct(float* a0, float* b0);
float v3dotProductNorm(float* a, float* b);
void v3inverse(float inV[]);
void v3inverse(float outV[], float inV[]);
float v3length(float* v);
float v3lengthXZ(float v[]);
float v3lengthXY(float v[]);
bool v3equals(float v[], float x);
bool v3match(float v0[], float v1[]);
void v3fromTo(float* v, float* v0, float* v1);
float v3lengthFromTo(float* v0, float* v1);
void v3dirFromTo(float* v, float* v0, float* v1);

long long int getSystemMillis();
long long int getSystemNanos();

int getRandom(int fromN, int toN);
float getRandom(float fromN, float toN);
std::vector<std::string> splitString(std::string inString, std::string delimiter);
std::string trimString(std::string inString);
bool fileExists(const char* filePath);
std::string getFullPath(std::string filePath);
std::string getInAppPath(std::string filePath);
int makeDirs(std::string filePath);

  • Также добавлено еще несколько векторных функций на будущее

9. Заменим utils.cpp код на:

#include "utils.h"
#include "platform.h"
#include <chrono>
#include <stdlib.h>     /* srand, rand */
#include <sys/stat.h> //if fileExists
#include <time.h> //for srand()

extern std::string filesRoot;
extern float radians2degrees;

int checkGLerrors(std::string ref) {
    //can be used after any GL call
    int res = glGetError();
    if (res == 0)
        return 0;
    std::string errCode;
    switch (res) {
        //case GL_NO_ERROR: errCode = "GL_NO_ERROR"; break;
        case GL_INVALID_ENUM: errCode = "GL_INVALID_ENUM"; break;
        case GL_INVALID_VALUE: errCode = "GL_INVALID_VALUE"; break;
        case GL_INVALID_OPERATION: errCode = "GL_INVALID_OPERATION"; break;
        case GL_INVALID_FRAMEBUFFER_OPERATION: errCode = "GL_INVALID_FRAMEBUFFER_OPERATION"; break;
        case GL_OUT_OF_MEMORY: errCode = "GL_OUT_OF_MEMORY"; break;
        default: errCode = "??"; break;
    }
    mylog("GL ERROR %d-%s in %s\n", res, errCode.c_str(), ref.c_str());
    return -1;
}
void mat4x4_mul_vec4plus(vec4 vOut, mat4x4 M, vec4 vIn, int v3) {
    vec4 v2;
    if (vOut == vIn) {
        memcpy(&v2, vIn, sizeof(vec4));
        vIn = v2;
    }
    vIn[3] = (float)v3;
    mat4x4_mul_vec4(vOut, M, vIn);
}
void v3set(float* vOut, float x, float y, float z) {
    vOut[0] = x;
    vOut[1] = y;
    vOut[2] = z;
}
void v3copy(float* vOut, float* vIn) {
    for (int i = 0; i < 3; i++)
        vOut[i] = vIn[i];
}
float v3yawRd(float* vIn) {
    return atan2f(vIn[0], vIn[2]);
}
float v3pitchRd(float* vIn) {
    return -atan2f(vIn[1], sqrtf(vIn[0] * vIn[0] + vIn[2] * vIn[2]));
}
float v3pitchDg(float* vIn) { 
    return v3pitchRd(vIn) * radians2degrees; 
}
float v3yawDg(float* vIn) { 
    return v3yawRd(vIn) * radians2degrees; 
}

long long int getSystemMillis() {
    auto currentTime = std::chrono::system_clock::now().time_since_epoch();
    return std::chrono::duration_cast<std::chrono::milliseconds>(currentTime).count();
}
long long int getSystemNanos() {
    auto currentTime = std::chrono::system_clock::now().time_since_epoch();
    return std::chrono::duration_cast<std::chrono::nanoseconds>(currentTime).count();
}
int randomCallN = 0;
int getRandom() {
    if (randomCallN % 1000 == 0)
        srand((unsigned int)getSystemNanos()); //re-initialize random seed:
    randomCallN++;
    return rand();
}
int getRandom(int fromN, int toN) {
    int randomN = getRandom();
    int range = toN - fromN + 1;
    return (fromN + randomN % range);
}
float getRandom(float fromN, float toN) {
    int randomN = getRandom();
    float range = toN - fromN;
    return (fromN + (float)randomN / RAND_MAX * range);
}
std::vector<std::string> splitString(std::string inString, std::string delimiter) {
    std::vector<std::string> outStrings;
    int delimiterSize = delimiter.size();
    outStrings.clear();
    while (inString.size() > 0) {
        int delimiterPosition = inString.find(delimiter);
        if (delimiterPosition == 0) {
            inString = inString.substr(delimiterSize, inString.size() - delimiterSize);
            continue;
        }
        if (delimiterPosition == std::string::npos) {
            //last element
            outStrings.push_back(trimString(inString));
            break;
        }
        std::string outString = inString.substr(0, delimiterPosition);
        outStrings.push_back(trimString(outString));
        int startAt = delimiterPosition + delimiterSize;
        inString = inString.substr(startAt, inString.size() - startAt);
    }
    return outStrings;
}
std::string trimString(std::string inString) {
    //Remove leading and trailing spaces
    int startsAt = inString.find_first_not_of(" ");
    if (startsAt == std::string::npos)
        return "";
    int endsAt = inString.find_last_not_of(" ") + 1;
    return inString.substr(startsAt, endsAt - startsAt);
}
bool fileExists(const char* filePath) {
    struct stat info;
    if (stat(filePath, &info) == 0)
        return true;
    else
        return false;
}
std::string getFullPath(std::string filePath) {
    if (filePath.find(filesRoot) == 0)
        return filePath;
    else
        return (filesRoot + filePath);
}
std::string getInAppPath(std::string filePath) {
    std::string inAppPath(filePath);
    if (inAppPath.find(filesRoot) == 0) {
        int startsAt = filesRoot.size();
        inAppPath = inAppPath.substr(startsAt, inAppPath.size() - startsAt);
    }
    if (inAppPath.find(".") != std::string::npos) {
        //cut off file name
        int endsAt = inAppPath.find_last_of("/");
        inAppPath = inAppPath.substr(0, endsAt + 1);
    }
    return inAppPath;
}
int makeDirs(std::string filePath) {
    filePath = getFullPath(filePath);
    std::string inAppPath = getInAppPath(filePath);
    std::vector<std::string> path = splitString(inAppPath, "/");
    int pathSize = path.size();
    filePath.assign(filesRoot);
    for (int i = 0; i < pathSize; i++) {
        filePath.append("/" + path.at(i));
        if (fileExists(filePath.c_str())) {
            continue;
        }
        //create dir
        myMkDir(filePath.c_str());
        mylog("Folder %d: %s created.\n", i, filePath.c_str());
    }
    return 1;
}
void v3inverse(float inV[]) {
    return v3inverse(inV, inV);
}
void v3inverse(float outV[], float inV[]) {
    for (int i = 0; i < 3; i++)
        outV[i] = -inV[i];
    return;
}

float v3dotProduct(float* a0, float* b0) {
    float a[3];
    float b[3];
    vec3_norm(a, a0);
    vec3_norm(b, b0);
    return v3dotProductNorm(a, b);
}
float v3dotProductNorm(float* a, float* b) {
    //assuming that vectors are normalized
    float dp = 0.0f;
    for (int i = 0; i < 3; i++)
        dp += a[i] * b[i];
    return dp;
}
float v3length(float* v) {
    float r = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
    if (r == 0)
        return 0;
    return sqrtf(r);
}
float v3lengthXZ(float v[]) {
    float r = v[0] * v[0] + v[2] * v[2];
    if (r == 0)
        return 0;
    return sqrtf(r);
}
float v3lengthXY(float v[]) {
    float r = v[0] * v[0] + v[1] * v[1];
    if (r == 0)
        return 0;
    return sqrtf(r);
}
bool v3equals(float v[],float x) {
    for (int i = 0; i < 3; i++)
        if (v[i] != x)
            return false;
    return true;
}
bool v3match(float v0[], float v1[]) {
    for (int i = 0; i < 3; i++)
        if (v0[i] != v1[i])
            return false;
    return true;
}
void v3fromTo(float* v, float* v0, float* v1) {
    for (int i = 0; i < 3; i++)
        v[i] = v1[i] - v0[i];
}
float v3lengthFromTo(float* v0, float* v1) {
    float v[3];
    v3fromTo(v, v0, v1);
    float r = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
    if (r == 0)
        return 0;
    return sqrtf(r);
}
void v3dirFromTo(float* v, float* v0, float* v1) {
    v3fromTo(v, v0, v1);
    vec3_norm(v,v);
}


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

До:

После:

Теперь – с тиснением.


На Андроиде тоже проверено – работает.


Leave a Reply

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