openGL 2D mouse click location - c

Like this question states - I want to convert a click (2D) into coordinates relating to the rendering on the screen. I have the function glutMouseFunc bound to my function (below) but can not make sense of the x and y that are passed in - they seem to be relating to distance in pixels from the top left corner.
I would like to know how to convert the x and y to world coordinates :)
void mouse(int button, int state, int x, int y) {
switch(button) {
case GLUT_LEFT_BUTTON:
printf(" LEFT ");
if (state == GLUT_DOWN) {
printf("DOWN\n");
printf("(%d, %d)\n", x, y);
}
else
if (state == GLUT_UP) {
printf("UP\n");
}
break;
default:
break;
}
fflush(stdout); // Force output to stdout
}

GLdouble ox=0.0,oy=0.0,oz=0.0;
void Mouse(int button,int state,int x,int y) {
GLint viewport[4];
GLdouble modelview[16],projection[16];
GLfloat wx=x,wy,wz;
if(state!=GLUT_DOWN)
return;
if(button==GLUT_RIGHT_BUTTON)
exit(0);
glGetIntegerv(GL_VIEWPORT,viewport);
y=viewport[3]-y;
wy=y;
glGetDoublev(GL_MODELVIEW_MATRIX,modelview);
glGetDoublev(GL_PROJECTION_MATRIX,projection);
glReadPixels(x,y,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&wz);
gluUnProject(wx,wy,wz,modelview,projection,viewport,&ox,&oy,&oz);
glutPostRedisplay();
}
Where ox, oy, oz are your outputted values
http://hamala.se/forums/viewtopic.php?t=20

In Windowing system, origin (0,0) is at the Upper Left corner, but OpenGL world window origin (0,0) is at the Lower Left corner.
So you need to convert y coordinates only like such a way:
new_y = window_height - y;

Related

Boundary fill incorrectly stops recursion?

I have been trying to execute a simple boundary fill program on my m1 mac using visual studio code where I had the setup, included all the libraries configuring default build task and the build is getting done nicely.
But the issue is when the window is visible, the program has a mouse click event listener, on click it should start region filling with desired color, but it seems to be stopping after drawing one line only.
Here is my program,
#include<stdio.h>
#include <GLUT/glut.h>
int xmin, ymin, xmax, ymax; //Polygon boundaries
float FillColor[3] = {1.0, 0.0, 0.0}; //Color to be filled - red
float BorderColor[3] = {0.0, 0.0, 0.0}; // Border color of polygon - black
void setPixel(int x, int y)
{
glBegin(GL_POINTS);
glColor3fv(FillColor);
glVertex2i(x, y);
glEnd();
glFlush();
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
//Drawing polygon
glColor3fv(BorderColor);
glLineWidth(6);
glBegin(GL_LINES);
glVertex2i(xmin, ymin);
glVertex2i(xmin, ymax);
glEnd();
glBegin(GL_LINES);
glVertex2i(xmax, ymin);
glVertex2i(xmax, ymax);
glEnd();
glBegin(GL_LINES);
glVertex2i(xmin, ymin);
glVertex2i(xmax, ymin);
glEnd();
glBegin(GL_LINES);
glVertex2i(xmin, ymax);
glVertex2i(xmax, ymax);
glEnd();
glFlush();
}
void BoundaryFill(int x,int y)
{
float CurrentColor[3];
glReadPixels(x, y, 1.0, 1.0, GL_RGB, GL_FLOAT, CurrentColor);
// if CurrentColor != BorderColor and CurrentColor != FillColor
if((CurrentColor[0] != BorderColor[0] && (CurrentColor[1]) != BorderColor[1] &&
(CurrentColor[2])!= BorderColor[2]) && (CurrentColor[0] != FillColor[0] &&
(CurrentColor[1]) != FillColor[1] && (CurrentColor[2]) != FillColor[2]))
{
setPixel(x, y);
BoundaryFill(x+1, y);
BoundaryFill(x-1, y);
BoundaryFill(x, y+1);
BoundaryFill(x, y-1);
//Using 4-connected approach, remove comment from below lines to make it 8-connected approach
BoundaryFill(x+1, y+1);
BoundaryFill(x+1, y-1);
BoundaryFill(x-1, y+1);
BoundaryFill(x-1, y-1);
}
}
void mouse(int btn, int state, int x, int y)
{
if(btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
printf("%d, %d\n", x, y);
BoundaryFill(x, 500-y);
}
}
void init()
{
glClearColor(0.101, 1.0, 0.980, 1.0); //Background color - cyan
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0, 500, 0, 500);
}
int main(int argc, char** argv)
{
printf("Window size - 500x500 i.e. range of x and y is 0 -> 500\n");
printf("\nEnter polygon boundaries:-\n");
printf("Enter xmin: ");
scanf("%d", &xmin);
printf("Enter ymin: ");
scanf("%d", &ymin);
printf("Enter xmax: ");
scanf("%d", &xmax);
printf("Enter ymax: ");
scanf("%d", &ymax);
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(500, 500);
glutCreateWindow("Boundary-Fill Algorithm");
init();
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutMainLoop();
return 0;
}
Please check the image -- where after this stage, the recursion stops which meant to be filling more pixels in 8 connected approach.
The orthographic projection covers an area of [0, 500]^2 pixels, whereas the algorithm assumes an area of [0, 500)^2 pixels. Note the difference between the inclusive range and the exclusive range.
This may cause glReadPixels to read the value of the pixel left (and/or down) of where you'd expect it to read, which breaks the algorithm. To solve this, use different dimensions for glOrtho2D:
gluOrtho2D(0, 499, 0, 499);
Additionally, note that the width and height parameters of glReadPixels should be integers, i.e.:
glReadPixels(x, y, 1, 1, GL_RGB, GL_FLOAT, CurrentColor);
and that it is not a good idea to compare float values directly (see elsewhere on StackOverflow). Instead, use something like this:
#include <math.h>
int isEqualFloat(float x, float y)
{
return fabs(x - y) < 0.001F;
}
I'm assuming the use of glReadPixels is here for educational purposes. A better approach would be to store a boolean matrix for "visited" pixels, which avoids the need for comparing colors in floating point values and retrieving data from GPU memory.
Lastly, using a recent Mac may have influence. For some low resolution program, the OS decides to repeat every pixel to avoid having a very small window. However, this effectively doubles the resolution without OpenGL knowing about it.

Get mouse click coordinates in OpenGL during animation?

I want to make a game in which when somebody clicks on the moving ball, it bursts. I have added the codes for animation and the mouse click event, but when the animation is going on, the click function is not working. When I tried it without the animation, it worked properly. I want to know why is this happening.
#include<stdio.h>
#include<GL/glut.h>
#include<unistd.h>
#include<math.h>
int x, y;
float mx, my;
float i, j;
void mouse(int button, int state, int mousex, int mousey)
{
if(button==GLUT_LEFT_BUTTON && state==GLUT_DOWN)
{
mx = mousex;
my = mousey;
printf("%f %f\n",mx,my);
glutPostRedisplay();
}
}
void init()
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glColor3f(0.0, 1.0, 0.0);
glPointSize(1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, 1560, 0, 840);
}
int randValue()
{
int i = rand();
int num = i%1000;
return num;
}
void blast(int x, int y)
{
glBegin(GL_POLYGON);
glColor3f(1.0f,0.0f,0.0f);
glVertex2i(x-100, y-100);
glVertex2i(x, y-100);
glVertex2i(x-22, y-20);
glVertex2i(x-100, y-30);
glVertex2i(x-30, y-40);
glVertex2i(x-150, y-80);
glVertex2i(x-20, y);
glVertex2i(x, y-40);
glVertex2i(x-66, y-125);
glVertex2i(x-34, y-32);
glVertex2i(x-32, y-55);
glVertex2i(x-32, y);
glVertex2i(x-60, y-57);
glVertex2i(x-75, y-69);
glVertex2i(x-100, y);
glEnd();
glFlush();
}
void display()
{
int j = 0, k = 0, l = 1;
while(1)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (i = 0;i < 6.29;i += 0.001)
{
x = 100 * cos(i);
y = 100 * sin(i);
glVertex2i(x / 2 + j, y / 2 + k);
if((x / 2 + j) >= 1560 || (y / 2 + k) >= 840)
{
glEnd();
glFlush();
glClear(GL_COLOR_BUFFER_BIT);
blast(x / 2 + j, y / 2 + k);
sleep(2);
j = randValue();
k = 0;
}
}
j = j + 3;
k = k + 5;
glEnd();
glFlush();
}
}
int main (int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize(1360, 768);
glutInitWindowPosition(0, 0);
glutCreateWindow("{Project}");
init();
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutMainLoop();
}
Your code has an infinite loop inside the display function, thus you never give the control back to GLUT. GLUT already has an infinite loop like that inside glutMainLoop.
Instead you shall render only ONE frame in display, post glutPostRedisplay and return:
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
// ... draw the frame here ...
// for exmaple:
i += 0.001;
float x = 100 * cos(i);
float y = 100 * sin(i);
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_POINTS);
glVertex2f(x, y);
glEnd();
glFlush();
glutPostRedisplay();
}
Then your mouse function will be called and you'll be able to update the state as necessary.
There are two problems here:
OpenGL has no support for an input device by itself, you normally use OpenGL to present information but you have something else attached to the window where you present the info that is what gives you mouse access. this involves to know which is the other environment you are using that offers you a pointing device into the screen area.
if you have the window mouse coordinates you need to map well on the window you present your OpenGL output, but you have to convert them back to some point in your scene, but probably your ball is not there. There's some ambiguity when passing from a plane image representing a 3D scene to a point in that scene in 3D, as you have all points in the Z axis sharing the same screen coordinates in 2D screen. so you have to trace back to the possible position of the ball from the point of view (the camera), based on the window coordinates of the mouse. This is a geometrical problem that involves the inverse transformation of a projection, that is always singular.
you can solve this without having to guess, as you know where your ball is, you can redo the transformation that made it to appear in the two dimensional window, and then compare coordinates based on those. OpenGL allows you to know the actual transformation it is doing to represent your scene, and you can use it to see where in the screen your ball is represented (you don't need to do this for every vertex of the ball, only for the center, for example) and then check if your shot has gone close enough to hit the ball. You should consider also if some other object upper in the Z axis is in the way, so you don't kill anybody behind a wall.

OpenGL image disappears on selection if the OS is not Linux

I need to check if an object or an empty space has been clicked in OpenGL. I capture the click event in the MouseCallback function which passes the x and y click coordinates to ProcessSelection. The function is:
void MouseCallback(int button, int state, int x, int y)
{
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
ProcessSelection(x, y);
}
Then I coordinates process the selection coordinates in ProcessSelection:
#define BUFFER_LENGTH 64
void ProcessSelection(int xPos, int yPos)
{
printf("xpos: %d\n", xPos);
printf("ypos: %d\n", yPos);
GLfloat fAspect; // Screen aspect ratio
GLuint selectBuff[BUFFER_LENGTH];
GLint viewport[4];
glSelectBuffer(BUFFER_LENGTH, selectBuff);
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glRenderMode(GL_SELECT);
glLoadIdentity();
gluPickMatrix(xPos, viewport[3] - yPos, 4, 4, viewport);
fAspect = (float)(viewport[2]) / (float)viewport[3];
gluPerspective(120, fAspect, 1, 300);
display();
// Restore the projection matrix
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
hits = glRenderMode(GL_RENDER);
if (hits >= 1)
enable_animation = !enable_animation;
else
glutSetWindowTitle("You clicked empty space!");
}
The gluPerspective() is the exact same function that is called in the reshape function. I noticed that it works well in Linux but not in Windows and Mac OS X where the image disappears at every click. I can't understand why. Can someone help me?

How to get a sketch to work within another sketch that is using screens?

I am working a project that involves screens. I want to be able to use the number keys to which screens, which will take the user into an interactive part of the sketch.
I began working on one of the interactive parts of the program in a separate sketch. Here is that sketch:
float x, y, r, g, b, radius;
int timer;
void setup() {
size(500, 500);
background(255);
noStroke();
smooth();
}
void draw() {
Zon();
}
void Zon(){
// use frameCount to move x, use modulo to keep it within bounds
x = frameCount % width;
// use millis() and a timer to change the y every 2 seconds
if (millis() - timer >= 8000) {
y = random(height);
timer = millis();
}
// use frameCount and noise to change the red color component
r = noise(frameCount * 0.01) * 255;
// use frameCount and modulo to change the green color component
g = frameCount % 1;
// use frameCount and noise to change the blue color component
b = 255 - noise(1 + frameCount * 0.025) * 255;
// use frameCount and noise to change the radius
radius = noise(frameCount * 0.01) * mouseX;
color c = color(r, g, b);
fill(c);
ellipse(x, y, radius, radius);
}
This was the code for the separate sketch. I want to be able to put this sketch into the my actual project, but it is not functioning the way it does in the separate sketch. Can someone explain to me why this is?
I want there to be a white background and for the ellipse to move across the screen, leaving behind a trail. Is it not working because the background is running over and over again, erasing the trail in the process?
When I remove the background(255); it kind of work, except its running on the menu screen, which I don't want.
Here the actual project code:
final int stateMenu = 0;
final int GreenBox = 3;
int state = stateMenu;
float x, y, r, g, b, radius;
int timer;
PFont font;
PFont Amatic;
void setup()
{
size(800, 700);
smooth();
font = createFont("ARCARTER-78.vlw", 14);
textFont(font);
//Amatic = createFont("Amatic-Bold.ttf",60);
//textFont(Amatic);
frameRate(15);
}
void draw()
{
// the main routine. It handels the states.
// runs again and again
switch (state) {
case stateMenu:
showMenu();
break;
case GreenBox:
handleGreenBox();
break;
default:
println ("Unknown state (in draw) "
+ state
+ " ++++++++++++++++++++++");
}
}
void keyPressed() {
// keyboard. Also different depending on the state.
switch (state) {
case stateMenu:
keyPressedForStateMenu();
break;
case GreenBox:
keyPressedForGreenBox();
}
}
void keyPressedForStateMenu() {
switch(key){
case '3':
state = GreenBox;
break;
default:
// do nothing
break;
}
}
void keyPressedForGreenBox(){
switch(key) {
default:
state = stateMenu;
break;
}
}
void showMenu() {
background(255);
fill(0);
textSize(45);
//textFont(Amatic);
text(" Music Box ", 330, 250, 3);
textSize(14);
text("Press 3 for Green", 350, 350);
}
void handleGreenBox() {
Zon();
}
void Zon(){
background(255);
noStroke();
smooth();
// use frameCount to move x, use modulo to keep it within bounds
x = frameCount % width;
// use millis() and a timer to change the y every 2 seconds
if (millis() - timer >= 8000) {
y = random(height);
timer = millis();
}
// use frameCount and noise to change the red color component
r = noise(frameCount * 0.01) * 255;
// use frameCount and modulo to change the green color component
g = frameCount % 1;
// use frameCount and noise to change the blue color component
b = 255 - noise(1 + frameCount * 0.025) * 255;
// use frameCount and noise to change the radius
radius = noise(frameCount * 0.01) * mouseX;
color c = color(r, g, b);
fill(c);
ellipse(x, y, radius, radius);
}
Can someone help me fix this issue, please?
In your original code you're not clearing the background, but in the merged code version you are:
void Zon(){
background(255);
...
and you don't want that as you pointed out.
The only other issue is that you still need to call background(255), but just once, when you switch from the menu state to the GreenBox state:
void keyPressedForStateMenu() {
switch(key) {
case '3':
state = greenBox;
//clear the menu once when moving into greenBox
background(255);
break;
default:
// do nothing
break;
}
}
You've done well in identifying the issue and how solve it partially, just needed to clear the background when changing modes.
Your code will look like this:
final int stateMenu = 0;
final int greenBox = 3;
int state = stateMenu;
float x, y, r, g, b, radius;
int timer;
PFont font;
PFont Amatic;
void setup()
{
size(800, 700);
smooth();
//maybe loadFont ?
font = createFont("ARCARTER-78.vlw", 14);
textFont(font);
//Amatic = createFont("Amatic-Bold.ttf",60);
//textFont(Amatic);
frameRate(15);
}
void draw()
{
// the main routine. It handels the states.
// runs again and again
switch (state) {
case stateMenu:
showMenu();
break;
case greenBox:
handlegreenBox();
break;
default:
println ("Unknown state (in draw) "
+ state
+ " ++++++++++++++++++++++");
}
}
void keyPressed() {
// keyboard. Also different depending on the state.
switch (state) {
case stateMenu:
keyPressedForStateMenu();
break;
case greenBox:
keyPressedForgreenBox();
}
}
void keyPressedForStateMenu() {
switch(key) {
case '3':
state = greenBox;
//clear the menu once when moving into greenBox
background(255);
break;
default:
// do nothing
break;
}
}
void keyPressedForgreenBox() {
switch(key) {
default:
state = stateMenu;
break;
}
}
void showMenu() {
background(255);
fill(0);
textSize(45);
//textFont(Amatic);
text(" Music Box ", 330, 250);
textSize(14);
text("Press 3 for Green", 350, 350);
}
void handlegreenBox() {
Zon();
}
void Zon() {
//don't clear the buffer continuously if you want to leave trails
// background(255);
noStroke();
smooth();
// use frameCount to move x, use modulo to keep it within bounds
x = frameCount % width;
// use millis() and a timer to change the y every 2 seconds
if (millis() - timer >= 8000) {
y = random(height);
timer = millis();
}
// use frameCount and noise to change the red color component
r = noise(frameCount * 0.01) * 255;
// use frameCount and modulo to change the green color component
g = frameCount % 1;
// use frameCount and noise to change the blue color component
b = 255 - noise(1 + frameCount * 0.025) * 255;
// use frameCount and noise to change the radius
radius = noise(frameCount * 0.01) * mouseX;
color c = color(r, g, b);
fill(c);
ellipse(x, y, radius, radius);
}
If you want to be able to draw a background and have a "trail" drawn on top of that background, then you have two options:
Option 1: Create a data structure that contains everything you need to draw, and then draw everything in that data structure every frame. This could be as simple as an ArrayList<PVector>, or you might be better off creating your own classes that encapsulate everything you need to know in order to draw everything in a frame.
Option 2: Create a PGraphics that you draw your trail to. Then draw your background to the screen, and then draw that PGraphics to the screen. Info on this approach can be found in the reference.
Which approach you take really depends on you. I recommend putting together a little example that tests out each to see which one makes more sense to you. Then you can post an MCVE of that small example if you get stuck. Good luck.

Mouse click changing the screen face in OpenGL

I'm programming a code that when the user click on the screen It marks a point and when he continues clicking on other points on the screen It'll be linking the points with lines.
I don't know why but I'm working with two different screen plan. When I click with the mouse It marks a point in the screen that I only see when I'm with the left mouse button clicked and when the button isn't clicked I see a clean screen.
My code that marks the points and link them with lines:
void exerciseThree(int x, int y){
write("Exercise Three", -5, 18);
float cx = 0, cy = 0;
if(x != 0 && y != 0 && isFinished == false){
glPushMatrix();
glPointSize(6.0f);
//Draw the points
glBegin(GL_POINTS);
cx = ((x * (maxx - minx)) / width) + minx;
cy = (((height - y) * (maxy - miny)) / height) + miny;
glVertex2f(cx , cy);
glEnd();
if(lastCx != 0 && lastCy != 0){
glBegin(GL_LINE_STRIP);
glVertex2f(lastCx , lastCy);
glVertex2f(cx , cy);
glEnd();
}
lastCx = cx;
lastCy = cy;
glPopMatrix();
}
write("Press 0 (zero) to come back.", -10, -18);
}
The mouse function:
void mouse(int button, int state, int x, int y){
switch(button){
case GLUT_LEFT_BUTTON:
if( option == 3){
exerciseThree(x, y);
projecao();
glutSwapBuffers();
}
break;
}
}
I know that I should handle the GLUT_DOWN and GLUT_UP of the mouse, but does exist a way of work with only one screen face?
You're only seeing something on the screen when you click because that's the only time you are drawing to and updating the buffer. You should just update the list of x/y coordinates when the mouse is clicked. However, you should draw the points and call glutSwapBuffers() every time in your main loop so that they are always on screen regardless of a button press.
The flow should be something like the following pseudo code:
ArrayOfPoints[];
while(Running)
{
CheckForMouseInput(); // Update array of points to draw if pressed
DrawPoints(); // Use same code draw code from exerciseThree()
glutSwapBuffers(); // Update video buffer
}

Resources