I have created a 3D animation with a perspective projection of white circles moving randomly in a fake 3D space projected on a 2D computer screen (GIF 1).
Since I need to keep the same number of visible circles, every time a circle disappears from the frame, I have to create a new visible one within the frame. To do so, I have written this piece of code:
First I created initial coordinates and the two angles of movements (spherical coordinates):
for circle in circles:
circle.position.xy = np.random.uniform(-25, 25, size=2)
z = np.random.uniform(near_z, far_z)
circle.position.z = z
circle.position.x *= z/-50
circle.position.y *= z/-50
circle.theta_deg = np.random.rand(1) * 360
circle.phi_deg = np.random.rand(1) * 360
theta_rad = circle.theta_deg * np.pi / 180
phi_rad = circle.phi_deg* np.pi / 180
circle.dx = speed * np.sin(-phi_rad - theta_rad) / frameRate
circle.dy = -speed * np.cos(phi_rad + theta_rad) / frameRate
circle.dz = -speed * np.cos(theta_rad) / frameRate
Then, in the loop that plays the animation, and updates the position of each circle, I have put this condition following the same answer that was provided to the same kind of issue here:
max_dist = max(abs(circle.position.x),abs(circle.position.y))
limit_dist = 25 * abs((circle.position.z-near_z) / far_z)
z_rel = np.random.uniform(near_z,far_z)
if max_dist > limit_dist:
circle.position.x = np.random.uniform(-25, 25) * z_rel/far_z
circle.position.y = np.random.uniform(-25, 25) * z_rel/far_z
I got a weird result as shown in GIF 2
What is wrong with my condition and how can I detect a circle that disappears from the frame and recreate one inside the frame?
Following the suggestion of #Fabian N. (answer below), I have reset the z-coordinates along with the x and y coordinates as follows:
max_dist = max(abs(circle.position.x), abs(circle.position.y)) # Find maximum distance of a circle to the center of the view:
limit_dist = 25 * abs((circle.position.z-near_z) / far_z)
if circle.position.z <= near_z or max_dist > limit_dist:
z_rel = np.random.uniform(near_z,far_z)
circle.position.z = z_rel + near_z
circle.position.x = np.random.uniform(-25, 25) * z_rel/far_z
circle.position.y = np.random.uniform(-25, 25) * z_rel/far_z
And I got this result:
Based on the code you posted I can only see two points of interest without actually running it (could you add some glue code around the code you posted to make it runnable as a standalone example?)
You are only resetting the x and y position in the if condition, you need to reset z too, otherwise, they will fly behind the camera or vanish in the distance
the if-condition from the question you linked has another part: sphere.position.z >= camera_z which would translate for your code into circle.position.z <= near_z to actually detect spheres flying behind the camera
Both can't really explain what's happening in your second gif...
On second thought: the jumping circles in gif 2 could just be circles that get instantly reset each frame because they aren't properly reset as their z-coordinate stays the same.
In the code that you've added, you are resetting the z parameter differently than the way you initialized it.
In the first part of your code, you use
z = np.random.uniform(near_z, far_z)
circle.position.z = z
circle.position.x *= z/-50
circle.position.y *= z/-50
while in the for loop you use
z_rel = np.random.uniform(near_z,far_z)
circle.position.z = z_rel + near_z
circle.position.x = np.random.uniform(-25, 25) * z_rel/far_z
circle.position.y = np.random.uniform(-25, 25) * z_rel/far_z
These do not seem to be equivalent. Perhaps you should use the same parameters for both.
You should also check the way that you update your x and y positions, especially your parameters dx and dy, as these may be getting so big that your circles fly out of the screen immediately
Related
I am making a geometry interface in python (currently using tkinter) but I have stumbled upon a major problem: I need a function that is able to return a point, that is at a certain angle with a certain line segment, is a certain length apart from the vertex of the angle. We know the coordinates of the points of the line segment, and also the angle at which we want the point to be. I have attached an image below for a more graphical view of my question.
The problem: I can calculate it using trigonometry, where
x, y = vertex.getCoords()
endx = x + length * cos(radians(angle))
endy = y + length * sin(radians(angle))
p = Point(endx, endy)
The angle I input is in degrees. That calculation is true only when the line segment is parallel to the abscissa. But the sizes of the angles I get back are very strange, to say the least. I want the function to work wherever the first two points are on the tkinter canvas, whatever the angle is. I am very lost as to what I should do to fix it. What I found out: I get as output a point that when connected to the vertex, makes a line that is at the desired angle to the abscissa. So it works when the first arm(leg, shoulder) of the angle is parallel to the abscissa, then the function runs flawlessly (because of cross angles) - the Z formation. As soon as I make it not parallel, it becomes weird. This is because we are taking the y of the vertex, not where the foot of the perpendicular lands(C1 on the attached image). I am pretty good at math, so feel free to post some more technical solutions, I will understand them
EDIT: I just wanted to make a quick recap of my question: how should I construct a point that is at a certain angle from a line segment. I have already made functions that create the angle in respect to the X and Y axes, but I have no idea how i can make it in respect to the line inputted. Some code for the two functions:
def inRespectToXAxis(vertex, angle, length):
x, y = vertex.getCoords()
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(abs(newx), abs(newy))
return p
def inRespectToYAxis(vertex, length, angle):
x, y = vertex.getCoords()
theta_rad = pi / 2 - radians(angle)
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(newx, newy)
return p
Seems you want to add line segment angle to get proper result. You can calculate it using segment ends coordinates (x1,y1) and (x2,y2)
lineAngle = math.atan2(y2 - y1, x2 - x1)
Result is in radians, so apply it as
endx = x1 + length * cos(radians(angle) + lineAngle) etc
I am trying to create a function that draws regular polygons inside each other as in the attached picture. The size of the polygons' side is defined by the following formula:
initial_size = initial_radius*(2*math.sin(360/(2*number_of_angles)))
I have two questions: 1. Why when I assign "initial_size" by the above formula my drawing starts in a different direction rather than when I simply assign initial_size = 100 (disregarding the formula)? 2. Can you please provide me with a hint (or any direction) on a way I can draw regular polygons as on the picture (i.e., starting from different points (moving along x-axis) and drawing each polygon inside the other?
import turtle
import math
turtle.speed(1)
def reg_polygon(number_of_angles, initial_radius):
Q = 180-(180/number_of_angles)/2
turtle.left(Q)
initial_size = initial_radius*(2*math.sin(360/(2*number_of_angles)))
if number_of_angles>=3:
sum_angle = 180*(number_of_angles-2)
angle = sum_angle/number_of_angles
for i in range(number_of_angles):
turtle.forward(initial_size)
turtle.left(180-angle)
elif number_of_angles<3:
print("Minimum number of angels should be >=3")
for i in range(3,6):
reg_polygon(i,100)
turtle.done()
Here is what the following code snippet draws; it is not an absolutely perfect reproduction of the gif you posted (some tweaks for the size progression for 3, 4, and 5-gons must be done to avoid the smaller neighbors to touch at some points - i/e the gif maker somewhat cheated!), but it follows mathematical symmetry and perfection.
The following code has some magic numbers; I may come back to it later, but not at this time. The resources I used to calculate a regular polygon can be found here, and there.
import turtle
import math
def reg_polygon(start_pos, number_of_angles, side):
interior_angle = (180 * (number_of_angles - 2)) / number_of_angles
turtle.setheading(180 - interior_angle//2)
for i in range(number_of_angles):
turtle.forward(side)
turtle.left(180 - interior_angle)
def reset_start_point():
global start_pos, startx, starty, initial_size, number_of_angles, side
startx += 8
starty -= 0
initial_size += 8
number_of_angles += 1
side = 2 * initial_size * math.sin(math.radians(180/number_of_angles))
start_pos = startx, starty
turtle.penup()
turtle.goto((startx, starty))
turtle.pendown()
start_pos = startx, starty = 0, 0
number_of_angles = 2
initial_size = 15 # radius
side = 0
while number_of_angles < 21:
reset_start_point()
reg_polygon(start_pos, number_of_angles, side)
turtle.done()
I am trying to create a rotating turret. The turret rotates correctly the problem is when I make the turret shoot with the space bar the bullet isn't the same size or shape at every angle. I tried using the angle that the turret is facing to do some trig calculations and find the two corner points needed to create the bullet (which is a circle). Nothing I have tried will work. Here is the code:
Barrel = [260,210,270,210,270,170,260,170]
def RotateBarrel():
global angle
angleChange = 2
mountCenterX = 265
mountCenterY = 215
#Rotate Barrel
cycle = 1
while cycle < len(Barrel):
x = Barrel[cycle-1]-mountCenterX
y = Barrel[cycle]-mountCenterY
Barrel[cycle-1] = (x*math.cos(angleChange*math.pi/180)-y*math.sin(angleChange*math.pi/180))+mountCenterX
Barrel[cycle] = (x*math.sin(angleChange*math.pi/180)+y*math.cos(angleChange*math.pi/180))+mountCenterY
cycle += 2
angle += angleChange
if angle == 360: angle = 0
canvas.coords(barrel,Barrel)
self.after(1,RotateBarrel)
def SpinningShoot(event):
global angle
speed = 10
shotXpos = Barrel[6]+10*(math.cos(angle*math.pi/180))
shotYpos = Barrel[7]-10*(math.sin(angle*math.pi/180))
cornerX = Barrel[6]+10*(math.cos((90-angle)*math.pi/180))
cornerY = Barrel[7]-10*(math.sin((90-angle)*math.pi/180))
shot = canvas.create_oval(shotXpos,shotYpos,cornerX,cornerY,fill="white")
Xmotion = speed*math.cos(angle*math.pi/180)
Ymotion = speed*math.sin(angle*math.pi/180)
Shots.append(shot)
ShotsPos.append(shotXpos)
ShotsPos.append(shotYpos)
ShotsMotion.append(Xmotion)
ShotsMotion.append(Ymotion)
It looks to me that your shot is not going to be centered on the "barrel", but the calculation using (90-angle) is going to give you an angular width for the shot of
angle - ( 90 - angle )
which is 2 * angle - 90
(ie the shot will be wider depending on the size of the angle).
I would have thought use (angle - 45 ) and (angle + 45 ) so your shot is always the same angular width and centered on the barrel.
You would also need to increase the "radius" for the second corner. Im confused about the +10 and -10, not quite sure what they would do.
Probably a better approach is to calculate the "centre" of the bullet and then from that just draw the circle centered on it. Im sure there would be a function that takes the centre and radius. So have
radius=10
centrex= radius * cos ( angle * math.pi /180 )
centrey= radius * sin ( angle * math. pi / 180 )
and then pass those two and a radius to a function that does a circle
One other little thing, I would suggest changing the line
if angle == 360: angle = 0
to
if angle >= 360: angle = angle-360
as if angle was initialized as anything other than an even number or you changed the angle step you could "miss" the 360 and then never wrap back around.
I'm using Python Turtles to draw a circle using forward() and right().
I have a for loop counting from 0 to 359, and each time it triggers, it moves the turtle forward 1 and right 1.
But the problem is I need specific diameters. I am nearly 100% sure I'll need to use trig, but I've tried to no avail.
I can't figure out the math how to do it. We're supposed to use forward() and right(), NOT circle().
Thanks!
Here is a working example:
import turtle
import math
def circle(radius):
turtle.up()
# go to (0, radius)
turtle.goto(0,radius)
turtle.down()
turtle.color("black")
# number of times the y axis has been crossed
times_crossed_y = 0
x_sign = 1.0
while times_crossed_y <= 1:
# move by 1/360 circumference
turtle.forward(2*math.pi*radius/360.0)
# rotate by one degree (there will be
# approx. 360 such rotations)
turtle.right(1.0)
# we use the copysign function to get the sign
# of turtle's x coordinate
x_sign_new = math.copysign(1, turtle.xcor())
if(x_sign_new != x_sign):
times_crossed_y += 1
x_sign = x_sign_new
return
circle(100)
print('finished')
turtle.done()
Well, a complete circle is 360°, and you are planning on turning 360 times, so each turn should be:
right( 360 ° / 360 ), or
right(1)
The distance traveled will be one circumference, or π * diameter, so your forward might be:
forward( diameter * π / 360 )
I haven't tested this yet -- give it a try and see how it works.
This is one of the exercises in "Think Python," in chapter 4. It really is a horrible exercise to have this early in the book, especially with the "hint" given. I'm using forward and left here, but you can switch left with right.
You should have the polygon function:
def polygon(t, length, n):
for i in range(n):
bob.fd(length)
bob.lt(360 / n)
Then you create a circle function:
def circle(t):
polygon(t, 1, 360)
That will draw a circle, no radius needed. The turtle goes forward 1, then left 1 (360 / 360), 360 times.
Then, if you want to make the circle bigger, you calculate the circumference of the circle. The hint says:
Hint: figure out the circumference of the circle and make sure that
length * n = circumference.
Ok, so the formula for circumference = 2 * pi * radius. And the hint says length * n = circumference. n = 360 (number of sides/degrees). We have circumference, so we need to solve for length.
So:
def circle(t, r):
circumference = 2 * 3.14 * r
length = circumference / 360
polygon(t, length, 360)
Now, call the function with whatever radius you want:
circle(bob, 200)
I am trying to move my world in reference to a character. It works if the degree is zero (the player goes forward) but messes up everywhere else. When it is 90 degrees, the player goes backwards instead of forwards. I feel like I am on the right track and I just messed up something small.
Here is my equation for the goForward() function
rad = angle * (pi/180)
world_loc = (world_loc[0] + speed * sin(rad), world_loc[1], world_loc[2] + speed* cos(rad))
Then this is how I display my world
glPushMatrix()
glRotate(angle, 0,1,0)
glTranslatef(world_loc[0],world_loc[1],world_loc[2])
for x in range(len(world)):
for y in range(len(world[0])):
for z in range(len(world[0][0])):
if(world[x][y][z] != None):
glPushMatrix()
glTranslatef(x*2,y*2,z*2)
glCallList(world[x][y][z])
glPopMatrix()
glPopMatrix()
Any thoughts on what it could be?
The formula is incorrect. Here is the correct one:
def moveForward():
global angle, angle_speed, world_loc, maxSize
rad = (angle+90) * (pi/180)
world_loc = (world_loc[0] - speed * cos(rad), world_loc[1], world_loc[2] - speed * sin(rad))