I made a 3d voxel rendering engine in python (turtle) a few days ago. Everything seems to be going smoothly, until you turn around 360∘ while in front of an object. The object will appear mirrored on the y axis. I came up with a fix for it via setting your y rotation, but that fix only works from one viewing angle. There are no console errors whatsoever, so I cannot pinpoint the location of the rendering error. I wish I had more info to aid in locating the issue.
from turtle import*
from time import*
from math import*
wn=Screen()
speed(0)
ht()
pu()
wn.tracer(0,0)
fov=200
xoff=0
yoff=0
zoff=0
camx=0
camy=0
camz=-105
xrot=0
yrot=0
zrot=0
jvel=0
onground=False
def goto3d(x,y,z):
rotxx=x
rotxy=y*cos(xrot)-z*sin(xrot)
rotxz=y*sin(xrot)+z*cos(xrot)
rotyx=rotxx*cos(yrot)+rotxz*sin(yrot)
rotyy=rotxy
rotyz=rotxz*cos(yrot)-rotxx*sin(yrot)
rotzx=rotyx*cos(zrot)-rotyy*sin(zrot)
rotzy=rotyx*sin(zrot)+rotyy*cos(zrot)
rotzz=rotyz
transx=rotzx-xoff
transy=rotzy-yoff
transz=rotzz-zoff
newx=fov*transx/transz
newy=fov*transy/transz
if newx<=-200 or newy<=-200 or newx>=200 or newy>=200:
pencolor('black')
else:
goto(newx,newy)
def cube(x,y,z):
z-=100
pu()
goto3d((-1+x)-camx,(-1+y)-camy,(-1+z)-camz)
pd()
goto3d((-1+x)-camx,(1+y)-camy,(-1+z)-camz)
goto3d((1+x)-camx,(1+y)-camy,(-1+z)-camz)
goto3d((1+x)-camx,(-1+y)-camy,(-1+z)-camz)
goto3d((-1+x)-camx,(-1+y)-camy,(-1+z)-camz)
pu()
goto3d((-1+x)-camx,(1+y)-camy,(-1+z)-camz)
pd()
goto3d((-1+x)-camx,(1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(1+y)-camy,(-1+z)-camz)
pu()
goto3d((-1+x)-camx,(-1+y)-camy,(-1+z)-camz)
pd()
goto3d((-1+x)-camx,(-1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(-1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(-1+y)-camy,(1+z)-camz)
goto3d((1+x)-camx,(-1+y)-camy,(-1+z)-camz)
pu()
goto3d((-1+x)-camx,(1+y)-camy,(1+z)-camz)
pd()
goto3d((-1+x)-camx,(-1+y)-camy,(1+z)-camz)
pu()
goto3d((1+x)-camx,(1+y)-camy,(1+z)-camz)
pd()
goto3d((1+x)-camx,(-1+y)-camy,(1+z)-camz)
def black():
pencolor('black')
pu()
goto(-400,-400)
pd()
begin_fill()
goto(-200,200)
goto(200,200)
goto(200,-200)
end_fill()
def w():
global camz
global camx
camz+=.3*cos(yrot)
camx+=-1*.3*sin(yrot)
def a():
global camz
global camx
camz+=-1*.3*sin(yrot)
camx+=-1*.3*cos(yrot)
def s():
global camz
global camx
camz+=-1*.3*cos(yrot)
camx+=.3*sin(yrot)
def d():
global camz
global camx
camz+=.3*sin(yrot)
camx+=.3*cos(yrot)
def left():
global yrot
yrot+=pi/50
def right():
global yrot
yrot-=pi/50
def jump():
global jvel
if onground==True:
jvel=.3
wn.onkey(w,'w')
wn.onkey(a,'a')
wn.onkey(s,'s')
wn.onkey(d,'d')
wn.onkey(left,'Left')
wn.onkey(right,'Right')
wn.onkey(jump,'Space')
wn.listen()
def bush(x,y,z):
pencolor('darkgreen')
cube(0+x,0+y,0+z)
cube(2+x,0+y,0+z)
cube(2+x,2+y,0+z)
def tree(x,y,z):
pencolor('brown')
cube(0+x,0+y,0+z)
cube(0+x,2+y,0+z)
pencolor('green')
cube(0+x,4+y,0+z)
cube(0+x,6+y,0+z)
cube(2+x,4+y,0+z)
cube(-2+x,4+y,0+z)
cube(0+x,4+y,2+z)
cube(0+x,4+y,-2+z)
def render():
bush(-5,0,10)
tree(3,0,7)
while True:
clear()
black()
render()
update()
jvel-=.01
camy+=jvel
if camy<=0:
camy=0
jvel=.01
onground=True
if camy>=.3:
onground=False
You are not culling out vertices that lie behind the camera. I cannot see any evidence of culling in your code. If you render a point that lies behind the camera, it will appear upside down in the result. You are not actually turning 360 degrees, but 180 degrees. When the voxels are directly behind you, they will appear inverted if you don't cull it. You can test this by printing the yrot variable. Try modifying the line if newx<=-200 or newy<=-200 or newx>=200 or newy>=200 to include a check of transz, i.e. if transz < 0.1 or newx<=-200 or newy<=-200 or newx>=200 or newy>=200. Any small value will do but 0.1 should work well in your current implementation.
Related
I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.
I am attempting to make pong using Turtle, I have most things working, however, I would like to implement the ability to hold a key to move the bumpers up and down, rather than having to tap the key multiple times. These are my functions for movement so far.
def lbump_move_up():
x = lbump.ycor()
x += bumpspeed
if x > 240:
x = 240
lbump.sety(x)
def lbump_move_down():
x = lbump.ycor()
x -= bumpspeed
if x < -240:
x = -240
lbump.sety(x)
def rbump_move_up():
x = rbump.ycor()
x += bumpspeed
if x > 240:
x = 240
rbump.sety(x)
def rbump_move_down():
x = rbump.ycor()
x -= bumpspeed
if x < -240:
x = -240
rbump.sety(x)
turtle.listen()
turtle.onkey(lbump_move_up,'w')
turtle.onkey(rbump_move_up,'Up')
turtle.onkey(lbump_move_down,'s')
turtle.onkey(rbump_move_down,'Down')
turtle.onkey(ball_move,'Return')
I don't have a complete answer to this, as I came here looking for one myself. However, I have some progress on it that someone else could finish...
You can create a new class that calls like a recursive function as follows:
class function2:
def __init__(self,fun,args=None):
self.fun=fun
self.args=args
def begin_loop(self):
self.looping=True
self.loop()
def loop(self):
self.fun(self.args)
if self.looping: self.loop()
def end_loop(self):
self.looping=False
Now to tie it to your example, you can convert your functions into function2s and thus call them onkeypress as follows:
l_up=function2(lbump_move_up)
r_up=function2(rbump_move_up)
l_down=function2(lbump_move_down)
r_down=function2(rbump_move_down)
Wn=turtle.Screen()
Wn.onkeypress(l_up.begin_loop,'w')
Wn.onkeypress(r_up.begin_loop,'Up')
Wn.onkeypress(l_down.begin_loop,'s')
Wn.onkeypress(r_down.begin_loop,'Down')
Wn.onkeyrelease(l_up.end_loop,'w')
Wn.onkeyrelease(r_up.end_loop,'Up')
Wn.onkeyrelease(l_down.end_loop,'s')
Wn.onkeyrelease(r_down.end_loop,'Down')
Wn.listen()
So to the issue: RecursionErrors
If you want your function to cause a small change, then you'll need to hold the button for a long time and that causes RecursionErrors - which at least in this case don't seem to be affected by try/except statements.
Once again, sorry for the incomplete solution, but I feel like you can probably get away with larger movements in pong.
I had the same issue, but found the "onkeypress" function in the Turtle documentation (https://docs.python.org/3/library/turtle.html#turtle.onkeypress), which resolved the issue for me.
turtle.onkeypress(fun, key=None)
"onkeypress", will move the turtle as long as the key is pressed, while "onkey" will move it once when the key is released.
Example:
from turtle import Turtle, Screen
turtle = Turtle("turtle")
turtle.penup()
screen = Screen()
screen.listen()
def move_forward():
turtle.forward(20)
def move_backward():
turtle.backward(20)
screen.onkeypress(move_forward, "w")
screen.onkey(move_backward, "s")
screen.exitonclick()
With the following code
x=1.0
def update(dt):
space.step(dt)
def xprinter(self, x):
print (x)
return x+1
if __name__ == "__main__":
x=pyglet.clock.schedule(xprinter,x)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
My return is simply 1.0 over and over. I would like for the value to be updated with each call. What am I missing?
The design here is based on that your function rely on returning a result.
Which causes a problem because Pyglet's internal functions are in charge of executing that function at a interval, meaning you're not the one executing the call - and there for you're not the one getting that return value, Pyglet is.
And since there's no meaningful way for Pyglet to relay that returned value (there are ways, but they involve hooking in and overriding certain internal functions), you will never see that return value.
The quick and easy workaround would be to do:
x=1.0
def update(dt):
space.step(dt)
def xprinter(self):
global x
print(x)
x += 1
if __name__ == "__main__":
pyglet.clock.schedule(xprinter)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
This way, the schedule call will update the global variable x rather than returning the result of the math equation.
A more neat approach would be to define a class with the x attribute and pass a class instance to the pyglet.clock.schedule():
class player():
def __init__(self):
self.x = 0
def update(dt):
space.step(dt)
def xprinter(self, p):
print(p)
p.x += 1
if __name__ == "__main__":
p = player()
x = pyglet.clock.schedule(xprinter, p)
pyglet.clock.schedule_interval(update, 1.0/60)
pyglet.app.run()
And if I'm not completely out of the ball park, this would remember the value across clock ticks, because of the instance.
This is also usually what you'll be using the schedule for, doing player / game / animation updates.
So I'm making a lottery number drawing machine,
import random
def lottoDraw1():
draw1 = random.randint(1,49)
def lottoDraw2():
draw2 = random.randint(1,49)
if draw2 == draw1:
lottoDraw2()
And I get the error, "NameError: name 'draw1' is not defined"
If I insert:
draw1 = 0
before the code, the answer is always 0.
Even after I define for draw1 to be changed.
What am I doing wrong?
What are Python namespaces all about
This question is asking for namespaces and that is basicially the problem you are having. In lottodraw1 you are only changing the local version of draw1 and thus the global value of draw1 stays unchanged (in your case 0). For that reason you will always use draw1 = None everywhere else.
My approach would be making an array of the draws and having a general draw function:
draws = []
def draw():
new_draw = random.randint(1,49)
if new_draw not in draws:
draws.append(new_draw)
else:
draw()
draw()
draw()
print(draws)
Now you can just call draw and it will add a newly drawn number that does not exist yet.
As noted by Jean-François Fabre the better version would be using sets, which are faster, but only allow unique values:
draws = set()
def draw():
new_draw = random.randint(1,49)
if new_draw not in draws:
draws.add(new_draw)
else:
draw()
draw()
draw()
print(draws)
You need to make draw1 as global variable
draw1, draw2 = None, None
def lottoDraw1():
global draw1
draw1 = random.randint(1,49)
def lottoDraw2():
global draw1
global draw2
draw2 = random.randint(1,49)
if draw2 == draw1:
lottoDraw2()
However, it is not a good approach which is another topic.
Through using VPython, I am able to get the program I am working on to generate multiple balls by calling the same class. I am also able to have the balls appear within a selected random range when they are generated (across x, y and z).
However, I am currently stumped on how I get to call the pos / position function from within my loop - as I would like to have the balls move.
Please see my code so far below.
If I call Ball.pos it states as undefined, but if I place my positioning via self.position, only one ball is generated, as they are not being referred to from under the sphere details?
from visual import *
from random import *
scene.title = "Multi Balls"
wallBm = box(pos=(0,-6,0), size=(12.2,0.1,12.1), color=color.blue, opacity=0.4)
vscale = 0.1
deltat = 0.005
t = 0
scene.autoscale = False
i = 0
totalBalls = 10
class Ball:
def __init__(self):
self.velocity = vector(0,5,0)
#vel sample ([5,10,15,20,25],3)
sphere (pos=(randrange (-6,6),randrange (-6,6),randrange (-6,6)), radius=0.5, color=color.cyan)
while True:
rate(100)
if i < totalBalls:
Ball()
i = i + 1
t = 5 + deltat
Try Inheritance from frame:
class Ball(frame):
def __init__(self, pos=(randrange (-6,6),randrange (-6,6),randrange (-6,6))):
frame.__init__(self,pos=pos)
self.velocity = vector(0,5,0)
sphere(frame=self,pos=pos,radius=0.5, color=color.cyan)
listOfBalls=[]
while True:
rate(100)
for i in range(totalBalls):
listOfBalls.append(Ball())
now try again!
You can call each Ball's position by calling listOfBalls[3].pos. I hope this helps!