Perhaps better "world" interaction with mouse

Discuss programming topics for the various GPL'd game engine sources.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Perhaps better "world" interaction with mouse

Post by Baker »

Thanks to some pointers from various usual suspects, I was able to make it so map surfaces can be selected in game (haven't taken the time to look at LordHavoc's suggestion about throwing brush models and other entities into an array, so it only works on "world surfaces" not doors and lifts and platforms, etc).

But what about selecting entities like monster or a gun? Well ... this has probably been done in DarkPlaces or FTEQW, I have a very hard time imagining it hasn't been done.

So raw thoughts on selecting entities with the mouse ...

1) Need to translate screen mouse coordinates to client area mouse coordinates. On Windows, that goes like this ...

Code: Select all

POINT		current_pos;
int client_x, client_y;
GetCursorPos (&current_pos);
ScreenToClient(mainwindow /* HWND global in Quake */, &current_pos);

client_x = current_pos.x;
client_y = current_pos.y;
2) Need to translate 3D coordinates to our screen coordinates. gluProject and gluUnProject are our friends here. But rather than bloat up stuff, better to borrow from FTEQW:

Code: Select all

int qglProject (float objx, float objy, float objz, float *model, float *proj, int *view, float* winx, float* winy, float* winz) {
	float in[4], out[4];
	int i;

	in[0] = objx; in[1] = objy; in[2] = objz; in[3] = 1.0;


	for (i = 0; i < 4; i++)
		out[i] = in[0] * model[0 * 4 + i] + in[1] * model[1 * 4 + i] + in[2] * model[2 * 4 + i] + in[3] * model[3 * 4 + i];


	for (i = 0; i < 4; i++)
		in[i] =	out[0] * proj[0 * 4 + i] + out[1] * proj[1 * 4 + i] + out[2] * proj[2 * 4 + i] + out[3] * proj[3 * 4 + i];

	if (!in[3])
		return 0;

	VectorScale(in, 1 / in[3], in);


	*winx = view[0] + (1 + in[0]) * view[2] / 2;
	*winy = view[1] + (1 + in[1]) * view[3] / 2;
	*winz = (1 + in[2]) / 2;

	return 1;
}
3) In order to use glProject/glUnproject, we need the projection matrix, the modelview matrix and the viewport. There is probably an equivalent in FTEQW that is renderer neutral. Might look for that later.

Code: Select all

	glGetFloatv(GL_MODELVIEW_MATRIX, model);
	glGetFloatv(GL_PROJECTION_MATRIX, project);
	glGetIntegerv(GL_VIEWPORT, view);
4) We'll need to figure out some way to determine if an entity is visible on-screen. Playing around with MH's camera fix stuff can be a component of this. Probably have to do some fiddling around with Z sorting of candidates (actually Y sorting in Quake, since Quake makes the Z axis "vertical").

Code: Select all

 qbool Still_Visible (vec3_t checkpoint, int viewcontents)
{
   int i;
   vec3_t mins;
   vec3_t maxs;

   // check against world model
   if ((Mod_PointInLeaf (checkpoint, cl.worldmodel))->contents != viewcontents)
      return false;

   // check visedicts - this happens *after* CL_ReadFromServer so the list will be valid
   for (i = 0; i < cl_numvisedicts; i++)
   {
      // retrieve the current entity
      entity_t *e = cl_visedicts[i];

      // don't check against self
      if (e == &cl_entities[cl.viewentity])
		  continue;

	  // don't check against players
	  if (e->modelindex == cl_modelindex[mi_player])
		  continue;

      // derive the bbox
      if (e->model->type == mod_brush && (e->angles[0] || e->angles[1] || e->angles[2]))
      {
         // copied from R_CullBox rotation test for inline bmodels, loop just unrolled
         mins[0] = e->origin[0] - e->model->radius;
         maxs[0] = e->origin[0] + e->model->radius;
         mins[1] = e->origin[1] - e->model->radius;
         maxs[1] = e->origin[1] + e->model->radius;
         mins[2] = e->origin[2] - e->model->radius;
         maxs[2] = e->origin[2] + e->model->radius;
      }
      else
      {
         VectorAdd (e->origin, e->model->mins, mins);
         VectorAdd (e->origin, e->model->maxs, maxs);
      }

      // check against bbox
      if (checkpoint[0] < mins[0]) continue;
      if (checkpoint[1] < mins[1]) continue;
      if (checkpoint[2] < mins[2]) continue;
      if (checkpoint[0] > maxs[0]) continue;
      if (checkpoint[1] > maxs[1]) continue;
      if (checkpoint[2] > maxs[2]) continue;

      // point inside
      return false;
   }

   // it's good now
   return true;
}

qbool CL_Visible_To_Client (vec3_t viewer, vec3_t seen)
{
   // calculate distance between chasecam and original org to establish number of tests we need.
   // an int is good enough here.:)  add a cvar multiplier to this...
   int num_tests = (sqrt ((viewer[0] - seen[0]) * (viewer[0] - seen[0]) +
               (viewer[1] - seen[1]) * (viewer[1] - seen[1]) +
               (viewer[2] - seen[2]) * (viewer[2] - seen[2])));

   // take the contents of the view leaf
   int viewcontents = (Mod_PointInLeaf (viewer, cl.worldmodel))->contents;
   int best;

   // move along path from viewer to seen
   for (best = 0; best < num_tests; best++)
   {
      vec3_t step_to_entity;

      step_to_entity[0] = viewer[0] + (seen[0] - viewer[0]) * best / num_tests;
      step_to_entity[1] = viewer[1] + (seen[1] - viewer[1]) * best / num_tests;
      step_to_entity[2] = viewer[2] + (seen[2] - viewer[2]) * best / num_tests;

      // check for a leaf hit with different contents
      if (!Still_Visible (step_to_entity, viewcontents))
      {
			return false;
      }
	}

   return true;
}
5) Then step 5 would be to take the closest matching entity (via "Z" testing ... errr.. well Quake so it's Y testing) to the viewpoint and highlight it in some manner. Like red shade it, draw lines around the bounding box or whatever.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Lardarse
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Post by Lardarse »

PRYDON_CLIENTCURSOR (as documented in dpextensions.qc) is another way of doing this.
Roaming status: Testing and documentation
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Lardarse wrote:PRYDON_CLIENTCURSOR (as documented in dpextensions.qc) is another way of doing this.
Thanks!

Hehe, well it has been 3 years or so since I played Prydon :P

/So EF_SELECTABLE ...

Code: Select all

float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent)
{
	float maxfrac, maxrealfrac;
	int n;
	entity_render_t *ent;
	float tracemins[3], tracemaxs[3];
	trace_t trace;
	float tempnormal[3], starttransformed[3], endtransformed[3];

	memset (&trace, 0 , sizeof(trace_t));
	trace.fraction = 1;
	trace.realfraction = 1;
	VectorCopy (end, trace.endpos);

	if (hitent)
		*hitent = 0;
	if (cl.worldmodel && cl.worldmodel->TraceBox)
		cl.worldmodel->TraceBox(cl.worldmodel, 0, &trace, start, vec3_origin, vec3_origin, end, SUPERCONTENTS_SOLID);

	if (normal)
		VectorCopy(trace.plane.normal, normal);
	maxfrac = trace.fraction;
	maxrealfrac = trace.realfraction;

	tracemins[0] = min(start[0], end[0]);
	tracemaxs[0] = max(start[0], end[0]);
	tracemins[1] = min(start[1], end[1]);
	tracemaxs[1] = max(start[1], end[1]);
	tracemins[2] = min(start[2], end[2]);
	tracemaxs[2] = max(start[2], end[2]);

	// look for embedded bmodels
	for (n = 0;n < cl.num_entities;n++)
	{
		if (!cl.entities_active[n])
			continue;
		ent = &cl.entities[n].render;
		if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs))
			continue;
		if (!ent->model || !ent->model->TraceBox)
			continue;
		if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer)
			continue;
		// if transparent and not selectable, skip entity
		if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST))))
			continue;
		if (ent == ignoreent)
			continue;
		Matrix4x4_Transform(&ent->inversematrix, start, starttransformed);
		Matrix4x4_Transform(&ent->inversematrix, end, endtransformed);
		Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, SUPERCONTENTS_SOLID, 0, NULL);
		if (maxrealfrac < trace.realfraction)
			continue;

		//if (ent->model && ent->model->TraceBox)
			ent->model->TraceBox(ent->model, ent->frameblend[0].frame, &trace, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID);

		if (maxrealfrac > trace.realfraction)
		{
			if (hitent)
				*hitent = n;
			maxfrac = trace.fraction;
			maxrealfrac = trace.realfraction;
			if (normal)
			{
				VectorCopy(trace.plane.normal, tempnormal);
				Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal);
			}
		}
	}
	maxfrac = bound(0, maxfrac, 1);
	maxrealfrac = bound(0, maxrealfrac, 1);
	//if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__);
	if (impact)
		VectorLerp(start, maxfrac, end, impact);
	return maxfrac;
}
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Post Reply