Skip to content

Commit bc3450b

Browse files
feat(example): add close button at bar component (#398)
1 parent 542b3ea commit bc3450b

File tree

6 files changed

+452
-73
lines changed

6 files changed

+452
-73
lines changed

src/examples/bar_component.cpp

Lines changed: 206 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <skia/include/core/SkPixmap.h>
1010

1111
#include "./bar_component.hpp"
12+
#include "./xr_renderer.hpp"
1213
#include "./content.hpp"
1314

1415
namespace 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

Comments
 (0)