Теперь хочу сделать герб и надпись “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. Компиляция и запуск.
До:
После:
Теперь – с тиснением.
На Андроиде тоже проверено – работает.