Constraining a billboard "pointing" image inside the screen

I’m getting weird results and would like more info on getScreenCoordinates(…).



What’s the defined returned values behavior of the above when the queried location is behind the camera? At 90, 45 degrees?



I’m taking a guess here that it’s undefined.



When the location is inside or close to the screen borders the values seem fine, but as I rotate the camera and the location moves behind me, results get funky.



A sample of some results. These were consecutive values.



[java]

Screen X Position = 285704.84

Screen X Position = 285704.84

Screen X Position = 784871.8

Screen X Position = 6285203.0

Screen X Position = -481937.66

Screen X Position = -313071.28

Screen X Position = -313071.28

[/java]

Well, this might be correct. In theory going up to 90° off you are going towards infinity. The question you ask by invoking the method is “Where does a ray from the camera to this point cross the screen?”

Maybe this clarifies (yes, I am an artist!):

Yeah. That’s the behavior I’m getting. The problem is as follows:



The values printed I’m interested in are those when the z is negative, thus the location is behind the camera.



To properly understand the schema imagine the image is the camera view as you look at a spatial. The numbers represent the direction you would be looking at if you were rotating the camera around the spatial.



#1 - All is fine at this point, the values reported are > screen width.

From #1 to #2 - At or around 90 degrees, values switch to positive numbers to negative numbers and descending as we go toward #2.

#2 - Values go back to positive numbers. Normal progression from last step. The queried location is “inside the screen” but behind the camera.

From #2 to #3 - Values increase (positive) but they should be negative since they’re on the “left” of the screen’s origin.

#3 - Here at about 90 degrees values go from positive values to big negative values until the location comes back inside the “viewport” where it becomes 0.



There are a couple of locations where numbers seems to jump from positive to negative then back again to positive, but the might be my eyesight, things are going pretty fast. I didn’t push farther than that to check, but it’s something I might test…



http://www.disenthral.com/files/screencoords.png

I’m still trying to understand why you want to get hypothetical screen coordinates for something that isn’t on the screen. Can you explain better?



There is probably another way to do what you want. I think asking for something that doesn’t exist is probably going to be unpredictable in the best of cases.

I don’t think I understand… For the deduction of the value the screen is an infinite plane, so you might get coords that are outside your “real” screen, like the ray I painted that goes through the red x at the bottom, it would cross the screen at some x coordinate way above the screen size. (its a top down view)

What I’m actually trying to achieve is have a “pointer” image that will, well point, to the ships in the area. But as it works now, it jumps from one side of the screen to the other, goes up and down when I reach the problematic areas above.



It’s not a huge deal, but I’d love it if I could get it to be smooth and accurate. As with #2 above, following what’s happening, the pointer will show the wrong direction. I guess I could nest lots of ifs…

Or just calculate off screen coordinates a more reliable (read: sensible) way.

There’s nothing convoluted with the basic check. It goes like this:



[java]



private void updatePointer(Vector3f screenLocation) {

float x = constraintX(screenLocation.x, screenLocation.z);

float y = constraintY(screenLocation.y, screenLocation.z);



pointer.setPosition(x - 15, y + 20);

}



private float constraintX(float x, float z) {

if (x - 15 < 0) {

return 18;

} else if (x + 15 > width) {

return width - 12;

} else {

return x;

}

}



private float constraintY(float y, float z) {

// lower than bottom of screen

if (y + 20 < 0) {

return -20;

} else if (y + 60 > height) {

// higher than top of screen

return height - 60;

} else {

// inside screen

return y;

}

}

[/java]



The thing is, if the location ends up behind the camera the pointer is displayed in the middle of the screen. That’s the main thing I want to avoid.

That thing only works when its 20 larger, not 40? It doesnt constrain.

The above is fine. That’s used when the location vector is “in front” of the camera.



The problem is, as I rotate the camera around the ship (the player’s ship), the other ship I want to point to goes behind the camera, the location vector’s screen coordinates are reported as being positive (both x and y) so the pointer is in the middle of the screen even if the ship it’s pointing to is behind the camera.

I’d probably do this with dot products and a few vectors, personally. I’d consider the values off screen or behind the camera to be “undefined”. It’s neat that you get values but they don’t really have to make sense.



Also, since they are projected onto a flat plane, any direction indicators (which I assume is the point) could be kind of inaccurate (though they would be for the axis dot product method, too, though not as bad).



If you dot with a forward, up, and left vector then you get everything you need to know to figure out which part of the border of the screen to draw the markers when they are not in view.

Well… I won’t have to do dot products or anything too complex, although it might be interesting to compare note on speed in the end.



The main problem is that when the location you’re trying to get a screen coordinate for is behind the camera, the returned value is the inverted mirrored image of the real value!



For example. If X is 10, the reverse will be width - 10. Effectively, it looks like this:





http://www.disenthral.com/files/screencoords2.png



The same applies to Y axis.



Right now the X axis is “fixed” (when z is negative or > 1.0f) and seemingly working. I say seemingly because I haven’t worked on the Y axis yet, but it looks right. Anyway it gives this:



[java]

// x being the x screen coordinate of the vector3 location (spatial).

// I haven’t simplified the If yet…

} else {

// Spatial is behind us.

float w1 = width - x - 30 < 0 ? 0 : width - x - 30;

float w2 = x + 30 > width ? width - 30 : w1;

w2 = w2 > width ? width - 30 : w2 - 30 < 0 ? 0 : width - x - 30;

if (w2 >= width) {

return width - 30;

} else if (w2 <= 0) {

return 0;

} else {

return w2;

}

}

[/java]

You know you can tell if something is behind you by taking the dot product of the camera’s direction with the position relative to the camera?



relative = somePos.substract(cameraPos);

dot = cameraDir.dot(relative);



dot is then the distance from camera along the direction vector. Positive is in front of you, negative is behind you.



You could do the same thing for up/down and side-to-side… but even the front-back thing could be useful for what you are doing.

Mwahahah! It’s ALIVE! :wink:



It works without failure. :smiley:


@pspeed said:
You know you can tell if something is behind you by taking the dot product of the camera's direction with the position relative to the camera?


Z negative or > 1 also. Any particular reason why I should use trigonometry instead of just checking range? I would imagine that range checking is be faster since that uses CPU registers... I might be wrong here though. Also, unary is also very fast on CPU from what I've read. But, I'm sure you'll correct me if I'm in the left field. ;)

I’m confused. Dot product is not trig.

Ok, algebraic equation… shrug Close enough. :wink:

Well, if anyone is interested in stealing this code, here it is. :stuck_out_tongue:



[java]



// imgW = image width

// imgH = image height



private void updatePointer(Vector3f screenLocation) {

float x = constraintX(screenLocation.x, screenLocation.z);

float y = constraintY(screenLocation.y, screenLocation.z);



pointer.setPosition(x, y);

}



/**

  • Returns the X coordinate for the image so it is inside the screen.
  • @param x The x coordinate of the spatial translated to screen coordinates.
  • @return The screen-constrained x.

    */

    private float constraintX(float x, float z) {

    // Spatial is in front of camera.

    if (z >= 0.0f && z <= 1.0f) {

    return x <= 0 ? 0 : x + imgW > width ? width - imgW : x;

    } else {

    // Spatial is behind us.

    float w1 = width - x - imgW < 0 ? 0 : width - x - imgW;

    float w2 = x + imgW > width ? width - imgW : w1;

    w2 = w2 > width ? width - imgW : w2 - imgW < 0 ? 0 : width - x - imgW;

    return w2 >= width ? width - imgW : w2 <= 0 ? 0 : w2;

    }

    }



    /**
  • Returns the Y coordinate for the image so it is inside the screen.
  • @param y The y coordinate of the spatial translated to screen coordinates.
  • @return The screen-constrained y.

    */

    private float constraintY(float y, float z) {

    if (z >= 0.0f && z <= 1.0f) {

    // lower than bottom of screen

    return y <= 0 ? 0 : y + imgH > height ? height - imgH : y;

    } else {

    // Spatial is behind camera.

    float w1 = height - y - imgH < 0 ? 0 : height - y - imgH;

    float w2 = y + imgH > height ? height - imgH : w1;

    w2 = w2 > height ? height - imgH : w2 - imgH < 0 ? 0 : height - y - imgH;

    return w2 >= height ? height - imgH : w2 <= 0 ? 0 : w2;

    }

    }

    [/java]



    It could be simplified even more by using only 1 constrain methods and passing it a “size” but readability would suffer even more, so I prefer it that way.
2 Likes

Here’s what it looks like.



In the first part I show the info box, but it not fully working at certain angles, but I figure since those will be read when in front of you, it’s not a big deal. The second part show the pointers on two ships. The class is a Control.



Works beautifully. :smiley:



http://www.youtube.com/watch?v=Kmc1Ahj-uhw

1 Like

very nice!!