Codea Extension Libraries

loopspace

2020-04-12

# Contents

Over the years that I've been enjoying programming with Codea, I've written an awful lot of code. Quite a bit has ended up in libraries that I reuse a lot; so I have code that handles touches, a user interface, bézier curves, fireworks, and other things that I use more than once. Pretty much all of this is public, because why not?, but I suspect that most of it is too tied to my way of working to be easily usable by others. The bits that might be are my extension libraries. These take built-in objects in Codea and add extra functionality. Because they work with existing objects, they probably would be more readily integrated into others' code than the rest of my stuff.

There are three key extension libraries (links are to the files on github):

As much as for myself as others, I'm going to document the extensions here.

# 1 Colours

The following describes the extensions available for the color userdata. In this, a variable starting with a c refers to an existing color object.

• c:new(<args>) This creates a new colour according to a scheme. The accepted arguments is a superset of the arguments that can be passed to the ‘color‘ userdata. The extra possibilities are of the form "model",args to specify a colour according to a model, or the argument can be an existing color object which is cloned into a new one. The available colour models are:

• hsl: specify the colour via hue, saturation, and lightness (and alpha). The range for each is $\left[0,1\right]$, except for alpha which is an integer in $\left[0,255\right]$.

• hsv: specify the colour via hue, saturation, and value (and alpha). The range for each is $\left[0,1\right]$, except for alpha which is an integer in $\left[0,255\right]$.

• x11: specify a colour by giving its x11 name.

• svg: specify a colour by giving its svg name.

• random: pick a colour at random. The colour is picked by specifying a random hue, with full saturation and value.

• transparent: returns the colour color(0,0,0,0)

• If passed an unknown model, it will try to match it to either an SVG or an X11 colour.

Note: I quite frequently use this via color():new(...) as it seems silly to assign a new color object to a variable just for the purpose of creating a new color from it.

I did consider trying to hack the color initialisation code, but decided against it, which is why this method has to be called explicitly.

• c:tohsl() returns an hsla representation of the colour as four numbers. The hue, saturation, and lightness are all in the range $\left[0,1\right]$ while the alpha is an integer in $\left[0,255\right]$.

• c:tohsv() returns an hsva representation of the colour as four numbers. The hue, saturation, and value are all in the range $\left[0,1\right]$ while the alpha is an integer in $\left[0,255\right]$.

• c1:xblend(t,c2,m) returns a blend of the colours c1 and c2, taking $t%$ of the first colour and $\left(100-t\right)%$ of the second. The optional boolean m determines whether to mix the alphas or just take the alpha of c1.

• c:tint(t,m) returns a tinted colour, meaning a blend of the colour with white, with $t%$ of the given colour. The optional boolean m determines whether to mix the alphas (the white is taken to be fully opaque) or just take the alpha of c.

• c:shade(t,m) returns a shaded colour, meaning a blend of the colour with black, with $t%$ of the given colour. The optional boolean m determines whether to mix the alphas (the black is taken to be fully transparent) or just take the alpha of c.

• c:tone(t,m) returns a toned colour, meaning a blend of the colour with grey (specifically, mid-tone), with $t%$ of the given colour. The optional boolean m determines whether to mix the alphas (the grey is taken to be exactly half opaque) or just take the alpha of c.

• c:complement(m) returns a colour in which each rgb channel is replaced with its complement. The optional boolean m determines whether to apply this also to the alpha.

• c:posterise(t,m) returns a "posterised" version of the colour where each channel is forced to be fully on or off. The optional parameter t determines the threshold between on or off, with default $127$. The optional boolean m determines whether to apply this also to the alpha.

• c:opacity(t) returns the colour with alpha channel adjusted by $t%$.

• c:opaque() returns an opaque version of the colour.

• c:tostring() pretty-prints the colour into a string of the form R:r G:g B:b A:a, this is also called if the colour object is used in a context where a string is expected.

• c:fromstring(s) takes the string in the pretty-printed format and converts it back into a colour object. Together with c:tostring(), this can be used to save and restore a colour object.

• c:readData(type,key,default) tries to read a colour from storage stored with key. The type can be one of global, project, or local.

• c:saveData(type,key) saves the colour into one of the storage areas with key. The type can be one of global, project, or local.

# 2 Vectors

My vector extension library grew fairly organically. It started as a set of classes implementing vector-like objects, such as complex numbers. Once I learned about the ability to extend objects via metatables, I rebuilt the classes as extensions. This does mean that there is a bit of legacy code which exists to avoid backwards incompatibility in some of my programs.

This code provides the following extensions:

1. vec2s are promoted to complex numbers

2. vec4s are promoted to quaternions (this was written before the quat userdata was added to Codea)

3. quats have extra methods added to make them more usable

4. vec3s interact with quaternions and matrices

5. matrix has extra methods added to interact better with other objects

6. native Codea functions such as line are adapted to take suitable vector inputs

7. math functions are adapted to take suitable vector inputs

8. some utility functions are provided

## 2.1 General Vector Methods

All the vector types (vec2, vec3, vec4, and quat) have the following methods added:

• v:is_finite() checks to see if the vec2 is finite.

• v:normalise() normalises the vec2 in the same manner as v:normalize() but in a safe way in that if the v:normalize() returns a non-finite vec2 then this returns vec2(1,0).

• v:len1() returns the $1$–norm of the vec2.

• v:dist1(u) returns the $1$–distance between the vec2s.

• v:leninf() returns the $\infty$–norm of the vec2.

• v:distinf(u) returns the $\infty$–distance between the vec2s.

## 2.2 vec2

In all these, variable v denotes an existing vec2 object.

• v:clone() clones the vec2 object.

• v:is_real() checks to see if the vec2 is real, when considered as a complex number; i.e., tests to see if the y component is zero.

• v:is_imaginary() checks to see if the vec2 is pure imaginary, when considered as a complex number; i.e., tests to see if the x component is zero.

• The arithmetical operators are overloaded in the following ways, which are guided by viewing vec2s as complex numbers:

• Addition and subtraction are allowed between a vec2 and a number.

• Multiplication and division are allowed between vec2s. Also acceptable is integer division, as in v//k.

• Powers are allowed with a variety of exponents: numbers and vec2s behave as the appropriate powers. An exponent that is neither a number or a vec2 is treated as meaning complex conjugation.

Conjugation is also available as v:co() or v:conjugate(). Powers are also available as realpower(v,n,k) and complexpower(v,n,k), where the extra parameter k selects the appropriate branch of the logarithm.

• v:real() returns the real part of v.

• v:imaginary() returns the real part of v.

• There are two methods for converting a vec2 to a string, either as in cartesian or polar form. By default, the cartesian form is used. Both are also available as v:topolarstring() and v:tocartesianstring().

• v:arg() returns the argument (angle) of the vec2 as a complex number.

• v:tomatrix() converts the vec2, viewed as a complex number, to a matrix.

• v:random() returns a random vec2 with Euclidean length $1$. That is, a random point on the unit circle.

• Complex_unit() returns vec2(1,0).

• Complex_zero() returns vec2(0,0).

• Complex_i() currently returns vec2(0,-1). I'm not entirely sure why.

## 2.3 vec3

The primary extensions to the vec3 userdata are to allow them to be transformed by various other things.

• v:toQuaternion() promotes it to a vec4, viewed as the vector component.

• v:applyQuaternion(q) applies q, which is a vec4, to v, viewing q as a quaternion.

• v:toquat() promotes it to a quat, viewed as the vector component.

• v:applyquat(q) applies q, which is a quat, to v.

• v:rotate() is extended to take either a quat or a number and vec3 (as angle-axix) to specify the rotation.

• Exponentiation is defined to mean applying a rotation, so v^q applies q, either a vec4 or a quat, to v as a rotation.

• v:rotateTo(u) returns a vec4 which represents the quaternion that rotates v to the direction u.

• v:rotateToquat(u) returns a quat which represents the quaternion that rotates v to the direction u. This just uses quat.fromToRotation(v,u).

• Arithmetic operators are extended, in that multiplication is allowed between a vec3 and a matrix, while addition and subtraction are allowed between a vec3 and a number by viewing the number as vec3(a,a,a).

• v:exp() converts v to a quaternion through considering it as a tangent vector at $1$ on the unit sphere.

• v:random() returns a random vec3 on the unit sphere.

• v:orthogonal(u) returns a unit vec3 which is orthogonal to both v and u.

## 2.4 vec4

Before the quat userdata was introduced, I used vec4s as quaternions. I still have legacy code that uses vec4 instead of quat so I keep this code around for the time being.

• v:is_real() is true if the vec4 represents a real number as a quaternion.

• v:is_imaginary() is true if the vec4 represents a pure imaginary quaternion.

• v:slen() projects the quaternion onto the unit sphere and then finds the spherical distance of it from $1$.

• v:sdist(u) projects the quaternions onto the unit sphere and then finds the spherical distance between them.

• The arithmetic operators are overloaded to take both numbers and vec4s, with multiplication and division implemented as quaternionic multiplication and division. Also, multiplication is allowed between a quaternion and a matrix, viewing the quaternion as a rotation matrix.

• v:co() and v:conjugate() return the conjugate of the vec4 as a quaternion.

• Powers of vec4s are allowed with integer or real powers, and if a non-numerical power is used then the conjugate is returned, so v^"" can be used as a shortcut for the conjugate.

• v:lerp(u,t) computes a unit quaternion by taking the point on the line from v to u given by the parameter t and then normalising it.

• v:slerp(u,t) computes a unit quaternion by taking the point on the spherical path from v to u given by the parameter t.

• v:make_lerp(u) can be used to cache the computations needed in computing the various lerps from v to u, so this returns a function which takes in t and computes the lerp from v to u at parameter t.

• v:make_slerp(u) can be used to cache the computations needed in computing the various slerps from v to u, so this returns a function which takes in t and computes the slerp from v to u at parameter t.

• v:toreal() returns the real part of the vec4.

• v:tovector() and v:vector() return the vector part of the vec4.

• v:log() treats v as a point on the unit sphere and computes a tangent vector at the identity which exponentiates to v.

• v:tomatrixleft() converts v to a matrix by viewing v as a rotation; this assumes that matrices act on the left.

• v:tomatrixright() converts v to a matrix by viewing v as a rotation; this assumes that matrices act on the right.

• v:tomatrix() is a synonym for v:tomatrixright().

• v:toangleaxis() returns an angle-axis representation of the quaternion represented by v.

• Gravity(v) returns a vec4 which represents rotating the negative $y$–axis to point down in the gravitational frame of reference, but modified by the rotation represented by v.

## 2.5 quat

When this was first written, the quat userdata wasn't available until the setup function was started, meaning that I had to provide a wrapper around it. So the quaternion creation function is called __quat. This could now be removed and all __quat replaced by quat, I just haven't gotten round to that as yet.

• q:is_real() tests if the quaternion happens to be real.

• q:is_imaginary() tests if the quaternion happens to be pure imaginary.

• q:len() returns the Euclidean length of q.

• q:lenSqr() returns the square of the Euclidean length of q.

• q:dist(qq) returns the Euclidean distance between q and qq.

• q:distSqr(qq) returns the square of the Euclidean distance between q and qq.

• q:dot(qq) returns the (inner) dot product of the two quats.

• q:normalize() is an unsafe normalisation, possibly returning a non-finite quat.

• q:slen() returns the spherical distance of the normalisation of q from the identity quaternion.

• q:sdist(qq) returns the spherical distance between the normalisations of the quata.

• q:tangent(x,y,z,t) applies a tangent vector (either as three numbers or a vec3) to the quaternion, scaled by the parameter t.

• q:random() returns a random unit quaternion.

• Arithmetic operations are defined for quaternions, and between quaternions and numbers.

• q:co() and q:conjugate() return the conjugate of q.

• Powers of quats are defined for integer and real powers, while if given something else then the conjugate is returned, so q^"" will return the conjugate.

• q:lerp(qq,t) defines the linear interpolation between q and qq as specified by t.

• q:make_lerp(qq) returns a function that can be used to find the linear interpolations between q and qq.

• q:make_slerp(qq) returns a function that can be used to find the spherical interpolations between q and qq.

• q:toreal() returns the real part of q.

• q:vector() and q:tovector() return the vector part of q as a vec3.

• q:log() returns a vec3 viewed as a tangent vector from the identity to q.

• q:tomatrixleft() converts the quat to a matrix with matrices acting on the left.

• q:tomatrixright() converts the quat to a matrix with matrices acting on the right.

• q:tomatrix() converts the quat to a matrix with matrices acting on the right.

• q:toangleaxis() converts the quat to an angle-axis representation.

• q:Gravity() returns a quaternion that rotates vec3(0,-1,0) to the current gravity vector, adjusted by the quaternion q.

## 2.6 matrix

The matrix userdata is extended so that multiplication and rotation can take multiple types.

• Multiplication is defined between a matrix and another matrix, a quat, a vec4 (as a quaternion), a vec2 (as a complex number promoted to a matrix), and a vec3.

• m:rotate() can take either a vec4 or quat as its input.

## 2.7 Native Codea Functions

Various native Codea functions are extended to take more inputs.

• modelMatrix, applyMatrix, viewMatrix, projectionMatrix can take a vec4 or quat, both representing a quaternion, or a vec2 representing a complex number.

• resetMatrices resets all matrices.

• rotate can take a vec4 or quat.

• translate and scale can take vec3s.

• camera can take vec3s for any of the three directions.

• line, ellipse, rect, clip, sprite, readImage, image, text can take vec2s as part or all of their arguments.

## 2.8 math

The various math functions are extended, generally to allow more types of input.

• The following functions can take a vec2, thought of as a complex number:

• math.abs

• math.pow

• math.sqrt

• math.exp

• math.cos

• math.sin

• math.cosh

• math.sinh

• math.tan

• math.tanh

• math.log (sometimes it is important to use the original logarithm, which is saved as math.olog)

## 2.9 Utilities

• Heaviside(t) is defined to be $1$ if $t\ge 0$ and $0$ if $t<0$.

• is_a(a,b) tests to see if a is the same type of thing as b. This is used in most of the extension methods.

• edge(t,a,b) forces $t$ to lie between $a$ and $b$ (with $a=0$ and $b=1$ if not specified).

• smoothstep(t,a,b) is like edge but more gentle.

• smootherstep(t,a,b) is smooth with a capital "smoo".

• qGravity() returns a vec4 (viewed as a quaternion) which rotates the $z$–axis to point directly down.

• quatGravity() returns a quat which rotates the $z$–axis to point directly down.

• qRotation(a,x,y,z) converts an angle-axis to a vec4.

• qEuler(a,b,c,v) converts an Euler-angles representation of a quaternion to a vec4, the optional table v defines how to interpret the axes corresponding to the given angles.

• qTangent(x,y,z,t) views views a vector (either as three numbers or a vec3) as a tangent vector at the identity and converts it to a vec4.

• qRotationRate() converts the current rotation rate into a vec4.

• quatRotationRate() converts the current rotation rate into a quat.

# 3 Meshes

The extensions to the mesh userdata all began life as separate functions, but once I learnt about metatables then I adapted them to methods. I still kept the original functions and made the methods wrappers around them, mainly for laziness and legacy code. With the advent of Codea Craft, I added the PseudoMesh class so that these solids could be used as models for craft entities.

The methods all work by taking in a table of configuration options. Most options have defaults. There are some options common to all solids.

• mesh: in the functions, this was for specifying the mesh.

• position: where in the mesh's vertices to insert the solid (default: at the end).

• light: pre-Craft, lighting was achieved by a shader, this specified the direction of the light.

• ambience: the level of ambient light.

• intensity: the intensity of the light.

• texture: assigns a texture to the mesh.

• basicLighting: whether to use a lighting shader or not. If not, then the "lighting" is baked into the mesh by adjusting the colours.

• colour (or color): overall colour of the solid.

• texOrigin: (vec2) lower-left corner of the texture to use for this solid.

• texSize: (vec2) width and height of the part of the texture to use for this solid.

• faceted: many solids can be faceted or smooth; faceted means that the individual faces are clearly visible whereas smooth adjusts the lighting to minimise the effect of the faces.

• texCoords: pass explicit coordinates for the texture (the solids can calculate their best guess which is usually best).

## 3.1 The Solids

• m:addJewel(t) adds a "jewel-like" solid to the mesh.

• origin: the centre of the solid.

• axis: a vec3 pointing along the axis of the solid.

• aspect: ratio of height to diameter.

• size: distance from the centre to the apex.

• sides: number of vertices around the middle of the jewel.

• m:addPolyton(t) adds a polygon to the mesh. Technically, the polygon doesn't have to be flat. It is specified by a family of points which are joined to their barycentre.

• vertices: a table of vertices specifying the polygon.

• closed: does the polygon close up?

• viewFrom: which side of the polygon is "up" (for lighting purposes), can be either a number (designating a direction away from the surface) or a vector (designating a position from which to view the polygon).

• m:addCone(t)

A cone is like a polygon, except that instead of joining the boundary points to their barycentre then they are joined to a specified apex.

• apex: the apex of the cone.

• vertices: a table of vertices specifying the rim of the cone.

• closed: does the cone close up?

• viewFrom: which side of the cone is "up" (for lighting purposes), can be either a number (designating a direction away from the surface) or a vector (designating a position from which to view the cone).

• m:addCylinder(t)

This adds a cylinder or segment of a cylinder to the mesh.

• !ends!: a $2$-bit number to determine which ends to fill in, first bit is start, second bit is end.

• !solid!: if the shape is a segment of a cylinder, do we add the flat faces of the slice? If !ends! is not given but !solid! is then !ends! defaults to filling in both ends.

• !startAngle!, !endAngle!, !deltaAngle! are used to determine the segment angles, any combination can be given and if not enough information is given then the defaults are, respectively, $0$, $360$, and $360$.

• The position and orientation of the cylinder is specified using some of the following parameters.

• !startCentre!, !startCenter! the position of the centre of the starting face.

• !endCentre!, !endCenter! the position of the centre of the ending face.

• !origin!, !centre!, !center! the position of the centre of the cylinder.

• !axis!, !axes! one or more vectors determining the orientation of the cylinder, if !axes! is given then the first vector is taken to point along the cylinder and the other two are used to determine how to interpret the angles (if given).

• !height! the length of the cylinder, used in combination with !axis! or !axes!.

• !startWidth!, !startHeight!, !startRadius!, !radius! used to determine the radii at the start of the cylinder. If the width and height are given then these can be used to make the ends elliptical. These can be given as numbers or vectors. If numbers, they are converted to vectors by a sensible method: if axes were specified then those vectors are used, otherwise vectors orthogonal to the primary axis are generated.

• m:addPyramid(t)

A pyramid is a cone on a regular polygon.

• sides number of sides of the polygon.

• axes used to determine the orientation of the polygon.

• apex apex of pyramid.

• aspect ratio of height to base radius.

• m:addBlock(t)

A block is a cuboid, namely a shape made up of rectangular faces.

• faces which faces to render. Each face is specified by a list of four vertices. If not given, it defaults to the list:



{1,2,3,4},
{5,7,6,8},
{1,5,2,6},
{3,4,7,8},
{2,6,4,8},
{1,3,5,7}


• singleImage if set, each side gets the whole texture. If not, it uses it as a strip of images to use for the sides.

• faceRotations used to rotate the images on each face. This is a table of integers, corresponding to how much to rotate the image on each of the faces, $1$ means no rotation.

• The vertices of the block itself can be specified in a variety of different ways.

• block is a list of eight vertices, they should be given in the order corresponding to a binary ordering of the corners of the unit cube.

• centre, center, width, height, depth, size. These define the "block" by specifying a centre followed by the width, height, and depth of the cube (size sets all three). These can be vec3s or numbers. If numbers, they correspond to the dimensions of the "block" in the x, y, and z directions respectively. If vec3s, then are used to construct the vertices by adding them to the centre so that the edges of the "block" end up parallel to the given vectors.

• startCentre, startCenter, startWidth, startHeight, endCentre, endCenter, endWidth, endHeight. This defined the "block" by defining two opposite faces of the cube and then filling in the region in between. The two faces are defined by their centres, widths, and heights. The widths and heights can be numbers or vec3s exactly as above.

• m:addSphere(t)

This adds a sphere to the mesh. By specifying axes, the sphere can be distorted to an ellipsoid. Axes are also useful when using a texture.

• !centre!, !center! defines the centre of the sphere.

• !axes! can be used to distort the sphere into an ellipsoid (note that each axis is also multiplied by the radius) or to orient the sphere with respect to its texture.

• !number! is the number of strips used to define the sphere.

• m:addSphereSegment(t)

This adds a segment of a sphere to the mesh. The segment is defined by specifying the latitude and longitude of the slice.

• !centre!, !center! defines the centre of the sphere.

• !axes! can be used to distort the sphere into an ellipsoid (note that each axis is also multiplied by the radius) or to orient the sphere with respect to its texture.

• !number! is the number of strips used to define the sphere.

• !solid! determines whether to add in the non-spherical faces.

• startLatitude, endLatitude, deltaLatitude are used to define the latitudinal angles of the segment.

• startLongitude, endLongitude, deltaLongitude specify the longitudinal angles of the segment.

• incoming and outgoing define directions that the ends of the segment will point towards, meaning that the interpretations of latitude for the starting and ending angles need not be the same.

## 3.2 PseudoMesh

The PseudoMesh class exists to convert all the above shapes to models that can be used with Codea Craft. It can be used as a drop in for a mesh, and then provides a method p:toModel() which converts the structure to a Craft model.

Because meshes and models handle normals slightly differently, it can be that the model ends up with its normals pointing the wrong way. To correct that, use p:invertNormals() before converting to a model.

This code also defines the function extendModel() which adds the following to the craft.model object:

• jewel

• pyramid

• polygon

• block

• cylinder

• sphere

• sphereSegment

These can be used as, for example craft.model.cylinder(t) to get a cylindrical model.