IL, an intensional language

Introduction

Intensionality

The term `Intensionality' is taken from formal logic. It comes from `in tense', meaning: relating to time. The intensional meaning of a term is not some static object, but rather the object as it varies in time. This notion is extended to mean `varying in some context' instead of just time. In IL these contexts are: 1, 2 and 3 dimensional space:

These spaces, or domains, (see section Map domains) can exist in two forms, discrete or continuous. A discrete domain is conceptually linked with a sampled data set for instance a sound file or a digitized image. On a continuous domain any (mathematical) function can be defined. Switching from one kind of domain to the other is done by interpolation or sampling. IL is an intensional language in the sense that any data-type can transparently take the form of a function on one or more of the above mentioned domains. In any expression involving terms of a certain type, the value of that term can be dependent on these domains. So by just defining a pixel-type and operators for it, automatically these operators work on complete computer animations, being functions on the 1-D and 2-D domains. By this mechanism unification is established of (at least) texture mapping, image processing, procedural animation and sound synthesis.

The language

IL is a pragmatic, interpreted functional language. Pragmatic in the sense that it takes the consequences of implementation issues to the limit. Thus, being interpreted and functional means that functions and strings are the same thing. In other words, quoting and function calling are complementary operations.

Basic control structures are (recursive) function calls and conditional expressions.

The language implementation software is written in C and consists of a kernel and any number of user modules. These modules implement the data-types, with functions and operators working on these. The kernel and a number of modules can be supplied as a library, because adding new types can be done without recompiling the kernel, just by linking the desired modules with the kernel. Adding new types is done in a true object-oriented fashion, with possible overloading of function names and operator symbols. The basic interpreter cycle can be mixed in a generic way with any interface mechanism, such as motif's dispatch event loop, to establish graphical user interfaces and multi-media operability.

The language basics

The interpreter, expressions

The simplest way to begin with the language is just calling the executable from the operating system. IL will respond with a prompt looking like:

}

The interpreter will from now on do nothing else but reading a line from the terminal, interpreting it as an expression, evaluating it and printing the result (if not coming across an error). After this it will repeat the cycle by prompting again. Only a few types will necessarily always be built in. In practice there will probably be a lot of them. Anyway, you can be sure there will be integers. So, the following dialog is a possible one:

} 3+7
          10
}

More then one expression can be put on one line, delimiting them by semicolons. They will all be evaluated in order, but only the last result will be printed.

} 3+7; 5*6+7*2
          44
}

It is important to note that expressions are really the only element of IL. So anything that happens will always leave a result. Two other types that will always be there, are floating-point reals and booleans. All the functions and operators you expect for them are (hopefully) implemented, especially the normal (C) form of conditional expression is provided.

} degree(T); sin( 30 ) < 25e-2 ? 7+8 : 3^5
 243.0
}

degree is a build in function to set the mode of operation for trigonometric functions. There are several of these mode-setting functions, and they all are called in the same way. They can be called without a parameter to just deliver the current setting of the mode, or they can be called with a new setting (and delivering it as a result value). Another one of these is eolmode. In the default case this mode is on. The end of line is then interpreted as end of expression, i.e. a semicolon is implied. With this mode off, semicolons at the end of any expression are obligatory and expressions can be extended across the end of line. T and F are built-in identifiers for the two boolean values. When degree mode is switched off (by calling degree(F), the default) all trigonometric functions work in radians.

In the above example sin is called with an integer value, yet it is only defined for real-valued parameters (and complex ones). This is an example of coercion and it plays an important role in the language. More about that later.

It is also clear from the example that the two alternative parts of a conditional expression need not be of the same type (but be careful when assigning the result of an expression to an identifier, as explained in the next section). The condition can, apart from boolean, also be of the integer or real type, with the usual interpretation of 0 being false and anything else being true. The empty list also functions as false in this context. The alternative part of the expression may be omitted, implementing something like a conditional statement without `else' part. This construct still is an expression though, returning an empty list in case the condition evaluates to false.

One important built in function is exit. When called it does what you expect it to do. But make sure you call it, rather then ask what it is:

} exit
implicit function
} exit()
%

Just exit means you evaluate an expression of the type implicit function, of which the value can not be printed, so you only get an indication of its type. Calling this function gives the desired effect.

Assignments

In the usual way it is possible to give names to results of expressions for later use. These names, or identifiers, are built from letters and digits, the first character being a letter (note that other characters, fi. `_', are not allowed in identifiers, so they are available in operator symbols). The difference between upper and lower case letters is significant and only the first 128 characters are taken into account. Identifiers are not declared. They come into existence when first assigned to and cannot change type afterwards.

Note that assignments are also expressions with as their result the value of their right hand side. This can be significant when coercion is needed to assign to an existing identifier.

} i = 7
           7
} i = exp( 1 )
 2.7183
} i
           2
}

Here i is initialized with an integer value, so it will be an integer identifier through its existence. When a real value is assigned to it, this value will be coerced to an integer value by truncating it. The result of the assignment expression will however be the unaltered real value.

Above we noted that implicit functions are expressions by themselves, with their own types. This has as a consequence that they too can be assigned.

} quit = exit
implicit function
} quit()
%

Functions and operators

Strings as functions

Apart from the built in implicit functions, the IL programmer has the possibility to write her own. The type for this feature is the string type. Just like booleans and integers, the string type is always implemented. Putting a row of characters between double quotes notates its values.

} "this is a string"
this is a string
}

Quotes within strings are written by doubling them:

} "this is a string quote: """
this is a string quote: "
}

Strings can be interpreted as functions by calling them as usual. That is, by following a string valued expression by parentheses. This means that the control for the interpreter is switched from the terminal input to the function string. This mechanism is of course the same as in most job-control (shell) languages. Here it is consequently exploited in the expression evaluating interpreter paradigm, resulting in a functional kind of language.

} f = "/exp(1)"
/exp(1)
} f()
 0.36788
}

(Note that the slash symbol / functions as a unary operator meaning 1 divided by ..., in accordance with the usual interpretation of the unary minus operator)

Function arguments

These functions can be called with parameters. The parameter evaluation mechanism is always `call by value'. The value of a parameter is obtainable inside the function with one of the operators % or $. They work in exactly the same way. In the following % will be used but can be replaced with $. %n, n being an integer, will evaluate to the nth parameter.

} f = "%1 + %2"
%1 + %2
} f( 2, 6 )
           8
}

% is a genuine unary operator, expecting an integer valued expression, so it is not necessary to use it with a plain integer. Moreover, there is the implicit function pars, supplying the current number of parameters the function is called with.

} f = "%pars()"
%pars()
} f( 6, 7, 8 )
           8
} f(1,2,3,4,5,sinh(2))
 3.6269
}

There are some tricks to pass arguments from inside a function to a function that is called in a nested fashion. This is best explained with a symbolic example. Suppose f is called with arguments as:

f( a, b, c, d, e );

inside f, g is called passing the same arguments:

g( %1, %2, %3, %4, %5 );

instead of this it is possible to write:

g(];

or

g[];

The difference of these two possibilities comes to light when calling with new arguments:

g( x, y ];

is equivalent to

g( x, y, %1, %2, %3, %4, %5 );

--- parameters are added at the start of the argument list.
Whereas

g[ x, y ];

is equivalent to

g( x, y, %3, %4, %5 );

--- parameters at the start of the list are overwritten.

There is also a possibility to add parameters at the end of the argument-list:

g[ x, y );

is equivalent to

g( %1, %2, %3, %4, %5, x, y );

but this construct has the added functionality that it forces a tail-recursive call. (See section Recursion) It will not return! This is therefore not legal as term in an expression.

Identifier scope

There is the notion of local identifiers in the language. Identifiers can be introduced in a function by assigning to them. They will not conflict with global identifiers. Global identifiers can be referred to however, when they are not used as local identifiers. This goes for local identifiers on a higher level too, when function calls occur in a nested way. When identifiers are evaluated, they will be looked for in succeeding lower levels, until the global level. In the following example the implicit function print is used. It invokes the same method as the interpreter does when printing the value of an expression in an interactive session.

} degree(F); pi = 4*atan(1);
 3.1416
} f = "print(pi); a = 6; g(); a"
print(pi); a = 6; g(); a
} g = "pi = F; print( pi ); a = exp(1); print( a )"
pi = F; print( pi ); a = exp(1); print( a )
} f()
 3.1416
       F
 2.7183
           6
} pi
 3.1416
}

There is the possibility to assign explicitly to a global identifier from within any function by preceding the identifier with :: or `

} g = "::pi = 180"
::pi = 180
} g()
          180
} pi
 180.0

Another way to get this effect is by using the implicit function assign. This function takes two parameters, the first being a string. This string is interpreted as a global identifier and the value of the second parameter is assigned to it.

} assign( "blob", 666 );

define works the same as assign, but protects the identifier (as assignment with :=).

Operators

The use of assign and define is primarily intended for the creation of user defined operators, because the symbol string has not to fulfil the demands required by the identifier syntax. The following example demonstrates this feature.

} fac = "n = %1; n ? n*fac(n-1) : 1"
n = %1; n ? n*fac(n-1) : 1
} assign( "~", operator( fac ) )
prio 0
n = %1; n ? n*fac(n-1) : 1
} ~6
         720
}

The implicit function operator creates an item of the type Operator. The parameter is the user-defined function associated with the operator. There is an optional second parameter denoting the priority of the operator, useful in the case of binary operators. It is possible to overload implicit operator symbols (like +) in this way. In this case it is not possible to overwrite the built-in priority. The interpreter will use the user-defined operator if the implicit operator is not applicable in the type context.

In the following example a pseudo array mechanism is implemented. The printing of results is controlled by the echomode. # is string-concatenation. It is overloaded to create identifiers with an index. getid is the complementary function of assign. Also a simple for statement is implemented.

} echo(F)
} assign( "#", operator( "%1 # sprintf( ""%4.4d"", %2 )" ) )
} print( "abc"#5 )
abc0005
} arrayass = "assign( %1#%2, %3 )"
} arrayval = "getid( %1#%2 )"
} for = "%1 > %2 ? return() : 0;
}" %3(%1); for( %1+1, %2, %3 )"
} rootable = "rootable"
} rootass = "assign( rootable # %1, sqrt( %1 ) )"
} for( 1,10, rootass )
} print( arrayval(rootable,5) )
 2.2361
}

Note the change of the prompt to }" when entering multi-line strings. The sprintf function is a partial copy of the C function. An alternative to the getid use here, because it uses correct identifier syntax, would be:

} arrayval = "(%1#%2)()"

The use of the function return should be obvious. In this case it makes the function to return the integer value 0. An alternative for this can be given as a parameter to return.

Recursion

We already encountered some uses of recursion in the definition of fac and for. A better for would be:

} for = "%1 > %2 ? pars() >= 4 ? %4 : 0 : for( %1+1, %2, %3, %3(%1) )"

or

} cfor = %1 < %2 ? cfor( %1+1, %2, %3, %3(%1) ) : pars() > 3 ? %4 : 0"

returning the value of the last iteration.

This kind of recursion is so-called tail-recursion. The interpreter knows the recursive call is the last one, so does not stack any items unnecessarily. The number of iterations will not be limited by stack-overflow. One has to be careful however when one wants local identifiers to be known to higher levels. In the case of tail-recursion local identifiers will be removed. So sometimes tail-recursion has to be deliberately prevented.

} f = "n=%1; g()"
} g = "n*(n-1)"
} print( f( 6 ) )
error 127: identifier unknown
n
} f = "n=%1; m=g(); m"
} print( f( 6 ) )
          30
}

Identifier protection and checking

An identifier can be protected from assigning to it (making it effectively a constant) by calling protect.

} pi = arg( -1 ); e = exp( 1 );
} protect( "pi", "e" );
} pi = 666;
error 275: assignment to protected variable
pi=
}

The effect can be canceled with unprotect. Protection can also be obtained with the definition symbol := .

} e := exp(1);

is equivalent to

} e = exp(1); protect( "e" );

The existence of a global identifier can be tested with exists;

} echo( T ); exists( "dfi" )
     F
} dfi = 469; exists( "dfi" )
     T
}

Comments

Lines beginning with /* are considered comment (outside strings).

} /* schuitje varen, theetje drinken
}

Program files

Instead of typing expressions at the terminal it must of course be possible to write programs in text files. The interpreter can be made to read such a file by calling the function read with the filename as parameter.

} read( "flofbla" )

The file extension .il is added to the filename and the file is sought for, first in the current directory, then in the directory designated by environment or option. The read call will properly return and can be nested. Executing the function exitfile can terminate reading a file. When starting IL, a number of filenames can be given as parameters. These will be read before starting an interactive session.

Modules

There is a module concept in IL. Such a module is a piece of IL code between calls of startmodule and endmodule. Global identifiers in such a module will only be known inside that module, except when they are explicitly exported. Likewise external identifiers can be imported. Modules are internally identified by an integer. In the default case startmodule will take the next available integer to create a new module. It is however possible to give an explicit number as parameter. In this way it is possible to `reopen' an already ended module. Both functions return the number of the module.

} modnr = startmodule();
} init = "::sum=0";
} add = "::sum=sum+%1";
} export( "init", "add" );
} endmodule();
} init();
} add( 7 ); add( 8 );
} sum
error 127: identifier unknown
sum
} startmodule( modnr )
} getsum = "sum"
} export( "getsum" )
} endmodule()
} print( getsum() )
          15
}

Types

Type characteristics

There are some things all types have in common. They have a name, an integer identification, a size and a number of methods applicable to them. types prints a list of all implemented types. When creating a new type in a C-module, the name is given to the kernel. Let that name be "Mine". The kernel then creates two IL identifiers. Minetyp is an integer identifier with as value the internal identification. Mine is an implicit function, which converts any parameter to the type Mine if at all possible.

} Inttyp
           5
} Int( pi )
           3
}

The integer type identification is very useful in combination with typeof, a function that returns this integer for any parameter

} typeof( "abc" ) == Stringtyp
       T
}

The name of a type can be obtained with typename.

} typename( 6 )
Real
}

One of the methods that are defined when a new type is called into existence is the one that is invoked by a call of print. Another one is to copy a value of the type. This is important in the case of dynamic types. These are types that are internally referred to by a pointer, and that have a reference count. The only such type we have encountered thus far is the string-type. Simple assignments of these types copy a pointer (pointing to a value). A copy of the value itself is created with the operator &. For some dynamic types there are two different kinds of copy. The so called deep-copy, with operator &&, recursively copies sub elements of a value. We will see a use for this feature with lists. (See section List)

} l = [1,2,3);
list
} m = l
list
} m == l
       T
} m = &l
list
} m == l
       F
}

Conversions from one type to another are done by methods called coercions. A type together with its coercions can be regarded as a class. Which method is invoked for an implicit function or operator is determined by the type context. So all parameters have their influence on the choice of method instead of one parameter determining the class. If there is no applicable method for the current context, the kernel tries to create one by coercing the parameters. In this way inheritance is established. A simple example is adding two numbers. There is an adding method for two integers and one for two reals. When adding an integer and a real, the integer is converted to a real and the real adding method is applied. With each coercion a scheme-number is associated. Likewise each function or operator belongs to such a scheme. Only matching coercions are sought for when looking for inheritance. The default scheme is 0. In the list printed by types, for each type one sees a line with:

Bool

For all types the operators == and != are defined, returning a boolean value. The normal boolean operators are defined as:

&
logical and
|
logical or
|^
logical exclusive or
!
logical not (unary operator)

List

There is a generic list-type build in the language. The standard means to implement this type is the constant nil representing the empty list and the functions cons, head and tail with their usual interpretation. All types can be mixed as list elements.
Instead of

} l = cons( T, cons( 2, cons( 3.0, nil ) ) );

there is the following construct:

} l = [ T, 2, 3.0 );

' is an index operator

} l'1
           2
}

So l'n is equivalent to applying n times tail followed by head. The same, but without the final head, is accomplished with @.

} l@1
list
}

The value of a list is not printed, just the type indication. One can define a function lprint with the following program fragment:

startmodule();
prlelt = "print( lst'%1 )";
import( "cfor" );
lprint = "lst = %1; cfor( 0, length( lst ), prlelt ); lst";
export( "lprint" );
endmodule();

The implicit function length gives the number of elements of a list. With lput the head of a list can be replaced with any other item. In combination with @, any element can be replaced.

} lput( l@1, 5.0 );
} lprint( l );
       T
 5.0
 3.0
}

Lists are dynamic. After an assignment

} m = l;
} lput( m, F )

both l and m are the same changed list.

Here the difference of simple copy & and deep copy && is significant. A simple copy is a new list with references to the same elements as the old list (for dynamic elements, for instance lists). A deep copy gives you recursively deep-copied elements.

There is a concatenation operator #.

There are complementary operators for removing and inserting sublists:

} l > n

remove a sublist of length n from l, starting with l'1. The result of the expression is the removed sublist.

} l < lx

insert list lx in list l, lx'0 becoming l'1.

Mathematical types

These types are Int, Real, Complex and Quat. There are standard coercions in the direction:
Int --> Real --> Complex --> Quat
and under scheme 1 the other way.

Int and Real have their standard notations for constants build in the scanner of the kernel. For Complex and Quat there exist functions to define new values by `summing up' the real constituents.

Int

The usual notations for decimal, octal and hexadecimal integers are available.

} 5
           5
} 0777
         511
} 0x4d
          77

All the relational operators are defined:
> < >= <=
as well as the arithmetical
- (unary)
+ - * / %
and logical (bitwise) operators
! & | |^
The function abs is defined for integers.
The binary operators
>? <?
give the maximum and minimum of two integers.

Reals are coercible to Ints under scheme 1. So this coercion hardly ever occurs automatically, but it does when calling Int. The same goes for complex values and quaternions.

Real and Complex

The same relational and arithmetical operators are defined as for Int. (% implements the `drem' function). On top of these there are the power operator ^ and unary /. The following functions are defined for both real and complex values:
abs, sqr, sqrt, exp, log, sin, cos, tan, asin, acos, atan,
sinh, cosh, tanh, asinh, acosh, atanh, sec, cosec, asec, acosec,
sech, cosech, asech, acosech.
They will return complex values for real arguments whenever necessary.

Real

These are some more functions for reals:
sinc, vsin, vcos, hypot, atan2, vatan2, J.
sinc : isin(pi*x) / (pi*x)
The trigonometric functions starting with a v are normalized (with regard to their domain as well as to their range) to the interval [0,1], for ease of use with (2-D) texture maps. J is equivalent to the C bessel-function 'jn'. hypot works for two or three arguments.

Complex

Complex values can be defined from a pair of reals with cmplx or pcmplx, the latter for representation in polar form. The following functions return the obvious real constituents:
re, im, abs, arg.
There is the special complex function conj for the complex conjugate. A special use of complex values is their interpretation as range type for texture bump maps.

Quat

Quaternions are arithmetically fully implemented. They can be defined with the function quat with either 4 Reals as arguments, or 1 Real and 1 Real3. They are implemented with a 3-D imaginary part. There is a normalization function norm to bring them on the `unit sphere' for the standard interpretation as rotations. There are coercions from and to transformation matrices. The following operators and functions are defined:
- / (unary)
+ - * / ^
abs, re, im, conj, sqr, exp, log

String

Already a lot has been said about strings. There are some functions and operators worth mentioning:

#
concatenation.
'
index operator, returning a 1-character string
@
returning indexed string-tail
length
edit
invokes a standard text editor to change a string. default vi
editor
change the editor
funof
returns the functions string of a user defined operator
priof
returns the priority

The basic coercions Int and String are overloaded to handle ASCII-character codes.

Vector types.

As base classes for the geometrical types and the intensional domains the following types are defined: (with their `C like' equivalent)

Int2
== int[2]
Int3
== int[3]
Real2
== real[2]
Real3
== real[3]
Real4
== real[4]
Intl2
== int[2][2]
Intl3
== int[2][3]
Reall2
== real[2][2]
Reall3
== real[2][3]

They all have basic definition functions with the same names but starting with lower case letters:
int2, int3, real2, real3, real4, intl2, intl3, reall2, reall3.
They all have the index operator '

} x3 = real3( 1, 2, 3 )
 1.0
 2.0
 3.0
} x3'2
 3.0
}

The single indexed types all have:
unary -
binary + -
scalar * /
The rest only have unary -
There are the following scheme 0 coercions:
Int2 --> Real2
Int3 --> Real3
Real2 --> Complex
and all of these reversed in scheme 1.

Geometrical types

2-D

The type Real2 is taken to represent 2-D points. The geometrical type of 2-D lines is Line2. Their internal representation can be:

There are coercions to and from Reall2 and Real3. There is the type Transform2, which is a 3*3 matrix, representing 2-D transformations.
Functions:

transf
takes 9 Reals or 6 Reals (homogeneous) or 3 Real2s to make a Transform2
linepnt
takes a Line2 (line) and a Real to return a 2-D point on the line.
linepnt( l, 0.0 ) is equivalent to l'0
linepnt( l, 1.0 ) is equivalent to l'0 + l'1
hypot
hypot( r2 ) equiv hypot( r2'0, r2'1 )
norm
normalizes points and lines.
det
gives determinant of matrix
translate
takes 2 Reals or 1 Real2 to make a translation matrix
scale
takes 1 Real and either a point or a line to make a scaling matrix
rotate
takes a Real and a point to make a rotation matrix (takes degree mode into account)
skew
takes a point and a line to make a skewing matrix. This transformation leaves the point at its place, shifting the starting point of the line along its direction and having determinant 1.0
mirror
takes a point or a line to make a mirror transformation.

These are the operators:

*
dot-product of two points
^
angle of two lines
~
distance of two points or a point and a line
|+
construct a line from two points
||
construct a line through a point parallel to a line
|_
construct a line through a point and perpendicular to a line
|*
construct the intersection point of two lines
*
takes a Transform2 and a Int2, Real2 or Line2 and returns the transformed item, or multiplies two matrices.
/
(unary) inverts matrix.

3-D

Here a plane type Plane3 is introduced. It is based on Real4. Also the type Transform is introduced (plain Transform is 3-D). All the 2-D functions have their 3-D counterpart with the following notes.

rotate
needs a line parameter rather then a point.
mirror
there are 3 mirror possibilities (point, line, plane)
**
cross-product of two points
^
defined for two lines, a line and a plane or two planes
~
defined for two points, a point and a line, a point and a plane or two lines.
|+
construct a line through two points
a plane through a point and a line
||
a line through a point parallel to a line
a plane through a point parallel to a plane
|_
a line through a point perpendicular to a plane
a plane through a point perpendicular to a line
a line perpendicular to --, and crossing two lines
|*
a point as intersection of a line and a plane
a line as intersection of two planes
*
again for all matrix multiplications.
^
takes a Transform to the power of a real. Of special interest to perform `inbetweening' on the transformation level. It is a complex algorithm that does not try to take a transformation apart in possible constituents but instead performs pure matrix algebra.

As mentioned before, special care has been taken to be able to mix matrices and quaternions by defining the appropriate coercions.
scheme 0: Quat --> Transform
scheme 1: both ways
matrix multiplication resides under scheme 1, also because of some technicalities involved with the above mentioned power algorithm.

Points as colors

Besides as geometrical coordinates, Real3 can also be interpreted as representing colors. In this respect there are a couple of functions:

The first two are obvious, except that they incorporate a correction to avoid the usual first order discontinuities in hue transitions. The third one interprets its argument to lie in a normalized YUV space. Normalized in the sense that each value in the interval ( [0,1], [-1,1], [-1,1] ) represents a color.

Pixel

The pixel type is based on Real4, interpreted as consisting of red, green, blue and `alpha'. Coercions to -- and from Real3 (colors) and Real exist. The following operators implement the usual compositing algebra:

pix1 +~ pix2
pix1 -~ pix2
pixel addition and subtraction
pix1 <- pix2
pix2 over pix1, normal compositing
pix1 /~ a2
pix1 in a2, part of pix1 inside a2, a2 probably pix2'3 )
pix1 %~ a2
pix1 out a2, part of pix1 outside a2
d <~ pix
multiply only rgb, `darken'
d >~ pix
multiply alpha, `opaque'

Maps

Introduction to maps

Map is the type of intensionally defined entities. They are functions of one or more of the standard domains to any one of the static IL types. They are normally not evaluated in the interpreter context, but when invoked in some kind of rendering operation, like ray-tracing, image- processing or sound-synthesis. An exception to this is the function evalmap, which explicitly evaluates a map after setting the domain with setmapdom.

All functions and operators for a certain type are automatically applicable to maps of that type, delivering maps as result. So the syntax for creating maps is exactly the same as for normal expressions. Only when one of the arguments is a map, the result is a map, which is a compiled version of the expression instead of the evaluated result.

The type a map evaluates to is obtainable with typeofmap.

Map domains

Continuous domains

There are 3 continuous map domains, one for each of the relevant dimensionalities. They are available through implicit functions:

Some possible interpretations are:

m1()
global time
m2()
normalized object surface for texturing
m3()
3-D space for solid textures

The following is an example that will produce a chessboard pattern when used as a texture map:

d = m2(); chs = Int( 8*d'0 ) + Int( 8*d'1 ) & 1 ? 0.0 : 1.0;

Now, chs is directly usable as intensity parameter for an attribute in a solid model. In fact, using scalar multiplication of Real2 and because integers are converted to real when used as intensity, it is also possible to write:

d = 8*m2(); chs = Int( d'0 ) + Int( d'1 ) & 1;

Discrete domains

As 1-dimensional domains are normally used as time-representation, there are 3 discrete versions of them, reflecting the different uses of `sample-time'. These are:

m1da()
animation time, frame rate
m1dc()
audio control time, the rate of sound parameters
m1ds()
audio sample rate

And for the 2- and 3-dimensional cases:

m2d()
images
m3d()
volume data

Domain conversion

Most of the time, when mixing discrete and continuous domains, the system does what you expect it to do, namely, converting to and from the relevant domains. To this end there are some quantities, which exists globally as well as associated with individual maps. These are (and can be set or get with):

dbnd2
Int2: bounds of m2d in relation to m2
dbnd3
Int3: bounds of m3d in relation to m3
frqa
Real: framerate, m1da in relation to m1, default 25
frqc
Real: audio control rate, m1dc in relation to m1, default 100
frqs
Real: audio sample rate, m1ds in relation to m1, default 44100

So when, for instance, you multiply a (discrete) image with a (continuous) noise, the noise function is automatically sampled (evaluated) at points corresponding with the image pixels, converting the image space to the square unit interval.

Apart from this there is a complete generic way to switch domains with trfmap.

trfmap( m, d )

The result is the map m, but with d as new domain, where d can be a map itself. The following example is a function to convert a 2-D map to a 3-D solid map with a `ball-projection':

ballprj = "m = m3(); x = m'0; y = m'1; z = m'2;
           u = vatan2( y, x );
           r = hypot( y, x );
           v = 2 * vatan2( r, -z );
           trfmap( %1, real2( u, v ) )";

%1 is supposed to be a 2-D map, dependent on m2(). So, the second parameter of trfmap is of type Real2. This expression is the new domain. It is solely dependent on m3, so will be the result map.

When maps on discrete domains are evaluated on continuous domains (perhaps as a automatic step in a conversion to another discrete domain), the values are to be interpolated. The way this is done can be set with polmode.

0
constant interpolation (no interpolation)
1
linear interpolation

For the linear case the function linpol must be implemented for the relevant types as a function that interpolates between two values of that type according to a third real argument in [0,1]:

} linpol( 4.2, 5.7, 0.4 )
 4.8
}

It is implemented for:
Real, Real2, Real3, Pixel, Complex, Quat, Transform

Miscellaneous mapfunctions

Already mentioned was the function evalmap. It evaluates a map. There are global domain values to form a context for this evaluation. These can be set with setmapdom. Which domain is meant, is determined by the type of the argument, on the understanding that an integer is taken to relate to m1ds, setting the other 1-D discrete domains according to the relevant frequencies.

} setmapdom( real3( 5.0, 6.7, 88.9 ) );
 5.0
 6.7
 88.9
} evalmap( 6*m3() );
 30.0
 40.2
 533.4
} setmapdom( 10000 );
         5
} evalmap( m1dc() );
        22
}

Maps can be regarded as trees, with maps as nodes and constants as leafs. The number of branches at a node is the result of the function pars. The nth branch of a map can be obtained with the @ operator. The branch can be changed with the setmap function. The general cast-function Map makes a constant valued map of any value.

} m = m2()'0 + 3;
map
} pars( m )
         2
} pars( m@0 )
         2
} m@1
 3.0
} setmap( m, 5.0, 1 )
map
} m@1
 5.0
} setmap( m, m2()'1, 1 )
map
} m@1
map
} m@1@1
         1
} m = Map( 6 );
map
} /* default index of setmap == 0
} setmap( m, 8 )
map
} evalmap( m )
         8

To check for equivalence of the top-node of maps the function islikemap is available.

} islikemap( 3*m2(), m1()*dnoise(m2()) )
     T
/* both are multiplication of scalar and 2-D vector

The domain dependency of a map can be obtained as a bit pattern with idep. The different dependency values are built-in constants:

} dep1c
           1
} dep2c
         2
} dep3c
         4
} dep1da
       256
} dep1dc
       512
} dep1ds
      1024
} dep2d
      2048
} dep3d
      4096
} idep( m1ds() * m2() )
      1026
}

Various functions

Random functions

The implemented random functions are based on the `rand48' package from the standard C library. The seed can be set (or obtained) with startrand in the following ways:

} startrand()
         123
        4567
         890
} startrand( int3( 654, 765, 56766 ) )
         654
         765
       56766
} startrand( 654, 9966, 12549 )
         654
        9966
       12549
} startrand( 555555555 )
       13070
        6883
        8477
} /* 13070 is a default value in the last case
}

The following random functions are implemented:

random()
a random integer in the total range, positive or negative
random( b )
an integer in the range [ 0, b )
random( l, u )
an integer from the interval [ l, u )
frandom()
a real from [ 0, 1 )
frandom( x )
a real from [ 0, x )
frandom( x, y )
a real from [ x, y )
normrand()
a real with normal distribution, mean 0.0, standard deviation 1.0
normrand( a )
deviation a
normrand( s, a )
mean s

User, system and real time

There are the usual process times available with usrtime and systime. They give their result in seconds as a real. Just time is a real-time function, default starting with startup time, but a time point can be set with settime.

} usrtime()
 0.17186
} systime()
 0.07421
} time()
 149.67
} settime( 1234 )
 1234.0
} time()
 1240.8

Stochastic fractals

The well-known fractal landscape `midpoint displacement' algorithm is implemented to create 2-D maps. The size of the defining grid can be set with fracsize. Its argument is either an integer, defining the size of a square, or an Int2, defining a rectangle. In either case the size is rounded to powers of 2.

} fracsize(100 )
         128
         128
} fracsize( int2( 300, 900 ) )
         256
        1024
}

The fracsize becomes the dbnd2 of the resulting map, which is m2d dependent. The maptype can be either real or complex (bumpmap!).

} rfract()
map
} bfract()
map
}

A parameter h can be given, defining the `fractal roughness' as usual. The default is 0.75. The normal range is in [0,1]. The resulting fractal dimension is 3-h. This parameter may be a map!!

Noise

The famous `Perlin noise' is implemented for 1, 2 and 3 dimensions. They really are normal functions, creating maps when called with the relevant domain. An optional second real parameter is an overall scaling factor. (For ease of use. The same effect could be obtained by multiplying the domain.) An optional third parameter is like an integer seed for the creation of independent noise functions. For each dimension there are three noises:

noise
values in the range [0,1]
bnoise
values in the range [-1,1]
dnoise
directional vector field, with the type of the domain.

So, normal 3-D solid noise is obtained with:

} ns = noise( m3() );

and a higher frequency 2-D bump noise with:

} bns2 = cmplx( bnoise( m2(), 0.1, 0 ), bnoise( m2(), 0.1, 1 ) );

and a 3-D vector valued bump map:

} vns = dnoise( m3() );

Density generators

For all dimensions it is possible to generate a cloud of (particle) coordinates according to a real valued map, interpreted as a density function. A simulated poisson process generates the coordinates. The function result is a list of values in the particular domain, which is determined by the type of the limiting interval and the dependency of the density map.

} pl = density( 100*noise( m3() ), real3(0,0,0) |+ real3(1,1,1) )
list
} length( pl )
          40
} pl'0
 0.06661
 0.39748
 0.32393
} pl = density( 100*noise( m2() ), real2(0,0) |+ real2(1,1) )
list
} length( pl )
          53
} pl'0
 0.06812
 0.20598
} pl = density( 100*noise( m1() ), real2( 0, 1 ) )
list
} length( pl )
          66
} pl'0
 0.00253
}

Dithering

Real valued maps can be dithered. This dithering is meant for `artistic' effects, it is not to be confused with means for exploiting limited display resources. The method is stochastic dithering, with a parameterized number of levels (default 2).

} m = dither( rfract() );
} n = dither( rfract(), 5 );
} s = dither( rfract(), 2+6*noise(m2(),0.1) );

Ordinary differential equations

The function ode solves an ordinary differential equation with initial value. For this purpose a new primitive map is available; v3 is a function giving the velocity vector as a 3-D domain. With v3(), m3() and m1(), functions can be defined for velocity and/or acceleration.

} mp = ode( ma, x0, v0 );

or

} mp = ode( ma, mv, x0, v0 );

or

} mp = ode( mv, x0 );

ma the map function for acceleration, mv the map function for velocity, x0 and v0 the initial values for place and velocity. Both calls can have a parameter extra for the order of the integrating algorithm (0, 1 or 2 giving first, second or fourth order). The default is 1.

} mp = ode( ma, mv, x0, v0, 2 );

The result is a m1da dependent map giving the place solution of the equation. With velocity the derivative can be obtained;

} m = velocity( mp );

With the function order the integration order can be inspected or changed.

} order( mp )
           1
} order( mp, 0 )
           0
}

Solid modeling and ray-tracing

Solid models

Solid models are trees with boolean operations at the nodes and primitives as leafs. Nodes as well as leafs can have lists of attributes attached to them. In IL they are implemented as a new type: Solid.

Primitives

Different primitives are distinguished by an identification integer. The integers for the implemented primitives are built-in constants:
sbal, scyl, scon, scub, stor, spol, siso.
A new primitive is created with a call of prim, with the identification as parameter.

} s = prim( sbal );
bal
}

Some primitives, like a torus, need more information to be created. In IL this is arranged with attributes.

Solid operators

These are the above mentioned boolean operators to create a new Solid from two others:

+
group
++
union
+-
overlay (non symmetric grouping of transparent objects)
-
subtraction
*
intersection

The + operator can be applied as a unary operator to create a single extended node without operation.

For the selection of one of the two constituents of a constructed solid the index operator ' is implemented with a boolean selector.

Solid transformation

Solids can be multiplied with transformations. This can happen on both sides, which is reflected in the order of matrix multiplication. Of course the transformations can be (time depended) maps. Transformations can be applied on each level of nodes and leafs to create a hierarchical transformed (animated) model. As solids constitute a dynamic type, the transformations are accumulated `inside', so no new assignments are necessary.

} translate( 1, 2, 3 ) * s
} s * rotate( 30, point(0,0,0) |+ point(1,0,0) )
} scale( 10*mt(), point(0,1,0) ) * s

A transformation term in a solid can also be m3 dependent. It is evaluated in the context of the hierarchical model and the `place' of the term in the current transformation.

The transformation of a solid can be obtained with the function trfof.

Attributes

A single attribute is implemented as a list. The first element must be an identifying integer, the following elements are parameters with types according to that identification. An attribute to be `hooked' on a solid is a list of these lists.

Each solid has a number of `attribute hooks', again identified by integers. In addition to the general hooks, a leaf has some more to be able to give distinct sides their own attribute lists. A general solid can be given an attribute list with putnatr:

} putnatr( s, i, al )

with s the solid, i the `hook' and al the attribute list. Possible hooks are:

light
to attach light sources (a list of ls... attributes)
refrind
for a refraction index attribute (a refractx attribute)
transcol
for a transparent color attribute (a trnsclr attribute)
nshad
for all surface property attributes (diffrefl, specrefl, reflect, refract, bumpmap, glass)
nodeat
a receptacle for anything else. A user can put here anything she likes for her own purpose. At the moment the only attribute recognized at this place is a nonvisi attribute.

The attribute list of a solid can be obtained with getnatr

} al = getnatr( s, i );

In the same manner the attachment of leaf attributes is implemented with putlatr and getlatr. Here the `hook' is identified with the number of the side, starting with 0. A special hook is identified with leafat. On this place it is possible to give the special primitive parameters that were mentioned above. For instance to give a torus a radius:

} st = prim( stor ); putlatr( st, leafat, [0.1) )

putnatr and putlatr return the solid. The identification of the recognizable attributes with their parameters:
surface properties (-->nshad):

diffrefl
Real: intensity
Real3: color
Real3: ambient light (only for this diffuse attribute)
specrefl
Real: intensity
Real3: color
Real: specular index
reflect
Real: intensity
Real3: color
refract
Real: intensity
Real3: color
glass
Real: reflection factor
Real3: color
bumpmap
Complex or Real3: surface-normal modulation

special transparent solid properties (combine with refract and glass) (-->refrind and transcol)

refractx
Real: refraction index
trnsclr
Real3: transparent color

light sources (-->light)

Point light sources can be classified as constant, linear or quadratic. This concerns the way the distance to the light source affects the resulting intensity. They also can be shadow casting. They all have as parameters their intensity at unit distance, their color and their place.

lspoint0
lspoint1
lspoint2 == lspoint
lspointsh0
lspointsh1
lspointsh2 == lspointsh
Real: intensity
Real3: color
Real3: place cordinates

Other light sources are:

lsambient
Real: intensity
Real3: color
lspot
Real: angle of light cone in radians
Real: focus index
Real3: place
Real3: direction

An lspot attribute is not to be used directly as a light attribute, but rather as a parameter for a point light source instead of the place.

visibility (-->nodeat)

nonvisi
Bool: T means nonvisible.

All attribute parameters can be maps on any domain.

Polygonal defined primitives

To be able to define polygonal primitives, a value of type Poly can be attached to the leafat hook of a primitive identified with spol. An instance of such a type is created with polymesh. It has two integer arguments, stating the number of vertices and the number of triangles. Further more there are two optional boolean arguments (default F), stating whether there are normals for the vertices to be supplied and whether 2-D (uv) coordinates will be defined for the vertices, to be able to control the definition of 2-D texture maps.

The number of vertices and triangles of a already `declared' polymesh can be obtained with polvertexcnt and poltrianglecnt. To define the different values there are the following functions:

} polvertex( pm, vi, r3 )
} /* pm the mesh, vi vertex index, r3 the Real3 value
} poltriangle( pm, ti, i3 )
} /* define the 3 vertex indices of the triangle with index ti
} polnormal( pm, vi, r3 )
} /* define the normal direction of vertex vi
} poluv( pm, vi, r2 )
} /* define uv-coordinate at vertex vi
} polsolid( pm, b )
} /* b boolean to make the mesh a closed solid

All the above functions can be called without the last argument to obtain the current value. To make a primimitiv of a Poly, do:

} s = putlatr( prim( spol ), leafat, [pm) );

As Poly is a dynamic type, more primitives can be made of the same Poly.

Ray-tracing

A model created along the above-mentioned lines can be rendered by the built-in ray-tracer. In IL raytrace is a function that returns a m2d dependent map of the (real4) pixel-type. Apart from the model, a number of entities have to be defined, together constituting the so-called ray-trace environment. This environment is given as a second (optional) argument to the ray-tracer in the form of a list. Like attribute lists, this environment is a list of lists, each of these consisting of an integer identification and a value. These are the identifications, their types and default value:

trcmsk
Int: Dither | Matting | Tapedump
This is a bit-mask that is the bit-wise or of none or more of the following:
Tapedump: creates a file of the resulting image.
Matting: creates a matte in the alpha-channel of the result
Dither: dithers the pixel-values instead of just rounding the calculated real pixel values. (Has nothing to do with IL map dithering)
Jitter: distributes the generated rays in their designated pixel-area as an aid in anti-aliasing.
Dangle: try to eliminate ambiguities of boolean operations when different surfaces coincide.
backcol
Real3: black
background color, i.e. the color when a direct ray does not hit any solid. This also determines the matte.
neutcol
Real3: black
neutral color, i.e. the color when a reflected or refracted ray does not hit any solid
hazecol
Real3: grey (.5*white)
atmosferic color
hazedist
Real: 10000000
critical distance for atmospheric effect.
pixelratio
Real: 1.0
to define non-square pixels
rendersize
Int2: (1280,1024)
pixel size of resulting image.
subrendersize
Intl2: ((0,0)(0,0))
define a render section other then default render size. It may be bigger. Interpreted as lower left corner and size.
camtraf
Transform: unit matrix
transformation of the camera.
camzoom
Real: 1.0
camera zoom, given as cotangent of half the zoom angle.
curdepth
Int: 8
maximum number of iteration steps for continued ray tracing after reflection or refraction.
reflstep
Int: 1
refrstep
Int: 1
stepsize through curdepth for reflection and refraction.
resolution
Int2 (3,0)
control of anti-aliasing. First value gives order of super pixel to start the rendering proces, second value the order of depth of sub pixel rendering. So default is: start with 8*8 pixels and do not go to sub-pixel level. Starting on the pixel level, going to a 4*4 sub pixel division (if necessary) would be: (0,-2).
renderfileformat
Int: 2
(vista, only one currently implemented)
renderfilename
String: none (-->unique filename generated)
because the result is a IL typed value, inside one IL session the filename is not necessary. It is however needed to get the image in a new session.
backmap
Real3 (map): none define a background map instead of backcol.

The default value of each of the environment items is always obtainable with rayenvdef.

Besides the call:

} mp = raytrace( s, envl )

There is:

} mp = anitrace( s, frfr, tofr, envl )

which renders an animation when s is somehow time-dependent. Of course, in addition to m2d, mp will also be m1da dependent. frfr and tofr are Ints, indicating the first and last frame.

Image rendering

Simple image generation

Ray tracing is a form of rendering. In IL it is possible to give a reasonable formal definition of rendering:

extensionalizing a value on a discrete domain.
I.e. calculating data that constitutes, in `tabular' form, a map on a discrete domain.

To do this in general for an image there is the function writeimage:

} md = writeimage( fname, m )

m is a map, probably dependent on m2 and/or m2d of the pixel type (Real4, or coercible to it. fname is a string, indicating the filename for the image. With an optional third argument the number of `colors' (default 3) can be set to 4, keeping or creating the alpha channel. There is the animated version for time dependent maps:

} md = writeanimge( fname, m, frfr, tofr )

In addition to the optional fifth argument as above, there is the possibility to give a integer skip parameter n, rendering every nth frame.

To get existing images, the following are implemented:

} md = openimage( fname )
} md1 = openvalimage( fname )
} md2 = openbmpimage( fname )
} ma = openanimage( fname )

(openanimage tries to read frame 0. It is possible to give an alternative frame number as second argument).

x = openimage("portrait");
/* take existing image, set global pixelsize according
dbnd2( dbnd2( x ) );

y = openvalimage("portrait");
z = openbmpimage("portrait");

/* define a color transforming function by rotating in rgb space
pi2 = 2*arg(-1);
collin = norm( real3(0,0,0)|+real3(1,1,1) );
colrot = "rotate( pi2*%1, collin ) * %2";

/* create a fractal defined color map
fracsize(512);
frcol = real3( rfract(), rfract(), rfract() );

/* define a distortion function, taking complex values as argument
distm = "m2() + %1 * z";
disto = "trfmap( %1, distm(%2) )";

/* make a new picture
dis = (0.5+0.5*y)*disto(1-hypot(z),0.1)*colrot( disto( y, 0.1 ), frcol );
writeimage( "distport", dis );

Complex image composition

There is an optimized function to combine a list of linear deformed images in one image.

} md = rimage( fname, iml );

iml is a list of lists, each of which is composed of a 2-D transformation and an image generating map, as above in writeimage. The animated version is:

} md = ranimge( fname, iml, frfr, tofr );

Time hierarchies

A timeframe is a, possibly non-linear, function of global linear time as determined by m1. Its parameters are a Real3 and another timeframe, thus constituting a recursive hierarchy. So a timeframe is completely defined by a list of Real3. The timeframe of nil is m1. The timeframe of [r3)#tl is a function of the timeframe of tl as follows:

r3'0 and r3'1 are interpreted as a 1-dimesional homogeneous transformation defining a linear function. r3'2 defines a non-linear `acceleration' or `deceleration' in the unit interval. Let a = r3'0, b = r3'1, p = r3'2 and t a time-value in the timeframe of tl.
t = ( t - b ) / a
For -1 <= p <= 1 the function defines a uniformly accelerated `movement':
p * t^2 + (1-p) * t
For p > 1 this transforms into:
t^p
and p < -1:
1 - (1-t)^-p
For direct evaluation of timeframes there are the functions: (tl a list of Real3, t a real)

} tlm1( tl, t )

and the inverse:

} itlm1( tl, t )

A timemap based on a timeframe is obtained as:

} m = tlm1( tl, m1() )

It must be noted that timeframes serve at least a double purpose. In the first place they serve to make different time-rates available. In this case the non-linear acceleration is not usable. On the other hand they define a hierarchy of intervals. This aspect is especially exploited in sound synthesis as is explained in the next section.

Audio rendering

There is a single call to calculate a `sound image' in analogy of raytrace and writeimage. The result is a Real valued, m1ds dependent, map, generated from a number of m1 and m1ds dependent maps. The function is raudio and its arguments are a filename and a number of lists, as many as there are channels required in the resulting sound files.

} m = raudio( name, lleft, lright )

The lists are build as follows:

each element is a list. Let el be such a list. The first element of el is a timeframe list. This timeframe indicates the interval and course of time in this interval for the following elements in el. For each further element of el, let ml be such an element, then
either:

ml is a real valued map that is to be evaluated in the interval to contribute to the sound channel
or:

ml is a list with parameters aimed at changing a map parameter at the time point indicated as the begin point of the interval. The first element of ml is a map. Let pm be this map. The second element is a value or a map. Let np be this element. np is a new parameter for pm, like a programmed execution of setmap at this point in time. The index of the parameter in pm can be given as a third element of ml. The default is 0.

An existing file can be opened as map with opensound.

There are a couple of functions dedicated to special sorts of sound- synthesis. One example is fsin, a sine generator which takes a frequency as argument. This is an aid in implementing complex fm synthesizers. The following is an example of the implementation of a fm synthesizer with one carrier and an indefinite number of modulators, each with its own frequency and modulation index.

fmgen = "fsin( rfmgen( 3 ] )";
rfmgen = "mfi = %1; ( mxi = mfi+1 ) > pars() ? return( %2 ) : 0;
          mf = %mfi; mx = %mxi;
          rfmgen[ %1+2, %2 + mx*mf*fsin( mf ) ]";

Another example is delay, a distortion-free delay line with real valued argument, ideal for implementing wave-guide algorithms. There is also an implementation of a vosim generator.

Interface coupling

For the generic coupling with (graphical) user interfaces there is a mode that can be switched on with uimode.

} uimode( T );

In that case the interpreter stops its normal execution cycle and passes control to some external mechanism by calling the user defined C-function uiloop. The interpreter can be given control again by calling the C-function:

extern void setuimode( int );

with argument 0, turning uimode off. A callback mechanism is available from the external (user interface) environment with the following means:

extern void inituiargs();

to initialize the arguments for a callback;

extern void adduiarg( void *argp, int argtp );

to push an argument for a callback on the parameter stack;

extern void setuicom( char *name );

to execute a named IL function with the parameters created with adduiargs. Implementations have been made with this mechanism for the following:

Invoking IL

IL is invoked by executing the command `il' from the operating system shell. Alternatively `ilm' can be executed for the X/Motif version.

il [file ...] [-M dir] [-L dir] [-D dir] [-P dir] [-A dir]

The file(s) are IL source files to be executed before entering the console interpreter. The options control, together with corresponding environment variables, the directories where the interpreter looks for files, or puts them. For each file category the process is the same:

When writing files the first-found (writable) directory is taken.

The file categories, with their option letter, environment variable and default subdirectory name, are:

A list of directories can be specified with an environment variable by separating the directories with a space or a colon `:'.

ildb, the IL-debugger

Debugging facilities become available when linking an executable with a special kernel object file. The debugger can be invoked by a direct call of the implicit function ildb.

} ildb()
(ildb)

Other ways to get inside the debugger are the occurrence of an error, the invoking of a stop event or the signaling of an interrupt (^ C). The ways commands can be given are very similar to `dbx', the UNIX source level debugger. The possible commands are:

quit
exit
cont
these are all equivalent and return to normal interpreter control.
where
print the contents of the function stack, with text pointers.
print var
print ::var
print `var
print the value of the identifier, as it would be interpreted by IL in the current context. ildb understands ` or :: prefixes. Information about module, local nesting-depth and protection are given.
print %i
print $i
print value of ith argument of current function.
pars
give number of arguments in current function.
stop var
create an event to invoke ildb when the global identifier var is assigned to.
stop in fun
create an event to invoke ildb when the function fun is called.
trace var
create an event to trace assignments to var, printing the function name where this happens.
trace in fun
create an event to trace calls of function fun, printing from which function this call is made.
status
print the list of active (stop and trace) events, with identfying numbers.
delete i
delete ith event from the list.
delete all
delete *
delete all events.
step
step n
continue operation until nth semicolon or end of line (default 1).
stepi
stepi n
continue operation for n syntax elements (default 1).
halt
resume with terminal prompt after leaving ildb. This is useful when ildb is invoked with an interrupt and no further continuation is wanted.

Concept Index

a

arguments

assignments

attributes of solids

audio rendering

c

checking for existence

coercion

coercion of quaternions

colors

comments

composited image rendering

conditional expression

continuous domains, continuous domains

conversion of domains

copying of lists

copying of values

d

debugging

density generators

differential equation

directories

discrete domains, discrete domains

dithering

domain conversion

domains, domains

e

environment

expressions

f

files for programs

fractals

functions, parameter mechanisms

functions, user defined

g

geometrical types

geometry, 2-D

geometry, 3-D

h

hierarchical time

i

identifiers

identifiers, existence

identifiers, protection

identifiers, scope

image rendering

images

implementation

integrating differential equations

intensionality

interface coupling

interpreter

invoking

m

maps

modes

modules

n

noise

o

operators, user defined

options

p

parameters

pixels

polygonal models

primitive solids

program files

protection of identifiers

q

quaternion coercion

quitting

r

random generators

ray-tracing

recursion

rendering of images

s

scope of identifiers

solid attributes

solid modeling

solid, transforming

sound synthesis

stochastic fractals

strings as functions

t

tail-recursion

time hierarchies

timeframes

timing functions

transforming solids

types

v

vector types

Function Index

!

! (bool)

! (int)

!=

#

# (list)

# (string)

%

%

% (real)

%~

&

& (bool)

& (int)

'

' (list)

' (solid)

' (string)

' (vector)

*

*

* (2-D)

* (solid)

* (transform solid)

* (transform)

* (transform2)

** (3-D)

+

+

+ (solid)

++ (solid)

+- (solid)

+~

-

-

- (solid)

-~

/

/

/ (real unary)

/ (transform2)

/~

:

:

<

<

< (list)

<-

<=

<?

<~

=

==

>

>

> (list)

>=

>?

>~

?

?

@

@ (list)

@ (map)

@ (string)

^

^

^ (2-D)

^ (3-D)

^ (transform)

a

abs, abs

abs (complex)

abs (quat)

acos

acosec

acosech

acosh

adduiarg (C)

anitrace

arg

asec

asech

asin

asinh

assign

atan

atan2

atanh

b

backcol

backmap

bfract

bnoise

bumpmap

c

camtraf

camzoom

conj (complex)

conj (quat)

cons

cos

cosec

cosech

cosh

curdepth

d

Dangle

dbnd2

dbnd3

define

degree

dep1c

dep1da

dep1dc

dep1ds

dep2c

dep2d

dep3c

dep3d

det

diffrefl

Dither

dither

dnoise

e

edit

editor

endmodule

evalmap

exists

exit

exitfile

exp

exp (quat)

export

f

fracsize

frandom

frqa

frqc

frqs

funof

g

getid

getlatr

getnatr

glass

h

hazecol

hazedist

head

hsvtorgb

hypot (real)

hypot (real2)

i

idep

ildb

im (complex)

im (quat)

import

inituiargs (C)

Int (string)

int2

int3

intl2

intl3

islikemap

itlm1

j

J

Jitter

l

leafat

length (list)

length (string)

light

linepnt

linpol

log

log (quat)

lput

lsambient

lspoint

lspoint0

lspoint1

lspoint2

lspointsh

lspointsh0

lspointsh1

lspointsh2

lspot

m

m1

m1da

m1dc

m1ds

m2

m2d

m3

m3d

Matting

mirror

mirror (3-D)

n

neutcol

nil

nodeat

noise

nonvisi

norm

normrand

nshad

nyuvtorgb

o

ode

openanimage

openbmpimage

openimage

openvalimage

operator

order

p

pars

pars (map)

pixelratio

polmode

polnormal

polsolid

poltriangle

poluv

polvertex

prim

print

priof

protect

putlatr

putnatr

r

random

ranimage

raudio

rayenvdef

raytrace

re (complex)

re (quat)

read

real2

real3

real4

reall2

reall3

reflect

reflstep

refract

refractx

refrind

refrstep

renderfileformat

renderfilename

rendersize

resolution

return

rfract

rgbtohsv

rimage

rotate

rotate (3-D)

s

sbal

scale

scon

scub

scyl

sec

sech

setmap

setmapdom

settime

setuicom (C)

setuimode (C)

sin

sinc

sinh

siso

skew

specrefl

spol

sqr

sqr (quat)

sqrt

startmodule

startrand

stor

String (int)

subrendersize

systime

t

tail

tan

tanh

Tapedump

time

tlm1

transcol

transf

translate

trcmsk

trfmap

trfof

trnsclr

typename

typeof

u

uimode

unprotect

usrtime

v

v3

vatan2

vcos

vsin

w

writeanimage

writeimage

|

| (bool)

| (int)

|* (2-D)

|* (3-D)

|+ (2-D)

|+ (3-D)

|^ (bool)

|^ (int)

|_ (2-D)

|_ (3-D)

|| (2-D)

|| (3-D)

~

~ (2-D)

~ (3-D)

Type Index

b

bool

c

complex

i

int

int2

int3

intl2

intl3

l

line2

list

o

operator

p

plane3

poly

q

quat

r

real

real2, real2

real3, real3, real3, real3

real4, real4

reall2, reall2

reall3

s

solid

string

t

transform

transform2

w

widget