Well, now I'm sure you're wondering "How can Construct ray trace anything!?". Well, it can't, but that doesn't mean we can't FAKE it. ;) The main concept is the object at some position needs to be TRANSLATED to the screen position (called "screen space"), based on depth and horizontal position. We simply need to know where to draw an image on the screen, based on depth, and what scale to give it! To do this, we need an equation to translate an object's x,y,z position to the screen, and determine the scale.
Note: There is a faster way to do this, see bottom of page. This next part still works fine, and I have it here as a good way to practice some trig. ;)
Let's explore how to do this. First, let's say the player's eye is at position 0,0,0 (x,y,z), and the object (the circle in the image above) is at -100,100,200 (x,y,z). First, we can assume that any object anywhere, should become 0 when translated to the starting point (where the player's eye is), so:
| ScreenX = ObjectX [*] 0
| ScreenY = ObjectY [*] 0
But, of course, ALL objects will be drawn at screen position 0,0 (center of the screen). The screen itself is at a DIFFERENT position in front of the player (again, like a window). The object drawn on this "window" would reflect, in 2D space, where the object is in 3D. It's much easier to look at one plane at a time, so let's explore the X/Z plane.
On the X/Z plane, the object's X position is proportional to the object's Z position. Given that, we can assume this:
| X = Z [*] W, where 'W' here is the horizontal scale factor.
For example, if W=1, then at a distance of 100 away from the player, the X position is the same, which is a line at a 45° angle (the thicker black lines in the image above). If 'W' was 0.5, then every Z position would give Z[]0.5 for X, or HALF of Z (and the angle would be half as well). We can see here then how the angle and the scale factor seem related! The trick is finding out what "W" is at a object's given distance, then working backwards* towards the screen! Let's get into a bit of trig...
So, let's look at the 'a', 'o', and 'h' lines in the image above. The basic formula for finding the angle of 'h', given 'a' and 'o' is the following:
| tan(angle) = o/a (FYI: I'll be working in degrees, and not radians)
| angle = atan(o/a) [atan(), or arctangent, is the inverse of tan()]
This gives the circle's angle from the 0,0 (x,y) point, but we want the 'o' part, which is:
| tan(angle) [*] a = o
| (or "x = tan(angle) [*] z")
The 'a' here represents the depth ('z'), and the 'o' represents the 'x' position. At a given distance of 'a' (or 'z') we can find 'x'! Now, that's all fine, but remember, we want the SCREEN position, which is where the 'h' line crosses the blue line (the screen). Can you figure out now how to find the screen pixel's 'x' for the circle knowing the above? ;) You already have the formula.
The screen x = distance from player (0,0), to the screen center (0,z), times tangent of the angle towards the object, so:
| ScreenX = tan(horizontalAngleToObject) [*] zDistanceToScreen
We can do the same for Y as well:
| ScreenY = tan(verticalAngleToObject) [*] zDistanceToScreen
Okay, so let's put this all together and find the screen position of the circle!
Circle object position: -100,100,200 (x,y,z - in pixels) ['z' would be a new instance variable added)
Screen distance from player (z): 50 pixels (units can be whatever you want, just be consistent - since I'm doing a 1:1 position for objects within the screen space, my depth is using the same unit. These examples are all contrived and nothing realistic! ;) )
| horizontalAngleToObject = atan(x/z) [inverse tangent remember]
| horizontalAngleToObject = atan(-100/200)
| horizontalAngleToObject = -26.565° (0° is straight ahead)
Good, now we know what HORIZONTAL angle the object is at. Let's get the screen position:
| ScreenX = tan(horizontalAngleToObject) [*] zDistanceToScreen
| ScreenX = tan(-26.565°) [*] 50
| ScreenX = -25 (25 pixels to the left of screen center, or ScreenCenterX + ScreenX)
Now let's do this for screen's vertical position:
| verticalAngleToObject = atan(y/z)
| verticalAngleToObject = atan(100/200)
| verticalAngleToObject = -26.565°
| ScreenY = tan(verticalAngleToObject ) [*] zDistanceToScreen
| ScreenY = tan(26.565°) [*] 50
| ScreenY = 25 (25 pixels up from screen center, or ScreenCenterY - ScreenY)
Now, all that said, you can shoot me later, because there is another way. ;) The point at which the ray intersects the screen can also be found like this:
| ScreenX = ObjectX / (ScreenZ/ObjectZ)
| ScreenY = ObjectY / (ScreenZ/ObjectZ)
Just remember that ScreenZ is from the player at 0,0,0, and so is ObjectZ.
There! Phew, we made it. That's wasn't so hard was it! ;) All you have to do now is test if the screen position is outside the screen boundary (taking the object's size into account!), then make it invisible and ignore it.
Now, what about the scale of the object!? ...