Mouse Picking? with my Game Machine

Hi,

I need help with what im doing wrong with my mouse picking. Getting the required info from the cursors x,y pixels, to building a ray, to intersection with AABB’s ive spent quite abit of time on this and im not great at maths and this seems to be very hard so some help would great. Also you might be interested in checking out The Foxy Game Machine for future reference.

When i rotate the camera it messes up, it sortof works for non-rotations but then i have problems with AABB detection. The entire project has been recently released under license at www.fobbix.com/projects.php and www.fobbix.com/projects/foxy_game_machine.php. The important files to be viewed for this problem are… In Template Project - Game.cpp (Rotation wise), In FoxyEngine - MathGL.cpp, Math.cpp, Map.cpp (PickTile) and Detect.cpp (GetPickRay) along with there .h files. I could list the sourcecode here but since ive released it there i dont really see the point.

And also ive done quite abit with networking - berkeley sockets TCP/IP and UDP/IP , and generally i need to know how to interrupt a thread though? - even though not apparent here but id thought id ask anyway.

Thanks,

David

nobody has an answer?, do i need to post my sourcecode here?

Heres the code anyway…

//apply rotation in startup

			//set camera
			//camera pos needs to be half of screen
			e_camera->setFOV(90.0f);
			e_camera->setNear(0.1f);
			e_camera->setFar(100.0f);
///should be doing this in camera class
			e_engine->setPerspective(true);
			e_camera->setPerspective();
			//e_camera->setOrtho(&pos, &size);
			//e_camera->setDirection(RADIANS(45.0f), -0.5f, 0.0f, 1.0f);
			//e_camera->setPosition(20.0f, 8.0f, -2.5f);
			//e_camera->ApplyTransform(FOXY_TRANSLATION);
			//e_camera->Rotate(RADIANS(-45.0f), 0.0f, 0.0f, 1.0f);
			//e_camera->ApplyTransform(FOXY_ROTATION);
			e_camera->Rotate(RADIANS(-20.0f), 1.0f, 0.0f, 0.0f);
			e_camera->ApplyTransform(FOXY_ROTATION);

//GetPickRay

	void Detect::GetPickRay(Vector3 *pos, Vector3 *dir, float sx, float sy, float fov, float width, float height)
	{
		float d, aspect;
		Vector3 vp, vpos, poss;
		Vector3 unit_rot, rot;
		Vector3 *cpos;

		d = 1.0f / tanf(fov * PI / 360.0f);
		aspect = width / height;

		//get clip space
		vp.x = 2.0f * aspect * sx / width - aspect; //could try testing the aspect after but seems ok (with paper)
		vp.y = 2.0f * sy / height - 1.0f; //from -2.0f and + 1.0f since we invert our pixel position
		vp.z = -d;

		//transform point into world space
		e_math_gl->TransformPointByMatrix(&vpos, e_camera->m_invert_mv_space, &vp);

		//pos from camera eye
		cpos = e_camera->getPosition(); //local positions not from matrix (saved a vec3)

		pos->x = (-cpos->x);
		pos->y = (-cpos->y);
		pos->z = (-cpos->z);

		//get rotation
		/*static float rot_space[9];
		e_math_gl->ExtractRotationMatrix(rot_space, e_camera->m_model_space);//e_camera->m_rotation_space);
		e_math_gl->getFixedAnglesVector(rot_space, &rot);
		e_math->Normal(&rot, &unit_rot);*/

		//doesnt work when rotating camera, or is this just a visual artefact? (for rendering the test since i add 0.1 in Z) if so then AABB - Ray Intersection isnt working?
//without rotation AABB - ray only works for the bottom half of the screen, even though the pickray appears to be working correctly (and i can move the screen around without problems)
		e_math->Normal(&vp, dir);
	}

//PickTile

	int Map::PickTile(int x, int y)
	{
		/*static float offset;
		static sfuMapTilePickingClosest data_new, data_old;
		static Vector3 point_on_ray, ray_origin;

///this might be wrong
		//first get the original ray coordinates (camera position + cursor offset)
		/*if (x > 0)
			offset = (2 * (x - e_engine->m_map_space[12])) / e_engine->wnd_size.x - 1;
		else
			offset = 0;

		ray_origin.x = m_cam_coords->x + offset;

		if (y > 0)
			offset = (2 * (y - e_engine->m_map_space[13])) / e_engine->wnd_size.y - 1;
		else
			offset = 0;

		ray_origin.y = m_cam_coords->y + offset;
		ray_origin.z = m_cam_coords->z; //might need to inverse this?

///need to also store here the current angle of the camera
///could try and get away with using the radians m_angle thingy here
///could also pass that directly into the function
		//then get the current rotation of the camera

		///can just shove the first tile into data_old
		/*data_old.dist = e_math_gl->ClosestPointOnRay(&point_on_ray, &ray_origin, m_cam_direction, &(m_tile_coords[(int)((m_cam_coords->y * m_width) + m_cam_coords->x)]));
		data_old.tile = (int)((m_cam_coords->y * m_width) + m_cam_coords->x);

		for (i = m_cam_coords->y; i < m_cam_view->x; i++)
		{
			for (o = m_cam_coords->x; o < m_cam_view->y; o++)
			{
				data_new.dist = e_math_gl->ClosestPointOnRay(&point_on_ray, 
															 &ray_origin, 
															 m_cam_direction, 
															 &(m_tile_coords[(int)((i * m_width) + o)]));
				if (data_new.dist < data_old.dist)
				{
					data_old.dist = data_new.dist;
					data_old.tile = (int)((i * m_width) + o);
				}
			}
		}*/

		static Vector3 unit_ray, ray, pos, endray, point;
		static float distance;

		m_hover_bool_render = false;

//mainly just need the position could get the direction anyway, and that should be just
//x/y/z * w? but meh, could put the position at the center of the camera aswell, which is what we want anyway
//obv not doing this very good, remember ray is just the direction
		/* get tile from cursor position via picking */
		e_detect->GetPickRay(&pos, &unit_ray, x, y, e_camera->getFOV(), e_engine->wnd_size.x, e_engine->wnd_size.y);

		memcpy(&ray, &unit_ray, sizeof(Vector3));

		//need to get the length for the ray, (times by a scalar in this case distance)
		e_math->MultiplyVectorByScalar(&ray, e_camera->getFar());
		m_endpos.x = ray.x + pos.x;
		m_endpos.y = ray.y + pos.y;
		m_endpos.z = ray.z + pos.z;

#ifdef _DEBUG
		memcpy(&m_startpos, &pos, sizeof(Vector3));
		m_startpos.z -= 0.1f;

		//debug/render pick ray
		if (m_debug_first == true)
		{
			e_sb->Begin(GL_QUADS, 4, NULL);
				/*e_sb->AddPos(4.0f, 4.0f, -1.0f);
				e_sb->AddPos(4.1f, 4.0f, -1.0f);
				e_sb->AddPos(4.1f, 8.0f, -1.0f);
				e_sb->AddPos(4.0f, 8.0f, -1.0f);*/

///need to rotate these
				e_sb->AddPos(m_startpos.x, m_startpos.y, m_startpos.z);
				e_sb->AddPos(m_startpos.x + 0.1f, m_startpos.y, m_startpos.z);
				e_sb->AddPos(m_endpos.x, m_endpos.y, m_endpos.z);
				e_sb->AddPos(m_endpos.x + 0.1f, m_endpos.y, m_endpos.z);
			m_debug_sb = e_sb->End();
			m_debug_first = false;
		}
		else //modifying positions
		{
			e_sb->ReplacePos(m_debug_sb, 0, m_startpos.x, m_startpos.y, m_startpos.z);
			e_sb->ReplacePos(m_debug_sb, 1, m_startpos.x + 0.1f, m_startpos.y, m_startpos.z);
			e_sb->ReplacePos(m_debug_sb, 2, m_endpos.x, m_endpos.y, m_endpos.z);
			e_sb->ReplacePos(m_debug_sb, 3, m_endpos.x + 0.1f, m_endpos.y, m_endpos.z);
		}
#endif
		static float t;

		//check all map but filter later to the size
		//or 1000 boxes
		for (i = 0; i < m_length; i++)
		{
			//first get the closest point on ray then scale to that ray
			/*memcpy(&point, &m_endpos, sizeof(Vector3));
			t = e_math_gl->ClosestPointOnRay(&point, &pos, &unit_ray, &(m_bbox[i].min));

			///would project to inside the box (might need max and get the middle to calculate)
			//e_math_gl->ClosestRayOnRay(&endray, &pos, &unit_ray, &(m_bbox[i].min));
			//t = e_math->DistanceSquared3(&pos, &(m_bbox[i].min));
			
			memcpy(&endray, &unit_ray, sizeof(Vector3));
			endray.x *= t;
			endray.y *= t;
			endray.z *= t;*/

			//just need to see whether distance is correct?
			//memcpy(&ray, &unit_ray, sizeof(Vector3));
			//e_math->MultiplyVectorByScalar(&ray, e_camera->getFar());

			//first to collide
			if (e_detect->BoxIntersectRay(&(m_bbox[i].min), &(m_bbox[i].max), &pos, &unit_ray) == true)
			{
				// select this one

				int type;

				///the selection should be based on what we current have in the in tile, so need to keymap that and then set the highlight
				/*switch (m_in_tile.sprite_type)
				{
				case FOXY_SPRITE_ACTOR:
					type = i_actor->getType(m_in_tile.sprite_pos);
					m_hover_width = m_keymap_actors[type].width;
					m_hover_height = m_keymap_actors[type].height;
				break;

				case FOXY_SPRITE_BUILDING:
					//type = i_building->getType(m_in_tile.sprite_pos);
					m_hover_width = m_keymap_buildings[type].width;
					m_hover_height = m_keymap_buildings[type].height;
				break;

				case FOXY_SPRITE_OBJECT:
					//type = i_object->getType(m_in_tile.sprite_pos);
					m_hover_width = m_keymap_objects[type].width;
					m_hover_height = m_keymap_objects[type].height;
				break;
				}*/

				m_hover_width = 1;
				m_hover_height = 1;

				//store records in hover tile for rendering puposes and return the tile
				m_hover_tile_pos = i;

				m_hover_bool_render = true;

				return i;
			}
		}

		//loose, no tile selected
		return FOXY_INVALID;
	}

//Math

	void MathGL::InvertMatrix(float *inverse, float *in_m)
	{
		int i, j;
		float det, detij;

		// calculate 4x4 determinant
		det = 0.0f;
		for (i = 0; i < 4; i++)
		{
			det += (i & 0x1) ? (-in_m[i] * DetIJ(in_m, 0, i)) : (in_m[i] * DetIJ(in_m, 0,i));
		}

		det = 1.0f / det;

		// calculate inverse
		for (i = 0; i < 4; i++)
		{
			for (j = 0; j < 4; j++)
			{
				detij = DetIJ(in_m, j, i);
				inverse[(i * 4) + j] = ((i + j) & 0x1) ? (-detij * det) : (detij * det); 
			}
		}
	}

void MathGL::TransformPointByMatrix(Vector3 *result, float *mat, Vector3 *point)
	{
		result->x = (mat[0] * point->x) + (mat[4] * point->y) + (mat[8] * point->z) + mat[12];
		result->y = (mat[1] * point->x) + (mat[5] * point->y) + (mat[9] * point->z) + mat[13];
		result->z = (mat[2] * point->x) + (mat[6] * point->y) + (mat[10] * point->z) + mat[14];
 
		return;
	} 

//Detection

	//parametric ray test
	bool Detect::BoxIntersectRay(Vector3 *min, Vector3 *max, Vector3 *point, Vector3 *dir)
	{
		static bool inside;
		static int which;
		static float t;
		static float xt, yt, zt;
		static float xn, yn, zn;
		static float x, y, z;

		inside = true;

		if (point->x < min->x)
		{
			xt = min->x - point->x;
			if (xt > dir->x)
				return false;
			xt /= dir->x;
			inside = false;
			xn = -1.0f;
		}
		else if (point->x > max->x)
		{
			xt = max->x - point->x;
			if (xt > dir->x)
				return false;
			xt /= dir->x;
			inside = false;
			xn = 1.0f;
		}
		else
		{
			xt = -1.0f;
		}

		if (point->y < min->y)
		{
			yt = min->y - point->y;
			if (yt > dir->y)
				return false;
			yt /= dir->y;
			inside = false;
			yn = -1.0f;
		}
		else if (point->y > max->y)
		{
			yt = max->y - point->y;
			if (yt > dir->y)
				return false;
			yt /= dir->y;
			inside = false;
			yn = 1.0f;
		}
		else
		{
			yt = -1.0f;
		}

		if (point->z < min->z)
		{
			zt = min->z - point->z;
			if (zt > dir->z)
				return false;
			zt /= dir->z;
			inside = false;
			zn = -1.0f;
		}
		else if (point->z > max->z)
		{
			zt = max->z - point->z;
			if (zt > dir->z)
				return false;
			zt /= dir->z;
			inside = false;
			zn = 1.0f;
		}
		else
		{
			zt = -1.0f;
		}

		if (inside == true)
		{
			return true;
		}

		//Select farthest plane, this is the
		//plane of intersection

		which = 0;
		t = xt;

		if (yt > t)
		{
			which = 1;
			t = yt;
		}

		if (zt > t)
		{
			which = 2;
			t = zt;
		}

		switch (which)
		{
		case 0: //intersect with yz plane
			y = point->y + dir->y * t;
			if (y < min->y || (y > max->y))
				return false;

			z = point->z + dir->z * t;
			if (z < min->z || (z > max->z))
				return false;
		break;

		case 1: //intersect with xz plane
			x = point->x + dir->x * t;
			if (x < min->x || (x > max->x))
				return false;

			z = point->z + dir->z * t;
			if (z < min->z || (z > max->z))
				return false;
		break;

		case 2: //intersect with xy plane
			x = point->x + dir->x * t;
			if (x < min->x || (x > max->x))
				return false;

			y = point->y + dir->y * t;
			if (y < min->y || (y > max->y))
				return false;
		break;

		default:
			return false;
		}

		//if reach here then intersection
		return true;
	}

	/*bool Detect::BoxIntersectRay(Vector3 *bpos, Vector3 *bsize, Vector3 *point, Vector3 *dir)
	{
		static float maxS;
		static float minT;

		maxS = -FLT_MAX;//could be 0.0f
		minT = FLT_MAX;

		// do tests against three sets of planes

		//X plane
		// ray is parallel to plane
		if (dir->x == 0.0f)
		{
			// ray passes by box
			if ((point->x < bpos->x) || (point->x > bsize->x))
				return false;
		}
		else
		{
			static float s, t, temp;

			// compute intersection parameters and sort
			s = (bpos->x - point->x / dir->x);
			t = (bsize->x - point->x / dir->x);
			if (s > t)
			{
				temp = s;
				s = t;
				t = temp;
			}

			// adjust min and max values
			if (s > maxS)
				maxS = s;
			if (t < minT)
				minT = t;
			// check for intersection failure
			if ((minT < 0.0f) || (maxS > minT))
				return false;
		}

		//Y plane
		// ray is parallel to plane
		if (dir->y == 0.0f)
		{
			// ray passes by box
			if ((point->y < bpos->y) || (point->y > bsize->y))
				return false;
		}
		else
		{
			static float s, t, temp;

			// compute intersection parameters and sort
			s = (bpos->y - point->y / dir->y);
			t = (bsize->y - point->y / dir->y);
			if (s > t)
			{
				temp = s;
				s = t;
				t = temp;
			}

			// adjust min and max values
			if (s > maxS)
				maxS = s;
			if (t < minT)
				minT = t;
			// check for intersection failure
			if ((minT < 0.0f) || (maxS > minT))
				return false;
		}

		//Z plane
		// ray is parallel to plane
		if (dir->z == 0.0f)
		{
			// ray passes by box
			if ((point->z < bpos->z) || (point->z > bsize->z))
				return false;
		}
		else
		{
			static float s, t, temp;

			// compute intersection parameters and sort
			s = (bpos->z - point->z / dir->z);
			t = (bsize->z - point->z / dir->z);
			if (s > t)
			{
				temp = s;
				s = t;
				t = temp;
			}

			// adjust min and max values
			if (s > maxS)
				maxS = s;
			if (t < minT)
				minT = t;
			// check for intersection failure
			if ((minT < 0.0f) || (maxS > minT))
				return false;
		}

		// done, have intersection
		return true;
	}*/

//Initialization of map

	void Map::CreateTiles()
	{
	    if (m_tiles_loaded == true)
	    {
	        e_engine->Error("Called CreateTiles without first destroying the tiles");
	    }

        m_tile = new sfuTile[m_length];
		//m_tile_coords = new Vector3[m_length];
		m_bbox = new sfuMapTilesBoundingBoxes[m_length];
        m_wide_tile = new sfuWideTile[m_wide_length];

		memset(m_tile, 0, sizeof(sfuTile) * m_length);
		memset(m_wide_tile, 0, sizeof(sfuWideTile) * m_wide_length);

		e_camera->setMapScrollingBoundies(0.0f, 0.0f, -10.0f, (m_width * m_tile_width), (m_height * m_tile_height), 10.0f);

		Vector3 pos;
		Vector3 size;
		Vector3 direction;
		float tc[4];

		memset(&pos, 0, sizeof(Vector3));
		memset(&direction, 0, sizeof(Vector3));

		pos.z = -2.5f;

		size.x = m_tile_width;
		size.y = m_tile_height;
		size.z = 0.0f;

		tc[0] = 0.0f;
		tc[1] = 0.0f;
		tc[2] = 1.0f;
		tc[3] = 1.0f;

		int x, y;
		//work out the tiles
		for (y = 0; y < m_height; y++)
		{
			for (x = 0; x < m_width; x++)
			{
				m_tile[x + (y * m_width)].o_sb = i_sb->AddBillboard(&pos, &size, &direction, NULL, tc, FOXY_OPT_TEXS | FOXY_OPT_DIRS);
				
				//bounding box for picking
				memcpy(&(m_bbox[x + (y * m_width)].min), &pos, sizeof(Vector3));

				m_bbox[x + (y * m_width)].max.x = pos.x + m_tile_width;
				m_bbox[x + (y * m_width)].max.y = pos.y + m_tile_height;
				m_bbox[x + (y * m_width)].max.z = pos.z + 0.3f; //m_tile_zth_step;

///TODO: need a workout depth tile and a entire map func aswell, also add the m_bbox and get rid of m_tile_coords
///this is more for loading in or changing the depth value - would need to re-work these out
///the above however is perfect for creation
				//swap z values
				/*if (m_bbox[x + (y * m_width)].min.z > m_bbox[x + (y * m_width)].max.z)
				{
					temp = m_bbox[x + (y * m_width)].min.z;
					m_bbox[x + (y * m_width)].min.z = m_bbox[x + (y * m_width)].max.z;
					m_bbox[x + (y * m_width)].min.z = temp;
				}*/
//remember
//ensure we dont overstep the bounds of the map, need to figure that out later (should draw one less aswell but perhaps add one more above to solve the problem)

				pos.x += m_tile_width;
			}

			pos.x -= m_width;
			pos.y += m_tile_height;
		}		
	}

Anyway i think ive got it all there.