Cycloids

A cycloid is the curve generated by tracing a point on a smaller circle rolling around an bigger circle.
The word cycloid comes from the greek word κύκλος meaning circle. There are epicycloids and hypocycloids (smaller circle located above and beneath the bigger circle).
The cycloid is determined by the ratio between the two circle’s radii k = \frac{R}{r}.
More information can be found on this Wikipedia entry.

Hypocycloid with k = 2.1 Hypocycloid with k = 2.1 Hypocycloid with k = 7.2


# Python 2.7.7 Code
# Pygame 1.9.1 (for Python 2.7.7)
# Jonathan Frech 3rd of June, 2016
#         edited 4th of June, 2016

# importing needed modules
import pygame, sys, time, math, os, random

# define the cycloid's type and k
CYCLOID = ["hypo", "epi"][1]
K = 10.1

""" CLASSES """

# vector class (2-dimensional)
class Vector():
	# initialization
	def __init__(self, p1, p2 = None):
		# get the vector as a 2-dimensional list
		if not p2:
			self.x = p1[0]
			self.y = p1[0]
		else:
			
			# get the vector between two points
			if type(p1) == type([]):
				self.x = p2[0] - p1[0]
				self.y = p2[1] - p1[1]
			
			# get the vector as two numbers
			else:
				self.x = p1
				self.y = p2

	# calculate the vector's length
	def length(self):
		return math.sqrt(self.x**2 + self.y**2)

	# multiply the vector by n
	def multiply(self, n):
		self.x *= n
		self.y *= n

	# unify the vector (set it's length to 1)
	def unify(self):
		l = self.length()
		if l != 0:
			self.multiply(1. / l)

	# set the vector's length
	def setlength(self, n):
		self.unify()
		self.multiply(n)

	# stick the vector to a position
	def stickto(self, p):
		return p[0] + self.x, p[1] + self.y

	# angles used in degrees!
	# conversion between radians and degrees
	def radians(self, phi):
		return (phi * (math.pi/180.)) % (2*math.pi)
	def degrees(self, phi):
		return (phi * (180./math.pi)) % 360

	# get the vector's angle
	def getangle(self):
		return self.degrees( math.atan2(self.y, self.x) )

	# set the vector's angle
	def setangle(self, phi):
		l = self.length()

		self.x = l * math.cos(self.radians( phi ))
		self.y = l * math.sin(self.radians( phi ))

	# add an angle phi to current rotation
	def rotate(self, phi):
		self.setangle(self.getangle() + phi)

""" FUNCTIONS """

# validates a color integer
def colorValid(_color, _min = 0, _max = 255):
	newColor = math.fabs(_color)
	n = _max - _min
	if newColor > n:
		if int(newColor / n) % 2 == 0:
			newColor = newColor % n
		else:
			newColor = n - (newColor % n)
	
	return int(newColor) + _min

# gets the position on a circle
def getcirclepos(p, r, phi):
	return [p[0] + r * math.cos(phi * (math.pi/180.)), p[1] + r * math.sin(phi * (math.pi/180.))]

# returns an integer version of given position
def intpos(_pos):
	return [int(_pos[0]), int(_pos[1])]

""" GAME """

# game class
class GAME():
	# initialize program
	def __init__(self):
		
		# surface
		self.width, self.height = 720, 720
		self.size = [self.width, self.height]
		self.surface = pygame.Surface(self.size)

		# img
		self.img = pygame.Surface(self.size)

		# screen
		self.Width, self.Height = self.width, self.height
		self.Size = [self.Width, self.Height]
		self.Screen = pygame.display.set_mode(self.Size)

		# running variables
		self.ticks = 0
		self.running = True

		# show gray lines
		self.gray = False

		# cycloid variables
		self.k = K
		if CYCLOID == "epi":
			self.r = self.width / 2. / (self.k + 2)
		elif CYCLOID == "hypo":
			self.r = self.width / 2. / self.k
		self.R = self.r * self.k

		self.phi = 0
		self.Phi = 0

		self.dphi = self.k-1
		self.dPhi = -1

		self.res = 10.
		self.dphi /= self.res
		self.dPhi /= self.res

		self.speed = self.res * 10

		# functions
		pygame.display.set_caption("*CAPTION*")
	
	# tick function
	def tick(self):
		# handle events
		for event in pygame.event.get():
			
			# quit when window is closed
			if event.type == pygame.QUIT:
				self.quit()

			# handle key presses
			if event.type == pygame.KEYDOWN:
				# F12 quits
				if event.key == pygame.K_F12:
					self.quit()

				# F1 screenshots
				if event.key == pygame.K_F1:
					self.screenshot()

		for _ in range(0, int(self.speed)):
			m = [self.width / 2., self.height / 2.]
			P = getcirclepos(m, self.R + self.r * {"hypo" : -1, "epi" : 1}[CYCLOID], self.Phi)

			p = getcirclepos(P, self.r, self.phi)
			self.img.set_at(intpos(p), [255, 0, 0])

			self.phi += self.dphi
			self.Phi += self.dPhi

	# render function
	def render(self):
		# fill
		self.surface.fill([0, 0, 0])
		self.surface.blit(self.img, [0, 0])

		m = [self.width / 2., self.height / 2.]
		if self.gray:
			pygame.draw.circle(self.surface, [50, 50, 50], intpos(m), int(self.R), 1)
		P = getcirclepos(m, self.R + self.r * {"hypo" : -1, "epi" : 1}[CYCLOID], self.Phi)
		if self.gray:
			pygame.draw.circle(self.surface, [70, 70, 70], intpos(P), 3, 0)
			pygame.draw.circle(self.surface, [50, 50, 50], intpos(P), int(self.r), 1)

		p = getcirclepos(P, self.r, self.phi)
		if self.gray:
			pygame.draw.line(self.surface, [70, 70, 70], P, p)
			pygame.draw.circle(self.surface, [255, 255, 255], intpos(p), 3, 0)

		# blit and flip
		self.Screen.blit(self.surface, [0, 0])
		pygame.display.flip()
	
	# quits
	def quit(self):
		self.running = False

	# takes a screenshot
	def screenshot(self):
		path = os.getcwd() + "/out/"
		
		try:
			if not os.path.isdir(path):
				os.mkdir(path)
			
			name = "img" + str(len(os.listdir(path))) + " (k = " + str(self.k) + ", " + CYCLOID + ").png"
			pygame.image.save(self.surface, path + name)
		except:
			pass

	# run function (uses tick() and render())
	def run(self):
		ticksPerSecond = 60
		lastTime = time.time() * 1000000000
		nsPerTick =  1000000000.0 / float(ticksPerSecond)
	
		ticks = 0
		frames = 0
	
		lastTimer = time.time() * 1000
		delta = 0.0
		
		while self.running:
			now = time.time() * 1000000000
			delta += float(now - lastTime) / float(nsPerTick)
			lastTime = now
			shouldRender = False
					
			while delta >= 1:
				ticks += 1
				self.ticks += 1
				self.tick()
				delta -= 1
				shouldRender = True
			
			if shouldRender:
				frames += 1
				self.render()
			
			if time.time() * 1000 - lastTimer >= 1000:
				lastTimer += 1000
			
				frames = 0
				ticks = 0

# start game
G = GAME()
G.run()
Advertisements

One thought on “Cycloids

  1. Hi Jonathan,

    I like your blog and the big variety of interesting math themes! It’s fun to see how creative math can be!!!

    Best regards
    Sabine

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s