diff --git a/data/levels/cemetery/cemetery.xml b/data/levels/cemetery/cemetery.xml index fd02c6d..2dd0d07 100644 --- a/data/levels/cemetery/cemetery.xml +++ b/data/levels/cemetery/cemetery.xml @@ -12,10 +12,18 @@ - + - + + + \ No newline at end of file diff --git a/data/models/weapons/v_m3.iqm b/data/models/weapons/v_m3.iqm new file mode 100644 index 0000000..f6b5315 Binary files /dev/null and b/data/models/weapons/v_m3.iqm differ diff --git a/data/models/weapons/v_ump.iqm b/data/models/weapons/v_ump.iqm new file mode 100644 index 0000000..30fa0a7 Binary files /dev/null and b/data/models/weapons/v_ump.iqm differ diff --git a/data/scripts/actors/actor_player.lua b/data/scripts/actors/actor_player.lua index 94bfb9b..c6dd04b 100644 --- a/data/scripts/actors/actor_player.lua +++ b/data/scripts/actors/actor_player.lua @@ -7,6 +7,16 @@ function actor_player:on_init() self:create_body() self:activate_camera() + + local ent = engine.create_entity("test_object") + engine.add_entity_to_world(ent) + + self.m_weapon_entity_id = ent:get_id() + + self.m_in_reload = false + + --local ent2 = engine.get_entity_from_id(self.m_weapon_entity_id) + --console.print(ent2:get_classname()) end function actor_player:on_shutdown() @@ -19,8 +29,39 @@ function actor_player:on_update(dt) self:update_camera_look() --self:update_camera_movement(dt) self:update_body_movement(dt) + + local ent = engine.get_entity_from_id(self.m_weapon_entity_id) + ent:set_relative_position_to_camera(self) + + if self:get_action() == ACTION_FIRE and self.m_in_reload == false then + ent:play_animation(ent:find_animation("shoot1"), ANIM_PLAYBACK_NONE) + end + + if self:get_action() == ACTION_RELOAD and self.m_in_reload == false then + ent:play_animation(ent:find_animation("reload"), ANIM_PLAYBACK_NONE) + self.m_in_reload = true + end + + + if ent:get_current_animation() == ent:find_animation("shoot1") and + ent:get_current_animation_time() >= ent:get_animation_time(ent:find_animation("shoot1")) then + ent:play_animation(ent:find_animation("idle1"), ANIM_PLAYBACK_REPEAT) + end + + if self.m_in_reload == true and ent:get_current_animation_time() >= ent:get_animation_time(ent:find_animation("reload")) then + ent:play_animation(ent:find_animation("idle1"), ANIM_PLAYBACK_REPEAT) + self.m_in_reload = false + end + -- if ent:get_current_animation() == ent:find_animation("reload") and + -- ent:get_current_animation_time() >= ent:get_animation_time(ent:find_animation("reload")) then + -- ent:play_animation(ent:find_animation("idle1"), ANIM_PLAYBACK_NONE) + --end end function actor_player:on_collide(other) console.print(string.format("actor_player:on_collide: %s", other:get_classname())) -end \ No newline at end of file +end + +function play_sound( filename, is_3d, posx, posy, posz ) + -- body +end diff --git a/data/scripts/test_object.lua b/data/scripts/test_object.lua index 464cbb6..4282f6b 100644 --- a/data/scripts/test_object.lua +++ b/data/scripts/test_object.lua @@ -3,14 +3,44 @@ test_object = inherit_table(game_object) function test_object:on_init() game_object.on_init(self) + + self:load_model("data/models/weapons/v_ump.iqm") + + self.m_test_id = self:find_animation("idle1") + console.print(string.format("test_object:on_init: self.m_test_id = %d", self.m_test_id)) + - self:load_model("data/models/scene_walls.obj") - - self.m_test = 0.0 + self:play_animation(self.m_test_id, ANIM_PLAYBACK_REPEAT) + +-- self.m_test = 0.0 end function test_object:on_update(dt) game_object.on_update(self, dt) - self:set_position(self.m_test, 0.0, 0.0) - self.m_test = self.m_test + ( 0.2 * dt ) -end \ No newline at end of file +-- self:set_position(self.m_test, 2.0, 0.0) +-- self.m_test = self.m_test + ( 0.2 * dt ) +end + +function test_object:set_relative_position_to_camera( ent ) + local camX, camY, camZ = camera.get_position() + local frontX, frontY, frontZ = camera.get_front() + local rightX, rightY, rightZ = camera.get_right() + local upX, upY, upZ = camera.get_up() + + local yaw = camera.get_yaw() + local pitch = camera.get_pitch() + + self:set_rotation(pitch, -yaw, 0.0) + + local offsetx = 0.0 + local offsety = 0.0 + local offsetz = 0.0 + + local x = camX + (rightX * offsetx) + (upX * offsety) + (frontX * offsetz) + local y = camY + (rightY * offsetx) + (upY * offsety) + (frontY * offsetz) + local z = camZ + (rightZ * offsetx) + (upZ * offsety) + (frontZ * offsetz) + self:set_position(x, y, z) + + -- force update transform + self:update_transform() +end diff --git a/data/shaders/unlit_generic.vs b/data/shaders/unlit_generic.vs index 6b1ca53..aecf713 100644 --- a/data/shaders/unlit_generic.vs +++ b/data/shaders/unlit_generic.vs @@ -14,33 +14,9 @@ uniform mat4 u_viewMatrix; uniform mat4 u_projectionMatrix; uniform mat4 u_modelViewProjection; -vec3 CalcOmniLight() -{ - vec3 lightPos = vec3(0.1, 2.1, 0.1); - float d = distance(lightPos, v_position); - vec3 L = normalize(lightPos-v_position); - vec3 N = normalize(v_normal); - vec3 col = vec3( max(0, dot(N, L) / d) ); - col = col * 0.8 + 0.2; - return col; -} - -vec3 CalcDirLight() -{ - vec3 lightPos = vec3(5.0, 10.0, 1.0); - //lightPos = -lightPos; - - vec3 L = normalize(lightPos); - vec3 N = normalize(v_normal); - vec3 col = vec3( max(0, dot(N, L)) ); - col = col * 0.8 + 0.2; - return col; -} - void main() { v_position = vec3( u_modelMatrix * vec4(a_position, 1.0) ); v_normal = vec3( mat3(u_modelMatrix) * a_normal ); v_texcoord = a_texcoord; - v_finalColor = CalcDirLight(); gl_Position = u_modelViewProjection * vec4(a_position, 1); } \ No newline at end of file diff --git a/data/shaders/unlit_generic_skin.vs b/data/shaders/unlit_generic_skin.vs new file mode 100644 index 0000000..e5f6b1b --- /dev/null +++ b/data/shaders/unlit_generic_skin.vs @@ -0,0 +1,34 @@ +#version 120 + +attribute vec3 a_position; +attribute vec3 a_normal; +attribute vec2 a_texcoord; +attribute vec4 a_boneIds; +attribute vec4 a_weights; + +varying vec3 v_position; +varying vec3 v_normal; +varying vec2 v_texcoord; +varying vec3 v_finalColor; + +uniform mat4 u_modelMatrix; +uniform mat4 u_viewMatrix; +uniform mat4 u_projectionMatrix; +uniform mat4 u_modelViewProjection; +uniform mat4 u_boneMatrices[128]; + +void main() { + // calculate bone transform + mat4 skinMatrix = a_weights.x * u_boneMatrices[ int( a_boneIds.x ) ] + + a_weights.y * u_boneMatrices[ int( a_boneIds.y ) ] + + a_weights.z * u_boneMatrices[ int( a_boneIds.z ) ] + + a_weights.w * u_boneMatrices[ int( a_boneIds.w ) ]; + + // Position + v_position = vec3( skinMatrix * vec4( a_position, 1.0 ) ); + v_position = vec3( u_modelMatrix * vec4( v_position, 1.0 ) ); + + v_normal = vec3( mat3(u_modelMatrix) * a_normal ); + v_texcoord = a_texcoord; + gl_Position = u_projectionMatrix * u_viewMatrix * vec4( v_position, 1.0 ); +} \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 76ccf49..a2d38b6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -306,7 +306,7 @@ void Engine::NewGame(const char* mapname) Disconnect(); g_PhysicsWorld = new PhysicsWorld(); - //g_PhysicsWorld->ToggleDebugDraw(); + g_PhysicsWorld->ToggleDebugDraw(); g_world = new World(); diff --git a/src/engine/inputmanager.h b/src/engine/inputmanager.h index 5772206..eecf4d7 100644 --- a/src/engine/inputmanager.h +++ b/src/engine/inputmanager.h @@ -40,6 +40,7 @@ public: void OnMouseMoveAction(float x, float y); InputLayout& GetKeyboard() { return m_keyboard; } + InputLayout& GetMouse() { return m_mouse; } glm::vec2& GetMousePos() { return m_mousePos; } glm::vec2& GetMouseDeltaPos() { return m_deltaMousePos; } diff --git a/src/game/game.cpp b/src/game/game.cpp index 6d0cb52..513952f 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -32,10 +32,19 @@ LuaPlus::LuaObject engineCreateEntity(const char* classname) { Entity* entity = static_cast(g_game->Lua_CreateEntity(classname)); SDL_assert_always(entity); + + entity->Init(); return entity->GetLuaObject(); } +LuaPlus::LuaObject engineGetEntityFromID(lua_Integer id) +{ + LuaPlus::LuaObject lookup = GetLuaState().GetGlobal("g_entities"); + LuaPlus::LuaObject entityTable = lookup.GetByIndex(id); + return entityTable; +} + void engineAddEntityToWorld(LuaPlus::LuaObject& object) { LuaPlus::LuaObject cppclass = object["__object"]; @@ -45,6 +54,92 @@ void engineAddEntityToWorld(LuaPlus::LuaObject& object) g_world->AddEntity(entity); } +int cameraGetPos(LuaPlus::LuaState* state) +{ + glm::vec3 v = glm::vec3(0.0f); + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetPosition(); + + state->PushNumber(v.x); + state->PushNumber(v.y); + state->PushNumber(v.z); + + return 3; +} + +int cameraGetFront(LuaPlus::LuaState* state) +{ + glm::vec3 v = glm::vec3(0.0f); + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetFront(); + + state->PushNumber(v.x); + state->PushNumber(v.y); + state->PushNumber(v.z); + + return 3; +} + +int cameraGetRight(LuaPlus::LuaState* state) +{ + glm::vec3 v = glm::vec3(0.0f); + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetRight(); + + state->PushNumber(v.x); + state->PushNumber(v.y); + state->PushNumber(v.z); + + return 3; +} + +int cameraGetUp(LuaPlus::LuaState* state) +{ + glm::vec3 v = glm::vec3(0.0f); + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetUp(); + + state->PushNumber(v.x); + state->PushNumber(v.y); + state->PushNumber(v.z); + + return 3; +} + +int cameraGetYaw(LuaPlus::LuaState* state) +{ + float v = 0.0f; + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetYaw(); + + state->PushNumber(v); + + return 1; +} + +int cameraGetPitch(LuaPlus::LuaState* state) +{ + float v = 0.0f; + + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + v = camera->GetPitch(); + + state->PushNumber(v); + + return 1; +} + void registerEngine() { using namespace LuaPlus; @@ -55,10 +150,24 @@ void registerEngine() engineTable.RegisterDirect("warning", &engineWarning); engineTable.RegisterDirect("create_entity", &engineCreateEntity); engineTable.RegisterDirect("add_entity_to_world", &engineAddEntityToWorld); + engineTable.RegisterDirect("get_entity_from_id", &engineGetEntityFromID); LuaObject consoleTable = GetLuaState().GetGlobals().CreateTable("console"); consoleTable.RegisterDirect("print", &consoleMsg); + LuaObject cameraTable = GetLuaState().GetGlobals().CreateTable("camera"); + cameraTable.Register("get_position", &cameraGetPos); + cameraTable.Register("get_front", &cameraGetFront); + cameraTable.Register("get_right", &cameraGetRight); + cameraTable.Register("get_up", &cameraGetUp); + cameraTable.Register("get_yaw", &cameraGetYaw); + cameraTable.Register("get_pitch", &cameraGetPitch); + + // action globals + GetLuaState().DoString("ACTION_FIRE = 0"); + GetLuaState().DoString("ACTION_ALT_FIRE = 1"); + GetLuaState().DoString("ACTION_RELOAD = 2"); + // animations globals GetLuaState().DoString("ANIM_PLAYBACK_NONE = 0"); GetLuaState().DoString("ANIM_PLAYBACK_REPEAT = 1"); @@ -231,6 +340,8 @@ void Game::LoadLevelXML(const char* mapname) entity->CreateTestBody(); } + entity->Init(); + //IEntityBase* entity = g_entityManager->CreateEntity(classname.as_string()); g_world->AddEntity(entity); } diff --git a/src/game/game_object.cpp b/src/game/game_object.cpp index 7e4b013..fe59268 100644 --- a/src/game/game_object.cpp +++ b/src/game/game_object.cpp @@ -67,6 +67,7 @@ REGISTER_ENTITY(Entity); Entity::Entity() : m_model(nullptr), + m_skeleton(nullptr), m_shape(nullptr), m_rigidBody(nullptr), m_bodyDirty(false) @@ -99,7 +100,11 @@ Entity::~Entity() void Entity::Update(float dt) { - UpdateBody(); + if (m_skeleton) + UpdateSkeleton(dt); + + if (m_rigidBody) + UpdateBody(); if ( m_onUpdateFunction.IsFunction()) { @@ -112,12 +117,12 @@ void Entity::Render() { if (m_model) { - m_model->Draw(GetWorldTransform()); + m_model->Draw(GetWorldTransform(), m_skeleton); - BoundingBox bbox = m_model->GetBoundingBox(); + /*BoundingBox bbox = m_boundingBox; bbox.TransformAABB(GetWorldTransform()); - g_debugRender->DrawBoundingBox(bbox, glm::vec3(1.0f, 0.0f, 0.0f)); + g_debugRender->DrawBoundingBox(bbox, glm::vec3(1.0f, 0.0f, 0.0f));*/ } } @@ -148,7 +153,17 @@ void Entity::SetName(const std::string& name) void Entity::LoadModel(const char* filename) { + if (m_skeleton) { + delete m_skeleton; + m_skeleton = nullptr; + } + m_model = g_modelSystem->LoadModel(filename); + if (m_model) + m_boundingBox = m_model->GetBoundingBox(); + + if (m_model && m_model->GetNumAnimations()) + m_skeleton = m_model->CreateSkeletonInstance(); } void Entity::SetVisible(bool visible) @@ -160,6 +175,33 @@ bool Entity::GetVisible() return true; } +void Entity::UpdateSkeleton(float dt) +{ + if (m_model && m_skeleton) + { + m_model->UpdateSkeletonInstance(m_skeleton, dt); + + m_boundingBox = m_model->GetBoundingBox(); + + for (int i = 0; i < m_skeleton->m_jointMatrices.size(); i++) + { + m_boundingBox.m_min = glm::min(m_boundingBox.m_min, glm::vec3(m_skeleton->m_jointMatrices[i][3])); + m_boundingBox.m_max = glm::max(m_boundingBox.m_max, glm::vec3(m_skeleton->m_jointMatrices[i][3])); + + } + } +} + +void Entity::Init() +{ + // call init + if (m_onInitFunction.IsFunction()) + { + LuaPlus::LuaFunctionVoid onInitFunction = m_onInitFunction; + onInitFunction(m_luaObject); + } +} + void Entity::InitFromTable(LuaPlus::LuaObject& _object) { SDL_assert_always(!_object.IsNil() && "Invalid object passed"); @@ -184,21 +226,31 @@ void Entity::InitFromTable(LuaPlus::LuaObject& _object) // assign C++ object m_luaObject.SetLightUserdata("__object", this); - - // call init - if (m_onInitFunction.IsFunction()) - { - LuaPlus::LuaFunctionVoid onInitFunction = m_onInitFunction; - onInitFunction(m_luaObject); - } } void Entity::RegisterBaseFunctions() { m_luaObject.Register("load_model", *this, &Entity::Lua_LoadModel); + m_luaObject.Register("update_transform", *this, &Entity::Lua_UpdateTransform); m_luaObject.Register("translate", *this, &Entity::Lua_Translate); m_luaObject.Register("set_position", *this, &Entity::Lua_SetPosition); + m_luaObject.Register("get_position", *this, &Entity::Lua_GetPosition); + m_luaObject.Register("set_rotation", *this, &Entity::Lua_SetRotation); m_luaObject.Register("get_classname", *this, &Entity::Lua_GetClassname); + m_luaObject.Register("get_id", *this, &Entity::Lua_GetID); + + // animation + m_luaObject.Register("find_animation", *this, &Entity::Lua_FindAnimation); + m_luaObject.Register("play_animation", *this, &Entity::Lua_PlayAnimation); + m_luaObject.Register("get_current_animation", *this, &Entity::Lua_GetCurrentAnimation); + m_luaObject.Register("get_current_animation_time", *this, &Entity::Lua_GetCurrentAnimationTime); + m_luaObject.Register("get_animation_time", *this, &Entity::Lua_GetAnimationTime); + + //int Lua_FindAnimation(LuaPlus::LuaState * state); + //int Lua_PlayAnimation(LuaPlus::LuaState * state); + //int Lua_GetCurrentAnimation(LuaPlus::LuaState * state); + //int Lua_GetCurrentAnimationTime(LuaPlus::LuaState * state); + //int Lua_GetAnimationTime(LuaPlus::LuaState * state); // m_luaObject.RegisterDirect("load_model", &Entity_LoadModel); //m_luaObject.RegisterDirect("set_visible", &Entity_SetVisible); @@ -223,6 +275,13 @@ void Entity::Help_SetPosition(float x, float y, float z) m_position.z = z; } +void Entity::Help_SetRotation(float x, float y, float z) +{ + m_rotation.x = x; + m_rotation.y = y; + m_rotation.z = z; +} + int Entity::Lua_LoadModel(LuaPlus::LuaState* state) { LuaPlus::LuaStack stack(state); @@ -265,12 +324,104 @@ int Entity::Lua_SetPosition(LuaPlus::LuaState* state) return 0; } +int Entity::Lua_GetPosition(LuaPlus::LuaState* state) +{ + state->PushNumber(m_position.x); + state->PushNumber(m_position.y); + state->PushNumber(m_position.z); + return 3; +} + +int Entity::Lua_SetRotation(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + + float x = stack[2].GetNumber(); + float y = stack[3].GetNumber(); + float z = stack[4].GetNumber(); + + Help_SetRotation(x, y, z); + + return 0; +} + int Entity::Lua_GetClassname(LuaPlus::LuaState* state) { state->PushString(GetClassname()); return 1; } +int Entity::Lua_GetID(LuaPlus::LuaState* state) +{ + state->PushInteger(GetID()); + return 1; +} + +int Entity::Lua_UpdateTransform(LuaPlus::LuaState* state) +{ + UpdateTransform(); + + return 0; +} + +int Entity::Lua_FindAnimation(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + const char* name = stack[2].GetString(); + + if (m_model) + state->PushInteger(m_model->FindAnimation(name)); + else + state->PushInteger(INVALID_ANIMATION_HANDLE); + + return 1; +} + +int Entity::Lua_PlayAnimation(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + AnimationId_t id = stack[2].GetInteger(); + int mode = stack[3].GetInteger(); + + if (m_skeleton) + m_skeleton->PlayAnimation(id, mode); + + return 0; +} + +int Entity::Lua_GetCurrentAnimation(LuaPlus::LuaState* state) +{ + if (m_skeleton) + state->PushInteger(m_skeleton->GetCurrentAnimation()); + else + state->PushInteger(INVALID_ANIMATION_HANDLE); + + return 1; +} + +int Entity::Lua_GetCurrentAnimationTime(LuaPlus::LuaState* state) +{ + if (m_skeleton) + state->PushNumber(m_skeleton->GetCurrentTime()); + else + state->PushNumber(0.0f); + + return 1; +} + +int Entity::Lua_GetAnimationTime(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + AnimationId_t id = stack[2].GetInteger(); + + if (m_skeleton) + state->PushNumber(m_skeleton->GetAnimationTime(id)); + else + state->PushNumber(0.0f); + + return 1; +} + const LuaPlus::LuaObject& Entity::GetLuaObject() { return m_luaObject; @@ -503,7 +654,7 @@ void ActorBase::CreatePlayerBody() m_rigidBody->setFriction(0.0f); m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f)); - m_rigidBody->setDamping(0.1f, 0.0f); + m_rigidBody->setDamping(0.0f, 0.0f); m_rigidBody->setActivationState(DISABLE_DEACTIVATION); @@ -541,6 +692,7 @@ void ActorBase::RegisterFunctions() m_luaObject.Register("update_camera_movement", *this, &ActorBase::Lua_UpdateCameraMovement); m_luaObject.Register("create_body", *this, &ActorBase::Lua_CreateBody); m_luaObject.Register("update_body_movement", *this, &ActorBase::Lua_UpdateBodyMovement); + m_luaObject.Register("get_action", *this, &ActorBase::Lua_GetAction); //m_luaObject.RegisterDirect("activate_camera", &ActorBase_ActivateCamera); //m_luaObject.RegisterDirect("update_camera_look", &ActorBase_UpdateCameraLook); @@ -583,6 +735,40 @@ int ActorBase::Lua_CreateBody(LuaPlus::LuaState* state) return 0; } +int ActorBase::Lua_GetAction(LuaPlus::LuaState* state) +{ +// uint32_t action = 0; + + //float x, y; + //SDL_MouseButtonFlags buttons = SDL_GetMouseState(&x, &y); + + //if (buttons & SDL_BUTTON_MASK(SDL_BUTTON_LEFT)) + // state->PushInteger(0); + //if (buttons & SDL_BUTTON_MASK(SDL_BUTTON_RIGHT)) + // state->PushInteger(1); + //else + // state->PushInteger(-1); + + int action = GenAction(); + state->PushInteger(action); + + return 1; +} + +int ActorBase::GenAction() +{ + int action = -1; + + if (g_inputManager.GetMouse().IsKeyDown(EMouseButton_Left)) + action = 0; + else if (g_inputManager.GetMouse().IsKeyDown(EMouseButton_Right)) + action = 1; + else if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_R)) + action = 2; + + return action; +} + uint32_t ActorBase::GenMovementDir() { uint32_t movementDir = EMovementDir_None; diff --git a/src/game/game_object.h b/src/game/game_object.h index 4da9f58..cf9f4a5 100644 --- a/src/game/game_object.h +++ b/src/game/game_object.h @@ -77,6 +77,10 @@ public: void SetVisible(bool visible); bool GetVisible(); + void UpdateSkeleton(float dt); + + void Init(); + // Game entity physics void CreateTestBody(); void UpdateBody(); @@ -91,11 +95,22 @@ public: // Game entity lua wrappers void Help_Translate(float x, float y, float z); void Help_SetPosition(float x, float y, float z); + void Help_SetRotation(float x, float y, float z); int Lua_LoadModel(LuaPlus::LuaState* state); int Lua_Translate(LuaPlus::LuaState* state); int Lua_SetPosition(LuaPlus::LuaState* state); + int Lua_GetPosition(LuaPlus::LuaState* state); + int Lua_SetRotation(LuaPlus::LuaState* state); int Lua_GetClassname(LuaPlus::LuaState* state); + int Lua_GetID(LuaPlus::LuaState* state); + int Lua_UpdateTransform(LuaPlus::LuaState* state); + + int Lua_FindAnimation(LuaPlus::LuaState* state); + int Lua_PlayAnimation(LuaPlus::LuaState* state); + int Lua_GetCurrentAnimation(LuaPlus::LuaState* state); + int Lua_GetCurrentAnimationTime(LuaPlus::LuaState* state); + int Lua_GetAnimationTime(LuaPlus::LuaState* state); const LuaPlus::LuaObject& GetLuaObject(); @@ -103,6 +118,7 @@ protected: std::string m_name; Model* m_model; + SkeletonInstance* m_skeleton; LuaPlus::LuaObject m_luaObject; LuaPlus::LuaObject m_onInitFunction; @@ -149,8 +165,10 @@ public: int Lua_UpdateCameraLook(LuaPlus::LuaState* state); int Lua_ActivateCamera(LuaPlus::LuaState* state); int Lua_CreateBody(LuaPlus::LuaState* state); + int Lua_GetAction(LuaPlus::LuaState* state); private: + int GenAction(); uint32_t GenMovementDir(); private: diff --git a/src/render/animation.cpp b/src/render/animation.cpp new file mode 100644 index 0000000..c75e7ed --- /dev/null +++ b/src/render/animation.cpp @@ -0,0 +1,80 @@ +#include "animation.h" +#include "filesystem.h" +#include "model.h" + +SkeletonInstance::SkeletonInstance() : + m_model(nullptr), + m_animation(INVALID_ANIMATION_HANDLE), + m_time(0.0f), + m_looped(false) +{ +} + +SkeletonInstance::~SkeletonInstance() +{ +} + +bool SkeletonInstance::IsValid() +{ + return m_model != nullptr; +} + +void SkeletonInstance::LoadAnimation(const char* filename) +{ + if (!m_model) + return; + + //m_model->LoadAnimation(filename); +} + +AnimationId_t SkeletonInstance::FindAnimation(const char* name) +{ + if (!m_model) + return INVALID_ANIMATION_HANDLE; + + return m_model->FindAnimation(name); +} + +uint32_t SkeletonInstance::GetNumAnimations() +{ + if (!m_model) + return 0; + + return m_model->GetNumAnimations(); +} + +float SkeletonInstance::GetAnimationTime(AnimationId_t id) +{ + if (!m_model) + return 0.0f; + + const Animation* animation = m_model->GetAnimation(id); + + float frameTime = 1.0f / animation->framerate; + float totalDuration = animation->numFrames * frameTime; + return totalDuration; +} + +void SkeletonInstance::PlayAnimation(AnimationId_t id, bool looped) +{ + m_time = 0.0f; + m_animation = id; + m_looped = looped; +} + +void SkeletonInstance::StopAnimation() +{ + m_time = 0.0f; + m_animation = INVALID_ANIMATION_HANDLE; + m_looped = -1; +} + +AnimationId_t SkeletonInstance::GetCurrentAnimation() +{ + return m_animation; +} + +float SkeletonInstance::GetCurrentTime() +{ + return m_time; +} diff --git a/src/render/animation.h b/src/render/animation.h new file mode 100644 index 0000000..2b6f92d --- /dev/null +++ b/src/render/animation.h @@ -0,0 +1,68 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include + +#include "render_shared.h" + +#define INVALID_ANIMATION_HANDLE -1 + +// Animation types +typedef /*int16_t*/ int AnimationId_t; + +class Model; + +/* Joint */ +struct Joint +{ + char name[64]; + int parentId; + + glm::vec3 origin; + glm::quat orient; +}; + +/* Animation */ +struct Animation +{ + char name[64]; + uint32_t numPoses; + uint32_t numFrames; + float framerate; + std::vector< glm::vec3 > origin; + std::vector< glm::quat > orient; +}; + +/* Skeleton */ +class SkeletonInstance +{ +public: + Model* m_model; + std::vector m_joints; + std::vector m_jointMatrices; + std::vector m_finalMatrices; + AnimationId_t m_animation; + float m_time; + bool m_looped; + +public: + SkeletonInstance(); + ~SkeletonInstance(); + + bool IsValid(); + + void LoadAnimation(const char* filename); + + AnimationId_t FindAnimation(const char* name); + uint32_t GetNumAnimations(); + float GetAnimationTime(AnimationId_t id); + + void PlayAnimation(AnimationId_t id, bool looped); + void StopAnimation(); + + AnimationId_t GetCurrentAnimation(); + float GetCurrentTime(); + +}; + +#endif // !ANIMATION_H diff --git a/src/render/iqm.h b/src/render/iqm.h new file mode 100644 index 0000000..a450504 --- /dev/null +++ b/src/render/iqm.h @@ -0,0 +1,134 @@ +#ifndef __IQM_H__ +#define __IQM_H__ + +#define IQM_MAGIC "INTERQUAKEMODEL" +#define IQM_VERSION 2 + +struct iqmheader +{ + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; +}; + +struct iqmmesh +{ + unsigned int name; + unsigned int material; + unsigned int first_vertex, num_vertexes; + unsigned int first_triangle, num_triangles; +}; + +enum +{ + IQM_POSITION = 0, + IQM_TEXCOORD = 1, + IQM_NORMAL = 2, + IQM_TANGENT = 3, + IQM_BLENDINDEXES = 4, + IQM_BLENDWEIGHTS = 5, + IQM_COLOR = 6, + IQM_CUSTOM = 0x10 +}; + +enum +{ + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8 +}; + +struct iqmtriangle +{ + unsigned int vertex[3]; +}; + +struct iqmadjacency +{ + unsigned int triangle[3]; +}; + +struct iqmjointv1 +{ + unsigned int name; + int parent; + float translate[3], rotate[3], scale[3]; +}; + +struct iqmjoint +{ + unsigned int name; + int parent; + float translate[3], rotate[4], scale[3]; +}; + +struct iqmposev1 +{ + int parent; + unsigned int mask; + float channeloffset[9]; + float channelscale[9]; +}; + +struct iqmpose +{ + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; +}; + +struct iqmanim +{ + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; +}; + +enum +{ + IQM_LOOP = 1<<0 +}; + +struct iqmvertexarray +{ + unsigned int type; + unsigned int flags; + unsigned int format; + unsigned int size; + unsigned int offset; +}; + +struct iqmbounds +{ + float bbmin[3], bbmax[3]; + float xyradius, radius; +}; + +struct iqmextension +{ + unsigned int name; + unsigned int num_data, ofs_data; + unsigned int ofs_extensions; // pointer to next extension +}; + +#endif + diff --git a/src/render/model.cpp b/src/render/model.cpp index 3c4e102..d133dbc 100644 --- a/src/render/model.cpp +++ b/src/render/model.cpp @@ -2,6 +2,7 @@ #include #include #include "ifilesystem.h" +#include "core.h" #include "log.h" #include "render.h" #include "gpu_buffer.h" @@ -11,8 +12,11 @@ #include "model.h" #include "modelsystem.h" #include "texturesmanager.h" +#include "iqm.h" +#include "debugrender.h" extern Shader* g_unlitShader; +extern Shader* g_unlitSkinnedShader; static std::string getFileNameWithoutExtension(const std::string& filename) { @@ -26,9 +30,8 @@ static std::string getFileNameWithoutExtension(const std::string& filename) Model::Model() { - m_data.vb = nullptr; - m_data.ib = nullptr; m_AlbedoTexture = nullptr; + m_numPoses = 0; //m_boundingBox.min = glm::vec3(0.0f); //m_boundingBox.max = glm::vec3(0.0f); @@ -38,11 +41,382 @@ Model::~Model() { m_AlbedoTexture = nullptr; - if (m_data.vb) + for (int i = 0; i < m_meshes.size(); i++) + ReleaseModelData(m_meshes[i]); +} + +// IQM + +typedef unsigned char byte; +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; +typedef signed long long int llong; +typedef unsigned long long int ullong; + +inline iqmheader* LoadIQMHeader(const char* filename) +{ + FileHandle_t handle = GetFileSystem()->OpenFile(filename, "rb"); + SDL_assert(handle != kInvalidFileHandleValue); + + size_t size = GetFileSystem()->GetFileLength(handle); + + char* buffer = (char*)malloc(size); + GetFileSystem()->ReadFile(handle, buffer, size); + + GetFileSystem()->CloseFile(handle); + + return (iqmheader*)buffer; +} + +inline void* IQMHeaderPtr(iqmheader* pHeader, uint offset) +{ + return ((byte*)pHeader) + offset; +} + +static bool g_bIsLittleEndian = false; + +static void EndianSwap_Init() +{ + union { - delete m_data.vb; - m_data.vb = nullptr; + int i; + uchar b[sizeof(int)]; + } conv; + conv.i = 1; + + g_bIsLittleEndian = conv.b[0] != 0; +} + +static inline bool islittleendian() { union { int i; uchar b[sizeof(int)]; } conv; conv.i = 1; return conv.b[0] != 0; } +inline ushort endianswap16(ushort n) { return (n << 8) | (n >> 8); } +inline uint endianswap32(uint n) { return (n << 24) | (n >> 24) | ((n >> 8) & 0xFF00) | ((n << 8) & 0xFF0000); } +template inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; } +template<> inline ushort endianswap(ushort n) { return endianswap16(n); } +template<> inline short endianswap(short n) { return endianswap16(n); } +template<> inline uint endianswap(uint n) { return endianswap32(n); } +template<> inline int endianswap(int n) { return endianswap32(n); } +template inline void endianswap(T* buf, int len) { for (T* end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); } +template inline T lilswap(T n) { return islittleendian() ? n : endianswap(n); } +template inline void lilswap(T* buf, int len) { if (!islittleendian()) endianswap(buf, len); } +template inline T bigswap(T n) { return islittleendian() ? endianswap(n) : n; } +template inline void bigswap(T* buf, int len) { if (islittleendian()) endianswap(buf, len); } + +template T getval(FILE* f) { T n; return fread(&n, 1, sizeof(n), f) == sizeof(n) ? n : 0; } +template T getlil(FILE* f) { return lilswap(getval(f)); } +template T getbig(FILE* f) { return bigswap(getval(f)); } + +void Model::LoadIqm(const char* filename) +{ + iqmheader* pHdr = LoadIQMHeader(filename); + pHdr->version = lilswap(pHdr->version); + pHdr->filesize = lilswap(pHdr->filesize); + pHdr->num_vertexarrays = lilswap(pHdr->num_vertexarrays); + pHdr->num_vertexes = lilswap(pHdr->num_vertexes); + pHdr->num_triangles = lilswap(pHdr->num_triangles); + pHdr->num_meshes = lilswap(pHdr->num_meshes); + pHdr->num_joints = lilswap(pHdr->num_joints); + pHdr->num_poses = lilswap(pHdr->num_poses); + pHdr->num_anims = lilswap(pHdr->num_anims); + pHdr->num_frames = lilswap(pHdr->num_frames); + pHdr->num_framechannels = lilswap(pHdr->num_framechannels); + + pHdr->ofs_vertexarrays = lilswap(pHdr->ofs_vertexarrays); + pHdr->ofs_triangles = lilswap(pHdr->ofs_triangles); + pHdr->ofs_meshes = lilswap(pHdr->ofs_meshes); + pHdr->ofs_joints = lilswap(pHdr->ofs_joints); + pHdr->ofs_poses = lilswap(pHdr->ofs_poses); + pHdr->ofs_anims = lilswap(pHdr->ofs_anims); + pHdr->ofs_frames = lilswap(pHdr->ofs_frames); + + m_numPoses = pHdr->num_poses; + + if (pHdr->version != IQM_VERSION) + { + Msg("Model::LoadIQM: %s is outdated. current is %i, model %i", filename, IQM_VERSION, pHdr->version); + free(pHdr); + return; } + + if (pHdr->filesize > (16 << 20)) + { + Msg("Model::LoadIQM: %s is bigger(%i) than 16 MB!", filename, pHdr->filesize); + free(pHdr); + return; + } + + iqmvertexarray* pVertexArrays = (iqmvertexarray*)IQMHeaderPtr(pHdr, pHdr->ofs_vertexarrays); + + float* position = nullptr; + float* normal = nullptr; + float* texcoord = nullptr; + byte* weight = nullptr; + byte* boneid = nullptr; + + for (uint32_t i = 0; i < pHdr->num_vertexarrays; i++) + { + const iqmvertexarray& vertarray = pVertexArrays[i]; + + if (vertarray.type == IQM_POSITION) + { + if (vertarray.format != IQM_FLOAT || vertarray.size != 3) + Core::Error("Model::LoadIQM: %s wrong model! position attribute are not vec3 and float.", filename); + + position = (float*)IQMHeaderPtr(pHdr, vertarray.offset); + } + else if (vertarray.type == IQM_NORMAL) + { + if (vertarray.format != IQM_FLOAT || vertarray.size != 3) + Core::Error("Model::LoadIQM: %s wrong model! normal attribute are not vec3 and float.", filename); + + normal = (float*)IQMHeaderPtr(pHdr, vertarray.offset); + } + else if (vertarray.type == IQM_TEXCOORD) + { + if (vertarray.format != IQM_FLOAT || vertarray.size != 2) + Core::Error("Model::LoadIQM: %s wrong model! texcoord attribute are not vec2 and float.", filename); + + texcoord = (float*)IQMHeaderPtr(pHdr, vertarray.offset); + } + else if (vertarray.type == IQM_BLENDWEIGHTS) + { + if (vertarray.format != IQM_UBYTE || vertarray.size != 4) + Core::Error("Model::LoadIQM: %s wrong model! bone weights attribute are not vec4 and ubyte.", filename); + + weight = (byte*)IQMHeaderPtr(pHdr, vertarray.offset); + } + else if (vertarray.type == IQM_BLENDINDEXES) + { + if (vertarray.format != IQM_UBYTE || vertarray.size != 4) + Core::Error("Model::LoadIQM: %s wrong model! bone indices attribute are not vec4 and ubyte.", filename); + + boneid = (byte*)IQMHeaderPtr(pHdr, vertarray.offset); + } + } + + std::vector< SkinnedMeshVertex > vertices; + vertices.resize(pHdr->num_vertexes); + + std::vector< uint > indices; + indices.resize(pHdr->num_triangles * 3); + + iqmmesh* pMeshes = (iqmmesh*)IQMHeaderPtr(pHdr, pHdr->ofs_meshes); + iqmtriangle* pTriangles = (iqmtriangle*)IQMHeaderPtr(pHdr, pHdr->ofs_triangles); +// Material* pMaterial = nullptr; + bool firstMesh = true; + + for (int i = 0; i < pHdr->num_meshes; ++i) + { + iqmmesh* pMesh = &pMeshes[i]; + + for (uint32_t j = 0; j < pMesh->num_vertexes; ++j) + { + memcpy(&vertices[j].position, &position[(pMesh->first_vertex + j) * 3], sizeof(vertices[j].position)); + memcpy(&vertices[j].normal, &normal[(pMesh->first_vertex + j) * 3], sizeof(vertices[j].normal)); + memcpy(&vertices[j].texcoord, &texcoord[(pMesh->first_vertex + j) * 2], sizeof(vertices[j].texcoord)); + + if (boneid) + { + vertices[j].boneIds.x = (float)boneid[(pMesh->first_vertex + j) * 4]; + vertices[j].boneIds.y = (float)boneid[(pMesh->first_vertex + j) * 4 + 1]; + vertices[j].boneIds.z = (float)boneid[(pMesh->first_vertex + j) * 4 + 2]; + vertices[j].boneIds.w = (float)boneid[(pMesh->first_vertex + j) * 4 + 3]; + } + else + { + vertices[j].boneIds.x = 0.0f; + vertices[j].boneIds.y = 0.0f; + vertices[j].boneIds.z = 0.0f; + vertices[j].boneIds.w = 0.0f; + } + + if (weight) + { + vertices[j].weights.x = weight[(pMesh->first_vertex + j) * 4] / 255.0f; + vertices[j].weights.y = weight[(pMesh->first_vertex + j) * 4 + 1] / 255.0f; + vertices[j].weights.z = weight[(pMesh->first_vertex + j) * 4 + 2] / 255.0f; + vertices[j].weights.w = weight[(pMesh->first_vertex + j) * 4 + 3] / 255.0f; + } + else + { + vertices[j].weights.x = 0.0f; + vertices[j].weights.y = 0.0f; + vertices[j].weights.z = 0.0f; + vertices[j].weights.w = 0.0f; + } + + if (i == 0 && (firstMesh && j == 0)) + { + m_boundingBox.m_min = vertices[j].position; + m_boundingBox.m_max= vertices[j].position; + } + else + { + m_boundingBox.m_min = glm::min(m_boundingBox.m_min, vertices[j].position); + m_boundingBox.m_max = glm::max(m_boundingBox.m_max, vertices[j].position); + } + } + + // Re-order triangle face counting to counter-clockwise + for (uint j = 0; j < pMesh->num_triangles; j++) + { + uint a = pTriangles[pMesh->first_triangle + j].vertex[0]; + uint b = pTriangles[pMesh->first_triangle + j].vertex[1]; + uint c = pTriangles[pMesh->first_triangle + j].vertex[2]; + indices[j * 3 + 0] = c - pMesh->first_vertex; + indices[j * 3 + 1] = b - pMesh->first_vertex; + indices[j * 3 + 2] = a - pMesh->first_vertex; + } + + if (pHdr->ofs_text) + { + // #TODO: weird weird getting material name string is so wrong + + const char* str = (char*)pHdr + pHdr->ofs_text; + const char* materialname = &str[pMesh->material]; + + //if (materialname) + // pMaterial = g_materialSystem.LoadMaterial(materialname); + } + + ModelData_t mesh = {}; + //mesh.m_vertices = vertices; + //mesh.m_indices = indices; + mesh.vb = g_renderDevice->CreateVertexBuffer(vertices.data(), pMesh->num_vertexes * sizeof(SkinnedMeshVertex), true); + mesh.vbcount = pMesh->num_vertexes; + mesh.ib = g_renderDevice->CreateIndexBuffer(indices.data(), pMesh->num_triangles * sizeof(iqmtriangle), false); + mesh.ibcount = pMesh->num_triangles * 3; + //mesh.m_material = pMaterial; + // + //if (!mesh.m_material) + // mesh.m_material = g_materialSystem.CreateMaterial("default_model", "textures/system/notex.tga", NULL, true); + + m_meshes.push_back(mesh); + + firstMesh = false; + } + + // get string data + char* pStrings = (char*)IQMHeaderPtr(pHdr, pHdr->ofs_text); + + // load joints + iqmjoint* pJoints = (iqmjoint*)IQMHeaderPtr(pHdr, pHdr->ofs_joints); + m_joints.resize(pHdr->num_joints); + + for (uint32_t i = 0; i < pHdr->num_joints; i++) + { + const char* jointName = &pStrings[pJoints[i].name]; + strncpy(m_joints[i].name, jointName, sizeof(m_joints[i].name)); + + m_joints[i].parentId = pJoints[i].parent; + + memcpy(&m_joints[i].origin, pJoints[i].translate, sizeof(glm::vec3)); + memcpy(&m_joints[i].orient, pJoints[i].rotate, sizeof(glm::vec4)); + + m_joints[i].orient = normalize(m_joints[i].orient); + + //Msg("%i origin %.2f %.2f %.2f, orient %.2f %.2f %.2f %.2f(w)", + // i, + // m_joints[i].origin.x, m_joints[i].origin.y, m_joints[i].origin.z, + // m_joints[i].orient.x, m_joints[i].orient.y, m_joints[i].orient.z, m_joints[i].orient.w); + } + + m_basePose = m_joints; + m_inverseBasePose.resize(m_basePose.size()); + + for (int i = 0; i < m_basePose.size(); i++) + { + // calculate inverse transform matrix + glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_basePose[i].origin); + mat *= glm::toMat4(m_basePose[i].orient); + + m_inverseBasePose[i] = glm::inverse(mat); + if (m_basePose[i].parentId >= 0) + { + m_inverseBasePose[i] *= m_inverseBasePose[m_basePose[i].parentId]; + } + } + + // load animations + iqmanim* pAnims = (iqmanim*)IQMHeaderPtr(pHdr, pHdr->ofs_anims); + iqmpose* pPoses = (iqmpose*)IQMHeaderPtr(pHdr, pHdr->ofs_poses); + ushort* pFramedata = (ushort*)IQMHeaderPtr(pHdr, pHdr->ofs_frames); + + // #TODO: tracks are Array class, with resize no ctor are called + // m_animations.resize(pHdr->num_anims); + + //m_jointTranslation.resize(pHdr->num_frames* pHdr->num_poses); + + glm::vec3 origin; + glm::vec3 scale; + glm::quat orient; + + for (uint32_t i = 0; i < pHdr->num_anims; i++) + { + Animation animation; + + const char* animName = &pStrings[pAnims[i].name]; + strncpy(animation.name, animName, sizeof(animation.name)); + + animation.numPoses = m_numPoses; + animation.numFrames = pAnims[i].num_frames; + animation.framerate = pAnims[i].framerate; + if (animation.framerate <= 0.0f) + animation.framerate = 30.0f; + + Msg("Animation: %s %i %i %i", animName, pAnims[i].first_frame, pAnims[i].num_frames, pAnims[i].framerate); + + animation.origin.resize(pHdr->num_poses * pAnims[i].num_frames); + animation.orient.resize(pHdr->num_poses * pAnims[i].num_frames); + + int dcounter = pAnims[i].first_frame * pHdr->num_framechannels; + + for (uint32_t j = 0; j < pAnims[i].num_frames; j++) + { + for (uint32_t k = 0; k < pHdr->num_poses; k++) + { + const iqmpose& p = pPoses[k]; + origin.x = p.channeloffset[0]; if (p.mask & 0x01) origin.x += *pFramedata++ * p.channelscale[0]; + origin.y = p.channeloffset[1]; if (p.mask & 0x02) origin.y += *pFramedata++ * p.channelscale[1]; + origin.z = p.channeloffset[2]; if (p.mask & 0x04) origin.z += *pFramedata++ * p.channelscale[2]; + orient.x = p.channeloffset[3]; if (p.mask & 0x08) orient.x += *pFramedata++ * p.channelscale[3]; + orient.y = p.channeloffset[4]; if (p.mask & 0x10) orient.y += *pFramedata++ * p.channelscale[4]; + orient.z = p.channeloffset[5]; if (p.mask & 0x20) orient.z += *pFramedata++ * p.channelscale[5]; + orient.w = p.channeloffset[6]; if (p.mask & 0x40) orient.w += *pFramedata++ * p.channelscale[6]; + scale.x = p.channeloffset[7]; if (p.mask & 0x80) scale.x += *pFramedata++ * p.channelscale[7]; + scale.y = p.channeloffset[8]; if (p.mask & 0x100) scale.y += *pFramedata++ * p.channelscale[8]; + scale.z = p.channeloffset[9]; if (p.mask & 0x200) scale.z += *pFramedata++ * p.channelscale[9]; + + // normalize orientation + orient = glm::normalize(orient); + + // assign to the animation track + animation.origin[j * pHdr->num_poses + k] = origin; + animation.orient[j * pHdr->num_poses + k] = orient; + } + } + + m_animations.push_back(animation); + } + + m_jointMatrices.resize(m_joints.size()); + m_finalMatrices.resize(m_joints.size()); + + // matrices + for (int i = 0; i < m_joints.size(); ++i) + { + glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_joints[i].origin); + mat *= glm::toMat4(m_joints[i].orient); + + if (m_joints[i].parentId >= 0) + m_jointMatrices[i] = m_jointMatrices[m_joints[i].parentId] * mat; + else + m_jointMatrices[i] = mat; + + m_finalMatrices[i] = m_jointMatrices[i] * m_inverseBasePose[i]; + } + + free(pHdr); } void Model::LoadObj(const char* filename) @@ -168,8 +542,10 @@ void Model::LoadObj(const char* filename) m_Vertices = vertices; + ModelData_t m_data = {}; m_data.vb = g_renderDevice->CreateVertexBuffer(vertices.data(), (int)sizeof(StaticMeshVertex) * (int)vertices.size()); m_data.vbcount = vertices.size(); + m_meshes.push_back(m_data); std::string mtlfilename = getFileNameWithoutExtension(filename); mtlfilename += ".mtl"; @@ -218,86 +594,157 @@ void Model::LoadMtl(const char* filename) fclose(file); } -void Model::Draw(const glm::mat4& model, bool isTransparent /*= false*/) +void Model::Draw(const glm::mat4& model, SkeletonInstance* instance /*= nullptr*/) { SDL_assert(g_unlitShader); - glFrontFace(GL_CCW); - glDepthFunc(GL_LESS); + Shader* shader = instance ? g_unlitSkinnedShader : g_unlitShader; - g_renderDevice->SetCullFace(true); - g_renderDevice->SetDepthTest(true); - g_renderDevice->SetDepthWrite(true); - - if (isTransparent) + for (int i = 0; i < m_meshes.size(); i++) { - // Enable blending - g_renderDevice->SetBlending(true); - g_renderDevice->SetBlendingFunction(BF_SRC_ALPHA, BF_ONE_MINUS_SRC_ALPHA); + ModelData_t& m_data = m_meshes[i]; - glm::vec4 color = glm::vec4(1.f, 1.f, 1.f, .5f); - g_shaderSystem->SetUniformFloat4(g_unlitShader, UNIFORM_CUSTOM_COLOR, glm::value_ptr(color)); - } - else - { - g_renderDevice->SetBlending(false); + glFrontFace(GL_CCW); + glDepthFunc(GL_LESS); - //glm::vec4 color = glm::vec4(1.f, 1.f, 1.f, 1.f); - //g_shaderSystem->SetUniformFloat4(g_unlitShader, UNIFORM_CUSTOM_COLOR, glm::value_ptr(color)); + g_renderDevice->SetCullFace(true); + g_renderDevice->SetDepthTest(true); + { + + + if (m_data.ib) + + + g_renderDevice->DrawElements(PT_TRIANGLES, m_data.ibcount, 0, NULL); + + glm::mat4 worldMat; + glm::mat4 worldMatParent; + for (int i = 0; i < instance->m_jointMatrices.size(); i++) + { + worldMat = model * instance->m_jointMatrices[i]; + glm::vec3 worldPos = worldMat[3]; + // g_debugRender->DrawAxis(worldPos); + + const Joint& joint = instance->m_joints[i]; + if (joint.parentId != -1) + { + worldMatParent = model * instance->m_jointMatrices[joint.parentId]; + g_debugRender->DrawLine(worldMat[3], worldMatParent[3], glm::vec3(1.0f, 1.0f, 1.0f)); + } + } } - g_renderDevice->SetVerticesBuffer(m_data.vb); +} - g_shaderSystem->SetShader(g_unlitShader); - g_shaderSystem->SetUniformMatrix(g_unlitShader, UNIFORM_MODEL_MATRIX, &model[0]); +AnimationId_t Model::FindAnimation(const char* name) +{ + for (int i = 0, numAnims = GetNumAnimations(); i < numAnims; i++) + { + if (strcmp(m_animations[i].name, name) == 0) + return i; + } - //static float test = 0.0f; - //test += g_systemTimer->GetDelta() * 6.0f; + return -1; +} - //glm::mat4 model = glm::mat4(1.0f); +uint32_t Model::GetNumAnimations() +{ + return (uint32_t)m_animations.size(); +} - //int32_t x = 0, y = 0; - //g_inputSystem->GetMousePosition(&x, &y); - // - //glm::mat4 model = glm::mat4(1.0f); - //model = glm::rotate(model, test, glm::vec3(0.0f, 1.0f, 0.0f)); - // - //float realY = (float)g_renderView.height - (float)y - 1; - // - //glm::vec4 viewport = glm::vec4(0.0f, 0.0f, (float)g_renderView.width, (float)g_renderView.height); - //glm::vec3 pos = glm::unProject( - // glm::vec3((float)x, realY, 0.0f), - // model, - // g_renderView.proj, - // viewport); - // - //pos *= 50.0f; - // - //model = glm::translate(model, pos); +const Animation* Model::GetAnimation(AnimationId_t index) +{ + if (-1 >= index || index >= GetNumAnimations()) + Core::Error("Model::GetAnimation: index %d is out of bounds.", index); - glm::mat4 mvp = glm::identity(); - mvp = g_render->GetProjectionMatrix() * g_render->GetViewMatrix() * model; - g_shaderSystem->SetUniformMatrix(g_unlitShader, UNIFORM_MVP_MATRIX, &mvp[0]); + return &m_animations[index]; +} - g_texturesManager->SetTexture(0, m_AlbedoTexture); - g_shaderSystem->SetUniformSampler(g_unlitShader, SAMPLER_ALBEDO, 0); +int Model::GetNumPoses() +{ + return m_numPoses; +} - g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_data.vbcount); +SkeletonInstance* Model::CreateSkeletonInstance() +{ + SkeletonInstance* instance = new SkeletonInstance(); + instance->m_model = this; + instance->m_joints = m_joints; + instance->m_jointMatrices.resize(m_joints.size()); + instance->m_finalMatrices.resize(m_joints.size()); + instance->m_animation = INVALID_ANIMATION_HANDLE; + instance->m_time = 0.0; + instance->m_looped = false; -#if 0 - glm::mat4 mvp = glm::identity(); - mvp = g_renderView.proj * g_renderView.view * model; - g_shaderSystem->SetUniformMatrix(g_litShader, UNIFORM_MVP_MATRIX, &mvp[0]); + for (int i = 0; i < m_joints.size(); ++i) + { + glm::mat4 mat = glm::translate(glm::mat4(1.0f), m_joints[i].origin); + mat *= glm::toMat4(m_joints[i].orient); - g_texturesManager->SetTexture(0, m_AlbedoTexture); - g_shaderSystem->SetUniformSampler(g_litShader, SAMPLER_ALBEDO, 0); + if (m_joints[i].parentId >= 0) + instance->m_jointMatrices[i] = instance->m_jointMatrices[m_joints[i].parentId] * mat; + else + instance->m_jointMatrices[i] = mat; - g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_data.vbcount); + instance->m_finalMatrices[i] = instance->m_jointMatrices[i] * m_inverseBasePose[i]; + } + + return instance; +} + +void Model::UpdateSkeletonInstance(SkeletonInstance* instance, float dt) +{ + if (!instance) + return; + + if (instance->m_animation == -1) + return; + + const Animation& animation = m_animations[instance->m_animation]; + + instance->m_time += dt; + + float frameTime = 1.0f / animation.framerate; + float totalDuration = animation.numFrames * frameTime; + + if (instance->m_time >= totalDuration && instance->m_looped) + instance->m_time = 0.0f; + + float animationFrame = instance->m_time / frameTime; + int frameA = (int)(floor(animationFrame)); + int frameB = (frameA + 1) % animation.numFrames; + + if (!instance->m_looped && frameA >= animation.numFrames - 1) + frameA = animation.numFrames - 1; + + float t = animationFrame - frameA; + + for (int i = 0; i < instance->m_joints.size(); ++i) + { + const glm::vec3& a = animation.origin[frameA * m_numPoses + i]; + const glm::vec3& b = animation.origin[frameB * m_numPoses + i]; + + const glm::quat& qa = animation.orient[frameA * m_numPoses + i]; + const glm::quat& qb = animation.orient[frameB * m_numPoses + i]; + + instance->m_joints[i].origin = glm::lerp(a, b, t); + instance->m_joints[i].orient = glm::normalize(glm::slerp(qa, qb, t)); + } + + // matrices + for (int i = 0; i < instance->m_joints.size(); ++i) + { + glm::mat4 mat = glm::translate(glm::mat4(1.0f), instance->m_joints[i].origin); + mat *= glm::toMat4(instance->m_joints[i].orient); + + if (m_joints[i].parentId >= 0) + instance->m_jointMatrices[i] = instance->m_jointMatrices[m_joints[i].parentId] * mat; + else + instance->m_jointMatrices[i] = mat; + + instance->m_finalMatrices[i] = instance->m_jointMatrices[i] * m_inverseBasePose[i]; + } - BoundingBox bbox = m_boundingBox; - TransformBoundingBox(bbox, model); - g_debugRender->DrawBoundingBox(bbox, glm::vec3(1.0f)); -#endif } void ReleaseModelData(ModelData_t& data) diff --git a/src/render/model.h b/src/render/model.h index 8767d80..32bfaee 100644 --- a/src/render/model.h +++ b/src/render/model.h @@ -5,6 +5,7 @@ #include "boundingbox.h" #include "render_shared.h" +#include "animation.h" #include #include @@ -31,18 +32,46 @@ public: Model(); ~Model(); + void LoadIqm(const char* filename); void LoadObj(const char* filename); void LoadMtl(const char* filename); - void Draw(const glm::mat4& model, bool isTransparent = false); + void Draw(const glm::mat4& model, SkeletonInstance* instance = nullptr); const BoundingBox& GetBoundingBox() { return m_boundingBox; } + // Animation + AnimationId_t FindAnimation(const char* name); + uint32_t GetNumAnimations(); + + const Animation* GetAnimation(AnimationId_t index); + + int GetNumPoses(); + + SkeletonInstance* CreateSkeletonInstance(); + void UpdateSkeletonInstance(SkeletonInstance* instance, float dt); + +// void PlayAnimation(AnimationId_t id, bool looped); +// void StopAnimation(); + private: + std::vector m_joints; + std::vector m_basePose; + std::vector m_inverseBasePose; + std::vector m_jointMatrices; + std::vector m_finalMatrices; + + std::vector m_animations; + std::vector m_Vertices; - ModelData_t m_data; + + + std::vector m_meshes; + BoundingBox m_boundingBox; Texture2D* m_AlbedoTexture; + + int m_numPoses; }; #endif // !MODEL_H diff --git a/src/render/modelsystem.cpp b/src/render/modelsystem.cpp index 5cca027..74aaf9c 100644 --- a/src/render/modelsystem.cpp +++ b/src/render/modelsystem.cpp @@ -23,11 +23,15 @@ static InputLayoutDesc_t g_staticVertexLayout[] = { static InputLayoutDesc_t g_skinnedVertexLayout[] = { { VERTEXATTR_VEC3, SHADERSEMANTIC_POSITION }, { VERTEXATTR_VEC3, SHADERSEMANTIC_NORMAL }, - { VERTEXATTR_VEC2, SHADERSEMANTIC_TEXCOORD } + { VERTEXATTR_VEC2, SHADERSEMANTIC_TEXCOORD }, + { VERTEXATTR_VEC4, SHADERSEMANTIC_BONEIDS }, + { VERTEXATTR_VEC4, SHADERSEMANTIC_WEIGHTS }, }; Shader* g_unlitShader = nullptr; Shader* g_litShader = nullptr; +Shader* g_unlitSkinnedShader = nullptr; +Shader* g_litSkinnedShader = nullptr; static std::string getFileNameWithoutExtension(const std::string& filename) { @@ -56,6 +60,10 @@ void ModelSystem::Init() // Load lighted model generic shader g_litShader = g_shaderSystem->CreateShader("lit_generic", "data/shaders/lit_generic.vs", "data/shaders/lit_generic.ps", g_staticVertexLayout, sizeof(g_staticVertexLayout) / sizeof(g_staticVertexLayout[0])); + + // Load unlighted skinned model generic shader + g_unlitSkinnedShader = g_shaderSystem->CreateShader("unlit_generic_skin", "data/shaders/unlit_generic_skin.vs", "data/shaders/unlit_generic.ps", + g_skinnedVertexLayout, sizeof(g_skinnedVertexLayout) / sizeof(g_skinnedVertexLayout[0])); } void ModelSystem::Shutdown() @@ -92,6 +100,10 @@ Model* ModelSystem::LoadModel(const char* filename) { model->LoadObj(filename); } + else if (strstr(filename, ".iqm")) + { + model->LoadIqm(filename); + } else { // Find file extension diff --git a/src/render/render_shared.h b/src/render/render_shared.h index cf97195..abc6fa6 100644 --- a/src/render/render_shared.h +++ b/src/render/render_shared.h @@ -5,6 +5,8 @@ #include #include +#include +#include typedef uint32_t index_t; @@ -207,6 +209,7 @@ enum ShaderUniform_t UNIFORM_SUN_COLOR, UNIFORM_SUN_AMBIENT, UNIFORM_CAMERA_POS, + UNIFORM_BONE_MATRICES, UNIFORM_MAX, }; @@ -238,4 +241,13 @@ struct StaticMeshVertex glm::vec2 texcoord; }; +struct SkinnedMeshVertex +{ + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texcoord; + glm::vec4 boneIds; + glm::vec4 weights; +}; + #endif \ No newline at end of file diff --git a/src/render/shadersystem.cpp b/src/render/shadersystem.cpp index cc0caaf..982a69f 100644 --- a/src/render/shadersystem.cpp +++ b/src/render/shadersystem.cpp @@ -13,6 +13,7 @@ static const char* g_uniformNameTable[UNIFORM_MAX] = "u_sunColor", "u_sunAmbientColor", "u_cameraPos", + "u_boneMatrices[0]" }; static const char* g_samplersNameTable[SAMPLER_MAX] = @@ -152,9 +153,9 @@ void ShaderSystem::SetUniformFloat4(const Shader* shader, ShaderUniform_t unifor glUniform4fv( shader->m_glUniformLocation[ uniform ], 1, ( const GLfloat* )data ); } -void ShaderSystem::SetUniformMatrix( const Shader* shader, ShaderUniform_t uniform, const void* data ) +void ShaderSystem::SetUniformMatrix( const Shader* shader, ShaderUniform_t uniform, const void* data, int numMatrices /*= 0*/ ) { - glUniformMatrix4fv( shader->m_glUniformLocation[ uniform ], 1, GL_FALSE, ( const GLfloat* )data ); + glUniformMatrix4fv( shader->m_glUniformLocation[ uniform ], numMatrices, GL_FALSE, ( const GLfloat* )data ); } void Shader::AllocateAttributes() diff --git a/src/render/shadersystem.h b/src/render/shadersystem.h index 38e6a9f..ddcdacd 100644 --- a/src/render/shadersystem.h +++ b/src/render/shadersystem.h @@ -21,7 +21,7 @@ public: void SetUniformSampler( const Shader* shader, ShaderSamplers_t sampler, int index ); void SetUniformFloat4( const Shader* shader, ShaderUniform_t uniform, const void* data ); - void SetUniformMatrix( const Shader* shader, ShaderUniform_t uniform, const void* data ); + void SetUniformMatrix( const Shader* shader, ShaderUniform_t uniform, const void* data, int numMatrices = 1 ); private: std::vector m_shaders;