Skip to content

Instantly share code, notes, and snippets.

@loopspace
Last active December 21, 2015 04:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loopspace/6252006 to your computer and use it in GitHub Desktop.
Save loopspace/6252006 to your computer and use it in GitHub Desktop.
Pendulum Release v1.0 -A pendulum simulator.
Pendulum Tab Order Version: 1.0
------------------------------
This file should not be included in the Codea project.
#Main
#Pendulum
#ccConfig
Pendulum Tab Order Version: 1
------------------------------
This file should not be included in the Codea project.
#Main
#Pendulum
--[[
###########################################
##Codea Community Project Config Settings##
###########################################
##You can use # to comment out a line
##Use 1 for true and 0 for false
###########################################
# Add project info below #
#==========================================
ProjectName: Place Project Name Here
Version: Alpha 1.0
Comments:
Author:
##License Info: http://choosealicense.com
##Supported Licneses: MIT, GPL, Apache, NoLicense
License: MIT
#==========================================
###########################################
# Settings #
[Settings]=================================
##Codea Community Configuration settings
##Format: Setting state
Button 0
NotifyCCUpdate 1
ResetUserOption 0
AddHeaderInfo 1
Connect 0
[/Settings]================================
###########################################
# Screenshots #
[Screenshots]==============================
##Screenshots from your project.
##Format: url
##Example: http://www.dropbox.com/screenshot.jpg
[/Screenshots]=============================
###########################################
# Video #
[Video]====================================
##Link to a YouTube.com video.
##Format: url
##Example: http://www.youtube.com/videolink
[/Video]===================================
###########################################
# Dependencies #
[Dependencies]=============================
##Include the names of any dependencies here
##Format: Dependency
##Example: Codea Community
[/Dependencies]============================
############################################
# Tabs #
[Tabs]======================================
##Select which tabs are to be uploaded.
##Keyword 'not' excludes a tab or tabs. Keyword 'add' includes a tab or tabs.
##not * will exclude all tabs to allow for individual selection
##not *tab1 will look for any tab with tab1 on the end.
##not tab1* will look for any tab with tab1 at the beginning.
##Format: (add/not)(*)tab(*)
##Example: not Main --this will remove main.
##Example: not *tab1 --this will remove any tab with "tab1" on the end.
##Example: add Main --This will add Main back in.
[/Tabs]=====================================
#############################################
# Assets #
[Assets]=====================================
##Directory, path and url info for any assets besides the standard Codea assets.
##Format: Folder:sprite URL
##Example: Documents:sprite1 http://www.somewebsite.com/img.jpg
[/Assets]====================================
--]]
-- Main program
--DEBUG = true
VERSION = 1.0
supportedOrientations(LANDSCAPE_ANY)
if isRecording() then
stopRecording()
end
function setup()
autogist = AutoGist("Pendulum","A pendulum simulator.",VERSION,false)
autogist:backup(true)
cmodule "Pendulum"
cmodule.path("Library Base", "Library UI", "Library Utilities", "Library Graphics", "Library Maths")
local Touches = cimport "Touch"
local UI = cimport "UI"
local Debug = cimport "Debug"
local TrendGraph = cimport "TrendGraph"
local Colour = unpack(cimport "Colour")
local Playlist = cimport "Playlist"
local Pendulum = cimport "Pendulum"
cimport "ColourNames"
cimport "Slider"
if DEBUG then
debug = 0
watch("debug")
else
displayMode(FULLSCREEN_NO_BUTTONS)
end
touches = Touches()
ui = UI(touches)
ui.messages.active = false
ui:systemmenu()
debug = Debug({ui = ui})
graph = TrendGraph({
x = 0,
y = 0,
width = WIDTH,
height = 150,
title = "Displacement",
rate = 5
})
paused = false
local pivot = vec2(WIDTH/2,HEIGHT - 100)
p = Pendulum({
pivot = pivot,
length = 40,
mass = 3,
colour = Colour.svg.Purple,
touchHandler = touches,
angle = 0,
ui = ui,
graph = graph
})
q = Pendulum({
pivot = pivot,
length = 30,
mass = 3,
colour = Colour.svg.Blue,
touchHandler = touches,
angle = 0,
ui = ui,
graph = graph
})
p:couple(q)
q:couple(p)
ui:addInformation("This is a pendulum simulation. The two pendula can be set in motion under a variety of conditions (selectable from the menus), including using the \"small angle approximation\", friction, and coupling. The graph at the bottom shows the horizontal displacements of the bobs. The pendula can be moved by dragging the bobs, the program will also set the velocity from the movement (so to get a velocity of zero, pause before releasing for about a second.). Double-click this box to get it out of the way.")
playlist = Playlist({
ui = ui,
--skipTo = 90,
lastAction = stopRecording
})
playlist:addEvent({duration = 5, event =
function()
ui:addNotice("Starting recording.")
return true
end
})
playlist:addEvent({duration = 1, relative = true, event =
function()
startRecording()
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("First, a simple pendulum with the small-angle approximation and no friction.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 10, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
p.angle = 0
p.velocity = 0
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("Next, a comparison of the pendula with and without the small-angle approximation. The blue has the approximation, the purple is \"real\". First, a small angle.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 10, relative = true, event =
function()
q.length = p.length
q.angle = .2
q.velocity = 0
p.angle = .2
p.velocity = 0
p.shm = false
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("Now, a larger angle.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 10, relative = true, event =
function()
q.angle = 1
q.velocity = 0
p.angle = 1
p.velocity = 0
p.shm = false
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("The next examples add friction. For simplicity, we use the small-angle approximation. We start with light friction.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 10, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.applyFriction = true
q.friction = .5
p.angle = 0
p.velocity = 0
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("Now, with heavy friction.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.applyFriction = true
q.friction = 4
p.angle = 0
p.velocity = 0
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("Lastly, with critical damping.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q:critical()
p.angle = 0
p.velocity = 0
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
q.angle = 1
q.velocity = -3
q.shm = true
q:critical()
p.angle = 0
p.velocity = 0
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("We can also couple the pendula together. One can drive the other, or it can be a true two-way coupling. When one is driving the other it is best to select the \"absolute position\" method. We start with that situation.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 15, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.length = 30
q.applyFriction = false
p.angle = 0
p.velocity = 0
p.shm = true
p.coupled = true
p.absolute = true
p.applyFriction = false
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("If the pendula have the same frequency, we get resonance. This is another place where the small-angle approximation becomes important. We start with the approximation.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 50, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.length = p.length
q.applyFriction = false
p.angle = 0
p.velocity = 0
p.coupled = true
p.absolute = true
p.applyFriction = false
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("And now the \"real\" case.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 50, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = false
q.length = p.length
q.applyFriction = false
p.angle = 0
p.shm = false
p.velocity = 0
p.coupled = true
p.absolute = true
p.applyFriction = false
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("We can also add friction to the \"follower\" pendulum.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 30, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.length = 30
q.applyFriction = false
p.angle = 0
p.shm = true
p.velocity = 0
p.coupled = true
p.absolute = true
p.applyFriction = true
p.friction = .3
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("If we couple them together, we get a nice oscillation not only of the pendula, but also between them.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({duration = 50, relative = true, event =
function()
q.angle = 1
q.velocity = 0
q.shm = true
q.length = 30
p.length = 30
q.coupled = true
q.absolute = false
q.applyFriction = false
p.angle = 0
p.shm = true
p.velocity = 0
p.coupled = true
p.absolute = false
p.applyFriction = false
p.friction = 1
p:pause(false)
q:pause(false)
graph:pause(false)
return true
end
})
playlist:addEvent({duration = 5, relative = true, event =
function()
ui:addNotice("That's all for now, thanks for watching.")
p:pause(true)
q:pause(true)
graph:pause(true)
return true
end
})
playlist:addEvent({time = 0, relative = true, event =
function()
if isRecording() then
stopRecording()
end
return true
end
})
end
-- This function gets called once every frame
function draw()
background(0,0,0)
-- process touches and taps
touches:draw()
playlist:draw()
-- update elements
p:update()
q:update()
-- draw elements
graph:draw()
p:draw()
q:draw()
ui:draw()
debug:draw()
end
function touched(touch)
touches:addTouch(touch)
end
function fullscreen()
local od = displayMode()
if displayMode() == STANDARD then
displayMode(FULLSCREEN_NO_BUTTONS)
else
displayMode(STANDARD)
end
local d = displayMode()
p:fullscreen(od,d)
q:fullscreen(od,d)
end
function pause()
paused = not paused
p:pause(paused)
q:pause(paused)
graph:pause(paused)
end
function reset()
touches:reset()
p:reset()
q:reset()
ui:reset()
end
--[==[
-- Pendulum class
local Pendulum = class()
local Colour = unpack(cimport "Colour")
Pendulum.delta = 0.1
Pendulum.count = 0
Pendulum.rod = vec2(0,-10)
Pendulum.gravity = 9.81
function Pendulum:init(t)
Pendulum.count = Pendulum.count + 1
self.index = Pendulum.count
self.ui = t.ui
self.pivot = t.pivot
self.length = t.length
self.ilength = t.length
self.mass = t.mass
self.imass = t.mass
self.colour = t.colour
t.graph:addSeries(
"Pendulum" .. self.index,
100,
-1,
1,
0,
1,
1,
t.colour,
function()
return math.sin(self.angle)
end
)
t.touchHandler:pushHandler(self)
self.fillColour = Colour.shade(t.colour,50)
self.angle = t.angle
self.iangle = t.angle
self.velocity = 0
self.shm = true
self.applyFriction = false
self.friction = 0
self.touchVelocity = {}
self.coupled = false
self.driver = nil
self.absolute = false
self.coupling = 1
self.paused = false
local m = ui:addMenu({title = "Pendulum " .. self.index,attach = true})
self.menu = m
m.colour = Colour.tint(t.colour,50)
m:addItem({
title = "Set length",
action = function()
self.ui:getParameter(
self.length,
5,
80,
function(t)
self.length = t
return true
end
)
return true
end
})
m:addItem({
title = "Set mass",
action = function()
self.ui:getParameter(
self.mass,
1,
10,
function(t)
self.mass = t
return true
end
)
return true
end
})
m:addItem({
title = "Small-angle approximation",
action = function()
self.shm = not self.shm
return true
end,
highlight = function()
return self.shm
end
})
m:addItem({
title = "Use friction",
action = function()
self.applyFriction = not self.applyFriction
return true
end,
highlight = function()
return self.applyFriction
end
})
m:addItem({
title = "Friction",
action = function()
self.ui:getParameter(
self.friction,
0,
5,
function(t)
self.friction = t
return true
end,
function(t)
self.applyFriction = true
return true
end
)
return true
end
})
m:addItem({
title = "Critical damping",
action = function()
self:critical()
return true
end
})
m:addItem({
title = "Coupled",
action = function()
self.coupled = not self.coupled
return true
end,
highlight = function()
return self.coupled
end
})
m:addItem({
title = "Coupled by absolute position",
action = function()
self.coupled = true
self.absolute = not self.absolute
return true
end,
highlight = function()
return self.absolute
end
})
m:addItem({
title = "Coupling Constant",
action = function()
self.ui:getParameter(
self.coupling,
0,
5,
function(t)
self.coupling = t
return true
end,
function(t)
self.coupled = true
return true
end
)
return true
end
})
m:addItem({
title = "Pause",
action = function()
self.paused = not self.paused
return true
end,
highlight = function()
return self.paused
end
})
m:addItem({
title = "Halt",
action = function()
self:halt()
return true
end
})
m:addItem({
title = "Reset",
action = function()
self:reset()
return true
end
})
end
function Pendulum:draw()
pushStyle()
resetStyle()
if not self.paused then
local force, acceleration
if self.shm then
force = self.angle
else
force = math.sin(self.angle)
end
force = - self.mass * force * Pendulum.gravity
if self.applyFriction then
if self.shm then
force = force - self.friction * self.length * self.velocity
else
force = force - self.friction * self.length^2 * self.velocity * math.abs(self.velocity)
end
end
-- add coupling
if self.coupled and self.driver then
local ang = self.driverangle
if not self.absolute then
ang = ang - self.angle
end
if self.shm then
force = force + self.coupling*ang
elseif self.absolute then
force = force + self.coupling*math.sin(ang)
else
force = force + self.coupling*2*math.sin(ang/2)
end
end
acceleration = force / (self.mass * self.length)
self.velocity = self.velocity + Pendulum.delta * acceleration
self.angle = self.angle + Pendulum.delta * self.velocity
end
local r,w,c
r = self.pivot + self.length*Pendulum.rod:rotate(self.angle)
stroke(self.colour)
strokeWidth(10)
line(self.pivot.x,self.pivot.y,r.x,r.y)
w = math.sqrt(self.mass)*50
strokeWidth(5)
--stroke(self.colour)
fill(self.fillColour)
ellipse(r.x,r.y,w)
popStyle()
end
function Pendulum:isTouchedBy(touch)
if vec2(touch.x - self.pivot.x - self.length * 10 * math.sin(self.angle), touch.y - self.pivot.y + self.length * 10 * math.cos(self.angle)):lenSqr() < self.mass * 2500 then
return true
else
return false
end
end
function Pendulum:processTouches(g)
if g.updated then
if g.num == 1 then
local t = g.touchesArr[1]
if t.touch.state == BEGAN then
self.velocity = 0
self.paused = true
end
if t.touch.state == ENDED or t.touch.state == MOVING then
self.angle = math.atan2(self.pivot.y - t.touch.y, self.pivot.x - t.touch.x) - math.pi / 2
end
if t.touch.state == ENDED then
self.velocity = (t:velocity().x * math.cos(self.angle)+ t:velocity().y * math.sin(self.angle))/self.length*Pendulum.delta
self.paused = paused
g:reset()
end
end
g:noted()
end
end
function Pendulum:couple(p)
self.driver = p
end
function Pendulum:update()
if self.driver then
self.driverangle = self.driver.angle
end
end
function Pendulum:fullscreen(od,d)
if od == d then
return
end
local ow,w
if d == STANDARD then
w = 751
else
w = 1024
end
if od == STANDARD then
ow = 751
else
ow = 1024
end
self.pivot.x = self.pivot.x * w/ow
end
function Pendulum:pause(p)
self.paused = p
end
function Pendulum:reset()
self.length = self.ilength
self.mass = self.imass
self.angle = self.iangle
self.velocity = 0
self.shm = true
self.applyFriction = false
self.friction = 0
self.touchVelocity = {}
self.coupled = false
self.absolute = false
self.coupling = 1
self.paused = false
end
function Pendulum:halt()
self.angle = 0
self.velocity = 0
self.touchVelocity = {}
end
function Pendulum:critical()
self.applyFriction = true
self.friction = 2*self.mass * math.sqrt(Pendulum.gravity / self.length)
end
return Pendulum
--]==]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment