2
votes

I have made a container class that basicly contains all the information I need for rendering an animation. I'm using the Assimp library to load the animation. Then assigning the data from the scene->mVertices etc. to my array buffers, what I'm having trouble figuring out is how I'm supposed to get that data for other frames into my buffers!

I know that there is a function called HasAnimations(), and also a aiAnimation **mAnimations. But what I can't find any data in there that is relevant to getting the next set of vertex data.

I have managed to load a series of obj-files with it and draw them in order to comfirm that my class works correctly. But obviously I would prefer or actaully need to use something else when I want to expand to the real deal. As loading 250 frames individually takes a couple of minutes. (Loading a simple animation should be done in about 5 seconds tops, right?)

I'm open to using any kind of file format. But I need to know how to set it up in Blender so that the animations will get exported. As I also seem to fail horribly at for now, as I have little experience with Blender.

I've been searching for tutorials on this library and blender exporting for 2 days now, and found almost nothing useful. I did check out the documentation for Assimp as well, which took me so far, but doesn't explain a thing about how aiAnimation affects the vertices. Or how I can get the other frames of data I need.

1

1 Answers

2
votes

Well, I did manage to make it work after endless hours! Sort of... I made a model that is transformed in a loop x+5, y+5, x-5, y-5... What I ended up doing, well it was the only thing I could think of anyways. Is reading the data from the scene->mAnimations[] and this basicly consists of an array of the keyframes only. So I had to interpolate all the vertices myself, (which is always a funny task to approach!).

Effectivly:

  1. You get the time the keyframe should have interpolated fully.
  2. Then subtract where the object currently is to figure out how much you need to move.
  3. Now you need to figure out how much to move each step, so I took the easiest solution, divided it by how many frames the movement should be splitted over.
  4. Now it was just a matter of updating all my vertices before sending them to the VBO. (This step is probably a little varying depending on your data-setup)

After those steps, I got something that looks like this:

Header:

class AssimpMesh {
private:
    struct ShaderProgram {
        //Shader data
        GLuint program;
        string programName;
        vector <GLuint> shaders, uniforms;
    };

    struct MeshData {
        //Mesh data
        GLuint meshArray;
        vector <GLuint> buffers;
        vector <string> bufferNames;

        //Shader data
        ShaderProgram *shader;

        vector <aiVector3D> transedVertices;
        int totalIndices;
    };

    struct Frame {
        vector <MeshData*> meshes;
    };

    struct Animation {
        string name;
        vector <Frame*> frames;
    };

    //Global shader data
    ShaderProgram *globalShader;

    //Model data
    Assimp::Importer importer;
    const aiScene *scene;

    //Mesh data
    bool initialized, perMeshShading;
    vector <Animation*> animations;
    int currentFrame, currentAnimation;
    Uint32 lastFrameTicks;
    Transform *transform;
    glm::mat4 projectionView;
    aiVector3D lightPosition;

    void loadScene(string filePath);
    void loadAnimation(Animation *animation, int numFrames);
    void initMesh(aiMesh *mesh, MeshData *data);
    ...

public:
    AssimpMesh(string filePath);
    ~AssimpMesh();

    void draw();
    ...
};

Source:

void AssimpMesh::loadScene(string filePath) {
    //Load animation file
    scene = importer.ReadFile(filePath.c_str(), aiProcessPreset_TargetRealtime_MaxQuality);

    if (scene) {
        if (scene->HasAnimations()) {
            for (int i = 0; i < scene->mNumAnimations; i++) {
                aiAnimation *anime = scene->mAnimations[i];
                int framesInAnimation = ceil(anime->mDuration * ANIMATION_IMPORT_FPS);

                Animation *animation = new Animation();
                animation->name = anime->mName.C_Str();
                loadAnimation(animation, framesInAnimation);
                animations.push_back(animation);
            }
        }
        else {
            Animation *animation = new Animation();
            animation->name = "Default";
            loadAnimation(animation, 1);
            animations.push_back(animation);
        }
        printf("Done loading '%s'\n", filePath.c_str());
    }
    else {
        //Report error
        printf("Assimp error: %s\n", importer.GetErrorString());
    }
}

void AssimpMesh::loadAnimation(Animation *animation, int numFrames) {
    int nextKeyframe = -1;
    int nextKeyframeId = -1;
    int transedFrames = 0;
    aiVector3D transPos = aiVector3D();
    aiVector3D transVec = aiVector3D();

    for (int f = 0; f < numFrames; f++) {
        Frame *frame = new Frame();

        if (f > nextKeyframe && nextKeyframe < numFrames) {
            //Get the new keyframe
            aiAnimation *anime = scene->mAnimations[animations.size()];
            aiNodeAnim *aniNode = anime->mChannels[0];
            aiVectorKey key = aniNode->mPositionKeys[++nextKeyframeId];
            nextKeyframe = ceil(key.mTime * ANIMATION_IMPORT_FPS);

            if (!nextKeyframeId) {
                transVec = key.mValue;
                transPos = key.mValue;
            }
            else {
                int transFrames = nextKeyframe - (f - 1);
                aiVector3D transDir = key.mValue - transPos;
                transPos = key.mValue;
                transVec = transDir;
                transVec /= transFrames;
                transedFrames = 0;
            }
        }

        if (scene->HasLights()) {
            aiLight *light = scene->mLights[0];
            //lightPosition = light->mPosition;
        }

        //Put data into vertex arrays
        transedFrames++;
        aiMesh *mesh;
        MeshData *data;
        for (int i = 0; i < scene->mNumMeshes; i++) {
            mesh = scene->mMeshes[i];
            data = new MeshData();

            if (!i) {
                for (int j = 0; j < mesh->mNumVertices; j++) {
                    if (!f) {
                        data->transedVertices.push_back(mesh->mVertices[j] + transVec);
                    }
                    else {
                        data->transedVertices.push_back(animation->frames[f-1]->meshes[i]->transedVertices[j] + transVec);
                    }
                }
            }

            //Assign VBO
            initMesh(mesh, data);

            //Assign shader
            if (perMeshShading) {
                initShader(mesh, data);
                setUniforms(mesh, data);
            }

            frame->meshes.push_back(data);
        }

        animation->frames.push_back(frame);
    }
}

void AssimpMesh::initMesh(aiMesh *mesh, MeshData *data) {
    //Buffer for temporary storage of new ids
    GLuint id;

    //Make vertex array
    if (!initialized) {
        glGenVertexArrays(1, &id);
    }
    data->meshArray = id;

    //Tell OpenGL to use this array
    glBindVertexArray(id);

    //Assign vertices
    if (mesh->HasPositions()) {
        //Make buffer
        if (!initialized) {
            glGenBuffers(1, &id);
        }
        data->buffers.push_back(id);
        data->bufferNames.push_back("Positions");

        //Set buffer data
        glBindBuffer(GL_ARRAY_BUFFER, id);
        if (data->transedVertices.size()) {
            glBufferData(GL_ARRAY_BUFFER, sizeof(aiVector3D) * data->transedVertices.size(), &data->transedVertices[0], GL_STATIC_DRAW);
        }
        else {
            glBufferData(GL_ARRAY_BUFFER, sizeof(aiVector3D) * mesh->mNumVertices, &mesh->mVertices[0], GL_STATIC_DRAW);
        }
        //Set shader attribute data
        glEnableVertexAttribArray(VBO_VERTEX);
        glVertexAttribPointer(VBO_VERTEX, 3, GL_FLOAT, GL_FALSE, NULL, NULL);
    }

    unsigned int matId = mesh->mMaterialIndex;
    aiMaterial *material = scene->mMaterials[matId];
    vector <aiColor3D> colors;
    aiColor3D diffuse(0, 0, 0);
    material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);

    for (int i = 0; i < mesh->mNumVertices; i++) {
        colors.push_back(diffuse);
    }

    //Make buffer
    if (!initialized) {
        glGenBuffers(1, &id);
    }
    data->buffers.push_back(id);
    data->bufferNames.push_back("Colors");

    //Set buffer data
    glBindBuffer(GL_ARRAY_BUFFER, id);
    glBufferData(GL_ARRAY_BUFFER, sizeof(aiColor3D) * mesh->mNumVertices, &colors.front(), GL_STATIC_DRAW);

    //Set shader attribute data
    glEnableVertexAttribArray(VBO_COLOR);
    glVertexAttribPointer(VBO_COLOR, 3, GL_FLOAT, GL_FALSE, NULL, NULL);

    //Assign texture coords
    if (mesh->HasTextureCoords(0)) {
        //Make buffer
        if (!initialized) {
            glGenBuffers(1, &id);
        }
        data->buffers.push_back(id);
        data->bufferNames.push_back("TextureCoords");

        //Set buffer data
        glBindBuffer(GL_ARRAY_BUFFER, id);
        glBufferData(GL_ARRAY_BUFFER, sizeof(aiVector3D) * mesh->mNumVertices, &mesh->mTextureCoords[0], GL_STATIC_DRAW);

        //Set shader attribute data
        glEnableVertexAttribArray(VBO_TEXCORD);
        glVertexAttribPointer(VBO_TEXCORD, 3, GL_FLOAT, GL_FALSE, NULL, NULL);
    }

    //Assign colors
    if (mesh->HasNormals()) {
        //Make buffer
        if (!initialized) {
            glGenBuffers(1, &id);
        }
        data->buffers.push_back(id);
        data->bufferNames.push_back("Normals");

        //Set buffer data
        glBindBuffer(GL_ARRAY_BUFFER, id);
        glBufferData(GL_ARRAY_BUFFER, sizeof(aiVector3D) * mesh->mNumVertices, &mesh->mNormals[0], GL_STATIC_DRAW);

        //Set shader attribute data
        glEnableVertexAttribArray(VBO_NORMAL);
        glVertexAttribPointer(VBO_NORMAL, 3, GL_FLOAT, GL_FALSE, NULL, NULL);
    }

    if (mesh->HasFaces()) {
        vector <unsigned int> indices;
        aiFace face;
        for (int i = 0; i < mesh->mNumFaces; i++) {
            face = mesh->mFaces[i];
            for (int j = 0; j < face.mNumIndices; j++) {
                indices.push_back(face.mIndices[j]);
            }
        }
        data->totalIndices = indices.size();

        //Make buffer
        if (!initialized) {
            glGenBuffers(1, &id);
        }
        data->buffers.push_back(id);
        data->bufferNames.push_back("Faces");

        //Set buffer data
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices.size(), &indices.front(), GL_STATIC_DRAW);
    }
}

Of course, it doesn't work for everything yet. Actually only translation and the entire model. Apparently it doesn't read name values correctly so I can't which meshes the animation is meant for. But it got me going, maybe someone will find this helpful. =)