From a998771486216213783238c02b146e244a7449e8 Mon Sep 17 00:00:00 2001 From: ugozapad Date: Sat, 7 Mar 2026 03:14:10 +0300 Subject: [PATCH] Big update --- data/scripts/actors/actor_player.lua | 181 +++++-- data/scripts/game_hud.lua | 70 +++ data/scripts/game_init.lua | 20 +- data/scripts/weapons/weapon_base.lua | 108 ++-- data/scripts/weapons/weapon_key.lua | 1 - data/scripts/weapons/weapon_ump.lua | 33 ++ data/textures/koshka1.jpg | Bin 0 -> 117293 bytes data/scripts/help.lua => docs/lua help.txt | 10 +- docs/экспорт оружия из goldsource.txt | 4 + docs/экспорт уровня или модели в obj.txt | 1 + src/engine/engine.cpp | 463 ++++++++++++++++- src/engine/engine.h | 4 + src/engine/ientity.cpp | 28 +- src/engine/ientity.h | 6 + src/engine/inputmanager.h | 1 + src/engine/log.cpp | 4 + src/engine/physics/physicsworld.cpp | 20 + src/engine/physics/physicsworld.h | 10 + src/engine/physics/rigidbody.cpp | 2 +- src/engine/world.cpp | 1 + src/game/actor_base.cpp | 380 ++++++++++++++ src/game/actor_base.h | 65 +++ src/game/game.cpp | 165 +++++- src/game/game.h | 7 +- src/game/game_object.cpp | 564 ++++++--------------- src/game/game_object.h | 52 +- src/game/game_ui.cpp | 79 +++ src/game/game_ui.h | 8 + src/render/model.cpp | 44 +- src/render/render.cpp | 13 +- src/render/render.h | 3 + src/render/scenemanager.cpp | 15 +- src/render/scenemanager.h | 3 + 33 files changed, 1788 insertions(+), 577 deletions(-) create mode 100644 data/scripts/game_hud.lua delete mode 100644 data/scripts/weapons/weapon_key.lua create mode 100644 data/scripts/weapons/weapon_ump.lua create mode 100644 data/textures/koshka1.jpg rename data/scripts/help.lua => docs/lua help.txt (88%) create mode 100644 docs/экспорт оружия из goldsource.txt create mode 100644 docs/экспорт уровня или модели в obj.txt create mode 100644 src/game/actor_base.cpp create mode 100644 src/game/actor_base.h create mode 100644 src/game/game_ui.cpp create mode 100644 src/game/game_ui.h diff --git a/data/scripts/actors/actor_player.lua b/data/scripts/actors/actor_player.lua index 28d2d64..db4c427 100644 --- a/data/scripts/actors/actor_player.lua +++ b/data/scripts/actors/actor_player.lua @@ -1,3 +1,15 @@ +local PLAYER_PHYS_MASS = 80.0 +local PLAYER_PHYS_RADIUS = 0.40 +local PLAYER_PHYS_HEIGHT = 1.79 +local PLAYER_PHYS_JUMPDIST = PLAYER_PHYS_RADIUS +local PLAYER_PHYS_JUMPHEIGHT = 2.0 +local PLAYER_PHYS_JUMPSPEEDY = 5.0 +local PLAYER_PHYS_WALK_SPEED = 5.5 +local PLAYER_PHYS_RUN_SPEED_MUL = 1.4 +local PLAYER_PHYS_MOVE_SPEED_EXP = 1.0 +local PLAYER_PHYS_FLY_SPEED_EXP = 4.0 +local AIR_CONTROL = 0.2 + -- игрок actor_player = inherit_table(actor_base) @@ -6,61 +18,162 @@ actor_player.m_camera_offset_y = 0.5 function actor_player:on_init() actor_base.on_init(self) - self:create_body() + g_player = self + + self:create_player_body(PLAYER_PHYS_RADIUS, PLAYER_PHYS_HEIGHT - PLAYER_PHYS_RADIUS * 2.0, 80.0, 0.0, 0.0) self:activate_camera() - local ent = engine.create_entity("test_object") + local ent = engine.create_entity("weapon_ump") 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() actor_base.on_shutdown(self) + + g_player = nil end function actor_player:on_update(dt) actor_base.on_update(self, dt) - self:update_camera_look() - --self:update_camera_movement(dt) - self:update_body_movement(dt) + self:update_camera_look() -- C++ + --self:update_camera() Lua (for perfomance should call from C++) + + --self:update_camera_movement(dt) -- C++ flying mode + --self:update_body_movement(dt) -- C++ + + self:update_player_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) - engine.play_sound("data/sounds/weapons/ump45_shoot.wav") + self:action_update() +end + +function actor_player:action_update() + local action = self:get_action() + + local ent = engine.get_entity_from_id(self.m_weapon_entity_id) + if ent then + if action == ACTION_FIRE then + ent:set_state(WEAPON_FSM_STATE_ATTACK) + elseif action == ACTION_RELOAD then + ent:set_state(WEAPON_FSM_STATE_RELOAD) + end + end +end + +-- TODO: remove +function vec3_magnitude(_x, _y, _z) + return math.sqrt(_x * _x + _y * _y + _z * _z) +end + +function vec3_normalize(_x, _y, _z) + local mag = vec3_magnitude(_x, _y, _z) + if mag == 0 then return 0, 0, 0 end + return _x / mag, _y / mag, _z / mag +end + +function vec3_cross(_x1, _y1, _z1, _x2, _y2, _z2) + return _y1 * _z2 - _y2 * _z1, + _z1 * _x2 - _z2 * _x1, + _x1 * _y2 - _x2 * _y1 +end + +function actor_player:update_player_movement(dt) + local movement = self:get_movement() + local speed = 4.0 + + local up_x, up_y, up_z = camera.get_up() + local front_x, front_y, front_z = camera.get_front() + front_y = 0.0 + + local final_front_x, final_front_y, final_front_z = vec3_normalize(front_x, front_y, front_z) + local cross_x, cross_y, cross_z = vec3_cross(final_front_x, final_front_y, final_front_z, + up_x, up_y, up_z) + + local final_cross_x, final_cross_y, final_cross_z = vec3_normalize(cross_x, cross_y, cross_z) + + local dir_x, dir_y, dir_z = 0.0, 0.0, 0.0 + + if (movement & EMovementDir_Forward) ~= 0 then + dir_x = dir_x + final_front_x + dir_y = dir_y + final_front_y + dir_z = dir_z + final_front_z + end + + if (movement & EMovementDir_Backward) ~= 0 then + dir_x = dir_x - final_front_x + dir_y = dir_y - final_front_y + dir_z = dir_z - final_front_z + end + + if (movement & EMovementDir_Left) ~= 0 then + dir_x = dir_x - final_cross_x + dir_y = dir_y - final_cross_y + dir_z = dir_z - final_cross_z + end + + if (movement & EMovementDir_Right) ~= 0 then + dir_x = dir_x + final_cross_x + dir_y = dir_y + final_cross_y + dir_z = dir_z + final_cross_z + end + + local current_vel_x, current_vel_y, current_vel_z = self:get_velocity() + + local vel_x = dir_x * speed + local vel_y = dir_y * speed + local vel_z = dir_z * speed + + + if self:on_ground() then + self:set_velocity(vel_x, current_vel_y, vel_z) + + if (movement & EMovementDir_Jump) ~= 0 then + self:set_velocity(current_vel_x, PLAYER_PHYS_JUMPSPEEDY, current_vel_z) + --console.print("!!! JUMP") + end + else + local air_vel_x = current_vel_x + dir_x * speed * AIR_CONTROL * dt + local air_vel_y = current_vel_y + dir_y * speed * AIR_CONTROL * dt + local air_vel_z = current_vel_z + dir_z * speed * AIR_CONTROL * dt + self:set_velocity(air_vel_x, current_vel_y, air_vel_z) + end +end + +local g_yaw = 0.0 +local g_pitch = 0.0 + +function actor_player:update_camera() + if not input.get_lock_mouse() then + return end - if self:get_action() == ACTION_RELOAD and self.m_in_reload == false then - ent:play_animation(ent:find_animation("reload"), ANIM_PLAYBACK_NONE) - engine.play_sound("data/sounds/weapons/ump45_reload.wav") - 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 + local mousePosX, mousePosY = input.get_mouse_pos() + local sensitivity = 0.15 -- #TODO: input.get_mouse_sensitivity() + + g_yaw = g_yaw + (mousePosX * sensitivity) + g_pitch = g_pitch - (mousePosY * sensitivity) + + -- lock axis + if g_pitch > 89.0 then g_pitch = 89.0 end + if g_pitch < -89.0 then g_pitch = -89.0 end + + camera.set_yaw_pitch(g_yaw, g_pitch) end function actor_player:on_collide(other) - console.print(string.format("actor_player:on_collide: %s", other:get_classname())) + --console.print(string.format("actor_player:on_collide: %s", other:get_classname())) + +-- put_debug_string_to_screen(string.format("actor_player:on_collide: %s", other:get_classname()), 4) + + -- FOR TEST +-- other:mark_for_delete() +end + +function actor_player:get_health() + return 100.0 end diff --git a/data/scripts/game_hud.lua b/data/scripts/game_hud.lua new file mode 100644 index 0000000..f101ef3 --- /dev/null +++ b/data/scripts/game_hud.lua @@ -0,0 +1,70 @@ +local RED_COLOR = { 1.0, 0.0, 0.0, 1.0 } +local GREEN_COLOR = { 0.0, 1.0, 0.0, 1.0 } +local BLUE_COLOR = { 0.0, 0.0, 1.0, 1.0 } +local BLACK_COLOR = { 0.0, 0.0, 0.0, 1.0 } +local WHITE_COLOR = { 1.0, 1.0, 1.0, 1.0 } + +local draw_test_hud = true +local draw_debug_string = false +local debug_string_text = "" +local debug_string_time = 0.0 +local debug_string_max_time = 0.0 + +function game_hud_draw( ) + if not draw_test_hud then + return + end + + -- example of color + local color = { 0.5, 0.5, 0.1, 1.0 } + + -- example of drawing + ui.draw_rect(150.0, 150.0, 350.0, 350.0, BLUE_COLOR) + ui.draw_image("data/textures/koshka1.jpg", 160.0, 160.0, 340.0, 340.0, WHITE_COLOR) + ui.draw_text("Hello, world!", 200.0, 200.0, GREEN_COLOR) + + if g_player then + game_player_hud_draw() + end + + -- debug + if draw_debug_string then + game_debug_string_draw() + end +end + +function game_player_hud_draw() + local display_x, display_y = ui.get_display_size() + local color = { 0.0, 0.0, 0.0, 0.3 } + local text_color = { 0.0, 0.0, 0.0, 0.6 } + local hud_rect_size_x = 200 + local hud_rect_size_y = 100 + local offset_text = ui.calc_text_width("Health") + 5 + + ui.draw_rect(0.0, display_y, hud_rect_size_x, display_y - hud_rect_size_y, color) + + ui.draw_text("Health", 10.0, display_y - hud_rect_size_y + 20, WHITE_COLOR) + ui.draw_text(string.format("%.0f", g_player:get_health()), 10.0 + offset_text, display_y - hud_rect_size_y + 20, WHITE_COLOR) +end + +function put_debug_string_to_screen(text, text_time) + debug_string_max_time = text_time + debug_string_text = text + debug_string_time = 0.0 + draw_debug_string = true +end + +function game_debug_string_draw() + debug_string_time = debug_string_time + engine.get_delta() + + if debug_string_time >= debug_string_max_time then + debug_string_max_time = 0.0 + debug_string_time = 0.0 + draw_debug_string = false + end + + local offset = ui.calc_text_width(debug_string_text) + + ui.draw_rect(490, 500, 490 + offset + 20, 550, BLACK_COLOR) + ui.draw_text(debug_string_text, 500, 500, RED_COLOR) +end \ No newline at end of file diff --git a/data/scripts/game_init.lua b/data/scripts/game_init.lua index a78a7b7..28f0755 100644 --- a/data/scripts/game_init.lua +++ b/data/scripts/game_init.lua @@ -1,25 +1,39 @@ -- Game initialization script -console.print("--- Game initialization ---") - -- загружаем скрипты load_script("game_utils.lua") +load_script("game_hud.lua") load_script("game_object.lua") load_script("test_object.lua") load_script("weapons/weapon_base.lua") + +load_script("weapons/weapon_ump.lua") + load_script("actors/actor_base.lua") load_script("actors/actor_player.lua") +-- глобальные переменные +g_player = nil + -- глобальная таблица сущностей g_entity_table = { -- Lua class -- CPP class -- Description + -- Actors + { "actor_player", "ActorBase", "Player entity" }, + -- Weapons + { "weapon_ump", "WeaponBase", "Weapon UMP" }, + -- Simple entity { "test_object", "Entity", "Test entity" }, -} \ No newline at end of file +} + +function sv_game_init( ) + console.print("--- Game initialization ---") +end diff --git a/data/scripts/weapons/weapon_base.lua b/data/scripts/weapons/weapon_base.lua index be38909..a856c8a 100644 --- a/data/scripts/weapons/weapon_base.lua +++ b/data/scripts/weapons/weapon_base.lua @@ -1,11 +1,12 @@ ----------------------------------------------------------- --- weapon_base.lua, Базоавый скрипт оружия +-- weapon_base.lua, Базовый скрипт оружия -- Автор: Кирилл -- Изменяли: -- Дата: 05.03.2026 ----------------------------------------------------------- -- индификаторы состояний FSM +WEAPON_FSM_STATE_NONE = 0 WEAPON_FSM_STATE_IDLE = 1 WEAPON_FSM_STATE_ATTACK = 2 WEAPON_FSM_STATE_ATTACK2 = 3 @@ -17,32 +18,11 @@ weapon_base = inherit_table(game_object) -- инициализация FSM weapon_base.m_fsm = {} --- покой -weapon_base.m_fsm[WEAPON_FSM_STATE_IDLE] = { - anim = "idle", -- имя анимации - anim_playback = ANIM_PLAYBACK_REPEAT, -- бесконечно играть - anim_speed = 1.0 -- обычная скорость анимации -} - --- атака -weapon_base.m_fsm[WEAPON_FSM_STATE_ATTACK] = { - anim = "attack", -- имя анимации - anim_playback = ANIM_PLAYBACK_NONE, - anim_speed = 1.0 -- обычная скорость анимации -} - --- перезарядка -weapon_base.m_fsm[WEAPON_FSM_STATE_RELOAD] = { - anim = "reload", -- имя анимации - anim_playback = ANIM_PLAYBACK_NONE, - anim_speed = 1.0 -- обычная скорость анимации -} - function weapon_base:on_init() game_object.on_init(self) -- начальное состояние FSM - self.m_state = WEAPON_FSM_STATE_IDLE + self.m_state = WEAPON_FSM_STATE_NONE self.m_next_state = WEAPON_FSM_STATE_IDLE self.m_state_time = 0.0 self.m_end_state_time = 0.0 @@ -64,46 +44,96 @@ function weapon_base:on_fsm_state_update(dt) self.m_state_time = self.m_state_time + dt -- проверка на стрельбу - if (self.m_state == WEAPON_STATE_ATTACK or - self.m_state == WEAPON_STATE_ATTACK2) and + if (self.m_state == WEAPON_FSM_STATE_ATTACK or + self.m_state == WEAPON_FSM_STATE_ATTACK2) and self.m_state_time >= self.m_end_state_time then -- переходим в ожидание - self:set_state(WEAPON_STATE_IDLE) + self:set_state(WEAPON_FSM_STATE_IDLE) end -- костыль, нету анимаций бесконечных - if self.m_state == WEAPON_STATE_IDLE and self.m_state_time >= self.m_end_state_time then + if self.m_state == WEAPON_FSM_STATE_IDLE and self.m_state_time >= self.m_end_state_time then -- переходим в ожидание - self:set_state(WEAPON_STATE_IDLE) + self:set_state(WEAPON_FSM_STATE_IDLE) end self:fsm_update(dt) end function weapon_base:set_state(next_state) + if self.m_state == next_state or self.m_next_state == next_state then + return + end + + if (self.m_state == WEAPON_FSM_STATE_ATTACK or self.m_state == WEAPON_FSM_STATE_RELOAD) + and next_state ~= WEAPON_FSM_STATE_IDLE then + return + end + self.m_next_state = next_state + console.print(string.format("weapon_base:set_state: switich to '%d' ", next_state)) end function weapon_base:fsm_update(dt) + -- надо ли менять состояние if self.m_next_state ~= self.m_state then - if self.m_next_state == WEAPON_STATE_ATTACK then - self:set_anim("attack1") - elseif self.m_next_state == WEAPON_STATE_ATTACK2 then - self:set_anim("attack1") - end + self:on_state_switch(self.m_next_state) -- сброс времени и установка его на таймер анимации self.m_state_time = 0.0 - self.m_end_state_time = render.get_anim_time(self:get_anim()) + self.m_end_state_time = self:get_animation_time(self:get_current_animation()) - -- запускаем атаку - if self.m_next_state == WEAPON_STATE_ATTACK or self.m_next_state == WEAPON_STATE_ATTACK2 then - self:attack() - end + console.print(string.format("weapon_base:fsm_update: switched from '%d' to '%d', end time %f ", + self.m_state, self.m_next_state, self.m_end_state_time)) self.m_state = self.m_next_state end -end \ No newline at end of file +end + +function weapon_base:find_fsm_state(state) + for k, v in pairs(self.m_fsm) do + if k == state then + return v + end + end + + console.print(string.format("weapon_base:find_fsm_state: state '%d' not found or not registered", state)) + return nil +end + +function weapon_base:on_state_switch(state) + -- ищем стейт в таблице + local fsm_state = self:find_fsm_state(state) + if not fsm_state then + return + end + + local anim_id = self:find_animation(fsm_state.anim) + self:play_animation(anim_id, fsm_state.anim_playback) +end + +function weapon_base: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() + + self:set_rotation_from_vectors(frontX, frontY, frontZ, + rightX, rightY, rightZ, + upX, upY, upZ) + + 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/scripts/weapons/weapon_key.lua b/data/scripts/weapons/weapon_key.lua deleted file mode 100644 index 73ee916..0000000 --- a/data/scripts/weapons/weapon_key.lua +++ /dev/null @@ -1 +0,0 @@ -weapon_key = inherit_table(weapon_base) diff --git a/data/scripts/weapons/weapon_ump.lua b/data/scripts/weapons/weapon_ump.lua new file mode 100644 index 0000000..5c22485 --- /dev/null +++ b/data/scripts/weapons/weapon_ump.lua @@ -0,0 +1,33 @@ +----------------------------------------------------------- +-- weapon_base.lua, Базоавый скрипт оружия +-- Автор: Кирилл +-- Изменяли: +-- Дата: 06.03.2026 +----------------------------------------------------------- +weapon_ump = inherit_table(weapon_base) + +-- покой +weapon_ump.m_fsm[WEAPON_FSM_STATE_IDLE] = { + anim = "idle1", -- имя анимации + anim_playback = ANIM_PLAYBACK_REPEAT, -- бесконечно играть + anim_speed = 1.0 -- обычная скорость анимации +} + +-- атака +weapon_ump.m_fsm[WEAPON_FSM_STATE_ATTACK] = { + anim = "shoot1", -- имя анимации + anim_playback = ANIM_PLAYBACK_NONE, + anim_speed = 1.0 -- обычная скорость анимации +} + +-- перезарядка +weapon_ump.m_fsm[WEAPON_FSM_STATE_RELOAD] = { + anim = "reload", -- имя анимации + anim_playback = ANIM_PLAYBACK_NONE, + anim_speed = 1.0 -- обычная скорость анимации +} + +function weapon_ump:on_init() + weapon_base.on_init(self) + self:load_model("data/models/weapons/v_ump.iqm") +end diff --git a/data/textures/koshka1.jpg b/data/textures/koshka1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d94d13c01e0afbe5fbf486f6c0859b5a2e33ea9c GIT binary patch literal 117293 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%0D+Y_%r4TfcMwCeZ%IYr@52fHrMn&NYD6Q~@5 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 literal 0 HcmV?d00001 diff --git a/data/scripts/help.lua b/docs/lua help.txt similarity index 88% rename from data/scripts/help.lua rename to docs/lua help.txt index ab26177..006b224 100644 --- a/data/scripts/help.lua +++ b/docs/lua help.txt @@ -27,6 +27,12 @@ string m_name integer m_id +Entity callbacks: +on_init() -- on entity creation +on_shutdown() -- on entity destroy +on_update(delta) -- on entity update +on_collide(other) -- on entity collide with another entity + Entity methods: load_model(string filename) @@ -40,12 +46,15 @@ get_position() -- return x, y, z set_rotation(float x, float y, float z) -- setting the euler rotation +get_rotation() -- return x, y, z + set_rotation_from_vectors(float frontx, float fronty, float frontz, float rightx, float righty, float rightz, float upx, float upy, float upz) -- rotate around axis get_classname() -- return the classname of the entity get_id() -- return the id of the entity +mark_for_delete() -- mark entity to delete find_animation(string name) -- find a animation in the model play_animation(integer id, integer mode) -- play a animation with specified mode (ANIM_PLAYBACK_NONE, ANIM_PLAYBACK_REPEAT) @@ -57,7 +66,6 @@ get_animation_time(integer id) -- return the time of a animation ActorBase methods: activate_camera() - update_camera_look() update_camera_movement(float nubmer) create_body() diff --git a/docs/экспорт оружия из goldsource.txt b/docs/экспорт оружия из goldsource.txt new file mode 100644 index 0000000..dd4c2b3 --- /dev/null +++ b/docs/экспорт оружия из goldsource.txt @@ -0,0 +1,4 @@ +1. импортировать smd с Y-up осью вверх +2. снова экспортировать как GoldSource с Y-up осью3\ +3. ввести команды для iqm.exe для сборки модели, пример команды: + iqm.exe -scale 0.0254 -rotate 0,90,0 v_m3.iqm v_m3-PV.smd after_reload.smd draw.smd idle.smd insert.smd shoot1.smd shoot2.smd start_reload.smd \ No newline at end of file diff --git a/docs/экспорт уровня или модели в obj.txt b/docs/экспорт уровня или модели в obj.txt new file mode 100644 index 0000000..2988511 --- /dev/null +++ b/docs/экспорт уровня или модели в obj.txt @@ -0,0 +1 @@ +1. ОБЯЗАТЕЛЬНО TRAINGULATE FACE !!! \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 95a9478..cda07a3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -15,6 +15,7 @@ #include "render.h" #include "renderdevice.h" #include "imguimanager.h" +#include "scenemanager.h" // game #include "game.h" @@ -77,6 +78,431 @@ void debug_overlay_render() { // ImGui::PopStyleVar(); } + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +//----------------------------------------------------------------------------- + +// Demonstrate creating a simple console window, with scrolling, filtering, completion and history. +// For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions. +struct ExampleAppConsole +{ + char InputBuf[256]; + ImVector Items; + ImVector Commands; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + ImGuiTextFilter Filter; + bool AutoScroll; + bool ScrollToBottom; + + ExampleAppConsole() + { + ClearLog(); + memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; + + // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. + Commands.push_back("help"); + Commands.push_back("history"); + Commands.push_back("clear"); + Commands.push_back("classify"); + Commands.push_back("ph_debug_draw"); + Commands.push_back("r_scene_debug_draw"); + Commands.push_back("r_show_stats"); + Commands.push_back("r_entity_debug_draw"); + Commands.push_back("map"); + Commands.push_back("quit"); + AutoScroll = true; + ScrollToBottom = false; + + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + ImGui::MemFree(History[i]); + } + + // Portable helpers + static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } + static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } + static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } + static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } + + void ClearLog() + { + for (int i = 0; i < Items.Size; i++) + ImGui::MemFree(Items[i]); + Items.clear(); + } + + void AddLog(const char* fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf) - 1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) + { + ImGui::End(); + return; + } + + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. + // So e.g. IsItemHovered() will return true when hovering the title bar. + // Here we create a context menu only available from the title bar. + if (ImGui::BeginPopupContextItem()) + { + if (ImGui::MenuItem("Close Console")) + *p_open = false; + ImGui::EndPopup(); + } + + + // TODO: display items starting from the bottom +#if 0 + if (ImGui::SmallButton("Add Debug Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } + ImGui::SameLine(); + if (ImGui::SmallButton("Add Debug Error")) { AddLog("[error] something went wrong"); } + ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { ClearLog(); } + ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); + //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + + ImGui::Separator(); + + // Options menu + if (ImGui::BeginPopup("Options")) + { + ImGui::Checkbox("Auto-scroll", &AutoScroll); + ImGui::EndPopup(); + } + + // Options, Filter + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_O, ImGuiInputFlags_Tooltip); + if (ImGui::Button("Options")) + ImGui::OpenPopup("Options"); + ImGui::SameLine(); + Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + ImGui::Separator(); +#endif + + // Reserve enough left-over height for 1 separator + 1 input text + const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) + { + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } + + // Display every line as a separate entry so we can change their color or add custom widgets. + // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping + // to only process visible items. The clipper will automatically measure the height of your first item and then + // "seek" to display only items in the visible area. + // To use the clipper we can replace your standard loop: + // for (int i = 0; i < Items.Size; i++) + // With: + // ImGuiListClipper clipper; + // clipper.Begin(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // - That your items are evenly spaced (same height) + // - That you have cheap random access to your elements (you can access them given their index, + // without processing all the ones before) + // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. + // We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices + // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage + // to improve this example code! + // If your items are of variable height: + // - Split them into same height items would be simpler and facilitate random-seeking into your list. + // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + // if (copy_to_clipboard) + // ImGui::LogToClipboard(); + for (const char* item : Items) + { + if (!Filter.PassFilter(item)) + continue; + + // Normally you would store more information in your item than just a string. + // (e.g. make Items[] an array of structure, store color/type etc.) + ImVec4 color; + bool has_color = false; + if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } + else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) + ImGui::PopStyleColor(); + } + // if (copy_to_clipboard) + // ImGui::LogFinish(); + + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. + // Using a scrollbar or mouse-wheel will take away from the bottom edge. + if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; + + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + ImGui::Separator(); + + // Command-line + bool reclaim_focus = false; + ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) + { + char* s = InputBuf; + Strtrim(s); + if (s[0]) + ExecCommand(s); + strcpy(s, ""); + reclaim_focus = true; + } + + // Auto-focus on window apparition + ImGui::SetItemDefaultFocus(); + if (reclaim_focus) + ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + + ImGui::End(); + } + + void ExecCommand(const char* command_line) + { + AddLog("# %s\n", command_line); + + const char* full_command_line = command_line; + if (const char* arg = strchr(command_line, ' ')) + { + + static char extracted[1024]; + strncpy(extracted, command_line, arg - command_line); + + command_line = extracted; + } + + // Insert into history. First find match and delete it so it can be pushed to the back. + // This isn't trying to be smart or optimal. + HistoryPos = -1; + for (int i = History.Size - 1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) + { + ImGui::MemFree(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) + { + ClearLog(); + } + else if (Stricmp(command_line, "HELP") == 0) + { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } + else if (Stricmp(command_line, "HISTORY") == 0) + { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } + else if (Stricmp(command_line, "PH_DEBUG_DRAW") == 0) + { + if (g_PhysicsWorld) + g_PhysicsWorld->ToggleDebugDraw(); + } + else if (Stricmp(command_line, "R_SCENE_DEBUG_DRAW") == 0) + { + g_sceneManager->toggleDebugRender(); + } + else if (Stricmp(command_line, "R_SHOW_STATS") == 0) + { + g_render->ToggleShowStats(); + } + else if (Stricmp(command_line, "R_ENTITY_DEBUG_DRAW") == 0) + { + extern bool g_debugEntityDraw; + g_debugEntityDraw = !g_debugEntityDraw; + } + else if (Stricmp(command_line, "MAP") == 0) + { + GetEngine()->NewGame(full_command_line + 4); + } + else if (Stricmp(command_line, "QUIT") == 0) + { + GetEngine()->RequestExit(); + } + else + { + AddLog("Unknown command: '%s'\n", command_line); + } + + // On command input, we scroll to bottom even if AutoScroll==false + ScrollToBottom = true; + } + + // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks + static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) + { + ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; + return console->TextEditCallback(data); + } + + int TextEditCallback(ImGuiInputTextCallbackData* data) + { + //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); + switch (data->EventFlag) + { + case ImGuiInputTextFlags_CallbackCompletion: + { + // Example of TEXT COMPLETION + + // Locate beginning of current word + const char* word_end = data->Buf + data->CursorPos; + const char* word_start = word_end; + while (word_start > data->Buf) + { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + break; + word_start--; + } + + // Build a list of candidates + ImVector candidates; + for (int i = 0; i < Commands.Size; i++) + if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) + candidates.push_back(Commands[i]); + + if (candidates.Size == 0) + { + // No match + AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); + } + else if (candidates.Size == 1) + { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } + else + { + // Multiple matches. Complete as much as we can.. + // So inputting "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches. + int match_len = (int)(word_end - word_start); + for (;;) + { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + break; + match_len++; + } + + if (match_len > 0) + { + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); + } + + // List matches + AddLog("Possible matches:\n"); + for (int i = 0; i < candidates.Size; i++) + AddLog("- %s\n", candidates[i]); + } + + break; + } + case ImGuiInputTextFlags_CallbackHistory: + { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) + { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } + else if (data->EventKey == ImGuiKey_DownArrow) + { + if (HistoryPos != -1) + if (++HistoryPos >= History.Size) + HistoryPos = -1; + } + + // A better implementation would preserve the data on the current input line along with cursor position. + if (prev_history_pos != HistoryPos) + { + const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, history_str); + } + } + } + return 0; + } +}; + +static ExampleAppConsole s_console; +static bool s_showConsole = false; +static bool s_lastLockedMouse = false; + +void PrintToConsole(const char* msg) +{ + s_console.AddLog(msg); +} + +static void OpenConsole() +{ + s_lastLockedMouse = g_inputManager.GetRelativeMouseMode(); + g_inputManager.SetRelativeMouseMode(false); + + s_showConsole = true; +} + +static void CloseConsole() +{ + g_inputManager.SetRelativeMouseMode(s_lastLockedMouse); + s_showConsole = false; +} + +static void UpdateConsole() +{ + s_console.Draw("Console", NULL); + // ShowExampleAppLog(NULL); +} + Engine::Engine() { } @@ -186,6 +612,17 @@ void Engine::Frame_SDL() } + if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_GRAVE) { + s_showConsole = !s_showConsole; + + if (s_showConsole) + OpenConsole(); + else + CloseConsole(); + } + + + // keyboard action if (event.key.key < kMaxKeyboardKeys) { if (event.type == SDL_EVENT_KEY_DOWN) { @@ -213,6 +650,11 @@ void Engine::Frame_SDL() } } +namespace +{ + float dt; +} + void Engine::Frame() { // *** updating @@ -224,11 +666,14 @@ void Engine::Frame() if (currentTime <= oldTime) currentTime = oldTime + 1; - float dt = oldTime > 0 ? (float)((double)(currentTime - oldTime) / frequency) : (float)(1.0f / 60.0f); + dt = oldTime > 0 ? (float)((double)(currentTime - oldTime) / frequency) : (float)(1.0f / 60.0f); // *** ImGui scope begin g_ImGuiManager.BeginFrame(); + if (s_showConsole) + UpdateConsole(); + // update physics if (g_PhysicsWorld) g_PhysicsWorld->Step(dt); @@ -281,6 +726,8 @@ void Engine::RenderFrame() if (g_world) g_world->Render(); + g_game->Render2D(); + g_render->RenderStats(); // ImGui scope end *** @@ -313,12 +760,14 @@ void Engine::NewGame(const char* mapname) Disconnect(); g_PhysicsWorld = new PhysicsWorld(); - g_PhysicsWorld->ToggleDebugDraw(); + //g_PhysicsWorld->ToggleDebugDraw(); g_world = new World(); g_render->LoadSceneXML(mapname); + CloseConsole(); + // after initializing client scene and collision system - we initialize the server game g_game->InitForNewMap(mapname); } @@ -345,6 +794,16 @@ SDL_Window* Engine::GetWindow() return m_window; } +void Engine::RequestExit() +{ + m_run = false; +} + +float Engine::GetDelta() +{ + return dt; +} + Engine g_engine; #ifdef WIN32 diff --git a/src/engine/engine.h b/src/engine/engine.h index 52e2ae3..6dd3974 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -26,6 +26,10 @@ public: SDL_Window* GetWindow(); + void RequestExit(); + + float GetDelta(); + private: SDL_Window* m_window = nullptr; bool m_run = true; diff --git a/src/engine/ientity.cpp b/src/engine/ientity.cpp index 9960aee..0b63d00 100644 --- a/src/engine/ientity.cpp +++ b/src/engine/ientity.cpp @@ -1,4 +1,5 @@ #include "ientity.h" +#include "world.h" #include @@ -10,7 +11,8 @@ IEntityBase::IEntityBase() : m_rotation(0.0f), m_scale(1.0f), m_classname(nullptr), - m_id(-1) + m_id(-1), + m_dirtyTransform(true) { m_id = s_entityId++; } @@ -43,13 +45,16 @@ const glm::mat4& IEntityBase::GetWorldTransform() void IEntityBase::UpdateTransform() { - glm::vec3 radiansRotation = glm::radians(m_rotation); + if (!m_dirtyTransform) + return; - glm::mat4 T = glm::translate(glm::mat4(1.0f), m_position); - glm::mat4 R = glm::toMat4(glm::quat(radiansRotation)); - glm::mat4 S = glm::scale(glm::mat4(1.0f), m_scale); + glm::vec3 radiansRotation = glm::vec3(glm::radians(m_rotation.x), glm::radians(m_rotation.y), glm::radians(m_rotation.z)); + glm::mat4 rotation = glm::toMat4(glm::quat(radiansRotation)); - m_worldTM = T * R * S; + m_worldTM = glm::mat4(1.0f); + m_worldTM = glm::translate(m_worldTM, m_position) * rotation * glm::scale(m_worldTM, m_scale); + + m_dirtyTransform = false; } uint32_t IEntityBase::GetID() @@ -61,3 +66,14 @@ void IEntityBase::ResetEntityID() { s_entityId = 0; } + +void IEntityBase::MarkForDelete() +{ + if (g_world) + g_world->MarkEntityToDelete(this); +} + +void IEntityBase::SetDirty() +{ + m_dirtyTransform = true; +} diff --git a/src/engine/ientity.h b/src/engine/ientity.h index adc4ab1..9ef90b4 100644 --- a/src/engine/ientity.h +++ b/src/engine/ientity.h @@ -27,6 +27,10 @@ public: static void ResetEntityID(); + void MarkForDelete(); + + void SetDirty(); + protected: glm::vec3 m_position; glm::vec3 m_rotation; @@ -39,6 +43,8 @@ protected: const char* m_classname; uint32_t m_id; + + bool m_dirtyTransform; }; // registration diff --git a/src/engine/inputmanager.h b/src/engine/inputmanager.h index eecf4d7..2c24fbc 100644 --- a/src/engine/inputmanager.h +++ b/src/engine/inputmanager.h @@ -46,6 +46,7 @@ public: glm::vec2& GetMouseDeltaPos() { return m_deltaMousePos; } void SetRelativeMouseMode(bool relativeMode); + bool GetRelativeMouseMode() { return m_relativeMouseMode; } void Frame(); diff --git a/src/engine/log.cpp b/src/engine/log.cpp index ca0a7a9..3c215e3 100644 --- a/src/engine/log.cpp +++ b/src/engine/log.cpp @@ -11,6 +11,8 @@ static int g_day_in_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 int build_id; FILE* g_logFile; +extern void PrintToConsole(const char* msg); + void CalculateBuildNumber() { static int start_day = 3; @@ -119,6 +121,8 @@ void Logger::MsgArg(const char* fmt, va_list args) //sprintf(buffer2, "[%s] %s", timestr, buffer); + PrintToConsole(buffer); + if (g_logFile) { fwrite(buffer, sizeof(char), len, g_logFile); diff --git a/src/engine/physics/physicsworld.cpp b/src/engine/physics/physicsworld.cpp index 7f66d1b..76002e3 100644 --- a/src/engine/physics/physicsworld.cpp +++ b/src/engine/physics/physicsworld.cpp @@ -136,6 +136,26 @@ void PhysicsWorld::Step(float delta) m_world->debugDrawWorld();*/ } +bool PhysicsWorld::TraceRay(TraceRayResult& _result, const glm::vec3& _rayBegin, const glm::vec3& _rayEnd, IEntityBase* _pIgnoreEntity) +{ + btVector3 rayStart = glmVectorToBt(_rayBegin); + btVector3 rayEnd = glmVectorToBt(_rayEnd); + + ClosestRayResultCallback RayResultCallback(rayStart, rayEnd, _pIgnoreEntity); + g_PhysicsWorld->GetWorld()->rayTest(rayStart, rayEnd, RayResultCallback); + + if (RayResultCallback.hasHit()) + { + _result.hit = true; + + _result.pEntity = (IEntityBase*)RayResultCallback.m_collisionObject->getUserPointer(); + _result.position = btVectorToGlm(RayResultCallback.m_hitPointWorld); + _result.normal = btVectorToGlm(RayResultCallback.m_hitNormalWorld); + } + + return _result.hit; +} + void PhysicsWorld::ToggleDebugDraw() { m_debugDraw = !m_debugDraw; diff --git a/src/engine/physics/physicsworld.h b/src/engine/physics/physicsworld.h index 6a335c0..3669a70 100644 --- a/src/engine/physics/physicsworld.h +++ b/src/engine/physics/physicsworld.h @@ -17,6 +17,14 @@ struct SceneCollisionModel btTriangleMesh* triangleMesh; }; +struct TraceRayResult +{ + glm::vec3 position; + glm::vec3 normal; + IEntityBase* pEntity; + bool hit; +}; + class PhysicsWorld { public: @@ -30,6 +38,8 @@ public: void Step(float delta); + bool TraceRay(TraceRayResult& _result, const glm::vec3& _rayBegin, const glm::vec3& _rayEnd, IEntityBase* _pIgnoreEntity); + btDynamicsWorld* GetWorld() { return m_world; } float GetFixedTimeStep() { return m_stepTime; } diff --git a/src/engine/physics/rigidbody.cpp b/src/engine/physics/rigidbody.cpp index 9f28836..3f9f149 100644 --- a/src/engine/physics/rigidbody.cpp +++ b/src/engine/physics/rigidbody.cpp @@ -76,7 +76,7 @@ RigidBody::~RigidBody() // } // } // -// CreatePlayerBody(); +// CreatePlayerBody_Old(); // UpdateBodyTranslationDirty(); // // if (changeFilterUsableHack) diff --git a/src/engine/world.cpp b/src/engine/world.cpp index 2ad2458..296eb58 100644 --- a/src/engine/world.cpp +++ b/src/engine/world.cpp @@ -43,6 +43,7 @@ void World::Update(float dt) // Update entities for (int i = 0; i < m_entities.size(); i++) { + m_entities[i]->SetDirty(); m_entities[i]->Update(dt); } diff --git a/src/game/actor_base.cpp b/src/game/actor_base.cpp new file mode 100644 index 0000000..7e99366 --- /dev/null +++ b/src/game/actor_base.cpp @@ -0,0 +1,380 @@ +#include "actor_base.h" +#include "inputmanager.h" + +#include + +REGISTER_ENTITY(ActorBase); + +ActorBase::ActorBase() +{ +} + +ActorBase::~ActorBase() +{ +} + +void ActorBase::Update(float dt) +{ + UpdateBodyDirty(); + + Entity::Update(dt); + + AfterEngineStep(); +} + +void ActorBase::AfterEngineStep() +{ + //btTransform xform = m_rigidBody->getWorldTransform(); + //m_position = btVectorToGlm(xform.getOrigin()); + + m_position = btVectorToGlm(m_ph_motion_state.m_transform.getOrigin()); + + glm::vec3 cameraPos = m_position; + + if (m_luaObject.IsTable()) + { + LuaPlus::LuaObject m_camera_offset_y = m_luaObject.GetByName("m_camera_offset_y"); + if (m_camera_offset_y.IsNumber()) + cameraPos.y += (float)m_camera_offset_y.ToNumber(); + } + + m_camera.SetPosition(cameraPos); +} + +void ActorBase::UpdateCameraMovement(float dt) +{ + // calculate player movement + float speed = 12.0f * dt; + + uint32_t movementDir = GenMovementDir(); + + if (movementDir & EMovementDir_Forward) + m_position += speed * m_camera.GetFront(); + if (movementDir & EMovementDir_Backward) + m_position -= speed * m_camera.GetFront(); + if (movementDir & EMovementDir_Left) + m_position -= glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed; + if (movementDir & EMovementDir_Right) + m_position += glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed; + + // set position back to camera for calculation view matrix + m_camera.SetPosition(m_position); +} + +void ActorBase::UpdateBodyMovement(float dt) +{ + + glm::vec3 dir = glm::vec3(0.0f); + + glm::vec3 camFront = m_camera.GetFront(); + camFront.y = 0.0f; + camFront = glm::normalize(camFront); + + // calculate player movement + float speed = 4.f; + + uint32_t movementDir = GenMovementDir(); + + if (movementDir & EMovementDir_Forward) + dir += camFront; + if (movementDir & EMovementDir_Backward) + dir -= camFront; + if (movementDir & EMovementDir_Left) + dir -= glm::normalize(glm::cross(camFront, m_camera.GetUp())); + if (movementDir & EMovementDir_Right) + dir += glm::normalize(glm::cross(camFront, m_camera.GetUp())); + + btVector3 currentvel = m_rigidBody->getLinearVelocity(); + glm::vec3 velocity = dir * speed; + + if (OnGround()) { + m_rigidBody->setLinearVelocity(btVector3(velocity.x, currentvel.y(), velocity.z)); + if (movementDir & EMovementDir_Jump) { + m_rigidBody->setLinearVelocity(btVector3(currentvel.x(), PLAYER_PHYS_JUMPSPEEDY, currentvel.z())); + } + } + else { + float airControl = 0.2f; + btVector3 airvel = currentvel + glmVectorToBt(dir * speed * airControl * dt); + m_rigidBody->setLinearVelocity(btVector3(airvel.x(), currentvel.y(), airvel.z())); + } + + /*if (glm::length(velocity) > 0.1f && OnGround() && !(movementDir & EMovementDir_Jump)) + m_rigidBody->setLinearVelocity(glmVectorToBt(velocity)); + + if ((movementDir & EMovementDir_Jump) && OnGround()) { + m_rigidBody->applyCentralImpulse(btVector3(0.0f, PLAYER_PHYS_JUMPSPEEDY, 0.0f)); + movementDir &= ~EMovementDir_Jump; + }*/ +} + +void ActorBase::UpdateCameraLook() +{ + if (!g_inputManager.GetRelativeMouseMode()) + return; + + glm::ivec2 mousePos = g_inputManager.GetMousePos(); + + // calculate yaw and pitch + static float yaw = 0.0f, pitch = 0.0f; + + int deltaX = mousePos.x; + int deltaY = mousePos.y; + + float sensitivity = 0.15f; + + yaw += deltaX * sensitivity; + pitch -= deltaY * sensitivity; + + if (pitch > 89.0f) pitch = 89.0f; + if (pitch < -89.0f) pitch = -89.0f; + + m_camera.SetYawPitch(yaw, pitch); +} + +void ActorBase::ActivateCamera() +{ + g_inputManager.SetRelativeMouseMode(true); + + g_cameraManager.SetActiveCamera(&m_camera); +} + +void ActorBase::CreatePlayerBody(float radius, float height, float mass, float friction, float damping) +{ + + m_shape = new btCapsuleShape(radius, height); + + m_mass = mass; + btVector3 local_inertia(0.0f, 0.0f, 0.0f); + if (m_mass > 0.f) { + m_shape->calculateLocalInertia(m_mass, local_inertia); + } + + btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia); + + m_rigidBody = new btRigidBody(rigid_body_ci); + m_rigidBody->setUserPointer(this); + m_rigidBody->setMotionState(&m_ph_motion_state); + m_ph_motion_state.setBody(m_rigidBody); + + // I'm sure that position is valid + btTransform xform; + xform.setIdentity(); + xform.setOrigin(glmVectorToBt(m_position)); + m_rigidBody->setWorldTransform(xform); + + // ACTOR STUFF + m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f)); + + m_rigidBody->setFriction(friction); + m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f)); + m_rigidBody->setDamping(damping, 0.0f); + + m_rigidBody->setActivationState(DISABLE_DEACTIVATION); + + // #TODO: body filter and mask + g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody); + + m_bodyDirty = true; + +} + +void ActorBase::CreatePlayerBody_Old() +{ + + m_shape = new btCapsuleShape(PLAYER_PHYS_RADIUS, PLAYER_PHYS_HEIGHT - PLAYER_PHYS_RADIUS * 2.0); + + m_mass = 80.0f; + btVector3 local_inertia(0.0f, 0.0f, 0.0f); + if (m_mass > 0.f) { + m_shape->calculateLocalInertia(m_mass, local_inertia); + } + + btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia); + + m_rigidBody = new btRigidBody(rigid_body_ci); + m_rigidBody->setUserPointer(this); + m_rigidBody->setMotionState(&m_ph_motion_state); + m_ph_motion_state.setBody(m_rigidBody); + + // I'm sure that position is valid + btTransform xform; + xform.setIdentity(); + xform.setOrigin(glmVectorToBt(m_position)); + m_rigidBody->setWorldTransform(xform); + + // ACTOR STUFF + m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f)); + + + //m_rigidBody->setFriction(2.5f); + + m_rigidBody->setFriction(0.0f); + m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f)); + m_rigidBody->setDamping(0.0f, 0.0f); + + m_rigidBody->setActivationState(DISABLE_DEACTIVATION); + + // #TODO: body filter and mask + g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody); + + m_bodyDirty = true; +} + +bool ActorBase::OnGround() +{ + float rayLength = (PLAYER_PHYS_HEIGHT / 2.0f) + 0.1f; + + btTransform xform = m_rigidBody->getWorldTransform(); + + btVector3 rayStart = xform.getOrigin(); + btVector3 rayEnd = rayStart - btVector3(0.0f, rayLength, 0.0f); + + ClosestRayResultCallback RayResultCallback(rayStart, rayEnd, this); + g_PhysicsWorld->GetWorld()->rayTest(rayStart, rayEnd, RayResultCallback); + if (RayResultCallback.hasHit()) { + btVector3 hitNormal = RayResultCallback.m_hitNormalWorld; + if (hitNormal.y() > 0.7f) { + return true; + } + } + + return false; +} + +void ActorBase::RegisterFunctions() +{ + m_luaObject.Register("activate_camera", *this, &ActorBase::Lua_ActivateCamera); + m_luaObject.Register("update_camera_look", *this, &ActorBase::Lua_UpdateCameraLook); + m_luaObject.Register("update_camera_movement", *this, &ActorBase::Lua_UpdateCameraMovement); + m_luaObject.Register("create_player_body", *this, &ActorBase::Lua_CreatePlayerBody); + m_luaObject.Register("create_player_body_old", *this, &ActorBase::Lua_CreatePlayerBodyOld); + m_luaObject.Register("update_body_movement", *this, &ActorBase::Lua_UpdateBodyMovement); + m_luaObject.Register("get_action", *this, &ActorBase::Lua_GetAction); + m_luaObject.Register("get_movement", *this, &ActorBase::Lua_GetMovement); + m_luaObject.Register("on_ground", *this, &ActorBase::Lua_OnGround); + + //m_luaObject.RegisterDirect("activate_camera", &ActorBase_ActivateCamera); + //m_luaObject.RegisterDirect("update_camera_look", &ActorBase_UpdateCameraLook); + //m_luaObject.RegisterDirect("update_camera_movement", &ActorBase_UpdateCameraMovement); +} + +int ActorBase::Lua_UpdateCameraMovement(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + + UpdateCameraMovement(stack[2].GetFloat()); + + return 0; +} + +int ActorBase::Lua_UpdateBodyMovement(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + + UpdateBodyMovement(stack[2].GetFloat()); + + return 0; +} + +int ActorBase::Lua_UpdateCameraLook(LuaPlus::LuaState* state) +{ + UpdateCameraLook(); + return 0; +} + +int ActorBase::Lua_ActivateCamera(LuaPlus::LuaState* state) +{ + ActivateCamera(); + return 0; +} + +int ActorBase::Lua_CreatePlayerBody(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + + float radius = (float)stack[2].GetNumber(); + float height = (float)stack[3].GetNumber(); + float mass = (float)stack[4].GetNumber(); + float friction = (float)stack[5].GetNumber(); + float damping = (float)stack[6].GetNumber(); + + CreatePlayerBody(radius, height, mass, friction, damping); + + return 0; +} + +int ActorBase::Lua_CreatePlayerBodyOld(LuaPlus::LuaState* state) +{ + CreatePlayerBody_Old(); + return 0; +} + +int ActorBase::Lua_GetAction(LuaPlus::LuaState* state) +{ + int action = GenAction(); + state->PushInteger(action); + + return 1; +} + +int ActorBase::Lua_GetMovement(LuaPlus::LuaState* state) +{ + uint32_t movement = GenMovementDir(); + state->PushInteger(movement); + + return 1; +} + +int ActorBase::Lua_OnGround(LuaPlus::LuaState* state) +{ + state->PushBoolean(OnGround()); + return 1; +} + +int ActorBase::GenAction() +{ + int action = -1; + + if (!g_inputManager.GetRelativeMouseMode()) + return action; + + 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; + + if (!g_inputManager.GetRelativeMouseMode()) + return movementDir; + + if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_W)) { + movementDir |= EMovementDir_Forward; + } + + if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_S)) { + movementDir |= EMovementDir_Backward; + } + + if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_A)) { + movementDir |= EMovementDir_Left; + } + + if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_D)) { + movementDir |= EMovementDir_Right; + } + + if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_SPACE)) { + movementDir |= EMovementDir_Jump; + } + + return movementDir; +} diff --git a/src/game/actor_base.h b/src/game/actor_base.h new file mode 100644 index 0000000..be5593a --- /dev/null +++ b/src/game/actor_base.h @@ -0,0 +1,65 @@ +#ifndef ACTOR_BASE_H +#define ACTOR_BASE_H + +#include "game_object.h" + +#define PLAYER_PHYS_MASS 80.0 +#define PLAYER_PHYS_RADIUS 0.40 +#define PLAYER_PHYS_HEIGHT 1.79 +#define PLAYER_PHYS_JUMPDIST PLAYER_PHYS_RADIUS +#define PLAYER_PHYS_JUMPHEIGHT 2.0 +#define PLAYER_PHYS_JUMPSPEEDY 5.0 +#define PLAYER_PHYS_WALK_SPEED ( 5.5 ) +#define PLAYER_PHYS_RUN_SPEED_MUL 1.4 +#define PLAYER_PHYS_MOVE_SPEED_EXP 1.0 +#define PLAYER_PHYS_FLY_SPEED_EXP 4.0 + +class ActorBase : public Entity +{ +public: + ActorBase(); + ~ActorBase(); + + virtual void Update(float dt); + + void AfterEngineStep(); + + void UpdateCameraMovement(float dt); + + void UpdateBodyMovement(float dt); + + void UpdateCameraLook(); + + void ActivateCamera(); + + void CreatePlayerBody(float radius, float height, float mass, float friction, float damping); + void CreatePlayerBody_Old(); + + bool OnGround(); + + // Lua bindings + + virtual void RegisterFunctions(); + + int Lua_UpdateCameraMovement(LuaPlus::LuaState* state); + int Lua_UpdateBodyMovement(LuaPlus::LuaState* state); + int Lua_UpdateCameraLook(LuaPlus::LuaState* state); + int Lua_ActivateCamera(LuaPlus::LuaState* state); + int Lua_CreatePlayerBody(LuaPlus::LuaState* state); + int Lua_CreatePlayerBodyOld(LuaPlus::LuaState* state); + int Lua_GetAction(LuaPlus::LuaState* state); + int Lua_GetMovement(LuaPlus::LuaState* state); + int Lua_OnGround(LuaPlus::LuaState* state); + +private: + int GenAction(); + uint32_t GenMovementDir(); + +private: + Camera m_camera; + + +}; + + +#endif // !ACTOR_BASE_H diff --git a/src/game/game.cpp b/src/game/game.cpp index 1f2fbe3..25e9e01 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1,8 +1,11 @@ #include "ifilesystem.h" #include "core.h" #include "log.h" +#include "engine.h" #include "game.h" #include "game_lua_help.h" +#include "game_ui.h" +#include "inputmanager.h" #include "ientity.h" #include "entitymanager.h" #include "world.h" @@ -105,6 +108,129 @@ void engineAddEntityToWorld(LuaPlus::LuaObject& object) g_world->AddEntity(entity); } +LuaPlus::LuaObject engineTraceRay(float rayBeginX, float rayBeginY, float rayBeginZ, + float rayEndX, float rayEndY, float rayEndZ, const LuaPlus::LuaObject& ignoreTable) +{ + IEntityBase* pIgnoreEntity = nullptr; + if (ignoreTable.IsTable()) + { + LuaPlus::LuaObject ignoreEntityTable = ignoreTable.GetByName("__object"); + if (ignoreEntityTable.IsLightUserdata()) + pIgnoreEntity = (IEntityBase*)ignoreEntityTable.GetLightUserdata(); + } + + TraceRayResult result = {}; + glm::vec3 rayBegin = glm::vec3(rayBeginX, rayBeginY, rayBeginZ); + glm::vec3 rayEnd = glm::vec3(rayEndX, rayEndY, rayEndZ); + + if (g_PhysicsWorld) + g_PhysicsWorld->TraceRay(result, rayBegin, rayEnd, pIgnoreEntity); + else + Logger::Msg("engine.trace_ray(): no server started or game loaded!"); + + LuaPlus::LuaObject resultTable = GetLuaState().CreateTable(); + resultTable.SetNumber("pos_x", result.position.x); + resultTable.SetNumber("pos_y", result.position.y); + resultTable.SetNumber("pos_z", result.position.z); + resultTable.SetNumber("normal_x", result.normal.x); + resultTable.SetNumber("normal_y", result.normal.y); + resultTable.SetNumber("normal_z", result.normal.z); + resultTable.SetInteger("entity_id", result.pEntity ? result.pEntity->GetID() : -1); + resultTable.SetInteger("hit", result.hit); + return resultTable; +} + +float engineGetDelta() +{ + return GetEngine()->GetDelta(); +} + +void registerEngine() +{ + using namespace LuaPlus; + + // register engine functions + LuaObject engineTable = GetLuaState().GetGlobals().CreateTable("engine"); + engineTable.RegisterDirect("error", &engineError); + engineTable.RegisterDirect("warning", &engineWarning); + engineTable.RegisterDirect("create_entity", &engineCreateEntity); + engineTable.RegisterDirect("add_entity_to_world", &engineAddEntityToWorld); + engineTable.RegisterDirect("get_entity_from_id", &engineGetEntityFromID); + engineTable.RegisterDirect("play_sound", &enginePlaySound); + engineTable.RegisterDirect("get_delta", &engineGetDelta); + engineTable.RegisterDirect("trace_ray", &engineTraceRay); + + LuaObject consoleTable = GetLuaState().GetGlobals().CreateTable("console"); + consoleTable.RegisterDirect("print", &consoleMsg); + + registerCamera(); + + registerInput(); + + registerEngineUI(); + + // action globals + GetLuaState().DoString("ACTION_FIRE = 0"); + GetLuaState().DoString("ACTION_ALT_FIRE = 1"); + GetLuaState().DoString("ACTION_RELOAD = 2"); + GetLuaState().DoString("ACTION_USE = 3"); + + // animations globals + GetLuaState().DoString("ANIM_PLAYBACK_NONE = 0"); + GetLuaState().DoString("ANIM_PLAYBACK_REPEAT = 1"); + + + char buffer[64]; + +#define REGISTER_CONSTANT(constant) \ + snprintf(buffer, sizeof(buffer), #constant" = %d", constant); \ + GetLuaState().DoString(buffer) + + REGISTER_CONSTANT(EMovementDir_None); + REGISTER_CONSTANT(EMovementDir_Forward); + REGISTER_CONSTANT(EMovementDir_Backward); + REGISTER_CONSTANT(EMovementDir_Left); + REGISTER_CONSTANT(EMovementDir_Right); + REGISTER_CONSTANT(EMovementDir_Jump); + +#undef REGISTER_CONSTANT +} + +int inputGetMousePos(LuaPlus::LuaState* state) +{ + const glm::vec2& pos = g_inputManager.GetMousePos(); + state->PushNumber(pos.x); + state->PushNumber(pos.y); + return 2; +} + +int inputGetDeltaMousePos(LuaPlus::LuaState* state) +{ + const glm::vec2& pos = g_inputManager.GetMouseDeltaPos(); + state->PushNumber(pos.x); + state->PushNumber(pos.y); + return 2; +} + +void inputLock(bool value) +{ + g_inputManager.SetRelativeMouseMode(value); +} + +bool inputGetLock() +{ + return g_inputManager.GetRelativeMouseMode(); +} + +void registerInput() +{ + LuaPlus::LuaObject inputTable = GetLuaState().GetGlobals().CreateTable("input"); + inputTable.Register("get_mouse_pos", &inputGetMousePos); + inputTable.Register("get_delta_mouse_pos", &inputGetDeltaMousePos); + inputTable.RegisterDirect("lock_mouse", &inputLock); + inputTable.RegisterDirect("get_lock_mouse", &inputGetLock); +} + int cameraGetPos(LuaPlus::LuaState* state) { glm::vec3 v = glm::vec3(0.0f); @@ -191,39 +317,23 @@ int cameraGetPitch(LuaPlus::LuaState* state) return 1; } -void registerEngine() +void cameraSetYawPitch(float yaw, float pitch) { - using namespace LuaPlus; + Camera* camera = g_cameraManager.GetActiveCamera(); + if (camera) + camera->SetYawPitch(yaw, pitch); +} - // register engine functions - LuaObject engineTable = GetLuaState().GetGlobals().CreateTable("engine"); - engineTable.RegisterDirect("error", &engineError); - engineTable.RegisterDirect("warning", &engineWarning); - engineTable.RegisterDirect("create_entity", &engineCreateEntity); - engineTable.RegisterDirect("add_entity_to_world", &engineAddEntityToWorld); - engineTable.RegisterDirect("get_entity_from_id", &engineGetEntityFromID); - engineTable.RegisterDirect("play_sound", &enginePlaySound); - - LuaObject consoleTable = GetLuaState().GetGlobals().CreateTable("console"); - consoleTable.RegisterDirect("print", &consoleMsg); - - LuaObject cameraTable = GetLuaState().GetGlobals().CreateTable("camera"); +void registerCamera() +{ + LuaPlus::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"); - + cameraTable.RegisterDirect("set_yaw_pitch", &cameraSetYawPitch); } void registerClasses() @@ -477,6 +587,11 @@ void Game::Shutdown() { } +void Game::Render2D() +{ + gameRenderUI(); +} + //LuaPrototype* pluaprototype = Lua_FindPrototype(classname); // //if (pluaprototype) diff --git a/src/game/game.h b/src/game/game.h index f233175..749dcf9 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -18,8 +18,13 @@ public: void Shutdown(); + void Render2D(); }; extern Game* g_game; -#endif \ No newline at end of file +#endif + +void registerCamera(); + +void registerInput(); diff --git a/src/game/game_object.cpp b/src/game/game_object.cpp index 7363cb6..30a23fa 100644 --- a/src/game/game_object.cpp +++ b/src/game/game_object.cpp @@ -1,68 +1,9 @@ +#include "core.h" #include "inputmanager.h" #include "debugrender.h" #include "game_object.h" #include -#define PLAYER_PHYS_MASS 80.0 -#define PLAYER_PHYS_RADIUS 0.40 -#define PLAYER_PHYS_HEIGHT 1.79 -#define PLAYER_PHYS_JUMPDIST PLAYER_PHYS_RADIUS -#define PLAYER_PHYS_JUMPHEIGHT 2.0 -#define PLAYER_PHYS_JUMPSPEEDY 5.0 -#define PLAYER_PHYS_WALK_SPEED ( 5.5 ) -#define PLAYER_PHYS_RUN_SPEED_MUL 1.4 -#define PLAYER_PHYS_MOVE_SPEED_EXP 1.0 -#define PLAYER_PHYS_FLY_SPEED_EXP 4.0 - -// Lua wrappers -void Entity_LoadModel(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); - entity->LoadModel(stack[2].GetString()); -} - -void Entity_SetVisible(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); - entity->SetVisible(stack[2].GetBoolean()); -} - -bool Entity_GetVisible(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); - return entity->GetVisible(); -} - -void ActorBase_UpdateCameraMovement(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); - entity->UpdateCameraMovement(stack[2].GetFloat()); -} - -void ActorBase_UpdateCameraLook(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); - entity->UpdateCameraLook(); -} - -void ActorBase_ActivateCamera(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); - entity->ActivateCamera(); -} - REGISTER_ENTITY(Entity); Entity::Entity() : @@ -96,6 +37,20 @@ Entity::~Entity() m_luaObject.AssignNil(); } + + if (m_rigidBody) { + g_PhysicsWorld->GetWorld()->removeRigidBody(m_rigidBody); + + delete m_rigidBody; + m_rigidBody = nullptr; + + m_ph_motion_state.setBody(nullptr); + } + + if (m_shape) { + delete m_shape; + m_shape = nullptr; + } } void Entity::Update(float dt) @@ -113,16 +68,20 @@ void Entity::Update(float dt) } } +bool g_debugEntityDraw = false; + void Entity::Render() { if (m_model) - { m_model->Draw(GetWorldTransform(), m_skeleton); - - /*BoundingBox bbox = m_boundingBox; + + + if (g_debugEntityDraw) + { + 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, 1.0f, 1.0f)); } } @@ -210,10 +169,7 @@ void Entity::InitFromTable(LuaPlus::LuaObject& _object) m_luaObject = _object; // find functions - m_onInitFunction = m_luaObject.GetByName("on_init"); - m_onShutdownFunction = m_luaObject.GetByName("on_shutdown"); - m_onUpdateFunction = m_luaObject.GetByName("on_update"); - m_onCollideFunction = m_luaObject.GetByName("on_collide"); + InitLuaCallbacks(); // check //SDL_assert_always(m_onInitFunction.IsFunction() || m_onShutdownFunction.IsFunction() || m_onUpdateFunction.IsFunction()); @@ -228,6 +184,14 @@ void Entity::InitFromTable(LuaPlus::LuaObject& _object) m_luaObject.SetLightUserdata("__object", this); } +void Entity::InitLuaCallbacks() +{ + m_onInitFunction = m_luaObject.GetByName("on_init"); + m_onShutdownFunction = m_luaObject.GetByName("on_shutdown"); + m_onUpdateFunction = m_luaObject.GetByName("on_update"); + m_onCollideFunction = m_luaObject.GetByName("on_collide"); +} + void Entity::RegisterBaseFunctions() { m_luaObject.Register("load_model", *this, &Entity::Lua_LoadModel); @@ -236,13 +200,21 @@ void Entity::RegisterBaseFunctions() 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_rotation", *this, &Entity::Lua_GetRotation); m_luaObject.Register("set_rotation_from_vectors", *this, &Entity::Lua_SetRotationFromVectors); m_luaObject.Register("get_classname", *this, &Entity::Lua_GetClassname); m_luaObject.Register("get_id", *this, &Entity::Lua_GetID); + m_luaObject.Register("mark_for_delete", *this, &Entity::Lua_MarkForDelete); + + // physics + m_luaObject.Register("set_velocity", *this, &Entity::Lua_SetVelocity); + m_luaObject.Register("get_velocity", *this, &Entity::Lua_GetVelocity); + m_luaObject.Register("has_rigid_body", *this, &Entity::Lua_HasRigidBody); // animation m_luaObject.Register("find_animation", *this, &Entity::Lua_FindAnimation); m_luaObject.Register("play_animation", *this, &Entity::Lua_PlayAnimation); + m_luaObject.Register("stop_animation", *this, &Entity::Lua_StopAnimation); 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); @@ -299,6 +271,12 @@ void Entity::Help_SetRotationFromVectors(const glm::vec3& front, const glm::vec3 m_rotation = glm::degrees(glm::eulerAngles(q)); } +void Entity::Help_SetVelocity(float x, float y, float z) +{ + if (m_rigidBody) + m_rigidBody->setLinearVelocity(btVector3(x, y, z)); +} + int Entity::Lua_LoadModel(LuaPlus::LuaState* state) { LuaPlus::LuaStack stack(state); @@ -349,6 +327,14 @@ int Entity::Lua_GetPosition(LuaPlus::LuaState* state) return 3; } +int Entity::Lua_GetRotation(LuaPlus::LuaState* state) +{ + state->PushNumber(m_rotation.x); + state->PushNumber(m_rotation.y); + state->PushNumber(m_rotation.z); + return 3; +} + int Entity::Lua_SetRotation(LuaPlus::LuaState* state) { LuaPlus::LuaStack stack(state); @@ -405,11 +391,58 @@ int Entity::Lua_UpdateTransform(LuaPlus::LuaState* state) return 0; } +int Entity::Lua_MarkForDelete(LuaPlus::LuaState* state) +{ + MarkForDelete(); + + return 0; +} + +int Entity::Lua_SetVelocity(LuaPlus::LuaState* state) +{ + LuaPlus::LuaStack stack(state); + + float x = stack[2].GetNumber(); + float y = stack[3].GetNumber(); + float z = stack[4].GetNumber(); + + Help_SetVelocity(x, y, z); + + return 0; +} + +int Entity::Lua_GetVelocity(LuaPlus::LuaState* state) +{ + glm::vec3 velocity = glm::vec3(0.0f); + if (m_rigidBody) + { + velocity = btVectorToGlm(m_rigidBody->getLinearVelocity()); + } + + state->PushNumber(velocity.x); + state->PushNumber(velocity.y); + state->PushNumber(velocity.z); + + return 3; +} + +int Entity::Lua_HasRigidBody(LuaPlus::LuaState* state) +{ + state->PushBoolean(!!m_rigidBody); + return 1; +} + int Entity::Lua_FindAnimation(LuaPlus::LuaState* state) { LuaPlus::LuaStack stack(state); const char* name = stack[2].GetString(); + if (!stack[2].IsString()) + { + Core::Warning("load_model: first argument is not an string"); + return 0; + } + if (m_model) state->PushInteger(m_model->FindAnimation(name)); else @@ -421,6 +454,13 @@ int Entity::Lua_FindAnimation(LuaPlus::LuaState* state) int Entity::Lua_PlayAnimation(LuaPlus::LuaState* state) { LuaPlus::LuaStack stack(state); + + if (!stack[2].IsNumber()) + { + Core::Warning("play_animation: first argument is not an number"); + return 0; + } + AnimationId_t id = stack[2].GetInteger(); int mode = stack[3].GetInteger(); @@ -430,6 +470,14 @@ int Entity::Lua_PlayAnimation(LuaPlus::LuaState* state) return 0; } +int Entity::Lua_StopAnimation(LuaPlus::LuaState* state) +{ + if (m_skeleton) + m_skeleton->StopAnimation(); + + return 0; +} + int Entity::Lua_GetCurrentAnimation(LuaPlus::LuaState* state) { if (m_skeleton) @@ -517,336 +565,6 @@ void Entity::UpdateBodyDirty() } } -REGISTER_ENTITY(ActorBase); - -ActorBase::ActorBase() -{ -} - -ActorBase::~ActorBase() -{ - if (m_rigidBody) { - g_PhysicsWorld->GetWorld()->removeRigidBody(m_rigidBody); - - delete m_rigidBody; - m_rigidBody = nullptr; - - m_ph_motion_state.setBody(nullptr); - } - - if (m_shape) { - delete m_shape; - m_shape = nullptr; - } -} - -void ActorBase::Update(float dt) -{ - static bool s_test = true; - - if (s_test) { - UpdateBodyDirty(); - Entity::Update(dt); - AfterEngineStep(); - } else { - ActivateCamera(); - - UpdateCameraLook(); - - UpdateCameraMovement(dt); - - } - - return; -} - -void ActorBase::AfterEngineStep() -{ - //btTransform xform = m_rigidBody->getWorldTransform(); - //m_position = btVectorToGlm(xform.getOrigin()); - - m_position = btVectorToGlm(m_ph_motion_state.m_transform.getOrigin()); - - glm::vec3 cameraPos = m_position; - - if (m_luaObject.IsTable()) - { - LuaPlus::LuaObject m_camera_offset_y = m_luaObject.GetByName("m_camera_offset_y"); - if (m_camera_offset_y.IsNumber()) - cameraPos.y += m_camera_offset_y.ToNumber(); - } - - m_camera.SetPosition(cameraPos); -} - -void ActorBase::UpdateCameraMovement(float dt) -{ - // calculate player movement - float speed = 12.0f * dt; - - uint32_t movementDir = GenMovementDir(); - - if (movementDir & EMovementDir_Forward) - m_position += speed * m_camera.GetFront(); - if (movementDir & EMovementDir_Backward) - m_position -= speed * m_camera.GetFront(); - if (movementDir & EMovementDir_Left) - m_position -= glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed; - if (movementDir & EMovementDir_Right) - m_position += glm::normalize(glm::cross(m_camera.GetFront(), m_camera.GetUp())) * speed; - - // set position back to camera for calculation view matrix - m_camera.SetPosition(m_position); -} - -void ActorBase::UpdateBodyMovement(float dt) -{ - - glm::vec3 dir = glm::vec3(0.0f); - - glm::vec3 camFront = m_camera.GetFront(); - camFront.y = 0.0f; - camFront = glm::normalize(camFront); - - // calculate player movement - float speed = 4.f; - - uint32_t movementDir = GenMovementDir(); - - if (movementDir & EMovementDir_Forward) - dir += camFront; - if (movementDir & EMovementDir_Backward) - dir -= camFront; - if (movementDir & EMovementDir_Left) - dir -= glm::normalize(glm::cross(camFront, m_camera.GetUp())); - if (movementDir & EMovementDir_Right) - dir += glm::normalize(glm::cross(camFront, m_camera.GetUp())); - - btVector3 currentvel = m_rigidBody->getLinearVelocity(); - glm::vec3 velocity = dir * speed; - - if (OnGround()) { - m_rigidBody->setLinearVelocity(btVector3(velocity.x, currentvel.y(), velocity.z)); - if (movementDir & EMovementDir_Jump) { - m_rigidBody->setLinearVelocity(btVector3(currentvel.x(), PLAYER_PHYS_JUMPSPEEDY, currentvel.z())); - } - } else { - float airControl = 0.2f; - btVector3 airvel = currentvel + glmVectorToBt(dir * speed * airControl * dt); - m_rigidBody->setLinearVelocity(btVector3(airvel.x(), currentvel.y(), airvel.z())); - } - - /*if (glm::length(velocity) > 0.1f && OnGround() && !(movementDir & EMovementDir_Jump)) - m_rigidBody->setLinearVelocity(glmVectorToBt(velocity)); - - if ((movementDir & EMovementDir_Jump) && OnGround()) { - m_rigidBody->applyCentralImpulse(btVector3(0.0f, PLAYER_PHYS_JUMPSPEEDY, 0.0f)); - movementDir &= ~EMovementDir_Jump; - }*/ -} - -void ActorBase::UpdateCameraLook() -{ - glm::ivec2 mousePos = g_inputManager.GetMousePos(); - - // calculate yaw and pitch - static float yaw = 0.0f, pitch = 0.0f; - - int deltaX = mousePos.x; - int deltaY = mousePos.y; - - float sensitivity = 0.15f; - - yaw += deltaX * sensitivity; - pitch -= deltaY * sensitivity; - - if (pitch > 89.0f) pitch = 89.0f; - if (pitch < -89.0f) pitch = -89.0f; - - m_camera.SetYawPitch(yaw, pitch); -} - -void ActorBase::ActivateCamera() -{ - g_inputManager.SetRelativeMouseMode(true); - - g_cameraManager.SetActiveCamera(&m_camera); -} - -void ActorBase::CreatePlayerBody() -{ - - m_shape = new btCapsuleShape(PLAYER_PHYS_RADIUS, PLAYER_PHYS_HEIGHT - PLAYER_PHYS_RADIUS * 2.0); - - m_mass = 80.0f; - btVector3 local_inertia(0.0f, 0.0f, 0.0f); - if (m_mass > 0.f) { - m_shape->calculateLocalInertia(m_mass, local_inertia); - } - - btRigidBody::btRigidBodyConstructionInfo rigid_body_ci(m_mass, nullptr, m_shape, local_inertia); - - m_rigidBody = new btRigidBody(rigid_body_ci); - m_rigidBody->setUserPointer(this); - m_rigidBody->setMotionState(&m_ph_motion_state); - m_ph_motion_state.setBody(m_rigidBody); - - // I'm sure that position is valid - btTransform xform; - xform.setIdentity(); - xform.setOrigin(glmVectorToBt(m_position)); - m_rigidBody->setWorldTransform(xform); - - // ACTOR STUFF - m_rigidBody->setAngularFactor(btVector3(0.0f, 0.0f, 0.0f)); - - - //m_rigidBody->setFriction(2.5f); - - m_rigidBody->setFriction(0.0f); - m_rigidBody->setAnisotropicFriction(btVector3(0.0f, 0.0f, 0.0f)); - m_rigidBody->setDamping(0.0f, 0.0f); - - m_rigidBody->setActivationState(DISABLE_DEACTIVATION); - - // #TODO: body filter and mask - g_PhysicsWorld->GetWorld()->addRigidBody(m_rigidBody); - - m_bodyDirty = true; -} - -bool ActorBase::OnGround() -{ - float rayLength = (PLAYER_PHYS_HEIGHT / 2.0f) + 0.1f; - - btTransform xform = m_rigidBody->getWorldTransform(); - - btVector3 rayStart = xform.getOrigin(); - btVector3 rayEnd = rayStart - btVector3(0.0f, rayLength, 0.0f); - - ClosestRayResultCallback RayResultCallback(rayStart, rayEnd, this); - g_PhysicsWorld->GetWorld()->rayTest(rayStart, rayEnd, RayResultCallback); - if (RayResultCallback.hasHit()) { - btVector3 hitNormal = RayResultCallback.m_hitNormalWorld; - if (hitNormal.y() > 0.7f) { - return true; - } - } - - return false; -} - -void ActorBase::RegisterFunctions() -{ - m_luaObject.Register("activate_camera", *this, &ActorBase::Lua_ActivateCamera); - m_luaObject.Register("update_camera_look", *this, &ActorBase::Lua_UpdateCameraLook); - 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); - //m_luaObject.RegisterDirect("update_camera_movement", &ActorBase_UpdateCameraMovement); -} - -int ActorBase::Lua_UpdateCameraMovement(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - UpdateCameraMovement(stack[2].GetFloat()); - - return 0; -} - -int ActorBase::Lua_UpdateBodyMovement(LuaPlus::LuaState* state) -{ - LuaPlus::LuaStack stack(state); - - UpdateBodyMovement(stack[2].GetFloat()); - - return 0; -} - -int ActorBase::Lua_UpdateCameraLook(LuaPlus::LuaState* state) -{ - UpdateCameraLook(); - return 0; -} - -int ActorBase::Lua_ActivateCamera(LuaPlus::LuaState* state) -{ - ActivateCamera(); - return 0; -} - -int ActorBase::Lua_CreateBody(LuaPlus::LuaState* state) -{ - CreatePlayerBody(); - 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; - - if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_W)) { - movementDir |= EMovementDir_Forward; - } - - if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_S)) { - movementDir |= EMovementDir_Backward; - } - - if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_A)) { - movementDir |= EMovementDir_Left; - } - - if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_D)) { - movementDir |= EMovementDir_Right; - } - - if (g_inputManager.GetKeyboard().IsKeyDown(SDLK_SPACE)) { - movementDir |= EMovementDir_Jump; - } - - return movementDir; -} - REGISTER_ENTITY(WeaponBase); WeaponBase::WeaponBase() @@ -859,8 +577,58 @@ WeaponBase::~WeaponBase() void WeaponBase::Update(float dt) { + Entity::Update(dt); } void WeaponBase::Fire(const glm::vec3& direction, float damage) { } + +//// Lua wrappers +//void Entity_LoadModel(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); +// entity->LoadModel(stack[2].GetString()); +//} +// +//void Entity_SetVisible(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); +// entity->SetVisible(stack[2].GetBoolean()); +//} +// +//bool Entity_GetVisible(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// Entity* entity = (Entity*)stack[1].GetByName("__object").GetLightUserdata(); +// return entity->GetVisible(); +//} +// +//void ActorBase_UpdateCameraMovement(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); +// entity->UpdateCameraMovement(stack[2].GetFloat()); +//} +// +//void ActorBase_UpdateCameraLook(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); +// entity->UpdateCameraLook(); +//} +// +//void ActorBase_ActivateCamera(LuaPlus::LuaState* state) +//{ +// LuaPlus::LuaStack stack(state); +// +// ActorBase* entity = (ActorBase*)stack[1].GetByName("__object").GetLightUserdata(); +// entity->ActivateCamera(); +//} diff --git a/src/game/game_object.h b/src/game/game_object.h index 254bb09..0d1f075 100644 --- a/src/game/game_object.h +++ b/src/game/game_object.h @@ -89,6 +89,7 @@ public: // Game entity lua bindings void InitFromTable(LuaPlus::LuaObject& _object); + void InitLuaCallbacks(); void RegisterBaseFunctions(); virtual void RegisterFunctions(); @@ -97,19 +98,27 @@ public: void Help_SetPosition(float x, float y, float z); void Help_SetRotation(float x, float y, float z); void Help_SetRotationFromVectors(const glm::vec3& front, const glm::vec3& right, const glm::vec3& up); + void Help_SetVelocity(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_GetRotation(LuaPlus::LuaState* state); int Lua_SetRotation(LuaPlus::LuaState* state); int Lua_SetRotationFromVectors(LuaPlus::LuaState* state); int Lua_GetClassname(LuaPlus::LuaState* state); int Lua_GetID(LuaPlus::LuaState* state); int Lua_UpdateTransform(LuaPlus::LuaState* state); + int Lua_MarkForDelete(LuaPlus::LuaState* state); + + int Lua_SetVelocity(LuaPlus::LuaState* state); + int Lua_GetVelocity(LuaPlus::LuaState* state); + int Lua_HasRigidBody(LuaPlus::LuaState* state); int Lua_FindAnimation(LuaPlus::LuaState* state); int Lua_PlayAnimation(LuaPlus::LuaState* state); + int Lua_StopAnimation(LuaPlus::LuaState* state); int Lua_GetCurrentAnimation(LuaPlus::LuaState* state); int Lua_GetCurrentAnimationTime(LuaPlus::LuaState* state); int Lua_GetAnimationTime(LuaPlus::LuaState* state); @@ -136,49 +145,6 @@ protected: bool m_bodyDirty; }; -class ActorBase : public Entity -{ -public: - ActorBase(); - ~ActorBase(); - - virtual void Update(float dt); - - void AfterEngineStep(); - - void UpdateCameraMovement(float dt); - - void UpdateBodyMovement(float dt); - - void UpdateCameraLook(); - - void ActivateCamera(); - - void CreatePlayerBody(); - - bool OnGround(); - - // Lua bindings - - virtual void RegisterFunctions(); - - int Lua_UpdateCameraMovement(LuaPlus::LuaState* state); - int Lua_UpdateBodyMovement(LuaPlus::LuaState* state); - 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: - Camera m_camera; - - -}; - class WeaponBase : public Entity { public: diff --git a/src/game/game_ui.cpp b/src/game/game_ui.cpp new file mode 100644 index 0000000..c1d2cb1 --- /dev/null +++ b/src/game/game_ui.cpp @@ -0,0 +1,79 @@ +#include "core.h" +#include "game_ui.h" +#include "game_lua_help.h" +#include "texturesmanager.h" + +#include + +using namespace LuaPlus; + +uint32_t ColorFromLua(const LuaObject& color) +{ + SDL_assert_always(color.IsTable()); + + float r = color[1].ToNumber(); + float g = color[2].ToNumber(); + float b = color[3].ToNumber(); + float a = color[4].ToNumber(); + + return ImGui::ColorConvertFloat4ToU32(ImVec4(r, g, b, a)); +} + +void uiDrawText(const char* text, float x, float y, const LuaObject& color) +{ + ImGui::GetBackgroundDrawList()->AddText(ImVec2(x, y), ColorFromLua(color), text); +} + +void uiDrawRect(float x, float y, float w, float h, const LuaObject& color) +{ + ImGui::GetBackgroundDrawList()->AddRectFilled(ImVec2(x, y), ImVec2(w, h), ColorFromLua(color)); +} + +void uiDrawImage(const char* filename, float x, float y, float w, float h, const LuaObject& color) +{ + ImTextureID texture = (ImTextureID)g_texturesManager->LoadTexture2D(filename); + ImGui::GetBackgroundDrawList()->AddImage(texture, ImVec2(x, y), ImVec2(w, h), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ColorFromLua(color)); +} + +int uiGetDisplaySize(LuaState* state) +{ + state->PushNumber(ImGui::GetIO().DisplaySize.x); + state->PushNumber(ImGui::GetIO().DisplaySize.y); + return 2; +} + +int uiGetMousePos(LuaState* state) +{ + state->PushNumber(ImGui::GetIO().MousePos.x); + state->PushNumber(ImGui::GetIO().MousePos.y); + return 2; +} + +int uiCalcTextWidth(LuaState* state) +{ + LuaStack stack(state); + SDL_assert_always(stack[1].IsString()); + + state->PushNumber(ImGui::CalcTextSize(stack[1].GetString()).x); + + return 1; +} + +void registerEngineUI() +{ + LuaObject uiTable = GetLuaState().GetGlobals().CreateTable("ui"); + uiTable.RegisterDirect("draw_text", &uiDrawText); + uiTable.RegisterDirect("draw_rect", &uiDrawRect); + uiTable.RegisterDirect("draw_image", &uiDrawImage); + uiTable.Register("get_display_size", &uiGetDisplaySize); + uiTable.Register("get_mouse_pos", &uiGetMousePos); + uiTable.Register("calc_text_width", &uiCalcTextWidth); +} + +void gameRenderUI() +{ + LuaObject renderFunction = GetLuaState().GetGlobal("game_hud_draw"); + + LuaFunctionVoid function = renderFunction; + function(); +} diff --git a/src/game/game_ui.h b/src/game/game_ui.h new file mode 100644 index 0000000..500b669 --- /dev/null +++ b/src/game/game_ui.h @@ -0,0 +1,8 @@ +#ifndef GAME_UI_H +#define GAME_UI_H + +void registerEngineUI(); + +void gameRenderUI(); + +#endif // !GAME_UI_H diff --git a/src/render/model.cpp b/src/render/model.cpp index f194a21..2842ddf 100644 --- a/src/render/model.cpp +++ b/src/render/model.cpp @@ -674,24 +674,24 @@ void Model::Draw(const glm::mat4& model, SkeletonInstance* instance /*= nullptr* } // debug draw - if (instance) - { - 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); + //if (instance) + //{ + // 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)); - } - } - } + // 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)); + // } + // } + //} } @@ -764,7 +764,7 @@ void Model::UpdateSkeletonInstance(SkeletonInstance* instance, float dt) instance->m_time += dt; float frameTime = 1.0f / animation.framerate; - float totalDuration = animation.numFrames * frameTime; + float totalDuration = (animation.numFrames - 1) * frameTime; if (instance->m_time >= totalDuration && instance->m_looped) instance->m_time = 0.0f; @@ -772,11 +772,17 @@ void Model::UpdateSkeletonInstance(SkeletonInstance* instance, float dt) float animationFrame = instance->m_time / frameTime; int frameA = (int)(floor(animationFrame)); int frameB = (frameA + 1) % animation.numFrames; + float t = animationFrame - frameA; if (!instance->m_looped && frameA >= animation.numFrames - 1) frameA = animation.numFrames - 1; - float t = animationFrame - frameA; + if (instance->m_time >= totalDuration) + { + frameA = animation.numFrames - 1; + frameB = animation.numFrames - 1; + t = 0.0f; + } for (int i = 0; i < instance->m_joints.size(); ++i) { diff --git a/src/render/render.cpp b/src/render/render.cpp index d77b191..0856eca 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -14,7 +14,7 @@ static GLuint g_VAO = 0; -static int g_NumModels = 0; +int g_NumModels = 0; // TEMP glm::vec3 g_viewOrigin; @@ -77,7 +77,8 @@ Render::Render() : m_pWindow(nullptr), m_pGLContext(nullptr), m_pStretchedPicVBuf(nullptr), - m_usingVAO(false) + m_usingVAO(false), + m_showStats(true) { m_viewMatrix = glm::identity(); m_projectionMatrix = glm::identity(); @@ -296,6 +297,9 @@ void Render::RenderScene() { void Render::RenderStats() { + if (!m_showStats) + return; + char buffer[256]; snprintf(buffer, sizeof(buffer), "FPS: %.1f", ImGui::GetIO().Framerate); @@ -343,6 +347,11 @@ void Render::LoadSceneXML(const char* filename) g_sceneManager->loadScene(filename); } +void Render::ToggleShowStats() +{ + m_showStats = !m_showStats; +} + //IRender* GetRender() //{ // return g_render; diff --git a/src/render/render.h b/src/render/render.h index 41448d5..cd9b2d0 100644 --- a/src/render/render.h +++ b/src/render/render.h @@ -36,6 +36,8 @@ public: SDL_GLContext GetGLContext() { return m_pGLContext; } + void ToggleShowStats(); + private: glm::mat4 m_viewMatrix; glm::mat4 m_projectionMatrix; @@ -46,6 +48,7 @@ private: GPUBuffer* m_pStretchedPicVBuf; bool m_usingVAO; + bool m_showStats; }; extern Render* g_render; diff --git a/src/render/scenemanager.cpp b/src/render/scenemanager.cpp index 95c00c3..41830f3 100644 --- a/src/render/scenemanager.cpp +++ b/src/render/scenemanager.cpp @@ -18,6 +18,9 @@ #include +extern int g_NumModels; +static bool g_debugRenderScene = false; + static std::string getFileExtension(const std::string& filename) { size_t whereIsDot = filename.find_last_of('.'); @@ -398,7 +401,8 @@ void SceneManager::renderScene(const glm::mat4& cameraTranslation) (*it)->RenderObjects(); - g_debugRender->DrawBoundingBox((*it)->GetBoundingBox(), glm::vec3(1.0f)); + if (g_debugRenderScene) + g_debugRender->DrawBoundingBox((*it)->GetBoundingBox(), glm::vec3(1.0f)); } } @@ -482,6 +486,11 @@ const char* SceneManager::getSceneName() return m_sceneName.c_str(); } +void SceneManager::toggleDebugRender() +{ + g_debugRenderScene = !g_debugRenderScene; +} + // SceneStaticMesh SceneStaticMesh::SceneStaticMesh() : @@ -818,7 +827,7 @@ void R_SceneStaticMesh_BindShader(const glm::mat4& worldMatrix, Texture2D* albed { glm::vec4 lightPos = glm::vec4(1.0f, 1.0f, 1.0f, 0.0f); - g_debugRender->DrawAxis(glm::vec3(lightPos)); + // g_debugRender->DrawAxis(glm::vec3(lightPos)); Camera* camera = g_cameraManager.GetActiveCamera(); if (camera) @@ -853,4 +862,6 @@ void SceneStaticMesh::RenderObjects() R_SceneStaticMesh_BindShader(s_identity, m_albedoTexture); g_renderDevice->DrawArrays(PT_TRIANGLES, 0, m_vbcount); + + g_NumModels++; } diff --git a/src/render/scenemanager.h b/src/render/scenemanager.h index fa05071..f8fb51f 100644 --- a/src/render/scenemanager.h +++ b/src/render/scenemanager.h @@ -43,6 +43,9 @@ public: // \brief Get current scene name. const char* getSceneName(); + // \brief Toggle an debug rendering. + void toggleDebugRender(); + private: void LoadSceneXML(const char* filename); void loadSkybox(const char*);