# Introduction

Amulet is a Lua-based toolkit for experimenting with interactive graphics and audio. It provides a cross-platform API for drawing graphics, playing audio and responding to user input, and a command-line interpreter for running Amulet scripts.

It tries to be simple to use, but without making too many assumptions about how you want to use it.

Amulet currently runs on the following platforms:

• Windows 7+ (32 bit)
• Mac OS X 10.6+ (64 bit)
• Linux (32 and 64 bit)
• HTML5
• iOS (armv7 and arm64)

Support for Android will be added at some point as well.

# How to use this document

If you have some programming experience, but are new to Lua then the Lua primer should bring you up to speed.

The quickstart tutorial introduces basic concepts and walks you through drawing things on screen, playing audio and responding to user input.

The online editor contains numerous examples you can experiment with from your browser.

If you see a function argument with square brackets around it in a function signature, then that argument is optional.

# Lua primer

Amulet code is written in Lua. The version of Lua used by default is LuaJIT-2 on Windows, Mac and Linux and Lua-5.1 on all other platforms.

What follows is a quick introduction to Lua. For more detail please see the Lua manual.

-- This is a single line comment

--[[ This is
a multi-line
comment ]]

Local variables (block scope):

local x = 3.14 -- a number
local str = "a string"
local str2 = [[
a
multi-line
string]]
local bool = true -- a bool, can also be false
local y = nil -- nil value
local v1, v2 = 1, 2 -- create two local variables at once

Global variables:

score = 0
title = "My Game"

If-then-else:

local x = 2
if x > 1 then
print("x > 1")
elseif x > 0 then
print("0 < x <= 0")
else
print("x <= 0")
end

The else part of an if-then-else executes only if the condition evaluates to false or nil.

While loop:

local n = 5
while n > 0 do
print(n)
n = n - 1
end
-- prints 5 4 3 2 1

Repeat-until loop:

local n = 0
repeat
n = n + 1
print(n)
until n == 5
-- prints 1 2 3 4 5

For loop:

for i = 1, 5 do
print(i)
end
-- prints 1 2 3 4 5
for j = 10, 1, -2 do
print(j)
end
-- prints 10 8 6 4 2

You can break out of a loop using the break statement:

for j = 1, 10 do
print(j)
if j == 5 then
break
end
end
-- prints 1 2 3 4 5

Operators:

The arithmetic operators are: +, -, *, /, ^ (exponent) and % (modulo).

The relational operators are: ==, ~= (not equal), <, >, <= and >=

The logical operators are: and, or and not.

The string concatenation operator is two dots (e.g. "abc".."def").

Tables:

Tables are the only data structure in Lua. They can be used as key-value maps or arrays.

Keys and values can be of any type except nil.

local t = {} -- create empty table
t["score"] = 10
t[1] = "foo"
t[true] = "x"

-- this creates a table with 2 string keys:
local t2 = {foo = "bar", baz = 123}

Special syntax is provided for string keys:

local t = {}
t.score = 10
print(t.score)

The # operator returns the length of an array and array indices start at 1 by default.

local arr = {3, 4, 5}
for i = 1, #arr do
print(arr[i])
end
-- prints 3, 4, 5
table.insert(arr, 6) -- appends 6 to end of arr
table.remove(arr, 1) -- removes the first element of arr
-- arr is now {4, 5, 6}

Setting a key's value to nil removes the key and indexing a missing key returns nil.

You can iterate over all key/value pairs using the pairs function. Note that the order is not preserved.

local t = {a = 1, b = 2, c = 3}
for k, v in pairs(t) do
print(k..":"..v)
end
-- prints a:1 c:3 b:2

To iterate over an array table, keeping the order, use ipairs:

local arr = {"a", "b", "c"}
for k, v in ipairs(arr) do
print(k..":"..v)
end
-- prints 1:a 2:b 3:c

Functions:

function factorial(n)
if n <= 1 then
return 1
else
return n * factorial(n-1)
end
end
print(factorial(3)) -- prints 6

Functions are values in Lua so they can be assigned to variables:

local add = function(a, b)
return a + b
end
print(add(1, 2)) -- prints 3

Special syntax is provided for adding functions to tables:

local t = {}
function t.say_hello()
print("hello")
end

This is equivalent to:

local t = {}
t.say_hello = function()
print("hello")
end

Special syntactic sugar allows you to use function fields like methods in object oriented languages:

The code:

function t:f()
...
end

is equivalent to:

function t.f(self)
...
end

and the code:

t:f()

is equivalent to:

t.f(t)

For example:

local t = {x = 3}
function t:print_x()
print(self.x)
end
t:print_x()   -- prints 3
t.x = 4
t:print_x()   -- prints 4

If a function call has only a single string or table argument, the parentheses can be omitted:

local
function append_z(str)
return str.."z"
end
print(append_z"xy") -- prints xyz

local
function sum(values)
local total = 0
for _, value in ipairs(values) do
total = total + value
end
end
print(sum{3, 1, 6}) -- prints 10

Functions may also return multiple values:

function f()
return 1, 2
end
local x, y = f()
print(x + y) -- prints 3

# Quickstart tutorial

## Installing Amulet

Windows:

Mac:

Linux:

Download and extract the Linux zip archive to a directory of your choice. Then add the directory to your PATH. The amulet executable is for x86_64. If you're running a 32 bit system, rename the file amulet.i686 to amulet.

Online editor: There is also an online editor which you can use without having to download or install anything. However be aware that there are some limitations when using the online editor: you can't load image or audio files and only one lua module is allowed per project. These restrictions do not apply when exporting to HTML from the desktop version.

## Running a script

Create a text file called main.lua containing the following:

log("It works!")

Open a terminal ("command prompt" on Windows) and change to the directory containing the file. Then type "amulet main.lua":

> amulet main.lua
main.lua:1: It works!

If you see the text "main.lua:1: It works!", Amulet is installed and working.

If you are using the online editor, just type this into the main text area and click "Run".

## Creating a window

Type the following into main.lua:

local win = am.window{
title = "Hi",
width = 400,
height = 300,
clear_color = vec4(1, 0, 0.5, 1)
}

and run it as before. This time a bright pink window should appear.

## Rendering text

Add the following line to main.lua after the line that creates the window:

win.scene = am.text("Hello!")

This assigns a scene graph to the window. The scene has a single text node. The window will render its scene graph each frame.

## Transformation nodes

Change the previous line to:

win.scene =
am.translate(150, 100)
^ am.scale(2)
^ am.text("Hello!")

This adds translate, scale and rotate nodes as parents of the text node. These nodes transform the position, size and rotation of all their children. The resulting scene graph looks like this:

The translate node moves its descendents to the right 150 and up 100 (by convention the y axis increases in upward direction). The scale node doubles its descendent's size and the rotate node rotates its descendents by 90 degrees (math.rad converts degrees to radians). Finally the text node renders some text to the screen.

When you run the program you should see something like this:

## Actions

Add the following to the end of main.lua:

win.scene:action(function(scene)
scene"rotate".angle = am.frame_time * 4
end)

When you run it the text will spin.

This code adds an action to the scene, which is a function that's run once per frame. Actions can be added to any scene node. In this case we've added it to the node win.scene, which is the top translate node of our scene graph. The node to which an action is attached is passed as an argument to the action function.

The line:

scene"rotate".angle = am.frame_time * 4

first finds a node with the tag "rotate" in the scene graph. By default nodes have tags that correspond to their names, so this returns the rotate node. You can also add your own tags to nodes using the tag method which we'll discuss in more detail in the next section.

Then we set the angle property of the rotate node to the current frame time (the time at the beginning of the frame, in seconds) times 4.

Note that scene"rotate" could also be written as scene("rotate"). The first form takes advantage of some Lua syntactic sugar that allows function parenthesis to be omitted if the argument is a single literal string.

Since this code is run each frame, it causes the text to spin.

Here is the complete code listing:

local win = am.window{
title = "Hi",
width = 400,
height = 300,
clear_color = vec4(1, 0, 0.5, 1)
}
win.scene =
am.translate(150, 100)
^ am.scale(2)
^ am.text("Hello!")
win.scene:action(function(scene)
scene"rotate".angle = am.frame_time * 4
end)

## Drawing shapes

Here is a simple program that draws 2 red circles on either side of a yellow square on a blue background.

local red = vec4(1, 0, 0, 1)
local blue = vec4(0, 0, 1, 1)
local yellow = vec4(1, 1, 0, 1)

local win = am.window{
title = "Hi",
width = 400,
height = 300,
clear_color = blue,
}

win.scene =
am.group()
^ {
am.translate(-150, 0)
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(150, 0)
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(0, -25)
^ am.rect(-50, -50, 50, 50, yellow)
}

This time, we've created variables for the different colours we'll need. In Amulet colours are 4-dimensional vectors. Each component of the vector represents the red, green, blue and alpha intensity of the colour and ranges from 0 to 1.

The scene graph has a group node at the top. group nodes don't have any effect on the rendering and are only used to group other nodes together. The group node has 3 children, each of which is a translate node with a shape child. The scene graph looks like this:

Instead of building the scene graph using the ^ operator as we've done above, we can also do it step-by-step using the append method, which adds a node to the child list of another node:

local circle1_node = am.translate(-150, 0)
circle1_node:append(am.circle(vec2(0, 0), 50, red))

local circle2_node = am.translate(150, 0)
circle2_node:append(am.circle(vec2(0, 0), 50, red))

local rect_node = am.translate(0, -25)
rect_node:append(am.rect(-50, -50, 50, 50, yellow))

local group_node = am.group()
group_node:append(circle1_node)
group_node:append(circle2_node)
group_node:append(rect_node)

win.scene = group_node

This results in the exact same scene graph.

## Responding to key presses

Let's change the above program so that the left circle only appears while the A key is down and the right circle only appears while the B key is down.

In order to distinguish the two circles in the scene graph we'll give them different tags.

Change the scene setup code to look like this:

win.scene =
am.group()
^ {
am.translate(-150, 0):tag"left_eye"
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(150, 0):tag"right_eye"
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(0, -25)
^ am.rect(-50, -50, 50, 50, yellow)
}

The only difference is the addition of :tag"left_eye" and :tag"right_eye". These add the tag "left_eye" to the translate node which is the parent of the left circle node and "right_eye" to the right translate node which is the parent of the right circle node.

win.scene:action(function(scene)
scene"left_eye".hidden = not win:key_down"a"
scene"right_eye".hidden = not win:key_down"b"
end)

The hidden field of a node determines whether it is drawn or not. Each frame we set the hidden field of the left and right translate nodes to whether the A or B keys are being pressed. win:key_down(X) returns true if key X was being held down at the start of the frame.

You may notice that the three shapes appear briefly when the window is first shown and then disappear immediately. This is because actions only start running in the next frame, so the shapes are only hidden on the second frame. To fix this we can add the following lines either before or after we add the action (it doesn't matter where):

win.scene"left_eye".hidden = true
win.scene"right_eye".hidden = true

Here is the complete code listing:

local red = vec4(1, 0, 0, 1)
local blue = vec4(0, 0, 1, 1)
local yellow = vec4(1, 1, 0, 1)

local win = am.window{
title = "Hi",
width = 400,
height = 300,
clear_color = blue,
}

win.scene =
am.group()
^ {
am.translate(-150, 0):tag"left_eye"
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(150, 0):tag"right_eye"
^ am.circle(vec2(0, 0), 50, red)
,
am.translate(0, -25)
^ am.rect(-50, -50, 50, 50, yellow)
}

win.scene:action(function(scene)
scene"left_eye".hidden = not win:key_down"a"
scene"right_eye".hidden = not win:key_down"b"
end)

win.scene"left_eye".hidden = true
win.scene"right_eye".hidden = true

## Drawing sprites

Sprites can be drawn using sprite nodes. To create a sprite node, pass the name of a .png or .jpg file to the am.sprite() function and add it to your scene graph.

Let's create a beach scene using the following two images:

Download these images and copy them to the same directory as your main.lua file. Then copy the following into main.lua:

local win = am.window{
title = "Beach",
width = 400,
height = 300,
}

win.scene =
am.group()
^ {
am.sprite"beach.jpg"
,
am.translate(0, -60)
^ am.sprite"ball.png"
}

Run the program and you should get something like this:

The children of any scene node are always drawn in order, so first the beach.jpg sprite node is drawn and then the ball.png sprite, with it's corresponding translation, is drawn. This ensures the ball is visible on the beach. If we draw the beach second it would obscure the ball.

If you are using the online editor then you won't be able to load image files. Instead you'll need to draw the sprites using text. See the "Beach ball" example in the online editor for an equivalent example that doesn't load any image files. See also the documentation for am.sprite for how to create sprites with text.

## Responding to mouse clicks

Let's make the ball bounce when we click it. We'll add a rotate node so we can make the ball spin when it's in the air. We'll also tag the ball's translate and rotate nodes so we can easily access them:

win.scene =
am.group()
^ {
am.sprite"beach.jpg"
,
am.translate(0, -60):tag"ballt"
^ am.rotate(0):tag"ballr"
^ am.sprite"ball.png"
}

Now add an action to animate the ball when it's clicked:

-- ball state variables:
local ball_pos = vec2(0, -60)
local ball_angle = 0
local velocity = vec2(0)
local spin = 0

-- constants:
local min_pos = vec2(-180, -60)
local max_pos = vec2(180, 500)
-- min and max impulse velocity:
local min_v = vec2(-50, 150)
local max_v = vec2(50, 300)
local gravity = vec2(0, -500)

win.scene:action(function(scene)
-- check if the left mouse button was pressed
if win:mouse_pressed"left" then
local mouse_pos = win:mouse_position()
-- check if the mouse click is on the ball
if math.distance(mouse_pos, ball_pos) < 50 then
-- compute a velocity based on click position
local dir = math.normalize(ball_pos - mouse_pos)
velocity = dir * 300
velocity = math.clamp(velocity, min_v, max_v)
-- set a random spin
spin = math.random() * 4 - 2
end
end

-- update the ball position
ball_pos = ball_pos + velocity * am.delta_time

-- if the ball is on the ground, set the
-- velocity and spin to zero.
if ball_pos.y <= -60 then
velocity = vec2(0)
spin = 0
end

-- clamp the ball position so it doesn't disappear
-- off the edge of the screen
ball_pos = math.clamp(ball_pos, min_pos, max_pos)

-- update the ball angle
ball_angle = ball_angle + spin * am.delta_time

-- update the ball translate and rotate nodes
scene"ballt".position2d = ball_pos
scene"ballr".angle = ball_angle

-- apply gravity to the velocity
velocity = velocity + gravity * am.delta_time
end)

First we create some variables to keep track of the ball's state. We need to track its position, angle, velocity and spin (angular velocity). The ball_pos and velocity variables are 2 dimensional vectors, since we want to track position and velocity along both the x and y axes. We could have made separate variables for the x and y components, but using a vec2 is more concise. Note that if the values of the x and y components of the vector are the same, we only need to give the value once, so we just need to write vec2(0) when initialising the velocity instead of vec2(0, 0).

We also create some constants that we'll need. We define the minimum and maximum positions of the ball (min_pos, max_pos). We also define the minimum and maximum impulse velocity to apply to the ball when it's clicked (min_v, max_v). And finally we create a constant for gravity.

Next comes the action itself. The comments in the body of the action should help you work out what's going on, but here are some things to note:

• win:mouse_pressed(button) can be used to check whether a mouse button was pressed in the last frame. button can be "left", "right" or "middle".
• win:mouse_position() returns the current mouse position as a vec2 value.
• math.distance computes the distance between two vectors.
• math.clamp clamps a value between two other values. It works with both numbers and vectors.
• math.random() returns a random number between 0 and 1.
• am.delta_time is the time since the last frame.
• Vector values can be added, subtracted and multiplied just like numbers. You can also combine numbers and vectors, for example when we multiply gravity by am.delta_time. The result of vec2(x, y) * c is vec2(x*c, y*c).

## Playing audio

To play a sound, attach a play action to a scene node in the current scene. For example to play a sound file bounce.ogg we could do the following:

scene:action(am.play("bounce.ogg"))

The file bounce.ogg must exist in the same directory as the script.

To have a sound loop, pass in a second argument of true, like so:

win.scene:action(am.play("ocean.ogg", true))

Note that Amulet only reads Ogg Vorbis audio files.

Here are some audio files you can try out with the beach ball game we made previously:

And here is the complete code listing for the beach ball game with sounds included. Note that we need an extra variable on_ground to keep track of whether the ball was on the ground, so we don't play the landing sound every frame.

local win = am.window{
title = "Beach",
width = 400,
height = 300,
}

win.scene =
am.group()
^ {
am.sprite"beach.jpg"
,
am.translate(0, -60):tag"ballt"
^ am.rotate(0):tag"ballr"
^ am.sprite"ball.png"
}

local ball_pos = vec2(0, -60)
local ball_angle = 0
local velocity = vec2(0)
local spin = 0
local min_pos = vec2(-180, -60)
local max_pos = vec2(180, 500)
local min_v = vec2(-50, 150)
local max_v = vec2(50, 300)
local gravity = vec2(0, -500)
local on_ground = true

win.scene:action(function(scene)
-- check if the left mouse button was pressed
if win:mouse_pressed"left" then
local mouse_pos = win:mouse_position()
-- check if the mouse click is on the ball
if math.distance(mouse_pos, ball_pos) < 50 then
-- compute a velocity based on click position
local dir = math.normalize(ball_pos - mouse_pos)
velocity = dir * 300
velocity = math.clamp(velocity, min_v, max_v)
-- set a random spin
spin = math.random() * 4 - 2
-- play bounce sound
scene:action(am.play("bounce.ogg"))
on_ground = false
end
end

-- update the ball position
ball_pos = ball_pos + velocity * am.delta_time

-- if the ball lands on the ground, set the
-- velocity and spin to zero.
if ball_pos.y <= -60 and not on_ground then
velocity = vec2(0)
spin = 0
-- play land sound
scene:action(am.play("land.ogg"))
on_ground = true
end

-- clamp the ball position so it doesn't disappear
-- off the edge of the screen
ball_pos = math.clamp(ball_pos, min_pos, max_pos)

-- update the ball angle
ball_angle = ball_angle + spin * am.delta_time

-- update the ball translate and rotate nodes
scene"ballt".position2d = ball_pos
scene"ballr".angle = ball_angle

-- apply gravity to the velocity
velocity = velocity + gravity * am.delta_time
end)

-- play ocean sound in a loop
win.scene:action(am.play("ocean.ogg", true))

# Math

## Vectors

Amulet has built-in support for 2, 3 or 4 dimensional vectors. Vectors are typically used to represent things like position, direction or velocity in 2 or 3 dimensional space. Representing RGBA colors is another common use of 4 dimensional vectors.

In Amulet vectors are immutable. This means that once you create a vector, its value cannot be changed. Instead you need to construct a new vector.

### Constructing vectors

To construct a vector use one of the functions vec2, vec3 or vec4. A vector may be constructed by passing its components as separate arguments to one of these functions, for example:

local velocity = vec3(1, 2, 3)

Passing a single number to a vector constructor will set all components of the vector to that value. For example:

local origin = vec2(0)

sets origin to the value vec2(0, 0).

It's also possible to construct a vector from a combination of other vectors and numbers. The new vector's components will be taken from the other vectors in the order they are passed in. For example:

local bottom_left = vec2(0)
local top_right = vec2(10, 100)
local rect = vec4(bottom_left, top_right)

sets rect to vec4(0, 0, 10, 100)

### Accessing vector components

There are multiple ways to access the components of a vector. The first component can be accessed using any of the fields x, r or s; the second using any of the fields y, g or t; the third using any of the fields z, b or p; and the fourth using any of the fields w, a or q. Here are some examples:

local color = vec4(0.1, 0.3, 0.7, 0.8)
print(color.r..", "..color.g..", "..color.b..", "..color.a)
local point = vec2(5, 2)
print("x="..point.x..", y="..point.y)

A vector's components can also be accessed with 1-based integer indices.

Vectors support the Lua length operator (#), which returns the number of components of the vector (not its magnitude). This allows for iterating through the components of a vector of unknown size, for example:

local v = vec3(10, 20, 30)
for i = 1, #v do
print(position[i])
end

### Swizzle fields

Another way to construct vectors is to recombine the components of an existing vector using swizzle fields, which are special fields whose names consist of a combination of any of the component field characters. A new vector containing the named components will be returned. For example:

local dir = vec3(1, 2, 3)
print(dir.xy)
print(dir.rggb)
print(dir.zzys)

Running the above code results in the following output:

vec2(1, 2)
vec4(1, 2, 2, 3)
vec4(3, 3, 2, 1)

Note: You can pass vectors, matrices and quaternions directly to print or other functions that expect strings and they will be formatted appropriately.

### Vector update syntax

Although you can't directly set the components of a vector, Amulet provides some syntactic sugar to make it easier to create a new vector from an existing vector that has only some fields modified. Say, for example, you had a 3 dimensional vector, v1, and you wanted to create a new vector, v2, that had the same components as v1, except for the y component, which you'd like to be 10. One way to do this would be to write:

v2 = vec3(v1.x, 10, v1.z)

but Amulet also allows you to write:

v2 = v1{y = 10}

You can use this syntax to "update" multiple components and it also supports swizzle fields. For example:

local v = vec4(1, 2, 3, 4)
v = v{x = 5, ba = vec2(6)}

This would set v to vec4(5, 2, 6, 6).

If the values of a swizzle field are going to be updated to the same value (as with ba above), you can just set the field to the value instead of constructing a vector. So the above could also have been written as:

v = v{x = 5, ba = 6}

### Vector arithmetic

You can do arithmetic with vectors using the standard operators +, -, * and /. If both operands are vectors then they should have the same size and the operation is applied in a component-wise fashion, yielding a new vector of the same size. If one operand is a number then the operation is applied to each component of the vector, yielding a new vector of the same size as the vector operand. For example:

print(vec2(3, 4) + 1)
print(vec3(30) / vec3(3, 10, 5))
print(2 * vec4(1, 2, 3, 4))

produces the following output:

vec2(4, 5)
vec3(10, 3, 6)
vec4(2, 4, 6, 8)

## Matrices

Amulet has built-in support for 2x2, 3x3 and 4x4 matrices. Matrices are typically used to represent transformations in 2 or 3 dimensional space such as rotation, scaling, translation or perspective projection.

Matrices, like vectors, are immutable.

### Constructing matrices

Use one of the functions mat2, mat3 or mat4 to construct a 2x2, 3x3 or 4x4 matrix.

Passing a single number argument to one of the matrix constructors generates a matrix with all diagonal elements equal to the number and all other elements equal to zero. For example mat3(1) constructs the 3x3 identity matrix:

$\begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}$

You can also pass the individual elements of the matrix as arguments to one of the constructors. These can either be numbers or vectors or a mix of the two. As the constructor arguments are consumed from left to right, the matrix is filled in column by column. For example:

local m = mat3(1, 2, 3,
4, 5, 6,
7, 8, 9)

sets m to the matrix:

$\begin{bmatrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{bmatrix}$

Here's another example:

local m = mat4(vec3(1, 2, 3), 4,
vec4(5, 6, 7, 8),
vec2(9, 10), vec2(11, 12),
13, 14, 15, 16)

This sets m to the matrix:

$\begin{bmatrix} 1 & 5 & 9 & 13 \\ 2 & 6 & 10 & 14 \\ 3 & 7 & 11 & 15 \\ 4 & 8 & 12 & 16 \end{bmatrix}$

Note: Matrix constructors are admittedly somewhat confusing, because when you write the matrix constructor in code the columns are layed out horizontally. This is however the convention used in the OpenGL Shader Language (GLSL).

A matrix can also be constructed by passing an existing matrix to one of the matrix construction functions. If the existing matrix is larger than the new one, the new matrix's elements come from the top-left corner of the existing matrix. Otherwise the top-left corner of the new matrix is filled with the contents of the existing matrix and the rest from the identity matrix. For example:

local m = mat4(mat2(1, 2, 3, 4))

will set m to the matrix:

$\begin{bmatrix} 1 & 3 & 0 & 0 \\ 2 & 4 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$

Finally a 3x3 or 4x4 rotation matrix can be constructed from a quaternion by passing the quaternion as the single argument to mat3 or mat4 (see quaternions).

### Accessing matrix components

The columns of a matrix can be accessed as vectors using 1-based integer indices. The Lua length operator can be used to determine the number of columns. For example:

local matrix = mat2(1, 0, 0, 2)
for i = 1, #matrix do
print(matrix[i])
end

This would produce the following output:

vec2(1, 0)
vec2(0, 2)

### Matrix arithmetic

As with vectors the +, -, * and / operators work with matrices too. When one operand is a number, the result is a new matrix of the same size with the operator applied to each element of the matrix. For example:

local m1 = 2 * mat2(1, 2, 3, 4)

sets m1 to the matrix:

$\begin{bmatrix} 2 & 6 \\ 4 & 8 \end{bmatrix}$

and:

local m2 = mat3(3) - 1

sets m2 to the matrix:

$\begin{bmatrix} 2 & -1 & -1 \\ -1 & 2 & -1 \\ -1 & -1 & 2 \end{bmatrix}$

When both operands are matrices, the + and - operators work in a similar way to vectors, with the operations applied component-wise. For example:

local m3 = mat2(1, 2, 3, 4) + mat2(0.1, 0.2, 0.3, 0.4)

sets m3 to the matrix:

$\begin{bmatrix} 1.1 & 3.3 \\ 2.2 & 4.4 \end{bmatrix}$

However, when both operands are matrices, the * operator computes the matrix product.

If the first operand is a vector and the second is a matrix, then the first operand is taken to be a row vector (a matrix with one row) and should have the same number of columns as the matrix. The result is the matrix product of the row vector and the matrix, which is another row vector.

Similarly if the first argument is a matrix and the second a vector, the vector is taken to be a column vector (a matrix with one column) and the result is the matrix product of the matrix and column vector, which is another column vector.

The / operator also works when both arguments are matrices and is equivalent to multiplying the first matrix by the inverse of the second.

## Quaternions

Quaternions are useful for representing 3D rotations.

Like vectors and matrices they are immutable.

### Constructing quaternions

The quat function is used to construct quaternions. The simplest way to construct a quaternion is to pass an angle (in radians) and a unit 3D vector representing the axis about which the rotation should occur. For example:

local q = quat(math.rad(45), vec3(0, 0, 1))

constructs a quaternion that represents a 45 degree rotation around the z axis. (math.rad converts degrees to radians).

If the axis argument is omitted then it is taken to be vec3(0, 0, 1), so the above is equivalent to:

local q = quat(math.rad(45))

This is a useful shortcut for 2D rotations in the xy plane.

A quaternion can also be constructed from Euler angles. Euler angles are rotations around the x, y and z axes, also known as pitch, yaw and roll. For example:

local q = quat(math.rad(30), math.rad(60), math.rad(20))

constructs a quaternion that represents the rotation you'd end up with if you first rotated 30 degrees around the x axis, then 60 degrees around the y axis and finally 20 degrees around the z axis.

If two unit vector arguments are given, then the quaternion represents the rotation that would be needed to rotate the one vector into into the other. For example:

local q = quat(vec3(1, 0, 0), vec3(0, 1, 0))

The above quaternion represents a rotation of 90 degrees in the xy plane, since it rotates a vector pointing along the x axis to one pointing along the y axis.

A quaternion can be constructed from a 3x3 or 4x4 matrix by passing the matrix as the single argument to quat.

A quaternion can also be converted to a 3x3 or 4x4 matrix by passing it as the single argument to the mat3 or mat4 functions (see mat-cons).

Finally a quaternion can be constructed from the coefficients of its real and imaginary parts:

local q = quat(w, x, y, z)

w is the real part and x, y and z are the coeffients of the imaginary numbers $$i$$, $$j$$ and $$k$$.

### Quaternion fields

The angle, axis, pitch, roll, yaw, w, x, y and z fields can be used to read the corresponding attributes of a quaternion.

Note: Quaternions use a normalized internal representation, so the value returned by a field might be different from the value used to construct the quaternion. Though the quaternion as a whole represents the equivalent rotation.

### Quaternion operations

Quaternions can be multiplied together using the * operator. The result of multiplying 2 quaternions is the rotation that results from applying the first quaternion's rotation followed by the second quaternion's rotation.

Multiplying a quaternion by a vector rotates the vector. For example:

local v1 = vec3(1, 0, 0)
local q = quat(math.rad(90), vec3(0, 0, 1))
local v2 = q * v1

would set v2 to the vector vec3(0, 1, 0), which is v1 rotated 90 degrees in the xy plain.

A vec2 can be rotated in a similar way (the z component is assumed to be zero and the z component of the result is dropped, yielding another vec2).

## Math functions

The following functions supplement the standard Lua math functions.

### math.sign(n)

Returns +1 if n > 0, -1 if n < 0 and 0 if n == 0.

### math.fract(v)

Returns the fractional part of v. If v is a vector it returns a vector of the same size with each component being the fractional part of the corresponding component in the original vector.

### math.clamp(v, min, max)

Clamps a value v between min and max. v, min and max may be vectors. In this case each component is clamped based on the corresponding components in min and max.

### math.randvec2()

Returns a vec2 with all components set to a random number between 0 and 1.

### math.randvec3()

Returns a vec3 with all components set to a random number between 0 and 1.

### math.randvec4()

Returns a vec4 with all components set to a random number between 0 and 1.

### math.dot(vector1, vector2)

Returns the dot product of two vectors. The vectors must have the same size.

### math.cross(vector1, vector2)

Returns the cross product of two 3 dimensional vectors.

### math.normalize(vector)

Returns the normalized form of a vector (i.e. the vector that points in the same direction, but whose length is 1). If the given vector has zero length, then a vector of the same size is returned whose first component is 1 and whose remaining components are 0.

### math.length(vector)

Returns the length of a vector.

### math.distance(vector1, vector2)

Returns the distance between two vectors.

### math.inverse(matrix)

Returns the inverse of a matrix.

### math.lookat(eye, center, up)

Creates a 4x4 view matrix at eye, looking in the direction of center with the y axis of the camera pointing in the same direction as up.

### math.perspective(fovy, aspect, near, far)

Creates a 4x4 matrix for a symmetric perspective-view frustum.

• fovy is the field of view in the y plain, in radians.
• aspect is typically the window width divided by its height.
• near and far are the distances of the near and far clipping plains from the camera (these should be positive).

### math.ortho(left, right, bottom, top [, near, far])

Creates a 4x4 orthographic projection matrix. near and far are the distance from the viewer of the near and far clipping plains (negative means behind the viewer). Their default values are -1 and 1.

### math.oblique(angle, zscale, left, right, bottom, top [, near, far])

Creates a 4x4 oblique projection matrix. near and far are the distance from the viewer of the near and far clipping plains (negative means behind the viewer). Their default values are -1 and 1.

### math.translate4(pos)

Creates a 4x4 translation matrix. pos should be a vec3.

### math.scale4(scl)

Creates a 4x4 scale matrix. scl should be a vec3.

### math.perlin(pos [, period])

Generate perlin noise. pos can be a 2, 3, or 4 dimensional vector, or a number. If the second argument is supplied then the noise will be periodic with the given period. period should be of the same type as pos and its components should be integers greater than 1.

The returned value is between -1 and 1.

### math.simplex(pos)

Generate simplex noise. pos can be a 2, 3, or 4 dimensional vector, or a number.

The returned value is between -1 and 1.

### math.mix(from, top, t)

Returns the linear interpolation between from and to determined by t. from and to can be numbers or vectors, and must be the same type. t should be a number between 0 and 1. from and to can also be quaternions. In this case math.mix returns the spherical linear interpolation of the two quaternions.

### math.slerp(from, top, t)

Returns the spherical linear interpolation of the two quaternions from and to. t should be a number between 0 and 1. Unlike (math.mix)[#math.mix] this interpolation takes the shortest path.

# Buffers and views

Buffers are contiguous blocks of memory. They are used for storing images, audio and vertex data, or anything else you like.

You can't access a buffer's memory directly. Instead you access a buffer through a view. Views provide a typed array-like interface to the buffer.

## Buffers

### am.buffer(size)

Returns a new buffer of the given size in bytes.

The buffer's memory will be zeroed.

The # operator can be used to retrieve the size of a buffer in bytes.

Fields:

• dataptr: Returns a pointer to the buffer as a Lua lightuserdata value. The intended use for this is to manipulate the buffer using the LuaJIT FFI library.

Methods:

• mark_dirty(): Mark the buffer dirty. This should be called if you update the buffer using the dataptr field. This will cause data to be copied to any textures or vbos that depend on the buffer when next they are drawn. Note that you don't need to call this method if you're not using dataptr to update the buffer, for example if you're updating it through a view - in that case the buffer will automatically be marked dirty.

Loads the given file and returns a buffer containing the file's data, or nil if the file wasn't found.

### am.base64_encode(buffer)

Returns a base64 encoding of a buffer as a string.

### am.base64_decode(string)

Converts a base64 string to a buffer.

## Views

### buffer:view(type [, offset [, stride [, count]]])

Returns a view into buffer.

type can be one of the following:

type size (bytes) Lua value range internal range endianess
"float" 4 approx -3.4e38 to 3.4e38 same native
"vec2" 8 any vec2 same native
"vec3" 12 any vec3 same native
"vec4" 16 any vec4 same native
"byte" 1 -128 to 127 same N/A
"ubyte" 1 0 to 255 same N/A
"byte_norm" 1 -1.0 to 1.0 -127 to 127 N/A
"ubyte_norm" 1 0.0 to 1.0 0 to 255 N/A
"short" 2 -32768 to 32767 same native
"ushort" 2 0 to 65535 same native
"short_norm" 2 -1.0 to 1.0 -32767 to 32767 native
"ushort_norm" 2 0.0 to 1.0 0 to 65535 native
"ushort_elem" 2 1 to 65536 0 to 65535 native
"int" 4 -2147483648 to 2147483647 same native
"uint" 4 0 to 4294967295 same native
"uint_elem" 4 1 to 4294967296 0 to 4294967295 native

The _norm types map Lua numbers in the range -1 to 1 (or 0 to 1 for unsigned types) to integer values in the buffer.

The _elem types are specifically for element array buffers and offset the Lua numbers by 1 to conform to the Lua convention of array indices starting at 1.

All view types currently use the native platform endianess, which happens to be little-endian on all currently supported platforms.

The offset argument is the byte offset of the first element of the view. The default is 0.

The stride argument is the distance between consecutive values in the view, in bytes. The default is the size of the view type.

The count argument determines the number of elements in the view. The underlying buffer must be large enough to accommodate the elements with the given stride. The default is the maximum supported by the buffer with the given stride.

You can read and write to views as if they were Lua arrays (as with Lua arrays, indices start at 1). For example:

local buf = am.buffer(12)
local view = buf:view("float")
view[1] = 1.5
view[2] = view[1] + 2

Attempting to read an index less than 1 or larger than the number of elements will return nil.

You can retrieve the number of elements in a view using the # operator.

## View fields

### view.buffer

The buffer associated with the view.

## View methods

### view:slice(n [, count])

Returns a new view of the same type as view that references the same buffer, but which starts at the nth element of view and continues for count elements. If count is omitted it is #view - n + 1 (i.e. it covers all the elements of view after and including the nth).

### view:set(val [, start [, count]])

Bulk sets values in a view. This is faster than setting them one at a time.

If val is a number or vector then this sets all elements of view to val.

If val is a table then the elements are set to their corresponding values from the table.

As a special case, if val is a table of numbers and the view's type is a vector, then the elements of the table will be used to set the components of the vectors in the view. For example:

local verts = am.buffer(24):view("vec3")
verts:set{1, 2, 3, 4, 5, 6}
print(verts[1]) -- vec3(1, 2, 3)
print(verts[2]) -- vec3(4, 5, 6)

Finally if val is another view then the elements are set to the corresponding values from that view. The views may be of different types as long as they are "compatible". The types are converted as if each element were set using the Lua code view1[i] = view2[i]. This means you can't set a number view to a vector view or vice versa.

If start is given then only elements at that index and beyond will be set. The default value for start is 1.

If count is given then at most that many elements will be set.

### am.float_array(table)

Returns a float view to a newly created buffer and fills it with the values in the given table.

### am.byte_array(table)

Returns a byte view to a newly created buffer and fills it with the values in the given table.

### am.ubyte_array(table)

Returns a ubyte view to a newly created buffer and fills it with the values in the given table.

### am.byte_norm_array(table)

Returns a byte_norm view to a newly created buffer and fills it with the values in the given table.

### am.ubyte_norm_array(table)

Returns a ubyte_norm view to a newly created buffer and fills it with the values in the given table.

### am.short_array(table)

Returns a short view to a newly created buffer and fills it with the values in the given table.

### am.ushort_array(table)

Returns a ushort view to a newly created buffer and fills it with the values in the given table.

### am.short_norm_array(table)

Returns a short_norm view to a newly created buffer and fills it with the values in the given table.

### am.ushort_norm_array(table)

Returns a ushort_norm view to a newly created buffer and fills it with the values in the given table.

### am.int_array(table)

Returns an int view to a newly created buffer and fills it with the values in the given table.

### am.uint_array(table)

Returns a uint view to a newly created buffer and fills it with the values in the given table.

### am.int_norm_array(table)

Returns an int_norm view to a newly created buffer and fills it with the values in the given table.

### am.uint_norm_array(table)

Returns a uint_norm view to a newly created buffer and fills it with the values in the given table.

### am.ushort_elem_array(table)

Returns a ushort_elem view to a newly created buffer and fills it with the values in the given table.

### am.uint_elem_array(table)

Returns a uint_elem view to a newly created buffer and fills it with the values in the given table.

### am.vec2_array(table)

Returns a vec2 view to a newly created buffer and fills it with the values in the given table.

The table may contain either vec2s or numbers (though not a mix). If the table contains numbers they are used for the vector components and the resulting view will have half the number of elements as there are numbers in the table.

### am.vec3_array(table)

Returns a vec3 view to a newly created buffer and fills it with the values in the given table.

The table may contain either vec3s or numbers (though not a mix). If the table contains numbers they are used for the vector components and the resulting view will have a third the number of elements as there are numbers in the table.

### am.vec4_array(table)

Returns a vec4 view to a newly created buffer containing the values in the given table.

The table may contain either vec4s or numbers (though not a mix). If the table contains numbers they are used for the vector components and the resulting view will have a quarter the number of elements as there are numbers in the table.

### am.struct_array(size, spec)

Returns a table of views of the given size as defined by spec. spec is a sequence of view name (a string) and view type (also a string) pairs. The returned table can be passed directly to the am.bind function. The views all use the same underlying buffer.

For example:

local arr = am.struct_array(3, {"vert", "vec2", "color", "vec4"})
arr.vert:set{vec2(-1, 0), vec2(1, 0), vec2(0, 1)}
arr.color:set(vec4(1, 0, 0.5, 1))

# Windows and input

## Creating a window

### am.window(settings)

Creates a new window and returns a handle to it. settings is a table with any of the following fields:

• mode: Either "windowed" or "fullscreen". A fullscreen window will have the same resolution as the user's desktop. The default is "windowed". Not all platforms support windowed mode (e.g. iOS). On these platforms this setting is ignored.

• width and height: The desired size of the window. This is not necessarily the size of the window in pixels (although it usually is if the window is created in "windowed" mode). Instead it defines the size of the window's default coordinate system. If letterboxing is enabled then this is (-width/2, -height/2) in the bottom-left corner and (width/2, height/2) in the top-right corner. If letterboxing is disabled, then the coordinate system will extend in the horizontal or vertical directions to ensure an area of at least width×height is visible in the center of the window. In either case the centre coordinate will always be (0, 0). The default size is 640×480.

• title: The window title.

• resizable: Whether the window can be resized by the user (true or false, default true).

• borderless: Whether the window has a title bar and border (true or false, default false).

• highdpi: Whether to use high DPI resolution if available (true or false, default false).

• depth_buffer: Whether the window has a depth buffer (true or false, default false).

• stencil_buffer: Whether the window has a stencil buffer (true or false, default false).

• lock_pointer: true or false. When pointer lock is enabled the cursor will be hidden and mouse movement will be set to "relative" mode. In this mode the mouse is tracked infinitely in all directions, i.e. as if there is no edge of the screen to stop the mouse cursor. This is useful for implementing first-person style mouse-look. The default is false.

• show_cursor: Whether to show the mouse cursor (true or false, default true).

• clear_color: The color (a vec4) used to clear the window each frame before drawing. The default clear color is black (vec4(0, 0, 0, 1)).

• letterbox: true or false. Indicates whether the original aspect ratio (as determined by the width and height settings of the window) should be maintained after a resize by adding black horizontal or vertical bars to the sides of the window. The default is true.

• msaa_samples: The number of samples to use for multisample anti-aliasing. This must be a power of 2. Use zero (the default) for no anti-aliasing.

• orientation: "portrait" or "landscape". This specifies the supported orientation of the window on platforms that support orientation changes (e.g. iOS). If omitted, both orientations are supported.

• projection: A custom projection matrix (a mat4) to be used for the window's coordinate system. This matrix is used when transforming mouse or touch event coordinates and is set as the projection matrix for rendering, but does not affect the left, right, top, bottom, width and height fields of the window.

## Window fields

### window.left

The x coordinate of the left edge of the window in the window's default coordinate system.

### window.right

The x coordinate of the right edge of the window, in the window's default coordinate system.

### window.bottom

The y coordinate of the bottom edge of the window, in the window's default coordinate system.

### window.top

The y coordinate of the top edge of the window, in the window's default coordinate system.

### window.width

The width of the window in the window's default coordinate system. This will always be equal to the width setting supplied when the window was created if the letterbox setting is enabled. Otherwise it may be larger, but it will never be smaller than the width setting.

### window.height

The height of the window in the window's default coordinate space. This will always be equal to the height setting supplied when the window was created if the letterbox setting is enabled. Otherwise it may be larger, but it will never be smaller than the height setting.

### window.pixel_width

The real width of the window in pixels.

### window.pixel_height

The real height of the window in pixels

### window.mode

See window settings.

Updatable.

### window.clear_color

See window settings.

Updatable.

### window.letterbox

See window settings.

Updatable.

### window.lock_pointer

See window settings.

Updatable.

### window.show_cursor

See window settings.

Updatable.

### window.scene

The scene node currently attached to the window. This scene will be rendered to the window each frame.

Updatable.

### window.projection

See window settings.

Updatable.

## Closing a window

### window:close()

Closes the window and quits the application if this was the only window.

## Detecting when a window resizes

### window:resized()

Returns true if the window's size changed since the last frame.

## Detecting key presses

The following functions detect physical key states and are not affected by the keyboard layout preferences selected in the OS (except when targetting HTML).

Keys are represented by one of the following strings:

• "a"
• "b"
• "c"
• "d"
• "e"
• "f"
• "g"
• "h"
• "i"
• "j"
• "k"
• "l"
• "m"
• "n"
• "o"
• "p"
• "q"
• "r"
• "s"
• "t"
• "u"
• "v"
• "w"
• "x"
• "y"
• "z"
• "1"
• "2"
• "3"
• "4"
• "5"
• "6"
• "7"
• "8"
• "9"
• "0"
• "enter"
• "escape"
• "backspace"
• "tab"
• "space"
• "minus"
• "equals"
• "leftbracket"
• "rightbracket"
• "backslash"
• "semicolon"
• "quote"
• "backquote"
• "comma"
• "period"
• "slash"
• "capslock"
• "f1"
• "f2"
• "f3"
• "f4"
• "f5"
• "f6"
• "f7"
• "f8"
• "f9"
• "f10"
• "f11"
• "f12"
• "printscreen"
• "scrolllock"
• "pause"
• "insert"
• "home"
• "pageup"
• "delete"
• "end"
• "pagedown"
• "right"
• "left"
• "down"
• "up"
• "numlock"
• "kp_divide"
• "kp_multiply"
• "kp_minus"
• "kp_plus"
• "kp_enter"
• "kp_1"
• "kp_2"
• "kp_3"
• "kp_4"
• "kp_5"
• "kp_6"
• "kp_7"
• "kp_8"
• "kp_9"
• "kp_0"
• "kp_period"
• "lctrl"
• "lshift"
• "lalt"
• "lgui"
• "rctrl"
• "rshift"
• "ralt"
• "rgui"

Keys not listed above are represented as a hash followed by the scancode, for example "#101".

### window:key_down(key)

Returns true if the given key was down at the start of the current frame.

### window:keys_down()

Returns an array of the keys that were down at the start of the current frame.

### window:key_pressed(key)

Returns true if the given key's state changed from up to down since the last frame.

Note that if key_pressed returns true for a particular key, then key_down will also return true. Also if key_pressed returns true for a particular key then key_released will return false for the same key. (If necessary, Amulet will postpone key release events to the next frame to ensure this.)

### window:keys_pressed(key)

Returns an array of all the keys whose state changed from up to down since the last frame.

### window:key_released(key)

Returns true if the given key's state changed from down to up since the last frame.

Note that if key_released returns true for a particular key, then key_down will return false. Also if key_released returns true for a particular key then key_pressed will return false. (If necessary, Amulet will postpone key press events to the next frame to ensure this.)

### window:keys_released(key)

Returns an array of all the keys whose state changed from down to up since the last frame.

## Detecting mouse events

### window:mouse_position()

Returns the position of the mouse cursor, as a vec2, in the window's coordinate system.

### window:mouse_norm_position()

Returns the position of the mouse cursor in normalized device coordinates, as a vec2.

### window:mouse_pixel_position()

Returns the position of the mouse cursor in pixels where the bottom left corner of the window has coordinate (0, 0), as a vec2.

### window:mouse_delta()

Returns the change in mouse position since the last frame, in the window's coordinate system (a vec2).

### window:mouse_norm_delta()

Returns the change in mouse position since the last frame, in normalized device coordinates (a vec2).

### window:mouse_pixel_delta()

Returns the change in mouse position since the last frame, in pixels (a vec2).

### window:mouse_down(button)

Returns true if the given button was down at the start of the frame. button may be "left", "right" or "middle".

### window:mouse_pressed(button)

Returns true if the given mouse button's state changed from up to down since the last frame. button may be "left", "right" or "middle".

Note that if mouse_pressed returns true for a particular button then mouse_down will also return true. Also if mouse_pressed returns true for a particular button then mouse_released will return false. (If necessary, Amulet will postpone button release events to the next frame to ensure this.)

### window:mouse_released(button)

Returns true if the given mouse button's state changed from down to up since the last frame. button may be "left", "right" or "middle".

Note that if mouse_released returns true for a particular button then mouse_down will return false. Also if mouse_released returns true for a particular button then mouse_pressed will return false. (If necessary, Amulet will postpone button press events to the next frame to ensure this.)

### window:mouse_wheel()

Returns the mouse scroll wheel position (a vec2).

### window:mouse_wheel_delta()

Returns the change in mouse scroll wheel position since the last frame (a vec2).

## Detecting touch events

### window:touches_began()

Returns an array of the touches that began since the last frame.

Each touch is an integer and each time a new touch occurs the lowest available integer greater than or equal to 1 is assigned to the touch.

If there are no other active touches then the next touch will always be 1, so if your interface only expects a single touch at a time, you can just use 1 for all touch functions that take a touch argument and any additional touches will be ignored.

### window:touches_ended()

Returns an array of the touches that ended since the last frame.

See window:touches_began for more info about the returned touches.

### window:active_touches()

Returns an array of the currently active touches.

See window:touches_began for more info about the returned touches.

### window:touch_began([touch])

Returns true if the specific touch began since the last frame.

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_ended([touch])

Returns true if the specific touch ended since the last frame.

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_active([touch])

Returns true if the specific touch is active.

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_position([touch])

Returns the last touch position in the window's coordinate system (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_norm_position([touch])

Returns the last touch position in normalized device coordinates (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_pixel_position([touch])

Returns the last touch position in pixels, where the bottom left corner of the window is (0, 0) (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_delta([touch])

Returns the change in touch position since the last frame in the window's coordinate system (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_norm_delta([touch])

Returns the change in touch position since the last frame in normalized device coordinates (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_pixel_delta([touch])

Returns the change in touch position since the last frame in pixels (a vec2).

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_force([touch])

Returns the force of the touch where 0 means no force and 1 is "average" force. Harder presses will result in values larger than 1.

The default value for touch is 1.

See window:touches_began for additional notes on the touch argument.

### window:touch_force_available()

Returns true if touch force is supported on the device.

# Scenes

Scene graphs are how you draw graphics in Amulet. Scene nodes are connected together to form a graph which is attached to a window (via the window's scene field). The window will then render the scene graph each frame.

Scene nodes correspond to graphics commands. They either change the rendering state in some way, for example by applying a transformation or changing the blend mode, or execute a draw command, which renders something to the screen.

Each scene node has a list of children, which are rendered in order.

A scene node may be the child of multiple other scene nodes, so in general a scene node does not have a unique parent. Cycles are also allowed and are handled by imposing a limit to the number of times a node can be recursively rendered.

## Scene graph construction syntax

Special syntax is provided for constructing scene graphs from nodes. The expression:

node1 ^ node2

adds node2 as a child of node1 and returns node1. The resulting scene graph looks like this:

The expression:

node1 ^ { node2, node3 }

adds both node2 and node3 as children of node1 and returns node1:

The expression:

node1 ^ { node2, node3 } ^ node4

does the same as the previous expression, except node4 is added as a child of both node2 and node3:

If node2 or node3 were graphs with multiple nodes, then node4 would be added to the leaf nodes of those graphs.

Here is a more complex example:

node1
^ node2
^ {
node3
^ node4
,
node5
^ {node6, node7, node8}
}
^ node9
^ node10

The above expression results in the following graph:

## Scene node common fields and methods

The following fields and methods are common to all scene nodes.

### node.hidden

Determines whether the node and its children are rendered. The default is false, meaning that the node is rendered.

Updatable.

### node.paused

Determines whether the node and its children's actions are executed. The default is false, meaning the actions are executed.

Note that a descendent node's actions might still be executed if it is has another, non-paused, parent.

Updatable.

### node.num_children

Returns the node's child count.

### node.recursion_limit

This determines the number of times the node will be rendered recursively when part of a cycle in the scene graph. The default is 8.

Updatable.

### node:tag(tagname)

Adds a tag to a node and returns the node. tagname should be a string.

Note that most scene nodes receive a default tag name when they are created. See the documentation of the different nodes below for what these default tags are.

No more than 65535 unique tag names may be created in a single application.

### node:untag(tagname)

Removes a tag from a node and returns the node.

### node:all(tagname)

Searches node and all its descendents for any nodes with the tag tagname and returns them as a table.

### node(tagname)

Searches node and its descendents for tagname and returns the first matching node found, or nil if no matching nodes were found. The search is depth-first left-to-right.

The found node's parent is also returned as a second value, unless the found node was the root of the given subgraph.

### node:action([id,] action)

Attaches an action to a node and returns the node.

action may be a function or a coroutine.

The action function will be called exactly once per frame as long as the node is part of a scene graph that is attached to a window. If a coroutine is used, it will be run until it yields or finishes. The action will be run for the first time on the frame after it was attached to the node.

The action function may accept a single argument which is the node to which it is attached. If a coroutine is used, then the node is returned by coroutine.yield().

If the action function returns true then the action will be removed from the node and not run again. Similarly if a coroutine yields true or finishes.

Each action has an ID. If the id argument is omitted, then its ID is the value of the action argument. If present, id may be a value of any type besides nil, a function or coroutine (typically it's a string).

Multiple actions may be attached to a scene node, but they must all have unique ids. If you attempt to attach an action with an ID that is already used by another action on the same node, then the other action will be removed before the new one is attached.

The order that actions are run is determined by the node's position in the scene graph. Each node is visited in depth-first, left-to-right order and the actions on each node are run in the order they were added to the node. Each action is never run more than once per frame, even if the node occurs multiple times in the graph or is part of a cycle. For example, given the following scene graph:

The nodes will be visited in this order:

• node1
• node2
• node4
• node5
• node7
• node6
• node3

Note that the action execution order is determined before the first action runs each frame and is not affected by any modifications to the scene graph made by actions running during the frame. Any modifications to the scene graph will only affect the order of actions in subsequent frames.

### node:late_action([id,] action)

Attach a late action to a scene node. Late actions are the same as normal actions, except they are run after all normal actions are finished.

### node:cancel(id)

Cancels an action.

### node:append(child)

Appends child to the end of node's child list and returns node.

### node:prepend(child)

Adds child to the start of node's child list and returns node.

### node:remove(child)

Removes the first occurrence of child from node's child list and returns node.

### node:remove(tagname)

Searches for a node with tag tagname in the descendents of node and removes the first one it finds. Then returns node.

### node:remove_all()

Removes all of node's children and returns node.

### node:replace(child, replacement)

Replaces the first occurrence of child with replacement in node's child list and returns node.

### node:replace(tagname, replacement)

Searches for a node with tag tagname in the descendents of node and replaces the first one it finds with replacement. Then returns node.

### node:child(n)

Returns the nth child of node (or nil if there is no such child).

### node:child_pairs()

Returns an iterator over all node's children. For example:

for i, child in node:child_pairs() do
-- do something with child
end

## Basic nodes

### am.group(children)

Group nodes are only for grouping child nodes under a common parent. They have no other effect. The children can be passed in as a table.

Default tag: "group".

Example:

local group_node = am.group{node1, node2, node3}

### am.text([font, ] string [, color] [, halign [, valign]])

Renders some text.

font is an object generated using the sprite packing tool. If omitted, the default font will be used, which is a monospace font of size 16px.

color should be a vec4. The default color is white.

halign and valign specify horizontal and vertical alignment. The allowed values for halign are "left", "right" and "center". The allowed values for valign are "bottom", "top" and "center". The default in both cases is "center".

Fields:

• text: The text to display. Updatable.
• color: The color of the text. Updatable.
• width: The width of the displayed text in pixels. Readonly.
• height: The height of the displayed text in pixels. Readonly.

Default tag: "text".

### am.sprite(source [, color] [, halign [, valign]])

Renders a sprite (an image).

source can be either a filename, an ASCII art string or a sprite spec.

When source is a filename, that file is loaded and displayed as the sprite. Currently only .png and .jpg files are supported. Note that loaded files are cached, so each file will only be loaded once.

source may also be an ASCII art string. This is a string with at least one newline character. Each row in the string represents a row of pixels. Here's an example:

local face = [[
..YYYYY..
.Y.....Y.
Y..B.B..Y
Y.......Y
Y.R...R.Y
Y..RRR..Y
.Y.....Y.
..YYYYY..
]]
am.window{}.scene = am.scale(20) ^ am.sprite(face)

The resulting image looks like this:

The mapping from characters to colors is determined by the am.ascii_color_map table. By default this is defined as:

am.ascii_color_map = {
W = vec4(1, 1, 1, 1),          -- full white
w = vec4(0.75, 0.75, 0.75, 1), -- silver
K = vec4(0, 0, 0, 1),          -- full black
k = vec4(0.5, 0.5, 0.5, 1),    -- dark grey
R = vec4(1, 0, 0, 1),          -- full red
r = vec4(0.5, 0, 0, 1),        -- half red (maroon)
Y = vec4(1, 1, 0, 1),          -- full yellow
y = vec4(0.5, 0.5, 0, 1),      -- half yellow (olive)
G = vec4(0, 1, 0, 1),          -- full green
g = vec4(0, 0.5, 0, 1),        -- half green
C = vec4(0, 1, 1, 1),          -- full cyan
c = vec4(0, 0.5, 0.5, 1),      -- half cyan (teal)
B = vec4(0, 0, 1, 1),          -- full blue
b = vec4(0, 0, 0.5, 1),        -- half blue (navy)
M = vec4(1, 0, 1, 1),          -- full magenta
m = vec4(0.5, 0, 0.5, 1),      -- half magenta
O = vec4(1, 0.5, 0, 1),        -- full orange
o = vec4(0.5, 0.25, 0, 1),     -- half orange (brown)
}

but you can modify it as you please (though this must be done before creating a sprite).

Any characters not in the color map will come out as transparent pixels, except for space characters which are ignored.

The third kind of source is a sprite spec. Sprite specs are usually generated using the sprite packing tool, though you can create them manually as well if you like.

You can define your own sprite spec by supplying a table with all of the following fields:

• texture: the texture containing the sprite.
• s1: the left texture coordinate (0 to 1)
• t1: the bottom texture coordinate (0 to 1)
• s2: the right texture coordinate (0 to 1)
• t2: the top texture coordinate (0 to 1)
• x1: the left offset of the sprite (in pixels)
• y1: the bottom offset of the sprite (in pixels)
• x2: the right offset of the sprite (in pixels)
• y2: the top offset of the sprite (in pixels)
• width: the width of the sprite (in pixels)
• height: the height of the sprite (in pixels)

Typically x1 and y1 would both be zero and x2 and y2 would be equal to width and height, though they may be different when transparents pixels are removed from the edges of sprites when packing them. The width and height fields are used for adjusting sprite position based on the requested alignment.

The color argument is a vec4 that applies a tinting color to the sprite. The default is white (no tinting).

The halign and valign arguments determine the alignment of the sprite. The allowed values for halign are "left", "right" and "center". The allowed values for valign are "bottom", "top" and "center". The default in both cases is "center".

Fields:

• source: The sprite source (filename, ascii art string or sprite spec). Updatable.
• color: The sprite tint color as a vec4. Updatable.
• width: The width of the sprite in pixels.
• height: The height of the sprite in pixels.
• spec: The sprite spec table, from which you can retrieve the texture, texture coordinates and vertices. This is available even if the sprite wasn't created with a sprite spec. Readonly.

Default tag: "sprite".

### am.rect(x1, y1, x2, y2 [, color])

Draws a rectangle from (x1, y1) to (x2, y2).

color should be a vec4 and defaults to white.

Fields:

• x1: The left coordinate of the rectangle. Updatable.
• y1: The bottom coordinate of the rectangle. Updatable.
• x2: The right coordinate of the rectangle. Updatable.
• y2: The top coordinate of the rectangle. Updatable.
• color: The color of the rectangle as a vec4. Updatable.

Default tag: "rect".

### am.circle(center, radius [, color [, sides]])

Draws a circle or regular polygon.

center should be a vec2.

color should be a vec4. The default is white.

sides is the number of sides to use when rendering the circle. The default is 255. You can change this to make other regular polygons. For example change it to 6 to draw a hexagon.

Fields:

• center: The circle center as a vec2. Updatable.
• radius: The circle radius. Updatable.
• color: The circle color as a vec4. Updatable.

Default tag: "circle".

### am.line(point1, point2 [, thickness [, color]])

Draws a line from point1 to point2.

point1 and point1 should be vec2s.

thickness should be a number. The default is 1.

color should be a vec4. The default is white.

Fields:

• point1: Updatable.
• point2: Updatable.
• thickness: Updatable.
• color: Updatable.

Default tag: "line".

### am.particles2d(settings)

Renders a simple 2D particle system.

settings should be a table with any of the following fields:

• source_pos: The position where the particles emit from (vec2)
• source_pos_var: The source position variation (vec2)
• start_size: The start size of the particles (number)
• start_size_var: The start size variation (number)
• end_size: The end size of the particles (number)
• end_size_var: The end size variation (number)
• angle: The angle the particles emit at (radians)
• angle_var: The variation in the angle the particles emit at (radians)
• speed: The speed of the particles (number)
• speed_var: The variation in the speed of the particles (number)
• life: The lifetime of the particles (seconds)
• life_var: The variation in lifetime of the particles (seconds)
• start_color: The start color of the particles (vec4)
• start_color_var: The variation in the start color of the particles (vec4)
• end_color: The end color of the particles (vec4)
• end_color_var: The variation in the end color of the particles (vec4)
• emission_rate: The number of particles to emit per second
• start_particles: The initial number of particles
• max_particles: The maximum number of particles
• gravity: Gravity to apply to the particles (vec2)
• sprite_source: The particle sprite source (see am.sprite). If this is omitted the particles will be colored squares.
• warmup_time: Simulate running the particle system for this number of seconds before showing it for the first time.

In the table the _var fields are an amount that is added to and subtracted from the corresponding field (the one without the _var suffix) to get the range of values from which one is randomly chosen. For example if source_pos is vec2(1, 0) and source_pos_var is vec2(3, 2), then source positions will be chosen in the range vec2(-2, -2) to vec2(4, 2).

All of the sprite settings are exposed as updatable fields on the particles node.

Note that no blending is applied to the particles, so if you want alpha on your particles, then you need to add a am.blend node. For example:

local node = am.blend("add_alpha")
^ am.particles2d{
source_pos = win:mouse_position(),
source_pos_var = vec2(20),
max_particles = 1000,
emission_rate = 500,
start_particles = 0,
life = 0.4,
life_var = 0.1,
speed = 200,
start_color = vec4(1, 0.3, 0.01, 0.5),
start_color_var = vec4(0.1, 0.05, 0.0, 0.1),
end_color = vec4(0.5, 0.8, 1, 1),
end_color_var = vec4(0.1),
start_size = 30,
start_size_var = 10,
end_size = 2,
end_size_var = 2,
gravity = vec2(0, 2000),
}

Methods:

• reset(): resets the particles as if they had just been created with their current settings.

Default tag: "particles2d".

## Transformation nodes

The following nodes apply transformations to all their descendents.

Note: These nodes have an optional uniform argument in the first position of their construction functions. This argument is only relevant if you're writing your own shader programs. Otherwise you can ignore it.

### am.translate([uniform,] position)

Apply a translation to a 4x4 matrix uniform. uniform is the uniform name as a string. It is "MV" by default.

position may be either 2 or 3 numbers (the x, y and z components) or a vec2 or vec3.

If the z component is omitted it is assumed to be 0.

Fields:

• position: The translation position as a vec3. Updatable.
• position2d: The translation position as a vec2. Updatable.
• x, y, z: The x, y and z components of the position. Updatable.

Default tag: "translate".

Examples:

local node1 = am.translate(10, 20)
local node2 = am.translate(vec2(10, 20))
local node3 = am.translate("MyModelViewMatrix", 1, 2, -3.5)
local node4 = am.translate(vec3(1, 2, 3))
node1.position2d = vec2(30, 40)
node2.x = 40
node2.y = 50
node3.position = vec3(1, 2, -3)

### am.scale([uniform,] scaling)

Apply a scale transform to a 4x4 matrix uniform. uniform is the uniform name as a string. It is "MV" by default.

scaling may be 1, 2 or 3 numbers or a vec2 or vec3. If 1 number is provided it is assume to be the x and y components of the scaling and the z scaling is assumed to be 1. If 2 numbers or a vec2 is provided, they are the scaling for the x and y components and z is assumed to be 1.

Fields:

• scale: The scale as a vec3. Updatable.
• scale2d: The scale as a vec2. Updatable.
• x, y, z: The x, y and z components of the scale. Updatable.

Default tag: "scale".

Examples:

local node1 = am.scale(2)
local node2 = am.scale(2, 1)
local node3 = am.scale(vec2(1, 2))
local node4 = am.scale("MyModelViewMatrix", vec3(0.5, 2, 3))
node1.scale2d = vec2(1)
node2.x = 3
node4.scale = vec3(1, 3, 2)

### am.rotate([uniform,] rotation)

Apply a rotation to a 4x4 matrix uniform. uniform is the uniform name as a string. It is "MV" by default.

rotation can be either a quaternion, or an angle (in radians) followed by an optional vec3 axis. If the axis is omitted it is assumed to be vec3(0, 0, 1) so the rotation becomes a 2D rotation in the xy plane.

Fields:

• rotation: The rotation as a quat. Updatable.
• angle: The rotation angle in radians. Updatable.
• axis: The rotation axis as a vec3. Updatable.

Default tag: "rotate".

Examples:

local node1 = am.rotate(math.rad(45))
local node2 = am.rotate(math.pi/4, vec3(0, 1, 0))
local node3 = am.rotate("MyModelViewMatrix",
quat(math.pi/6, vec3(1, 0, 0)))
node2.axis = vec3(0, 0, 1)
node3.rotation = quat(math.rad(60), vec3(0, 0, 1))

### am.use_program(program)

Sets the shader program to use when rendering descendents. A program object can be created using the am.program function.

Fields:

• program: The shader program to use. Updatable.

Default tag: "use_program".

### am.bind(bindings)

Binds shader program parameters (uniforms and attributes) to values. bindings is a table mapping shader parameter names to values.

The named parameters are matched with the uniforms and attributes in the shader program just before a am.draw node is executed.

Program parameter types are mapped to the following Lua types:

Program parameter type Lua type
float uniform number
vec2 uniform vec2
vec3 uniform vec3
vec4 uniform vec4
mat2 uniform mat2
mat3 uniform mat3
mat4 uniform mat4
sampler2D uniform texture2d
float attribute view("float")
vec2 attribute view("vec2")
vec3 attribute view("vec3")
vec4 attribute view("vec4")

Any bound parameters not in the program are ignored, but all program parameters must have been bound before a draw node is executed.

Note: The parameter P is initially bound to a 4x4 projection matrix defined by the window's coordinate system, while the parameter MV (the default model view matrix) is initially bound to the 4x4 identity matrix.

The bound parameters are available as updatable fields on the bind node. The fields have the same names as their corresponding parameters.

Default tag: "bind".

Example:

local bind_node = am.bind{
P = mat4(1),
MV = mat4(1),
color = vec4(1, 0, 0, 1),
vert = am.vec2_array{
vec2(-1, -1),
vec2(0, 1),
vec2(1, -1)
}
}
-- update a parameter
bind_node.color = vec4(0, 1, 1, 1)

### am.draw(primitive [, elements] [, first [, count]])

Draws the currently bound vertices using the current shader program with the currently bound parameter values.

primitive can be one of the following:

• "points"
• "lines"
• "line_strip"
• "line_loop"
• "triangles"
• "triangle_strip"
• "triangle_fan"

Note that "line_loop" and "triangle_fan" may be slow on some systems.

elements, if supplied, should be a ushort_elem or uint_elem view containing 1-based attribute indices. If omitted the attributes are rendered in order as if elements were 1, 2, 3, 4, 5, ... etc. See also buffers and views.

first specifies where in the list of vertices to start drawing (starting from 1). The default is 1.

count specifies how many vertices to draw. The default is as many as are supplied through bound vertex attributes and the elements view if present.

Fields:

• primitive: The primitive to draw. Updatable.
• elements: The elements view. Updatable.
• first: The first vertex to draw. Updatable.
• count: The number of vertices to draw. Updatable.

Default tag: "draw".

Here is a complete example that draws a triangle with red, green and blue corners using am.use_program, am.bind and am.draw nodes:

local win = am.window{}
local prog = am.program([[
precision highp float;
attribute vec2 vert;
attribute vec4 color;
uniform mat4 MV;
uniform mat4 P;
varying vec4 v_color;
void main() {
v_color = color;
gl_Position = P * MV * vec4(vert, 0.0, 1.0);
}
]], [[
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
]])
win.scene =
am.use_program(prog)
^ am.bind{
P = mat4(1),
MV = mat4(1),
color = am.vec4_array{
vec4(1, 0, 0, 1),
vec4(0, 1, 0, 1),
vec4(0, 0, 1, 1)
},
vert = am.vec2_array{
vec2(-1, -1),
vec2(0, 1),
vec2(1, -1)
}
}
^ am.draw"triangles"

The resulting image looks like this:

### am.blend(mode)

Set the blending mode.

The possible values for mode are:

• "off"
• "alpha"
• "premult"
• "add"
• "subtract"
• "add_alpha"
• "subtract_alpha"
• "multiply"
• "invert"

Fields:

• mode: Updatable.

Apply a color mask. The four arguments can be true or false and determine whether the corresponding color channel is updated in the rendering target (either the current window or framebuffer being rendered to).

For example using a mask of am.color_mask(false, true, false, true) will cause only the green and alpha channels to be updated.

### am.cull_face(face)

Culls triangles with a specific winding.

The possible values for face are:

• "cw": Cull clockwise wound triangles.
• "ccw": Cull counter-clockwise wound triangles.
• "none": Do not cull any triangles.

Fields:

• face: Updatable.

Sets the depth test and mask. The window or framebuffer being rendered to needs to have a depth buffer for this to have any effect.

test is used to determine whether a fragment is rendered by comparing the depth value of the fragment to the value in the depth buffer. The possible values for test are:

• "never"
• "always"
• "equal"
• "notequal"
• "less"
• "lequal"
• "greater"
• "gequal"

Mask determines whether the fragment depth is written to the depth buffer. The possible values are true and false. The default is true.

### am.viewport(x, y, width, height)

Set the viewport, which is the rectangular area of the window into which rendering will occur.

x and y is the bottom-left corner of the viewport in pixels, where the bottom-left corner of the window is (0, 0). width and height are also in pixels.

Fields:

• x, y, width, height: Updatable.

Default tag: "viewport".

### am.lookat([uniform,] eye, center, up)

Sets uniform to the "lookat matrix" which looks from eye (a vec3) to center (a vec3), with up (a unit vec3) as the up direction.

This node can be thought of a camera positioned at eye and facing the point center.

The default value for uniform is "MV".

Fields:

• eye: The camera position (vec3). Updatable.
• center: A point the camera is facing (vec3). Updatable.
• up: The up direction of the camera (vec3). Updatable.

Default tag: "lookat".

This first takes the matrix product of the given uniforms (which should be mat4s). Then it determines whether the sphere with the given center and radius would be visible using the previously computed matrix product as the model-view-projection matrix. If it wouldn't be visible then none of this node's children are rendered (i.e. they are culled).

The default value for uniforms is "P" and "MV" and the default value for center is vec3(0).

Fields:

• radius: Updatable.
• center: Updatable.

### am.billboard([uniform,] [preserve_scaling])

Removes rotation from uniform, which should be a mat4. By default uniform is "MV".

If preserve_scaling is false or omitted then any scaling will also be removed from the matrix. If it is true, then scaling will be preserved, as long as it's the same across all three axes.

Default tag: "billboard"

This node has no effect on rendering. Instead it records the value of the named uniform when rendering occurs.

This is useful for finding the value of the model-view matrix (MV) at a specific node without having to keep track of all the ancestor transforms. This could then be used to, for example, determine the position of a mouse click in a node's coordinate space, by taking the inverse of the model-view matrix.

Fields:

• value: The value of the uniform, or nil if the node hasn't been rendered yet, or the named uniform wasn't set in an ancestor node.

Returns a node that renders a set of quads. The returned node is actually an am.bind node with an am.draw node child. i.e. no program or blending is defined -- these must be created separately as parent nodes.

n is the initial capacity. Set this to the number of quads you think you'll want to render. It doesn't matter if it's too small as the capacity will be increased as required, though it's slightly faster if no capacity increases are required.

spec is a table of attribute name and type pairs (the same as used for am.struct_array ).

Fields:

• num_quads: The number of quads. This is zero initially.

Methods:

• add_quad(data): adds a quad to be rendered and returns the quad number. data is a table where the keys are attribute names and the values are the values of the 4 vertices of the quad. The values can be specified in several ways:
• as a table where each each element is the value for the left-top, left-bottom, right-bottom and right-top corners of the quad.
• a single value for all corners.
• a view containing the values for the elements As with the view:set method, if the attribute is a vector, a table of numbers is also accepted. The quad number (starting at 1) is returned.
• remove_quad(n [,count]): Removes count quads starting with the nth. count is 1 if omitted.
• Additionally methods are created for each attribute of the form quad_<attribute name> that can be used to update the value of a quad attribute. The signature of the method is: quad_attr(n, values) where n is the quad number and values has the same meaning as in the add_quad method.

Example:

local quads = am.quads(2, {"vert", "vec2", "color", "vec3"})
vec2(0, -100), vec2(0, 0)},
color = {vec3(1, 0, 0), vec3(0, 1, 0),
vec3(0, 0, 1), vec3(1, 1, 1)}}
vec2(100, 0), vec2(100, 100)},
color = {vec3(1, 0, 0), vec3(0, 1, 0),
vec3(0, 0, 1), vec3(1, 1, 1)}}
local win = am.window{}
local prog = am.program([[
precision highp float;
attribute vec2 vert;
attribute vec3 color;
uniform mat4 MV;
uniform mat4 P;
varying vec3 v_color;
void main() {
v_color = color;
gl_Position = P * MV * vec4(vert, 0.0, 1.0);
}
]], [[
precision mediump float;
varying vec3 v_color;
void main() {
gl_FragColor = vec4(v_color, 1.0);
}
]])
win.scene = am.use_program(prog) ^ quads

The above program produces the following output:

### am.postprocess(settings)

Allows for post-processing of a scene. First the children of the postprocess node are rendered into a texture, then the texture is rendered to the entire window using a user-supplied shader program.

settings is a table containing any number of the following fields:

• width: the width of the texture to render the children into. If omitted the window width is used.
• height: the height of the texture to render the children into. If omitted the window height is used.
• minfilter: the minfilter of the texture. The default is "nearest".
• magfilter: the magfilter of the texture. The default is "nearest".
• depth_buffer: whether there should be a depth buffer when rendering the scene. The default is false.
• stencil_buffer: whether there should be a stencil buffer when rendering the scene. The default is false.
• clear_color: The color to clear the texture to before rendering each frame (a vec4). The default is black.
• auto_clear: Whether to automatically clear the texture before rendering each frame. The default is true.
• program: The shader program to use to render the texture.

The shader program should expect the following uniforms and attributes:

uniform sampler2D tex;
attribute vec2 vert;
attribute vec2 uv;

Note that if either width or height are set then they must both be set.

Fields:

• clear_color: The color to clear the texture to before rendering each frame (a vec4). Updatable.
• auto_clear: Whether to automatically clear the texture before rendering each frame. Updatable.
• program: The shader program to use to render the texture. Updatable.

Methods:

• clear(): Clear the texture manually.

## Creating custom scene nodes

Creating a custom leaf node is relatively simple: just create a function that constructs the graph you want and return it.

You can add custom methods by setting the appropriate fields on the root node of the returned graph (any node can have any number of custom fields set on it, as long as they don't clash with pre-defined fields).

If you define methods of the form:

function node:get_FIELD()
...
end

function node:set_FIELD(val)
...
end

Then you will be able to access FIELD as if it's a regular field and the appropriate access method will be called.

For example:

node.FIELD = val

will be equivalent to:

node:set_FIELD(val)

If you want a readonly field, just define the get_FIELD method and not the set_FIELD method.

The am.text, am.sprite and am.particles2d nodes are all implemented this way. Their source is here and here.

If you want to create a non-leaf node, then you need to use the am.wrap function:

### am.wrap(node)

This "wraps" node inside a special type of node called a wrap node.

When a wrap node is rendered it renders the inner node. However any nodes added as children of a wrap node are also added to the leaf node(s) of the inner node.

For example suppose we want to create a transformation node called move_and_rotate that does both a translation and a rotation:

function move_and_rotate(x, y, degrees)
end

We would like to be able to create such a node and add children to it. Like so:

local mvrot = move_and_rotate(10, 20, 60)
mvrot:append(am.rect(-10, -10, 10, 10))

However what this will do is add the rect node as a child of the translate node returned by move_and_rotate.

mvrot"rotate":append(am.rect(-10, -10, 10, 10))

which is a bit clunky.

A wrap node solves this problem:

function move_and_rotate(x, y, degrees)
end
local mvrot = move_and_rotate(10, 20, 60)
mvrot:append(am.rect(-10, -10, 10, 10))

For completeness here we add some fields to set the x, y and degrees properties of our new node:

function move_and_rotate(x, y, degrees)
local inner = am.translate(x, y) ^ am.rotate(math.rad(degrees))
local wrapped = am.wrap(inner)
function wrapped:get_x()
return x
end
function wrapped:set_x(v)
x = v
inner.position2d = vec2(x, y)
end
function wrapped:get_y()
return y
end
function wrapped:set_y(v)
y = v
inner.position2d = vec2(x, y)
end
function wrapped:get_degrees()
return degrees
end
function wrapped:set_degrees(v)
degrees = v
end
return wrapped
end
local mvrot = move_and_rotate(-100, -100, 0)
mvrot:append(am.rect(-50, -50, 50, 50))
mvrot.x = 100
mvrot.y = 100
mvrot.degrees = 45

There are some caveats when using wrap nodes:

• The inner node is not considered part of the scene graph for the purpose of running actions. So any actions need to be attached to the wrap node, not the inner node.
• Tag search functions do not search the inner node.

The am.postprocess node is implemented using a wrap node. You can view it's implementation here.

Compiles and returns a shader program for use with am.use_program nodes.

vertex_shader and fragment_shader should be the sources for the vertex and fragment shaders in the OpenGL ES shader language version 1. This is the same shader language supported by WebGL 1.

Example:

local vert_shader = [[
precision highp float;
attribute vec3 vert;
uniform mat4 MV;
uniform mat4 P;
void main() {
gl_Position = P * MV * vec4(vert, 1);
}
]]
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0, 0.5, 1.0);
}
]]
local prog = am.program(vert_shader, frag_shader)

# Image buffers

An image buffer represents a 2D image in memory. Each pixel occupies 4 bytes with 1 byte per channel (RGBA). The data for the image buffer is stored in an am.buffer.

## Creating image buffers

### am.image_buffer([buffer, ] width [, height])

Creates an image buffer of the given width and height. If height is omitted it is the same as width (the image is square).

If a buffer (created using am.buffer) is given, it is used as the image buffer. It must have the correct size, which is 4 * width * height. If buffer is omitted a new one is created.

Fields:

• width: the image width.
• height: the image height.
• buffer: the raw data buffer.

Loads the given image file and returns a new image buffer. Only .png and .jpg files are supported. Returns nil if the file was not found.

## Saving images

### image_buffer:save_png(filename)

Saves the given image as a png in filename.

## Pasting images

### image_buffer:paste(src, x, y)

Pastes one image into another such that the bottom-left corner of the source image is at the given pixel coordinate in the target image. The bottom-left pixel of the target image has coordinate (1, 1).

## Decoding/encoding PNGs

### am.encode_png(image_buffer)

Returns a raw buffer containing the png encoding of the given image.

### am.decode_png(buffer)

Converts the raw buffer, which should be a png encoding of an image, into an image buffer.

# Textures

Textures are 2D images that can be used as input to shader programs when bound to a sampler2D uniform.

They can also be rendered to via framebuffers.

A texture may optionally have a backing image buffer. If a texture has a backing image buffer, then any changes to the image buffer will be automatically transferred to the texture.

Textures always have 4 channels (RGBA) with 1 byte per channel.

## Creating a texture

### am.texture2d(width [, height])

Creates a texture of the given width and height without a backing image buffer.

### am.texture2d(image_buffer)

Creates a texture using the given image buffer as the backing image buffer.

### am.texture2d(filename)

This is shorthand for am.texture2d(am.load_image(filename)).

## Texture fields

### texture.image_buffer

The backing image buffer or nil if there isn't one.

### texture.minfilter

Defines the minification filter which is applied when the texture's pixels are smaller that the screen's pixels.

The allowed values are:

• "nearest" (the default)
• "linear"
• "nearest_mipmap_nearest"
• "linear_mipmap_nearest"
• "nearest_mipmap_linear"
• "linear_mipmap_linear"

If one of the mipmap filters is used a mipmap will be automatically generated.

Updatable.

### texture.magfilter

Defines the magnification filter which is applied when the texture's pixels are larger that the screen's pixels.

The allowed values are:

• "nearest" (the default)
• "linear"

Updatable.

### texture.filter

This field can be used to set both the minification and magnification fields. The allowed values are:

• "nearest"
• "linear"

Updatable.

### texture.swrap

Sets the wrapping mode in the x direction. The allowed values are:

• "clamp_to_edge" (the default)
• "repeat"
• "mirrored_repeat"

Note that the texture width must be a power of 2 if either "repeat" or "mirrored_repeat" is used.

Updatable.

### texture.twrap

Sets the wrapping mode in the y direction. The allowed values are:

• "clamp_to_edge" (the default)
• "repeat"
• "mirrored_repeat"

Note that the texture height must be a power of 2 if either "repeat" or "mirrored_repeat" is used.

Updatable.

### texture.wrap

This field can be used to set the wrap mode in both the x and y directions at the same time.

The allowed values are:

• "clamp_to_edge"
• "repeat"
• "mirrored_repeat"

Updatable.

# Framebuffers

A framebuffer is like an off-screen window you can draw to. It has a texture associated with it that gets updated when you draw to the framebuffer. The texture can then be used as input to further rendering.

## Creating a framebuffer

### am.framebuffer(texture [, depth_buf [, stencil_buf]])

Creates framebuffer with the given texture attached.

depth_buf and stencil_buf determine whether the framebuffer has a depth and/or stencil buffer. These should be true or false.

## Framebuffer fields

### framebuffer.clear_color

The color to use when clearing the framebuffer (a vec4).

Updatable.

### framebuffer.projection

A mat4 projection matrix to use when rendering nodes into this framebuffer. The "P" uniform will be set to this. If this is not specified then the projection math.ortho(-width/2, width/2, -height/2, height/2) is used.

Updatable.

### framebuffer.pixel_width

The width of the framebuffer, in pixels.

### framebuffer.pixel_height

The height of the framebuffer, in pixels.

## Framebuffer methods

### framebuffer:render(node)

Renders node into the framebuffer.

### framebuffer:render_children(node)

Renders node's children into the framebuffer (but not node itself).

### framebuffer:clear([color [, depth [, stencil]]])

Clears the framebuffer, using the current clear_color.

By default the color, depth and stencil buffers are cleared, but you can selectively clear only some buffers by setting the color, depth and stencil argumnets to true or false.

Reads the pixels from the framebuffer into the texture's backing image buffer. This is a relatively slow operation, so use it sparingly.

### framebuffer:resize(width, height)

Resize the framebuffer. Only framebuffers with textures that have no backing image buffers can be resized. Also the texture must not use a filter that requires a mipmap.

# Actions

This section covers useful action utility functions. For information on the action mechanism see the description of the action method.

## Delay action

### am.delay(seconds)

Returns an action that does nothing for the given number of seconds.

## Combining actions

### am.series(actions)

Returns an action that runs the given array of actions one after another.

### am.parallel(actions)

Returns an action that runs the given array of actions all at the same time. The returned action finishes once all the actions in the array are finished.

### am.loop(func)

func should be a function that returns an action. am.loop returns an action that repeatedly runs the action returned by func.

## Tweens

### am.tween([target,] time, values [, ease])

Returns an action that changes the values of one or more fields of target to new values over a given time period.

time is in seconds.

values is a table mapping field names to their new values.

ease is an easing function. This function takes a value between 0 and 1 and returns a value between 0 and 1. This determines the "shape" of the interpolation between the two values. If omitted a linear interpolation is used.

The following easing functions are pre-defined (though of course you can define your own):

• am.ease.linear
• am.ease.quadratic
• am.ease.cubic
• am.ease.hyperbola
• am.ease.sine
• am.ease.windup
• am.ease.elastic
• am.ease.bounce
• am.ease.cubic_bezier(x1, y1, x2, y2): This returns a cubic bezier ease function with the given control points.
• am.ease.out(f): This takes an existing ease function and returns its reverse. E.g. if the existing ease function is slow and then fast, the new one will be fast and then slow.
• am.ease.inout(f, g). This returns an ease function that uses the ease function f for the first half of the transition and out(g) for the second half.

Tweening works with fields that are numbers or vectors (vec2, vec3 or vec4). It also works with quaternions. In all cases the math.mix function is used to interpolate between the initial value and the final value.

If target is omitted it is taken to be the node to which the tween action is attached.

Example:

am.window{}.scene =
am.rect(-100, -100, 100, 0, vec4(1, 0, 0, 1))
:action(
am.tween(1, {
color = vec4(0, 1, 0, 1),
y2 = 100
}))

## Coroutine actions

An action can also be a coroutine. Inside a coroutine action it can sometimes be useful to wait for another action to finish, such as a tween. That is what the am.wait function is for:

### am.wait(action)

Waits for the given action to finish before continuing. This can only be called from within a coroutine.

Example:

am.window{}.scene =
am.rect(-100, -100, 100, 0, vec4(1, 0, 0, 1))
:action(coroutine.create(function(node)
while true do
am.wait(am.tween(node, 1, {
color = vec4(0, 1, 0, 1),
y2 = 100
}))
am.wait(am.tween(node, 1, {
color = vec4(1, 0, 0, 1),
y2 = 0
}))
end
end))

# Time

## Frame time

### am.frame_time

This contains the time the current frame started, in seconds.

## Delta time

### am.delta_time

This contains the amount of time that lapsed between the start of the current frame and the start of the previous frame, in seconds.

## Real time

### am.current_time()

This returns the time since the program started, in seconds. This value can change over the course of a frame.

### am.save_state(key, state [,format])

Save the table state under key.

format can be json or lua. The default is lua.

Note: The lua format allows users to execute arbitrary lua code by modifying the save file.

Loads the table saved under key and returns it. If no table has been previously saved under key then nil is returned.

format can be json or lua. The default is lua.

Note: The lua format allows users to execute arbitrary lua code by modifying the save file.

# Audio

## Audio buffers

An audio buffer is a block of memory that stores an uncompressed audio sample.

An audio buffer may have any number of channels, although Amulet will currently only play up to two channels.

An audio buffer also has a sample rate. Amulet currently plays all audio at a sample rate 44.1kHz and will resample an audio buffer if it has a different sample rate.

The audio data itself is stored in a raw buffer as a series of single precision floats (4 bytes each). The samples for each channel are contiguous. The first channel's data comes first, then the second channel's data, etc (the channels are not interleaved).

If the sample data is stored in the buffer buf, and there are two channels, then the following code will create views that can be used to update or read the samples for each channel:

local c = 2 -- channels
local s = #buf / 4 / c -- samples per channel
local left_channel = buf:view("float", 0, 4, s)
local right_channel = buf:view("float", s * 4, 4, s)

### am.audio_buffer(buffer, channels, sample_rate)

Returns a new audio buffer using the given raw buffer (a buffer created with am.buffer). The audio data should be layed out as described above.

channels is the number of channels and sample_rate is the sample rate in Hz.

Loads the given audio file and returns a new audio buffer. The file must be a .ogg audio file. Returns nil if the file was not found.

## Audio buffer fields

### audio_buffer.sample_rate

The sample rate in Hz. Readonly.

### audio_buffer.samples_per_channel

The number of samples per channel. Readonly.

### audio_buffer.length

The length of the audio in seconds. Readonly.

### audio_buffer.buffer

The underlying raw buffer where the audio data is stored. Readonly.

## Playing audio

### am.play(source, loop, pitch, gain)

Returns an action that plays audio. Like any other action it needs to be attached to a scene node that's connected to a window to run.

source can either be an audio buffer, the name of a ".ogg" file or a seed generated by the sfxr tool (there's an online version of that tool in the examples list of the online editor).

loop can be true or false.

pitch is a multiplier applied to the playback speed. 1.0 mean play at the original speed. 2.0 means play twice as fast and 0.5 means play at half the original speed.

gain should be between 0 and 1.

Note: When the source is an sfxr seed or a filename, an audio buffer is generated behind the scenes and cached. Therefore there might be a short delay the first time this function is called with a given seed or filename (though for short audio clips this will probably not be noticeable). Also avoid calling this function many times with different seeds or filenames, because that will cause unbounded memory growth.

## Generating sound effects

### am.sfxr_synth(settings)

Returns an audio buffer containing a generated sound effect.

settings can either be a table containing any number of the following fields:

Field Default value Notes
wave_type "square" Can also be "sawtooth", "sine" or "noise"
base_freq 0.3
freq_limit 0.0
freq_ramp 0.0
freq_dramp 0.0
duty 0.0
duty_ramp 0.0
vib_strength 0.0
vib_speed 0.0
vib_delay 0.0
env_attack 0.0
env_sustain 0.3
env_decay 0.4
env_punch 0.0
filter_on false
lpf_resonance 0.0
lpf_freq 1.0
lpf_ramp 0.0
hpf_freq 0.0
hpf_ramp 0.0
pha_offset 0.0
pha_ramp 0.0
repeat_speed 0.0
arp_speed 0.0
arp_mod 0.0

or a numeric seed. Use the sfxr example in the online editor to generate seeds.

## Audio graphs

TODO

(Amulet has support for building graphs of audio effect nodes, however it is currently undocumented because it will probably be changing shortly to use syntax more consistent with the way scene graphs are constructed.)

TODO

TODO

# Controllers

Each controller is assigned an index starting from 1. The first controller attached will always have index 1, so if your game only uses one controller you can just use index 1.

Controllers are supported on Windows, OSX and Linux and up to 8 controllers can be connected at once.

### am.controller_present(index)

Returns true if controller index is currently connected.

### am.controller_attached(index)

Returns true if controller index was attached since the last frame.

### am.controller_detached(index)

Returns true if controller index was removed since the last frame.

### am.controllers_present()

Returns a list of currently connected controller indexes.

### am.controllers_attached()

Returns a list of controller indexes attached since the last frame.

### am.controllers_detached()

Returns a list of controller indexes removed since the last frame.

### am.controller_lt_val(index)

Returns the value of the left trigger axis of controller index. The returned value is between 0 and 1.

### am.controller_rt_val(index)

Returns the value of the right trigger axis of controller index. The returned value is between 0 and 1.

### am.controller_lstick_pos(index)

Returns the position of the left stick of controller index as a vec2. The position values range from -1 to 1 in both the x and y components. Negative x means left and negative y means down.

### am.controller_rstick_pos(index)

Returns the position of the left stick of controller index as a vec2. The position values range from -1 to 1 in both the x and y components. Negative x means left and negative y means down.

### am.controller_button_pressed(index, button)

Returns true if the given button of controller index was pressed since the last frame.

button can be one of the following strings:

• "a"
• "b"
• "x"
• "y"
• "back"
• "guide"
• "start"
• "ls"
• "rs"
• "lb"
• "rb"
• "up"
• "down"
• "left"
• "right"

### am.controller_button_released(index, button)

Returns true if the given button of controller index was released since the last frame. See am.controller_button_pressed for a list of valid values for button.

### am.controller_button_down(index, button)

Returns true if the given button of controller index was down at the start of the current frame. See am.controller_button_pressed for a list of valid values for button.

# Packing sprites and generating fonts

## Overview

Amulet includes a tool for packing images and font glyphs into a sprite sheet and generating a Lua module for conveniently accessing the images and glyphs therein.

Suppose you have an images directory containing some .png files and a fonts directory containing myfont.ttf and suppose these two directories are subdirectories of the main game directory (where your main.lua file lives). To generate a sprite sheet, run the following command while in the main game directory:

> amulet pack -png mysprites.png -lua mysprites.lua
images/*.png fonts/myfont.ttf@32

This will generate mysprites.png and mysprites.lua in the current directory.

The @32 after the font file specifies the size of the glyphs to generate (in this case 32px).

To use the generated sprite sheet in your code, first required the Lua module and then pass the fields in that module to am.sprite or am.text. For example:

local mysprites = require "mysprites"
local run1_node = am.sprite(mysprites.run1)
local text_node = am.text(mysprites.myfont32, "BARF!")

The sprite field names are generated from the image filenames with the extension removed (so the file images/run1.png would result in the field mysprites.run1).

The font field name (myfont32) is the concatenation of the font file name (without the extension) and the font size.

## Pack options

In addition to the required -png and -lua options the pack command also supports the following options:

Option Description
-mono Do not anti-alias fonts.
-minfilter The minification filter to apply when loading the sprite sheet texture. linear (the default) or nearest.
-magfilter The magnification filter to apply when loading the sprite sheet texture. linear (the default) or nearest.
-no-premult Do not pre-multiply RGB channels by alpha.
-keep-padding Do not strip transparent pixels around images.

Unless you specify the -keep-padding option, Amulet will remove excess rows and columns of completely transparent pixels from each image before packing it. It will always however leave a one pixel border of transparent pixels if the image was surrounded by transparent pixels to begin with. This is done to prevent pixels from one image "bleeding" into another image when it's drawn.

When excess rows and columns are removed the vertices of the sprite will be adjusted so the images is still drawn in the correct position, as if the excess pixels were still there.

If an image is not surrounded by a border of transparent pixels, then the border pixels will be duplicated around the image. This helps prevent "cracks" when tiling the image.

In summary, if you don't intend to use an image for tiling, surround it with a border of completely transparent pixels.

## Specifying which font glyphs to generate

Optionally each font item may also be followed by a colon and a comma separated list of character ranges to include in the sprite sheet.

The characters in the character range can be written directly if they are ASCII, or given as hexadecimal Unicode codepoints.

Here are some examples of valid font items:

• VeraMono.ttf@32:A-Z,0-9
• ComicSans.ttf@122:0x20-0xFF
• gbsn00lp.ttf@42:a-z,A-Z,0-9,0x4E00-0x9FFF

If the character range list is omitted, it defaults to 0x20-0x7E, i.e:

!"#\$%&'()*+,-./:;<=>?@[\]^_{|}~0123456789
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz

Loads the image filename and returns a font using the glyph layout described by key where key is a string containing all the glyphs in the file as they are layed out. All glyphs must have the same width and height which is determined from the width and height of the image and the number of rows and columns in key. For example if the key is:

[[
ABC
DEF
GHI
]]

then the image contains 9 glyphs in 3 rows and 3 columns. If the image has height 30 and width 36 then each glyph has width 10 and height 12.

# Exporting

To generate distribution packages for Windows, Mac OS X, iOS, Linux and HTML5, use the amulet export command.

Run the following command from the directory containing your lua, image and audio files:

> amulet export

This will generate package files for each supported platform in the current directory. If you just want to generate a package for one platform you can pass one of the options -windows, -mac, -ios, -linux or -html.

All files in the directory with the following extensions will be included as data in the distribution: .lua, .json, .png, .jpg, .ogg and .obj. All .txt files will also be copied to the generated zip and be visible to the user when they unzip it. This is intended for README.txt or similar files.

If the -r option is given then all subdirectories will also be included (recursively), otherwise only the files in current directory are included.

The generated zip will also contain an amulet_license.txt file containing the Amulet license as well as the licenses of all third party libraries used by Amulet. Some of these licenses require that they be distributed with copies of their libraries, so to comply you should include amulet_license.txt when you distribute your work. Note that these licenses do not apply to your work itself.

If you create a conf.lua file in the same directory as your other Lua files containing the following:

title = "My Game Title"
shortname = "mygame"
appid = "com.some-unique-id.123"
version = "1.0.0"

display_name = "My Game"
dev_region = "en"
supported_languages = "en,fr,nl,de,ja,zh-CN,zh-TW"
icon = "icon.png"
launch_image = "launch.png"
orientation = "any"

then this will be used for various bits of meta-data in the generated packages. In particular shortname will be used in the name of the generated zip files (otherwise they will just be called "Untitled").

If you're generating an iOS package for submission to the App Store you'll need to fill out all the settings.

Note that the generated iOS ipa package is not signed. You will need to sign it using a tool like sigh or this script before submitting it to the app store or deploying on a device.

IMPORTANT: Avoid unzipping and re-zipping the generated packages as you may inadvertently strip the executable bit from some files, which will cause them not to work.

# 3D models

Amulet has some basic support for loading 3D models in Wavefront .obj format.

This loads the given .obj file and returns 4 things:

1. A buffer containing the vertex, normal and texture coordinate data.
2. The stride in bytes.
3. The offset of the normals in bytes.
4. The offset of the texture coordinates in bytes.

The vertex data is always at offset 0. If the normal or texture coordinate data is not present, the corresponding return value will be nil.

The faces in the .obj file must all be triangles (quads aren't supported).

Here's an example of how to load a model and display it. The example loads an model from model.obj and assumes it contains normal and texture coordinate data and the triangles have a counter-clockwise winding. It loads a texture from the file texture.png.

local win = am.window{depth_buffer = true}

local buf, stride, norm_offset, tex_offset = am.load_obj("model.obj")
local verts = buf:view("vec3", 0, stride)
local normals = buf:view("vec3", norm_offset, stride)
local uvs = buf:view("vec2", tex_offset, stride)

precision mediump float;
attribute vec3 vert;
attribute vec2 uv;
attribute vec3 normal;
uniform mat4 MV;
uniform mat4 P;
varying vec2 v_uv;
void main() {
vec3 light = normalize(vec3(1, 0, 2));
vec3 nm = normalize((MV * vec4(normal, 0.0)).xyz);
v_uv = uv;
gl_Position = P * MV * vec4(vert, 1.0);
}
]], [[
precision mediump float;
uniform sampler2D tex;
varying vec2 v_uv;
void main() {
gl_FragColor = texture2D(tex, v_uv) * vec4(v_shadow, 1.0);
}
]])

win.scene =
am.cull_face"ccw"
^ am.translate(0, 0, -5)
^ am.bind{
P = math.perspective(math.rad(60), win.width/win.height, 1, 1000),
vert = verts,
normal = normals,
uv = uvs,
tex = am.texture2d("texture.png"),
}
^am.draw"triangles"

# Extra table functions

The following functions suplement the standard Lua table functions.

### table.shallow_copy(t)

Returns a shallow copy of t (i.e. a new table with the same keys and values as t).

### table.deep_copy(t)

Returns a deep copy of t (all t's keys and values are copied recursively). Cycles are detected and reproduced in the new table.

### table.search(arr, elem)

Return the index of elem in arr or nil if it's not found.

### table.shuffle(t [,rand])

Randomly rearranges the values of t. The optional rand argument should be a function where rand(n) returns an integer between 1 and n (like math.random). By default math.random is used.

### table.clear(t)

Remove all t's pairs.

### table.remove_all(arr, elem)

Remove all values equal to elem from arr.

### table.append(arr1, arr2)

Inserts all of arr2's values at the end of arr1.

### table.merge(t1, t2)

Sets all the key-value pairs from t2 in t1.

### table.keys(t)

Returns an array of t's keys.

### table.values(t)

Returns an array of t's values.

### table.filter(arr, f)

Returns a new array which contains only the values from arr for which f(elem) returns true (or any value besides nil or false).

### table.tostring(t)

Converts a table to a string. The returned string is a valid Lua table literal.

### table.count(t)

Returns the total number of pairs in the table.

# Amulet require function

The require function in Amulet is slightly different from the default one. The default Lua package loaders have been removed and replaced with a custom loader. The loader passes a new empty table into each module it loads. All exported functions can be added to this table, instead of creating a new table. If no other value is returned by the module, the passed in table will be used as the return value for require.

The passed in export table can be accessed via the ... expression. Here's a short example:

local mymodule = ...

mymodule.message = "hello"

function mymodule.print_message()
print(mymodule.message)
end

If this module is in the file mymodule.lua, then it can be imported like so:

local mymodule = require "mymodule"

mymodule.print_message() -- prints hello

This scheme allows cyclic module imports, e.g. module A requires module B which in turn requires module A. Amulet will detect the recursion and return A's (incomplete) export table in B. Then when A has finished initialising, all its functions will be available in B. This does mean that B can't call any of A's functions while its initialising, but after initialisation all of A's functions will be available.

Of course you can still return your own values from modules and they will be returned by require as with the default Lua require function.

# Logging

### log(msg, ...)

Log a message to the console. The message will also appear in an overlay on the main window.

msg may contain format specifiers like the standard Lua string.format function.

The logged messages are prefixed with the file name and line number where log was called.

Example:

log("here")
log("num = %g, string = %s", 1, "two")

# Preventing accidental global variable usage

### noglobals()

Prevents the creation of new global variables. An error will be raised if a new global is created after this call, or if an attempt is made to read a nil global.

# Globbing

### am.glob(patterns)

Returns an array (table) of file names matching the given glob pattern(s). patterns should be a table of glob pattern strings. A glob pattern is a file path with zero or more wildcard (*) characters.

Any matching files that are directories will have a slash (/) appended to their names, even on Windows.

The slash character (/) can be used as a directory separator on Windows (you don't need to use \). Furthermore returned paths will always have '/' as the directory separator, even on Windows.

Note: This function only searches for files on the file system. It won't search the resource archive in a exported game. Its intended use is for writing file processing utilities and not for use directly in games you wish to distribute.

Example:

local image_files = am.glob{"images/*.png", "images/*.jpg"}

# Running JavaScript

### am.eval_js(js)

Runs the given JavaScript string and returns the result as a Lua value. JavaScript objects and arrays are converted to Lua tables and other JavaScript types are converted to the corresponding Lua types. undefined is converted to nil.

This function only works when running in a browser. On other platforms it has no effect and always returns nil.

# Converting to/from JSON

### am.to_json(value)

Converts the given Lua value to a JSON string and returns it.

Tables with string keys are converted to JSON objects and tables with consecutive integer keys starting at 1 are converted to JSON arrays. Empty tables are converted to empty JSON arrays. Other types of tables are not supported anc cycles are not detected.

### am.parse_json(json)

Converts the given JSON string to a Lua value and returns it. If there was an error parsing the JSON then nil is returned and the error message is returned as a second return value.

Loads the Lua script in filename and returns a function that, when called, will run the script. If the file doesn't exist nil is returned.

Loads filename and returns its contents as a string or nil if the file wasn't found.

# Performance stats

### am.perf_stats()

Returns a table with the following fields:

• avg_fps: frames per second averaged over the last 60 frames
• min_fps: the minimum frames per second over the last 60 frames
• frame_draw_calls: the number of draw calls in the last frame
• frame_use_program_calls: the number of use_program calls in the last frame

# Amulet version

### am.version

The current Amulet version, as a string. E.g. "1.0.3".

# Platform

### am.platform

The platform Amulet is running on. It will be one of the strings "linux" "windows" "osx" "ios" "android" or "html".

# Language

### am.language()

Returns the user's preferred ISO 639-1 language code in lower case(e.g. "en"), possibly followed by a dash and an ISO 3166-1 coutry code in upper case (e.g. "fr-CA"). The returned value will be one of the languages listed in the conf.lua file (see Exporting). This currently only returns a meaningful value on iOS and Android (on all other platforms it always returns "en"`).

# Game Center (iOS only)

The following functions are only available on iOS.

### am.init_gamecenter()

Initialize Game Center. This must be called before any other Game Center functions.

### am.gamecenter_available()

Returns true if Game Center was successfully initialized.