From 8d255731e606ebec8340898afe230007e75c175a Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Sat, 28 Dec 2024 20:50:39 -0600 Subject: [PATCH] Read Descriptio Added: - Engine Profiler - Playback - Lua Math Library - Temp Folder Access --- assets/scripts/BouncingItem.lua | 95 ++++++++++ assets/scripts/math.lua | 285 ++++++++++++++++++++++++++-- imgui.ini | 117 ++++++++++-- scenes/Default.scene | 88 ++++----- src/Componenets/CameraComponent.cpp | 172 +++++++++++++++++ src/Componenets/CameraComponent.h | 56 ++++++ src/Componenets/Component.h | 24 ++- src/Componenets/GameObject.cpp | 1 + src/Componenets/ScriptComponent.cpp | 3 +- src/Engine.cpp | 96 ++++++++-- src/Engine.h | 8 + src/Engine/AssetManager.h | 7 - src/Engine/LuaAPI.cpp | 1 + src/Engine/Profiler.h | 47 +++++ src/Engine/ScopedTimer.cpp | 12 ++ src/Engine/ScopedTimer.h | 17 ++ src/Engine/Utilitys.cpp | 58 ++++++ src/Engine/Utilitys.h | 7 + src/Windows/InspectorWindow.cpp | 137 ++++++++----- src/Windows/InspectorWindow.h | 5 +- src/Windows/LoggerWindow.cpp | 2 +- src/Windows/LuaEditorWindow.cpp | 2 +- src/Windows/PerformanceWindow.cpp | 2 +- src/Windows/ProfilerWindow.cpp | 252 ++++++++++++++++++++++++ src/Windows/ProfilerWindow.h | 42 ++++ src/Windows/RenderWindow.cpp | 107 ++++++++--- src/Windows/RenderWindow.h | 2 +- src/Windows/SceneWindow.cpp | 70 ++++--- 28 files changed, 1498 insertions(+), 217 deletions(-) create mode 100644 assets/scripts/BouncingItem.lua create mode 100644 src/Componenets/CameraComponent.cpp create mode 100644 src/Componenets/CameraComponent.h create mode 100644 src/Engine/Profiler.h create mode 100644 src/Engine/ScopedTimer.cpp create mode 100644 src/Engine/ScopedTimer.h create mode 100644 src/Engine/Utilitys.cpp create mode 100644 src/Engine/Utilitys.h create mode 100644 src/Windows/ProfilerWindow.cpp create mode 100644 src/Windows/ProfilerWindow.h diff --git a/assets/scripts/BouncingItem.lua b/assets/scripts/BouncingItem.lua new file mode 100644 index 0000000..37b5848 --- /dev/null +++ b/assets/scripts/BouncingItem.lua @@ -0,0 +1,95 @@ +-- script.lua +local Math = require("math") -- Require the enhanced math module + +-- Variables to track elapsed time and rotation +local elapsedTime = 0 +local rotationSpeed = 90 -- Degrees per second for spinning +local new_rotation = 0 + +-- Variables for bobbing effect +local initial_position = {x = 0, y = 0, z = 0} -- To store the gun's initial position +local bobAmplitude = 0.1 -- Amplitude of the bobbing (units) +local bobFrequency = 0.5 -- Frequency of the bobbing (oscillations per second) + +-- Reference to the Gun GameObject and its Transform component +local gun = nil +local transform = nil + +local TAU = 6.283185307179586 + + +-- Update function called every frame +function OnUpdate(deltaTime) + -- Ensure that the Gun and its Transform component are valid + if not gun then + gun = Engine.GetGameObjectByTag("Gun") + if gun then + transform = gun:GetComponent("Transform") + if transform then + local pos = transform:GetPosition() + initial_position = {x = pos.x, y = pos.y, z = pos.z} + Engine.Log("Gun found and initial position updated.", {1, 1, 1, 1}) + else + Engine.Log("Transform component not found on Gun.", {1, 1, 0, 1}) + return + end + else + Engine.Log("Gun GameObject still not found.", {1, 1, 0, 1}) + return + end + elseif not transform then + transform = gun:GetComponent("Transform") + if transform then + local pos = transform:GetPosition() + initial_position = {x = pos.x, y = pos.y, z = pos.z} + Engine.Log("Transform component found and initial position updated.", {1, 1, 1, 1}) + else + Engine.Log("Transform component still not found on Gun.", {1, 1, 0, 1}) + return + end + end + + -- Increment elapsed time + elapsedTime = elapsedTime + deltaTime + + -- === Spinning the Gun === + -- Update the rotation angle based on rotationSpeed and deltaTime + new_rotation = new_rotation + (deltaTime * rotationSpeed) + + -- Keep the rotation angle within 0-360 degrees to prevent overflow + if new_rotation >= 360 then + new_rotation = new_rotation - 360 + end + + -- Define the new rotation (spinning around the Y-axis) + local rotation = { + x = -180, -- Preserving existing rotation on X-axis + y = new_rotation, -- Updated rotation on Y-axis for spinning + z = 0 -- Preserving existing rotation on Z-axis + } + + -- Apply the new rotation to the Transform component + transform:SetRotation(rotation) + + -- === Bobbing the Gun Up and Down === + -- Calculate the bobbing offset using a sine wave + local bobOffset = bobAmplitude * math.sin(TAU * bobFrequency * elapsedTime) + + -- Define the new position by adding the bobbing offset to the initial Y position + local new_position = { + x = initial_position.x, -- No change on X-axis + y = initial_position.y + bobOffset, -- Bouncing up and down on Y-axis + z = initial_position.z -- No change on Z-axis + } + + -- Apply the new position to the Transform component + transform:SetPosition(new_position) + + -- === Optional: Log Current Rotation and Position === + -- Uncomment the following lines if you wish to log the gun's current rotation and position + -- local current_rotation = transform:GetRotation() + -- Engine.Log(string.format("Gun Rotation: (X: %.2f, Y: %.2f, Z: %.2f)", current_rotation.x, current_rotation.y, current_rotation.z), {1, 1, 1, 1}) + + -- local current_position = transform:GetPosition() + -- Engine.Log(string.format("Gun Position: (X: %.2f, Y: %.2f, Z: %.2f)", current_position.x, current_position.y, current_position.z), {1, 1, 1, 1}) +end diff --git a/assets/scripts/math.lua b/assets/scripts/math.lua index 4cfa597..9a42096 100644 --- a/assets/scripts/math.lua +++ b/assets/scripts/math.lua @@ -2,9 +2,12 @@ -- Math constants local MathConstants = { - PI = 3.14159, - E = 2.71828, - TAU = 6.28318 -- 2 * PI + PI = 3.141592653589793, + E = 2.718281828459045, + TAU = 6.283185307179586, -- 2 * PI + HALF_PI = 1.5707963267948966, -- PI / 2 + SQRT2 = 1.4142135623730951, -- Square root of 2 + LN2 = 0.6931471805599453 -- Natural log of 2 } -- Basic math functions @@ -18,40 +21,284 @@ function MathFunctions.cube(x) return x * x * x end -function MathFunctions.max(a, b) - return (a > b) and a or b +function MathFunctions.max(...) + local args = {...} + local maxVal = args[1] + for i = 2, #args do + if args[i] > maxVal then + maxVal = args[i] + end + end + return maxVal end -function MathFunctions.min(a, b) - return (a < b) and a or b +function MathFunctions.min(...) + local args = {...} + local minVal = args[1] + for i = 2, #args do + if args[i] < minVal then + minVal = args[i] + end + end + return minVal end function MathFunctions.clamp(value, minValue, maxValue) - if value < minValue then - return minValue - elseif value > maxValue then - return maxValue - else - return value - end + return MathFunctions.max(minValue, MathFunctions.min(value, maxValue)) +end + +function MathFunctions.lerp(a, b, t) + return a + (b - a) * t +end + +function MathFunctions.is_close(a, b, tolerance) + tolerance = tolerance or 1e-9 + return MathFunctions.abs(a - b) <= tolerance +end + +-- Optimized absolute value function +function MathFunctions.abs(x) + return (x < 0) and -x or x end -- Trigonometric functions local TrigFunctions = {} -function TrigFunctions.deg_to_rad(degrees) - return degrees * (MathConstants.PI / 180) +-- Manual definitions of hyperbolic functions +function TrigFunctions.sinh(x) + return (math.exp(x) - math.exp(-x)) / 2 end -function TrigFunctions.rad_to_deg(radians) - return radians * (180 / MathConstants.PI) +function TrigFunctions.cosh(x) + return (math.exp(x) + math.exp(-x)) / 2 end +function TrigFunctions.tanh(x) + return (math.exp(x) - math.exp(-x)) / (math.exp(x) + math.exp(-x)) +end + +function TrigFunctions.asinh(x) + return math.log(x + math.sqrt(x * x + 1)) +end + +function TrigFunctions.acosh(x) + return math.log(x + math.sqrt(x * x - 1)) +end + +function TrigFunctions.atanh(x) + return 0.5 * math.log((1 + x) / (1 - x)) +end + +function TrigFunctions.sin(x) + return math.sin(x) +end + +function TrigFunctions.cos(x) + return math.cos(x) +end + +function TrigFunctions.tan(x) + return math.tan(x) +end + +function TrigFunctions.atan2(y, x) + return math.atan2(y, x) +end + +function TrigFunctions.hypot(x, y) + return math.sqrt(x * x + y * y) +end + +-- Angle normalization and utilities +function TrigFunctions.normalize_angle_rad(angle) + angle = angle % MathConstants.TAU + if angle < 0 then + angle = angle + MathConstants.TAU + end + return angle +end + +function TrigFunctions.normalize_angle_pi(angle) + angle = angle % MathConstants.TAU + if angle <= -MathConstants.PI then + angle = angle + MathConstants.TAU + elseif angle > MathConstants.PI then + angle = angle - MathConstants.TAU + end + return angle +end + +-- Exponential and logarithmic functions +local ExpFunctions = {} + +function ExpFunctions.exp(x) + return math.exp(x) +end + +function ExpFunctions.log(x, base) + if base then + return math.log(x) / math.log(base) + end + return math.log(x) +end + +function ExpFunctions.pow(base, exponent) + return base ^ exponent +end + +function ExpFunctions.sqrt(x) + return math.sqrt(x) +end + +-- Random utility functions +local RandomFunctions = {} + +function RandomFunctions.random(min, max) + if min and max then + return math.random() * (max - min) + min + elseif min then + return math.random() * min + else + return math.random() + end +end + +function RandomFunctions.random_int(min, max) + return math.random(min, max) +end + +-- Statistical functions +local StatFunctions = {} + +-- Calculate the mean of a list of numbers +function StatFunctions.mean(numbers) + local sum = 0 + for _, num in ipairs(numbers) do + sum = sum + num + end + return sum / #numbers +end + +-- Calculate the median of a list of numbers +function StatFunctions.median(numbers) + table.sort(numbers) + local n = #numbers + if n % 2 == 1 then + return numbers[math.ceil(n / 2)] + else + return (numbers[n / 2] + numbers[(n / 2) + 1]) / 2 + end +end + +-- Calculate the mode of a list of numbers +function StatFunctions.mode(numbers) + local counts = {} + local max_count = 0 + local mode_val = numbers[1] + for _, num in ipairs(numbers) do + counts[num] = (counts[num] or 0) + 1 + if counts[num] > max_count then + max_count = counts[num] + mode_val = num + end + end + return mode_val +end + +-- Calculate the variance of a list of numbers +function StatFunctions.variance(numbers) + local mean_val = StatFunctions.mean(numbers) + local sum_sq_diff = 0 + for _, num in ipairs(numbers) do + sum_sq_diff = sum_sq_diff + (num - mean_val) ^ 2 + end + return sum_sq_diff / #numbers +end + +-- Calculate the standard deviation of a list of numbers +function StatFunctions.stddev(numbers) + return math.sqrt(StatFunctions.variance(numbers)) +end + +-- Advanced mathematical functions +local AdvancedMath = {} + +-- Calculate factorial of n +function AdvancedMath.factorial(n) + assert(n >= 0 and math.floor(n) == n, "Factorial is only defined for non-negative integers.") + if n == 0 or n == 1 then + return 1 + else + return n * AdvancedMath.factorial(n - 1) + end +end + +-- Calculate permutations P(n, k) +function AdvancedMath.permutation(n, k) + assert(n >= 0 and k >= 0 and math.floor(n) == n and math.floor(k) == k, "Permutation requires non-negative integers.") + if k > n then + return 0 + end + return AdvancedMath.factorial(n) / AdvancedMath.factorial(n - k) +end + +-- Calculate combinations C(n, k) +function AdvancedMath.combination(n, k) + assert(n >= 0 and k >= 0 and math.floor(n) == n and math.floor(k) == k, "Combination requires non-negative integers.") + if k > n then + return 0 + end + return AdvancedMath.factorial(n) / (AdvancedMath.factorial(k) * AdvancedMath.factorial(n - k)) +end + +-- Calculate the Greatest Common Divisor using Euclidean algorithm +function AdvancedMath.gcd(a, b) + a = math.abs(a) + b = math.abs(b) + while b ~= 0 do + a, b = b, a % b + end + return a +end + +-- Calculate the Least Common Multiple +function AdvancedMath.lcm(a, b) + a = math.abs(a) + b = math.abs(b) + if a == 0 or b == 0 then + return 0 + end + return (a * b) / AdvancedMath.gcd(a, b) +end + +-- Numerical approximation of the derivative of a function f at point x +function AdvancedMath.derivative(f, x, h) + h = h or 1e-5 + return (f(x + h) - f(x - h)) / (2 * h) +end + +-- Numerical approximation of the integral of a function f from a to b using the trapezoidal rule +function AdvancedMath.integral(f, a, b, n) + n = n or 1000 + local h = (b - a) / n + local sum = 0.5 * (f(a) + f(b)) + for i = 1, n - 1 do + sum = sum + f(a + i * h) + end + return sum * h +end + +-- Statistical and Advanced Math Functions can be grouped similarly + -- Export the math module local Math = { constants = MathConstants, functions = MathFunctions, - trig = TrigFunctions + trig = TrigFunctions, + exp = ExpFunctions, + random = RandomFunctions, + stats = StatFunctions, + advanced = AdvancedMath } return Math diff --git a/imgui.ini b/imgui.ini index d74c2f2..b9529ff 100644 --- a/imgui.ini +++ b/imgui.ini @@ -18,7 +18,7 @@ DockId=0x00000002,0 Pos=374,27 Size=1212,770 Collapsed=0 -DockId=0x0000000D,0 +DockId=0x0000001B,0 [Window][Performance] Pos=8,774 @@ -48,7 +48,7 @@ DockId=0x0000000B,0 Pos=374,27 Size=1202,849 Collapsed=0 -DockId=0x0000000D,0 +DockId=0x0000001B,0 [Window][Lua Text Editor] Pos=8,481 @@ -62,20 +62,101 @@ Size=1202,569 Collapsed=0 DockId=0x0000000E,0 -[Docking][Data] -DockSpace ID=0x14621557 Window=0x3DA2F1DE Pos=8,27 Size=1904,1142 Split=X Selected=0xF7365A5A - DockNode ID=0x00000009 Parent=0x14621557 SizeRef=364,1142 Split=Y Selected=0x3DC5AC3F - DockNode ID=0x00000005 Parent=0x00000009 SizeRef=364,745 Split=Y Selected=0x3DC5AC3F - DockNode ID=0x0000000B Parent=0x00000005 SizeRef=364,452 HiddenTabBar=1 Selected=0x3DC5AC3F - DockNode ID=0x0000000C Parent=0x00000005 SizeRef=364,291 Selected=0xAE3C694A - DockNode ID=0x00000006 Parent=0x00000009 SizeRef=364,395 HiddenTabBar=1 Selected=0x726D8899 - DockNode ID=0x0000000A Parent=0x14621557 SizeRef=1538,1142 Split=X - DockNode ID=0x00000007 Parent=0x0000000A SizeRef=357,1142 Selected=0x7737E8B2 - DockNode ID=0x00000008 Parent=0x0000000A SizeRef=1545,1142 Split=X - DockNode ID=0x00000001 Parent=0x00000008 SizeRef=1202,1142 Split=Y Selected=0xDF0EC458 - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1202,849 Split=Y Selected=0xDF0EC458 - DockNode ID=0x0000000D Parent=0x00000003 SizeRef=1202,571 CentralNode=1 Selected=0xDF0EC458 - DockNode ID=0x0000000E Parent=0x00000003 SizeRef=1202,569 Selected=0xE98146C5 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1202,291 Selected=0x9DD4E196 - DockNode ID=0x00000002 Parent=0x00000008 SizeRef=334,1142 HiddenTabBar=1 Selected=0x36DC96AB +[Window][DockSpace##Dockspace] +Size=1920,1177 +Collapsed=0 + +[Window][Inspector##InspectorWindow] +Pos=1567,27 +Size=345,1142 +Collapsed=0 +DockId=0x00000016,0 + +[Window][Editor##EditorWindow] +Pos=336,27 +Size=1229,527 +Collapsed=0 +DockId=0x0000001B,0 + +[Window][Performance##performance] +Pos=8,720 +Size=326,449 +Collapsed=0 +DockId=0x00000014,0 + +[Window][Logger##logger] +Pos=967,844 +Size=598,325 +Collapsed=0 +DockId=0x0000001E,0 + +[Window][Lua Text Editor##LuaEditor] +Pos=336,556 +Size=1229,286 +Collapsed=0 +DockId=0x0000001C,0 + +[Window][Scene Window@SceneWindow] +Pos=8,27 +Size=301,722 +Collapsed=0 +DockId=0x0000000F,0 + +[Window][Scene Window##SceneWindow] +Pos=8,27 +Size=326,691 +Collapsed=0 +DockId=0x00000013,0 + +[Window][Game Objects] +Pos=182,27 +Size=301,571 +Collapsed=0 +DockId=0x0000001A,0 + +[Window][Profiler] +Pos=336,844 +Size=629,325 +Collapsed=0 +DockId=0x0000001D,0 + +[Table][0xE9E836E4,4] +Column 0 Weight=1.0000 +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 +Column 3 Weight=1.0000 + +[Docking][Data] +DockSpace ID=0x14621557 Window=0x3DA2F1DE Pos=8,27 Size=1904,1142 Split=X Selected=0xF7365A5A + DockNode ID=0x00000015 Parent=0x14621557 SizeRef=1557,1142 Split=X + DockNode ID=0x00000011 Parent=0x00000015 SizeRef=326,1142 Split=X Selected=0x1D5D92B6 + DockNode ID=0x00000019 Parent=0x00000011 SizeRef=172,571 Split=Y Selected=0x1D5D92B6 + DockNode ID=0x00000013 Parent=0x00000019 SizeRef=326,691 Selected=0x1D5D92B6 + DockNode ID=0x00000014 Parent=0x00000019 SizeRef=326,449 Selected=0x818D04BB + DockNode ID=0x0000001A Parent=0x00000011 SizeRef=301,571 Selected=0xD71D2CC1 + DockNode ID=0x00000012 Parent=0x00000015 SizeRef=1229,1142 Split=X + DockNode ID=0x00000009 Parent=0x00000012 SizeRef=364,1142 Split=Y Selected=0x3DC5AC3F + DockNode ID=0x00000005 Parent=0x00000009 SizeRef=364,745 Split=Y Selected=0x3DC5AC3F + DockNode ID=0x0000000B Parent=0x00000005 SizeRef=364,452 HiddenTabBar=1 Selected=0x3DC5AC3F + DockNode ID=0x0000000C Parent=0x00000005 SizeRef=364,291 Selected=0xAE3C694A + DockNode ID=0x00000006 Parent=0x00000009 SizeRef=364,395 HiddenTabBar=1 Selected=0x726D8899 + DockNode ID=0x0000000A Parent=0x00000012 SizeRef=1538,1142 Split=X + DockNode ID=0x00000007 Parent=0x0000000A SizeRef=357,1142 Selected=0x7737E8B2 + DockNode ID=0x00000008 Parent=0x0000000A SizeRef=1545,1142 Split=X + DockNode ID=0x00000001 Parent=0x00000008 SizeRef=1202,1142 Split=Y Selected=0xDF0EC458 + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=1202,849 Split=Y Selected=0xDF0EC458 + DockNode ID=0x0000000D Parent=0x00000003 SizeRef=1202,571 Split=Y Selected=0xDFF75B3F + DockNode ID=0x00000017 Parent=0x0000000D SizeRef=1303,815 Split=Y Selected=0xDFF75B3F + DockNode ID=0x0000001B Parent=0x00000017 SizeRef=1258,527 CentralNode=1 Selected=0xDFF75B3F + DockNode ID=0x0000001C Parent=0x00000017 SizeRef=1258,286 Selected=0x7D9E6BA2 + DockNode ID=0x00000018 Parent=0x0000000D SizeRef=1303,325 Split=X Selected=0x9B5D3198 + DockNode ID=0x0000001D Parent=0x00000018 SizeRef=629,325 Selected=0x9B5D3198 + DockNode ID=0x0000001E Parent=0x00000018 SizeRef=598,325 Selected=0x1C0788A1 + DockNode ID=0x0000000E Parent=0x00000003 SizeRef=1202,569 Selected=0xE98146C5 + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=1202,291 Selected=0x9DD4E196 + DockNode ID=0x00000002 Parent=0x00000008 SizeRef=334,1142 HiddenTabBar=1 Selected=0x36DC96AB + DockNode ID=0x00000016 Parent=0x14621557 SizeRef=345,1142 Selected=0x8D0E8380 +DockSpace ID=0xC6145A92 Pos=8,27 Size=1904,1142 Split=X + DockNode ID=0x0000000F Parent=0xC6145A92 SizeRef=301,1142 Selected=0xA8433A03 + DockNode ID=0x00000010 Parent=0xC6145A92 SizeRef=1601,1142 CentralNode=1 diff --git a/scenes/Default.scene b/scenes/Default.scene index aa60e49..15984c8 100644 --- a/scenes/Default.scene +++ b/scenes/Default.scene @@ -2,111 +2,113 @@ Entities: - ID: 0 Name: Player Components: - Transform: - Position: [0, 2.79999995, -12.6000004] - Rotation: [128.988251, 128.988251, 128.988251] - Scale: [1, 1, 1] Mesh: vao: 2 indexCount: 15810 textureID: 1 MeshPath: assets/models/LowPolyFiatUNO.obj + Transform: + Position: [0, 2.79999995, -12.6000004] + Rotation: [466.191284, 466.191284, 466.191284] + Scale: [1, 1, 1] - ID: 2 Name: Gun Components: - Transform: - Position: [-0.899999976, 0.600000024, -0.300000012] - Rotation: [203.563934, 203.563934, 203.563934] - Scale: [0.00999999978, 0.00999999978, 0.00999999978] + ScriptComponent: + ScriptPath: assets/scripts/BouncingItem.lua Mesh: vao: 5 indexCount: 116445 textureID: 6 MeshPath: assets/models/Ak-47.obj + Transform: + Position: [-0.899999976, 0.727798522, -0.300000012] + Rotation: [-180, 321.793457, 0] + Scale: [0.00499999989, 0.00499999989, 0.00499999989] - ID: 3 Name: Grass Box Top Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 4 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [-1.20000005, -3.4000001, -17.7000008] Rotation: [-23.5, 15.8999996, -59.9000015] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 4 - MeshPath: assets/models/DefaultMesh.obj - ID: 4 Name: Bark Box Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 5 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [8.10000038, 0.800000012, -12] Rotation: [-17.2999992, -16.1000004, -19.2999992] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 5 - MeshPath: assets/models/DefaultMesh.obj - ID: 5 Name: Skybox Components: - Mesh: - vao: 6 - indexCount: 36 - textureID: 7 - MeshPath: assets/models/DefaultMesh.obj Transform: Position: [0, 0, 43.2000008] Rotation: [0, 0, 0] Scale: [100, 100, 100] + Mesh: + vao: 1 + indexCount: 36 + textureID: 7 + MeshPath: assets/models/DefaultMesh.obj - ID: 6 Name: Null Texture Box Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 3 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [-6.5, -6, -18] Rotation: [15.8000002, -18.2000008, -11.1000004] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 3 - MeshPath: assets/models/DefaultMesh.obj - ID: 7 Name: Grass Box Bottom Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 4 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [6.5999999, 1.79999995, -23.8999996] Rotation: [-16.1000004, -15.8999996, -35] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 4 - MeshPath: assets/models/DefaultMesh.obj - ID: 8 Name: Wood Box Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 1 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [-7.80000019, 0.200000003, -29.7999992] Rotation: [22.2999992, -32.7999992, 0] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 1 - MeshPath: assets/models/DefaultMesh.obj - ID: 9 Name: Bricks Components: + Mesh: + vao: 1 + indexCount: 36 + textureID: 2 + MeshPath: assets/models/DefaultMesh.obj Transform: Position: [5.5, -2.9000001, -19.5] Rotation: [-41.4000015, -22.6000004, -52.2999992] Scale: [1, 1, 1] - Mesh: - vao: 6 - indexCount: 36 - textureID: 2 - MeshPath: assets/models/DefaultMesh.obj - ID: 10 Name: Script Handler Components: diff --git a/src/Componenets/CameraComponent.cpp b/src/Componenets/CameraComponent.cpp new file mode 100644 index 0000000..e5c2a4c --- /dev/null +++ b/src/Componenets/CameraComponent.cpp @@ -0,0 +1,172 @@ +// CameraComponent.cpp + +#include "CameraComponent.h" +#include "GameObject.h" // Ensure this is included to access GameObject +#include "Transform.h" // Ensure Transform component is available +#include + +// Constructor implementation +CameraComponent::CameraComponent() : + m_IsPerspective(true), + m_FOV(45.0f), m_AspectRatio(16.0f / 9.0f), + m_NearPlane(0.1f), m_FarPlane(100.0f), + m_Left(-1.0f), m_Right(1.0f), m_Bottom(-1.0f), m_Top(1.0f), + m_ViewMatrix(1.0f), m_ProjectionMatrix(1.0f) +{ + UpdateProjectionMatrix(); +} + +CameraComponent::~CameraComponent() +{ + // Cleanup if necessary +} + +const std::string &CameraComponent::GetName() const +{ + static const std::string name = "CameraComponent"; + return name; +} + +const std::string &CameraComponent::GetStaticName() +{ + static const std::string name = "CameraComponent"; + return name; +} + + + +YAML::Node CameraComponent::Serialize() +{ + YAML::Node node; + node["IsPerspective"] = m_IsPerspective; + if (m_IsPerspective) + { + node["FOV"] = m_FOV; + node["AspectRatio"] = m_AspectRatio; + node["NearPlane"] = m_NearPlane; + node["FarPlane"] = m_FarPlane; + } + else + { + node["Left"] = m_Left; + node["Right"] = m_Right; + node["Bottom"] = m_Bottom; + node["Top"] = m_Top; + node["NearPlane"] = m_NearPlane; + node["FarPlane"] = m_FarPlane; + } + return node; +} + +void CameraComponent::Deserialize(const YAML::Node &node) +{ + if (node["IsPerspective"]) + { + m_IsPerspective = node["IsPerspective"].as(); + } + + if (m_IsPerspective) + { + if (node["FOV"]) + m_FOV = node["FOV"].as(); + if (node["AspectRatio"]) + m_AspectRatio = node["AspectRatio"].as(); + if (node["NearPlane"]) + m_NearPlane = node["NearPlane"].as(); + if (node["FarPlane"]) + m_FarPlane = node["FarPlane"].as(); + } + else + { + if (node["Left"]) + m_Left = node["Left"].as(); + if (node["Right"]) + m_Right = node["Right"].as(); + if (node["Bottom"]) + m_Bottom = node["Bottom"].as(); + if (node["Top"]) + m_Top = node["Top"].as(); + if (node["NearPlane"]) + m_NearPlane = node["NearPlane"].as(); + if (node["FarPlane"]) + m_FarPlane = node["FarPlane"].as(); + } + + UpdateProjectionMatrix(); + UpdateViewMatrix(); +} + +void CameraComponent::SetPerspective(float fov, float aspectRatio, float nearPlane, float farPlane) +{ + m_IsPerspective = true; + m_FOV = fov; + m_AspectRatio = aspectRatio; + m_NearPlane = nearPlane; + m_FarPlane = farPlane; + UpdateProjectionMatrix(); +} + +void CameraComponent::SetOrthographic(float left, float right, float bottom, float top, float nearPlane, float farPlane) +{ + m_IsPerspective = false; + m_Left = left; + m_Right = right; + m_Bottom = bottom; + m_Top = top; + m_NearPlane = nearPlane; + m_FarPlane = farPlane; + UpdateProjectionMatrix(); +} + +const glm::mat4 &CameraComponent::GetViewMatrix() const +{ + return m_ViewMatrix; +} + +const glm::mat4 &CameraComponent::GetProjectionMatrix() const +{ + return m_ProjectionMatrix; +} + +void CameraComponent::UpdateViewMatrix() +{ + // Retrieve the Transform component from the owning GameObject + std::shared_ptr transform = owner->GetComponent(); + + if (transform) + { + glm::vec3 position = transform->GetPosition(); + glm::vec3 rotation = transform->GetRotation(); + + // Convert Euler angles to radians + glm::vec3 rotRad = glm::radians(rotation); + + // Calculate forward vector + glm::vec3 forward; + forward.x = cos(rotRad.y) * cos(rotRad.x); + forward.y = sin(rotRad.x); + forward.z = sin(rotRad.y) * cos(rotRad.x); + forward = glm::normalize(forward); + + // Define up vector (assuming Y-up) + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); + + m_ViewMatrix = glm::lookAt(position, position + forward, up); + } + else + { + std::cerr << "Transform component missing on GameObject: " << m_Owner->name << std::endl; + } +} + +void CameraComponent::UpdateProjectionMatrix() +{ + if (m_IsPerspective) + { + m_ProjectionMatrix = glm::perspective(glm::radians(m_FOV), m_AspectRatio, m_NearPlane, m_FarPlane); + } + else + { + m_ProjectionMatrix = glm::ortho(m_Left, m_Right, m_Bottom, m_Top, m_NearPlane, m_FarPlane); + } +} diff --git a/src/Componenets/CameraComponent.h b/src/Componenets/CameraComponent.h new file mode 100644 index 0000000..6f83065 --- /dev/null +++ b/src/Componenets/CameraComponent.h @@ -0,0 +1,56 @@ +// CameraComponent.h + +#pragma once + +#include "Component.h" +#include "GameObject.h" +#include +#include +#include + +class CameraComponent : public Component +{ +public: + // Updated constructor to accept GameObject* + CameraComponent(); + virtual ~CameraComponent(); + + // Overridden methods from Component + virtual const std::string& GetName() const override; + + static const std::string& GetStaticName(); + + virtual YAML::Node Serialize() override; + virtual void Deserialize(const YAML::Node& node) override; + + // Camera-specific methods + void SetPerspective(float fov, float aspectRatio, float nearPlane, float farPlane); + void SetOrthographic(float left, float right, float bottom, float top, float nearPlane, float farPlane); + const glm::mat4& GetViewMatrix() const; + const glm::mat4& GetProjectionMatrix() const; + +private: + // Projection parameters + bool m_IsPerspective; + float m_FOV; + float m_AspectRatio; + float m_NearPlane; + float m_FarPlane; + + float m_Left; + float m_Right; + float m_Bottom; + float m_Top; + + // Matrices + glm::mat4 m_ViewMatrix; + glm::mat4 m_ProjectionMatrix; + + GameObject* owner; + + + + // Update matrices + void UpdateViewMatrix(); + void UpdateProjectionMatrix(); +}; diff --git a/src/Componenets/Component.h b/src/Componenets/Component.h index 4c513bc..ce58fb1 100644 --- a/src/Componenets/Component.h +++ b/src/Componenets/Component.h @@ -1,16 +1,38 @@ #pragma once +// Component.h + #include #include +// Forward declaration to avoid circular dependency +class GameObject; + class Component { public: + // Constructor accepting a pointer to the owning GameObject + Component() {} + + // Virtual destructor virtual ~Component() {} + + // Pure virtual methods virtual const std::string& GetName() const = 0; + void SetOwner(GameObject* owner) { + + m_Owner = owner; + + } // Serialization methods virtual YAML::Node Serialize() = 0; virtual void Deserialize(const YAML::Node& node) = 0; -}; \ No newline at end of file + + // Getter for the owning GameObject + GameObject* GetOwner() const { return m_Owner; } + +protected: + GameObject* m_Owner; // Pointer to the owning GameObject +}; diff --git a/src/Componenets/GameObject.cpp b/src/Componenets/GameObject.cpp index 358e1f3..4a10892 100644 --- a/src/Componenets/GameObject.cpp +++ b/src/Componenets/GameObject.cpp @@ -27,6 +27,7 @@ std::string GameObject::GetName() const void GameObject::AddComponent(const std::shared_ptr &component) { + component->SetOwner(this); components[component->GetName()] = component; // std::cout << "Added " << component->GetName() << std::endl; } diff --git a/src/Componenets/ScriptComponent.cpp b/src/Componenets/ScriptComponent.cpp index cad5284..2ed2e3a 100644 --- a/src/Componenets/ScriptComponent.cpp +++ b/src/Componenets/ScriptComponent.cpp @@ -14,8 +14,9 @@ const std::string ScriptComponent::name = "ScriptComponent"; ScriptComponent::ScriptComponent() - : ScriptPath(""), m_LastErrorMessage("") + : ScriptPath("assets/scripts/script.lua"), m_LastErrorMessage("") { + Initialize(); } diff --git a/src/Engine.cpp b/src/Engine.cpp index dec8053..ec86b5d 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -22,6 +22,9 @@ #include "Windows/InspectorWindow.h" #include "Windows/SceneWindow.h" +#include "Windows/ProfilerWindow.h" + + // Create an instance @@ -30,6 +33,10 @@ #include "Engine/ThemeManager.h" #include "Engine/SceneManager.h" #include "Engine/LuaAPI.h" +#include "Engine/Utilitys.h" + +#include "Engine/ScopedTimer.h" +#include "Engine/Profiler.h" // #define YAML_CPP_STATIC_DEFINE #include @@ -42,16 +49,13 @@ LoggerWindow *g_LoggerWindow; SceneManager g_SceneManager; - std::vector> g_GameObjects; int g_GPU_Triangles_drawn_to_screen = 0; GameObject *g_SelectedObject; // Pointer to the currently selected object - - - +int m_GameRunning = 0; bool MyEngine::Init(int width, int height, const std::string &title) { @@ -116,7 +120,10 @@ bool MyEngine::Init(int width, int height, const std::string &title) m_InspectorWindow = std::make_unique(); m_SceneWindow = std::make_unique(); m_luaEditor = std::make_unique(); + m_profilerWindow = std::make_unique(); + m_GameRunning = false; + m_FirstTickGameRunning = true; g_LoggerWindow = m_LoggerWindow.get(); @@ -160,7 +167,7 @@ void MyEngine::Run() if (mesh) { // printf("Got Valid Mesh Component\n"); - Model * model = g_AssetManager.loadAsset(AssetType::MODEL, "assets/models/DefaultCube.obj"); + Model *model = g_AssetManager.loadAsset(AssetType::MODEL, "assets/models/DefaultMesh.obj"); mesh->vao = model->vao; mesh->indexCount = model->indices.size(); mesh->textureID = g_AssetManager.loadAsset(AssetType::TEXTURE, "assets/textures/wood.png"); @@ -190,7 +197,6 @@ void MyEngine::Run() g_AssetManager.loadAsset(AssetType::TEXTURE, "assets/textures/vegetation_tree_bark_40.png"); g_AssetManager.loadAsset(AssetType::TEXTURE, "assets/textures/ak-47.jpg"); - g_AssetManager.loadAsset(AssetType::TEXTURE, "assets/textures/sky.png"); // Load a model @@ -223,8 +229,13 @@ void MyEngine::Run() while (!glfwWindowShouldClose(m_Window) && m_Running) { + ScopedTimer frameTimer("MainLoop"); // Optional: Profile the entire loop + // Poll events - glfwPollEvents(); + { + ScopedTimer timer("glfwPollEvents"); + glfwPollEvents(); + } // Calculate current time double current_time = glfwGetTime(); @@ -251,43 +262,75 @@ void MyEngine::Run() // Start new frame BeginFrame(); + if (m_FirstTickGameRunning && m_GameRunning) + { + ScopedTimer timer("SaveScene"); + m_FirstTickGameRunning = false; + + std::string savePath = createTempFolder().string() + "TesseractEngineTempScene.scene"; + DEBUG_PRINT("Save path: %s", savePath.c_str()); + g_SceneManager.SaveScene(g_GameObjects, savePath); + } + + if (!m_FirstTickGameRunning && !m_GameRunning) + { + ScopedTimer timer("LoadScene"); + m_FirstTickGameRunning = true; + + std::string loadPath = createTempFolder().string() + "TesseractEngineTempScene.scene"; + + DEBUG_PRINT("Load path: %s", loadPath.c_str()); + + g_SceneManager.LoadScene(g_GameObjects, loadPath); + } + // Show main DockSpace ShowDockSpace(); - m_InspectorWindow->Show(); - - if (1) + if (m_GameRunning) { + ScopedTimer timer("UpdateGameObjects"); for (auto &Gameobject : g_GameObjects) { - // Handle Componenets That require Updates - + // Handle Components That Require Updates std::shared_ptr script = Gameobject->GetComponent(); if (script) - { // Stupid Null Checks + { // Null Checks + ScopedTimer timer("GameObjectLuaCall: "+Gameobject->name); + script->Update(frame_delta); } } } - // Pass per-frame delta time to Lua + // Render and show various windows + { + ScopedTimer timer("RenderGame"); + + m_RenderWindow->Show(&m_GameRunning); // The spinning triangle as ImGui::Image - m_RenderWindow->Show(); // The spinning triangle as ImGui::Image + } + { + ScopedTimer timer("ShowEditor"); - m_PerformanceWindow->Show(m_Fps, m_Ms); // FPS & ms + m_InspectorWindow->Show(); + m_PerformanceWindow->Show(m_Fps, m_Ms); // FPS & ms + m_LoggerWindow->Show(); // Logs + m_SceneWindow->Show(); + m_luaEditor->Show(); - m_LoggerWindow->Show(); // Logs - - m_SceneWindow->Show(); - - m_luaEditor->Show(); + m_profilerWindow->Show(); + } // After rendering m_PerformanceWindow->UpdatePerformanceStats(-1, g_GPU_Triangles_drawn_to_screen); // End frame EndFrame(); + + // Mark the end of frame for profiling + Profiler::Get().EndFrame(); } DEBUG_PRINT("[OK] Engine Run "); @@ -398,6 +441,17 @@ void MyEngine::ShowDockSpace() } ImGui::EndMenu(); } + + if (ImGui::BeginMenu("Engine")) + { + + if (ImGui::MenuItem(m_GameRunning ? "Stop" : "Start")) + { + m_GameRunning = !m_GameRunning; // Stop the engine + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); } diff --git a/src/Engine.h b/src/Engine.h index 454e382..b3ef73c 100644 --- a/src/Engine.h +++ b/src/Engine.h @@ -1,6 +1,7 @@ // src/Engine.h #pragma once + #include #include #include "Windows/RenderWindow.h" @@ -9,6 +10,7 @@ #include "Windows/InspectorWindow.h" #include "Windows/SceneWindow.h" #include "Windows/LuaEditorWindow.h" +#include "Windows/ProfilerWindow.h" #include "Componenets/GameObject.h" #include "Componenets/Mesh.h" @@ -18,6 +20,7 @@ #include "Engine/ThemeManager.h" #include "Engine/SceneManager.h" #include "Engine/LuaAPI.h" +#include "Engine/Utilitys.h" #include "TestModel.h" @@ -43,6 +46,9 @@ private: private: GLFWwindow *m_Window = nullptr; bool m_Running = false; + bool m_GameRunning = false; + + bool m_FirstTickGameRunning = true; // Windows std::unique_ptr m_RenderWindow; @@ -52,6 +58,8 @@ private: std::unique_ptr m_SceneWindow; std::unique_ptr m_luaEditor; + std::unique_ptr m_profilerWindow; + double m_LastFrameTime = 0.0; // Initialize with the current time double m_TimeAccumulator = 0.0; diff --git a/src/Engine/AssetManager.h b/src/Engine/AssetManager.h index 44edaad..80d518d 100644 --- a/src/Engine/AssetManager.h +++ b/src/Engine/AssetManager.h @@ -56,13 +56,6 @@ public: using AssetVariant = std::variant; - // Load an asset from disk (texture, shader, etc.) - // Returns a void* pointer to the loaded resource. - // - For TEXTURE, cast to (GLuint) - // - For SHADER, cast to (Shader*) - // - For SOUND, cast to whatever you store - - // Template function to load an asset // Template function to load an asset template T loadAsset(AssetType type, const std::string &path) diff --git a/src/Engine/LuaAPI.cpp b/src/Engine/LuaAPI.cpp index 7ad357f..46882d9 100644 --- a/src/Engine/LuaAPI.cpp +++ b/src/Engine/LuaAPI.cpp @@ -142,6 +142,7 @@ bool LuaManager::Initialize(const std::string &scriptPath) // Update function called every frame void LuaManager::Update(float deltaTime) { + if (!m_LuaState) { if (g_LoggerWindow) diff --git a/src/Engine/Profiler.h b/src/Engine/Profiler.h new file mode 100644 index 0000000..ec43394 --- /dev/null +++ b/src/Engine/Profiler.h @@ -0,0 +1,47 @@ +// Profiler.h +#pragma once + +#include +#include +#include + +struct ProfileResult { + double TotalTime; // Total time in microseconds + int CallCount; +}; + +class Profiler { +public: + static Profiler& Get() { + static Profiler instance; + return instance; + } + + void AddProfileResult(const std::string& name, double time) { + std::lock_guard lock(m_Mutex); + auto& result = m_ProfileData[name]; + result.TotalTime += time; + result.CallCount += 1; + } + + // Call this at the end of each frame to prepare data for display + void EndFrame() { + std::lock_guard lock(m_Mutex); + m_LastFrameData = m_ProfileData; + m_ProfileData.clear(); + } + + const std::unordered_map& GetLastFrameData() const { + return m_LastFrameData; + } + +private: + Profiler() {} + ~Profiler() {} + Profiler(const Profiler&) = delete; + Profiler& operator=(const Profiler&) = delete; + + std::unordered_map m_ProfileData; + std::unordered_map m_LastFrameData; + mutable std::mutex m_Mutex; +}; diff --git a/src/Engine/ScopedTimer.cpp b/src/Engine/ScopedTimer.cpp new file mode 100644 index 0000000..c5fd6e3 --- /dev/null +++ b/src/Engine/ScopedTimer.cpp @@ -0,0 +1,12 @@ +// ScopedTimer.cpp +#include "ScopedTimer.h" +#include "Profiler.h" + +ScopedTimer::ScopedTimer(const std::string& name) + : m_Name(name), m_StartTime(std::chrono::high_resolution_clock::now()) {} + +ScopedTimer::~ScopedTimer() { + auto endTime = std::chrono::high_resolution_clock::now(); + double duration = std::chrono::duration(endTime - m_StartTime).count(); // Duration in microseconds + Profiler::Get().AddProfileResult(m_Name, duration); +} diff --git a/src/Engine/ScopedTimer.h b/src/Engine/ScopedTimer.h new file mode 100644 index 0000000..46f44c2 --- /dev/null +++ b/src/Engine/ScopedTimer.h @@ -0,0 +1,17 @@ +// ScopedTimer.h +#pragma once + +#include +#include + +class Profiler; // Forward declaration + +class ScopedTimer { +public: + ScopedTimer(const std::string& name); + ~ScopedTimer(); + +private: + std::string m_Name; + std::chrono::high_resolution_clock::time_point m_StartTime; +}; diff --git a/src/Engine/Utilitys.cpp b/src/Engine/Utilitys.cpp new file mode 100644 index 0000000..f87e907 --- /dev/null +++ b/src/Engine/Utilitys.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +/** + * @brief Creates a unique temporary folder in the user's temp directory. + * + * This function generates a unique folder name using a combination of a prefix + * and random characters. If the folder already exists, it returns the existing path. + * + * @return fs::path The path to the created or existing temporary folder. + * @throws fs::filesystem_error if directory creation fails. + */ +std::filesystem::path createTempFolder() { + const std::string folder_name = "temp_tesseract_fixed"; + + // Get the system's temporary directory + fs::path temp_dir = fs::temp_directory_path(); + + // Define the fixed folder path + fs::path fixed_folder = temp_dir / folder_name; + + // Check if the folder exists + if (fs::exists(fixed_folder)) { + if (fs::is_directory(fixed_folder)) { + // Folder already exists; return its path + return fixed_folder; + } else { + // A file with the same name exists; handle the conflict + throw fs::filesystem_error("A file with the fixed folder name exists.", + fixed_folder, + std::error_code()); + } + } + + // Attempt to create the directory + try { + if (fs::create_directory(fixed_folder)) { + // Successfully created the folder + return fixed_folder; + } else { + // Failed to create the folder for an unknown reason + throw fs::filesystem_error("Failed to create the fixed temporary folder.", + fixed_folder, + std::error_code()); + } + } catch (const fs::filesystem_error& e) { + // Handle filesystem errors (e.g., permission issues) + std::cerr << "Error creating directory: " << e.what() << '\n'; + throw; // Re-throw the exception after logging + } +} + + diff --git a/src/Engine/Utilitys.h b/src/Engine/Utilitys.h new file mode 100644 index 0000000..1b1b421 --- /dev/null +++ b/src/Engine/Utilitys.h @@ -0,0 +1,7 @@ +#include +#include + + + +std::filesystem::path createTempFolder(); + diff --git a/src/Windows/InspectorWindow.cpp b/src/Windows/InspectorWindow.cpp index dc941d2..d3fdbdd 100644 --- a/src/Windows/InspectorWindow.cpp +++ b/src/Windows/InspectorWindow.cpp @@ -18,7 +18,7 @@ void InspectorWindow::Show() ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 4)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10, 10)); - if (ImGui::Begin("Inspector")) + if (ImGui::Begin("Inspector##InspectorWindow")) { // Title label (white text) if (g_SelectedObject) @@ -34,8 +34,6 @@ void InspectorWindow::Show() ImGui::Text("Editing Object: %s", g_SelectedObject->name.c_str()); ImGui::Text("Components: %d", g_SelectedObject->GetComponentCount()); - ImGui::Spacing(); - ImGui::Separator(); ImGui::Spacing(); // Begin two-column layout for labels and inputs @@ -69,9 +67,93 @@ void InspectorWindow::Show() // End columns ImGui::Columns(1); - ImGui::Spacing(); ImGui::Separator(); - ImGui::Spacing(); + + // =========================== + // 2) ADD COMPONENT SECTION + // =========================== + + + ImGui::Text("Add Component:"); + ImGui::SameLine(); + + // Define available components to add + static int selectedComponent = 0; + const char *componentOptions[] = {"Transform", "Mesh", "Script", "Camera"}; + const int componentCount = sizeof(componentOptions) / sizeof(componentOptions[0]); + + // Create a Combo Box for component selection + + ImGui::Combo("##ComponentCombo", &selectedComponent, componentOptions, componentCount); + + // Add Button to add the selected component + if (ImGui::Button("Add")) + { + if (selectedComponent == 0) // TransformComponent + { + // Check if TransformComponent already exists to prevent duplicates + std::shared_ptr existingTransform = g_SelectedObject->GetComponent(); + if (!existingTransform) + { + g_SelectedObject->AddComponent(std::make_shared()); + g_LoggerWindow->AddLog("TransformComponent added to %s.", g_SelectedObject->name.c_str()); + } + else + { + g_LoggerWindow->AddLog("TransformComponent already exists on %s.", g_SelectedObject->name.c_str()); + } + } + else if (selectedComponent == 1) // MeshComponent + { + // Check if MeshComponent already exists to prevent duplicates + std::shared_ptr existingMesh = g_SelectedObject->GetComponent(); + if (!existingMesh) + { + g_SelectedObject->AddComponent(std::make_shared()); + g_LoggerWindow->AddLog("MeshComponent added to %s.", g_SelectedObject->name.c_str()); + } + else + { + g_LoggerWindow->AddLog("MeshComponent already exists on %s.", g_SelectedObject->name.c_str()); + } + } + else if (selectedComponent == 2) // ScriptComponent + { + // Check if ScriptComponent already exists to prevent duplicates + std::shared_ptr existingScript = g_SelectedObject->GetComponent(); + if (!existingScript) + { + g_SelectedObject->AddComponent(std::make_shared()); + g_LoggerWindow->AddLog("ScriptComponent added to %s.", g_SelectedObject->name.c_str()); + } + else + { + g_LoggerWindow->AddLog("ScriptComponent already exists on %s.", g_SelectedObject->name.c_str()); + } + } + else if (selectedComponent == 3) // CameraComponent + { + // Check if CameraComponent already exists to prevent duplicates + std::shared_ptr existingCamera = g_SelectedObject->GetComponent(); + if (!existingCamera) + { + g_SelectedObject->AddComponent(std::make_shared()); + g_LoggerWindow->AddLog("CameraComponent added to %s.", g_SelectedObject->name.c_str()); + } + else + { + g_LoggerWindow->AddLog("CameraComponent already exists on %s.", g_SelectedObject->name.c_str()); + } + } + else + { + // Handle unknown selections if necessary + g_LoggerWindow->AddLog("Unknown component selection."); + } + } + + ImGui::Separator(); + // =========================== // 1) TRANSFORM @@ -344,7 +426,6 @@ void InspectorWindow::Show() if (script && g_SelectedObject) { - // Transform* transform = &g_SelectedObject->transform; ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); @@ -356,19 +437,25 @@ void InspectorWindow::Show() { // Define a maximum buffer size const size_t BUFFER_SIZE = 256; + // Allocate a buffer and initialize it with the current string char buffer[BUFFER_SIZE]; strncpy(buffer, script->ScriptPath.c_str(), BUFFER_SIZE - 1); + buffer[BUFFER_SIZE - 1] = '\0'; // Ensure null-termination + + // Render the InputText widget - if (ImGui::InputText(script->ScriptPath.c_str(), buffer, BUFFER_SIZE)) + if (ImGui::InputText("Script Path", buffer, BUFFER_SIZE)) { + // Update the string if user made changes script->ScriptPath = buffer; } if (ImGui::Button("Reload Script")) { + if (script->Initialize()) { g_LoggerWindow->AddLog("Reloaded Script: %s", ImVec4(0.0f, 1.0f, 0.0f, 1.0f), script->ScriptPath.c_str()); @@ -376,42 +463,6 @@ void InspectorWindow::Show() } } } - - // =========================== - // 2) SCRIPT - // =========================== - // We keep script text in white - // if (ImGui::CollapsingHeader("Script##Main", ImGuiTreeNodeFlags_DefaultOpen)) - //{ - // if (ImGui::IsItemHovered()) - // { - // ImGui::BeginTooltip(); - // ImGui::TextUnformatted("Attach a script or logic component here."); - // ImGui::EndTooltip(); - // } - - // ImGui::TextUnformatted("Script Name:"); - // ImGui::SameLine(); - - // { - // char buffer[128]; - // std::snprintf(buffer, sizeof(buffer), "%s", script.scriptName.c_str()); - // ImGui::SetNextItemWidth(-1); - // if (ImGui::InputText("##ScriptName", buffer, sizeof(buffer))) - // { - // script.scriptName = buffer; - // } - // } - - // ImGui::Spacing(); - - // ImGui::TextUnformatted("Script Enabled:"); - // ImGui::SameLine(); - // ImGui::Checkbox("##ScriptEnabled", &script.enabled); - - // ImGui::Spacing(); - // ImGui::Separator(); - //} } ImGui::End(); diff --git a/src/Windows/InspectorWindow.h b/src/Windows/InspectorWindow.h index fc5b1e9..c8e6e79 100644 --- a/src/Windows/InspectorWindow.h +++ b/src/Windows/InspectorWindow.h @@ -3,18 +3,21 @@ #include #include // or if you prefer #include "imgui.h" +#include "gcml.h" #include "Componenets/GameObject.h" #include "Componenets/Mesh.h" #include "Componenets/Transform.h" #include "Componenets/ScriptComponent.h" +#include "Componenets/CameraComponent.h" + // Example struct for a Script component struct Script { - std::string scriptName = "MyBehavior.lua"; + std::string scriptName = "Default.lua"; bool enabled = true; }; diff --git a/src/Windows/LoggerWindow.cpp b/src/Windows/LoggerWindow.cpp index f92a3c2..fa7e9db 100644 --- a/src/Windows/LoggerWindow.cpp +++ b/src/Windows/LoggerWindow.cpp @@ -33,7 +33,7 @@ void LoggerWindow::AddLog(const char* fmt, std::optional color, ...) { } void LoggerWindow::Show() { - ImGui::Begin("Logger"); + ImGui::Begin("Logger##logger"); if (ImGui::Button("Clear")) { m_Logs.clear(); diff --git a/src/Windows/LuaEditorWindow.cpp b/src/Windows/LuaEditorWindow.cpp index e4eca9d..24a9327 100644 --- a/src/Windows/LuaEditorWindow.cpp +++ b/src/Windows/LuaEditorWindow.cpp @@ -36,7 +36,7 @@ void LuaEditorWindow::Show() { return; } - ImGui::Begin("Lua Text Editor"); + ImGui::Begin("Lua Text Editor##LuaEditor"); // Toolbar buttons if (ImGui::Button("Save")) { diff --git a/src/Windows/PerformanceWindow.cpp b/src/Windows/PerformanceWindow.cpp index c3c4990..07d825a 100644 --- a/src/Windows/PerformanceWindow.cpp +++ b/src/Windows/PerformanceWindow.cpp @@ -103,7 +103,7 @@ void PerformanceWindow::Show(float fps, float ms) // Optional style adjustments ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); - ImGui::Begin("Performance"); + ImGui::Begin("Performance##performance"); // Colored header ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "Performance Stats"); diff --git a/src/Windows/ProfilerWindow.cpp b/src/Windows/ProfilerWindow.cpp new file mode 100644 index 0000000..e01d651 --- /dev/null +++ b/src/Windows/ProfilerWindow.cpp @@ -0,0 +1,252 @@ +#include "ProfilerWindow.h" +#include +#include +#include +#include // For debug statements + +// Constructor +ProfilerWindow::ProfilerWindow() + : m_UpdateInterval(0.1) // Set update interval to 0.1 seconds +{ + // Initialize m_LastUpdateTime to force an immediate update on the first frame + m_LastUpdateTime = std::chrono::steady_clock::now() - std::chrono::duration_cast(std::chrono::duration(m_UpdateInterval)); + +} + +// Calculate moving average +std::vector ProfilerWindow::MovingAverage(const std::deque& data, size_t window) +{ + std::vector averages; + if (data.size() < window) + window = data.size(); + + for (size_t i = 0; i <= data.size() - window; ++i) + { + double sum = 0.0; + for (size_t j = i; j < i + window; ++j) + sum += data[j]; + averages.push_back(static_cast(sum / window)); + } + return averages; +} + +// Update the history data structures with the latest profiling data +void ProfilerWindow::UpdateHistory(const std::unordered_map& data, double totalFrameTime) +{ + // Update total frame time history + m_TotalFrameTimeHistory.push_back(totalFrameTime); + if (m_TotalFrameTimeHistory.size() > MaxFrameHistory) + m_TotalFrameTimeHistory.pop_front(); + + // Debug: Print the size of m_TotalFrameTimeHistory + + // Update each function's profiling history + for (const auto& [name, result] : data) + { + auto& history = m_ProfileHistories[name]; + + // Update total time history + history.totalTimeHistory.push_back(result.TotalTime); + if (history.totalTimeHistory.size() > ProfileHistory::MaxHistory) + history.totalTimeHistory.pop_front(); + + // Update average time history + double average = result.CallCount > 0 ? result.TotalTime / result.CallCount : 0.0; + history.averageTimeHistory.push_back(average); + if (history.averageTimeHistory.size() > ProfileHistory::MaxHistory) + history.averageTimeHistory.pop_front(); + } +} + +// Render the profiler window with table and graphs +void ProfilerWindow::Show() +{ + // Check if it's time to update the profiler data + auto now = std::chrono::steady_clock::now(); + std::chrono::duration elapsed = now - m_LastUpdateTime; + + bool shouldUpdate = false; + if (elapsed.count() >= m_UpdateInterval) + { + shouldUpdate = true; + m_LastUpdateTime = now; + } + + // Begin ImGui window + ImGui::Begin("Profiler"); + + const auto& data = Profiler::Get().GetLastFrameData(); + + if (data.empty()) + { + ImGui::Text("No profiling data available."); + ImGui::End(); + return; + } + + if (shouldUpdate) + { + // Calculate total frame time + double totalFrameTime = 0.0; + for (const auto& [name, result] : data) + { + totalFrameTime += result.TotalTime; + } + + // Update history data + UpdateHistory(data, totalFrameTime); + + // Reset profiling data for the next interval + Profiler::Get().EndFrame(); + } + + // Render profiling data table + RenderTable(data); + + // Render profiling graphs + RenderGraphs(); + + // Display total frame time (from the last update) + if (!m_TotalFrameTimeHistory.empty()) + { + double lastTotalFrameTime = m_TotalFrameTimeHistory.back(); + ImGui::Separator(); + ImGui::Text("Total Frame Time: %.3f µs", lastTotalFrameTime); + } + + ImGui::End(); +} + +// Render the profiling data table +void ProfilerWindow::RenderTable(const std::unordered_map& data) +{ + // Sort functions by total time descending + std::vector> sortedData(data.begin(), data.end()); + std::sort(sortedData.begin(), sortedData.end(), + [](const std::pair& a, const std::pair& b) -> bool { + return a.second.TotalTime > b.second.TotalTime; + }); + + // Add a filter input + static char filterBuffer[128] = ""; + ImGui::InputText("Filter", filterBuffer, IM_ARRAYSIZE(filterBuffer)); + + // Convert filter to string + std::string filterStr = filterBuffer; + + // Filtered data + std::vector> filteredData; + for (const auto& [name, result] : sortedData) + { + if (filterStr.empty() || name.find(filterStr) != std::string::npos) + filteredData.emplace_back(name, result); + } + + // Define threshold for highlighting (e.g., 1000 µs) + const double highlightThreshold = 1000.0; + + // Table with sorted data + if (ImGui::BeginTable("ProfilerTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) + { + ImGui::TableSetupColumn("Function", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Total Time (µs)", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Average Time (µs)", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Calls", ImGuiTableColumnFlags_None); + ImGui::TableHeadersRow(); + + for (const auto& [name, result] : filteredData) + { + ImGui::TableNextRow(); + + // Function Name with tooltip + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(name.c_str()); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Total Time: %.3f µs", result.TotalTime); + double average = result.CallCount > 0 ? result.TotalTime / result.CallCount : 0.0; + ImGui::Text("Average Time: %.3f µs", average); + ImGui::Text("Call Count: %d", result.CallCount); + ImGui::EndTooltip(); + } + + // Total Time with color coding + ImGui::TableSetColumnIndex(1); + if (result.TotalTime > highlightThreshold) + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%.3f", result.TotalTime); + else + ImGui::Text("%.3f", result.TotalTime); + + // Average Time + ImGui::TableSetColumnIndex(2); + double average = result.CallCount > 0 ? result.TotalTime / result.CallCount : 0.0; + ImGui::Text("%.3f", average); + + // Call Count + ImGui::TableSetColumnIndex(3); + ImGui::Text("%d", result.CallCount); + } + + ImGui::EndTable(); + } +} + +// Render the profiling graphs +void ProfilerWindow::RenderGraphs() +{ + ImGui::Separator(); + ImGui::Text("Profiling Graphs"); + + // Example: Render a bar graph for the top 5 functions by total time + std::vector> sortedData; + + const auto& data = Profiler::Get().GetLastFrameData(); + for (const auto& [name, result] : data) + { + sortedData.emplace_back(name, result); + } + + // Sort and take top 5 + std::sort(sortedData.begin(), sortedData.end(), + [](const std::pair& a, const std::pair& b) -> bool { + return a.second.TotalTime > b.second.TotalTime; + }); + + size_t displayCount = std::min(5, sortedData.size()); + + for (size_t i = 0; i < displayCount; ++i) + { + const auto& [name, result] = sortedData[i]; + double percentage = 0.0; + + if (!m_TotalFrameTimeHistory.empty()) + { + // Prevent division by zero + double lastTotalFrameTime = m_TotalFrameTimeHistory.back(); + if (lastTotalFrameTime > 0.0) + { + percentage = (result.TotalTime / lastTotalFrameTime) * 100.0; + } + } + + ImGui::PushID(static_cast(i)); + ImGui::Text("%s", name.c_str()); + ImGui::SameLine(); + ImGui::ProgressBar(static_cast(percentage / 100.0f), ImVec2(-1.0f, 0.0f), + (std::to_string(percentage) + "%").c_str()); + ImGui::PopID(); + } + + // Example: Render a line plot for total frame time with moving average + if (!m_TotalFrameTimeHistory.empty()) + { + ImGui::Text("Frame Time Over Last %zu Frames (Smoothed)", m_TotalFrameTimeHistory.size()); + + // Calculate moving average with a window of 10 frames + size_t windowSize = 10; + std::vector smoothedFrameTimes = MovingAverage(m_TotalFrameTimeHistory, windowSize); + + ImGui::PlotLines("##FrameTimeSmoothed", smoothedFrameTimes.data(), static_cast(smoothedFrameTimes.size()), 0, NULL, 0.0f, 1000.0f, ImVec2(0, 80)); + } +} diff --git a/src/Windows/ProfilerWindow.h b/src/Windows/ProfilerWindow.h new file mode 100644 index 0000000..8ea806d --- /dev/null +++ b/src/Windows/ProfilerWindow.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "Engine/Profiler.h" // Ensure Profiler classes are included + +class ProfilerWindow +{ +public: + ProfilerWindow(); + ~ProfilerWindow() = default; + + // Render the profiler window + void Show(); + +private: + struct ProfileHistory + { + std::deque totalTimeHistory; + std::deque averageTimeHistory; + static const size_t MaxHistory = 100; + }; + + std::unordered_map m_ProfileHistories; + std::deque m_TotalFrameTimeHistory; + static const size_t MaxFrameHistory = 100; + + // Timing variables for update throttling + double m_UpdateInterval; // Interval in seconds (0.1) + std::chrono::steady_clock::time_point m_LastUpdateTime; + + // Helper functions + void UpdateHistory(const std::unordered_map& data, double totalFrameTime); + void RenderTable(const std::unordered_map& data); + void RenderGraphs(); + + // Helper for data smoothing + std::vector MovingAverage(const std::deque& data, size_t window); +}; diff --git a/src/Windows/RenderWindow.cpp b/src/Windows/RenderWindow.cpp index d9fa448..6061b2c 100644 --- a/src/Windows/RenderWindow.cpp +++ b/src/Windows/RenderWindow.cpp @@ -10,7 +10,6 @@ #include "gcml.h" - #include "Componenets/GameObject.h" #include "Componenets/mesh.h" #include "Componenets/transform.h" @@ -28,8 +27,8 @@ extern std::vector> g_GameObjects; // Extern reference to our global (or extern) asset manager extern AssetManager g_AssetManager; -extern int g_GPU_Triangles_drawn_to_screen; +extern int g_GPU_Triangles_drawn_to_screen; // Example cube data (position + UVs) static float g_CubeVertices[] = @@ -182,10 +181,77 @@ static unsigned int g_CubeIndices[] = // Bottom 20, 21, 22, 22, 23, 20}; -void RenderWindow::Show() + + + +bool PlayPauseButton(const char* label, bool* isPlaying) { - - ImGui::Begin("Editor"); + // Define button size + ImVec2 buttonSize = ImVec2(50, 50); // Adjust size as needed + + // Begin the button + if (ImGui::Button(label, buttonSize)) + { + // Toggle the state + *isPlaying = !(*isPlaying); + return true; // Indicate that the state was toggled + } + + // Add tooltip + if (ImGui::IsItemHovered()) + { + ImGui::SetTooltip(*isPlaying ? "Pause (Space)" : "Play (Space)"); + } + + // Get the current window's draw list + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Get the position of the button + ImVec2 button_pos = ImGui::GetItemRectMin(); + ImVec2 button_size = ImGui::GetItemRectSize(); + ImVec2 center = ImVec2(button_pos.x + button_size.x * 0.5f, button_pos.y + button_size.y * 0.5f); + + // Define icon size + float icon_size = 20.0f; + float half_icon_size = icon_size / 2.0f; + + // Define colors + ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_Text); + + if (*isPlaying) + { + // Draw Pause Icon (two vertical bars) + float bar_width = 4.0f; + float spacing = 6.0f; + + // Left bar + ImVec2 left_bar_p1 = ImVec2(center.x - spacing - bar_width, center.y - half_icon_size); + ImVec2 left_bar_p2 = ImVec2(center.x - spacing, center.y + half_icon_size); + draw_list->AddRectFilled(left_bar_p1, left_bar_p2, icon_color, 2.0f); + + // Right bar + ImVec2 right_bar_p1 = ImVec2(center.x + spacing, center.y - half_icon_size); + ImVec2 right_bar_p2 = ImVec2(center.x + spacing + bar_width, center.y + half_icon_size); + draw_list->AddRectFilled(right_bar_p1, right_bar_p2, icon_color, 2.0f); + } + else + { + // Draw Play Icon (triangle) + ImVec2 p1 = ImVec2(center.x - half_icon_size, center.y - half_icon_size); + ImVec2 p2 = ImVec2(center.x - half_icon_size, center.y + half_icon_size); + ImVec2 p3 = ImVec2(center.x + half_icon_size, center.y); + draw_list->AddTriangleFilled(p1, p2, p3, icon_color); + } + + return false; // No toggle occurred +} + + + +void RenderWindow::Show(bool *GameRunning) +{ + + ImGui::Begin("Editor##EditorWindow"); ImVec2 size = ImGui::GetContentRegionAvail(); int w = static_cast(size.x); @@ -197,6 +263,13 @@ void RenderWindow::Show() m_Initialized = true; } + // Center the button + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - 60) * 0.5f); + + // Render the Play/Pause button + // Render the Play/Pause button + PlayPauseButton("##PlayPauseButton", GameRunning); + // If there's space, render to the FBO, then show it as an ImGui image @@ -205,7 +278,7 @@ void RenderWindow::Show() { if (w != m_LastWidth || h != m_LastHeight) { - + m_FBO.Create(w, h); m_LastWidth = w; m_LastHeight = h; @@ -220,8 +293,9 @@ void RenderWindow::Show() ImGui::Text("No space to render."); } - ImGui::End(); + + ImGui::End(); } void RenderWindow::InitGLResources() @@ -231,7 +305,7 @@ void RenderWindow::InitGLResources() // ---------------------------------------------------- { - Shader* shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/UnlitMaterial"); + Shader *shaderAsset = g_AssetManager.loadAsset(AssetType::SHADER, "assets/shaders/UnlitMaterial"); if (!shaderAsset) { fprintf(stderr, "[RenderWindow] Failed to load shader via AssetManager.\n"); @@ -285,14 +359,11 @@ void RenderWindow::InitGLResources() // ---------------------------------------------------- // 4) Initialize GameObjects // ---------------------------------------------------- - } void RenderWindow::RenderSceneToFBO() { - - m_RotationAngle += 0.001f; // spin per frame // Bind the FBO @@ -319,27 +390,21 @@ void RenderWindow::RenderSceneToFBO() // Iterate over each GameObject and render it - for (auto &obj : g_GameObjects) - { + { // ----------------------------------- // 1) Build MVP from transform // ----------------------------------- glm::mat4 model = glm::mat4(1.f); - std::shared_ptr transform = obj->GetComponent(); - - std::shared_ptr mesh = obj->GetComponent(); - - if (transform && mesh) { - + // Translate g_GPU_Triangles_drawn_to_screen += static_cast(mesh->indexCount); @@ -348,7 +413,7 @@ void RenderWindow::RenderSceneToFBO() // Rotate around X, Y, Z - //transform->rotation.x += m_RotationAngle; + // transform->rotation.x += m_RotationAngle; model = glm::rotate(model, glm::radians(transform->rotation.x), glm::vec3(1.f, 0.f, 0.f)); model = glm::rotate(model, glm::radians(transform->rotation.y), glm::vec3(0.f, 1.f, 0.f)); model = glm::rotate(model, glm::radians(transform->rotation.z), glm::vec3(0.f, 0.f, 1.f)); @@ -356,8 +421,6 @@ void RenderWindow::RenderSceneToFBO() // Scale model = glm::scale(model, transform->scale); - - // Compute MVP glm::mat4 mvp = proj * view * model; diff --git a/src/Windows/RenderWindow.h b/src/Windows/RenderWindow.h index 72a8afb..088a229 100644 --- a/src/Windows/RenderWindow.h +++ b/src/Windows/RenderWindow.h @@ -8,7 +8,7 @@ class RenderWindow { public: - void Show(); + void Show(bool *GameRunning); private: void InitGLResources(); diff --git a/src/Windows/SceneWindow.cpp b/src/Windows/SceneWindow.cpp index 99f6564..b323288 100644 --- a/src/Windows/SceneWindow.cpp +++ b/src/Windows/SceneWindow.cpp @@ -13,7 +13,6 @@ extern std::vector> g_GameObjects; extern std::shared_ptr g_SelectedObject; - extern AssetManager g_AssetManager; // Helper: Create a default cube GameObject @@ -38,7 +37,7 @@ std::shared_ptr CreateDefaultCube() void SceneWindow::Show() { - if (ImGui::Begin("Scene Window")) + if (ImGui::Begin("Scene Window##SceneWindow")) { // Add Button if (ImGui::Button("Add Object")) @@ -52,61 +51,58 @@ void SceneWindow::Show() // Begin child region for the list to make it scrollable ImGui::BeginChild("GameObjectList", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), false, ImGuiWindowFlags_HorizontalScrollbar); - // Define TreeNode flags for better visuals and interaction - ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth; + // Initialize an external index to keep track of each GameObject's position + size_t index = 0; - // Iterate through GameObjects using index for unique identification - for (size_t i = 0; i < g_GameObjects.size(); ++i) + // Iterate through GameObjects using a range-based for loop + for (auto &obj : g_GameObjects) { - auto &obj = g_GameObjects[i]; + // Determine if the current GameObject is selected + bool isSelected = (g_SelectedObject == obj); - // Determine flags based on selection - ImGuiTreeNodeFlags flags = nodeFlags; - if (g_SelectedObject == obj) - flags |= ImGuiTreeNodeFlags_Selected; + // Create a unique label for each selectable item using the index + // This ensures ImGui uniquely identifies each item + std::string label = obj->name + "##" + std::to_string(index); - // Unique identifier for each GameObject node using pointer to ensure uniqueness - // Alternatively, you can use the object's ID or address - std::string nodeLabel = obj->name; - bool nodeOpen = ImGui::TreeNodeEx((void *)(intptr_t)i, flags, nodeLabel.c_str()); - - // Handle selection - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) + // Render the GameObject as a selectable item in the list + if (ImGui::Selectable(label.c_str(), isSelected)) { + // Update the selected GameObject when clicked g_SelectedObject = obj; } - // Right-click context menu for GameObject actions + // Handle right-click context menu for the current item if (ImGui::BeginPopupContextItem()) { - // Delete GameObject Option + // Option to remove the GameObject if (ImGui::MenuItem("Remove")) { + // Remove the GameObject by its index + RemoveGameObject(static_cast(index)); - RemoveGameObject(static_cast(i)); - + // End the popup before breaking out of the loop ImGui::EndPopup(); - // Since we've erased the current entity, adjust the loop accordingly - // Decrement i to account for the removed element - --i; - continue; // Skip the rest of the loop iteration + + // Since we've modified the container, exit the loop to prevent issues + break; } + // End the context menu popup ImGui::EndPopup(); } - // Optionally, implement double-click to rename or perform other actions - - // Close the tree node - if (nodeOpen) - { - // If you decide to add child nodes in the future, handle them here - // Currently, no additional handling is required - - ImGui::TreePop(); - } + // Increment the index for the next GameObject + ++index; } + // Optional: Display a message if there are no GameObjects + if (g_GameObjects.empty()) + { + ImGui::Text("No Game Objects available."); + } + + // End the ImGui window or group + ImGui::EndChild(); ImGui::Separator(); @@ -149,7 +145,7 @@ void SceneWindow::RemoveGameObject(int index) } else { - DEBUG_PRINT("Attempted to remove GameObject with invalid index: %d", index ); + DEBUG_PRINT("Attempted to remove GameObject with invalid index: %d", index); } }