Name
Pass

14. Scripting

13. Animation action overview | | 15. Export

iconScript actionicon

Scripting is by far the most powerful way to animate your animation elements. In fact, scripting is so powerful you can (if you wanted) to replace any normal animation action except for the 'Sequence reference' and 'Current camera' ones. But in practice you should only use scripting in places normal actions are getting too clumsy or are to limited.

It's properties are:

NameSame as other actions.
EnabledSame as other actions.
OffsetSame as other actions.
DurationSame as other actions.

Edit window

For editing the script associated with a script animation action there is an object window. So lets start by opening such a window to get familiar with our 'workbench'. First you add a script action to any sequence, just like the other actions. After that you open the window by double clicking the action object. This will open a window like the one below.

edit window

Please note I used the window of a configured script action to have an example in place. The script in this example calculates which segments are visible on a led display given a number between 0 and 9. You'll find it as part of the "segDisplay" sample project.

The window has four main areas, namely: 'The test environment control' (left top), 'Variable mapping' (right top), 'The actual script text editor' (blue area) and an 'Object inspector' (right bottom). Of course there is also a generic toolbar, so let's walk through its buttons first:

toolbar

1Adds a new variable mapping.
2Removes selected variable mapping(s).
3Edit selected variable mapping (reassign target animation element).
4Load a script from text file.
5Save current script to a text file.
6Apply script text changes to the action object.
7Reload script from action object (discarding changes).
8Run the current script in the test environment.
9Undo last script text change.
10Redo last undone script text change.

Variable mapping

To mutate animation element values with scripts you first need to map these elements to local 'simple' script variables. This is done to avoid using the long element names. It does not matter if you add analog or binary elements they will be automatically mapped to the correct script variable type (double or boolean).

You add a mapping by clicking button #1, this will open the familiar element selection dialog. Choose any element and click 'Ok'. A row will be added to the mapped variables grid. This grid has four columns, namely:

NameThe name as used within the script. By default it will be the element name without spaces and points. You may change it at any time by clicking in the cell you want to edit.
ElementThe target animation element chosen for this mapping. You can change it by double clicking on the cell. This will reopen the element selection dialog.
InputA starting value to use in the test environment. You can edit its value by clicking the cell.
OutputThe result value for this mapping as generated by the last script run.

The scripting language

Note: I will be assuming you have some sort of experience with scripting/programming, because teaching programming from scratch isn't in the scope of this manual. If you're not familiar with any scripting but still want to learn how to use it in LD4DStudio I suggest you take up a generic (pascal) beginners tutorial first.

When done creating mappings it's time to do the actual scripting. For this you need to learn the used scripting language. Programmers among you will recognize pascal in the used language. The language is indeed highly based upon pascal but it nether offers all the possibilities of pascal nor is it exclusively pascal.

Some base rules:

  • The actual script is always contained between a begin and end.
  • Statements are separated by ;
  • There are two variable types: double (analog) and boolean (binary).
  • Everything is case insensitive.

Operators

This script language is mainly about calculating, therefore you will be using a lot of formula-based variable assignments. So lets start with the operators you can use:

1Power^
2Multiply*
divide/
3plus+
minus-

In this table the first column states the execution order. Operators with a lower number will be applied first. Same level ones will be done from left to right. And of course you can use '(' and ')' to enforce groups.

For example the assignment:

begin
  a:=b+5-c*5;
end.

This will be processed like: (Presume b=3 and c=4)

a:=b+5-c*5 
a:=b+5-20c*5=20
a:=8-20b+5=8
a:=-128-20=-12

One more example:

begin
  a:=((b+5)*(c+5)^2-15) / 2;
end.

This will be processed like: (Presume b=3, c=4)

a:=a:=((b+5)*(c+5)^2-15) / 2 
a:=(8*(c+5)^2-15) / 2(b+5)=8
a:=(8*9^2-15) / 2(c+5)=9
a:=(8*81-15) / 29^2=81
a:=(648-15) / 28*81=648
a:=633 / 2(648-15)=633
a:=316.5633 / 2=316.5

For working with the booleans (binary elements) there are the boolean operators. An overview:

1notnot
2smaller<
smaller or equal<=
bigger>
bigger or equal>=
3equal=
unequal<>
4andand
5xorxor
6oror

Just like the numeric operators some order of execution is in place. For example:

begin
  a:=true and c or d;
end.

This will be processed like: (Presume c=false, d=true)

a:=true and c or d 
a:=false or truetrue and c=false
a:=truefalse or true=true

It's also possible to mix boolean and numeric, in this case numeric will go above boolean. For example:

begin
  a:=3+5>4 xor b and c;
end.

This will be processed like: (Presume b=false, c=true)

a:=3+5>4 xor b and c 
a:=8>4 xor b and c3+5=8
a:=true xor b and c8>4=true
a:=true xor falseb and c=false
a:=truetrue xor false=true

But do note this is not very human friendly formated. It's better to include some extra '(' and ')' signs to make it easier to read for ourself.

Variables

Besides the mapped variables you might also want to use some extra variables for holding 'in between' values. This can be done by declaring variables at script level.

You may declare any number of variables at the start of the script (before the first begin) using the var keyword. It's best shown by example:

var
  a: double;
  b, c: boolean;

begin
  a:=mapA*2;
  b:=a>4;c:=mapA<10;
  a:=b xor c;
  mapB:=a;
end.

This example uses three internal variables ('a', 'b' and 'c'). One is of type double (analog) and two are of type boolean (binary). Variables mapA and mapB are mapped variables. Like the example shows, there's no real difference between the usage of the total of five variables. You just need to prevent declaring variables with names already used by a mapping.

Arrays

Both local and mapped variables can also be used as (multi dimensional) arrays where needed. For the local variables you do this by adding a 'array[n..m] of' infront of the type part of a declaration row. For example:

var
  a: array[0..4] of double;

This will make 'a' an array with 5 elements. You can declare multiple variables of the same array type by adding more names in front of the type just like when declaring simple variables.

You can also use multidimensional arrays, for example:

var
  b, c, d: array[0..1, 0..4] of double;

This will give you three variables whom all have 10 elements organized in a virtual grid of 2 rows of 5 elements.

Accessing specific elements in an array are done by supling a number enclosed with '[' and ']' after the variable name, like:

begin
  mapA:=a[3];
  mapB:=b[1, 2];
  a[2]:=a[3]*c[mapA, mapB];
end.

Like this example shows you can also use other variables as part of the array index specification. Do note array indexes are always whole numbers, and therefor the 'double' values of any used variable are truncated. So e.g. '3.2321' will be used as just '3'.

Like said arrays can be used in both local script and the mappings. It can be very handy to access multiple mappings as elements in a single array variable. This is possible by adding additional information to the mapping name in the top right grid. For example when you have 'camera 1.lookat.x' and 'camera 1.lookat.y' mapped to 'mapA' and 'mapB' you can 'upgrade' those mappings to an array by renaming the mapnames to e.g. 'camMap[0]' and 'camMap[1]'. Internally this will create an 'camMap: array[0..1] of double' variable you can use just like your local array variables.

If needed you can also widen the array to more elements then just the two mappings, this can be done by changing the name to e.g. 'camMap[0 {0..9}]' and 'camMap[1]'. This will result in an internal variable of 'camMap: array[0..9] of double'. You can even use multidimensional arrays with mappings by using e.g. 'camMap[0, 2]' or even 'camMap[0 {0..9}, 1 {0..4}]'.

If you want to use a series of animation elements in an array from the start just add them at once. If all are of the same type the dialog will detect this and it will ask if you want to create an array mapping with them. If you agree the below dialog will open to help you initialize the array.

map to an array dialog

The dialog only needs a name for the mapped variable in order to proceed. But if also lets you control the order of elements in the array and gives you full control over the array indexes to be used.

Statements

Statements are the 'commands' separated by the ; character. Up till now we only used the := (assignment) statement. But there are several more statements you can use, namely:

:=Assignment. (the one we used in the above examples)
++Increases a double variable by one. (example a++;)
--Decreases a double variable by one. (example a--;)
+=Adds the result of the right formula to the left variable. (example: a+=5; is the same as a:=a+5;)
-=Same but while using minus.
*=Same but while using multiply.
/=Same but while using division.
//Comment, all text behind it on the same line will be ignored by the parser. Use it to put some reminders in place on complicated formulas.

If then else statement

Very useful in any scripting language is the ability to make decisions, this is done with the if then else construction. For example:

begin
  if a>5 then
    b:=a*5
  else
    b:=a*10
end.

This script calculates the value for b differently if the value of a is above 5. The part of 'a>5' can be any full blown formula as long the result is boolean. The else is not mandatory so if nothing has to be done at the else part just leave it out. But remember the statement after the then can't have the ';' character when else is present.

If you want to do multiple statements in the 'then' or 'else' part you must use a block statement like the example below

begin
  if a>5 then
  begin
    b:=a*5;
    c:=b+6;
  end
  else
    b:=a*10
end.

You can of course do the same for the else part if needed.

Loop statements

Sometimes you want to apply the effect of a piece of script multiple times to get a desired effect. For this there are loop statements. LD4DStudio's script language supports three different kind of loops, let's go over them one by one.

First there is the 'for do' loop. It's the most simple and elegant loop variant available. With it you just 'say' do the following statement(block) this much times. The most handy thing of 'for do' loop is having a control variable which will contain the current loop index. Things are is best demonstrated with an example:

var
  i, a: double;

begin
  a:=1;
  for i:=2 to 10 do
    a:=a*i;
end.

This little script will calculate the Factorial of 10. you could of course have written a:=1*2*3*4*5*6*7*8*9*10; but this is more dynamic (you can change 10 at any time).

This example has only one statement as the 'do' part but just like the 'if then' statement you can use 'begin' and 'end' to encapsulate multiple 'normal' statements.

The second kind of loop available it the 'while do' loop. This loop executes it's 'do' statement as long the set condition is true. For example:

var
  i, a: double;

begin
  a:=100;
  i:=1;
  while i<5 do
  begin
    a:=a*i;
    i++;
  end;
end.

This script will result in a being '2400'. The advantage over a for loop is you can use (multiple) dynamic conditions in control of the number of loops there will be done.

The downside of the while loop is it can be 'dangerous'. Because you could create an condition whom never will be met resulting in an infinite loop. This will happen in the above example if you took away the 'i++' statement. But don't worry LD4DStudio has a protection mechanism for these kind of situations. You'll read about that in the 'Runtime errors' section below.

The example uses a 'begin end' block but this can also be a single statement when needed, like for example:

var
  a: double;

begin
  a:=100;
  while a>10 do
    a:=a/4;
end.

The third and last loop kind available is the 'repeat until' loop. This one is very much like the 'while do' kind, except it will always be ran at least once. This is because the 'exit' condition is set after the statements in the loop. An example:

var
  a, i: double;

begin
  i:=0;
  a:=1;
  repeat
    a:=a*2;
    i++;
  until a>=64;
end.

This script will calculate how many 'powers' it take to get 64 from 2. At the end of the script i therefore will be '6' (2^6=64).

Just like the 'while do' loop the 'repeat until' loop can potentially be 'dangerous' in being infinite. But don't worry the same protection is in place. Also as a result of the construction of this loop you will not be needing 'begin' and 'end' if you want to encapsulate multiple statements. In many ways the 'repeat' and 'until' keywords function like a 'begin' and 'end' on their own account.

As with the 'if then' statement you may nest loops because they are after all just statements on their own. You can also use full formula based constructions for ether their control variable range (e.g. 'for a:=b to c-1 do') or loop conditions (e.g. 'while (round(a)>10) and (b or c) do').

Internal functions

For certain mathematical functions you can use the internal function library. This is a collection of useful tools you can call in any formula. All functions ether give a boolean or double result based upon one or more input parameters. Available functions are:

Function Parameters Result type Goal
sin value: double double Gives sine of value. Value is in degrees.

Example

sin(60) gives 0.8660.
arcSin value: double double Reverse sine. Gives back a base degree which leads to given sine value.

Example

arcSin(0.8660) gives 60.
cos value: double double Gives cosine of value. Value is in degrees.

Example

cos(60) gives 0.5.
arcCos value: double double Reverse cosine. Gives back a base degree which leads to given cosine value.

Example

arcCos(0.5) gives 60.
tan value: double double Gives tangent of value. Value is in degrees.

Example

tan(60) gives 1.7321.
arcTan value: double double Reverse tangebt. Gives back a base degree which leads to given tangent value.

Example

arcTan(1.7321) gives 60.
log base: double
value: double
double Returns the logarithm of value using base.

Example

log(10, 1000) gives 3 because 10^3=1000.
And log(2, 64) gives 6 because 2^6=64
log2 value: double double Returns the logarithm of value using base 2. This function is just a shortcut for using log(2, value).
log10 value: double double Returns the logarithm of value using base 10. This function is just a shortcut for using log(10, value).
floor value: double double Rounds to whole numbers. The result is always less than or equal to value.

Example

floor(1.2345) gives 1.
ceil value: double double Rounds to whole numbers, the result is alway higher or equal to value.

Examples

ceil(1.2345) gives 2
ceil(1.0) gives 1.
round value: double double Rounds to the closest whole number. The result will be less or higher to value depending on the fraction (<.5 less, >=.5 higher).

Examples

round(1.2345) gives 1
round(1.5432) gives 2.
abs value: double double Ensures positive value by optionally removing the minus sign

Examples

abs(12.5) gives 12.5
abs(-12.5) gives 12.5.
sqrt value: double double Returns the square root of value

Example

sqrt(64) gives 8.
rangeMinMax value: double
min: double
max: double
double Enforces value to min and max values (just like used with animation element "min max" range mode).

Examples

rangeMinMax(91, 0, 90) gives 90
rangeMinMax(-5, 0, 90) gives 0
rangeMinMax(45, 0, 90) gives 45.
rangeModulo value: double
min: double
max: double
double Enforces value to stay within the min and max range (just like used with animation element "modulo" range mode).

Examples

rangeModulo(91, 0, 90) gives 1
rangeModulo(-5, 0, 90) gives 85
rangeModulo(45, 0, 90) gives 45.
odd value: double boolean Returns true if the number before the decimal is odd.

Examples

odd(123.45) gives true.
odd(234.43) gives false.
even value: double boolean Returns true if the number before the decimal is even.

Examples

even(123.45) gives false.
even(234.43) gives true.
bool2dbl value: boolean double Converts a boolean value to a double one. It will return '1' if value is 'true' and '0' if it's false.
dbl2bool value: double boolean Converts a double value to a boolean one. Basically it does 'result:=value<>0', meaning it will return 'true' if the given value is non zero otherwise it returns 'false'.

All parameters used in the function calls can be formulas them self, for example:

begin
  a:=sin(b+15);
end.

Or nested, like:

begin
  a:=sin(b+cos(abs(d*5)));
end.

Script constants

While calculating stuff with scripts it might be useful to know which is the current frame, or the frames per second value used. To this end there are a number of constants you can use in your formulas. Constants have a name just like variables, the only difference is they start with the @ character.

Constants you can use are:

NameGoal
@piValue of pi (3.1415926536).
@fpsNumber of frames per second in animation.
@frameNrCurrent frame number.
@absFrameTimeAbsolute frame time (basicly frameNr/fps*1000).
@relFrameTimeRelative frame time, the time within the current sequence. For example the main sequence has a sequence reference to sequence 2 at offset 5000. Your script is in sequence 2. So if the current absFrameTime is e.g. 24000, relFrameTime will be 19000.
@actionOffsetOffset property value of the script action.
@actionDurationDuration property value of the script action.

The test environment

When your script is completed you might want to test the results it generates. To this end you can 'simulate' a current frame's environment by adjusting the values in the top left panel. The values are:

ValueMeaning
fpsThis will govern the @fps value.
frameNrThis will govern the @frameNr value.
relFrameTime ofs.You might think this is the @relFrameTime. But this is not true, relFrameTime will be: frameNr*fps-actionOffset. In the real animations this sequence may be called via several other sequence references. To simulate such a case you can specify an additional offset to be subtracted from the relFrameTime value.

To get the current animation's environment press the 'load current' button. You can also supply test values for the used animation elements. This is done in the mapping grid. There you can edit the 'input' cell values.

After you configure your test values, you can test the script results by clicking the #8 button to run your script. If successful the 'output' column will show the result values for all mapped variables.

Don't forget to press the #6 button once in a while to save your script to the script action object. The animation engine will not use your latest script if you don't do this.

The object inspector

Until now I kinda ignored the little panel at the right of the script text area. It's there for multiple reasons. It shows you an overview of all currently available constants, functions and variables. You access the different lists by clicking the vertical tabs on the left of the panel. In any of the lists double clicking on an item will insert it's name to the script text at the current cursor position. This will help you prevent typing mistakes.

The 'functions' list can also be used as a reminder for needed parameters and the result type of a particular function. And the 'variables' list does the same for e.g. array index ranges. This is especially handy for arrays resulting from mappings, their ranges can sometimes be tricky to deduce yourself.

Error messages

While learning to script you will be getting a fair amount of error messages from the script compiler. Below you'll find a list of all possible messages and their meaning.

Parameter count mismatch

You failed to supply the correct number of parameters in a function call. For example you might used a:=abs(12,2); abs has only one parameter. You probably meant abs(12.2) a common mistake by people using the comma for decimal separation in their country.

Type mismatch

You tried to supply a double where a boolean is expected or the other way around. For example you might have done: a:=sin(true);

Unexpected end of script

The parser is not expecting the end of the script. You probably forgot the end. or just its .

Unexpected token "foo"

You are trying to use some character or keyword on an unsupported location. For example you start typing "a:=b*10;" without having a begin first.

Unknown variable "foo"

The variable you are trying to use does not exist. You most likely made a typo like a:=leftLig instead of a:=leftLeg .

Unknown constant "foo"

The @someName constant you are referring to does not exists. You most likely made a typo like @frmeNr instead of @frameNr.

Unknown function "foo"

The function you are trying to call does not exist. You most likely made a typo like rund(a) instead of round(a).

Variable "foo" redeclared

You are trying to declare a variable in the var block with a name used by a variable mapping. Or you are declaring a variable for the second time in the var block.

Array dimension interval must be from low to high

you declared an array using e.g. 'array[5..2] of double' this is not allowed, the range of elements should always be incremental.

Array is too big; N elements declared, M elements allowed.

You declared a huge array which exceeds the internal maximum for total number of elements in an array. It's very unlikely you actually intended such a large array because I don't see when you will be needing an array using more then 262144 elements (the current limit).

Variable "foo" is an array, index information expected.

You are using an array variable without specifying the wanted element index, e.g. a:=10 instead of a[0]:=10.

Variable "foo" is not an array.

You are using a simple variable like an array, e.g. a[10]:=0 instead of a:=0.

Array index count mismatch.

You are supplying the incorrect amount of array indexes to an variable reference. If you define e.g. an [0..1, 0..7] array you also need to address it with two indexes ([n, m]).

Runtime errors

Above errors are compile errors, you might encounter them whenever you compile your script. But there are some errors whom only occur during the execution of a script. When these kind of errors are encountered the workspace will open (or refocus) the problem script's edit window. Possible runtime errors are:

Runtime index out of bound detected for variable "foo"

An array index exceeds the limits set by the associated variable declaration. For example variable 'a: array[0..9] of double' is being accessed like e.g. 'a[12]'. This is a runtime error because the used index can also be the result of an formula or other variable whom are unknown at compile time.

This script execution takes longer then set maximum

Because of the looping possibilities it could be possible the animation engine gets stuck in a infinite loop as a result of a programming mistake (e.g. a while loop with an condition whom never can be met). To prevent the whole application from hanging the execution of any script is bound to a maximum amount of time. By default this is 40ms which should be more then enough for most scripts. But because of this limitation a very complicated script might also get killed just because it's taking to long. If thats the case you could increase the set limit in the configuration dialog, but be aware with scripts running over 40 ms you can forget a decent frame rate while playing the animation.

Fatal errors

There are two more possible messages, namely:

  • Invalid input
  • Unknown internal error

But they 'should' never happen. If you do get one of them please post a bug report

Conclusion

This chapter is in no way complete. You could write a whole second manual on scripting alone, but I'm not going to do that. To learn using scripting wisely I suggest you study the sample projects for some helpful examples. After that the best thing to do is to 'just' start scripting.

13. Animation action overview | | 15. Export
Best viewed with Firefox 2, Seamonkey or IE 7 at 1024x768 or higher resolution.
 
LEGO is a registered trademark of the LEGO Group, which does not sponsor, endorse, or authorize this website.