Drawing squircles in Lau for WatchMaker (using WatchMaker functions)

tl;dr I recently made a WatchMaker Android Wear watch face based on the ‘squircle’ (download here). This post covers the maths behind squircles and getting (x,y) co-ordinates for any given polar angle. Finally I reveal the secrets of functions in WatchMaker Lau script.

A couple of months ago I started to play with the WatchMaker Watch Face app for Android. This app makes it relatively simple to design your own watch faces for Android Wear. After my first foray in becoming a “watch [face] maker” I’ve gone on to customise other watch faces and also put together R∆D∆R, shown below.

R∆D∆R

As part of the WatchMaker app there is a Google+ Community which include a ‘request a watch face’ thread. A lot of the time people are just asking for copies of existing ‘real’ watches, which can result in beautiful replicas but don’t interest me much. However recently a request from Andrew Davis came in that intrigued me.

Calls to squircle

Squircles

Squircles are apparently the The Hottest Thing In Car Design Right Now and hitting Wikipedia you can find a basic equation to represent the path:

\left( x - a \right)^4 + \left( y - b \right)^4 = r^4

Given Watchmaker uses a Cartesian co-ordinate system with 0,0 at the centre (that is, with a = b = 0) you get Lamé’s special quartic. Delving into the recesses of my high schools maths I knew if I solved this equation with that of a line (using tan(ѳ) for a gradient I could get the intercept co-ordinates.

Intercept

So basically:

y=tan(f)x and y^4+x^4=a^4so

tan(f)^4x^4+x^4=a^4

And cue much scribbling and crumpled paper you get…

x=a/(tan(f)^4+1)^(1/4); and y=(a*tan(f))/(tan(f)^4+1)^(1/4);

Putting these equations into a spreadsheet and providing a range of angles gives:

Graphing in a spreadsheet it’s easy to adjust the variables and see the effect. In this case we only have a, the radius of a squircle. And it turns out a is not much to play with e.g. a = 20 and so on.

Wondering if I missed something I hit ‘google’ again and discovered on Wolfram Mathworld that there are ‘two incompatible definitions of the squircle’ (guess wikipedia needs updating), the second by Fernandez Guasti:

 s^2(x^2)/(k^2)(y^2)/(k^2)-((x^2)/(k^2)+(y^2)/(k^2))+1=0,

with squareness parameter s, where s=0 corresponds to a circle with radius k and s=1 to a square of side length 2k. This curve is actually semialgebraic, as it must be restricted to |x|,|y|≤k to exclude other branches.

Having scraped a solution for the first definition of a squircle I didn’t feel confident that I’d be able to do the same with the Guasti definition. Knowing of the existence of software for solving algebraic systems another ‘googling’ turned up the open source Maxima. So cue much keyboard bashing and head scratching to get:

x=(k*sqrt(-sqrt(-4*tan(f)^2*s^2+tan(f)^4+2*tan(f)^2+1)+tan(f)^2+1))/(sqrt(2)*tan(f)*s)

and

y=(k*sqrt(-sqrt(-4*tan(f)^2*s^2+tan(f)^4+2*tan(f)^2+1)+tan(f)^2+1))/(sqrt(2)*s)

The nice thing about Maxima is you can copy and paste solved equations directly into a spreadsheet and get them to work with little modification i.e. copying the x output can be pasted as =(k*sqrt(-sqrt(-4*tan(f)^2*s^2 + tan(f)^4 + 2*tan(f)^2 + 1) + tan(f)^2+1))/(sqrt(2)*tan(f)*s). To make your life even either you can use Named Ranges to define the constants. Putting some data in to this Google Sheet I was able to see how the definition of a Guasti Squircle renders, and importantly performs.

Something I wanted to do in Google Sheets, but couldn’t get the charting tools to do was to graph different Squircles with varying k and s values on the same x-y scatter graph, the idea being I could use this as the background image for my watch face, so exported to MS Excel I’ve dropped this file on to OneDrive if you want to have a look):

Squircles in Excel

WatchMaker functions and Lau scripting

WatchMaker makes it easy to add primitive elements to your watch face, and a host of predefined variables you can drop in. In previous faces I’ve designed I’ve dropped these into an element property. For example, rotations based on second, minute, hour hand and other device readings can be used as part of the element properties. Below in an example where I’m using the watch battery charge level expressed as a rotation {br} as the degrees in a radar element.

WatchMaker {br} property

So I could use the formula developed in the spreadsheet for the x, y position of an element using a rotation variable, but given I’ve got three rings and separate x and y values this didn’t seem clever particularly if I wanted to adjust the k and s values. Looking at the WatchMaker developer reference I could see you could define functions that return values, but despite my best efforts every time I tried to call these functions in a property nothing would happen. Trying to find a solution online I drew I blank, my breakthrough was to look at other people’s watches I’d imported into WatchMaker to see how they did it. Doing this I was able to finally work out what the documentation was trying to say … or at least what I think it should say. The solution was to define a set of global variables which are then triggered to update every millisecond or every second in a script file that runs on startup. The variables are then used in the element properties. I’ve included the script below after you can see the finished work, which you can download here.

Screenshot_2015-07-13-21-36-42[1]

-- setup some globals
-- var_ms used to prefix on_millisecond variables
var_s_s = 0.85
var_s_k = 230
var_ms_s_x = 0
var_ms_s_y = -var_s_k

-- var_s used to prefix on_second variables
var_m_s = 0.895
var_m_k = 180
var_s_m_x = 0
var_s_m_y = -var_m_k

var_h_s = 0.94
var_h_k = 130
var_s_h_x = 0
var_s_h_y = -var_h_k

-- adaptive layout for circular faces
var_round_face = 0
if ({around}) then
  var_s_s = 0.7
  var_s_k = 187
  var_ms_s_x = 0
  var_ms_s_y = -var_s_k

  var_m_s = 0.71
  var_m_k = 147
  var_s_m_x = 0
  var_s_m_y = -var_m_k

  var_h_s = 0.72
  var_h_k = 100
  var_s_h_x = 0
  var_s_h_y = -var_h_k
  var_round_face = 100
end

-- things to do each millisecond
function on_millisecond()
  var_ms_s_x = xpos({drss}, var_s_s, var_s_k)
  var_ms_s_y = ypos({drss}, var_s_s, var_s_k)
end

-- things to do every second
function on_second()
  var_s_m_x = xpos({drm}, var_m_s, var_m_k)
  var_s_m_y = ypos({drm}, var_m_s, var_m_k)
  var_s_h_x = xpos({drh}, var_h_s, var_h_k)
  var_s_h_y = ypos({drh}, var_h_s, var_h_k)
end

-- function to calc x position
function xpos(a, s, k)
  if (a == 0 or a == 180) then
    return 0
  elseif (a == 90 or a == 270) then
     pos = k
  else
    pos = (k*math.sqrt(-math.sqrt(math.tan(math.rad(a-90))^4-4*s^2*math.tan(math.rad(a-90))^2+2*math.tan(math.rad(a-90))^2+1)+math.tan(math.rad(a-90))^2+1))/(math.sqrt(2)*s*math.tan(math.rad(a-90)))
  end
  if (a>0 and a <90) then
    return -pos
  elseif (a>=90 and a<270) then
    return pos
  else
    return -pos
  end
end

-- function to calc y position
function ypos(a, s, k)
  if (a == 90 or a == 270) then
    return 0
  elseif (a == 0 or a == 180) then
    pos = k
  else
     pos = (k*math.sqrt(-math.sqrt(math.tan(math.rad(a-90))^4-4*s^2*math.tan(math.rad(a-90))^2+2*math.tan(math.rad(a-90))^2+1)+math.tan(math.rad(a-90))^2+1))/(math.sqrt(2)*s)
   end
 if (a>=0 and a <90) then
    return -pos
  elseif (a>=90 and a<270) then
    return pos
  else
    return -pos
  end
end

If you’ve made it this far, thank you :)