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):
-
Meshes (which can be used with Codea Craft via my PseudoMesh class)
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 existingcolor
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 , except for alpha which is an integer in . -
hsv
: specify the colour via hue, saturation, and value (and alpha). The range for each is , except for alpha which is an integer in . -
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 colourcolor(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 while the alpha is an integer in . -
c:tohsv()
returns an hsva representation of the colour as four numbers. The hue, saturation, and value are all in the range while the alpha is an integer in . -
c1:xblend(t,c2,m)
returns a blend of the coloursc1
andc2
, taking of the first colour and of the second. The optional booleanm
determines whether to mix the alphas or just take the alpha ofc1
. -
c:tint(t,m)
returns a tinted colour, meaning a blend of the colour with white, with of the given colour. The optional booleanm
determines whether to mix the alphas (the white is taken to be fully opaque) or just take the alpha ofc
. -
c:shade(t,m)
returns a shaded colour, meaning a blend of the colour with black, with of the given colour. The optional booleanm
determines whether to mix the alphas (the black is taken to be fully transparent) or just take the alpha ofc
. -
c:tone(t,m)
returns a toned colour, meaning a blend of the colour with grey (specifically, mid-tone), with of the given colour. The optional booleanm
determines whether to mix the alphas (the grey is taken to be exactly half opaque) or just take the alpha ofc
. -
c:complement(m)
returns a colour in which eachrgb
channel is replaced with its complement. The optional booleanm
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 parametert
determines the threshold between on or off, with default . The optional booleanm
determines whether to apply this also to the alpha. -
c:opacity(t)
returns the colour with alpha channel adjusted by . -
c:opaque()
returns an opaque version of the colour. -
c:tostring()
pretty-prints the colour into a string of the formR: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 withc: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 withkey
. Thetype
can be one ofglobal
,project
, orlocal
. -
c:saveData(type,key)
saves the colour into one of the storage areas withkey
. Thetype
can be one ofglobal
,project
, orlocal
.
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:
-
vec2
s are promoted to complex numbers -
vec4
s are promoted to quaternions (this was written before thequat
userdata was added to Codea) -
quat
s have extra methods added to make them more usable -
vec3
s interact with quaternions and matrices -
matrix
has extra methods added to interact better with other objects -
native Codea functions such as
line
are adapted to take suitable vector inputs -
math functions are adapted to take suitable vector inputs
-
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 thevec2
is finite. -
v:normalise()
normalises thevec2
in the same manner asv:normalize()
but in a safe way in that if thev:normalize()
returns a non-finitevec2
then this returnsvec2(1,0)
. -
v:len1()
returns the –norm of thevec2
. -
v:dist1(u)
returns the –distance between thevec2
s. -
v:leninf()
returns the –norm of thevec2
. -
v:distinf(u)
returns the –distance between thevec2
s.
2.2 vec2
In all these, variable v
denotes an existing vec2
object.
-
v:clone()
clones thevec2
object. -
v:is_real()
checks to see if thevec2
is real, when considered as a complex number; i.e., tests to see if they
component is zero. -
v:is_imaginary()
checks to see if thevec2
is pure imaginary, when considered as a complex number; i.e., tests to see if thex
component is zero. -
The arithmetical operators are overloaded in the following ways, which are guided by viewing
vec2
s as complex numbers:-
Addition and subtraction are allowed between a
vec2
and a number. -
Multiplication and division are allowed between
vec2
s. Also acceptable is integer division, as inv//k
. -
Powers are allowed with a variety of exponents: numbers and
vec2
s behave as the appropriate powers. An exponent that is neither a number or avec2
is treated as meaning complex conjugation.Conjugation is also available as
v:co()
orv:conjugate()
. Powers are also available asrealpower(v,n,k)
andcomplexpower(v,n,k)
, where the extra parameterk
selects the appropriate branch of the logarithm.
-
-
v:real()
returns the real part ofv
. -
v:imaginary()
returns the real part ofv
. -
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 asv:topolarstring()
andv:tocartesianstring()
. -
v:arg()
returns the argument (angle) of thevec2
as a complex number. -
v:tomatrix()
converts thevec2
, viewed as a complex number, to a matrix. -
v:random()
returns a randomvec2
with Euclidean length . That is, a random point on the unit circle. -
Complex_unit()
returnsvec2(1,0)
. -
Complex_zero()
returnsvec2(0,0)
. -
Complex_i()
currently returnsvec2(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 avec4
, viewed as the vector component. -
v:applyQuaternion(q)
appliesq
, which is avec4
, tov
, viewingq
as a quaternion. -
v:toquat()
promotes it to aquat
, viewed as the vector component. -
v:applyquat(q)
appliesq
, which is aquat
, tov
. -
v:rotate()
is extended to take either aquat
or a number andvec3
(as angle-axix) to specify the rotation. -
Exponentiation is defined to mean applying a rotation, so
v^q
appliesq
, either avec4
or aquat
, tov
as a rotation. -
v:rotateTo(u)
returns avec4
which represents the quaternion that rotatesv
to the directionu
. -
v:rotateToquat(u)
returns aquat
which represents the quaternion that rotatesv
to the directionu
. This just usesquat.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 avec3
and a number by viewing the number asvec3(a,a,a)
. -
v:exp()
convertsv
to a quaternion through considering it as a tangent vector at on the unit sphere. -
v:random()
returns a randomvec3
on the unit sphere. -
v:orthogonal(u)
returns a unitvec3
which is orthogonal to bothv
andu
.
2.4 vec4
Before the quat
userdata was introduced, I used vec4
s 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 thevec4
represents a real number as a quaternion. -
v:is_imaginary()
is true if thevec4
represents a pure imaginary quaternion. -
v:slen()
projects the quaternion onto the unit sphere and then finds the spherical distance of it from . -
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
vec4
s, 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()
andv:conjugate()
return the conjugate of thevec4
as a quaternion. -
Powers of
vec4
s are allowed with integer or real powers, and if a non-numerical power is used then the conjugate is returned, sov^""
can be used as a shortcut for the conjugate. -
v:lerp(u,t)
computes a unit quaternion by taking the point on the line fromv
tou
given by the parametert
and then normalising it. -
v:slerp(u,t)
computes a unit quaternion by taking the point on the spherical path fromv
tou
given by the parametert
. -
v:make_lerp(u)
can be used to cache the computations needed in computing the variouslerp
s fromv
tou
, so this returns a function which takes int
and computes thelerp
fromv
tou
at parametert
. -
v:make_slerp(u)
can be used to cache the computations needed in computing the variousslerp
s fromv
tou
, so this returns a function which takes int
and computes theslerp
fromv
tou
at parametert
. -
v:toreal()
returns the real part of thevec4
. -
v:tovector()
andv:vector()
return the vector part of thevec4
. -
v:log()
treatsv
as a point on the unit sphere and computes a tangent vector at the identity which exponentiates tov
. -
v:tomatrixleft()
convertsv
to a matrix by viewingv
as a rotation; this assumes that matrices act on the left. -
v:tomatrixright()
convertsv
to a matrix by viewingv
as a rotation; this assumes that matrices act on the right. -
v:tomatrix()
is a synonym forv:tomatrixright()
. -
v:toangleaxis()
returns an angle-axis representation of the quaternion represented byv
. -
Gravity(v)
returns avec4
which represents rotating the negative –axis to point down in the gravitational frame of reference, but modified by the rotation represented byv
.
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 ofq
. -
q:lenSqr()
returns the square of the Euclidean length ofq
. -
q:dist(qq)
returns the Euclidean distance betweenq
andqq
. -
q:distSqr(qq)
returns the square of the Euclidean distance betweenq
andqq
. -
q:dot(qq)
returns the (inner) dot product of the twoquat
s. -
q:normalize()
is an unsafe normalisation, possibly returning a non-finitequat
. -
q:slen()
returns the spherical distance of the normalisation ofq
from the identity quaternion. -
q:sdist(qq)
returns the spherical distance between the normalisations of thequat
a. -
q:tangent(x,y,z,t)
applies a tangent vector (either as three numbers or avec3
) to the quaternion, scaled by the parametert
. -
q:random()
returns a random unit quaternion. -
Arithmetic operations are defined for quaternions, and between quaternions and numbers.
-
q:co()
andq:conjugate()
return the conjugate ofq
. -
Powers of
quat
s are defined for integer and real powers, while if given something else then the conjugate is returned, soq^""
will return the conjugate. -
q:lerp(qq,t)
defines the linear interpolation betweenq
andqq
as specified byt
. -
q:make_lerp(qq)
returns a function that can be used to find the linear interpolations betweenq
andqq
. -
q:make_slerp(qq)
returns a function that can be used to find the spherical interpolations betweenq
andqq
. -
q:toreal()
returns the real part ofq
. -
q:vector()
andq:tovector()
return the vector part ofq
as avec3
. -
q:log()
returns avec3
viewed as a tangent vector from the identity toq
. -
q:tomatrixleft()
converts thequat
to amatrix
with matrices acting on the left. -
q:tomatrixright()
converts thequat
to amatrix
with matrices acting on the right. -
q:tomatrix()
converts thequat
to amatrix
with matrices acting on the right. -
q:toangleaxis()
converts thequat
to an angle-axis representation. -
q:Gravity()
returns a quaternion that rotatesvec3(0,-1,0)
to the current gravity vector, adjusted by the quaternionq
.
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
, avec4
(as a quaternion), avec2
(as a complex number promoted to a matrix), and avec3
. -
m:rotate()
can take either avec4
orquat
as its input.
2.7 Native Codea Functions
Various native Codea functions are extended to take more inputs.
-
modelMatrix
,applyMatrix
,viewMatrix
,projectionMatrix
can take avec4
orquat
, both representing a quaternion, or avec2
representing a complex number. -
resetMatrices
resets all matrices. -
rotate
can take avec4
orquat
. -
translate
andscale
can takevec3
s. -
camera
can takevec3
s for any of the three directions. -
line
,ellipse
,rect
,clip
,sprite
,readImage
,image
,text
can takevec2
s 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 asmath.olog
)
-
2.9 Utilities
-
Heaviside(t)
is defined to be if and if . -
is_a(a,b)
tests to see ifa
is the same type of thing asb
. This is used in most of the extension methods. -
edge(t,a,b)
forces to lie between and (with and if not specified). -
smoothstep(t,a,b)
is likeedge
but more gentle. -
smootherstep(t,a,b)
is smooth with a capital "smoo". -
qGravity()
returns avec4
(viewed as a quaternion) which rotates the –axis to point directly down. -
quatGravity()
returns aquat
which rotates the –axis to point directly down. -
qRotation(a,x,y,z)
converts an angle-axis to avec4
. -
qEuler(a,b,c,v)
converts an Euler-angles representation of a quaternion to avec4
, the optional tablev
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 avec3
) as a tangent vector at the identity and converts it to avec4
. -
qRotationRate()
converts the current rotation rate into avec4
. -
quatRotationRate()
converts the current rotation rate into aquat
.
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
(orcolor
): 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
: avec3
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 -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, , , and .
-
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.
-
!endWidth!, !endHeight!, !endRadius!, !radius! used to determine the radii at the start of the cylinder.
-
-
-
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, 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 bevec3
s or numbers. If numbers, they correspond to the dimensions of the "block" in thex
,y
, andz
directions respectively. Ifvec3
s, 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 orvec3
s 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.
-
!size!, !radius! defines the radius 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.
-
!size!, !radius! defines the radius 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
andoutgoing
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.