10.1 Your Personality and a Little Maths
This tutorial is based on some code that we ported from our iPhone App Personality Profile. In this App you use a dial to answer a number of questions and a profile is provided based on your selection. We used a dial because we wanted a touch friendly solution and this seemed like an intuitive approach. The App is FREE on iTunes so download it and compare the functionality of the Objective C version to our Lua implementation. The embedded video above gives you an idea of how it works but the effect is better understood when used on your iDevice.
The trick to working out how to move the dial is a mathematical one. In essence you need to convert the cartesian co-ordinates of your finger on the screen to equivalent polar co-ordinates.
It is about now that you realise that those maths lessons way back weren't completely useless and perhaps you should have paid more attention. Don't panic, all you need to know is that it is possible to convert from a point (x, y) to polar co-ordinates which consist of an angle and a distance. The angle tells you how much to rotate the dial and you can use the distance portion to work out whether a point is within a circle which is handy for detecting if a tap was on the dial.
To make life easy we created a function which does all the hard work. We have reproduced it below but note that you also need the math.hypot() function which isn't available in the Codea version of Lua. Both of these functions (and more) are contained in the Math tab of RSLibrary.
We have made use of the new Codea dependency functionality so if you download the entire tutorial code in one file, then you also need to download RSLibrary and link it as a dependency to DialDemo. See Tutorial 9 if you want to understand dependencies better or see what is in RSLibrary. For this tute you only need the Math and Button tabs but you might as well grab the whole thing.
As an aside, a handy trick which we use in the math.polar function is to assign a default value to a parameter using "or". This works because nil evaluates to false in Lua. See the code below to understand how you can use this to overload functions.
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
function math.polar(x, y, originX, originY) | |
-- Usage: math.polar(x, y) - origin is assumed at (0, 0) | |
-- math.polar(x, y, originX, originY) | |
-- | |
-- This function converts from cartesian co-ordinates (x, y) to | |
-- polar co-ordinates (distance, angle) using the two functions: | |
-- | |
-- 1. distance = hypot(x, y) - the hypotenuse of a right-angle triangle; and | |
-- 2. angle = atan2(x, y) - is the arc tangent of y/x in radians. | |
-- | |
-- Since nil evaluates to false in Lua, you can use "or" to assign a default value. | |
-- | |
-- Reefwing Software (www.reefwing.com.au) | |
-- Version 1.0 | |
local oX = originX or 0 | |
local oY = originY or 0 | |
local dx = x - oX | |
local dy = y - oY | |
local distance = math.hypot(dx, dy) | |
local angleInDegrees = math.deg(math.atan2(dy, dx)) | |
-- Functions may return multiple results in Lua. | |
return distance, angleInDegrees | |
end |
10.2 The Main Class
In this instance the Main class is just there to demonstrate the Dial Class. Both Landscape and Portrait orientations are supported using the orientationChanged() function.
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 | |
-- DialDemo | |
-- Define supported orientations | |
supportedOrientations(ANY) | |
-- Use this function to perform your initial setup | |
function setup() | |
-- Project Metadata | |
version = 1.0 | |
saveProjectInfo("Description", "Codea Dial Demonstration") | |
saveProjectInfo("Author", "Reefwing Software") | |
saveProjectInfo("Date", "26th July 2012") | |
saveProjectInfo("Version", version) | |
print("DialDemo v"..version.."\n") | |
-- Keep an eye on Frame Rate (Frames Per Second, FPS) so we can | |
-- compare and contrast different approaches. | |
FPS = 0 | |
watch("FPS") | |
-- Define a new dial = Dial(x, y, angle) | |
-- | |
-- Where: (x, y) are the CENTER co-ordinates of the dial. (default alignment is CENTER). | |
-- angle = the current angle (in degrees) of the dial. | |
dial = Dial(WIDTH/2, HEIGHT/2, 0) | |
-- Create a button which will zero the dial | |
-- The Button Class is contained within RSLibrary | |
local rbX = WIDTH/2 - dial.width/2 | |
local rbY = HEIGHT/2 - dial.height - 20 | |
resetButton = Button("Reset", rbX, rbY, dial.width, 50) | |
resetButton.action = function() resetButtonTapped() end | |
end | |
-- Drawing Functions | |
function draw() | |
-- Update instantaneous FPS | |
FPS = math.round(1/DeltaTime) | |
-- This sets a black background color | |
background(blackColor) | |
-- Draw the dial and reset button | |
dial:draw() | |
resetButton:draw() | |
end | |
function orientationChanged(newOrientation) | |
-- If the iPad orientation changes we need to | |
-- adjust object co-ordinates. | |
local rbX = WIDTH/2 - dial.width/2 | |
local rbY = HEIGHT/2 - dial.height - 20 | |
dial.x, dial.y = WIDTH/2, HEIGHT/2 | |
resetButton.x, resetButton.y = rbX, rbY | |
end | |
-- Handle Touches | |
function touched(touch) | |
dial:touched(touch) | |
resetButton:touched(touch) | |
end | |
function resetButtonTapped() | |
dial:reset() | |
end |
10.3 The Dial Class
The Dial Class is where the magic happens. We only have a licence to use the knob and dial images in our App, so you are going to have to draw or obtain your own version. Strictly speaking you only need a knob image (the bit that moves). The class should handle any differences in size to our sprites.
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
--# Dial | |
Dial = class() | |
-- A Dial Class | |
-- Reefwing Software (www.reefwing.com.au) | |
-- | |
-- 26 July 2012 | |
-- Version 1.0 | |
function Dial:init(x, y, angle) | |
-- These parameters are used to customise your Dial | |
self.x = x -- x screen co-ordinate for the dial | |
self.y = y -- y screen co-ordinate for the dial | |
self.alignment = CENTER -- Dial uses CENTER alignment | |
self.visible = true -- Boolean to indicate whether dial is drawn and touches handled | |
self.currentAngle = angle or 0 -- Current Angle of the dial in degrees (default 0) | |
dialImage = readImage("Dropbox:pDial200px") | |
knobImage = readImage("Dropbox:pKnob147px") | |
radius = knobImage.width/2 -- Radius of knob circle sprite | |
self.width = dialImage.width -- Defined by the dial sprite width | |
self.height = dialImage.height -- Defined by the dial sprite height | |
end | |
-- Draw function | |
function Dial:draw() | |
-- Codea does not automatically call this method | |
if self.visible then | |
sprite(dialImage, self.x, self.y) | |
pushMatrix() | |
rotateDialByDegrees(self.x, self.y, self.currentAngle) | |
sprite(knobImage, 0, 0) | |
popMatrix() | |
end | |
end | |
-- Touch Handler | |
function Dial:touched(touch) | |
-- Codea does not automatically call this method | |
-- | |
-- The pointInCircle() function determines if the touch was | |
-- on the dial. This function is contained in RSLibrary. | |
-- | |
-- math.polar(x, y) converts from cartesian co-ordinates (x, y) to polar | |
-- co-ordinates (distance, angle). We only use the returned angle not the | |
-- distance. This function is also contained in RSLibrary. | |
if self.visible and pointInCircle(touch.x, touch.y, self.x, self.y, radius) then | |
if touch.state == MOVING then | |
_, self.currentAngle = math.polar(touch.x, touch.y, self.x, self.y) | |
end | |
end | |
end | |
-- Utility Functions | |
function Dial:reset() | |
self.currentAngle = 0 | |
end | |
function rotateDialByDegrees(centreX, centreY, angle) | |
translate(centreX, centreY) | |
rotate(angle) | |
end |
No comments:
Post a Comment