88#include " ../glm/gtc/matrix_transform.hpp"
99#include " ../glm/gtc/type_ptr.hpp"
1010#include < vector>
11+ #include < float.h>
1112#include " GLFW/glfw3.h" // For GLFWwindow
1213#include " sphere.h"
1314
@@ -30,7 +31,9 @@ struct RodInstance {
3031extern int x, y, z;
3132extern bool createNewNode;
3233extern bool createNewRod;
33- extern float camAngleX, camAngleY, camAngleZ, camDistance, zoom;
34+ extern float camAngleX, camAngleZ, camDistance, zoom;
35+ extern int selectedNodeIndex, selectedRodIndex;
36+ extern glm::vec3 recenterOffset;
3437extern float xyzRodEnd1[3 ];
3538extern float xyzRodEnd2[3 ];
3639extern std::vector<SphereInstance> spheres;
@@ -45,6 +48,120 @@ void initImGui(GLFWwindow* window) {
4548 ImGui_ImplOpenGL3_Init (" #version 330" );
4649}
4750
51+ // --------- Keyboard Input Handling --------- //
52+ // Handle object selection with Shift+Click
53+ void handleObjectSelection (GLFWwindow* window) {
54+ // Only handle Shift+Click for object selection
55+ bool shiftPressed = glfwGetKey (window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
56+ glfwGetKey (window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
57+
58+ if (shiftPressed && glfwGetMouseButton (window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
59+ static bool wasPressed = false ;
60+ if (!wasPressed) { // Only trigger on press, not hold
61+ wasPressed = true ;
62+
63+ double xpos, ypos;
64+ glfwGetCursorPos (window, &xpos, &ypos);
65+
66+ int width, height;
67+ glfwGetWindowSize (window, &width, &height);
68+
69+ // Convert screen coordinates to normalized device coordinates
70+ float x = (2 .0f * xpos) / width - 1 .0f ;
71+ float y = 1 .0f - (2 .0f * ypos) / height;
72+
73+ // Simple selection logic - find closest object to mouse position
74+ float minDistance = 0 .3f ; // Selection threshold
75+ int closestNode = -1 ;
76+ int closestRod = -1 ;
77+ float closestNodeDist = FLT_MAX; // Start with very large distance
78+ float closestRodDist = FLT_MAX; // Start with very large distance
79+
80+ // Check nodes (spheres) - use actual world positions
81+ for (int i = 0 ; i < spheres.size (); i++) {
82+ glm::vec3 worldPos = spheres[i].position ;
83+ // Convert to screen space (very simplified - assumes orthographic projection)
84+ float screenX = worldPos.x * 0 .3f ; // Scale factor for ortho projection
85+ float screenY = worldPos.y * 0 .3f ;
86+
87+ float distance = sqrt ((x - screenX) * (x - screenX) + (y - screenY) * (y - screenY));
88+ if (distance < closestNodeDist) {
89+ closestNode = i;
90+ closestNodeDist = distance;
91+ }
92+ }
93+
94+ // Check rods
95+ for (int i = 0 ; i < rods.size (); i++) {
96+ glm::vec3 mid = (rods[i].end1 + rods[i].end2 ) * 0 .5f ;
97+ float screenX = mid.x * 0 .3f ;
98+ float screenY = mid.y * 0 .3f ;
99+
100+ float distance = sqrt ((x - screenX) * (x - screenX) + (y - screenY) * (y - screenY));
101+ if (distance < closestRodDist) {
102+ closestRod = i;
103+ closestRodDist = distance;
104+ }
105+ }
106+
107+ // Always clear selections first
108+ selectedNodeIndex = -1 ;
109+ selectedRodIndex = -1 ;
110+
111+ // Select the closest object (node or rod) if it's within threshold
112+ if (closestNodeDist < closestRodDist && closestNodeDist < minDistance) {
113+ selectedNodeIndex = closestNode;
114+ } else if (closestRodDist < minDistance) {
115+ selectedRodIndex = closestRod;
116+ }
117+ // If no object is close enough, selections remain cleared (deselection)
118+ }
119+ } else {
120+ static bool wasPressed = false ;
121+ wasPressed = false ; // Reset when button is released or shift is not pressed
122+ }
123+ }
124+
125+ void handleKeyboardInput (GLFWwindow* window) {
126+ // Arrow key controls for camera
127+ float step = 2 .0f ; // Step size for camera angle changes
128+ float zoomStep = 0 .1f ; // Step size for zoom changes
129+
130+ // Check if Shift is pressed
131+ bool shiftPressed = (glfwGetKey (window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ||
132+ glfwGetKey (window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS);
133+
134+ if (shiftPressed) {
135+ // Shift + arrow keys for Z axis and zoom controls
136+ if (glfwGetKey (window, GLFW_KEY_LEFT) == GLFW_PRESS) {
137+ camAngleZ -= step;
138+ if (camAngleZ < -180 .0f ) camAngleZ += 360 .0f ;
139+ }
140+ if (glfwGetKey (window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
141+ camAngleZ += step;
142+ if (camAngleZ > 180 .0f ) camAngleZ -= 360 .0f ;
143+ }
144+ if (glfwGetKey (window, GLFW_KEY_UP) == GLFW_PRESS) {
145+ zoom += zoomStep;
146+ if (zoom > 3 .0f ) zoom = 3 .0f ;
147+ }
148+ if (glfwGetKey (window, GLFW_KEY_DOWN) == GLFW_PRESS) {
149+ zoom -= zoomStep;
150+ if (zoom < 0 .1f ) zoom = 0 .1f ;
151+ }
152+ } else {
153+ // Regular arrow keys for X axis controls only (limited to -89 to 89 degrees)
154+ if (glfwGetKey (window, GLFW_KEY_UP) == GLFW_PRESS) {
155+ camAngleX += step;
156+ if (camAngleX > 89 .0f ) camAngleX = 89 .0f ;
157+ }
158+ if (glfwGetKey (window, GLFW_KEY_DOWN) == GLFW_PRESS) {
159+ camAngleX -= step;
160+ if (camAngleX < -89 .0f ) camAngleX = -89 .0f ;
161+ }
162+ }
163+ }
164+
48165// --------- ImGui UI Controls --------- //
49166void renderImGuiControls () {
50167 ImGui_ImplOpenGL3_NewFrame ();
@@ -65,9 +182,16 @@ void renderImGuiControls() {
65182 ImGui::Separator ();
66183 ImGui::Text (" Camera Controls" );
67184 ImGui::SliderFloat (" Orbit X (up/down)" , &camAngleX, -89 .0f , 89 .0f );
68- ImGui::SliderFloat (" Orbit Y (left/right)" , &camAngleY, 0 .0f , 360 .0f );
69- ImGui::SliderFloat (" Orbit Z (roll)" , &camAngleZ, -180 .0f , 180 .0f ); // Add this line
185+ ImGui::SliderFloat (" Orbit Z (roll)" , &camAngleZ, 0 .0f , 360 .0f );
70186 ImGui::SliderFloat (" Zoom (distance)" , &zoom, 0 .1f , 3 .0f );
187+
188+ ImGui::Separator ();
189+ ImGui::Text (" Recenter Controls" );
190+ ImGui::DragFloat3 (" Recenter Offset" , glm::value_ptr (recenterOffset), 0 .01f );
191+ ImGui::TextColored (ImVec4 (0 .8f , 0 .8f , 0 .0f , 1 .0f ), " Note: Recenter offset is applied when loading YAML" );
192+
193+ ImGui::Separator ();
194+ ImGui::TextColored (ImVec4 (0 .0f , 0 .0f , 1 .0f , 1 .0f ), " Click on items in dropdowns to select" );
71195
72196 ImGui::End ();
73197
@@ -76,23 +200,46 @@ void renderImGuiControls() {
76200
77201 if (ImGui::Button (" Clear All Nodes" )) {
78202 spheres.clear ();
203+ selectedNodeIndex = -1 ; // Clear selection when clearing all nodes
79204 }
80205
81206 for (int i = 0 ; i < static_cast <int >(spheres.size ()); ++i) {
82207 std::string header = " Node " + std::to_string (i);
83- if (ImGui::CollapsingHeader (header.c_str ())) {
84- ImGui::PushID (i);
85-
208+ bool isSelected = (i == selectedNodeIndex);
209+
210+ // Highlight selected node with blue background
211+ if (isSelected) {
212+ ImGui::PushStyleColor (ImGuiCol_Header, ImVec4 (0 .0f , 0 .0f , 1 .0f , 0 .3f )); // Blue background
213+ ImGui::PushStyleColor (ImGuiCol_HeaderHovered, ImVec4 (0 .0f , 0 .0f , 1 .0f , 0 .5f )); // Brighter blue on hover
214+ }
215+
216+ // Make the header clickable for selection
217+ if (ImGui::Selectable (header.c_str (), isSelected, ImGuiSelectableFlags_AllowItemOverlap)) {
218+ // Clear other selections and select this node
219+ selectedNodeIndex = i;
220+ selectedRodIndex = -1 ;
221+ }
222+
223+ // Show properties if this node is selected
224+ if (isSelected) {
225+ ImGui::Indent ();
86226 ImGui::DragFloat3 (" Position" , glm::value_ptr (spheres[i].position ), 0 .1f );
87227 ImGui::ColorEdit4 (" Color" , glm::value_ptr (spheres[i].color ));
88-
228+
89229 if (ImGui::Button (" Delete Node" )) {
90230 spheres.erase (spheres.begin () + i);
91- ImGui::PopID ();
231+ selectedNodeIndex = -1 ; // Clear selection if deleting selected node
232+ ImGui::Unindent ();
233+ // Pop the blue background colors before breaking
234+ ImGui::PopStyleColor (2 );
92235 break ;
93236 }
94-
95- ImGui::PopID ();
237+ ImGui::Unindent ();
238+ }
239+
240+ // Pop the blue background colors if this was the selected node
241+ if (isSelected) {
242+ ImGui::PopStyleColor (2 );
96243 }
97244 }
98245
@@ -103,24 +250,47 @@ void renderImGuiControls() {
103250
104251 if (ImGui::Button (" Clear All Rods" )) {
105252 rods.clear ();
253+ selectedRodIndex = -1 ; // Clear selection when clearing all rods
106254 }
107255
108256 for (int i = 0 ; i < static_cast <int >(rods.size ()); ++i) {
109257 std::string header = " Rod " + std::to_string (i);
110- if (ImGui::CollapsingHeader (header.c_str ())) {
111- ImGui::PushID (i);
112-
258+ bool isSelected = (i == selectedRodIndex);
259+
260+ // Highlight selected rod with blue background
261+ if (isSelected) {
262+ ImGui::PushStyleColor (ImGuiCol_Header, ImVec4 (0 .0f , 0 .0f , 1 .0f , 0 .3f )); // Blue background
263+ ImGui::PushStyleColor (ImGuiCol_HeaderHovered, ImVec4 (0 .0f , 0 .0f , 1 .0f , 0 .5f )); // Brighter blue on hover
264+ }
265+
266+ // Make the header clickable for selection
267+ if (ImGui::Selectable (header.c_str (), isSelected, ImGuiSelectableFlags_AllowItemOverlap)) {
268+ // Clear other selections and select this rod
269+ selectedRodIndex = i;
270+ selectedNodeIndex = -1 ;
271+ }
272+
273+ // Show properties if this rod is selected
274+ if (isSelected) {
275+ ImGui::Indent ();
113276 ImGui::DragFloat3 (" Start" , glm::value_ptr (rods[i].end1 ), 0 .1f );
114277 ImGui::DragFloat3 (" End" , glm::value_ptr (rods[i].end2 ), 0 .1f );
115278 ImGui::ColorEdit4 (" Color" , glm::value_ptr (rods[i].color ));
116-
279+
117280 if (ImGui::Button (" Delete Rod" )) {
118281 rods.erase (rods.begin () + i);
119- ImGui::PopID ();
282+ selectedRodIndex = -1 ; // Clear selection if deleting selected rod
283+ ImGui::Unindent ();
284+ // Pop the blue background colors before breaking
285+ ImGui::PopStyleColor (2 );
120286 break ;
121287 }
122-
123- ImGui::PopID ();
288+ ImGui::Unindent ();
289+ }
290+
291+ // Pop the blue background colors if this was the selected rod
292+ if (isSelected) {
293+ ImGui::PopStyleColor (2 );
124294 }
125295 }
126296
0 commit comments