Let’s take a look at the the following unfilled cloud picture. As you can see, we drew the cloud just by drawing many arcs of different sizes and extent.
The problem is how we can draw this arcs to form the shape of a cloud (You can try randomly draw these arcs to see what shape it forms). The solution is to use an oval shape to control the end points of these arcs. As you can see in the following picture, the arcs starts and ends on the points of an oval shape.
Here is the general idea of the algorithm for drawing the cloud shape:
1. Create an oval shape and put the coordinates of points on the oval into a list.
2. Starting from a point in the list, randomly skip certain number of points and stop on another point.
3. Use two points from step 2, decide the extent of arc randomly and draw the arc.
4. Repeat 2,3 until all the points on the oval is covered.
Now let’s get into the details:
Creating the Oval
We can use two halves of ellipses to draw the oval shape. Because clouds have flatter bottom than the top, we should make top ellipse is taller than the bottom one. Both of them should have the same width. The easiest way to create this shape is to use parametric equations for ellipse. The following is the code snippet:
n = 500 # number of points on each ellipse
# X,Y is the center of ellipse, a is radius on x-axis, b is radius on y-axis
# ts is the starting angle of the ellipse, te is the ending angle of the ellipse
# P is the list of coordinates of the points on the ellipse
def ellipse(X,Y,a,b,ts,te,P):
t = ts
turtle.up()
for i in range(n):
x = a*math.cos(t)
y = b*math.sin(t)
P.append((x+X,y+Y))
turtle.goto(x+X,y+Y)
turtle.down()
t += (te-ts)/(n-1)
P = [] # starting from empty list
ellipse(0,0,300,200,0,math.pi,P) # taller top half
ellipse(0,0,300,50,math.pi,math.pi*2,P) # shorter bottom half
Drawing an Random Arc
The next problem we need to solve is: how can we draw an arc given two points and extent? We need to figure out the radius and the initial heading for drawing the circle. This requires some geometry work details of which will be skipped for this tutorial. The following code snippet for this part:
# computes Euclidean distance between p1 and p2
def dist(p1,p2):
return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)**0.5
# draws an arc from p1 to p2 with extent value ext
def draw_arc(p1,p2,ext):
turtle.up()
turtle.goto(p1)
turtle.seth(turtle.towards(p2))
a = turtle.heading()
b = 360-ext
c = (180-b)/2
d = a-c
e = d-90
r = dist(p1,p2)/2/math.sin(math.radians(b/2)) # r is the radius of the arc
turtle.seth(e) # e is initial heading of the circle
turtle.down()
turtle.circle(r,ext,100)
return (turtle.xcor(),turtle.ycor()) # returns the landing position of the circle
# this position should be extremely close to p2 but may not be exactly the same
# return this for continuous drawing to the next point
draw_arc((0,0), (-100,200), 150)
Drawing the Cloud
With previous two sections done, we are ready to draw the cloud. We randomly skip a number of points to get to the next point, randomly generate an extent, and draw the arc between these two points. This is to be repeated until all points are covered. There are a few details to address. The top part of cloud is more ragged than the bottom part. Therefore, we should give wider range for the extent and steps for random number generator. The following is the complete code for drawing clouds:
import turtle
import math
import random
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Random Cloud - PythonTurtle.Academy")
turtle.speed(0)
turtle.hideturtle()
turtle.up()
turtle.bgcolor('dodger blue')
turtle.pencolor('white')
turtle.pensize(2)
n = 500 # number of points on each ellipse
# X,Y is the center of ellipse, a is radius on x-axis, b is radius on y-axis
# ts is the starting angle of the ellipse, te is the ending angle of the ellipse
# P is the list of coordinates of the points on the ellipse
def ellipse(X,Y,a,b,ts,te,P):
t = ts
for i in range(n):
x = a*math.cos(t)
y = b*math.sin(t)
P.append((x+X,y+Y))
t += (te-ts)/(n-1)
# computes Euclidean distance between p1 and p2
def dist(p1,p2):
return ((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)**0.5
# draws an arc from p1 to p2 with extent value ext
def draw_arc(p1,p2,ext):
turtle.up()
turtle.goto(p1)
turtle.seth(turtle.towards(p2))
a = turtle.heading()
b = 360-ext
c = (180-b)/2
d = a-c
e = d-90
r = dist(p1,p2)/2/math.sin(math.radians(b/2)) # r is the radius of the arc
turtle.seth(e) # e is initial heading of the circle
turtle.down()
turtle.circle(r,ext,100)
return (turtle.xcor(),turtle.ycor()) # returns the landing position of the circle
# this position should be extremely close to p2 but may not be exactly the same
# return this for continuous drawing to the next point
def cloud(P):
step = n//10 # draw about 10 arcs on top and bottom part of cloud
a = 0 # a is index of first point
b = a + random.randint(step//2,step*2) # b is index of second point
p1 = P[a] # p1 is the position of the first point
p2 = P[b] # p2 is the position of the second point
turtle.fillcolor('white')
turtle.begin_fill()
p3 = draw_arc(p1,p2,random.uniform(70,180)) # draws the arc with random extention
while b < len(P)-1:
p1 = p3 # start from the end of the last arc
if b < len(P)/2: # first half is top, more ragged
ext = random.uniform(70,180)
b += random.randint(step//2,step*2)
else: # second half is bottom, more smooth
ext = random.uniform(30,70)
b += random.randint(step,step*2)
b = min(b,len(P)-1) # make sure to not skip past the last point
p2 = P[b] # second point
p3 = draw_arc(p1,p2,ext) # draws an arc and return the end position
turtle.end_fill()
P = [] # starting from empty list
ellipse(0,0,300,200,0,math.pi,P) # taller top half
ellipse(0,0,300,50,math.pi,math.pi*2,P) # shorter bottom half
cloud(P)
Related Projects:
Random Cloud Generator
Random Mountain Generator
Random City Skyline Generator
Random Island Generator