HomeNewsFeaturesLicensingDownloadsScreenshotsFAQRoadmap Contact Us
Search:
8 Online

Community

Discussion Topics Recent Postings User Contributions General Articles Example Documentation Credits

Licensed Developer

Programmer's Manual Artists Manual Tutorials and Articles

Programming: Game Code

Game Code Overview Server Side Game Code Client Side Game Code

Programming: System

Alphabetical Function List Renderer File System Collision & Ray Casting Low Level Audio Game Audio The Console Console Variable List Multiplayer Localisation Maths Library Memory Manager Model File Formats Texture Formats

Art: Overviews

Specifications Shaders Particle Systems Lens Flares Cipher console Cipher file types Tutorials Reference

Art: Tools

Shader Designer Particle Designer 3dsmax tools Model Conversion Font Generator

Cipher Engine
Game Development Search Engine
GameDev.net
You are not signed in - [sign in] [register]

Screen picking using only cgame-side code

Overview

Author: mmelo
Date Submitted: 2004-07-12 08:28:51

Source Code

Code is inline in description


Description

This code snippet will allow to calculate the right direction to raycast enabling you to do picking inside Cipher.

Other ways of doing this thing were posted in the forums at one time or another but I somehow could never get them to work well: this came at one of those rare occasions in which I go on out-of-the-box-thinking binges so I ended up doing it using trigonometry. The present approach has an advantage over the others in that you do not have to change the renderer to export new stuff: this is a purely cgame-side implementations so you just plug in and be done with it, no need to add cipher_r_ScreenToWorld() to cg_local.h and code to the render dll.

This should be faster too (see clarification further down), as you are doing just a few multiplications between vectors and scalars as well as a couple vector adds rather than full blown matrix multiplications.

I put plenty of comments so you can follow what the maths are doing if you really want. I kept dependencies to a minimum as the code's intent is to be pluggable directly when you create a new project using the New Game Wizard that ships with Cipher.

Please note that this code does not include mouse reading code to find out its coordinates, it simply receives these values that you must source elsewhere: this subject is covered elsewhere (see songho?s post in http://www.cipherengine.com/discuss/viewthread.php?t=726 ) and I can attest that it does work because I use it myself.

To start off add the following members to struct cg_s in file cg_local.h


float fPixelOffX;
float fPixelOffY;


Now these members must be filled in by how much each pixel away from the centre (in each direction of the screen, horizontal and vertical will "bend" the camera forward vector, so we can calculate the right ray to cast each time. As you'll see in the actual picking code, we don't actually bend the forward (hence the quotes): we offset the tip of the forward vector along the Right and Up directions.

With this in mind, add the following lines at the very end of function cg_Setfov(void) in file cg_main.c

cg.fPixelOffX = com_tan( cg.view.fov_x / 2 * PI / 180.0f ) / (cg.videoinfo.width / 2);
cg.fPixelOffY = com_tan( cg.view.fov_y / 2 * PI / 180.0f ) / (cg.videoinfo.height / 2);


Now for the performance clarification: you'll notice a couple of fairly slow tangents here. The code above is indeed being run every frame, but this is for this tutorial's simplicity: you only need to recalculate these pixel offsets when either the field-of-view (FOV) or the resolution changes. I leave this optimisation to you.

And now the actual raycast/collision function (put it somewhere in cg_main.c). This receives the camera position and the mouse position and returns the hit result. Unfortunately the skeleton app that Cipher creates does not have the camera position accessible from all of the code so you will need to fix that: I suggest that as a quick hack you move the variable campos defined in function cg_Draw3D(void) to be global and pass that in. As mentioned previously mouse coords that you must obtain elsewhere need to be passed in too (it assumes that this coords reach the funcion in the habitual virtual 640x480 format i.e. between 0-640 and 0-480 regardless of actual screen resolution).

rbool DoPicking
(
vec3_t vCamPos,
float fMouseX,
float fMouseY,
hittest_t* pOutHit
)
{
float fDummy;
vec2_t vScreenPos;
vec3_t vScaledTemp;
vec3_t vRayDir;
vec3_t vEndOfRay;

/* Convert from mouse position from virtual 640x480 coords to actual coords */
vScreenPos[0] = fMouseX;
vScreenPos[1] = fMouseY;
cg_From640x480Rect( &(vScreenPos[0]), &(vScreenPos[1]) , &fDummy, &fDummy );

/* Ray direction starts being cam forward */
vec3_Copy( cg.view.axis[1], vRayDir );
/* Create a temp vec, pointing along cam right scaled by how far off from the centre we are horizontally */
vec3_Scale( cg.view.axis[0], cg.fPixelOffX * (vScreenPos[0] - cg.videoinfo.width/2), vScaledTemp );
/* Add this "right" component to our ray direction */
vec3_Add( vRayDir, vScaledTemp, vRayDir );
/* Create a temp vec, pointing along cam up scaled by how far off from the centre we are vertically */
vec3_Scale( cg.view.axis[2], cg.fPixelOffY * (cg.videoinfo.height/2 - vScreenPos[1]), vScaledTemp );
/* Add this "up" component to our ray direction */
vec3_Add( vRayDir, vScaledTemp, vRayDir );
/* The direction is done, just make the vector long enough to collide with what your scene needs */
/* my scenes are pretty small so I just threw in 100 as a magic number */
vec3_Scale( vRayDir, 100.0f, vRayDir );
/* Since cipher_RayCast (called bsp_MovingPointCollision() in the manual) requires an end point rather */
/* than a direction, we just add the camera position to our "long" raycast direction */
vec3_Add( vRayDir, vCamPos, vEndOfRay );

/* Call the raycast routine, in this case looking for ANY type of objects - hence the 0xffffffff mask */
com_zeromem( pOutHit, sizeof(hittest_t) );
cipher_RayCast( vCamPos, vEndOfRay, pOutHit, -1, 0xffffffff );
};


Of course that before anything can be picked/collided with you will need to add stuff to the scene (duh!).
You can add objects each frame using the function cipher_InsertCollisionEntity(). The above code will also collide against the BSP tree, if one is loaded: in this case pOutHit->entity will be 0 (zero) but pOutHit->t will be less than 1 (one). Be warned that if the camera is inside a wall (i.e. in the solid area of the BSP) cipher raycast somehow seems to stop there: it will not cross the wall, enter open space and then hit anything inside.

[Recent Contributions] [Recent Source Code]

User Contributed Comments

rikh 12th July, 2004 17:10
Cool


Iguana 12th July, 2004 18:24
That's what I call a tutorial.


philk 12th July, 2004 19:50
Hey mmelo, thats exactly what I am doing for entity picking. I precalculate some more values, but that is just optimisation. A good start is to replace all "value / 2.0" by "value * 0.5f". Its also important to remember to subtract the screenpos[1] from the view half height and not the other way arround :)


mmelo 13th July, 2004 09:11
@philk: Yeah, you are right... there are still a couple of optimisations that could be done. In the particular case of the divides by two in DoPicking I was not too worried because I believe the compiler will change them to a shift right by 1 because they are ints (hopefully they are not being cohersed to float until later - but I should check). ;-)

I am not sure, however, if in the second sentence you mean that, yes, I am doing it the right way or that, no, you believe it should be the other way around. Either way, for the benefit of our viewers (LOL), let me state that the vertical subtraction is the other way around from the horizontal one because screen Y grows along camera _down_ and not up (while horizontally screen already grows along camera right).
Edited 13th July, 2004 09:15


philk 13th July, 2004 12:51
@mmelo: Yes the code you presented is indeed right. I had it wrong first in my implementation.




Register and Sign In to discuss this article