播放 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

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

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

添加评论