99#include < skia/include/core/SkPixmap.h>
1010
1111#include " ./bar_component.hpp"
12+ #include " ./xr_renderer.hpp"
1213#include " ./content.hpp"
1314
1415namespace jsar ::example
@@ -59,6 +60,7 @@ namespace jsar::example
5960 initGLProgram ();
6061 createGeometry ();
6162 createBarTexture ();
63+ createButtonGeometry ();
6264
6365 if (glGetError () != GL_NO_ERROR)
6466 cout << " OpenGL error on BarComponent init" << endl;
@@ -69,8 +71,13 @@ namespace jsar::example
6971 glDeleteVertexArrays (1 , &vao_);
7072 glDeleteBuffers (1 , &vertexVBO_);
7173 glDeleteBuffers (1 , &instanceVBO_);
72- glDeleteProgram (program_);
7374 glDeleteTextures (1 , &barTexture_);
75+
76+ glDeleteVertexArrays (1 , &buttonVAO_);
77+ glDeleteBuffers (1 , &buttonVertexVBO_);
78+ glDeleteBuffers (1 , &buttonInstanceVBO_);
79+
80+ glDeleteProgram (barShader_.ID );
7481 }
7582
7683 void BarComponent::addContent (Content *content)
@@ -135,56 +142,7 @@ namespace jsar::example
135142
136143 void BarComponent::initGLProgram ()
137144 {
138- // Create and compile shaders
139- GLuint vertexShader = glCreateShader (GL_VERTEX_SHADER);
140- glShaderSource (vertexShader, 1 , &barVertSource_, NULL );
141- glCompileShader (vertexShader);
142-
143- GLuint fragmentShader = glCreateShader (GL_FRAGMENT_SHADER);
144- glShaderSource (fragmentShader, 1 , &barFragSource_, NULL );
145- glCompileShader (fragmentShader);
146-
147- // Check for shader compile errors
148- int success;
149- char infoLog[512 ];
150- glGetShaderiv (vertexShader, GL_COMPILE_STATUS, &success);
151- if (!success)
152- {
153- glGetShaderInfoLog (vertexShader, 512 , NULL , infoLog);
154- cout << " ERROR::SHADER::VERTEX::COMPILATION_FAILED\n "
155- << infoLog << endl;
156- }
157-
158- glGetShaderiv (fragmentShader, GL_COMPILE_STATUS, &success);
159- if (!success)
160- {
161- glGetShaderInfoLog (fragmentShader, 512 , NULL , infoLog);
162- cout << " ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n "
163- << infoLog << endl;
164- }
165-
166- // Create shader program
167- program_ = glCreateProgram ();
168- glAttachShader (program_, vertexShader);
169- glAttachShader (program_, fragmentShader);
170- glLinkProgram (program_);
171-
172- // Check for linking errors
173- glGetProgramiv (program_, GL_LINK_STATUS, &success);
174- if (!success)
175- {
176- glGetProgramInfoLog (program_, 512 , NULL , infoLog);
177- cout << " ERROR::SHADER::PROGRAM::LINKING_FAILED\n "
178- << infoLog << endl;
179- }
180-
181- glDeleteShader (vertexShader);
182- glDeleteShader (fragmentShader);
183-
184- // Get uniform locations
185- viewMatrixLoc_ = glGetUniformLocation (program_, " view" );
186- projectionMatrixLoc_ = glGetUniformLocation (program_, " projection" );
187- textureLoc_ = glGetUniformLocation (program_, " barTexture" );
145+ barShader_ = Shader (barVertSource_, barFragSource_);
188146 }
189147
190148 void BarComponent::createGeometry ()
@@ -270,6 +228,89 @@ namespace jsar::example
270228 glBindVertexArray (0 );
271229 }
272230
231+ void BarComponent::createButtonGeometry ()
232+ {
233+ // Generate circle vertices for close button (center + perimeter)
234+ // Each vertex has: position (x, y, z) + texCoord (u, v) = 5 floats
235+ buttonVertices_.clear ();
236+
237+ // Center vertex
238+ buttonVertices_.push_back (0 .0f ); // Center x
239+ buttonVertices_.push_back (0 .0f ); // Center y
240+ buttonVertices_.push_back (0 .0f ); // Center z
241+ buttonVertices_.push_back (0 .5f ); // Center texCoord u (center of texture)
242+ buttonVertices_.push_back (0 .5f ); // Center texCoord v (center of texture)
243+
244+ // Perimeter vertices
245+ const float radius = CLOSE_BUTTON_RADIUS;
246+ for (int i = 0 ; i <= 360 ; i += 10 )
247+ {
248+ float theta = glm::radians ((float )i);
249+ float x = radius * cos (theta);
250+ float y = radius * sin (theta);
251+
252+ buttonVertices_.push_back (x); // Position x
253+ buttonVertices_.push_back (y); // Position y
254+ buttonVertices_.push_back (0 .0f ); // Position z
255+
256+ // Texture coordinates mapped from circle to [0,1] range
257+ buttonVertices_.push_back (0 .5f + 0 .5f * cos (theta)); // texCoord u
258+ buttonVertices_.push_back (0 .5f + 0 .5f * sin (theta)); // texCoord v
259+ }
260+
261+ // Create VAO and VBO for button
262+ glGenBuffers (1 , &buttonVertexVBO_);
263+ glGenVertexArrays (1 , &buttonVAO_);
264+ glBindVertexArray (buttonVAO_);
265+
266+ glBindBuffer (GL_ARRAY_BUFFER, buttonVertexVBO_);
267+ glBufferData (GL_ARRAY_BUFFER, buttonVertices_.size () * sizeof (float ), buttonVertices_.data (), GL_STATIC_DRAW);
268+
269+ // Position attribute (3 floats per vertex, stride = 5 floats)
270+ glVertexAttribPointer (0 , 3 , GL_FLOAT, GL_FALSE, 5 * sizeof (float ), (void *)0 );
271+ glEnableVertexAttribArray (0 );
272+
273+ // Texture coordinate attribute
274+ glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 5 * sizeof (float ), (void *)(3 * sizeof (float )));
275+ glEnableVertexAttribArray (1 );
276+
277+ // Instance buffer (will be updated dynamically)
278+ glGenBuffers (1 , &buttonInstanceVBO_);
279+ glBindBuffer (GL_ARRAY_BUFFER, buttonInstanceVBO_);
280+
281+ struct InstanceData
282+ {
283+ glm::mat4 transform;
284+ glm::vec3 color;
285+ };
286+
287+ size_t vec4Size = sizeof (glm::vec4);
288+ size_t instanceSize = sizeof (InstanceData);
289+
290+ glVertexAttribPointer (2 , 4 , GL_FLOAT, GL_FALSE, instanceSize, (void *)offsetof (InstanceData, transform));
291+ glVertexAttribPointer (3 , 4 , GL_FLOAT, GL_FALSE, instanceSize, (void *)(offsetof (InstanceData, transform) + vec4Size));
292+ glVertexAttribPointer (4 , 4 , GL_FLOAT, GL_FALSE, instanceSize, (void *)(offsetof (InstanceData, transform) + 2 * vec4Size));
293+ glVertexAttribPointer (5 , 4 , GL_FLOAT, GL_FALSE, instanceSize, (void *)(offsetof (InstanceData, transform) + 3 * vec4Size));
294+ glEnableVertexAttribArray (2 );
295+ glEnableVertexAttribArray (3 );
296+ glEnableVertexAttribArray (4 );
297+ glEnableVertexAttribArray (5 );
298+
299+ // Instance color
300+ glVertexAttribPointer (6 , 3 , GL_FLOAT, GL_FALSE, instanceSize, (void *)offsetof (InstanceData, color));
301+ glEnableVertexAttribArray (6 );
302+
303+ // Set instance divisors
304+ glVertexAttribDivisor (2 , 1 );
305+ glVertexAttribDivisor (3 , 1 );
306+ glVertexAttribDivisor (4 , 1 );
307+ glVertexAttribDivisor (5 , 1 );
308+ glVertexAttribDivisor (6 , 1 );
309+
310+ glBindBuffer (GL_ARRAY_BUFFER, 0 );
311+ glBindVertexArray (0 );
312+ }
313+
273314 void BarComponent::createBarTexture ()
274315 {
275316 // Create Skia surface for rendering the bar texture with Apple design
@@ -347,18 +388,16 @@ namespace jsar::example
347388 if (instances_.empty ())
348389 return ;
349390
350-
351- glUseProgram (program_);
391+ glUseProgram (barShader_.ID );
352392 glBindVertexArray (vao_);
353393
354394 // Bind the Skia-generated bar texture
355395 glActiveTexture (GL_TEXTURE0);
356396 glBindTexture (GL_TEXTURE_2D, barTexture_);
357- glUniform1i (textureLoc_, 0 );
358-
397+ barShader_.setInt (" barTexture" , 0 );
359398 // Set uniforms
360- glUniformMatrix4fv (viewMatrixLoc_, 1 , GL_FALSE, & viewMatrix[ 0 ][ 0 ] );
361- glUniformMatrix4fv (projectionMatrixLoc_, 1 , GL_FALSE, & projectionMatrix[ 0 ][ 0 ] );
399+ barShader_. setMat4 ( " view " , viewMatrix);
400+ barShader_. setMat4 ( " projection " , projectionMatrix);
362401
363402 {
364403 glEnable (GL_DEPTH_TEST);
@@ -368,6 +407,15 @@ namespace jsar::example
368407 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
369408
370409 glDrawArraysInstanced (GL_TRIANGLE_FAN, 0 , 4 , instances_.size ());
410+
411+ // Render button
412+ // Each button vertex has 5 floats (position + texCoord)
413+ GLsizei vertexCount = static_cast <GLsizei>(buttonVertices_.size () / 5 );
414+ GLsizei instanceCount = static_cast <GLsizei>(instances_.size ());
415+
416+ glBindVertexArray (buttonVAO_);
417+ glDrawArraysInstanced (GL_TRIANGLE_FAN, 0 , vertexCount, instanceCount);
418+ glBindVertexArray (0 );
371419 }
372420
373421 // Reset state
@@ -461,6 +509,100 @@ namespace jsar::example
461509 glBindBuffer (GL_ARRAY_BUFFER, instanceVBO_);
462510 glBufferData (GL_ARRAY_BUFFER, instanceData.size () * sizeof (InstanceData), instanceData.data (), GL_DYNAMIC_DRAW);
463511 glBindBuffer (GL_ARRAY_BUFFER, 0 );
512+
513+ updateButtonInstanceBuffer ();
514+ }
515+
516+ void BarComponent::updateButtonInstanceBuffer ()
517+ {
518+ struct ButtonInstanceData
519+ {
520+ glm::mat4 transform;
521+ glm::vec3 color;
522+ };
523+ vector<ButtonInstanceData> instanceData;
524+ instanceData.reserve (instances_.size ());
525+
526+ for (const auto &inst : instances_)
527+ {
528+ ButtonInstanceData data;
529+ glm::vec3 buttonPos = calculateButtonWorldPos (inst.transform [3 ]);
530+ data.transform = glm::translate (glm::mat4 (1 .0f ), buttonPos);
531+
532+ // Set color based on state
533+ if (inst.buttonIsHovered )
534+ {
535+ data.color = glm::vec3 (0 .7f , 0 .7f , 0 .7f ); // Gray when hovered
536+ }
537+ else
538+ {
539+ data.color = glm::vec3 (1 .0f , 1 .0f , 1 .0f ); // White default
540+ }
541+
542+ instanceData.push_back (data);
543+ }
544+
545+ // Update instance buffer - always has data, even if dummy
546+ glBindBuffer (GL_ARRAY_BUFFER, buttonInstanceVBO_);
547+ glBufferData (GL_ARRAY_BUFFER, instanceData.size () * sizeof (ButtonInstanceData), instanceData.data (), GL_DYNAMIC_DRAW);
548+ glBindBuffer (GL_ARRAY_BUFFER, 0 );
549+ }
550+
551+ void BarComponent::processInput (Content *content)
552+ {
553+ auto windowCtx = content->getWindowContext ();
554+ auto xrRenderer = windowCtx->xrRenderer ;
555+ assert (xrRenderer != nullptr );
556+ bool mousePressed = (glfwGetMouseButton (windowCtx->window , GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS);
557+
558+ // Extract ray origin and direction from the main controller target ray matrix
559+ glm::mat4 controllerMatrix = xrRenderer->getMainControllerTargetRay ();
560+ glm::vec3 rayOrigin = glm::vec3 (controllerMatrix[3 ]); // Translation column
561+ glm::vec3 rayDir = -glm::vec3 (controllerMatrix[2 ]); // Negative Z axis (forward)
562+
563+ Content *contentToClose = nullptr ; // Clear pending close from previous frame
564+
565+ // Check all instances
566+ for (auto &inst : instances_)
567+ {
568+ glm::vec3 barPos = glm::vec3 (inst.transform [3 ]);
569+ glm::vec3 buttonPos = calculateButtonWorldPos (barPos);
570+
571+ // Check button first (higher priority)
572+ float distToButton = rayDistanceToPoint (rayOrigin, rayDir, buttonPos);
573+ if (distToButton <= CLOSE_BUTTON_RADIUS)
574+ {
575+ inst.buttonIsHovered = true ;
576+ // Check for click - delay callback execution to avoid iterator invalidation
577+ if (mousePressed)
578+ {
579+ contentToClose = inst.content ; // Mark for closing (defer callback)
580+ }
581+ }
582+ else
583+ {
584+ inst.buttonIsHovered = false ;
585+ }
586+ }
587+ // Process close callback after all instance processing is complete
588+ // This callback may modify instances_ via removeContent(), which is safe now
589+ if (contentToClose != nullptr && onCloseCallback_)
590+ {
591+ onCloseCallback_ (contentToClose);
592+ contentToClose = nullptr ;
593+ }
594+ }
595+
596+ float BarComponent::rayDistanceToPoint (const glm::vec3 &rayOrigin, const glm::vec3 &rayDir, const glm::vec3 &point) const
597+ {
598+ glm::vec3 toPoint = point - rayOrigin;
599+ float t = glm::dot (toPoint, rayDir);
600+
601+ if (t < 0 )
602+ return std::numeric_limits<float >::max (); // Point is behind ray
603+
604+ glm::vec3 closestPoint = rayOrigin + t * rayDir;
605+ return glm::distance (closestPoint, point);
464606 }
465607
466608 glm::mat4 BarComponent::calculateBarTransform (const glm::vec3 &contentPosition) const
@@ -471,4 +613,10 @@ namespace jsar::example
471613 // Create transformation matrix
472614 return glm::translate (glm::mat4 (1 .0f ), barPosition);
473615 }
474- }
616+
617+ glm::vec3 BarComponent::calculateButtonWorldPos (const glm::vec3 &contentPosition) const
618+ {
619+ // Button is positioned at top-right corner of the bar
620+ return contentPosition + glm::vec3 (BAR_WIDTH / 2 + CLOSE_BUTTON_RADIUS + CLOSE_BUTTON_OFFSET, 0 .0f , 0 .0f );
621+ }
622+ }
0 commit comments