Compare commits
10 commits
c7572e3d55
...
4084e5007e
Author | SHA1 | Date | |
---|---|---|---|
Epicalert | 4084e5007e | ||
Epicalert | 293154034e | ||
Epicalert | fe75856701 | ||
Epicalert | 972a0cc9b9 | ||
Epicalert | 678393ccb1 | ||
Epicalert | f077ca5b5f | ||
Epicalert | 57ad5cd47a | ||
Epicalert | c06f6beae6 | ||
Epicalert | eb4bf55cda | ||
Epicalert | 583ec11ee5 |
|
@ -47,6 +47,7 @@ file(
|
|||
DESTINATION
|
||||
${PROJECT_BINARY_DIR} )
|
||||
file( MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/models )
|
||||
pack_model( "default" )
|
||||
pack_model( "test" )
|
||||
pack_model( "rms" )
|
||||
if (APPLE)
|
||||
|
@ -69,6 +70,7 @@ add_executable( fc2d
|
|||
src/error.cpp
|
||||
src/eye.cpp
|
||||
src/configfile.cpp
|
||||
src/input.cpp
|
||||
packaging/fc2d.rc
|
||||
)
|
||||
target_link_libraries( fc2d ${OpenCV_LIBS} ${OPENGL_LIBRARIES} ${WEBP_LIBRARIES}
|
||||
|
|
7
TODO.md
|
@ -5,10 +5,9 @@
|
|||
- demo video?
|
||||
|
||||
## models
|
||||
- better default model
|
||||
- clipping (alpha stencil)
|
||||
|
||||
## Bugs
|
||||
- crash when eye goes outside of roi
|
||||
- incorrect appdata location on Windows
|
||||
|
||||
# Whenever
|
||||
|
@ -19,3 +18,7 @@
|
|||
|
||||
## vision
|
||||
- eye open/closed
|
||||
|
||||
## graphics
|
||||
- in-window text rendering
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.TH FC2D_MODEL 5 "Facecam2D Model Format 0.3 Manual"
|
||||
.TH FC2D_MODEL 5 "Facecam2D Model Format 0.5 Manual"
|
||||
.
|
||||
.SH NAME
|
||||
fc2d_model \- Facecam2D model file format
|
||||
|
@ -65,6 +65,23 @@ Unnecessary files should not be present in the archive, but archives with
|
|||
unnecessary files are still valid.
|
||||
.
|
||||
.
|
||||
.SH FORMAT VERSION
|
||||
.PP
|
||||
.
|
||||
Every Facecam2D model has a version code in the
|
||||
.B model.toml
|
||||
in the format "major.minor".
|
||||
A given version of Facecam2D can read model files with the same
|
||||
major version as the version it is compatible with.
|
||||
For example, if Facecam2D can read the model format up to version 0.3,
|
||||
the same build of Facecam2D can also read a model written in format 0.2,
|
||||
but cannot play a model written in format 1.2 or 0.5.
|
||||
It is only backwards compatible with the same major version.
|
||||
Higher minor versions of the model format indicate that there are newer
|
||||
optional features available, but does not break compatibility with formats
|
||||
of the same major version.
|
||||
.
|
||||
.
|
||||
.SH KEYS
|
||||
.SS format
|
||||
.TP
|
||||
|
@ -85,6 +102,27 @@ String: The name of the model (will be displayed in the window title).
|
|||
version
|
||||
String: A version string for the model (currently unused).
|
||||
.
|
||||
.TP
|
||||
artist
|
||||
.ft B
|
||||
(Since version 0.5)
|
||||
.ft
|
||||
String: Name of artist or photographer
|
||||
.
|
||||
.TP
|
||||
modeler
|
||||
.ft B
|
||||
(Since version 0.5)
|
||||
.ft
|
||||
String: Name of modeler or rigger
|
||||
.
|
||||
.TP
|
||||
license
|
||||
.ft B
|
||||
(Since version 0.5)
|
||||
.ft
|
||||
String: SPDX license identifier (if none model is assumed to be proprietary)
|
||||
.
|
||||
.
|
||||
.SS part
|
||||
.TP
|
||||
|
@ -114,6 +152,54 @@ Float: How far the part will move towards the
|
|||
point.
|
||||
If negative, the part will move in the opposite direction.
|
||||
.
|
||||
.TP
|
||||
rot_factor, scale_factor
|
||||
.ft B
|
||||
(Since version 0.2)
|
||||
.ft
|
||||
Float: How much the part will rotate/scale.
|
||||
Default is 1.0.
|
||||
Higher values result in more exaggerated rotation or scaling.
|
||||
.
|
||||
.TP
|
||||
offset_factor
|
||||
.ft B
|
||||
(Since version 0.4)
|
||||
.ft
|
||||
float: How much the part will be moved by the offset bind.
|
||||
See
|
||||
.B offset_bind.
|
||||
.
|
||||
.TP
|
||||
origin
|
||||
.ft B
|
||||
(Since version 0.3)
|
||||
.ft
|
||||
Float Array: Origin (pivot point) of the model defined in
|
||||
normalized coordinates.
|
||||
Default is [0.0, 0.0].
|
||||
.
|
||||
.TP
|
||||
pos_offset, scale_offset
|
||||
.ft B
|
||||
(Since version 0.3)
|
||||
.ft
|
||||
Float Array: Positional offset or scale of a part.
|
||||
Default is [0.0, 0.0] for
|
||||
.B pos_offset
|
||||
and [1.0, 1.0] for
|
||||
.B scale_offset.
|
||||
.
|
||||
.TP
|
||||
offset_bind
|
||||
.ft B
|
||||
(Since version 0.4)
|
||||
.ft
|
||||
String: Offsets are vectors instead of actual points on the image.
|
||||
You can bind to an offset with this property, which will
|
||||
offset the part's position according to the vector.
|
||||
Possible values: offset-eyes
|
||||
.
|
||||
.SS textures
|
||||
.TP
|
||||
file
|
||||
|
|
BIN
models/default/back hair.webp
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
models/default/body.webp
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
models/default/clothes.webp
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
models/default/collar.webp
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
models/default/eye brows.webp
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
models/default/eye lids.webp
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
models/default/eye.webp
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
models/default/front hair.webp
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
models/default/head.webp
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
models/default/iris.webp
Normal file
After Width: | Height: | Size: 26 KiB |
91
models/default/model.toml
Normal file
|
@ -0,0 +1,91 @@
|
|||
[format]
|
||||
version_major = 0
|
||||
version_minor = 4
|
||||
|
||||
[model_info]
|
||||
name = "Default-chan"
|
||||
version = "1.0"
|
||||
|
||||
|
||||
[[part]]
|
||||
texture = "back hair.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = -0.1
|
||||
|
||||
[[part]]
|
||||
texture = "body.webp"
|
||||
bind = "head"
|
||||
rot_factor=0.3
|
||||
|
||||
[[part]]
|
||||
texture = "clothes.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.1
|
||||
rot_factor=0.3
|
||||
|
||||
[[part]]
|
||||
texture = "collar.webp"
|
||||
bind = "head"
|
||||
rot_factor=0.3
|
||||
|
||||
[[part]]
|
||||
texture = "head.webp"
|
||||
bind = "head"
|
||||
|
||||
[[part]]
|
||||
texture = "nose.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
||||
|
||||
[[part]]
|
||||
texture = "eye.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
||||
|
||||
[[part]]
|
||||
texture = "iris.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
||||
offset_bind = "offset-eyes"
|
||||
offset_factor = 0.03
|
||||
|
||||
[[part]]
|
||||
texture = "eye lids.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
||||
|
||||
[[part]]
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
||||
|
||||
[[part.textures]]
|
||||
file = "mouth closed.webp"
|
||||
|
||||
[[part.textures]]
|
||||
file = "mouth open.webp"
|
||||
trigger = "mouth-open"
|
||||
|
||||
[[part]]
|
||||
texture = "side hair.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.1
|
||||
|
||||
[[part]]
|
||||
texture = "front hair.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.2
|
||||
|
||||
[[part]]
|
||||
texture = "eye brows.webp"
|
||||
bind = "head"
|
||||
follow = "face"
|
||||
factor = 0.35
|
BIN
models/default/mouth closed.webp
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
models/default/mouth open.webp
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
models/default/nose.webp
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
models/default/side hair.webp
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
models/default/source.kra
Normal file
|
@ -1,10 +1,13 @@
|
|||
[format]
|
||||
version_major = 0
|
||||
version_minor = 2
|
||||
version_minor = 5
|
||||
|
||||
[model_info]
|
||||
name = "Richard Stallman"
|
||||
version = "1.0"
|
||||
version = "1.1"
|
||||
artist = "Ruben Rodriguez"
|
||||
modeler = "Epicalert"
|
||||
license = "CC-BY-4.0"
|
||||
|
||||
[[part]]
|
||||
texture = "bg.webp"
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
[format]
|
||||
version_major = 0
|
||||
version_minor = 1
|
||||
version_minor = 5
|
||||
|
||||
[model_info]
|
||||
name = "Test Model"
|
||||
version = "1.0"
|
||||
|
||||
artist = "Epicalert"
|
||||
modeler = "Epicalert"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
[[part]]
|
||||
texture = "head-base.png"
|
||||
|
|
|
@ -36,7 +36,7 @@ struct optData optData = {
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
"test",
|
||||
"default",
|
||||
0,
|
||||
};
|
||||
|
||||
|
|
|
@ -170,13 +170,20 @@ void cvFrame() {
|
|||
cv::rectangle(frame, eyeRect, cv::Scalar(255, 255, 255));
|
||||
|
||||
cv::Mat eyeROI;
|
||||
glm::vec2 eyeVector(0,0);
|
||||
|
||||
// prevent assertion failed when eye is partly outside of image
|
||||
if(eyeRect.x < 0 || eyeRect.x >= gray.cols || eyeRect.y < 0 || eyeRect.y >= gray.rows ||
|
||||
eyeRect.x + eyeRect.width >= gray.cols || eyeRect.y - eyeRect.height >= gray.rows) goto noeye;
|
||||
|
||||
eyeROI = gray(eyeRect);
|
||||
|
||||
glm::vec2 eyeVector(0,0);
|
||||
if (!optData.noEyes) {
|
||||
eyeVector = eyeDirection(eyeROI); // run pupil tracking algorithm and get look direction
|
||||
}
|
||||
|
||||
noeye:
|
||||
|
||||
//send control information to graphics
|
||||
float faceSize = landmarks[biggestFace][14].x - landmarks[biggestFace][2].x;
|
||||
|
||||
|
|
|
@ -23,6 +23,12 @@
|
|||
#include <cv.hpp>
|
||||
#include <args.hpp>
|
||||
#include <error.hpp>
|
||||
#include <input.hpp>
|
||||
|
||||
#ifndef NO_GRAPHICAL_DIALOG
|
||||
#include <boxer/boxer.h>
|
||||
#endif
|
||||
|
||||
|
||||
GLuint shader; //standard shader program used for all elements
|
||||
GLuint transUniform; //location of the "transMatrix" transformation matrix uniform in the shader
|
||||
|
@ -94,8 +100,12 @@ void initGraphics () {
|
|||
char *argv[1] = {(char*)"fc2d"};
|
||||
|
||||
glutInit(&argc, argv);
|
||||
glutCreateWindow(PROJECT_NAME);
|
||||
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
|
||||
glutInitWindowSize(512, 512);
|
||||
glutCreateWindow(PROJECT_NAME);
|
||||
|
||||
// input callback
|
||||
glutKeyboardFunc(keyInput);
|
||||
|
||||
glewExperimental = GL_TRUE;
|
||||
glewInit();
|
||||
|
@ -236,3 +246,7 @@ void updateModel(struct FaceData faceData) {
|
|||
//tell FreeGLUT to schedule a screen update
|
||||
glutPostRedisplay();
|
||||
}
|
||||
|
||||
void showModelInfo() {
|
||||
boxer::show(model->getInfoString().c_str(), "Model info", boxer::Style::Info);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <cv.hpp>
|
||||
#include <model.hpp>
|
||||
#include <modelpart.hpp>
|
||||
|
||||
extern GLuint transUniform;
|
||||
extern float windowAspectRatio;
|
||||
|
@ -28,4 +30,6 @@ void printShaderCompileLog(GLuint shader);
|
|||
|
||||
void updateModel(struct FaceData faceData);
|
||||
|
||||
void showModelInfo();
|
||||
|
||||
#endif
|
||||
|
|
12
src/input.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include <input.hpp>
|
||||
#include <graphics.hpp>
|
||||
|
||||
void keyInput(unsigned char key, int x, int y) {
|
||||
switch(key) {
|
||||
case 'i': // model info
|
||||
showModelInfo();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
6
src/input.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef INPUT_HPP
|
||||
#define INPUT_HPP
|
||||
|
||||
void keyInput(unsigned char key, int x, int y);
|
||||
|
||||
#endif
|
|
@ -12,7 +12,7 @@
|
|||
#define BUFFER_SIZE_TEXTURE 16777220 // 16 MiB
|
||||
|
||||
#define SUPPORTED_MODEL_MAJOR 0
|
||||
#define SUPPORTED_MODEL_MINOR 3
|
||||
#define SUPPORTED_MODEL_MINOR 5
|
||||
|
||||
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);
|
||||
|
@ -90,7 +90,29 @@ Model::Model(const char* path) {
|
|||
} else {
|
||||
name = nameResult.second;
|
||||
}
|
||||
|
||||
// get authors
|
||||
// artist (or photographer if image)
|
||||
auto artistResult = modelInfo->getString("artist");
|
||||
|
||||
if (artistResult.first) {
|
||||
artist = artistResult.second;
|
||||
}
|
||||
// rigger/modeler
|
||||
auto modelerResult = modelInfo->getString("modeler");
|
||||
|
||||
if (modelerResult.first) {
|
||||
modeler = modelerResult.second;
|
||||
}
|
||||
|
||||
// get license (SPDX identifier, if not present file is assumed to be proprietary)
|
||||
auto licenseResult = modelInfo->getString("license");
|
||||
|
||||
if (licenseResult.first) {
|
||||
license = licenseResult.second;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// parse parts
|
||||
auto partsDescArray = modelDesc.table->getArray("part");
|
||||
|
@ -212,3 +234,7 @@ std::string Model::getName() {
|
|||
return name;
|
||||
}
|
||||
|
||||
std::string Model::getInfoString() {
|
||||
return fmt::format("{}\n{}\n\nArtist: {}\nModeler: {}",
|
||||
name, license, artist, modeler);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ class Model {
|
|||
|
||||
std::string name;
|
||||
|
||||
std::string artist;
|
||||
std::string modeler;
|
||||
std::string license;
|
||||
|
||||
public:
|
||||
Model(const char* path);
|
||||
|
||||
|
@ -18,6 +22,7 @@ class Model {
|
|||
void updateTransforms(struct FaceData faceData);
|
||||
|
||||
std::string getName();
|
||||
std::string getInfoString();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|