Correct image swapping onMouseOver with alpha

Started by
14 comments, last by rick_appleton 20 years, 3 months ago
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)

Advertisement
Ok, I understand now. Thanks for the clarification. Please keep me updated on how it goes. I''ll do the same.
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.
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.
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]
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);}

This topic is closed to new replies.

Advertisement