|||
  O o  Electro
   -

Electro is an application development environment designed for use on cluster-driven tiled displays, virtual reality systems, and desktop workstations. Electro is based on the MPI process model and is bound to the Lua programming language. With support for 3D graphics, 2D graphics, audio, networking, and input handling, Electro provides an easy-to-use scripting system for interactive applications spanning multiple hosts and a variety of displays.

0. This Document

This document details the functions and data types of the Electro API. A knowledge of the Lua Programming Language version 5.0 is prerequisite. Lua documentation and tutorials may be found at the official Lua site. A tutorial example of the use of the Electro API with Lua is included here.

Electro API function definitions appear here as follows:

baz = function(foo, [bar])

Invoke function with arguments foo, and optionally bar, giving baz.

Deprecated functions may be removed in a future release. Deprecated function definitions appear here as follows:

baz = oldfunction(foo, [bar])

Invoke oldfunction with arguments foo, and optionally bar, giving baz.

This document includes a number of Lua code examples. These examples are distinguished from the main text as follows:

print("Hello, World!")

This document also includes a number of paragraphs giving design rationale. These describe some of the trade-offs and design decisions made during the development of Electro. They are presented here in the hope that they will provide the reader with a more thorough understanding of the details of the implementation. Rationale paragraphs are distinguished from the main text as follows:

The implementation of Electro adheres to a number of closely-held design principles: simplicity, orthogonality, and efficiency.

1. Command Line Arguments

Electro accepts the following command line arguments. All arguments with the extension .lua are taken to be Lua scripts to be executed in order. All unrecognized arguments are copied to a table in the Lua environment called E.arguments. This table is populated before any script is run, so all scripts are free to access arguments there.

-f <Lua script>

Execute the named Lua script. This may be used to execute script files that lack the .lua extension.

-m

Grab the mouse pointer. Normally, mouse motion events are only detected while the pointer is within the Electro window. This limits the useful range of mouse motion to the size of the window. When the mouse pointer is grabbed, an application can track mouse motion over unlimited distances. This is useful to applications that implement a pointer that spans a large tiled display.

-a

Disable audio playback. Audio processing will appear to proceed normally, but the audio hardware will remain untouched. This is useful when running on hardware without audio support, or when running Electro along side an application that requires exclusive access to the audio hardware.

-l <log file>

Log all console output to the named file.

-p <port>

Use the given TCP port as network console. The default is 0, and remote console access is disabled.

-t <key>

Use the given key value to access trackd tracker information. The default is 4126.

-c <key>

Use the given key value to access trackd controller information. The default is 4127.

-h <index>

Use the given trackd sensor index as head tracker. The position and orientation of this sensor will be applied to all cameras. The default is zero.

-H <catalog file> <galaxy file>

Process the given Hipparcos stellar catalog into an Electro galaxy file.

-T <catalog file> <galaxy file>

Process the given Tycho stellar catalog into an Electro galaxy file.

2. API

The Electro API is encapsulated in a Lua namespace “E”. The namespace specifier is omitted in this documentation, though it is included in the examples. Be sure to precede all Lua function and constant references with the “E” namespace specifier, or they will be reported as undefined.

E.set_background(0.0, 0.5, 1.0)

These are the major functional sections of the Electro API:

  1. Entities
  2. Images
  3. Brushes
  4. Sound
  5. Console
  6. Configuration
  7. System
  8. Miscellaneous

2.1. Entities

The core of any Electro application is the scene graph. The scene graph is a very common organizing scheme in 3D graphics, and examples of its use abound. Other scene graph libraries include Open Inventor, Blitz3D, Open Scene Graph, OpenSG, and many others. Users of other scene-graph-based libraries should find the Electro API unsurprising.

Scene graph functionality is organized into a small class hierarchy. The entity base class handles all of the common properties and behaviors of scene graph elements, most notably 3D transformation, and the parent-child relationships that hold a scene hierarchy together. The subclasses handle the specifics of controlling the view and lighting, drawing 3D and 2D geometry, etc. The class hierarchy is as follows:

  1. Entity
    1. Object
    2. Sprite
    3. String
    4. Camera
    5. Light
    6. Pivot
    7. Galaxy

Constructor functions instantiate scene graph objects.

object = create_object(filename)

Load an OBJ format 3D object and return an Electro object entity. This is the normal means of importing 3D geometry into an Electro application. It supports polygonal geometry in OBJ file format, including material files and texture maps. The OBJ export capabilities of Maya, Lightwave, Wings3D, and Milkshape are known to be compatible.

The Electro OBJ loader lacks support for free-form curve specification. This is actually due to the fact that the Electro renderer lacks the ability to render free-form curves. Any curve specifiers appearing in OBJ files will be silently ignored. If curves are required, tessellate them using your favorite 3D modeler.

sprite = create_sprite(brush)

Create a sprite entity using the given brush. A sprite consists of a single quadrilateral. The default size of the sprite object is equal to the size of the brush's image. Commonly, sprite objects are used as billboards in 3D scenes, or are displayed as 2D overlays using an orthogonal camera.

string = create_string(text)

Create a string object using the current typeface and the given text. A string object is a polygonal model one unit in height. It sits in the XY-plane, with its lower-left corner at the origin.

camera = create_camera(type)

Create a camera of the given type. To be visible in a scene, all entities must be descendants of a camera entity. Because of this, cameras are commonly left unparented and allowed to remain at the root. Here are the defined camera types:

A scene is allowed to include any number of cameras. One reasonable configuration is to include one perspective camera for the display of 3D objects, and one orthogonal camera for the display of 2D overlays.

light = create_light(type)

Create a light source of the given type. To be illuminated, all entities must be descendants of a light entity. Conversely, entities which should appear unshaded and fully-bright should not descend from a light entity. Here are the defined light types:

In the implementation, the only difference between positional and directional light sources is the value of the w component of the homogeneous position of the light. A positional light has w=1 and a directional light has w=0, thus the homogeneous position vector is finite or infinite in length, respectively.

pivot = create_pivot()

Create a pivot entity. A pivot does nothing, but is useful as a grouping entity. As it has all the properties of an entity, including transformation and render flags, It provides a mechanism by which the transform hierarchy may be extended and controlled.

galaxy = create_galaxy(filename, brush)

Load a galaxy definition from the named file and return a galaxy entity. A galaxy entity is a static 3D collection of particles, organized into a BSP structure for efficient rendering.

A galaxy object must have a brush with the examples/data/star.fp and examples/data/star.vp fragment and vertex programs applied to it to render correctly.

The galaxy object should be considered a hack. It has only a very specific use. It will probably be removed in a future revision of the Electro API, or possibly abstracted into a general particle system.

entity = create_clone(entity)

Duplicate the given entity. Cloning allows entities to be instanced within a scene graph. Cloning an entity is more efficient than recreating an entity from scratch, so cloning is often useful in circumstances where large numbers of similar entities are frequently created and destroyed.

Note, however, that a clone is a shallow copy of the original. A clone has all of the attributes of a distinct entity, but it shares the attributes specific to its derived type. For example, if a scaling operation is applied to a cloned entity, only that entity will be scaled. However if the text value of a string entity is changed, all instances of the original string will change.

2.1.0. Entity

The following functions act on entities. The entity argument of each function may take any entity value, including object, sprite, camera, light, and pivot.

2.1.0.1. Hierarchy Management

parent_entity(entity, parent)

Include entity as a child of parent. This will cause entity to be drawn in the coordinate system of parent, using its graphics state. If entity is already the child of some parent entity prior to the call, it is cleanly removed from that parent's child list. An entity may be unparented by specifying a parent entity of nil.

delete_entity(entity)

Remove entity from the scene hierarchy and release all resources associated with it. Any children of entity will be recursively deleted as well. If entity is a clone, then the entity itself will be removed, but any resources shared by other instances of the same entity will remain.

parent = get_entity_parent(entity)

Return the parent of entity, or nil if it is unparented.

child = get_entity_child(entity, n)

Return the nth child of entity, or nil if it has no such child. Unparented entities may be referenced as children of nil. The first child is at index 0.

2.1.0.2. Absolute Transformation

set_entity_position(entity, x, y, z)

Specify the position of the entity. Position is given in the coordinate system of the entity's parent.

set_entity_rotation(entity, x, y, z)

Specify the rotation of the entity. The 3 values give the angles of rotation in degrees about the X, Y, and Z axes. Rotation is given in the coordinate system of the entity's parent.

set_entity_scale(entity, x, y, z)

Specify the scale of the entity. The 3 values give scale multipliers along the X, Y, and Z axes. Scale is given in the coordinate system of the entity's parent.

set_entity_bound(entity, minx, miny, minz, maxx, maxy, maxz)

Specify the axis-aligned bounding box of the entity. The 6 values give the minimum and maximum X, Y, and Z values in object coordinates.

To enable view culling based on the given bound, set entity_flag_bounded. Keep in mind that object, sprite, and string bounds are computed automatically, and manually-set boundaries are not likely to be used. The intended use of set_entity_bound is the specification of pivot bounds for hierarchical view culling.

set_entity_tracking(entity, sensor, mode)

Specify the tracking parameters of the entity. If entity_flag_track_pos or entity_flag_track_rot is set on the entitiy, it will track sensor number sensor using one of the following tracking modes:

2.1.0.3. Relative Transformation

Strictly speaking, to provide both absolute and relative transformation functions is to violate the principle of orthogonality. In this case, I just couldn't resist. Inspiration for this feature is drawn from Blitz3D, which showed that relative transform is very beginner-friendly, leading to a very intuitive turtle-style system of entity control.

move_entity(entity, x, y, z)

Move the entity relative to its current position. X is to the right, Y is up, and Z is to the rear, as defined by the entity's current orientation.

It might be more clear to the beginner if the Z axis to pointed forward rather than back. However, this would be in conflict with the normal right-handed coordinate system, and would only lead to inconsistency and confusion for the experienced 3D user.

turn_entity(entity, x, y, z)

Rotate the entity relative to its current orientation. Rotation about the X, Y, and Z axes corresponds to pitch, yaw, and roll, respectively.

2.1.0.4. Rendering properties

set_entity_alpha(entity, alpha)

Specify the transparency of the entity. A value of 1.0 is fully opaque and 0.0 is fully invisible. This transparency value propagates down the hierarchy. Transparency values specified by child nodes will accumulate. That is, if a parent and a child both specify alpha=0.5 then the child will appear with a transparency of 0.25.

2.1.0.5. Query

x, y, z = get_entity_position(entity)

Return the position (x, y, z) of the entity in local coordinates.

x, y, z = get_entity_x_vector(entity)

Return the vector pointing to the right from the entity in local coordinates.

x, y, z = get_entity_y_vector(entity)

Return the vector pointing up from the entity in local coordinates.

x, y, z = get_entity_z_vector(entity)

Return the vector pointing to the rear of entity in local coordinates.

x, y, z = get_entity_scale(entity)

Return the scaling of the entity along the local X, Y, and Z axes.

minx, miny, minz, maxx, maxy, maxz = get_entity_bound(entity)

Return the minimum and maximum X, Y, and Z values of the entity. This may be used to determine the extent of an object or sprite. This is useful when computing pivot bounds for hierarchical view culling, or when positioning entities on-screen.

a = get_entity_alpha(entity)

Return the transparency value of the entity.

2.1.0.6. Flags

set_entity_flags(entity, flags, value)

Set or clear flags on the given entity. Entity flags enable and disable extra features in the entity hierarchy. These flags act not only on the entity that carries them, but also on the children of that entity. The flag parameter gives the set of flags to be modified, and may be the sum of multiple flags. The value parameter is a boolean giving the desired state. A value of true will set the given flags, and false will clear them.

value = get_entity_flags(entity, flags)

Return true if flags are set on the entity.

2.1.0.7. Collision Detection and Response

Electro uses the Open Dynamics Engine for collision detection and rigid body dynamics. These are broad topics, and ODE is a complex library. To fully document Electro's dynamics support would be to fully reproduce the ODE documentation. So in the interest of brevity, only Electro's interface to ODE is presented here. Interested readers should consult the full ODE documentation. Electro exposes most, but not all, of ODE's functionality. The API is simplified and heavily reduced, but the concepts are the same. Any ODE types and parameters not defined here are not supported by Electro.

In short, a rigid body system is composed of “geoms”, “bodies” and “joints”. A geom is a simple solid: a sphere, box, cylinder, or plane. A body is a compound solid: a rigid arrangement of one or more geoms. A joint is a non-rigid connection between bodies. Geoms define the collision characteristics of an object. Bodies define the physics characteristics of an object. Joints define the behaviors of articulated objects.

Bodies and geoms are defined using the normal Electro hierarchy. An entity may be marked as a body, as a geom, or as both. All geom-entities within the hierarchy of a body-entity are geoms of that body. Child entity positions and rotations define the arrangements of geoms within bodies.

Types

set_entity_body_type(entity, type)

Mark an entity as body. If type is true then the position and rotation will be updated each frame according to the parameters of the physical system. If false, the entity will remain exclusively user-controlled. The default is false.

set_entity_geom_type(entity, type, ...)

Mark an entity as a geom and define its shape. This geom will become a part of the nearest body defined above it in the entity hierarchy. The child entity's transformation defines the geom's position within the body, and so affects the collision characteristics, mass, and moment of inertia of the body.

If no enclosing body exists, then the geom is rigidly attached to the environment. It will act in collision, but will not respond physically. This is the normal mechanism for creating solid scene geometry.

The following geom types and shape parameters are allowed:

set_entity_joint_type(entity1, entity2, type)

Define a joint between the two given body-entities. If a given entity is not a body-entity or is nil then the joint will be rigidly attached to the environment. The following joint types are defined. For detailed descriptions and images of these joints, see the ODE documentation.

Attributes

Bodies, geoms, and joints have a variety of attributes defining their behavior. The following enumerations define these attributes and the functions provided to manipulate them. The functions take arguments of varying number and type, and all numbers and types are checked for correctness against the named attribute.

set_entity_body_attr(entity, attr, value)

Set one of the following attributes on the given body-entity.

set_entity_geom_attr(entity, attr, value)

Set one of the following attributes on the given geom-entity.

set_entity_joint_attr(entity1, entity2, attr, value)

Set one of the following attributes on the joint between the given bodies. Most of these attributes are scalar values, but a few are vectors.

Query

The following functions allow the attributes of the physical system to be queried. Most queried attributes are scalars, but a few are vectors. These query functions will return 1 or 3 values depending on the attribute.

value = get_entity_body_attr(entity, attr)

Return the value of the corresponding body attribute. All of the settable body attributes defined above may be queried. In addition, the following attribute may be queried but not directly set:

value = get_entity_geom_attr(entity, attr)

Return the value of the corresponding geom attribute. All of the geom attributes defined above may be queried.

value = get_entity_joint_attr(entity1, entity2, attr)

Return the value of the corresponding joint attribute. All of the joint attributes defined above may be queried. In addition, the following attributes may be queried but not directly set:

Direct Force Application

Joint motors are an incredibly powerful and flexible means of initiating action in a physical system. However, they do not always suffice. The following functions may be used to apply external forces on arbitrary body-entities. These forces accumulate, but are reset each frame. So, if a persistent force is to be applied to a body then it must be reapplied during each update.

add_entity_force(entity, x, y, z)

Apply force to entity with the magnitude and direction of vector (x, y, z).

add_entity_torque(entity, x, y, z)

Apply torque to entity with the magnitude and axis of vector (x, y, z).

2.1.1. Object

An object is a collection of vertices and meshes. A mesh is a collection of faces and edges. Faces and edges are defined by sets of indices into the object's vertex list. Brushes are applied per-mesh.

All vertices, meshes, faces, and edges are indexed beginning with zero, and all element indices in [0, n-1] are valid, where n is the respective element count.

The following functions allow objects to be created, destroyed, modified, and queried through the manipulation of object element indices.

2.1.1.1. Constructors

iM = create_mesh(object, [brush])

Add a new mesh to object and return its index. A brush may be applied to the new mesh at creation time, but if none is provided then the default brush will be used.

iV = create_vert(object, [vx, vy, vz, [nx, ny, nz, [tu, tv]]])

Add a new vertex to object and return its index. The vertex's position (vx, vy, vz), normal (nx, ny, nz), and texture coordinate (tu, tv) may be specified. If any of these are not provided, default values of (0, 0, 0), (0, 0, 1), and (0, 0) are used.

iF = create_face(object, iM, iV, jV, kV)

Add a new triangular face to mesh iM of object defined by the vertices (iV, jV, kV). Vertex order distinguishes the front of a face from the back. Vertices should be appear counter-clockwise when the face is viewed from the front.

iE = create_edge(object, iM, iV, jV)

Add a new edge to mesh iM of object defined by the vertices (iV, jV)

2.1.1.2. Destructors

Object elements are stored in vectors. The addition of a new element is usually a constant-time append operation, but the deletion of an element requires a linear-time traversal of the vector. In addition, a vertex removal necessitates the traversal of the all edge and face vectors, potentially causing removals there. Vertex removal also triggers the re-computation of the object bound. For these reasons, object element deletion can be expensive. Applications should avoid creating and deleting geometry on a per-frame basis.

delete_mesh(object, iM)

Remove mesh iM from object. All faces and edges contained by the mesh are deleted as well.

delete_vert(object, iV)

Remove vertex iV from object. The vertex is deleted from the object's vertex vector and all following vertices are shifted to fill in the remaining space. Vertex references in the object's face and edge vectors are updated accordingly, but vertex indices held in Lua variables may be off by one. To ensure the internal correctness of the object and all its indices, any faces and edges referencing the missing vertex are also deleted.

delete_face(object, iM, iF)

Remove face iF from mesh iM of object. This function does not remove the vertices referenced by the face.

delete_edge(object, iM, iE)

Remove edge iE from mesh iM of object. As with faces, this function does not alter the object's vertex vector.

The deletion of faces and edges can result in unused vertices within an object. This is sometimes desired, but often not. It is left to the application to do the right thing.

2.1.1.3. Modifiers

set_mesh(object, iM, brush)

Set the brush used to render mesh iM of object.

set_vert(object, iV, vx, vy, vz, [nx, ny, nz, [tu, tv]]])

Set the vector values of vertex iV. The normal and texture coordinate are optional and, if not specified, the existing values are retained.

Like vertex deletion, vertex modification can trigger object bound re-computation. This computation is done only when the bound becomes necessary, so modifying a large batch of vertices at once has the same overhead as modifying a single vertex.

set_face(object, iM, iF, iV, jV, kV)

Specify the set of vertices defining face iF. Again, vertices should be listed in counter-clockwise order.

set_edge(object, iM, iE, iV, jV)

Specify the set of vertices defining edge iE.

2.1.1.4. Value Queries

brush = get_mesh(object, iM)

Return the brush used by a mesh.

vx, vy, vz, nx, ny, nz, tu, tv = get_vert(object, iV)

Return the position, normal, and texture coordinate of a vertex.

iV, jV, kV = get_face(object, iM, iF)

Return the set of vertex indices that define a face.

iV, jV = get_edge(object, iM, iF)

Return the set of vertex indices that define an edge.

2.1.1.5. Number queries

n = get_mesh_count(object)
n = get_vert_count(object)
n = get_face_count(object, iM)
n = get_edge_count(object, iM)

Return the number of meshes or vertices in an object, or the number of faces or edges in a mesh. All element indices in [0, n-1] are valid.

2.1.2. Sprite

set_sprite_brush(sprite, brush)

Set the brush used to display sprite.

set_sprite_range(sprite, s0, s1, t0, t1)

Set the texture coordinate rectangle used by sprite. By default, a sprite uses its brush's entire image. This corresponds to texture coordinate rectangle (0.0, 1.0, 0.0, 1.0). However it is frequently useful to display only a subset of a texture on a sprite. For example, a numerical display might use a single texture that contains images of all of the digits 0-9, and select the digit currently displayed simply by modifying the sprite bounds.

2.1.3. String

set_string_text(string, text)

Set the text displayed by the given string object.

Modifying a string value is very cheap. When an application requires that an on-screen text element change frequently, changes should be made using set_string_text rather than the deletion and creation of string objects.

set_string_fill(string, brush)

Set the fill brush of the string entity.

set_string_line(string, brush)

Set the outline brush of the string entity.

2.1.4. Camera

x, y, z = get_camera_vector(camera, displayx, displayy)

Return the normalized world-space vector corresponding to the display position (displayx, displayy) as viewed from camera. In effect, this provides a mapping between the 2D display definition as specified using set_tile_viewport and its 3D definition specified by set_tile_position. It is especially useful when doing 3D picking using the 2D pointer position determined using do_point. The returned vector (x, y, z) takes into account the current camera offset, determined by set_camera_offset or by head tracking, but not any eye offset specified using set_camera_stereo. If the given point does not fall within any defined tile, then the last known correct world-space vector is returned. The default is (0.0, 0.0, 0.0).

set_camera_stereo(camera, mode, Lx, Ly, Lz, Rx, Ry, Rz)

Set stereo options on the given camera. The mode argument gives the stereo method, as described below. The (Lx, Ly, Lz) and (Rx, Ry, Rz) arguments give the offsets from the camera to the user's left and right eyes.

The available stereo modes are as follows:

set_camera_offset(camera, x, y, z)

Set the offset of a camera's view. The camera offset is a vector (x, y, z) giving the real position of the viewpoint relative to the position of the camera entity. This function moves the only apex of the view frustum, not its view rectangle. Thus it modifies the shape of the projection rather than its position.

Camera offset allows a distinction to be drawn between the navigational and projection-defining aspects of camera positioning. In a VR environment for example, the camera entity position represents the position of the display within the virtual space, while the camera offset represents the position of the user's head within the display. In the presence of trackd, Electro automatically updates the offset of all cameras based on the position of the head sensor.

set_camera_range(camera, near, far)

Set the near and far clipping planes for a camera. The default range is (0.1, 1000.0) for perspective cameras and (-1000.0,+1000.0) for orthogonal cameras.

set_camera_image(camera, image, left, right, bottom, top)

Set an image to be used as off-screen render target for camera. The left, right, bottom, and top arguments define the projection to be applied. The near and far planes of this projection are specified using set_camera_range and the projection is perspective or orthogonal depending on the camera type.

2.1.5. Light

set_light_color(light, r, g, b)

Set the diffuse color of a light source.

2.1.6. Pivot

Pivots have no type-specific functionality, but they share all of the attributes common to entities.

2.1.7. Galaxy

set_galaxy_magnitude(galaxy)

Set the magnitude multiplier of a galaxy. By default, stars are rendered using their computed luminosity. For all but a few stars, this is far too dim to be visible. The magnitude multiplier can be treated as a linear scale of apparent star size. For example, a magnitude value of 100 will result in the very largest of stars being rendered approximately 100 pixels across.

iS = get_star_index(galaxy, entity)

Return the star “being pointed at” by the given entity. That is, return the index of the star closest to the forward vector of the entity. This is useful for star selection and galaxy navigation, and is particularly well applied to cameras and tracked entities.

x, y, z = get_star_position(galaxy, iS)

Return the 3D position of the star at index iS of galaxy.

2.2. Images

Images are used as textures for objects, strings, and sprites. A default image of 128×128 white pixels is always available. It may be referenced using the symbol nil in any context where an image is required.

Electro supports several different image types. Images of all types are created using the create_image function. Image type is distinguished by argument type.

image = create_image(filename)

Load a static image from the named PNG or JPEG file.

A given image file will only ever be loaded once. Calling create_image a second time with the same filename will result in the same image object.

Some hardware requires that image width and height be powers of two. If support for non-power-of-two images is available, Electro will use it. If a non-power-of-two image is used on hardware that does not support it, then the image will appear white.

image = create_image(filename-x, filename+x, filename-y, filename+y, filename-z, filename+z)

Load a set of cube map images. All 6 cube map images must be the same size. If the image is to applied as a cubic environment map, applications must set brush_flag_env_map on any brush referencing it. Pixel queries act only on the first image of a cube map.

image = create_image(pattern, width, height, bytes, frame0, framen, pulldownn, pulldownd)

Create flip-book animated image. For optimal playback performance, flip-book images are assumed to be in raw data format. Pixel sizes of 4, 3, 2, and 1 correspond to RGBA, RGB, luminance-alpha, and monochome formats, respectively. Raw DXT1-compressed RGB images are supported, and are recognized by a .dxt extension.

The pattern argument gives the image file name pattern in C printf format. The width, height, and bytes arguments give the image and pixel size. The frame0 and framen arguments give the first frame number and total frame count to be applied to the file name pattern. The pulldownn and pulldownd give the numerator and denominator of the pulldown ratio, which may be used to adapt the playback rate of an animation to the sync rate of the display.

image = create_image(port)

Create a video stream image using UDP port port. The UDP image protocol is documented here.

image = create_image(width, height, bytes)

Create a blank image using of the given size and depth. A blank image may be used as a render target attached to a camera using set_camera_image.

delete_image(image)

Delete an image and release all resources associated with it. The image may not be deleted immediately if it is referenced by an existing brush.

In general, applications should explicitly delete any images that they explicitly create. This includes the circumstance where an image is created and assigned to a brush which is subsequently deleted. In that case, the image is not automatically released because it is presumed to be directly referenced by the application. The same create/delete policy applies to brushes.

r, g, b, a = get_image_pixel(image, x, y)

Return the color value of the (x, y) pixel of an image. This function always returns 4 color components. If the source image does not include all four channels, then values are extrapolated: grey-scale values are copied to all three color channels and opaque textures are assigned an alpha value of 1.

w, h = get_image_size(image)

Return the size of the image in pixels.

2.3. Brushes

Brushes describe the appearance of the surfaces of objects, strings, and sprites.

A default brush is always available. It has 80% grey opaque diffuse material, 0% specular, and 20% grey ambient material with a specular exponent of 0. These coincide with the OpenGL defaults. It references the default image. The default brush may be referenced using the symbol nil in any context where a brush is required.

brush = create_brush()

Create a new brush object with the default material properties.

delete_brush(brush)

Delete a brush and release all resources associated with it. The brush may not be deleted immediately if it is referenced by an existing entity.

set_brush_color(brush, dr, dg, db, da, [sr, sg, sb, sa, [ar, ag, ab, aa, [e]]])

Set the material color properties of brush. The (dr, dg, db, da) arguments give the diffuse color, (sr, sg, sb, sa) gives the specular color, (ar, ag, ab, aa) gives the ambient color, and e gives the specular exponent. Specular, ambient, and specular exponent properties are optional and any not specified retain their existing values.

set_brush_image(brush, image, [n])

Select image for use as the texture map of brush. The n argmument gives an optional texture unit number between 0 and 3, defaulting to zero.

set_brush_flags(brush, flags, value)

Set or clear flags on the given brush. Brush flags enable and disable the application of various material properties. The flag argument gives the set of flags to be modified, and may be the sum of multiple flags. The value argument is a boolean giving the desired state. A value of true will set the given flags, and false will clear them.

set_brush_frag_shader(brush, file)

Specify a fragment shader to be applied to brush. The file argument gives a text file in GLslang syntax. To remove a fragment shader, specify nil for the file argument.

set_brush_vert_shader(brush, file)

Specify a vertex shader to be applied to brush. The file argument gives a text file in GLslang syntax. To remove a vertex shader, specify nil for the file argument.

set_brush_uniform_sampler(brush, name, T)

Specify a uniform sampler to be applied to brush. The name argument gives the name of the uniform, which must correspond to a uniform sampler declared in the brush's fragment shader or vertex shader. The T argument gives the sampler's texture unit number.

set_brush_uniform_vector(brush, name, x, [y, [z, [w]]])

Specify a uniform vector to be applied to brush. The name argument gives the name of the uniform, which must correspond to a uniform vector declared in the brush's fragment shader or vertex shader. The (x, y, z, w) arguments give optional values.

set_brush_uniform_matrix(brush, name,[...])

Specify a uniform matrix to be applied to brush. The name argument gives the name of the uniform, which must correspond to a uniform variable declared in the brush's fragment shader or vertex shader. Supported matrix sizes include 2×2, 3×3, and 4×4. The variable argument list gives the uniform values.

set_brush_frag_prog(brush, file)

Specify a fragment program to be applied to brush. The file argument gives a text file in ARB fragment program syntax. To remove a fragment program, specify nil for the file argument.

set_brush_vert_prog(brush, file)

Specify a vertex program to be applied to brush. The file argument gives a text file in ARB vertex program syntax. to remove a vertex program, specify nil for the file argument.

set_brush_frag_param(brush, param, x, [y, [z, [w]]])

Specify a local parameter to be accessible to brush's fragment program. The param argument gives an index between 0 and 96. The (x, y, z, w) arguments give optional values.

set_brush_vert_param(brush, param, x, [y, [z, [w]]])

Specify a local parameter to be accessible to brush's vertex program. The param argument gives an index between 0 and 96. The (x, y, z, w) arguments give optional values.

set_brush_line_width(brush, width)

Specify the width in pixels of all lines drawn using this brush. The default is 1.0. This will apply both to lines defined by OBJ models and to entities drawn in wire-frame mode.

2.4. Sound

Electro provides a basic audio API suitable for adding simple sounds to an application. It supports playback of 44.1 KHz stereo or mono Ogg Vorbis audio files. Any number of voices may play simultaneously.

2.4.1 Sound I/O

sound = load_sound(filename)

Open a reference to the named Ogg Vorbis file. This is the normal means by which audio is imported into an Electro application. This function initializes a sound for playback, but does not start playback.

free_sound(sound)

Close a sound and release all resources associated with it.

play_sound(sound)

Start playback of a sound. If sound is already playing when this function is called, then the original playback will stop and the sound will be rewound to the beginning before being restarted. If it is necessary to mix multiple instances of one specific sound, then multiple sound objects must be used.

loop_sound(sound)

Begin playing a sound, and automatically rewind it when the end is reached.

stop_sound(sound)

Stop a playing sound. This function merely stops playback, and does not release any resources. The sound may be restarted from the beginning.

2.4.1 Sound Control

set_sound_amplitude(sound, amplitude)

Set the playback amplitude (volume) of a sound. The amplitude argument should be a value between 0 (silent) and 1 (normal volume), though it may be higher if gain is desired. Negative values will be clamped at zero.

set_sound_frequency(sound, frequency)

Set the playback frequency (pitch) of a sound. The frequency argument is a playback rate multiplier, so a value of 2 will cause the sound to play twice as fast normal. For reference, music playing with a frequency of 2 will sound one octive higher than normal, and will play at double tempo. Negative values will be clamped at zero.

2.4.1 Positional Sound

set_sound_receiver(entity, distance)

Specify entity as the receiver. Any sound associated with an entity will be mixed according to that entity's position with respect to this receiver entity. The distance argument gives the maximum hearing range of the receiver. Sound is attenuated as the distance to an emitting entity appreaches this value, and any emitting entity outside of this range will be unheard. Commonly a perpective camera is used as a receiver. If the receiver is nil then all sounds will play at normal volume regardless of associated entity positions.

set_sound_emitter(sound, entity)

Associate sound with entity. The sound will be mixed in stereo according to the position and distance of the associated entity from the receiver entity. Sounds associated with entity nil will play at normal volume regardless of receiver position.

2.5. Console

Electro uses a text console overlay to display error messages and enable run-time interaction with Lua state. This console can be enabled and disabled by pressing F1. It also appears automatically whenever anything is printed to it. Applications are free to use this console for their own purposes as well. For example, debugging information may be sent there.

print_console(...)

Print all string and number arguments to the console. To print formatted output, pass arguments via Lua's string.format function.

clear_console()

Clear the console to blank.

close_console()

Remove the console from the screen.

color_console(r, g, b)

Set the console text color to (r, g, b).

2.6. Configuration

2.6.1. Hosts

host = add_host(name, x, y, w, h)

Add a host, returning a host index. This function is used by configuration scripts to define the structure of the cluster driving a tiled display.

The name argument gives the Internet host name of the rendering node to be used. The x, y, w, h arguments give the position and size of the area of that host's desktop to be used for OpenGL rendering. A given render node can only support one Electro host. If the same name argument is reused for multiple configurations, only the first will be used. If name is “default” then the given host configuration will be applied to all hosts not specifically named in another configuration. If a render node is to handle multiple displays, they must be defined as Electro tiles.

Host configuration and rendering context creation is closely bound with MPI process initialization, so it must be performed immediately upon application startup, and cannot be modified later in an application's execution.

set_host_flags(host, flags, value)

Set or clear host flags on host. The flags argument gives the set of flags to be modified and value gives the desired state. Like host window configurations, host flags must be specified at application startup and later modification will have no effect. The default value for all flags is false.

Full-screen and frame handling are notoriously unpredictable. Both are subject to the whim of the operating system, the window manager, and the display. Getting this right may take some experimentation. Also keep in mind that the host's window position (x, y) is measured from the upper left, while the tile's window position is measured from the lower left.

2.6.2. Tiles

tile = add_tile(host, x, y, w, h)

Add a tile to a host, returning a tile index. The x, y, w, h arguments give position and size of the area of the indexed host's render area to be used for display.

There is a static limit of 8 tiles per host.

set_tile_position(tile, ox, oy, oz, rx, ry, rz, ux, uy, uz)

Set the world-space position of tile. The (ox, oy, oz) arguments give the position vector of the origin of the tile, usually the real-space lower-left corner of the display to which the tile is rendered. The (rx, ry, rz) arguments give the “right” vector, from the origin to the lower-right corner. The (ux, uy, uz) arguments give the “up” vector, from the origin to the upper-left corner.

These three vectors, with the view position vector, define the view frustum of the indexed tile's perspective projection. The default view position vector is (0, 0, 0), though this is subject to the motion-tracked offset of any perspective camera used during rendering.

set_tile_viewport(tile, x, y, w, h)

Set the pixel area of tile. This defines the range of any orthogonal camera used during rendering.

set_tile_mirror(tile, x, y, z, d)

Specify a world-space plane of reflection to be applied to the view position vector when rendering to tile. The (x, y, z) arguments give the plane normal, and d gives the plane offset from the origin along the normal. This allows a correct view frustum to be computed on PARIS and IDesk4 systems, where the view position vector is dynamically tracked, but the reflection of a tile is viewed in a mirror. Reflection must be enabled by setting the tile_flag_mirror option.

set_tile_flags(tile, flags, value)

Set or clear tile flags on tile. The flags argument gives the set of flags to be modified and value gives the desired state.

set_tile_linescreen(tile, pitch, angle, thickness, shift, cycle)

Set the Varrier line screen parameters for tile. These are only significant for stereo_mode_varrier camera modes. They give the line screen pitch, angle of rotation about the tile's Z axis, optical thickness (shift along the tile's Z axis), shift along the tile's X axis, and duty cycle (percentage of black).

2.6.3. Query

x, y, w, h = get_display_union()

Return the total pixel size of the display. The return values x, y, w, h give the position, width, and height of the rectangle of available pixels. This is the union of the viewports of all tile definitions. This information is often useful when positioning entities in view of an orthogonal camera, or configuring a scene to match the aspect ratio of a display.

minx, miny, minz, maxx, maxy, maxz = get_display_bound()

Return the axis-aligned bounding box of the entire display, in world coordinates. This information is useful when positioning entities in view of a perspective camera.

2.6.4. Tracking

set_tracker_transform(index, m0, m1, ... m15)

Set a transform to be applied to tracker sensor index. The 4×4 transform matrix is given in column-major order:

m0m4m8m12
m1m5m9m13
m2m6m10m14
m3m7m11m15

2.7. System API

Functions

exit()

Exit Electro cleanly. This function should be used instead of Lua's os.exit function in order to ensure that processes running on client hosts of a cluster display are properly exited.

chdir(path)

Change the current working directory to path. The previous working directory will be lost.

pushdir(path)

Push the current working directory to the directory stack and change to path.

popdir()

Pop the previous current working directory from the directory stack and change there.

Variables

arguments

This symbol is bound to a table of command line arguments. Any arguments not recognized by Electro are passed along to the application in this fashion.

2.8. Miscellaneous API

set_background(Tr, Tg, Tb, [Br, Bg, Bb])

Set the background color of the display. If only one color (Tr, Tg, Tb) is specified, the background will appear a solid color. If an optional second color (Br, Bg, Bb) is supplied, the background will be drawn with a top-to-bottom gradient. The default background has the gradient (0.0, 0.0, 0.0) to (0.1, 0.2, 0.4).

set_typeface(filename, [epsilon, outline])

Set the typeface to be used for all subsequently created string entities. The filename gives a TrueType font file.

The optional epsilon parameter gives an error bound in world units to which glyphs are tessellated. This allows the application to make a quality-speed trade-off, favoring finely tessellated text for magnified string entities, and coarse text for minimized string entities. The default epsilon value is 0.001.

The optional outline parameter gives the width of the glyph outline. The default value is 0.0 and glyphs are rendered without outline.

Applications that use multiple typefaces need not be concerned about the cost of switching typefaces. The overhead of loading a new typeface is incurred only the first time. Applications are free to switch from one typeface to another and back again without penalty. Note, however that two typefaces with different epsilon or outline values are distinct even when loaded from the same TrueType font file.

enable_timer(enabled)

Enable or disable the do_timer callback. If enabled has value true then callback will occur.

state = get_modifier(modifier)

Return the current state of a keyboard modifier. The return value is true if the modifier key is down. The queriable modifiers are:

x, y = get_joystick(number, [axisx, axisy])

Return the current value of a pair of axes of joystick number. The axisx and axisy arguments optionally give desired axis numbers. The defaults are axes 0 and 1. Requests for the values of non-existant axes will return 0.

There is no direct mechanism provided to determine the number of joysticks connected to the system. The capability exists in SDL, but it isn't really necessary in Electro. If an application needs to count joysticks, it should observe the device numbers received by the do_joystick callback. For example, allow the user to select the number of active joysticks by pressing Start on each.

3. Callbacks

Callback functions are the mechanism by which Electro Lua applications respond to user interface events. An application may define these functions as needed. If a function exists then it is invoked when the corresponding event occurs. Callback functions should return a boolean value to indicate whether the event resulted in a dirty screen. Returning true schedules a screen update to occur at the next opportunity.

do_point(dx, dy)

This callback is invoked whenever the mouse pointer moves within the Electro main window. Motion is reported relatively, so the pointer may move an unlimited distance in any direction. Absolute position is not reported. If absolute mouse position is necessary, then it must be tracked manually, as follows:

mouse_x = 0
mouse_y = 0

function do_point(dx, dy)
    mouse_x = mouse_x + dx
    mouse_y = mouse_y + dy
    return true
end
do_click(b, s)

This callback is invoked whenever a mouse button is pressed or released. The b argument gives the button number, 1, 2, 3, etc. The s argument is a boolean giving button state. A value of true indicates the button has been pressed, and false indicates it has been released. Note, on most modern systems mouse wheel up and down are reported as presses of buttons 4 and 5. Button press events on wheel buttons do not have accompanying release events.

do_timer(dt)

The do_timer callback is invoked regularly while idling is enabled. The dt argument gives the amount of time, in seconds, that has passed since the last time the callback was invoked. The rate of callback is not defined, but it will happen as often as is possible. The do_timer function is intended to be used to update animations and other background processes. Applications should be sure to return true in order to force an update to the display.

If an absolute measure of time is necessary, applications should accumulate the dt parameter, just as they would accumulate relative mouse motion.

time = 0

E.enable_idle(true)

function do_timer(dt)
    time = time + dt
    return true
end

The update rate of the do_timer callback is deliberately obscured because it is nearly impossible to guarantee. While it might make sense to allow an application to request a callback rate, this request can only be precisely filled in a limited number of cases. Just trust the dt.

do_frame()

This callback is invoked immediately before a frame is rendered. This is useful for operations that should be performed exactly once per frame.

In general, events are queued and several events are handled during each frame period. This is necessary because rendering is usually more expensive than event handling, and re-rendering after each event leads to sluggish performance. Often, the most efficient event-handling organization is to accumulate events as they arrive (as in the do_timer and do_point examples) and use the resulting totals to update the scene at the last moment.

Unlike other callbacks, the “dirty” flag return value from do_frame is ignored, as a redraw has already been scheduled.

do_keyboard(k, s)

This callback is invoked whenever a key is pressed or released. The s argument is a boolean giving the key state. The k argument gives a key identifier. For most keys, this gives the ASCII value of the unshifted character. For non-ASCII keys and modifiers it is a unique identifier outside of the range of ASCII. Here is a full listing of keys and their associated value symbols. When in doubt, the value of a key may be determined interactively as follows:

function do_keyboard(k, s)
    if s then
        print(k, "down")
    else
        print(k, "up")
    end
    return false
end
do_joystick(n, b, s)

This callback is invoked whenever a joystick button is pressed or released. The n argument gives the joystick device number. The b argument gives the button number. The s argument is a boolean giving button state. Applications respond to button presses in an event-driven fashion, but they should read joystick axis input more continuously. Usually joystick axes are acquired by calling get_joystick in a do_timer callback.

do_contact(entityA, entityB, px, py, pz, nx, ny, nz, d)

This callback is invoked when a rigid body collision has been detected between two bodies with a geom_attr_callback category bit set. The entityA and entityB arguments give the entities (in no specific order), (px, py, pz) gives the point of collision, (nx, ny, nz) gives the normal vector of the collision, and d gives the depth of penetration of the two geoms (assuming their CFM values permit penetration.)

Keep in mind that a collision can produce multiple contacts, so more than one may be reported. For example, a box sitting flat on a floor makes contact across an area larger than a point. This may be reported as multiple point contacts. Similarly, a soft or non-responding collision will be reported multiple times. Soft impacting bodies remain in contact for several frames, and contact will be reported each frame. Non-responding colliding bodies are allowed to pass through each other, and contact will be reported every frame during which they intersect.

rlk (at) evl.uic.edu