播放 bvh 运动捕捉动画文件
2023-1-10 ~ 2023-11-1
(0)
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。
程序启动后,需要几秒钟的初始化时间,请耐心等待。
5. 修改日志
2023-11-01:加速 bvh 文件的加载、版本号显示、bug 修复。
2023-01-10:提交代码。
/////////////////////////////////////////////////////////////
// bvh viewer
// Platform : Visual Studio 2022 (v143), EasyX_20230723
// 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 <sstream>
#include <functional>
#include <regex>
#include <shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")
constexpr int WIDTH = 1280;
constexpr int HEIGHT = 720;
constexpr int BVH_VIEW_VERSION_MAJOR = 1;
constexpr int BVH_VIEW_VERSION_MINOR = 1;
constexpr int BVH_VIEW_VERSION_PATCH = 0;
///////////////////////////////////////////////////////////////////////////////////
// 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()));
#if 0
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]);
}
}
#else
for (int frame_index = 0; frame_index < frames_; ++frame_index)
{
std::string line;
if (std::getline(ifs, line))
{
if (line.empty())
continue;
std::stringstream ss;
ss << line;
for (int channel_index = 0; channel_index < channels_.size(); ++channel_index)
{
ss >> motion_[frame_index][channel_index];
}
}
}
#endif
}();
}
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)
{
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;
}
const wchar_t* VersionInfo(void)
{
static std::wstring version;
if (!version.empty())
return version.c_str();
int year = 0, month = 0, day = 0;
int hour = 0, minute = 0, second = 0;
const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char date[] = __DATE__;
char time[] = __TIME__;
date[3] = date[6] = 0;
time[2] = time[5] = 0;
year = atoi(&date[7]);
day = atoi(&date[4]);
for (int i = 0; i < 12; i++)
{
if (!strncmp(&date[0], months[i], 3))
{
month = i + 1;
break;
}
}
hour = atoi(&time[0]);
minute = atoi(&time[3]);
second = atoi(&time[6]);
char buff[256] = {'\0'};
snprintf(buff, 256, "v%d.%d.%d_build%04d%02d%02d_%02d%02d%02d", BVH_VIEW_VERSION_MAJOR, BVH_VIEW_VERSION_MINOR, BVH_VIEW_VERSION_PATCH, year, month, day, hour, minute, second);
version = LocalToUnicode(&buff[0]);
return version.c_str();
}
int main(int argc, char* argv[])
{
#ifdef NDEBUG
#define SHOW_CONSOLE 0
#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);
const auto &bbox = bvh.GetBox();
vec3 box_center = bbox.Center();
float box_ext = bbox.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(box_ext * 0.1f));
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);
outtextxy(5, HEIGHT - 1 * font_height - 5, VersionInfo());
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
添加评论
取消回复