播放 bvh 运动捕捉动画文件 金牌收录

1. 说明

bvh 是一种动作捕捉数据格式,里面包含骨骼的父子层级信息、动画帧序列。

编译完二进制后,在 bvh 文件属性上设置"打开程序"为编译好的二进制路径,后面双击 bvh 文件就可以预览 bvh 动画了。

2. 程序操作

  • 按空格键暂停或播放。
  • 按 M 键,切换 mesh 渲染模式或线条渲染模式。
  • mesh 模式下,按 L 键,绘制三角形线框。
  • 线条模式下,按 S 键,切换单线条预览或多线条预览。

3. 程序效果

线框渲染模式:

mesh 渲染模式 ( mesh 模式的线框可以背面隐藏,原理是画三角形,和上面不一样):

4.  程序

程序执行时,默认加载当前工作目录下的示例 dazhaohu.bvh,请【点击这里下载 dazhaohu.bvh】,下载后解压缩,放到项目当前路径下。

更多的 bvh 素材可从这个仓库下载:https://github.com/BandaiNamcoResearchInc/Bandai-Namco-Research-Motiondataset

程序启动后,需要几秒钟的初始化时间,请耐心等待。

/////////////////////////////////////////////////////////////
// bvh viewer
// Platform : Visual Studio 2022 (v143), EasyX_20220901
// Author   : 872289455@qq.com
// Date     : 2023-01-10
//
#ifndef UNICODE
#error only for wchar_t
#endif // !UNICODE

#include <easyx.h>
#include <assert.h>

#include <vector>
#include <unordered_map>
#include <fstream>
#include <functional>
#include <regex>

#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")

constexpr int WIDTH = 1280;
constexpr int HEIGHT = 720;

///////////////////////////////////////////////////////////////////////////////////
// Math
///////////////////////////////////////////////////////////////////////////////////
inline float Radians(float degrees) { return degrees * 0.0174532925f; }
inline float Degrees(float radians) { return radians * 57.295779513f; }

struct vec2
{
    float x, y;
    vec2(float a = 0.f, float b = 0.f) :
        x(a), y(b) {}
    vec2 operator*(float f) { return vec2(f * x, f * y); }
    vec2 operator-(const vec2& b) const { return vec2(x - b.x, y - b.y); }
    vec2 operator+(const vec2& b) const { return vec2(x + b.x, y + b.y); }
};
struct vec3
{
    union
    {
        struct
        {
            float x, y, z;
        };
        float data[3];
    };

    explicit vec3(float a = 0.f, float b = 0.f, float c = 0.f) :
        x(a), y(b), z(c) {}

    float& operator[](size_t i)
    {
        assert(i < 3);
        return data[i];
    }
    const float& operator[](size_t i) const
    {
        assert(i < 3);
        return data[i];
    }

    const vec3& operator=(const vec3& o)
    {
        x = o.x;
        y = o.y;
        z = o.z;
        return *this;
    }

    vec3 operator*(const float f) const { return vec3(f * x, f * y, f * z); }
    vec3 operator-() const { return vec3(-x, -y, -z); }
    vec3 operator-(float f) const { return vec3(x - f, y - f, z - f); }
    vec3 operator-(const vec3& b) const
    {
        return vec3(x - b.x, y - b.y, z - b.z);
    }
    vec3 operator+(const vec3& b) const
    {
        return vec3(x + b.x, y + b.y, z + b.z);
    }
    vec3& operator+=(const vec3& b)
    {
        x += b.x, y += b.y, z += b.z;
        return *this;
    }
};

struct vec4
{
    union
    {
        struct
        {
            float x, y, z, w;
        };
        float data[4];
    };

    vec4(float a = 0, float b = 0, float c = 0, float d = 0) :
        x(a), y(b), z(c), w(d) {}
    vec4(vec3 v, float f = 1.f) :
        x(v.x), y(v.y), z(v.z), w(f) {}

    float& operator[](size_t i)
    {
        assert(i < 4);
        return data[i];
    }

    const float& operator[](size_t i) const
    {
        assert(i < 4);
        return data[i];
    }

    vec4 operator*(const float f) const
    {
        return vec4(f * x, f * y, f * z, f * w);
    }
    vec3 to_vec3() { return vec3(x, y, z); }
};

vec3 operator*(float f, const vec3& vec)
{
    return vec * f;
}

vec4 operator*(float f, const vec4& vec)
{
    return vec * f;
}

float dot(const vec3& a, const vec3& b)
{
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

vec3 cross(const vec3& a, const vec3& b)
{
    return vec3(a.y * b.z - b.y * a.z, a.z * b.x - b.z * a.x,
                a.x * b.y - b.x * a.y);
}

float length(const vec3& v) { return sqrt(v.x * v.x + v.y * v.y + v.z * v.z); }

vec3 normalize(const vec3& v)
{
    float len_sq = v.x * v.x + v.y * v.y + v.z * v.z;
    if (len_sq < FLT_EPSILON)
        return v;
    return v * (1.f / sqrt(len_sq));
}

struct quat
{
    union
    {
        struct
        {
            float x, y, z, w;
        };
        struct
        {
            vec3 vec;
            float scalar;
        };
    };

    quat() :
        x(0), y(0), z(0), w(1) {}
    quat(float _x, float _y, float _z, float _w) :
        x(_x), y(_y), z(_z), w(_w) {}
    quat(const quat& r) { *this = r; }
    quat(float angle, const vec3& _axis)
    {
        float s = 0.f;
        float c = 0.f;
        float rads = Radians(angle);
        auto axis = normalize(_axis);
        s = sinf(rads * 0.5f);
        c = cosf(rads * 0.5f);
        x = s * axis.x;
        y = s * axis.y;
        z = s * axis.z;
        w = c;
    }
    vec3 rotate(vec3 from)
    {
        vec3 a = 2.f * dot(vec, from) * vec;
        vec3 b = (w * w - dot(vec, vec)) * from;
        vec3 c = 2.f * w * cross(vec, from);
        return a + b + c;
    }

    quat& operator=(const quat& r)
    {
        this->w = r.w;
        this->x = r.x;
        this->y = r.y;
        this->z = r.z;
        return *this;
    }

    quat& operator*=(const quat& r)
    {
        quat const p(*this);
        quat const q(r);
        this->w = p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z;
        this->x = p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y;
        this->y = p.w * q.y + p.y * q.w + p.z * q.x - p.x * q.z;
        this->z = p.w * q.z + p.z * q.w + p.x * q.y - p.y * q.x;
        return *this;
    }
};

quat operator-(const quat& q) { return quat(-q.x, -q.y, -q.z, -q.w); }

quat operator-(const quat& a, const quat& b)
{
    return quat(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);
}

vec3 operator*(quat const& q, vec3 const& v)
{
    vec3 const vec = q.vec;
    vec3 const uv(cross(vec, v));
    vec3 const uuv(cross(vec, uv));

    return v + 2.f * ((uv * q.w) + uuv);
}

quat operator*(quat const& q, float const& s)
{
    return quat(q.x * s, q.y * s, q.z * s, q.w * s);
}

quat operator/(quat const& q, float const& s)
{
    return quat(q.x / s, q.y / s, q.z / s, q.w / s);
}

quat operator+(quat const& a, quat const& b)
{
    return quat(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);
}

float dot(const quat& a, const quat& b)
{
    return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}

quat normalize(const quat& q)
{
    float lenSq = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w;
    if (lenSq < FLT_EPSILON)
    {
        return quat();
    }
    float i_len = 1.0f / sqrtf(lenSq);

    return quat(q.x * i_len, q.y * i_len, q.z * i_len, q.w * i_len);
}

quat nlerp(const quat& from, const quat& to, float t)
{
    return normalize(from + (to - from) * t);
}

quat slerp(quat const& x, quat const& y, float a)
{
    quat z = y;
    float cosTheta = dot(x, y);
    if (cosTheta < 0.f)
    {
        z = -y;
        cosTheta = -cosTheta;
    }
    if (cosTheta > 1.f - FLT_EPSILON)
    {
        return nlerp(x, y, a);
    }
    else
    {
        float angle = acos(cosTheta);
        return (x * sin((1.f - a) * angle) + z * sin(a * angle)) / sin(angle);
    }
}

// column major
struct mat4
{
    union
    {
        float v[16];
        struct
        {
            vec4 col[4];
        };
    };

    mat4(const vec4& c0 = vec4(1.f, 0.f, 0.f, 0.f),
         const vec4& c1 = vec4(0.f, 1.f, 0.f, 0.f),
         const vec4& c2 = vec4(0.f, 0.f, 1.f, 0.f),
         const vec4& c3 = vec4(0.f, 0.f, 0.f, 1.f))
    {
        col[0] = c0;
        col[1] = c1;
        col[2] = c2;
        col[3] = c3;
    }

    vec4& operator[](size_t i)
    {
        assert(i < 4);
        return col[i];
    }
    const vec4& operator[](size_t i) const
    {
        assert(i < 4);
        return col[i];
    }
    mat4 operator*(float f)
    {
        return mat4(col[0] * f, col[1] * f, col[2] * f, col[3] * f);
    }
    vec4 operator*(const vec4& v)
    {
        return vec4(
            col[0].x * v.x + col[1].x * v.y + col[2].x * v.z + col[3].x * v.w,
            col[0].y * v.x + col[1].y * v.y + col[2].y * v.z + col[3].y * v.w,
            col[0].z * v.x + col[1].z * v.y + col[2].z * v.z + col[3].z * v.w,
            col[0].w * v.x + col[1].w * v.y + col[2].w * v.z + col[3].w * v.w);
    }

    mat4 operator*(const mat4& m)
    {
        return mat4(*this * m.col[0], *this * m.col[1], *this * m.col[2],
                    *this * m.col[3]);
    }

    mat4& operator*=(const mat4& m)
    {
        *this* m.col[0];
        *this* m.col[1];
        *this* m.col[2];
        *this* m.col[3];
        return *this;
    }
};

mat4 transposed(const mat4& m)
{
    return mat4(vec4(m[0].x, m[1].x, m[2].x, m[3].x),
                vec4(m[0].y, m[1].y, m[2].y, m[3].y),
                vec4(m[0].z, m[1].z, m[2].z, m[3].z),
                vec4(m[0].w, m[1].w, m[2].w, m[3].w));
}

#define M4_3X3MINOR(c0, c1, c2, r0, r1, r2)                     \
    (m.v[c0 * 4 + r0] * (m.v[c1 * 4 + r1] * m.v[c2 * 4 + r2] -  \
                         m.v[c1 * 4 + r2] * m.v[c2 * 4 + r1]) - \
     m.v[c1 * 4 + r0] * (m.v[c0 * 4 + r1] * m.v[c2 * 4 + r2] -  \
                         m.v[c0 * 4 + r2] * m.v[c2 * 4 + r1]) + \
     m.v[c2 * 4 + r0] * (m.v[c0 * 4 + r1] * m.v[c1 * 4 + r2] -  \
                         m.v[c0 * 4 + r2] * m.v[c1 * 4 + r1]))

float determinant(const mat4& m)
{
    return m.v[0] * M4_3X3MINOR(1, 2, 3, 1, 2, 3) -
           m.v[4] * M4_3X3MINOR(0, 2, 3, 1, 2, 3) +
           m.v[8] * M4_3X3MINOR(0, 1, 3, 1, 2, 3) -
           m.v[12] * M4_3X3MINOR(0, 1, 2, 1, 2, 3);
}

mat4 adjugate(const mat4& m)
{
    mat4 ret;
    float* cv = &ret.v[0];
    cv[0] = M4_3X3MINOR(1, 2, 3, 1, 2, 3);
    cv[1] = -M4_3X3MINOR(1, 2, 3, 0, 2, 3);
    cv[2] = M4_3X3MINOR(1, 2, 3, 0, 1, 3);
    cv[3] = -M4_3X3MINOR(1, 2, 3, 0, 1, 2);
    cv[4] = -M4_3X3MINOR(0, 2, 3, 1, 2, 3);
    cv[5] = M4_3X3MINOR(0, 2, 3, 0, 2, 3);
    cv[6] = -M4_3X3MINOR(0, 2, 3, 0, 1, 3);
    cv[7] = M4_3X3MINOR(0, 2, 3, 0, 1, 2);
    cv[8] = M4_3X3MINOR(0, 1, 3, 1, 2, 3);
    cv[9] = -M4_3X3MINOR(0, 1, 3, 0, 2, 3);
    cv[10] = M4_3X3MINOR(0, 1, 3, 0, 1, 3);
    cv[11] = -M4_3X3MINOR(0, 1, 3, 0, 1, 2);
    cv[12] = -M4_3X3MINOR(0, 1, 2, 1, 2, 3);
    cv[13] = M4_3X3MINOR(0, 1, 2, 0, 2, 3);
    cv[14] = -M4_3X3MINOR(0, 1, 2, 0, 1, 3);
    cv[15] = M4_3X3MINOR(0, 1, 2, 0, 1, 2);
    return transposed(ret);
}

mat4 inverse(const mat4& m)
{
    float det = determinant(m);
    if (fabs(det) < FLT_EPSILON)
        return mat4();
    return adjugate(m) * (1.f / det);
}

///////////////////////////////////////////////////////////////////////////////////
// Orbit Camera
///////////////////////////////////////////////////////////////////////////////////

bool activeLineMode = false;
bool cullFace = true;
bool play = true;
bool meshMode = true;
bool singleLine = false;

class Camera
{
    vec3 pos, target;
    float zoom, aspect;
    mat4 projection, view;

    float speed = 0.1f;
    const float z_near = 1.f;
    const float z_far = 10000.f;
    const vec3 world_up = vec3(0.f, 1.f, 0.f);

public:
    Camera(vec3 position, vec3 center, float asp, float move_speed) :
        pos(position),
        target(center),
        zoom(25.f),
        aspect(asp),
        speed(move_speed)
    {
        updateProjection();
        updateView();
    }
    void processInput()
    {
        ExMessage msg;
        static bool firstClick = true;
        static int lastx, lasty;
        while (peekmessage(&msg, EX_MOUSE | EX_KEY))
        {
            // Drag Left Button: Rotate
            if (WM_MOUSEMOVE == msg.message)
            {
                if (false == msg.lbutton)
                {
                    firstClick = true;
                }
                else if (firstClick)
                {
                    firstClick = false;
                    lastx = msg.x;
                    lasty = msg.y;
                }
                else
                {
                    float xoff = 0.5f * (msg.x - lastx);
                    float yoff = 0.5f * (msg.y - lasty);
                    lastx = msg.x;
                    lasty = msg.y;
                    mouseMove(xoff, yoff);
                }
            }
            else if (WM_MOUSEWHEEL == msg.message)
            {
                if (msg.lbutton)
                    srcollPosition(speed * msg.wheel);
                else
                    scrollZoom(-0.01f * msg.wheel);
            }
            else if (WM_KEYDOWN == msg.message)
            {
                switch (msg.vkcode)
                {
                case 'L':
                    activeLineMode = !activeLineMode;
                    break;
                case 'K':
                    cullFace = !cullFace;
                    break;
                case 'M':
                    meshMode = !meshMode;
                    break;
                case 'S':
                    singleLine = !singleLine;
                    break;
                case VK_SPACE:
                    play = !play;
                    break;
                default:
                    break;
                }
            }
        }
    }
    mat4& viewMatrix() { return view; }
    mat4& projectionMatrix() { return projection; }
    vec3& position() { return pos; }
    float fov() { return zoom; }

private:
    void updateProjection()
    {
        float t = z_near * tan(Radians(0.5f * zoom));
        float b = -t;
        float r = t * aspect;
        float l = -r;
        projection[0] = vec4(2 * z_near / (r - l), 0.f, 0.f, 0.f);
        projection[1] = vec4(0.f, 2 * z_near / (t - b), 0.f, 0.f);
        projection[2] = vec4((r + l) / (r - l), (t + b) / (t - b),
                             -(z_far - z_near) / (z_far - z_near), -1.f);
        projection[3] =
            vec4(0.f, 0.f, -2 * z_far * z_near / (z_far - z_near), 0.f);
    }
    void updateView()
    {
        vec3 z_axis = normalize(pos - target);
        vec3 x_axis = normalize(cross(world_up, z_axis));
        vec3 y_axis = normalize(cross(z_axis, x_axis));

        view[0] = vec4(x_axis.x, y_axis.x, z_axis.x, 0.f);
        view[1] = vec4(x_axis.y, y_axis.y, z_axis.y, 0.f);
        view[2] = vec4(x_axis.z, y_axis.z, z_axis.z, 0.f);
        view[3] =
            vec4(-dot(x_axis, pos), -dot(y_axis, pos), -dot(z_axis, pos), 1.f);
    }

    void mouseMove(float xoff, float yoff)
    {
        vec3 front = normalize(target - pos);
        vec3 right = normalize(cross(front, world_up));
        vec3 up = normalize(cross(right, front));
        float angle = Degrees(acos(dot(front, world_up)));
        if ((yoff < 0 && (angle + yoff < 20)) ||
            (yoff > 0 && (angle + yoff > 160)))
            return;

        quat qx(-xoff, up);
        pos = qx.rotate(pos - target) + target;
        front = normalize(target - pos);
        right = normalize(cross(front, up));

        quat qy(-yoff, right);
        pos = qy.rotate(pos - target) + target;
        updateView();
    }

    void srcollPosition(float yoff)
    {
        vec3 front = normalize(target - pos);
        vec3 newPos = pos + yoff * front;
        vec3 pt = target - newPos;
        if (dot(pt, front) <= 0)
            return;
        pos = newPos;
        updateView();
    }

    void scrollZoom(float yoff)
    {
        if (zoom + yoff > 120 || zoom + yoff < 10)
            return;
        zoom += yoff;
        updateProjection();
    }
};

///////////////////////////////////////////////////////////////////////////////////
// Rendering
///////////////////////////////////////////////////////////////////////////////////

struct Mesh
{
    std::vector<vec3> m_position;
    std::vector<vec3> m_normal;
    std::vector<size_t> m_index;
    vec3 m_color;
};

struct Vertex
{
    vec3 pos, normal, color;
};

typedef void (*VERTEX_SHADER)(const Vertex& in, Vertex& out, vec4& mvpPos);
typedef vec3 (*FRAGMENT_SHADER)(const Vertex& in);

struct Program
{
    VERTEX_SHADER vertexShader;
    FRAGMENT_SHADER fragmentShader;
    Program(VERTEX_SHADER vert = nullptr, FRAGMENT_SHADER frag = nullptr,
            bool isFlat = false) :
        vertexShader(vert),
        fragmentShader(frag) {}
};

struct Light
{
    vec3 dir; // Light direction in eye coords.
    float La; // Ambient light intensity
    float Ld; // Diffuse light intensity
    float Ls; // Specular light intensity
    Light(vec3 d = vec3(0.f, 1.f, 1.f), float la = 0.1f, float ld = 0.4f,
          float ls = 0.8f) :
        dir(d),
        La(la), Ld(ld), Ls(ls) {}
};

// Sharing of uniform data between shader programs.
struct perFrameData
{
    mat4 modelViewMatrix;
    mat4 projectionMatrix;
    mat4 normalMatrix;
    mat4 viewportMatrix;
    mat4 MVP;

    Light light[2] = {Light(vec3(1.f, 2.5f, 1.f)), Light(vec3(1.f, -1.f, 1.f))};
} uniform;

void setMatrixUniform(mat4& v, mat4& proj)
{
    float w2 = WIDTH / 2.f;
    float h2 = HEIGHT / 2.f;
    uniform.modelViewMatrix = v;
    uniform.projectionMatrix = proj;
    uniform.normalMatrix = transposed(inverse(v));
    uniform.viewportMatrix =
        mat4(vec4(w2, 0.f, 0.f, 0.f), vec4(0.f, h2, 0.f, 0.f),
             vec4(0.f, 0.f, 1.f, 0.f), vec4(w2 - 0.5f, h2 - 0.5f, 0.f, 1.f));
    uniform.MVP = proj * v;
}

enum class PolygonType
{
    LINES = 0x0001,
    TRIANGLES = 0x0002,
};

class Render
{
    struct VertexBuffers
    {
        PolygonType m_type;
        std::vector<Vertex> m_VertexIn;
        std::vector<size_t> m_index;
    };

    DWORD* m_framebuf;
    VERTEX_SHADER m_vertexShader;
    FRAGMENT_SHADER m_fragmentShader;
    std::vector<VertexBuffers> m_vertex_buffers;

    std::vector<float> m_zbuf;

public:
    Render()
    {
        m_framebuf = GetImageBuffer();
        assert(m_framebuf);

        m_vertexShader = nullptr;
        m_fragmentShader = nullptr;

        m_zbuf.resize(WIDTH * HEIGHT);
    }

    size_t CreateMeshBuffer(const Mesh& mesh)
    {
        if (mesh.m_position.empty() || mesh.m_index.empty())
            return 0;
        m_vertex_buffers.emplace_back();
        auto& buf = m_vertex_buffers.back();
        buf.m_type = PolygonType::TRIANGLES;
        UpdateBuffer(m_vertex_buffers.size(), mesh);
        return m_vertex_buffers.size();
    }

    size_t CreateLineBuffers(const std::vector<vec3>& p)
    {
        if (p.empty())
            return 0;
        m_vertex_buffers.emplace_back();
        auto& buf = m_vertex_buffers.back();
        buf.m_type = PolygonType::LINES;
        UpdateBufferPosition(m_vertex_buffers.size(), p);
        return m_vertex_buffers.size();
    }

    void UpdateBufferPosition(size_t handle, const std::vector<vec3>& p)
    {
        auto& buf = m_vertex_buffers[handle - 1];
        buf.m_VertexIn.resize(p.size());
        for (size_t i = 0; i < p.size(); i++)
            buf.m_VertexIn[i].pos = p[i];
    }

    void UpdateBuffer(size_t handle, const Mesh& mesh)
    {
        assert(mesh.m_position.size() == mesh.m_normal.size());
        assert(mesh.m_index.size() % 3 == 0);
        auto& buf = m_vertex_buffers[handle - 1];
        buf.m_VertexIn.resize(mesh.m_position.size());
        buf.m_index = mesh.m_index;
        for (size_t i = 0; i < mesh.m_position.size(); i++)
        {
            buf.m_VertexIn[i].pos = mesh.m_position[i];
            buf.m_VertexIn[i].normal = mesh.m_normal[i];
            buf.m_VertexIn[i].color = mesh.m_color;
        }
    }

    void useProgram(const Program& shader)
    {
        m_vertexShader = shader.vertexShader;
        m_fragmentShader = shader.fragmentShader;
    }

    void drawElements(size_t handle)
    {
        assert(handle > 0 && handle <= m_vertex_buffers.size());
        auto& buf = m_vertex_buffers[handle - 1];

        if (buf.m_type == PolygonType::LINES)
            drawLineList(handle);
        else if (buf.m_type == PolygonType::TRIANGLES)
            drawTriangleList(handle);
    }

private:
    int indexOf(int x, int y) { return (HEIGHT - 1 - y) * WIDTH + x; }

    void setpixel(int x, int y, vec3 fragColor)
    {
        if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)
            return;
        setpixel(indexOf(x, y), fragColor);
    }

    void setpixel(int index, vec3 fragColor)
    {
        uint8_t r = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.x));
        uint8_t g = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.y));
        uint8_t b = (uint8_t)max(0.f, min(255.f, 255.f * fragColor.z));
        m_framebuf[index] = 0Xffu << 24 | r << 16 | g << 8 | b; // BGRA
    }

    bool insideTriangle(vec3& centric)
    {
        return centric.x >= 0.f && centric.x <= 1.f && centric.y >= 0.f &&
               centric.y <= 1.f && centric.z >= 0.f && centric.z <= 1.f;
    }

    vec3 barycentric(int x, int y, const vec4* _v)
    {
        vec2 p(static_cast<float>(x), static_cast<float>(y));
        vec2 a(_v[0].x, _v[0].y), b(_v[1].x, _v[1].y), c(_v[2].x, _v[2].y);
        vec2 v0 = b - a, v1 = c - a, v2 = p - a;
        float div = 1.f / (v0.x * v1.y - v1.x * v0.y);
        float v = div * (v2.x * v1.y - v1.x * v2.y);
        float w = div * (v0.x * v2.y - v2.x * v0.y);
        float u = 1.f - v - w;
        return vec3(u, v, w);
    }

    void drawLineList(size_t handle)
    {
        auto& buf = m_vertex_buffers[handle - 1];
        size_t vert_cnt = buf.m_VertexIn.size();
        std::vector<Vertex> VertexOut(vert_cnt);
        std::vector<vec4> mvpPos(vert_cnt);

        for (size_t i = 0; i < vert_cnt; i++)
            m_vertexShader(buf.m_VertexIn[i], VertexOut[i], mvpPos[i]);

        for (size_t i = 0; i < vert_cnt; i += 2)
        {
            Vertex vert[2] = {VertexOut[i], VertexOut[i + 1]};
            vec4 v[2] = {mvpPos[i], mvpPos[i + 1]};
            for (auto& p : v)
            {
                p = uniform.viewportMatrix * p;
                p = p * (1.f / p.w);
            }
            drawLine(v[0], v[1]);
        }
    }

    void drawTriangleList(size_t handle)
    {
        auto& buf = m_vertex_buffers[handle - 1];

        size_t vert_cnt = buf.m_VertexIn.size();
        const auto& index = buf.m_index;
        std::vector<Vertex> VertexOut(vert_cnt);
        std::vector<vec4> mvpPos(vert_cnt);

        std::fill_n(m_zbuf.begin(), m_zbuf.size(), FLT_MAX);

        for (size_t i = 0; i < vert_cnt; i++)
            m_vertexShader(buf.m_VertexIn[i], VertexOut[i], mvpPos[i]);

        for (size_t i = 0; i < vert_cnt; i += 3)
        {
            Vertex vert[3] = {VertexOut[index[i]], VertexOut[index[i + 1]],
                              VertexOut[index[i + 2]]};
            vec4 v[3] = {mvpPos[index[i]], mvpPos[index[i + 1]],
                         mvpPos[index[i + 2]]};
            for (auto& p : v)
            {
                p = uniform.viewportMatrix * p;
                p = p * (1.f / p.w);
            }

            if (cullFace && cross(v[1].to_vec3() - v[0].to_vec3(),
                                  v[2].to_vec3() - v[0].to_vec3())
                                    .z <= 0)
                continue;

            if (activeLineMode)
            {
                drawLine(v[0], v[1]);
                drawLine(v[0], v[2]);
                drawLine(v[1], v[2]);
                continue;
            }

            int left = (int)min(v[0].x, min(v[1].x, v[2].x)) - 1;
            int right = (int)max(v[0].x, max(v[1].x, v[2].x)) + 1;
            int bottom = (int)min(v[0].y, min(v[1].y, v[2].y)) - 1;
            int top = (int)max(v[0].y, max(v[1].y, v[2].y)) + 1;

            for (int x = left; x <= right; x++)
            {
                if (x >= WIDTH || x <= 0)
                    continue;
                for (int y = bottom; y <= top; y++)
                {
                    if (y >= HEIGHT || y <= 0)
                        continue;
                    vec3 centric = barycentric(x, y, v);
                    if (insideTriangle(centric))
                    {
                        float z = v[1].z;
                        int index = indexOf(x, y);
                        if (z < m_zbuf[index])
                        {
                            m_zbuf[index] = z;
                            setpixel(index, m_fragmentShader(vert[1]));
                        }
                    }
                }
            }
        }
    }

    void drawLine(vec4 begin, vec4 end)
    {
        vec3 lineColor(0.45f, 0.63f, 0.39f);
        int x0 = int(begin.x);
        int y0 = int(begin.y);
        int x1 = int(end.x);
        int y1 = int(end.y);

        int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
        int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
        int err = (dx > dy ? dx : -dy) / 2;
        while (setpixel(x0, y0, lineColor), x0 != x1 || y0 != y1)
        {
            int e2 = err;
            if (e2 > -dx)
            {
                err -= dy;
                x0 += sx;
            }
            if (e2 < dy)
            {
                err += dx;
                y0 += sy;
            }
        }
    }
};

///////////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////////

vec3 blinnPhongColor(const vec3& pos, const vec3& n, const vec3& color)
{
    float la = 0.f;
    vec3 ambient, diffuse, specular;
    for (auto& l : uniform.light)
    {
        ambient = ambient + l.La * color;
        vec3 s = l.dir;
        float sDotN = max(dot(s, n), 0.f);
        diffuse = diffuse + l.Ld * sDotN * color;
        if (sDotN > 0.0)
        {
            vec3 v = normalize(-pos);
            vec3 h = normalize(v + s);
            specular = specular + l.Ls * powf(max(dot(h, n), 0), 64) *
                                      vec3(0.8f, 0.8f, 0.8f);
        }
    }
    return ambient + diffuse + specular;
}

void vertex_shader(const Vertex& in, Vertex& out, vec4& mvpPos)
{
    out.pos = (uniform.modelViewMatrix * vec4(in.pos)).to_vec3();
    out.normal =
        normalize((uniform.normalMatrix * vec4(in.normal, 0.f)).to_vec3());
    out.color = blinnPhongColor(out.pos, out.normal, in.color);
    mvpPos = uniform.MVP * vec4(in.pos);
}

vec3 frag_shader(const Vertex& in) { return in.color; }

///////////////////////////////////////////////////////////////////////////////////
// bvh parser
///////////////////////////////////////////////////////////////////////////////////

class Joint
{
public:
    Joint(const std::string& name, std::shared_ptr<Joint> parent) :
        name_(name), parent_(parent) {}

    const vec3& offset() const { return offset_; }
    vec3& offset() { return offset_; }

    bool has_end_site() const { return has_end_site_; }
    bool& has_end_site() { return has_end_site_; }

    const vec3& end_site() const { return end_site_; }
    vec3& end_site() { return end_site_; }

    const std::string& name() const { return name_; }

    const std::vector<std::shared_ptr<Joint>>& children() const
    {
        return children_;
    }
    const std::vector<int>& associated_channels_indices() const
    {
        return associated_channels_indices_;
    }

    std::shared_ptr<Joint> parent() const { return parent_; }

    void AddChild(std::shared_ptr<Joint> child) { children_.push_back(child); }
    void AssociateChannel(int channel_index)
    {
        associated_channels_indices_.push_back(channel_index);
    }

private:
    const std::string name_;
    const std::shared_ptr<Joint> parent_;

    bool has_end_site_ = false;
    vec3 end_site_;
    vec3 offset_;
    std::vector<std::shared_ptr<Joint>> children_;
    std::vector<int> associated_channels_indices_;
};

struct Channel
{
    enum class Type
    {
        x_position,
        y_position,
        z_position,
        z_rotation,
        x_rotation,
        y_rotation
    };

    const Type type;
    const std::shared_ptr<Joint> target_joint;
};

class BvhObject
{
public:
    void open(const std::wstring& file_path);
    int frames() const { return frames_; }
    float frame_time() const { return frame_time_; }

    int frame_index(float time) const
    {
        int f = int(time / frame_time_);
        return f % frames_;
    }

    const std::vector<Channel>& channels() const { return channels_; }
    const std::vector<std::vector<float>>& motion() const { return motion_; }

    std::shared_ptr<const Joint> root_joint() const { return root_joint_; }

    std::vector<std::shared_ptr<const Joint>> GetJointList() const;

private:
    inline std::vector<std::string> split(const std::string& sequence,
                                          const std::string& pattern);
    inline std::vector<std::string> tokenize_next_line(std::ifstream& ifs);
    inline vec3 read_offset(const std::vector<std::string>& tokens);

private:
    int frames_ = 0;
    float frame_time_ = 0.f;

    std::vector<Channel> channels_;
    std::vector<std::vector<float>> motion_;

    std::shared_ptr<const Joint> root_joint_;
};

inline std::vector<std::string> BvhObject::split(const std::string& sequence,
                                                 const std::string& pattern)
{
    const std::regex regex(pattern);
    const std::sregex_token_iterator first{sequence.begin(), sequence.end(),
                                           regex, -1};
    const std::sregex_token_iterator last{};
    return std::vector<std::string>{
        first->str().empty() ? std::next(first) : first, last};
}

inline std::vector<std::string> BvhObject::tokenize_next_line(
    std::ifstream& ifs)
{
    while (true)
    {
        std::string line;
        if (std::getline(ifs, line))
        {
            if (line.empty())
                continue; // skip blank line
            return split(line, R"([\t\s]+)");
        }
        else
        {
            // reached EOF
            assert(false && "Failed to read the next line");
            return {};
        }
    }
}

inline vec3 BvhObject::read_offset(const std::vector<std::string>& tokens)
{
    assert(tokens.size() == 4 && tokens[0] == "OFFSET");
    float offset_x = std::stof(tokens[1]);
    float offset_y = std::stof(tokens[2]);
    float offset_z = std::stof(tokens[3]);
    return vec3(offset_x, offset_y, offset_z);
}

void BvhObject::open(const std::wstring& file_path)
{
    std::ifstream ifs(file_path);
    assert(ifs.is_open() && "Failed to open the input file.");
    if (!ifs.is_open())
        return;
    printf("loading %ls...\n", file_path.c_str());
    [&]() -> void {
        std::string line;
        std::vector<std::shared_ptr<Joint>> stack;
        while (std::getline(ifs, line))
        {
            const std::vector<std::string> tokens = split(line, R"([\t\s]+)");
            if (tokens.empty())
            {
                continue;
            }
            else if (tokens[0] == "HIERARCHY")
            {
                continue;
            }

            else if (tokens[0] == "ROOT" || tokens[0] == "JOINT")
            {
                assert(tokens.size() == 2 && "Failed to find a joint name");

                const std::string& joint_name = tokens[1];

                const std::shared_ptr<Joint> parent =
                    stack.empty() ? nullptr : stack.back();

                std::shared_ptr<Joint> new_joint =
                    std::make_shared<Joint>(joint_name, parent);
                if (parent)
                {
                    parent->AddChild(new_joint);
                }
                else
                {
                    root_joint_ = new_joint;
                }

                stack.push_back(new_joint);
                const std::vector<std::string> tokens_begin_block =
                    tokenize_next_line(ifs);
                assert(tokens_begin_block.size() == 1 &&
                       "Found two or more tokens");
                assert(tokens_begin_block[0] == "{" &&
                       "Could not find an expected '{'");
            }

            else if (tokens[0] == "OFFSET")
            {
                assert(tokens.size() == 4);
                const std::shared_ptr<Joint> current_joint = stack.back();
                current_joint->offset() = read_offset(tokens);
            }
            else if (tokens[0] == "CHANNELS")
            {
                assert(tokens.size() >= 2);
                const int num_channels = std::stoi(tokens[1]);

                assert(tokens.size() == num_channels + 2);
                for (int i = 0; i < num_channels; ++i)
                {
                    const std::shared_ptr<Joint> target_joint = stack.back();
                    const Channel::Type type =
                        [](const std::string& channel_type) {
                            if (channel_type == "Xposition")
                                return Channel::Type::x_position;
                            else if (channel_type == "Yposition")
                                return Channel::Type::y_position;
                            else if (channel_type == "Zposition")
                                return Channel::Type::z_position;
                            else if (channel_type == "Zrotation")
                                return Channel::Type::z_rotation;
                            else if (channel_type == "Xrotation")
                                return Channel::Type::x_rotation;
                            else if (channel_type == "Yrotation")
                                return Channel::Type::y_rotation;

                            assert(false &&
                                   "Could not find a valid channel type");
                            return Channel::Type();
                        }(tokens[i + 2]);

                    channels_.push_back(Channel{type, target_joint});

                    const int channel_index =
                        static_cast<int>(channels_.size() - 1);
                    target_joint->AssociateChannel(channel_index);
                }
            }
            else if (tokens[0] == "End")
            {
                assert(tokens.size() == 2 && tokens[1] == "Site");
                const std::shared_ptr<Joint> current_joint = stack.back();
                current_joint->has_end_site() = true;
                const std::vector<std::string> tokens_begin_block =
                    tokenize_next_line(ifs);
                assert(tokens_begin_block.size() == 1 &&
                       "Found two or more tokens");
                assert(tokens_begin_block[0] == "{" &&
                       "Could not find an expected '{'");

                const std::vector<std::string> tokens_offset =
                    tokenize_next_line(ifs);
                current_joint->end_site() = read_offset(tokens_offset);

                const std::vector<std::string> tokens_end_block =
                    tokenize_next_line(ifs);
                assert(tokens_end_block.size() == 1 &&
                       "Found two or more tokens");
                assert(tokens_end_block[0] == "}" &&
                       "Could not find an expected '}'");
            }

            else if (tokens[0] == "}")
            {
                assert(!stack.empty());
                stack.pop_back();
            }
            else if (tokens[0] == "MOTION")
            {
                return;
            }
        }
        assert(false && "Could not find the MOTION part");
    }();

    [&]() -> void {
        const std::vector<std::string> tokens_frames = tokenize_next_line(ifs);
        assert(tokens_frames.size() == 2);
        assert(tokens_frames[0] == "Frames:");
        frames_ = std::stoi(tokens_frames[1]);

        const std::vector<std::string> tokens_frame_time =
            tokenize_next_line(ifs);
        assert(tokens_frame_time.size() == 3);
        assert(tokens_frame_time[0] == "Frame" &&
               tokens_frame_time[1] == "Time:");
        frame_time_ = std::stof(tokens_frame_time[2]);

        motion_ = std::vector<std::vector<float>>(
            frames_, std::vector<float>(channels_.size()));
        for (int frame_index = 0; frame_index < frames_; ++frame_index)
        {
            const std::vector<std::string> tokens = tokenize_next_line(ifs);
            assert(tokens.size() == channels_.size() &&
                   "Found invalid motion data");

            for (int channel_index = 0; channel_index < channels_.size();
                 ++channel_index)
            {
                motion_[frame_index][channel_index] =
                    std::stof(tokens[channel_index]);
            }
        }
    }();
}

std::vector<std::shared_ptr<const Joint>> BvhObject::GetJointList() const
{
    std::vector<std::shared_ptr<const Joint>> joint_list;
    std::function<void(std::shared_ptr<const Joint>)> add_joint =
        [&](std::shared_ptr<const Joint> joint) -> void {
        if (!joint)
            return;
        joint_list.push_back(joint);
        for (const auto& child : joint->children())
            add_joint(child);
    };
    add_joint(root_joint_);
    return joint_list;
}

///////////////////////////////////////////////////////////////////////////////////
// BVH Animation Clip
///////////////////////////////////////////////////////////////////////////////////

struct Node
{
    std::string m_name;
    vec3 m_position;
    vec3 m_global_position;
    quat m_rotate;

    vec3 m_position_line[4];
    vec3 m_position_line_global[4];

    int m_parent = -1;
    std::vector<int> m_children;

    std::shared_ptr<const Joint> bvh_joint;
};

struct BBox
{
    vec3 m_min = vec3(FLT_MAX, FLT_MAX, FLT_MAX);
    vec3 m_max = vec3(FLT_MIN, FLT_MIN, FLT_MIN);

    vec3 Center() const { return 0.5f * (m_min + m_max); }
    float Extent() const { return length(m_max - m_min); }
};

class BVH
{
public:
    void Init(const std::wstring& bvh_path)
    {
        m_bvh.open(bvh_path);

        // get rest pos
        m_rest_pos = GetBoneNodeFromBVH();
        GetDebugPoint(m_rest_pos);
        GetGlobalPosition(m_rest_pos);
        if (m_rest_pos.empty())
        {
            printf("%ls: empty nodes.\n", bvh_path.c_str());
            exit(-1);
        }

        // generate debug lines
        UpdateLineFrom(m_rest_pos);
        UpdateMesh(m_rest_pos);

        // get all frame data
        for (size_t frame = 0; frame < m_bvh.frames(); frame++)
        {
            std::vector<Node> this_frame = m_rest_pos;
            for (auto& node : this_frame)
                std::tie(node.m_position, node.m_rotate) =
                    GetPos(node.bvh_joint, m_bvh, frame);
            GetDebugPoint(this_frame);
            GetGlobalPosition(this_frame);
            m_clip.push_back(this_frame);
        }

        // Get Bounding Box
        {
            std::vector<Node>* first_pos =
                m_clip.size() > 1 ? &m_clip[0] : &m_rest_pos;
            for (const auto& node : *first_pos)
            {
                const auto& pos = node.m_global_position;
                for (int i = 0; i < 3; i++)
                {
                    m_box.m_min[i] = min(m_box.m_min[i], pos[i]);
                    m_box.m_max[i] = max(m_box.m_max[i], pos[i]);
                }
            }
        }
    }

    void Update(float delta_time)
    {
        if (m_clip.size() < 2)
            return;
        static std::vector<Node> cur_pos = m_rest_pos;
        if (play)
        {
            m_time += delta_time;
            Sample(cur_pos, m_time);
        }
        UpdateLineFrom(cur_pos);
        UpdateMesh(cur_pos);
    }

    const BBox& GetBox() const { return m_box; }
    const std::vector<vec3>& GetLines() const { return m_lines; }
    const Mesh& GetMesh() const { return m_mesh; }

private:
    void UpdateLineFrom(const std::vector<Node>& pos)
    {
        m_lines.clear();
        for (const auto& node : pos)
        {
            if (node.m_parent == -1)
                continue;
            if (pos[node.m_parent].m_parent == -1)
                continue;
            if (singleLine)
            {
                m_lines.push_back(pos[node.m_parent].m_global_position);
                m_lines.push_back(node.m_global_position);
            }
            else
            {
                for (int i = 0; i < 4; i++)
                {
                    m_lines.push_back(pos[node.m_parent].m_global_position);
                    m_lines.push_back(node.m_position_line_global[i]);

                    m_lines.push_back(node.m_global_position);
                    m_lines.push_back(node.m_position_line_global[i]);

                    m_lines.push_back(node.m_position_line_global[i]);
                    m_lines.push_back(node.m_position_line_global[(i + 1) % 4]);
                }
            }
        }
    }

    void UpdateMesh(const std::vector<Node>& pos)
    {
        m_mesh.m_position.clear();
        m_mesh.m_normal.clear();
        m_mesh.m_index.clear();
        m_mesh.m_color = vec3(0.61f, 0.46f, 0.51f);

        for (const auto& node : pos)
        {
            if (node.m_parent == -1)
                continue;
            if (pos[node.m_parent].m_parent == -1)
                continue;

            auto& parent_node = pos[node.m_parent];

            for (int i = 0; i < 4; i++)
            {
                vec3 v[4];

                v[0] = parent_node.m_global_position;
                v[1] = node.m_position_line_global[(i + 1) % 4];
                v[2] = node.m_position_line_global[i];
                v[3] = node.m_global_position;

                vec3 normal = normalize(cross(v[1] - v[0], v[2] - v[0]));
                vec3 _normal_ = normalize(cross(v[2] - v[3], v[1] - v[3]));

                for (int j = 0; j < 3; j++)
                {
                    m_mesh.m_position.push_back(v[j]);
                    m_mesh.m_normal.push_back(normal);
                    m_mesh.m_index.push_back(m_mesh.m_position.size() - 1);
                }

                for (int j = 3; j >= 1; j--)
                {
                    m_mesh.m_position.push_back(v[j]);
                    m_mesh.m_normal.push_back(_normal_);
                    m_mesh.m_index.push_back(m_mesh.m_position.size() - 1);
                }
            }
        }
    }

    std::vector<Node> GetBoneNodeFromBVH()
    {
        std::vector<Node> nodes_lists;
        auto joints_list = m_bvh.GetJointList();

        if (m_bvh.channels().empty() || m_bvh.motion().empty() ||
            joints_list.empty() || m_bvh.frames() == 0)
        {
            printf("bvh error.\n");
            exit(-1);
        }

        std::unordered_map<const Joint*, size_t> joints_map;
        for (size_t i = 0; i < joints_list.size(); i++)
        {
            assert(joints_list[i]->associated_channels_indices().size() == 3 ||
                   joints_list[i]->associated_channels_indices().size() == 6);
            joints_map.insert({joints_list[i].get(), i});
        }
        auto FindIndex = [&](const Joint* jnt) -> int {
            if (!jnt)
                return -1;
            auto it = joints_map.find(jnt);
            if (it != joints_map.end())
                return (int)it->second;

            printf("FindIndex: cannot find joint: %s\n", jnt->name().c_str());
            return -1;
        };
        for (size_t i = 0; i < joints_list.size(); i++)
        {
            const auto& joint = joints_list[i];

            nodes_lists.emplace_back();
            auto& node = nodes_lists.back();
            node.m_name = joint->name();

            node.bvh_joint = joint;

            node.m_position = joint->offset();
            node.m_parent = FindIndex(joint->parent().get());
            for (auto& chd : joint->children())
                node.m_children.push_back(FindIndex(chd.get()));
        }
        return nodes_lists;
    }

    void GetDebugPoint(std::vector<Node>& nodes_lists)
    {
        for (auto& node : nodes_lists)
            GetDebugPoint(node.m_position_line, node.m_position);
    }

    void GetDebugPoint(vec3* points, const vec3& position)
    {
        if (length(position) < FLT_EPSILON)
            return;

        vec3 ortho = vec3(1, 0, 0);
        if (fabsf(position.y) < fabsf(position.x))
            ortho = vec3(0, 1, 0);
        if (fabsf(position.z) < fabs(position.y) &&
            fabs(position.z) < fabsf(position.x))
            ortho = vec3(0, 0, 1);

        vec3 axis = normalize(cross(position, ortho));

        vec3 piv = 0.3f * position;

        quat qx(25.f, axis);
        points[0] = qx.rotate(piv);

        quat qx0(90.f, position);
        points[1] = qx0.rotate(points[0]);
        points[2] = qx0.rotate(points[1]);
        points[3] = qx0.rotate(points[2]);
    }

    inline Node Combine(const Node& parent, const Node& child)
    {
        Node out = parent;
        out.m_rotate *= child.m_rotate;
        out.m_position = parent.m_position + parent.m_rotate * child.m_position;

        for (int i = 0; i < 4; i++)
            out.m_position_line[i] =
                parent.m_position + parent.m_rotate * child.m_position_line[i];

        return out;
    }

    void GetGlobalPosition(std::vector<Node>& nodes_lists)
    {
        for (size_t i = 0; i < nodes_lists.size(); i++)
        {
            Node result = nodes_lists[i];
            while (result.m_parent >= 0)
            {
                Node parent = nodes_lists[result.m_parent];
                result = Combine(parent, result);
            }
            nodes_lists[i].m_global_position = result.m_position;
            for (int j = 0; j < 4; j++)
                nodes_lists[i].m_position_line_global[j] =
                    result.m_position_line[j];
        }
    }

    std::pair<vec3, quat> GetPos(std::shared_ptr<const Joint> joint,
                                 const BvhObject& bvh, size_t frame)
    {
        assert(joint);
        assert(frame < bvh.frames() && "Invalid frame is specified.");
        assert(joint->associated_channels_indices().size() == 3 ||
               joint->associated_channels_indices().size() == 6);

        vec3 t1 = joint->offset();
        quat r1;

        // Apply time-varying transformations
        for (int channel_index : joint->associated_channels_indices())
        {
            const Channel& channel = bvh.channels()[channel_index];
            const float value = bvh.motion()[frame][channel_index];
            switch (channel.type)
            {
            case Channel::Type::x_position:
                t1.x = value;
                break;
            case Channel::Type::y_position:
                t1.y = value;
                break;
            case Channel::Type::z_position:
                t1.z = value;
                break;
            case Channel::Type::x_rotation:
                r1 *= quat(value, vec3(1.f, 0.f, 0.f));
                break;
            case Channel::Type::y_rotation:
                r1 *= quat(value, vec3(0.f, 1.f, 0.f));
                break;
            case Channel::Type::z_rotation:
                r1 *= quat(value, vec3(0.f, 0.f, 1.f));
                break;
            }
        }
        return {t1, r1};
    }

    inline vec3 Interpolate(const vec3& a, const vec3& b, float t)
    {
        return (1.f - t) * a + t * b;
    }

    inline quat Interpolate(const quat& a, const quat& b, float t)
    {
        quat result = slerp(a, b, t);
        return normalize(result);
    }

    void Sample(std::vector<Node>& current_pos, float time)
    {
        int this_frame = m_bvh.frame_index(time);
        int next_frame = (this_frame + 1) % m_bvh.frames();

        float t = fmodf(time, m_bvh.frame_time());

        auto& frame_1 = m_clip[this_frame];
        auto& frame_2 = m_clip[next_frame];

        assert(current_pos.size() == frame_1.size());
        assert(frame_1.size() == frame_2.size());

        for (size_t i = 0; i < frame_1.size(); i++)
        {
            const auto& nd1 = frame_1[i];
            const auto& nd2 = frame_2[i];
            current_pos[i].m_position =
                Interpolate(nd1.m_position, nd2.m_position, t);
            current_pos[i].m_global_position =
                Interpolate(nd1.m_global_position, nd2.m_global_position, t);
            current_pos[i].m_rotate =
                Interpolate(nd1.m_rotate, nd2.m_rotate, t);
            for (int j = 0; j < 4; j++)
                current_pos[i].m_position_line_global[j] =
                    Interpolate(nd1.m_position_line_global[j],
                                nd2.m_position_line_global[j], t);
        }
    }

private:
    BvhObject m_bvh;
    std::vector<Node> m_rest_pos;
    std::vector<std::vector<Node>> m_clip;
    BBox m_box;
    std::vector<vec3> m_lines;
    Mesh m_mesh;

    float m_time = 0.f;
};

std::vector<vec3> GetGround()
{
    float per_len = 10.f;
    int cell_num = 8;
    float _min = -per_len * cell_num;
    std::vector<vec3> ret;
    for (int i = 0; i <= cell_num * 2; i++)
    {
        vec3 l[4];
        l[0] = vec3(_min + per_len * i, 0, _min);
        l[1] = vec3(_min + per_len * i, 0, -_min);
        l[2] = vec3(_min, 0, _min + per_len * i);
        l[3] = vec3(-_min, 0, _min + per_len * i);
        for (int j = 0; j < 4; j++)
            ret.push_back(l[j]);
    }
    return ret;
}

// https://codebus.cn/yangw/about-unicode
// ANSI 转换为 Unicode 编码
inline std::wstring LocalToUnicode(const char* src)
{
    std::wstring dst;
    int len = MultiByteToWideChar(CP_ACP, 0, src, -1, NULL, 0);
    dst.resize(len);
    MultiByteToWideChar(CP_ACP, 0, src, -1, &dst[0], (int)dst.size());
    return dst;
}

int main(int argc, char* argv[])
{
#ifdef NDEBUG
#define SHOW_CONSOLE
#else
#define SHOW_CONSOLE , 1
#endif
    int font_height = 20;

    HWND window = initgraph(WIDTH, HEIGHT SHOW_CONSOLE);

    if (!window)
        exit(-1);
    settextstyle(font_height, 0, L"楷体");

    std::wstring bvh_file = L"dazhaohu.bvh";

    if (argc > 1)
        bvh_file = LocalToUnicode(argv[1]);
    if (PathFileExists(bvh_file.c_str()) == false)
    {
        printf("file not exists: %ls\n", bvh_file.c_str());
        return -1;
    }
    const wchar_t* ext = PathFindExtension(bvh_file.c_str());
    if (!ext || std::wstring(ext) != L".bvh")
    {
        printf("extension error: %ls\n", bvh_file.c_str());
        return -1;
    }

    SetWindowText(window, bvh_file.c_str());

    BVH bvh;
    bvh.Init(bvh_file);

    vec3 box_center = bvh.GetBox().Center();
    float box_ext = bvh.GetBox().Extent();

    vec3 cam_pos = box_center;
    cam_pos.z += 4 * box_ext;
    cam_pos.y = cam_pos.x = 0.65f * cam_pos.z;

    Render render;
    Camera camera(cam_pos, box_center, (float)WIDTH / (float)HEIGHT,
                  0.001f * box_ext);
    Program prog(vertex_shader, frag_shader);
    render.useProgram(prog);

    size_t ground_buf = render.CreateLineBuffers(GetGround());
    size_t line_buf = render.CreateLineBuffers(bvh.GetLines());
    size_t mesh_buf = render.CreateMeshBuffer(bvh.GetMesh());
    assert(ground_buf);
    assert(line_buf);
    assert(mesh_buf);

    TCHAR buf[128];
    double deltaTime = 0.f;
    double lastTime = 0.f;
    double currentTime = 0.f;
    uint64_t start, now, frequency;
    QueryPerformanceCounter((LARGE_INTEGER*)&start);
    QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
    BeginBatchDraw();

    while (true)
    {
        QueryPerformanceCounter((LARGE_INTEGER*)&now);
        currentTime = (double)(now - start) / frequency;
        deltaTime = currentTime - lastTime;
        lastTime = currentTime;

        camera.processInput();
        setMatrixUniform(camera.viewMatrix(), camera.projectionMatrix());

        cleardevice();

        bvh.Update((float)deltaTime);
        render.UpdateBufferPosition(line_buf, bvh.GetLines());
        render.UpdateBuffer(mesh_buf, bvh.GetMesh());

        render.drawElements(ground_buf);
        if (meshMode)
            render.drawElements(mesh_buf);
        else
            render.drawElements(line_buf);

        outtextxy(0, 0 * font_height, L"空格键 : 播放或暂停");
        outtextxy(0, 1 * font_height, L"按 M 键: 切换三角形网格或线段渲染模式");
        outtextxy(0, 2 * font_height, L"按 S 键: 线段模式下单线条");
        outtextxy(0, 3 * font_height, L"按 L 键: 网格模式下只绘制边界");
        outtextxy(0, 4 * font_height, L"按 K 键: 网格模式背面剔除");

        outtextxy(0, 5 * font_height, L"鼠标滚轮    : 视角缩放");
        outtextxy(0, 6 * font_height, L"拖拽鼠标左键: 旋转相机");
        outtextxy(0, 7 * font_height, L"按左键+滚轮 : 相机前进或后退");

        swprintf_s(buf, _T("相机位置 : (%.3f, %.3f, %.3f)"),
                   camera.position().x, camera.position().y,
                   camera.position().z);
        outtextxy(0, 8 * font_height, buf);
        swprintf_s(buf, _T("视角大小(垂直): %.3f°"), camera.fov());
        outtextxy(0, 9 * font_height, buf);
        swprintf_s(buf, _T("fps : %d"), (int)(1.f / deltaTime));
        outtextxy(WIDTH - font_height * 6, 0 * font_height, buf);
        swprintf_s(buf, _T("time: %.1fms"), 1000 * deltaTime);
        outtextxy(WIDTH - font_height * 6, 1 * font_height, buf);
        FlushBatchDraw();
    }

    return 0;
}

5. 参考资料

bvh 格式比较简单,定义比较简单清晰,相关定义及字段含义请参考:https://research.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html

渲染部分的代码参考:https://codebus.cn/renderluu/render-vase

bvh 解析的代码参考:https://github.com/yuki-koyama/bvh11

数学库的实现及接口风格参考:https://github.com/g-truc/glm

添加评论