Archived

This topic is now archived and is closed to further replies.

Correct image swapping onMouseOver with alpha

This topic is 5143 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I''m working on my UI system, and I can currently show alphablended quads, and correctly flip this texture on mouse over. However, this flipping takes place when the mouse comes over the quad, not the visible texture. So what I really need to do is (I think) first detect if the cursor is within the quad, then calculate the position on the texture, then get the alpha value for that point. If alpha>certainValue do the swap of the image. Normally I dump my image data once it''s been passed to openGL, so I can''t easily get the alpha value. I could make a kind of hotspot on the quad, but this requires extra work (not that I''m opposed to that mind you), but it would be much more convinient if I could just use tha alpha. What other possibilities could be used to do this?

Share on other sites
You can get the X pixel out of the GL context IIRC, but I also remember its quite slow, why not make your quads smaller, or the texture fit to them exactly or are we talking a curvy interface here?

Share on other sites
I also seem to recall that being ''slow''. And yes, we are talking curvy interface here

Share on other sites
Hmm bit of a tricky one :| If it was fixed size then I would day just create a hotspot bitmap and use a function (seperate from GL) to test that, but it depends on how the texture has been displayed and scaled on your context. Im sure it can be done.. Some how.

Share on other sites
any other takers?

Share on other sites
This is exactly my problem too, so I can''t give any solutions, but this is what I''ve read so far - I''ve just started with openGL so this might be very basic or downright wrong, but for what it''s worth:

You could assign a unique color to every object near the mouse click, render that small area to the backbuffer only and use glReadPixels to get the pixel under the mouse. I guess it would slow things down a bit, but not as much as keeping a separate bitmap, would it?

You''d have to disable everything that modifies the color, like lighting and dithering and use an alpha test to "quantize" the alpha (e.g. everything below .5 is considered transparent) in order to get flat colors.

The thing I can''t figure out is how to render a quad with the alpha from the texture and a solid color instead of the image data without generating a new texture, but my guess (and hope...) is that it should be doable.

Anyone?

If you find a good solution to this on your own, please post it.

Share on other sites
Well, so far as I know, reading from the videocard is very slow. So my best solution is storing a imagemap with 1byte (or even 1 bit) per pixel. The when determining if the mouse is ''over'' just check that ''texture'' map.

If you use 1 bit per pixel (so 8 pixels per byte) then an image of 128x64 (which seems to me to be a nice size for a button image) could be stored as a 1 kByte array. So the overhead isn''t too large.

Share on other sites
Oh, ok that would probably work fine, as long as you have no scaling or perspective - which you don''t I suppose since it''s an interface. If nothing moves or changes much except for texture flipping, wouldn''t it be easier to build one array in memory with index colors i.e. if array[10,10] = 20 then it''s button 5. Then you can skip picking all together?

My problem is that stuff is moving and scaling on screen (it''s a very light tile/sprite 2D engine) and I still need to pick based on the alpha. Reading from the screen buffer can''t be THAT slow, can it? I just have to render a very minimal sceen area and need only read one pixel.

Anyway, I think I''ve figured out a (clumsy) way of doing it so I''ll try - if you''re interested I''ll let you know how it worked out.

Share on other sites
hmm, I''m not sure what you mean with that array. Could you explain that with a bit more detail?

I''m not sure about the reading speed of a single pixel. You could try it. And yes please do let me know how it works.

It''s true that my idea works better for static object (UI elements) and less well for moving objects I think.

Share on other sites
Personally, I would just keep a copy of the texture in RAM. If you can''t for some reason, your can probably use low-threshold alphatest and stencil buffer (set the stencil bits to an id number that''s unique to each window). Reading just one pixel shouldn''t be that slow. This can of course conflict with any other use of stencil in your program, ie. you might have to clear it multiple times per frame.

Share on other sites
Regarding the array - it''s an offscreen image really, with the same dimensions as your viewport, so if your viewport is 400x400 you do char myArray[400][400].

When you add a textured quad to the screen, you also assign a unique "color" (0-255) to it. Then add that "color" in the same pixels in the array as it is on the screen using the alpha from the texture.

After that you don''t have to keep the textures, nor do you really have to keep track of the quads - if you get a mouse click at position (20,20) - just get which "color" is stored in that position in the array and look up which object it belongs to.

Note that this is really only useful if you have a more or less static interface (you don''t want to move around those pixels in main memory more than once),
and an orthographic view with a 1:1 relation between pixel and 3D coordinates. (Although I suppose it could work with some work in other cofigurations)

Share on other sites
Ok, I understand now. Thanks for the clarification. Please keep me updated on how it goes. I''ll do the same.

Share on other sites
Well, today I implemented my solution: keeping a seperate array of bools, one for each texel.

It seems to work as planned, but I need to test some more.

Share on other sites
And I got mine to work too... I''m glad to say that the performance is enough for me, typically the whole lookup process took around 2-4 milliseconds which is totally acceptable for me. And my guess is that I''m doing this all wrong, so it could probably be optimised a lot once I get to know OpenGL a bit more. But for now it''s good enough for me.

Share on other sites
Seems we've got the classic example that a problem has more than one solution

I was wondering, does it take 2-4 ms for each lookup? And the lookup happens when you click? So that system wouldn't support mouseOvers easily would it? Since then it would have to lookup each frame.

Would you be willing to post the lookup code?

I've posted mine below here:

Creating the array with data.
if (strstr(out->filename.c_str(), ".tga")){	// We need to create the map for testing the mouse	unsigned char* data = 0;	data = materialManager.GetData(out, &texWidth, &texHeight);	overMap = new bool[texWidth*texHeight];	for (int i=0; i<texWidth*texHeight; ++i)	{		if (data[4*i+3]>0)			overMap[i] = true;		else			overMap[i] = false;	}}

Using the lookup table to get in or out:
   bool CUINode::Contains(float x, float y){	if ( x>=left && x<=(left+width) && y<=top && y>=(top-height) )	{		bool contained;		// The coordinate is within the texture range. Now query the overMap, if		// it exists, to accomodate alpha channels		// First normalize the mouseposition		if (overMap)		{			float mouseX = (x - left)/(float)width;			float mouseY = (y - (top-height))/(float)height;			int texelX = mouseX*texWidth;			int texelY = mouseY*texHeight;			if ( overMap[texelY*texWidth+texelX] )				contained = true;			else				contained = false;		}		else			contained = true;		if (contained)		{			bool isContainedInChild = false;			std::list<CUINode*>::iterator it = children.begin();			while (it!=children.end())			{				if ((*it)->Contains(x,y))					isContainedInChild = true;				it++;			}			if (!isContainedInChild && visible && materialStructOver)			{	// This is the lowest level that contains this position				materialStruct = materialStructOver;			}			return true;		}	}	if (visible && materialStructOut)	{		materialStruct = materialStructOut;	}	return false;}

[edited by - rick_appleton on January 19, 2004 5:07:56 AM]

Share on other sites
Two solutions, but I think yours is best for you and mine is best for me :-)

"I was wondering, does it take 2-4 ms for each lookup?"

Yeah, it did, but I looked into it a bit and easily got it down to around 0.4- 0.6 ms on quite modest harware, which should be fair. You could probably do better than that.
Here''s the code, with one big honkin'' disclaimer: I''m new to this and even quite new to C++ so for what it''s worth. Comments are very welcome.

Here''s the main function which sets up the scissor draws and checks the result
Note that the backbuffer is dirty after this call.

unsigned long Scene::Pick(PointF rScreenLoc, long rMode){	if (!inited)		return 0;		PointF	tSceneLoc;		tSceneLoc.Set(rScreenLoc);	tSceneLoc.Add(screenRect.left, screenRect.top);	if (!tSceneLoc.IsInside(screenRect))		return 0;			// Reset the list used to store the relation between object and color	pickList.Reset();	pickColor = 1; // 0 = transparent	RectF	tScreenScissor;	// Set the screen scissor to be used with OpenGl Scissor test	tScreenScissor.Set(rScreenLoc, rScreenLoc);	tScreenScissor.Add(-2.0, -2.0, 2.0, 2.0);		// Set a scene scissor, any scene rect not interecting this shouldn''t be drawn	sceneScissor.Set(tScreenScissor);	sceneScissor.Add(screenRect.left, screenRect.top);			glEnable(GL_SCISSOR_TEST);		// The scene is divided into regions to minimize update		// Test the regions for intersect, start with first in linked list	Region* tDrawRegion = regionHead;	while (tDrawRegion)	{		// If the mouse click is inside this drawRegion		if (tSceneLoc.IsInside(tDrawRegion->sceneRect))		{			glScissor((GLint) tScreenScissor.left , (GLint) (viewportRect.Height() - tScreenScissor.bottom) , (GLsizei) tScreenScissor.Width() , (GLsizei) tScreenScissor.Height() );			// clear the region    		glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			tDrawRegion->DrawPicker(rMode);			tDrawRegion = 0; // Break out - draw only the region with the point inside		}		else		{			tDrawRegion = tDrawRegion->next;		}		};		// Done drawing, now get the color from the backbuffer and compare with the list of 	// drawn objects.		PointF	tGlLoc;	unsigned long tColor;		tGlLoc.x = rScreenLoc.x;	tGlLoc.y = viewportRect.Height() - rScreenLoc.y;		// Get the color from the backbuffer	glReadBuffer(GL_BACK_LEFT);	glReadPixels((GLint) tGlLoc.x , (GLint) tGlLoc.y , 1 , 1 , GL_BGRA , GL_UNSIGNED_INT_8_8_8_8_REV, &tColor);	tColor = (tColor & 0xFFFFFF);		// Debug	// aglSwapBuffers (glCtx);	// Look it up in the pick list		unsigned long tScenePropIndex = 0;	// Normally, only two or three objects are drawn so I didn''t bother with 	// sorting, just loop through the list and compare	Sprite3d*	tTestSprite = (Sprite3d*) pickList.GetAt(0);	while (tTestSprite)	{		if (tTestSprite->pickColor == tColor)		{			// Found			tScenePropIndex = tTestSprite->sceneProp->index;			tTestSprite = 0;		}		else			// Check next			tTestSprite = (Sprite3d*) pickList.GetNext();	};			// Return the id of the picked object	return tScenePropIndex;	}

The scene is my entire scene which is pretty big, the screen is which part you actually see in the viewport, the scene is divided into regions to easily update parts of it. Don''t worry about the "sceneProp" stuff - doesn''t make sense out of context.

And here is the drawing routine, of which I''m pretty uncertain, as I seem to recall that the DST_ALPHA is not supported on many cards under Windows (I''m on Mac) There is probably a single pass way of doing this.

void Sprite3d::DrawPicker(long rMode){	// Skip the drawing if this sceneProp isn''t "pickable"	if ((rMode == PICK_STANDARD) && (!sceneProp->pick))		goto skip_draw;	// Only draw if this sprite intersects the scene scissor		if (!currentScene->sceneScissor.DoesIntersect(sceneProp->sceneRect))		goto skip_draw;			glDisable(GL_BLEND);                                                     	// Disable Blending (enable alpha testing)	// Set alpha blending to quantize the fuzzy edges (everything below .5 is transparent)	glAlphaFunc(GL_GREATER,0.5f);                                           // Set Alpha Testing     (disable blending)	glEnable(GL_ALPHA_TEST);                                                // Enable Alpha Testing  (disable blending)	glEnable(GL_TEXTURE_2D);                                                // Enable Texture Mapping	glBindTexture (GL_TEXTURE_2D, sceneProp->glTexture);	glColor4f (1.0f, 1.0f, 1.0f, 1.0f);			// set the quad to white 	// Write only to the alpha buffer		glColorMask(GL_FALSE , GL_FALSE , GL_FALSE , GL_TRUE );	// Draw the texture to the alpha buffer	glBegin (GL_QUADS);		glTexCoord2f (0.0, 0.0);		glVertex3f(sceneProp->glRect.left, sceneProp->glRect.bottom, 0.0f);                          // Bottom Left					glTexCoord2f (0.0, 1.0);		glVertex3f(sceneProp->glRect.left, sceneProp->glRect.top, 0.0f);                          // Top Left		glTexCoord2f (1.0, 1.0);		glVertex3f(sceneProp->glRect.right, sceneProp->glRect.top, 0.0f);                          // Top Right		glTexCoord2f (1.0, 0.0);		glVertex3f(sceneProp->glRect.right, sceneProp->glRect.bottom, 0.0f);                          // Bottom Right				glEnd ();		glDisable(GL_ALPHA_TEST);	glEnable(GL_BLEND);	// Use the target alpha to cut out the silhouette from the solid	glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);                      // Enable Alpha Blending (disable alpha testing)		glDisable(GL_TEXTURE_2D);                                                // Disable texture		currentScene->pickColor += 1; // ++;	// Generate a unique color	pickColor = currentScene->pickColor;	currentScene->pickList.Add(this);	// Add it to the list of objects to be picked		glColor4ub ( ((unsigned char) ((pickColor & 0xFF0000) >> 16)), ((unsigned char) ((pickColor & 0xFF00) >> 8)), ((unsigned char) (pickColor & 0xFF)), 0);			// set the quad to the unique color	glColorMask(GL_TRUE , GL_TRUE , GL_TRUE , GL_TRUE );		// Redraw the quad with the solid only	glBegin (GL_QUADS);		glVertex3f(sceneProp->glRect.left, sceneProp->glRect.bottom, 0.0f);                          // Bottom Left					glVertex3f(sceneProp->glRect.left, sceneProp->glRect.top, 0.0f);                          // Top Left		glVertex3f(sceneProp->glRect.right, sceneProp->glRect.top, 0.0f);                          // Top Right		glVertex3f(sceneProp->glRect.right, sceneProp->glRect.bottom, 0.0f);                          // Bottom Right				glEnd ();skip_draw:		if (next)		next->DrawPicker(rMode);}