Add support for model files
Model files are zip archives with a "model.toml" file at the root describing the model and all its textures.
This commit is contained in:
parent
cbbdf5ff79
commit
4770c96d92
|
|
@ -1,5 +1,6 @@
|
||||||
cmake_minimum_required( VERSION 3.0 )
|
cmake_minimum_required( VERSION 3.0 )
|
||||||
project( Facecam2D VERSION 0.1.0 )
|
project( Facecam2D VERSION 0.1.0 )
|
||||||
|
find_package( libzip REQUIRED )
|
||||||
find_package( OpenCV REQUIRED )
|
find_package( OpenCV REQUIRED )
|
||||||
message (STATUS "Found OpenCV at: " ${OpenCV_INCLUDE_DIRS} )
|
message (STATUS "Found OpenCV at: " ${OpenCV_INCLUDE_DIRS} )
|
||||||
find_package( OpenGL REQUIRED )
|
find_package( OpenGL REQUIRED )
|
||||||
|
|
@ -38,5 +39,8 @@ add_executable( fc2d
|
||||||
src/cv.cpp
|
src/cv.cpp
|
||||||
src/paths.cpp
|
src/paths.cpp
|
||||||
src/args.cpp
|
src/args.cpp
|
||||||
|
src/model.cpp
|
||||||
|
src/toml.c
|
||||||
|
src/tomlcpp.cpp
|
||||||
)
|
)
|
||||||
target_link_libraries( fc2d ${OpenCV_LIBS} ${OPENGL_LIBRARIES} FreeGLUT::freeglut GLEW::glew )
|
target_link_libraries( fc2d ${OpenCV_LIBS} ${OPENGL_LIBRARIES} FreeGLUT::freeglut GLEW::glew zip )
|
||||||
|
|
|
||||||
BIN
models/test.fma
Normal file
BIN
models/test.fma
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 8.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
33
src/cv.cpp
33
src/cv.cpp
|
|
@ -6,6 +6,8 @@
|
||||||
#include <graphics.hpp>
|
#include <graphics.hpp>
|
||||||
#include <paths.hpp>
|
#include <paths.hpp>
|
||||||
#include <args.hpp>
|
#include <args.hpp>
|
||||||
|
#include <cv.hpp>
|
||||||
|
#include <modelpart.hpp>
|
||||||
|
|
||||||
cv::Ptr<cv::face::Facemark> facemark;
|
cv::Ptr<cv::face::Facemark> facemark;
|
||||||
cv::CascadeClassifier haarFaceDetector;
|
cv::CascadeClassifier haarFaceDetector;
|
||||||
|
|
@ -101,30 +103,29 @@ void cvFrame() {
|
||||||
|
|
||||||
//send control information to graphics
|
//send control information to graphics
|
||||||
float faceSize = landmarks[biggestFace][14].x - landmarks[biggestFace][2].x;
|
float faceSize = landmarks[biggestFace][14].x - landmarks[biggestFace][2].x;
|
||||||
updateModel(
|
|
||||||
//head position
|
struct FaceData faceData;
|
||||||
glm::vec2(
|
faceData.positions[BIND_NULL] = glm::vec2(0.0f, 0.0f);
|
||||||
|
faceData.positions[BIND_HEAD] = glm::vec2(
|
||||||
(landmarks[biggestFace][2].x + landmarks[biggestFace][14].x) / 2
|
(landmarks[biggestFace][2].x + landmarks[biggestFace][14].x) / 2
|
||||||
* 2 / (float)frame.cols - 1,
|
* 2 / (float)frame.cols - 1,
|
||||||
(landmarks[biggestFace][2].y + landmarks[biggestFace][14].y) / 2
|
(landmarks[biggestFace][2].y + landmarks[biggestFace][14].y) / 2
|
||||||
* 2 / (float)frame.rows - 1
|
* 2 / (float)frame.rows - 1
|
||||||
),
|
);
|
||||||
|
faceData.positions[BIND_FACE] = glm::vec2(
|
||||||
//face position
|
|
||||||
glm::vec2(
|
|
||||||
landmarks[biggestFace][30].x * 2 / (float)frame.cols - 1,
|
landmarks[biggestFace][30].x * 2 / (float)frame.cols - 1,
|
||||||
landmarks[biggestFace][30].y * 2 / (float)frame.rows - 1
|
landmarks[biggestFace][30].y * 2 / (float)frame.rows - 1
|
||||||
),
|
);
|
||||||
|
faceData.triggers[TRIGGER_NULL] = false;
|
||||||
|
faceData.triggers[TRIGGER_MOUTH_OPEN] =
|
||||||
|
(landmarks[biggestFace][66].y - landmarks[biggestFace][62].y) / faceSize > 0.04f;
|
||||||
|
|
||||||
//rotation
|
faceData.headRotation = atanf(
|
||||||
atanf((float)(landmarks[biggestFace][14].y - landmarks[biggestFace][2].y) /
|
(float)(landmarks[biggestFace][14].y - landmarks[biggestFace][2].y) /
|
||||||
(float)(landmarks[biggestFace][2].x - landmarks[biggestFace][14].x)),
|
(float)(landmarks[biggestFace][2].x - landmarks[biggestFace][14].x));
|
||||||
|
faceData.scale = faceSize * 6 / (float)frame.cols;
|
||||||
|
|
||||||
//scale
|
updateModel(faceData);
|
||||||
faceSize * 6 / (float)frame.cols,
|
|
||||||
|
|
||||||
//mouth open/closed state
|
|
||||||
(landmarks[biggestFace][66].y - landmarks[biggestFace][62].y) / faceSize > 0.04f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
src/cv.hpp
11
src/cv.hpp
|
|
@ -1,6 +1,17 @@
|
||||||
#ifndef CV_HPP
|
#ifndef CV_HPP
|
||||||
#define CV_HPP
|
#define CV_HPP
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
|
||||||
|
struct FaceData {
|
||||||
|
std::map<int, glm::vec2> positions;
|
||||||
|
std::map<int, bool> triggers;
|
||||||
|
|
||||||
|
float headRotation;
|
||||||
|
float scale;
|
||||||
|
};
|
||||||
|
|
||||||
void initCV();
|
void initCV();
|
||||||
|
|
||||||
void cvFrame();
|
void cvFrame();
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,21 @@
|
||||||
|
|
||||||
#include <graphics.hpp>
|
#include <graphics.hpp>
|
||||||
#include <modelpart.hpp>
|
#include <modelpart.hpp>
|
||||||
|
#include <model.hpp>
|
||||||
#include <paths.hpp>
|
#include <paths.hpp>
|
||||||
#include <config.hpp>
|
#include <config.hpp>
|
||||||
|
#include <cv.hpp>
|
||||||
|
|
||||||
GLuint shader; //standard shader program used for all elements
|
GLuint shader; //standard shader program used for all elements
|
||||||
GLuint transUniform; //location of the "transMatrix" transformation matrix uniform in the shader
|
GLuint transUniform; //location of the "transMatrix" transformation matrix uniform in the shader
|
||||||
|
|
||||||
//parts of the model (see modelpart.hpp)
|
//parts of the model (see modelpart.hpp)
|
||||||
ModelPart parts[3];
|
Model* model;
|
||||||
|
|
||||||
void display () {
|
void display () {
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(parts)/sizeof(ModelPart); i++) {
|
model->draw();
|
||||||
parts[i].bindAndDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
glutSwapBuffers();
|
glutSwapBuffers();
|
||||||
}
|
}
|
||||||
|
|
@ -82,10 +82,7 @@ void initGraphics () {
|
||||||
|
|
||||||
initShader();
|
initShader();
|
||||||
|
|
||||||
parts[0] = ModelPart(resolvePath("models/test/head-base.png").c_str(), transUniform);
|
model = new Model(resolvePath("models/test.fma").c_str());
|
||||||
parts[1] = ModelPart(resolvePath("models/test/face-eyes.png").c_str(), transUniform);
|
|
||||||
parts[2] = ModelPart(resolvePath("models/test/face-mouth-closed.png").c_str(), transUniform);
|
|
||||||
parts[2].addTexture(resolvePath("models/test/face-mouth-open.png").c_str(), 1);
|
|
||||||
|
|
||||||
//enable blending for alpha textures
|
//enable blending for alpha textures
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
|
@ -100,12 +97,12 @@ void initGraphics () {
|
||||||
std::cout << "graphics init complete" << std::endl;
|
std::cout << "graphics init complete" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initTexture (GLuint* texNum, const char* path) {
|
void initTexture (GLuint* texNum, unsigned char* buffer, size_t bufferLength) {
|
||||||
glGenTextures(1, texNum);
|
glGenTextures(1, texNum);
|
||||||
glBindTexture(GL_TEXTURE_2D, *texNum);
|
glBindTexture(GL_TEXTURE_2D, *texNum);
|
||||||
|
|
||||||
int x, y, channels;
|
int x, y, channels;
|
||||||
GLubyte* pixels = stbi_load(path, &x, &y, &channels, 4);
|
GLubyte* pixels = stbi_load_from_memory(buffer, bufferLength, &x, &y, &channels, 4);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
|
@ -175,11 +172,8 @@ void printShaderCompileLog(GLuint shader) {
|
||||||
std::cout << logBuffer << std::endl;
|
std::cout << logBuffer << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initModel () {
|
void updateModel(struct FaceData faceData) {
|
||||||
|
/*
|
||||||
}
|
|
||||||
|
|
||||||
void updateModel(glm::vec2 headPos, glm::vec2 facePos, float rotation, float scale, bool mouthOpen) {
|
|
||||||
//calculate transforms
|
//calculate transforms
|
||||||
parts[0].setTransform(headPos, rotation, scale);
|
parts[0].setTransform(headPos, rotation, scale);
|
||||||
parts[1].setTransform(facePos, rotation, scale);
|
parts[1].setTransform(facePos, rotation, scale);
|
||||||
|
|
@ -187,6 +181,8 @@ void updateModel(glm::vec2 headPos, glm::vec2 facePos, float rotation, float sca
|
||||||
|
|
||||||
//set mouth texture to open or closed
|
//set mouth texture to open or closed
|
||||||
parts[2].selectTexture(mouthOpen ? 1 : 0);
|
parts[2].selectTexture(mouthOpen ? 1 : 0);
|
||||||
|
*/
|
||||||
|
model->updateTransforms(faceData);
|
||||||
|
|
||||||
//tell FreeGLUT to schedule a screen update
|
//tell FreeGLUT to schedule a screen update
|
||||||
glutPostRedisplay();
|
glutPostRedisplay();
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,22 @@
|
||||||
|
|
||||||
#include <glm/vec2.hpp>
|
#include <glm/vec2.hpp>
|
||||||
|
|
||||||
|
#include <cv.hpp>
|
||||||
|
|
||||||
|
extern GLuint transUniform;
|
||||||
|
|
||||||
void initGraphics ();
|
void initGraphics ();
|
||||||
|
|
||||||
void graphicsFrame ();
|
void graphicsFrame ();
|
||||||
|
|
||||||
void initBuffers (GLuint* vaoNum);
|
void initBuffers (GLuint* vaoNum);
|
||||||
|
|
||||||
void initTexture (GLuint* texNum, const char* path);
|
void initTexture (GLuint* texNum, unsigned char* buffer, size_t bufferLength);
|
||||||
|
|
||||||
void initShader();
|
void initShader();
|
||||||
|
|
||||||
void printShaderCompileLog(GLuint shader);
|
void printShaderCompileLog(GLuint shader);
|
||||||
|
|
||||||
void updateModel(glm::vec2 headPos, glm::vec2 facePos, float rotation, float scale, bool mouthOpen);
|
void updateModel(struct FaceData faceData);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <paths.hpp>
|
#include <paths.hpp>
|
||||||
#include <args.hpp>
|
#include <args.hpp>
|
||||||
#include <config.hpp>
|
#include <config.hpp>
|
||||||
|
#include <model.hpp>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
@ -17,8 +18,11 @@ int main (int argc, char** argv) {
|
||||||
std::cout << "Default asset prefix: " << prefixDefault << std::endl;
|
std::cout << "Default asset prefix: " << prefixDefault << std::endl;
|
||||||
|
|
||||||
initGraphics();
|
initGraphics();
|
||||||
|
|
||||||
|
//Model model(resolvePath("models/test.fma").c_str());
|
||||||
initCV();
|
initCV();
|
||||||
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
cvFrame();
|
cvFrame();
|
||||||
|
|
||||||
|
|
|
||||||
113
src/model.cpp
Normal file
113
src/model.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <zip.h>
|
||||||
|
#include <tomlcpp.hpp> //dynamically link tomlcpp if it becomes common in repositories
|
||||||
|
#include <model.hpp>
|
||||||
|
|
||||||
|
#define BUFFER_SIZE_MODEL_DESC 8192 // 8 KiB
|
||||||
|
#define BUFFER_SIZE_TEXTURE 16777220 // 16 MiB
|
||||||
|
|
||||||
|
void textureFromArchive(zip_t* archive, const char* path, ModelPart* part, size_t slot, std::string triggerName) {
|
||||||
|
zip_file_t* textureFile = zip_fopen(archive, path, 0);
|
||||||
|
if (textureFile != NULL) {
|
||||||
|
unsigned char* textureBuffer = new unsigned char[BUFFER_SIZE_TEXTURE];
|
||||||
|
size_t textureLength = zip_fread(textureFile, textureBuffer, BUFFER_SIZE_TEXTURE-1);
|
||||||
|
|
||||||
|
part->addTexture(textureBuffer, textureLength, slot, triggerName);
|
||||||
|
|
||||||
|
delete [] textureBuffer;
|
||||||
|
} else {
|
||||||
|
std::cerr << path << " does not exist in archive!" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Model::Model(const char* path) {
|
||||||
|
int zipError;
|
||||||
|
zip_t* archive = zip_open(path, ZIP_RDONLY, &zipError);
|
||||||
|
|
||||||
|
if (!archive) {
|
||||||
|
std::cerr << "Model file " << path << " does not exist!" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get model description file (model.toml)
|
||||||
|
zip_file_t* modelDescFile = zip_fopen(archive, "model.toml", 0);
|
||||||
|
char modelDescBuffer[BUFFER_SIZE_MODEL_DESC];
|
||||||
|
size_t descLength = zip_fread(modelDescFile, modelDescBuffer, BUFFER_SIZE_MODEL_DESC-1);
|
||||||
|
modelDescBuffer[descLength] = 0; //null-terminate
|
||||||
|
|
||||||
|
// parse model.toml
|
||||||
|
auto modelDesc = toml::parse(std::string(modelDescBuffer));
|
||||||
|
if (!modelDesc.table) {
|
||||||
|
std::cerr << "cannot parse model.toml! " << std::endl << modelDesc.errmsg << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto partsDescArray = modelDesc.table->getArray("part");
|
||||||
|
|
||||||
|
auto partsVec = *partsDescArray->getTableVector();
|
||||||
|
|
||||||
|
for (int i = 0; i < partsVec.size(); i++) {
|
||||||
|
ModelPart newPart;
|
||||||
|
|
||||||
|
// position
|
||||||
|
auto bindResult = partsVec[i].getString("bind");
|
||||||
|
if (!bindResult.first) {
|
||||||
|
std::cerr << "Part " << i << " does not define a bind!" << std::endl;
|
||||||
|
} else {
|
||||||
|
newPart.setBind(bindResult.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parentResult = partsVec[i].getString("follow");
|
||||||
|
auto factorResult = partsVec[i].getDouble("factor");
|
||||||
|
|
||||||
|
if (parentResult.first) {
|
||||||
|
newPart.setFollowTarget(parentResult.second);
|
||||||
|
|
||||||
|
if (factorResult.first) {
|
||||||
|
newPart.setFollowFactor((float)factorResult.second);
|
||||||
|
} else {
|
||||||
|
newPart.setFollowFactor(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// texture
|
||||||
|
auto textureSingle = partsVec[i].getString("texture");
|
||||||
|
|
||||||
|
if (textureSingle.first) {
|
||||||
|
// only a single texture was defined
|
||||||
|
textureFromArchive(archive, textureSingle.second.c_str(), &newPart, 0, "null");
|
||||||
|
} else {
|
||||||
|
auto textureArray = partsVec[i].getArray("textures");
|
||||||
|
auto textureVec = *textureArray->getTableVector().get();
|
||||||
|
|
||||||
|
if (textureVec.size() < 1) {
|
||||||
|
std::cerr << "Part " << i << " does not define any textures!" << std::endl;
|
||||||
|
} else {
|
||||||
|
// a list of textures with triggers were defined
|
||||||
|
for (int j = 0; j < textureVec.size(); j++) {
|
||||||
|
auto fileResult = textureVec[j].getString("file");
|
||||||
|
auto triggerResult = textureVec[j].getString("trigger");
|
||||||
|
|
||||||
|
if (fileResult.first) {
|
||||||
|
std::string trigger = triggerResult.first ? triggerResult.second : "null";
|
||||||
|
textureFromArchive(archive, fileResult.second.c_str(), &newPart, j, trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push_back(newPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::draw() {
|
||||||
|
for (size_t i = 0; i < parts.size(); i++) {
|
||||||
|
parts[i].bindAndDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::updateTransforms(struct FaceData faceData) {
|
||||||
|
for (size_t i = 0; i < parts.size(); i++) {
|
||||||
|
parts[i].processFaceData(faceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/model.hpp
Normal file
19
src/model.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef MODEL_HPP
|
||||||
|
#define MODEL_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <modelpart.hpp>
|
||||||
|
#include <cv.hpp>
|
||||||
|
|
||||||
|
class Model {
|
||||||
|
std::vector<ModelPart> parts;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Model(const char* path);
|
||||||
|
|
||||||
|
void draw();
|
||||||
|
|
||||||
|
void updateTransforms(struct FaceData faceData);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
#include <glm/mat4x4.hpp>
|
#include <glm/mat4x4.hpp>
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
|
@ -8,36 +7,72 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
ModelPart::ModelPart() {
|
std::map<std::string, int> bindStringToNum {
|
||||||
}
|
{"null", BIND_NULL},
|
||||||
|
{"head", BIND_HEAD},
|
||||||
|
{"face", BIND_FACE},
|
||||||
|
};
|
||||||
|
|
||||||
ModelPart::ModelPart(const char* texPath, GLuint transUniformNum) {
|
std::map<std::string, bool> triggerStringToNum {
|
||||||
|
{"null", TRIGGER_NULL},
|
||||||
|
{"mouth-open", TRIGGER_MOUTH_OPEN},
|
||||||
|
};
|
||||||
|
|
||||||