Saturday, November 11, 2017

pygame - 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).



Answer



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.


enter image description here


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


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...