Наша нынешняя квадратная модель не позволяет оценить всю прелесть Phong шейдера, в частности – блики. Для этого лучше подошло бы что-то более округлое. Зачит, давайте соответственно расширим Modeler.
Windows
1. Запускаем VS. Открываем C:\CPP\a997modeler\p_windows\p_windows.sln.
Во-первых – надо расширить VirtualShape.h.
2. Заменим VirtualShape.h код на:
#pragma once
#include "platform.h"
class VirtualShape
{
public:
char shapeType[20] = "box";
float whl[3] = { 0 }; //width/height/length (x,y,z sizes/dimensions)
int sections[3] = { 1,1,1 }; //number of sections for each axis
int sectionsR = 1; //number of radial sections
//side extensions
float extU = 0; //up
float extD = 0; //down
float extL = 0; //left
float extR = 0; //right
float extF = 0; //front
float extB = 0; //back
public:
void setShapeType(std::string needType) { setShapeType(this, needType); };
static void setShapeType(VirtualShape* pVS, std::string needType) { myStrcpy_s(pVS->shapeType, 20, (char*)needType.c_str()); };
void setExt(float v) { setExt(this, v); };
void setExtX(float v) { setExtX(this, v); };
void setExtY(float v) { setExtY(this, v); };
void setExtZ(float v) { setExtZ(this, v); };
static void setExt(VirtualShape* pVS, float v) { pVS->extU = v; pVS->extD = v; pVS->extL = v; pVS->extR = v; pVS->extF = v; pVS->extB = v; };
static void setExtX(VirtualShape* pVS, float v) { pVS->extL = v; pVS->extR = v; };
static void setExtY(VirtualShape* pVS, float v) { pVS->extU = v; pVS->extD = v; };
static void setExtZ(VirtualShape* pVS, float v) { pVS->extF = v; pVS->extB = v; };
};
Новая концепция тут – “extensions” (расширения). Они нужны для форм с закругленными углами и ребрами:
Другое изменение здесь – char* shapeType вместо std::string.
Также появилась новая platform-specific функция – myStrcpy_s(), копирование массива char. Причина та же что и с myFopen_s(). Другая platform-specific функция, которая нам скоро понадобится – это myMkDir(), создать каталог.
3. Заменим platform.h код на:
#pragma once
#include <glad/glad.h>
#include <stdio.h>
typedef unsigned _int64 myUint64;
typedef unsigned _int32 myUint32;
typedef unsigned _int16 myUint16;
typedef unsigned _int8 myUint8;
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
int myMkDir(const char* outPath);
void myStrcpy_s(char* dst, int maxSize, const char* src);
4. Заменим platform.cpp код на:
#include <stdarg.h>
#include <stdio.h>
#include <GLFW/glfw3.h>
#include "platform.h"
#include "TheGame.h"
#include <direct.h> //myMkDir: mkdir
#include <sys/stat.h> //myMkDir: if file esists
extern GLFWwindow* myMainWindow;
extern TheGame theGame;
void mylog(const char* _Format, ...) {
#ifdef _DEBUG
va_list _ArgList;
va_start(_ArgList, _Format);
vprintf(_Format, _ArgList);
va_end(_ArgList);
#endif
};
void mySwapBuffers() {
glfwSwapBuffers(myMainWindow);
}
void myPollEvents() {
glfwPollEvents();
//check if closing the window
theGame.bExitGame = glfwWindowShouldClose(myMainWindow);
//check screen size
int width, height;
glfwGetFramebufferSize(myMainWindow, &width, &height);
theGame.onScreenResize(width, height);
}
int myFopen_s(FILE** pFile, const char* filePath, const char* mode) {
return fopen_s(pFile, filePath, mode);
}
int myMkDir(const char* outPath) {
struct stat info;
if (stat(outPath, &info) == 0)
return 0; //exists already
int status = _mkdir(outPath);
if (status == 0)
return 1; //Successfully created
mylog("ERROR creating, status=%d\n", status);
return -1;
}
void myStrcpy_s(char* dst, int maxSize, const char* src) {
strcpy_s(dst, maxSize, src);
//fill tail by zeros
int strLen = strlen(dst);
if (strLen < maxSize)
for (int i = strLen; i < maxSize; i++)
dst[i] = 0;
}
Класс ModelBuilder становится настораживающе большим. Лучше разобьем его на 2 уровня, первый уровень – базовый функционал, назовем его ModelBuilder1base.
Только 1 новая функция – moveGroupDg(…) – которая будет разворачивать и сдвигать группу вертексов (чтобы ставить “расширения” на их окончательные позиции)
5. Под modeler добавим новый Header File (.h) ModelBuilder1base.h
Location: C:\CPP\engine\modeler\
Код:
#pragma once
#include <string>
#include <vector>
#include "Vertex01.h"
#include "Triangle01.h"
#include "VirtualShape.h"
#include "Group01.h"
#include "Material.h"
#include "GameSubj.h"
class ModelBuilder1base
{
public:
std::vector<Vertex01*> vertices;
std::vector<Triangle01*> triangles;
std::vector<VirtualShape*> vShapesStack;
std::vector<Group01*> groupsStack;
std::vector<Material*> materialsList;
std::vector<int> subjNumbersList;
int usingSubjN = -1;
int usingMaterialN = -1;
Group01* pCurrentGroup = NULL;
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);
};
6. Под modeler добавим новый C++ File (.cpp) ModelBuilder1base.cpp
Location: C:\CPP\engine\modeler\
Код:
#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;
}
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) {
delete pMB->pCurrentGroup;
if (pMB->groupsStack.size() > 0) {
pMB->pCurrentGroup = pMB->groupsStack.back();
pMB->groupsStack.pop_back();
}
else
pMB->pCurrentGroup = NULL;
}
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 1;
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 + 0] = (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;
}
Изменения в классе ModelBuilder:
a) Теперь он наследут ModelBuilder1base.
b) Новые функции ModelBuilder-а:
- buildFace(…) – выбирает чем генерировать поверхность в зависимости от типа VirtualShape
- buildBoxFace(…) – расширена для приема/обработки “расширений”
- boxFacePlain(…) – рисует простую прямоугольную проекцию
- boxFaceTank(…) – рисует прямоугольную проекцию с закругленными краями и углами
- cylinderWrap(…) – рисует сектор цилиндра (для ребер расширений)
- capWrap(…) – рисует сектор “чаши” (полу-сферы) (для углов расширений)
7. Заменим ModelBuilder.h код на:
#pragma once
#include "ModelBuilder1base.h"
class ModelBuilder : public ModelBuilder1base
{
public:
virtual ~ModelBuilder();
static int buildFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);
static int buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);
static int buildBoxFacePlain(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);
static int buildBoxFaceTank(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS);
static int cylinderWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo);
static int capWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo);
};
8. Заменим ModelBuilder.cpp код на:
#include "ModelBuilder.h"
#include "platform.h"
#include "utils.h"
#include "DrawJob.h"
#include "Shader.h"
extern float degrees2radians;
ModelBuilder::~ModelBuilder() {
}
int ModelBuilder::buildFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
if (strstr(pVS->shapeType, "box") == pVS->shapeType)
return buildBoxFace(pMB, applyTo, pVS);
return -1;
}
int ModelBuilder::buildBoxFace(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
//this code is for simple box
VirtualShape vs; //face VS,
mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
vs.sectionsR = pVS->sectionsR;
//rotate desirable side to face us.
if (applyTo.find("front") == 0) {
//Side <front> is facing us as is.
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[1];
vs.sections[0] = pVS->sections[0];
vs.sections[1] = pVS->sections[1];
//extensions
vs.extF = pVS->extF;
vs.extL = pVS->extL;
vs.extR = pVS->extR;
vs.extU = pVS->extU;
vs.extD = pVS->extD;
//define how to move/place generated face back to the VirtualShape
//just shift closer to us by length/2
mat4x4_translate(transformMatrix, 0, 0, pVS->whl[2] / 2);
}
else if (applyTo.find("back") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[1];
vs.sections[0] = pVS->sections[0];
vs.sections[1] = pVS->sections[1];
//extensions
vs.extF = pVS->extB;
vs.extL = pVS->extR;
vs.extR = pVS->extL;
vs.extU = pVS->extU;
vs.extD = pVS->extD;
//rotate 180 degrees around Y and shift farther from us by half-length
mat4x4_translate(transformMatrix, 0, 0, -pVS->whl[2] / 2);
mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 180);
}
else if (applyTo.find("left") == 0) {
vs.whl[0] = pVS->whl[2]; //width = original length
vs.whl[1] = pVS->whl[1];
vs.sections[0] = pVS->sections[2];
vs.sections[1] = pVS->sections[1];
//extensions
vs.extF = pVS->extL;
vs.extL = pVS->extB;
vs.extR = pVS->extF;
vs.extU = pVS->extU;
vs.extD = pVS->extD;
//rotate -90 degrees around Y (CW) and shift half-width to the left
mat4x4_translate(transformMatrix, -pVS->whl[0] / 2, 0, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 90);
}
else if (applyTo.find("right") == 0) {
vs.whl[0] = pVS->whl[2]; //width = original length
vs.whl[1] = pVS->whl[1];
vs.sections[0] = pVS->sections[2];
vs.sections[1] = pVS->sections[1];
//extensions
vs.extF = pVS->extR;
vs.extL = pVS->extF;
vs.extR = pVS->extB;
vs.extU = pVS->extU;
vs.extD = pVS->extD;
//rotate +90 degrees around Y (CCW) and shift half-width to the right
mat4x4_translate(transformMatrix, pVS->whl[0] / 2, 0, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, degrees2radians * 90);
}
else if (applyTo.find("top") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[2]; //height = original length
vs.sections[0] = pVS->sections[0];
vs.sections[1] = pVS->sections[2];
//extensions
vs.extF = pVS->extU;
vs.extL = pVS->extR;
vs.extR = pVS->extL;
vs.extU = pVS->extF;
vs.extD = pVS->extB;
//rotate -90 degrees around X (CW) and 180 around Y, and shift half-height up
mat4x4_translate(transformMatrix, 0, pVS->whl[1] / 2, 0);
mat4x4_rotate_Y(transformMatrix, transformMatrix, -degrees2radians * 180);
mat4x4_rotate_X(transformMatrix, transformMatrix, -degrees2radians * 90);
}
else if (applyTo.find("bottom") == 0) {
vs.whl[0] = pVS->whl[0];
vs.whl[1] = pVS->whl[2]; //height = original length
vs.sections[0] = pVS->sections[0];
vs.sections[1] = pVS->sections[2];
//extensions
vs.extF = pVS->extD;
vs.extL = pVS->extL;
vs.extR = pVS->extR;
vs.extU = pVS->extF;
vs.extD = pVS->extB;
//rotate 90 around X (CCW) and shift half-height down
mat4x4_translate(transformMatrix, 0, -pVS->whl[1] / 2, 0);
mat4x4_rotate_X(transformMatrix, transformMatrix, degrees2radians * 90);
}
lockGroup(pMB);
//create vertices
if (strstr(pVS->shapeType, "tank") != nullptr)
buildBoxFaceTank(pMB, applyTo, &vs);
else
buildBoxFacePlain(pMB, applyTo, &vs);
//move face to it's place (apply transform matrix)
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);
}
releaseGroup(pMB);
return 1;
}
int ModelBuilder::buildBoxFacePlain(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
if (pVS->whl[0] == 0 || pVS->whl[1] == 0)
return 0;
//create vertices
int sectionsX = pVS->sections[0];
int sectionsY = pVS->sections[1];
int pointsX = sectionsX + 1;
int pointsY = sectionsY + 1;
float stepX = pVS->whl[0] / sectionsX;
float stepY = pVS->whl[1] / sectionsY;
float kY = pVS->whl[1] / 2;
for (int iy = 0; iy < pointsY; iy++) {
float kX = -pVS->whl[0] / 2;
for (int ix = 0; ix < pointsX; ix++) {
int nSE = addVertex(pMB, kX, kY, pVS->extF, 0, 0, 1); //vertex number on south-east
if (iy > 0 && ix > 0) {
//add 2 triangles
int nSW = nSE - 1; //vertex number south-west
int nNE = nSE - pointsX; //north-east
int nNW = nSW - pointsX; //north-west
add2triangles(pMB, nNW, nNE, nSW, nSE, iy + ix);
}
kX += stepX;
}
kY -= stepY;
}
return 1;
}
int ModelBuilder::buildBoxFaceTank(ModelBuilder* pMB, std::string applyTo, VirtualShape* pVS) {
//for diamond effect - sectionsRad=1, don't merge normals
bool drawMiddle = true;
//edges
bool drawTop = false;
bool drawBottom = false;
bool drawLeft = false;
bool drawRight = false;
//corners
bool drawTopLeft = false;
bool drawTopRight = false;
bool drawBottomLeft = false;
bool drawBottomRight = false;
if (pVS->extF == 0 || applyTo.find(" all") != std::string::npos) {
drawTop = true;
drawBottom = true;
drawLeft = true;
drawRight = true;
drawTopLeft = true;
drawTopRight = true;
drawBottomLeft = true;
drawBottomRight = true;
}
else if (applyTo.find(" h") != std::string::npos) {
drawLeft = true;
drawRight = true;
}
else if (applyTo.find(" v") != std::string::npos) {
drawTop = true;
drawBottom = true;
}
if (applyTo.find(" no") != std::string::npos) {
if (applyTo.find(" noM") != std::string::npos) {
//middle
if (applyTo.find(" noMrow") != std::string::npos) {
drawMiddle = false;
drawLeft = false;
drawRight = false;
}
if (applyTo.find(" noMcol") != std::string::npos) {
drawMiddle = false;
drawTop = false;
drawBottom = false;
}
if (applyTo.find(" noMid") != std::string::npos)
drawMiddle = false;
}
if (applyTo.find(" noN") != std::string::npos) {
//north
if (applyTo.find(" noNrow") != std::string::npos) {
drawTop = false;
drawTopLeft = false;
drawTopRight = false;
}
if (applyTo.find(" noNedge") != std::string::npos)
drawTop = false;
if (applyTo.find(" noNW") != std::string::npos)
drawTopLeft = false;
if (applyTo.find(" noNE") != std::string::npos)
drawTopRight = false;
}
if (applyTo.find(" noS") != std::string::npos) {
//south
if (applyTo.find(" noSrow") != std::string::npos) {
drawBottom = false;
drawBottomLeft = false;
drawBottomRight = false;
}
if (applyTo.find(" noSedge") != std::string::npos)
drawBottom = false;
if (applyTo.find(" noSW") != std::string::npos)
drawBottomLeft = false;
if (applyTo.find(" noSE") != std::string::npos)
drawBottomRight = false;
}
if (applyTo.find(" noW") != std::string::npos) {
//west
if (applyTo.find(" noWcol") != std::string::npos) {
drawLeft = false;
drawTopLeft = false;
drawBottomLeft = false;
}
if (applyTo.find(" noWedge") != std::string::npos)
drawLeft = false;
}
if (applyTo.find(" noE") != std::string::npos) {
//east
if (applyTo.find(" noEcol") != std::string::npos) {
drawRight = false;
drawTopRight = false;
drawBottomRight = false;
}
if (applyTo.find(" noEedge") != std::string::npos)
drawRight = false;
}
}
lockGroup(pMB);
//middle
if (pVS->whl[0] > 0 && pVS->whl[1] > 0 && drawMiddle) {
buildBoxFacePlain(pMB, applyTo, pVS);
}
VirtualShape vs;
//edges
//vs.type.assign("cylinder");
vs.sectionsR = pVS->sectionsR;
if (pVS->whl[0] > 0) {
vs.sections[2] = pVS->sections[0]; //cylinder Z sections n
vs.whl[2] = pVS->whl[0]; //cylinder length Z
vs.whl[0] = pVS->extF * 2; //cylinder diameter X
if (pVS->extU > 0 && drawTop) {
vs.whl[1] = pVS->extU * 2; //cylinder diameter Y
lockGroup(pMB);
cylinderWrap(pMB, &vs, 0, 90);
//rotate -90 degrees around Y and shift up
moveGroupDg(pMB, 0, -90, 0, 0, pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
if (pVS->extD > 0 && drawBottom) {
vs.whl[1] = pVS->extD * 2; //cylinder diameter Y
lockGroup(pMB);
cylinderWrap(pMB, &vs, -90, 0);
//rotate -90 degrees around Y and shift down
moveGroupDg(pMB, 0, -90, 0, 0, -pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
}
if (pVS->whl[1] > 0) {
vs.sections[2] = pVS->sections[1]; //cylinder Z sections n
vs.whl[2] = pVS->whl[1]; //cylinder length Z
vs.whl[1] = pVS->extF * 2; //cylinder diameter Y
if (pVS->extL > 0 && drawLeft) {
vs.whl[0] = pVS->extL * 2; //cylinder diameter X
lockGroup(pMB);
cylinderWrap(pMB, &vs, 90, 180);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 90, 0, 0, -pVS->whl[0] * 0.5f, 0, 0);
releaseGroup(pMB);
}
if (pVS->extR > 0 && drawRight) {
vs.whl[0] = pVS->extR * 2; //cylinder diameter X
lockGroup(pMB);
cylinderWrap(pMB, &vs, 0, 90);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 90, 0, 0, pVS->whl[0] * 0.5f, 0, 0);
releaseGroup(pMB);
}
}
//corners
//vs.type.assign("cap");
vs.sectionsR = pVS->sectionsR;
vs.sections[2] = pVS->sectionsR;
vs.whl[2] = pVS->extF;
if (pVS->extU > 0) {
//top corners
vs.whl[1] = pVS->extU * 2;
if (pVS->extL > 0 && drawTopLeft) {
vs.whl[0] = pVS->extL * 2;
lockGroup(pMB);
capWrap(pMB, &vs, 90, 180);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 0, 0, 0, -pVS->whl[0] * 0.5f, pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
if (pVS->extR > 0 && drawTopRight) {
vs.whl[0] = pVS->extR * 2;
lockGroup(pMB);
capWrap(pMB, &vs, 0, 90);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 0, 0, 0, pVS->whl[0] * 0.5f, pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
}
if (pVS->extD > 0) {
//bottom corners
vs.whl[1] = pVS->extD * 2;
if (pVS->extL > 0 && drawBottomLeft) {
vs.whl[0] = pVS->extL * 2;
lockGroup(pMB);
capWrap(pMB, &vs, -180, -90);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 0, 0, 0, -pVS->whl[0] * 0.5f, -pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
if (pVS->extR > 0 && drawBottomRight) {
vs.whl[0] = pVS->extR * 2;
lockGroup(pMB);
capWrap(pMB, &vs, -90, 0);
//rotate 90 degrees around Y and shift left
moveGroupDg(pMB, 0, 0, 0, pVS->whl[0] * 0.5f, -pVS->whl[1] * 0.5f, 0);
releaseGroup(pMB);
}
}
if (pVS->extF == 0) {
int vertsN = pMB->vertices.size();
for (int i = pMB->pCurrentGroup->fromVertexN; i < vertsN; i++) {
Vertex01* pVX = pMB->vertices.at(i);
//normal
v3set(pVX->aNormal, 0, 0, 1);
}
}
releaseGroup(pMB);
return 1;
}
int ModelBuilder::cylinderWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo) {
// angleFrom/To - in degrees
lockGroup(pMB);
float stepZ = pVS->whl[2] / pVS->sections[2];
float stepDg = (angleTo - angleFrom) / pVS->sectionsR; //in degrees
for (int nz = 0; nz <= pVS->sections[2]; nz++) {
float kz = stepZ * nz - pVS->whl[2] * 0.5f;
for (int rpn = 0; rpn <= pVS->sectionsR; rpn++) {
// rpn - radial point number
float angleRd = (angleFrom + stepDg * rpn) * degrees2radians;
float kx = cosf(angleRd);
float ky = sinf(angleRd);
int nSE = addVertex(pMB, kx, ky, kz, kx, ky, 0);
if (nz > 0 && rpn > 0) {
int nSW = nSE - 1;
int nNW = nSW - pVS->sectionsR - 1;
int nNE = nSE - pVS->sectionsR - 1;
add2triangles(pMB, nNE, nNW, nSE, nSW, nz + rpn);
}
}
}
//scale to desirable diameters
mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
mat4x4_scale_aniso(transformMatrix, transformMatrix, pVS->whl[0] * 0.5f, pVS->whl[1] * 0.5f, 1);
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);
}
releaseGroup(pMB);
return 1;
}
int ModelBuilder::capWrap(ModelBuilder* pMB, VirtualShape* pVS, float angleFrom, float angleTo) {
// angleFrom/To - in degrees
lockGroup(pMB);
//center point
int n0 = addVertex(pMB, 0, 0, 1, 0, 0, 1);
float stepZdg = 90.0f / pVS->sections[2]; //in degrees
float stepRdg = (angleTo - angleFrom) / pVS->sectionsR; //in degrees
for (int nz = 1; nz <= pVS->sections[2]; nz++) {
float angleZrd = stepZdg * nz * degrees2radians;
float kz = cosf(angleZrd);
float R = sinf(angleZrd);
for (int rpn = 0; rpn <= pVS->sectionsR; rpn++) {
// rpn - radial point number
float angleRd = (angleFrom + stepRdg * rpn) * degrees2radians;
float kx = cosf(angleRd) * R;
float ky = sinf(angleRd) * R;
int nSE = addVertex(pMB, kx, ky, kz, kx, ky, kz);
if (rpn > 0) {
if (nz == 1) {
int nSW = nSE - 1;
addTriangle(pMB, n0, nSW, nSE);
}
else {
int nSW = nSE - 1;
int nNW = nSW - pVS->sectionsR - 1;
int nNE = nSE - pVS->sectionsR - 1;
add2triangles(pMB, nNW, nNE, nSW, nSE, nz + rpn);
}
}
}
}
//scale to desirable diameters
mat4x4 transformMatrix = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
mat4x4_scale_aniso(transformMatrix, transformMatrix, pVS->whl[0] * 0.5f, pVS->whl[1] * 0.5f, pVS->whl[2]);
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);
}
releaseGroup(pMB);
return 1;
}
9. Заменим TheGame.cpp код на:
#include "TheGame.h"
#include "platform.h"
#include "utils.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
#include "ModelBuilder.h"
extern std::string filesRoot;
std::vector<GameSubj*> TheGame::gameSubjs;
int TheGame::getReady() {
bExitGame = false;
Shader::loadShaders();
glEnable(GL_CULL_FACE);
//=== create box ========================
GameSubj* pGS = new GameSubj();
gameSubjs.push_back(pGS);
pGS->name.assign("box1");
pGS->ownCoords.setPosition(0, 0, 0);
pGS->ownCoords.setDegrees(0, 0, 0);
pGS->ownSpeed.setDegrees(0,1,0);
ModelBuilder* pMB = new ModelBuilder();
pMB->useSubjN(gameSubjs.size() - 1);
//define VirtualShape
VirtualShape vs;
vs.setShapeType("box-tank");
vs.whl[0] = 60;
vs.whl[1] = 160;
vs.whl[2] = 390;
vs.setExt(20);
vs.extD = 0;
vs.extF = 0; //to make front face "flat"
vs.sectionsR = 2;
Material mt;
//define material - flat red
mt.shaderN = Shader::spN_phong_ucolor;
mt.primitiveType = GL_TRIANGLES;
mt.uColor.setRGBA(255, 0, 0,255); //red
pMB->useMaterial(&mt);
pMB->buildBoxFace(pMB,"front v", &vs);
pMB->buildBoxFace(pMB, "back v", &vs);
pMB->buildBoxFace(pMB, "top", &vs);
pMB->buildBoxFace(pMB, "bottom", &vs);
pMB->buildBoxFace(pMB, "left all", &vs);
mt.uColor.setRGBA(0, 0, 255,255); pMB->useMaterial(&mt); //blue
pMB->buildBoxFace(pMB, "right all", &vs);
pMB->buildDrawJobs(gameSubjs);
delete pMB;
//===== set up camera
v3set(mainCamera.ownCoords.pos, 0, 200, 1000); //set position
float cameraDir[3];
v3set(cameraDir, 0, -200, -1000); //set direction vector
float cameraYawDg = v3yawDg(cameraDir);
float cameraPitchDg = v3pitchDg(cameraDir);
//mylog("cameraYaw=%f, cameraPitch=%f\n", cameraYawDg, cameraPitchDg);
mainCamera.ownCoords.setDegrees(cameraPitchDg, cameraYawDg, 0);
float cameraUp[4] = { 0,1,0,0 }; //y - up
mat4x4_mul_vec4plus(cameraUp, *mainCamera.ownCoords.getRotationMatrix(), cameraUp, 0);
mat4x4_look_at(mainCamera.lookAtMatrix, mainCamera.ownCoords.pos, pGS->ownCoords.pos, cameraUp);
//===== set up light
v3set(dirToMainLight, -1, 1, 1);
vec3_norm(dirToMainLight, dirToMainLight);
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
//glClearColor(0.0, 0.0, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
//calculate halfVector
float dirToCamera[4] = { 0,0,-1,0 }; //-z
mat4x4_mul_vec4plus(dirToCamera, *mainCamera.ownCoords.getRotationMatrix(), dirToCamera, 0);
float uHalfVector[4] = { 0,0,0,0 };
for (int i = 0; i < 3; i++)
uHalfVector[i] = (dirToCamera[i] + dirToMainLight[i]) / 2;
vec3_norm(uHalfVector, uHalfVector);
mat4x4 mProjection, mViewProjection, mMVP, mMV4x4;
//mat4x4_ortho(mProjection, -(float)screenSize[0] / 2, (float)screenSize[0] / 2, -(float)screenSize[1] / 2, (float)screenSize[1] / 2, 100.f, 500.f);
mat4x4_perspective(mProjection, 3.14f / 6.0f, (float)screenSize[0] / screenSize[1], 700.f, 1300.f);
mat4x4_mul(mViewProjection, mProjection, mainCamera.lookAtMatrix);
//mViewProjection[1][3] = 0; //keystone effect
//scan subjects
int subjsN = gameSubjs.size();
for (int subjN = 0; subjN < subjsN; subjN++) {
GameSubj* pGS = gameSubjs.at(subjN);
//behavior - apply rotation speed
pGS->moveSubj();
//prepare subject for rendering
pGS->buildModelMatrix(pGS);
//build MVP matrix for given subject
mat4x4_mul(mMVP, mViewProjection, pGS->ownModelMatrix);
//build Model-View (rotation) matrix for normals
mat4x4_mul(mMV4x4, mainCamera.lookAtMatrix, (vec4*)pGS->ownCoords.getRotationMatrix());
//convert to 3x3 matrix
float mMV3x3[3][3];
for (int y = 0; y < 3; y++)
for (int x = 0; x < 3; x++)
mMV3x3[y][x] = mMV4x4[y][x];
//render subject
for (int i = 0; i < pGS->djTotalN; i++) {
DrawJob* pDJ = DrawJob::drawJobs.at(pGS->djStartN + i);
pDJ->execute((float*)mMVP, *mMV3x3, dirToMainLight, uHalfVector, NULL);
}
}
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;
screenRatio = (float)width / (float)height;
glViewport(0, 0, width, height);
mylog(" screen size %d x %d\n", width, height);
return 1;
}
int TheGame::run() {
getReady();
while (!bExitGame) {
drawFrame();
}
cleanUp();
return 1;
}
Отличие от прежней версии:
Функция TheGame::getReady() теперь строит не просто “box”, а “box-tank” (кубик с закругленными краями и углами), строки с 33 по 55.
Обратите внимание, что вместо простых “front”, “right”, и т.д., у нас теперь “front v”, “right all”, etc. Это все про “extensions”:
10. Компиляция и запуск:
До:
После:
Теперь – с бликами.
Android
Тут надо добавить Андроид-овские имплементации myMkDir() и myStrcpy_s().
11. Пере-запускаем VS. Открываем C:\CPP\a997modeler\p_android\p_android.sln.
12. Заменим platform.h код на:
#pragma once
typedef uint32_t myUint64;
typedef uint32_t myUint32;
typedef uint8_t myUint16;
typedef uint8_t myUint8;
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
int myMkDir(const char* outPath);
void myStrcpy_s(char* dst, int maxSize, const char* src);
13. Заменим platform.cpp код на:
#include <android/log.h>
#include "stdio.h"
#include "TheGame.h"
#include <sys/stat.h> //mkdir for Android
extern struct android_app* androidApp;
extern const ASensor* accelerometerSensor;
extern ASensorEventQueue* sensorEventQueue;
extern EGLDisplay androidDisplay;
extern EGLSurface androidSurface;
extern TheGame theGame;
void mylog(const char* _Format, ...) {
#ifdef _DEBUG
char outStr[1024];
va_list _ArgList;
va_start(_ArgList, _Format);
vsprintf(outStr, _Format, _ArgList);
__android_log_print(ANDROID_LOG_INFO, "mylog", outStr, NULL);
va_end(_ArgList);
#endif
};
void mySwapBuffers() {
eglSwapBuffers(androidDisplay, androidSurface);
}
void myPollEvents() {
// Read all pending events.
int ident;
int events;
struct android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(0, NULL, &events,
(void**)&source)) >= 0) {
// Process this event.
if (source != NULL) {
source->process(androidApp, source);
}
// If a sensor has data, process it now.
if (ident == LOOPER_ID_USER) {
if (accelerometerSensor != NULL) {
ASensorEvent event;
while (ASensorEventQueue_getEvents(sensorEventQueue,
&event, 1) > 0) {
//LOGI("accelerometer: x=%f y=%f z=%f",
// event.acceleration.x, event.acceleration.y,
// event.acceleration.z);
}
}
}
// Check if we are exiting.
if (androidApp->destroyRequested != 0) {
theGame.bExitGame = true;
break;
}
}
}
int myFopen_s(FILE** pFile, const char* filePath, const char* mode) {
*pFile = fopen(filePath, mode);
if (*pFile == NULL) {
mylog("ERROR: can't open file %s\n", filePath);
return -1;
}
return 1;
}
int myMkDir(const char* outPath) {
struct stat info;
if (stat(outPath, &info) == 0)
return 0; //exists already
int status = mkdir(outPath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (status == 0)
return 1; //Successfully created
mylog("ERROR creating, status=%d, errno: %s.\n", status, std::strerror(errno));
return -1;
}
void myStrcpy_s(char* dst, int maxSize, const char* src) {
strcpy(dst, src);
//fill tail by zeros
int strLen = strlen(dst);
if (strLen < maxSize)
for (int i = strLen; i < maxSize; i++)
dst[i] = 0;
}
14. Под modeler добавим Existing Item
из C:\CPP\engine\modeler
- ModelBuilder1base.cpp
- ModelBuilder1base.h
Add.
15. Включаем, разблокируем, подключаем, разреншаем.
Rebuild solution. Run.
Хорошо.