protot/src/modules/CharacterModule.cc

585 lines
16 KiB
C++

#include <map>
#include <vector>
#include <cstdint>
#include <iostream>
#include <sstream>
#include "3rdparty/ocornut-imgui/imgui.h"
#include "imgui/imgui.h"
#include <bx/fpumath.h>
#include "RuntimeModule.h"
#include "Globals.h"
#include "modules/RenderModule.h"
#include "Serializer.h"
#include "Timer.h"
#include "string_utils.h"
#include "CharacterModule.h"
#include "LuaTableTypes.h"
#include "rbdl/addons/luamodel/luatables.h"
// forward declaration of a function that is in the LuaModel addon
// of RBDL. We make it visible here to extract more information
// from the Lua file.
namespace RigidBodyDynamics {
namespace Addons {
bool LuaModelReadFromTable (LuaTable &model_table, Model *model, bool verbose);
}
}
// Types needed to parse
using namespace std;
using namespace RigidBodyDynamics;
using namespace RigidBodyDynamics;
using namespace RigidBodyDynamics::Addons;
const float cJumpVelocity = 4.0f;
const float cVelocityDamping = 4.0f;
const float cGravity = 9.81f;
const float cGroundAcceleration = 10.0f;
const float cCharacterHeight = 1.8f;
const float cCharacterWidth = 1.f;
const char* cRigModelFile = "data/models/model.lua";
const char* cAnimFile = "data/models/anim.csv";
static VectorNf sRigQ;
Quaternion offset_quat;
struct module_state {
};
bool Animation::Load(const char* filename) {
ifstream infile (filename);
if (!infile) {
cerr << "Error reading animation file from '" << filename << "'" << endl;
abort();
return false;
}
mFrames.clear();
int line_index = 0;
string line;
string previous_line;
while (!infile.eof()) {
previous_line = line;
getline (infile, line);
if (infile.eof()) {
break;
} else {
line_index++;
}
vector<string> tokens = tokenize_csv_strip_whitespaces (line);
if (tokens.size() == 0)
continue;
double frame_time = 0.;
VectorNf state = VectorNf::Zero (tokens.size() - 1);
double value;
istringstream value_stream (tokens[0]);
// If the first entry is not a number we ignore the whole line
if (!(value_stream >> value))
continue;
for (size_t i = 0; i < tokens.size(); i++) {
value_stream.clear();
value_stream.str(tokens[i]);
if (!(value_stream >> value)) {
cerr << "Error: could not convert string '" << tokens[i] << "' to number in " << filename << ", line " << line_index << ", column " << i << endl;
abort();
}
if (i == 0)
frame_time = value;
else {
state[i - 1] = value;
}
}
mFrameTimes.push_back(frame_time);
mDuration = frame_time;
mFrames.push_back(state);
}
infile.close();
return true;
}
void Animation::Sample(float cur_time, VectorNf& state) {
assert (mFrameTimes.size() > 0 && mFrameTimes[0] == 0.0f);
while (mIsLooped && cur_time >= mDuration) {
cur_time -= mDuration;
}
assert(cur_time >= 0.0f);
int next = 0;
for (next = 0; next < mFrameTimes.size() - 1; ++next) {
if (mFrameTimes[next] > cur_time)
break;
}
float t1 = mFrameTimes[next];
assert (t1 > cur_time);
int prev = (next - 1) % mFrameTimes.size();
if (prev < 0)
prev += mFrameTimes.size();
float t0 = mFrameTimes[prev];
// if we do not want to loop we use the last frame
assert (cur_time >= t0 && cur_time <= t1);
float alpha = (cur_time - t0) / (t1 - t0);
state = mFrames[prev] * (1.0f - alpha) + mFrames[next] * alpha;
}
CharacterEntity::CharacterEntity() {
mEntity = gRenderer->createEntity();
mPosition = Vector3f (0.f, 0.0f, 0.0f);
mRigModel = new Model();
bool load_result = LoadRig (cRigModelFile);
assert (load_result);
cout << "Creating render entity mesh ..." << endl;
// mState->character->entity->mesh = bgfxutils::createCuboid (1.f, 1.f, 1.f);
// mState->character->entity->mesh = bgfxutils::createCylinder (20);
cout << "Creating render entity mesh ... success!" << endl;
gLog ("Loading Animation %s", cAnimFile);
load_result = mAnimation.Load(cAnimFile);
assert (load_result);
mAnimTime = 0.0f;
}
CharacterEntity::~CharacterEntity() {
gRenderer->destroyEntity(mEntity);
mEntity = nullptr;
delete mRigModel;
mRigModel = nullptr;
}
bool CharacterEntity::LoadRig(const char* filename) {
gLog ("Creating rig model from %s ... ", filename);
LuaTable model_table = LuaTable::fromFile (filename);
mRigModel->fixed_body_discriminator = 1000;
bool load_result = LuaModelReadFromTable (model_table, mRigModel, false);
gLog ("Creating rig model from %s ... %s", filename, load_result ? "success" : "failed!");
gLog ("Rig model has %d degrees of freedom", mRigModel->qdot_size);
mRigState.q = VectorNd::Zero (mRigModel->q_size);
gLog ("Reading rig geometry information ... ");
int frame_count = model_table["frames"].length();
gLog ("Found %d frames", frame_count);
std::map<std::string, int> frame_name_bone_index;
int num_meshes = 0;
for (int fi = 1; fi <= frame_count; ++fi) {
LuaTableNode frame_table = model_table["frames"][fi];
string frame_name = frame_table["name"].getDefault<std::string>("ROOT");
// create a bone for the frame
string frame_parent = frame_table["parent"].getDefault<std::string>("ROOT");
int parent_index = -1;
if (frame_parent != "ROOT") {
if (frame_name_bone_index.find(frame_parent)
== frame_name_bone_index.end()) {
gLog (" Error: could not find frame for parent %s",
frame_parent.c_str());
}
parent_index = frame_name_bone_index[frame_parent];
}
// assemble the parent transform
Transform parent_transform;
if (frame_table["joint_frame"].exists()) {
Vector3f translation = frame_table["joint_frame"]["r"].getDefault(Vector3f(0.f, 0.f, 0.f));
Matrix33f rot_matrix = frame_table["joint_frame"]["E"].getDefault(
Matrix33f::Identity()
);
parent_transform.translation = translation;
parent_transform.rotation = Quaternion::fromMatrix(rot_matrix);
}
int bone_index = mEntity->mSkeleton.AddBone (
parent_index,
parent_transform
);
frame_name_bone_index[frame_name] = bone_index;
gLog (" Added frame %s, bone index %d, parent index %d",
frame_name.c_str(),
bone_index,
parent_index);
assert (bone_index == mBoneFrameIndices.size());
mBoneFrameIndices.push_back(mRigModel->GetBodyId(frame_name.c_str()));
// add visuals to the bone
int visuals_count = frame_table["visuals"].length();
gLog (" Frame %s has %d visuals", frame_name.c_str(), visuals_count);
for (int vi = 1; vi <= visuals_count; ++vi) {
LuaTableNode visual_table = frame_table["visuals"][vi];
bool have_geometry = visual_table["geometry"].exists();
if (!have_geometry) {
gLog (" Warning: could not find geometry for visual %d of frame %s",
vi, frame_name.c_str());
continue;
}
Vector4f color (1.f, 0.f, 1.f, 1.f);
if (visual_table["color"].length() == 3) {
color.block<3,1>(0,0) = visual_table["color"].getDefault(Vector3f(color.block<3,1>(0,0)));
} if (visual_table["color"].length() == 4) {
color = visual_table["color"].getDefault(color);
}
// read the geometry
Mesh* mesh = nullptr;
if (visual_table["geometry"]["box"].exists()) {
Vector3f dimensions = visual_table["geometry"]["box"]["dimensions"].getDefault(Vector3f (1.f, 1.f, 1.f));
mesh = Mesh::sCreateCuboid (
dimensions[0],
dimensions[1],
dimensions[2]
);
} else if (visual_table["geometry"]["sphere"].exists()) {
int rows = static_cast<int>(
visual_table["geometry"]["sphere"]["rows"].getDefault (16.f)
);
int segments = static_cast<int>(
visual_table["geometry"]["sphere"]["segments"].getDefault (16.f)
);
float radius = visual_table["geometry"]["sphere"]["radius"].getDefault (1.f);
mesh = Mesh::sCreateUVSphere (rows, segments, radius);
} else if (visual_table["geometry"]["capsule"].exists()) {
int rows = static_cast<int>(
visual_table["geometry"]["capsule"]["radius"].getDefault (16.f)
);
int segments = static_cast<int>(
visual_table["geometry"]["capsule"]["segments"].getDefault (16.f)
);
float radius = visual_table["geometry"]["capsule"]["radius"].getDefault (1.f);
float length = visual_table["geometry"]["capsule"]["length"].getDefault (1.f);
mesh = Mesh::sCreateCapsule (rows, segments, length, radius);
}
if (mesh == nullptr) {
gLog (" Warning: could not find geometry for visual %d of frame %s",
vi, frame_name.c_str());
continue;
}
if (mesh->mVertices.size() == 0) {
gLog (" Warning: Invalid geometry for visual %d of frame %s: no vertices found",
vi, frame_name.c_str());
}
Transform mesh_transform;
if (visual_table["scale"].exists()) {
gLog(" Warning: keyword scale not supported for visual %d of frame %s",
vi, frame_name.c_str());
}
Vector3f dimensions = visual_table["dimensions"].getDefault(Vector3f(0.f, 0.f, 0.f));
Vector3f mesh_center = visual_table["mesh_center"].getDefault(Vector3f(0.f, 0.f, 0.f));
Vector3f translate = visual_table["translate"].getDefault(Vector3f(0.f, 0.f, 0.f));
// parse mesh rotation
float angle = 0.0f;
Vector3f axis (1.f, 0.f, 0.f);
if (visual_table["rotate"].exists()) {
angle = visual_table["rotate"]["angle"].getDefault (0.0f);
axis = visual_table["rotate"]["axis"].getDefault (axis);
}
Quaternion visual_rotate (Quaternion::fromAxisAngle (axis, angle * M_PI / 180.f));
mesh_transform.rotation = visual_rotate;
// compute mesh center
mesh->UpdateBounds();
Vector3f bbox_size = mesh->mBoundsMax - mesh->mBoundsMin;
mesh_transform.scale = Vector3f(
fabs(dimensions[0]) / bbox_size[0],
fabs(dimensions[1]) / bbox_size[1],
fabs(dimensions[2]) / bbox_size[2]
);
if (translate.squaredNorm() > 0.0f) {
mesh_transform.translation = translate;
} else {
mesh_transform.translation = mesh_center;
}
// apply transform
mesh->Transform (mesh_transform.toMatrix());
mesh->UpdateBounds();
mesh->Update();
mEntity->mSkeletonMeshes.AddMesh(
mesh,
bone_index
);
num_meshes++;
}
}
gLog ("Loaded rig with %d bones and %d meshes",
mEntity->mSkeleton.Length(),
num_meshes);
return load_result;
}
static float cur_time = 0.0f;
void CharacterEntity::Update(float dt) {
Vector3f mController_acceleration (
mController.mDirection[0] * cGroundAcceleration,
mController.mDirection[1] * cGroundAcceleration,
mController.mDirection[2] * cGroundAcceleration
);
Vector3f gravity (0.0f, -cGravity, 0.0f);
Vector3f damping (
-mVelocity[0] * cVelocityDamping,
0.0f,
-mVelocity[2] * cVelocityDamping
);
Vector3f acceleration = mController_acceleration + gravity + damping;
mVelocity = mVelocity + acceleration * dt;
if (mPosition[1] == 0.0f
&& mController.mState[CharacterController::ControlStateJump]) {
mVelocity[1] = cJumpVelocity;
}
// integrate mPosition
mPosition += mVelocity * dt;
if (mPosition[1] < 0.f) {
mPosition[1] = 0.f;
mVelocity[1] = 0.0f;
}
// apply transformation
mEntity->mTransform.translation.set(
mPosition[0],
mPosition[1],
mPosition[2]);
gRenderer->drawDebugSphere (Vector3f (0.f, 1.3 + sin(cur_time * 2.f), 0.f), 2.2f);
// Convert to different coordinate frame
Quaternion quat (1., 0.0f, 0.0f, 1.0f);
quat.normalize();
float plane_angle = atan2 (mVelocity[2], mVelocity[0]);
Quaternion heading_rot (Quaternion::fromAxisAngle(Vector3f (0.f, 1.f, 0.f), plane_angle));
mEntity->mTransform.rotation = quat * heading_rot;
if (mVelocity.squaredNorm() > 0.01f) {
mAnimTime += dt;
VectorNf sampled_anim_state;
mAnimation.Sample(mAnimTime, sampled_anim_state);
mRigState.q = sampled_anim_state;
} else {
mAnimTime = mAnimation.mDuration * 0.125f;
mRigState.q.setZero();
}
UpdateBoneMatrices();
cur_time += dt;
}
void CharacterEntity::UpdateBoneMatrices() {
VectorNd q = mRigState.q;
UpdateKinematicsCustom(*mRigModel, &q, nullptr, nullptr);
for (int i = 0; i < mBoneFrameIndices.size(); ++i) {
int frame_index = mBoneFrameIndices[i];
if (frame_index < mRigModel->fixed_body_discriminator)
{
Matrix33f mat = mRigModel->X_lambda[frame_index].E;
mEntity->mSkeleton.mLocalTransforms[i].rotation =
Quaternion::fromMatrix(mat);
// mEntity->mSkeleton.mLocalTransforms[i].translation =
// mRigModel->X_lambda[frame_index].r;
} else {
const FixedBody& fbody =
mRigModel->mFixedBodies[frame_index - mRigModel->fixed_body_discriminator];
Math::SpatialTransform parent_transform; // = mRigModel->X_lambda[fbody.mMovableParent];
Math::SpatialTransform fixed_transform = fbody.mParentTransform;
Math::SpatialTransform transform = fixed_transform * parent_transform;
Matrix33f mat = transform.E;
mEntity->mSkeleton.mLocalTransforms[i].rotation =
Quaternion::fromMatrix(mat);
// mEntity->mSkeleton.mLocalTransforms[i].translation =
// transform.r;
}
}
// update matrices
Transform entity_rig_transform = mEntity->mTransform;
entity_rig_transform.translation[1] += 0.98f;
mEntity->mSkeleton.UpdateMatrices(entity_rig_transform.toMatrix());
}
void ShowCharacterPropertiesWindow (CharacterEntity* character) {
assert (character != nullptr);
ImGui::SetNextWindowSize (ImVec2(600.f, 300.0f), ImGuiSetCond_Once);
ImGui::SetNextWindowPos (ImVec2(400.f, 16.0f), ImGuiSetCond_Once);
ImGui::Begin("Character");
if (ImGui::Button ("Reset")) {
character->Reset();
}
ImGui::Protot::DragFloat4Normalized ("Offset Quat", offset_quat.data(),
0.01f, -1.0f, 1.0f);
ImGui::DragFloat3 ("Position", character->mPosition.data(), 0.01, -10.0f, 10.0f);
ImGui::DragFloat3 ("Velocity", character->mVelocity.data(), 0.01, -10.0f, 10.0f);
float angle = atan2 (-character->mVelocity[2], character->mVelocity[0]);
ImGui::LabelText ("", "Angle %f", angle * 180.f / M_PI);
ImGui::LabelText("",
"Skeleton Bones %d",
character->mEntity->mSkeleton.mLocalTransforms.size());
ImGui::LabelText("",
"Rig Frames %d",
character->mRigModel->mBodies.size());
bool node_open = ImGui::TreeNodeEx(
"DOFs",
0);
if (node_open) {
bool dof_modified = false;
for (int i = 0; i < character->mRigModel->q_size; ++i) {
char buf[32];
snprintf (buf, 32, "DOF %d", i);
if (ImGui::DragFloat (buf, &character->mRigState.q[i], 0.01, -10.0f, 10.0f)) {
dof_modified = true;
}
}
if (dof_modified) {
VectorNd q = character->mRigState.q;
}
ImGui::TreePop();
}
node_open = ImGui::TreeNodeEx(
"Meshes",
0);
if (node_open) {
for (int i = 0; i < character->mEntity->mSkeleton.Length(); ++i) {
char buf[32];
snprintf (buf, 32, "Mesh %d", i);
ImGuiTreeNodeFlags node_flags = 0;
bool node_open = ImGui::TreeNodeEx(
buf,
node_flags);
if (node_open) {
Transform &transform = character->mEntity->mSkeleton.mLocalTransforms[i];
ImGui::DragFloat3 ("Position", transform.translation.data(), 0.01, -10.0f, 10.0f);
if (ImGui::Protot::DragFloat4Normalized ("Rotation", transform.rotation.data(), 0.001, -1.0f, 1.0f)) {
if (isnan(transform.rotation.squaredNorm())) {
std::cout << "nan! " << transform.rotation.transpose() << std::endl;
abort();
}
}
ImGui::DragFloat3 ("Scale", transform.scale.data(), 0.01, 0.001f, 10.0f);
ImGui::TreePop();
}
}
ImGui::TreePop();
}
ImGui::End();
}
static struct module_state *module_init() {
std::cout << "Module init called" << std::endl;
module_state *state = (module_state*) malloc(sizeof(*state));
return state;
}
template <typename Serializer>
static void module_serialize (
struct module_state *state,
Serializer* serializer) {
// SerializeVec3(*serializer, "protot.TestModule.entity.mPosition", state->character->mPosition);
}
static void module_finalize(struct module_state *state) {
std::cout << "Module finalize called" << std::endl;
free(state);
}
static void module_reload(struct module_state *state, void* read_serializer) {
std::cout << "Module reload called. State: " << state << std::endl;
// load the state of the entity
if (read_serializer != nullptr) {
module_serialize(state, static_cast<ReadSerializer*>(read_serializer));
}
}
static void module_unload(struct module_state *state, void* write_serializer) {
// serialize the state of the entity
if (write_serializer != nullptr) {
module_serialize(state, static_cast<WriteSerializer*>(write_serializer));
}
}
static bool module_step(struct module_state *state, float dt) {
return true;
}
extern "C" {
const struct module_api MODULE_API = {
.init = module_init,
.reload = module_reload,
.step = module_step,
.unload = module_unload,
.finalize = module_finalize
};
}