Last active
December 21, 2015 04:49
-
-
Save loopspace/6252006 to your computer and use it in GitHub Desktop.
Pendulum Release v1.0 -A pendulum simulator.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Pendulum Tab Order Version: 1.0 | |
------------------------------ | |
This file should not be included in the Codea project. | |
#Main | |
#Pendulum | |
#ccConfig |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Pendulum Tab Order Version: 1 | |
------------------------------ | |
This file should not be included in the Codea project. | |
#Main | |
#Pendulum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
########################################### | |
##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]==================================== | |
--]] | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[==[ | |
-- 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