# 3D vector graphics - maintaining correct Z 'length'

So I'm trying to build myself a 3D display 'engine', or whatever it ought to be called. I've been experimenting with PyGame, as it's familiar to me.

In a way this has almost been more an excuse to learn the math than to actually build a 'game engine' or anything like that, though ultimately that's why we're all programmers right? To code the next Super Mario Brothers. :) Anyway, this isn't that, it's a learning project, but unfortunately I seem to have hit a wall and I'm not entirely sure how to proceed or what to look for.

I'm unsure of how to move items smoothly on the Z-axis. 0 is the 'closest' a point gets to the screen, whereas N is the 'furthest' it could go. In the interest of (hopefully) keeping my math similar across calculations, I've set HEIGHT, WIDTH, and DEPTH all to an arbitrary value of 600.

It would appear that moving objects along the Z-axis is not quite the same as moving them along X and Y. Whereas X and Y are just addition and subtraction, Z is apparently only really effective as a percentage. Unfortunately I can't seem to find a method of incrementing Z in any way that doesn't stretch the shapes as if they're going into Warp when I move them closer to the horizon point. Likewise they squash nearly flat as they move closer to the screen.

I believe that the two most likely code candidates for weirdness are the following two snippets; one is the model's "move all the points" method, the other is a view method. Hopefully the code I'm not sharing is self-evident; Zpt is a class that contains x, y, z coordinates, and so on.

def move_pts_to_pt(self, newX=None, newY=None, newZ=None):
##This is how the model moves its points
oldCtr = self.center
if newX is not None:
deltaX = newX - oldCtr.x
else: deltaX = 0
if newY is not None:
deltaY = newY - oldCtr.y
else: deltaY = 0
if newZ is not None:
deltaZ = newZ
else: deltaZ = 1
for zpt in self.zpts + [self.center]:
zpt.x += deltaX
zpt.y += deltaY
zpt.z *= deltaZ

def zshape_draw(shapelist, objcolor=(255, 0, 0), objwidth=2):
##This is the view's drawing method for points with a z coordinate
for shape in shapelist:
drawnpoints = []
for zpt in shape:
z = zpt.z
horzoff = z / DEPTH
invhorz = 1.0 - horzoff

newX = (zpt.x * invhorz) + (WIDTH * HOR_X * horzoff)
newY = (zpt.y * invhorz) + (HEIGHT * HOR_Y * horzoff)
drawnpoints.append((newX, newY))

pygame.draw.polygon(SCREEN, objcolor, drawnpoints, objwidth)


I apologize if I've left any code out; I'm happy to re-edit my post. In any event, I cannot suss out what it is I need to do to prevent the z-values that are closest to the 'back' of the screen from increasing much faster than those closer to the screen, while still maintaining their intended 'shape' -- that is, generally looking like they aren't changing in depth as they move from z=0 to z=600 (or, whatever ends up being 100% 'away' from the screen).

• Did you read about raytracing? What 3d rendering technique are you toying with? Commented Jan 31, 2014 at 11:19
• I believe the correct term is 'vector graphics'… something like ye olde Spectre / Battlezone-style graphics. Commented Jan 31, 2014 at 14:24
• Are you not using a form of raytracing to calculate the positions of the vectors on the screen since they are in 3d space? Commented Jan 31, 2014 at 14:42
• The vectors are being drawn incidentally. I group 'Zpts' into lists and throw them to the view to render. That may be where the failing is; the length of the vectors is not calculated, just drawn after the points have been shifted. If I understand you correctly, Z translation requires knowing how long a vector 'should' be? If so, I think that explains a lot of my issues. Commented Jan 31, 2014 at 15:52
• Ohhh see I didn't see your posted answer yet. Didn't mean to overlook anything Commented Jan 31, 2014 at 16:19

The way to figure out where a dot in 3d space should be positioned on the screen is by imagining the monitor is a window and the dot is behind that window. To know where that dot is displayed on the monitor, you need to imagine you have one eye and the draw an imaginary line from that eye to that dot beyond the window and see where that line intersects with the window. This is where it should be drawn. If you have a line between two points, calculate the position of each of them on the monitor and proceed to draw the line.

The math in this simple case is deciding on some arbitary z' as the distance between the eye and the display output device. Then multiplying each dot's x & y values by that z' divided by their z value.

So if we have d.x, d.y, d.z, we"ll draw it on the monitor at d.x/d.z*z', d.y/d.z*z'

Hope that helps

• I suggest reading about theory when you are stuck in implementation. Commented Jan 31, 2014 at 14:54
• Indeed, that's mostly what I'd been doing, I just wasn't sure what I actually needed to look for. I will start playing with this. Commented Jan 31, 2014 at 16:19
• So z' is basically a magic number? And in this way, zz' is essentially how many "z-units" to shift towards the horizon point? In the same way that the delta change for any translation of a 2D (x, y) point is implicitly "move N *pixels in this direction"? Commented Jan 31, 2014 at 17:01
• hmm.. I think you are almost there.. (1/point.z) * (monitor.z) is the percentage (%) that the point's x & y values will affect their position on the monitor. If the points' z is very large, their x & y values will have very little affect conceptually intuitively because all lines converge in the far horizon. And it is not magic, it is supposed to reflect the relative distance of your eyes from the monitor if the content beyond that monitor was actually a physical entity that you are looking at that is z units deep. Commented Jan 31, 2014 at 17:20
• Yeah that's what I've come to realize since you posted. Up until now I had been expressing Z as "the percentage of 'closeness' between the screen and the horizon point at the back", since there was no 'z pixel' to simply increment/decrement, unlike X and Y. I think I understand what it is that I need to do now, and I will try it as soon as I'm able. Commented Jan 31, 2014 at 18:22