getScreenVect + vector math tutorial

Hello, here is a little tutorial.

I feel that I already have to apologize, I made this little script and thought I’d explain it but it turned out to be quite long. I might be doable in a much simpler fashion but I like to do things myself. A bit math heavy and hard. You can always just download the .blend and try to understand it. You have been warned.

In my game I wanted to mark a spot on the ground behind some objects. Mouse over any sensor have no property filter so I looked at the new Rasterizer.getScreenVect() function. It was not as straight forward as I hoped but not that hard. All it takes is a little vector math.

getScreenVect did not, as I hoped it would, take coordinates on the screen and made a vector out of them. So I thought that I could use the mouse coordinates to find coordinates on the ground under the mouse. This only works for flat surfaces (it can be extended to cover more but that is a lot of work)

Okay I feel that I have to explain what a vector is, a vector is usually described as force and direction (english is not my first language so some of the terms might not be the standard). In 3d space which we will be using in blender it is represented by a x,y,z value. A vector can exist in any number of dimensions but 2d and 3d are most useful for game programming. The x,y,z values is the force/movement the vector has along the x,y,z axis. A vector has no position. They are graphically represented by arrows, pointing in to the the x,y,z coordinates they have.
http://upload.wikimedia.org/wikipedia/commons/8/87/Vector_components.svg

Back to getScreenVect, it makes a vector in the direction in 3d that hit everything “behind” a coordinate on the screen. It does not take x,y coordinates as I hoped. It takes the ratio of the coordinate. So 0.5,0.5 is the centre of the screen, 0.0,0.0 is the top left corner, 1.0,1.0 is the bottom right. But we can get the screen width and height so it isn’t a real problem. “Sens” here is a mouse movement sensor


cont = GameLogic.getCurrentController()
own = cont.owner
import Rasterizer
sens = cont.sensors["sensor"]
x = sens.position[0]
y = sens.position[1]
h = Rasterizer.getWindowHeight()
w = Rasterizer.getWindowWidth()
X = float(x)/float(w)
Y = float(h-y)/float(h)

The reason for the float() functions is because python does not like to divide integers. And the reason for h-y is that the functions starts to count from the opposite sides I think. It could be written as

1-float(y)/float(h)

Then we put that in the function

vect = Rasterizer.getScreenVect(X,Y)

Okay now we have the vector that hits everything under the mouse cursor. Now I want it to hit the ground, to do that we need some more vector maths.

A plane can be described in two way, either with parameters with is very messy. Or with normals. A normal is a vector pointing away from a face in a right angle (90 degrees or Pi/2 radians).
http://i36.tinypic.com/2uzcw9c.png
We can describe a plane as an equation, and every set of x,y,z coordinates that fits the equation is part of the plane.
http://resumbrae.com/ub/dms424_s05/10/planeEq1.jpg If we put the point 4,2,5 in the equation we get 4+02+05-2=0, or 4-2=0 which is not true, so the point is not in the plane. 2,6,1 then? 2+06+10-2=0, 2-2=0 yes. If we take away the coordinates with a 0 (since they will always be 0) we see that all is left is x=2. Every point with x-coordinates 2 is in the plane (if the plane is infinitely wide). The normal of the plane is the x-axis, or (1,0,0) and -2 defines where on the a-axis the plane is.

How does that help me? I know my ground has the normal (0,0,1), the z-axis, because the normal of the ground would point to the sky. And my plane lies at z=0 so my equation would be: 0x+0y+z=0, or z=0. Every point with z coordinate 0 is on the ground. So we have a vector which describes the direction from the camera, and we have a plane. We want to know where a line with the vector as direction crosses the plane. So if think of it like this, how many “steps” do I need to walk from the camera in the vectors direction to end up in the plane? only the z-direction matters in this case so if we want the cameras z-position plus n (the number of steps) multiplied with the vector to be in the plane, then camera.position[2] + n*vect[2] = 0, if we use some algebra


cam = cont.sensors["sensor2"].owner
n = -cam.position[2]/vect[2]

(sensor2 is a non-pulsing always sensor connected from the camera) Now we know our n, if we now want to place an object on the ground. We simple add n*vector to the cameras position.

own.position = [cam.position[0]+n*vect[0],cam.position[1]+n*vect[1],cam.position[2]+n*vect[2]]

The full script (put on the marker object with sensor2 coming from the camera)

cont = GameLogic.getCurrentController()
own = cont.owner
import Rasterizer
sens = cont.sensors["sensor"]
cam = cont.sensors["sensor2"].owner
x = sens.position[0]
y = sens.position[1]
h = Rasterizer.getWindowHeight()
w = Rasterizer.getWindowWidth()
X = float(x)/float(w)
Y = float(h-y)/float(h)
vect = Rasterizer.getScreenVect(X,Y)
n = -cam.position[2]/vect[2]
own.position = [cam.position[0]+n*vect[0],cam.position[1]+n*vect[1],cam.position[2]+n*vect[2]]

If you want a leaning ground will have to find out its normal and a new calculation for the n.
Okay not the simplest of tutorials, don’t feel stupid if you don’t understand it.

Attachments

vector.blend (195 KB)

Small note:
after 2.49RC2 we thought it would be better to move getScreenPos, getScreenRay and getScreenVect to the camera object.

Therefore in 2.49 final release your script will look like:

cont = GameLogic.getCurrentController()
own = cont.owner
import Rasterizer
sens = cont.sensors["sensor"]
cam = cont.sensors["sensor2"].owner
x = sens.position[0]
y = sens.position[1]
h = Rasterizer.getWindowHeight()
w = Rasterizer.getWindowWidth()
X = float(x)/float(w)
Y = float(h-y)/float(h)
vect = cam.getScreenVect(X,Y)
n = -cam.position[2]/vect[2]
own.position = [cam.position[0]+n*vect[0],cam.position[1]+n*vect[1],cam.position[2]+n*vect[2]]

I’m glad you found some use for this function btw :slight_smile:

That seems reasonable, especially if you use split screen or something I guess.
Thanks for the function!

Now THAT is ressource!

Fun, i have done the same script but for the plane z=1. If only I had seen the post before…

Anyway, well explained, and it’s a useful feature.

Now a question. You get weird results if the camera is moving? Cause I do and I don’t know why. I have my camera parented to an object that moves, when is static all is fine but when moving the numbers get crazy in the call to getScreenVect.

I also managed to restrict the object movement to some degrees around one specific object (the main character) and in a fixed range so the pointer will act as it has some constraints. It may be useful to you. You can shoot the other tank and the cube :wink:

Attachments

Aiming.blend (233 KB)

You should make an if-check for zeros, I’m getting some zero division errors in your file, other than that it looks cool.

I had some problems with the camera when it was parented, check the position you use, I had to add the the offset from the parent to the parents position to get correct results on my scene.

Back to getScreenVect, it makes a vector in the direction in 3d that hit everything “behind” a coordinate on the screen. It does not take x,y coordinates as I hoped. It takes the ratio of the coordinate. So 0.5,0.5 is the centre of the screen, 0.0,0.0 is the top left corner, 1.0,1.0 is the bottom right
Sim88: If we accept input non-normalized (running from 0.0 to 1.0) you may find troubles when changing the resolution of your screen/target audience. Although the output of Mouse sensor is in pixel, if you want to hard-code the center of the screen you wouldn’t be able to guess the correct pixel (720,450 here :)). Of course you could multiply it by the width/height, but normalized inputs make more sense for me …

Important thing:
I changed the coordinates to match Blender/BGE coordinate system (which runs from the top to the bottom). I was used to openGL (bottom-top) and forgot that Blender coordinate system is different.

So instead of
Y = float(h-y)/float(h)
you can do:
Y = float(y)/float(h)

It was committed in revision 20641 and will be part of Blender 2.49a.
**note there was a fix in getScreenRay fixed too and other for getScreenPosition and getScreenVect
Can you test in your files to see if they are working better now? (or if they stopped working?)

Yes this makes sense, there has to be conversion either way and this is probably the simplest.

Great, I will test it as soon as I can get a build.

Hi there,

I’m currently working on mouse input, too. About an hour ago I concluded that getScreenRay() is nice but useless. Of course it is not, but I wonder why it does not (even optionally) return the hitPosition and hitFacenormal etc. The application I have in mind is steering an adventure character to the position. If I got some tiny object as driving goal, every possible point I click on will yield the same results, but when I click just somewhere, (say a large wall) I need the hitPosition of the ray cast.
I’m merely just a starter on Blender and BGE, so I was not going to crawl into the SVN - but if someone finds my proposal usefull, maybe I will see it with 2.5? (wishful thinking)

greetz
-Tom

BTW: I’m not going about to try gamepad/joystick steering for an adventure. Gamedevs should’ve never gone that way and hopefully those responsible burn in hell :wink:

EDIT: Using the getScreenVect it’s actually quite easy to rayCast like you would with getScreenRay AND still get the position and normal where the ray hit the object. Here is a snippet of my testcode:

normx = float(sens.position[0])/Rasterizer.getWindowWidth()
normy = float(sens.position[1])/Rasterizer.getWindowHeight()

obj,hitPos,hitNorm = cam.rayCast(m.Vector(cam.worldPosition) - m.Vector(cam.getScreenVect(normx,normy)), None,1000,"shooter",1,1) 

I’ve attached a blend-file demonstrating this. In the scene you have a sphere. When you click on any point on the sphere it shoots out a projectile, facing away from the sphere’s center. Also you have a marker showing the point which you where clicking at.
Call it an extension to the topic of this thread :slight_smile:

Attachments

mouse_test.blend (173 KB)