Download - EDITORIAL ThinBasic Journal
- 1 -
EDITORIAL
ThinBasic Journal 2/2008
your resource to keep up with development of passion powered interpreter
Exploring features of
thinBASIC 1.7.0.0
TBGL canvas
Using CALLBACKs
Programming in x86
ThinBasic Adventure
Builder
Entry function
Matrix calculations
Motion blur
- 2 -
EDITORIAL
Welcome to the second issue,
ThinBasic 1.7.0.0 is ready and Journal cannot be left behind!
As new version represents major leap forward, this issue will introduce you to the most
important additions. Callbacks, matrices, TBGL control, we have it all covered for your
convenience. But not only new features are things of interest for true ThinBasic programmer,
so we come with more.
If you always wanted to understand “that assembler thing” to produce incredibly fast code,
you can jump after the article opening the series of assembly programming basics, written by
author of Oxygen module.
ThinBasic has big advantage in being modular, so what about first article about developing
modules? Text covers even some interesting details about how ThinBasic works inside, so
definitely worth reading.
Sure that is not all, we got article on creating motion blur, TBGL effect prototyped using real
world model scene. It is simple, it is fast and it comes with source code, hope you will like it.
New section introduces you to world of most complex ThinBasic script – ThinBasic
Adventure Builder, again written by the author himself.
We should not forget last part of the magazine, “Bits and pieces” demonstrating some useful
tips for your next script.
It is very nice to watch the gang of authors for this issue is bigger than two man show last
time. But don’t be mistaken, it does not mean we don’t want even more contributors! If you
have anything to submit for next issue, wait no longer and send your article to our new email
box at [email protected].
Enough talking, we hope you will enjoy this issue!
Authors
- 3 -
CONTENTS
In this issue
Second issue of ThinBasic journal offers following materials for study.
New in core
Introduction of entry function ................................................................................................ 4
New functions to master strings ............................................................................................. 6
New in modules
Callbacks – new approach for UI programming .................................................................... 8
New Oxygen features ........................................................................................................... 11
Matrix calculus using Math module ..................................................................................... 17
TBGL and UI cooperation for rendering to dialog canvas ................................................... 20
Introducing FileLine module ................................................................................................ 23
Articles
Programming in x86 Assembly code ................................................................................... 24
Faking motion blur with fixed pipeline ................................................................................ 27
Insider
ThinBasic inside ................................................................................................................... 32
Proggies
Understanding adventure coding in ThinBASIC Adventure Builder .................................. 39
Bits and pieces
Alias keyword ...................................................................................................................... 41
Casting data type .................................................................................................................. 41
- 4 -
NEW IN CORE
New in core
New ThinBasic brings some very
important enhancements – again in
multiple areas.
Big utility value is represented by
new string handling functions –
Patch$, Grab$, Digit$ and others.
They will be presented to you in
separate article.
Other very interesting addition is
concept of entry function. Entry
function is one of the approaches
which might help you to organize
program code better. Let’s have a
look at it first.
Introduction of entry function
Petr Schreiber
As you will notice in text about UI module too, new ThinBasic
made an interesting step towards better modularization of
source code.
Beginning with version 1.7, you can start writing the code into
so called entry function.
It is function invoked only once during the script execution,
and takes similar role as main function in C language for
example.
Entry function is named TBMAIN by default, but if you dislike
this fact, you can use APP_SetEntryPoint to specify your own
function. You can retrieve entry function name using
APP_GetEntryPoint as well.
This change to the way scripts are written is completely
optional, so if you don’t see any advantage for your project
(for example in quick scripts in few lines), you are not
obligated to use this approach.
For bigger scripts where code aesthetics matter, it is
recommended to use concept of entry point.
Order of parsing
After initial pre parsing stage ThinBasic interpreted script in
following order:
Global space
User functions, if called
With concept of TBMAIN function, the order is modified to:
Global space
TBMAIN, or other specified entry function
User functions, if called
From this scheme it is evident you will most probably use
global space just for module specification, global variables and
global constants declaration, including header files or declaring
API access generally. The main application code will be now
encapsulated in TBMAIN function. TBMAIN can handle just its
own data necessary and call user functions.
Important difference from other user defined function is in
fact, that after processing all lines in TBMAIN program ends.
Entry point function also does not take any parameters.
- 5 -
NEW IN CORE
The most typical task for TBMAIN is
retrieving initial input data or definition of
main application dialog.
Possible use of entry function
I would recommend using entry point
function in case of bigger applications
generally. Code is a bit better organised this
way, you always know where you begin.
There are also other cases as well, for
example console utilities. Imagine you write
a simple tool to encrypt or decrypt file. It
would take as parameter whether to decrypt
or encrypt file and the file itself. So here are
possible uses:
C:\myTool encrypt file.txt
C:\myTool decrypt file.txt
To make your code simply organised, you
can develop two programs represented by
function, one handling encryption, and other
decryption. Once the script is executed, you
can decide which main function will be
executed based on first parameter of
program.
Did you know?
Different programming
languages take different
approach on main function.
Some do not feature it, some
make it obligatory, and only
few allow hybrid approach with
possibility to choose which
function becomes main.
- 6 -
NEW IN CORE
New functions to master strings
Petr Schreiber
Comfortable string handling is spice of every
good BASIC, ThinBasic makes no
exception.
Set of string commands has been expanded
in latest version with Patch$, Grab$,
Letter$, Digit$, Remain$ and StrFormat$.
Following text will introduce you to their
use.
Patch$
This function serves to replace contents of
special part of passed string. It operates on
string according to passed pair of delimiters,
delimiting tag.
Consider you need to replace wrong text in
second parenthesis with “elephant seal”:
myString =“There is lot of relatively small
animals in Antarctica ( such as penguins ),
but there are some big ones as well ( krill ).”
This is very easy to accomplish with Patch$.
We can imagine tag in this case as any text
in brackets. Code you would use would look
like:
myString = PATCH$( myString, _
“(”, “)”, _
2, _
“elephant seal”)
As you can see, the syntax is quite simple,
first parameter is string we work with,
second and third are matching delimiters,
fourth parameter specifies which parenthesis
to replace and finally the last parameter
contains string to replace text in brackets
with.
As the delimiters are user defined you can
customize Patch$ to work on elements of
HTML code, or other cases where something
like tag is used.
Grab$
This function is complementary one to
Patch$. It allows retrieving text from
specified nth
tag. Consider you have a web
page, and you want to list all alternative
descriptions for images which this web page
contains.
HTML syntax for alternative text looks like
this:
alt=”Nice picture of Yetti”
So you need to retrieve anything between
alt=” and “. This is very easy now. Let’s
presume you have HTML in string named
htmlSource and you want to output any
hyperlinks found to console.
Then following brief script can do the job for
you.
USES "Console"
' -- Page source from clipboard
DIM htmlSource AS STRING = _
TRIMFULL$(CLIPBOARD_GETTEXT)
DIM altText AS STRING
DIM counter AS LONG
' -- Seek hyperlinks
counter = 0
DO
INCR counter
' -- GRAB$ extracts hyperlink
altText = GRAB$(htmlSource, _
"alt="+$DQ, $DQ, counter)
' -- If nothing found then end
IF LEN(altText) = 0 THEN EXIT DO
' -- Add link to our list
PRINTL altText
LOOP
' -- Wait for user key press
WAITKEY
- 7 -
NEW IN CORE
Remain$
This function represents nice complement to
already existing Extract$. While Extract$
returns part of the string up to the first
occurrence of passed match string, Remain$
does the opposite by returning text from the
end of specified match string till end.
Following code will write “It is nice
program” and then “ it runs fast”.
USES “Console”
DIM s AS STRING = _
“It is nice program, it runs fast”
PRINTL EXTRACT$(s, “,”)
PRINTL REMAIN$(s, “,”)
WAITKEY
This kind of commands is useful in case you
do not know the position of match character.
In other case you might consider Left$ and
Right$ for example.
But Extract$ and Remain$ goes a bit more
far, as they allow to optionally setup position
from which their effect starts as well as
specify whether to ignore or not the
character case.
Digit$ and Letter$
These two functions allow you to retain just
specific characters from passed string. The
first one, Digit$, returns just numeric digits
from the string. Following example will
return string “32”.
s = DIGIT$("We have 32 whales”)
On the other side following will return just
letters, so “Wehavewhales”.
s = LETTER$("We have 32 whales”)
You might ask which characters are
considered letters and which digits, and if it
can be changed.
The answer to first question are functions
Digit_GetMask$ and Letter_GetMask$.
And if you want to change the characters in
mask, you might use Digit_SetMask$ and
Letter_SetMask$.
So imagine you want to use Digit$ to
retrieve digits of hexadecimal number.
Hexadecimal numbers are represented using
symbols 0-9 and A-F. You can extend the
mask and use the Digit$ to do the job in
following way.
DIGIT_SetMask$(“0123456789ABCDEF”)
s = DIGIT$(“Value is 1A”)
As mask contains all the necessary
characters, the code above will store “1A”
correctly to the string variable s.
StrFormat$
The last added function comes handy in
situations when you need to present results
of your calculation in text form. While STR$
and FORMAT$ do their job well, you can
build string now without any concatenations.
The code you would do in older releases as
following: STR$(x)+”,”+STR$(y)+”,”+STR$(z)
You can do now like this:
StrFormat$(“{1}, {2}, {3}”, x, y, z)
The numbers in curly brackets contain order
of parameter to be printed. The best part of it
is that you don’t have to point to parameters
linearly; you can reference 3rd
parameter first
and so on.
- 8 -
NEW IN MODULES
New in modules
Many modules have been improved
since last release.
User Interface brings tweaks to
many of its commands, but most
importantly it adds support for
callback functions. New version
means big jump forward from both
code maintainability and speed
point of view.
Oxygen, ThinBasic secret killer
weapon, has been incredibly
expanded as well – do you want to
code OOP with assembler, do you
want to compile own ThinBasic
module with Oxygen? No problem
now.
Math module provides very useful
set of commands to handle matrix
calculations. You can use array as
matrix now, and perform all the
common operations with it. Not just
a toy for mathematicians, matrix
calculations have very wide
practical use.
TBGL, module for 3D graphics
adds possibility to render to dialog
control, which opens new range of
possibilities to editor and TBGL
support tools developers.
And then we have here new
FileLine module – how it will
simplify your next job can be
studied in separate article.
Callbacks – new approach for UI programming
Petr Schreiber
UI module in ThinBasic provides interface for creating dialogs
with controls for long time. Till recent version, the only
supported type of dialog was modeless, which in older
versions meant you could create dialog and continue with
script to handle its events.
In most cases you just put loop processing messages in code
after displaying such a dialog. This way is flexible, allows
handling of all possible Windows messages and events
occurring to dialog.
But - when your script uses such a dialog, ThinBasic parser
processes the lines of message pump loop again and again,
until reaction to some event allows exiting this loop and
closing the window.
This way possibly leads to loss of some script performance
because of this message pump overhead, which is increasing
with growing number of dialogs used in application.
In most cases this approach does not cause any observable
trouble, but it is always better to go faster, isn’t it?
So what is callback?
Every dialog, every control in Windows receives messages.
Now imagine you do not need any kind of (almost) never-
ending loop in your script. With new ThinBasic, you can
handle events using your own callback function, which is fired
automatically only when some message arrives.
In ThinBasic 1.7, you can specify two types of callbacks – for
dialog, and for controls. You can specify both, or just one of
them. Of course, you don’t have to use them, but I think you
won’t go back to continuous loops, let me explain a bit more.
Callback for
Cancel button Callback
for
dialog
Callback for
Submit button
- 9 -
NEW IN MODULES
Dialog callback
This kind of function comes handy with both
modal and modeless dialogs.
When you create modal dialog, the execution
of script is halted until destruction of the
dialog.
Modeless dialog is type present in ThinBasic
till now, it means you create dialog and
continue with script evaluation.
Dialog callback serves for very similar
purpose as the internals of previous looping
approach served in this case.
Let’s compare the old message loop and new
callback approach on code example.
Old style message loop looks like the
following:
DIALOG SHOW MODELESS hDlg
WHILE ISWINDOW(hDlg)
' -- Get dialog messages
Msg = GETMESSAGE(hDlg, _
wParam, lParam)
SELECT CASE Msg
CASE %WM_INITDIALOG
' -- Some initial setup here
CASE %WM_Command
' -- Handle controls
Ctrl = LoWrd(wParam)
CtrlMsg = HiWrd(wParam)
SELECT CASE Ctrl
' -- Processing controls
END SELECT
CASE %WM_SYSCOMMAND
If wParam = %SC_Close Then
' -- Last action before end
EXIT WHILE
END IF
END SELECT
WEND
As you can see, you simply created dialog,
and then script checked messages coming to
dialog in loop, which ended only when
requested.
You can note you had to do following
things:
check if dialog exists with IsWindow
obtain messages using GetMessage
keep looping
While this approach is usable, it keeps
ThinBasic busy parsing the loop. The
callback approach works slightly differently.
Let’s see the same functionality
implemented using callback function.
DIALOG SHOW MODAL hDlg, _
CALL dlgCallback()
CALLBACK FUNCTION dlgCallback()
SELECT CASE CBMSG
CASE %WM_INITDIALOG
„ – Some initial setup here
CASE %WM_COMMAND
„ – Handling controls
SELECT CASE CBCTL
' -- Processing controls
END SELECT
CASE %WM_CLOSE
„ – Last action before end
END SELECT
END FUNCTION
Using this function, you can handle events
for dialog as such, but you can take care of
its controls as well. So the functionality is
the same as for old approach, but code is
shorter for example.
But there are more differences, let’s have a
look at them.
- 10 -
NEW IN MODULES
What is different from the old approach is:
introduction of read only variables
related to messages
callback function is executed only if
something happens to dialog or its
controls
The mentioned read only variables always
begin with prefix CB, like callback. They
help you to recognize what happened to
dialog and provide all functionality
GetMessage processing offered and a bit
more.
CBHNDL returns handle of dialog to which
the event occurred (caller).
CBMSG returns code of message.
CBWPARAM returns the wParam parameter
of message received.
CBLPARAM returns the lParam parameter
of message received.
CBCTL returns id of control.
CBCTLMSG returns message sent to
control.
Another difference comparing to the old way
is fact you can use the same dialog callback
for multiple dialogs, variables above will be
filled with appropriate handle and other
things automatically. This is very good sign
for reusable code. And it is fast, very fast.
Very nice feature attached to this approach is
no need for keeping dialog handle as global
variable – CBHNDL always return the
handle of caller dialog.
Control callback
Well, with dialog callbacks we got some
nice new possibilities and better code
comfort, but fact is that handling control
events in dialog callback, although possible,
could still result in not very easy to maintain
nested tests for control events. Let’s let
dialog callback care just about the dialog.
Controls are complex things, so they deserve
their very own callback functions. The
definition of callback is as easy as adding
command after control parameters.
CONTROL ADD BUTTON, hDlg, %btnOn, _
"On" , 520, 5, 100, 30,,, _
CALL ctrlCallback
Similarly to dialog callbacks, multiple
controls can share the same callback as well.
To be able to differentiate between controls,
each control callback again contains all the
mentioned read only variables. That means
CBCTL returns called control id in this case
and CBCTLMSG the message which
arrived. In control callback, those variables
will return data related only to caller control,
so it filters the right messages for you.
You can use one control callback for more
controls if you like, you can then use code
similar to the following.
CALLBACK FUNCTION ctrlButtons()
IF CBMSG = %WM_COMMAND THEN
„ – Get control id
SELECT CASE CBCTL
CASE %btnOpen
IF CBCTLMSG = %BN_CLICKED THEN
„ – Do something
END IF
CASE %btnClose
IF CBCTLMSG = %BN_CLICKED THEN
„ – Do something else
END IF
END SELECT
END IF
END CALLBACK
As you can see, new callback approach
brings new blood to user interface handling –
more speed, more transparent code and lot of
things done automatically for you. Do you
still want to use old approach?
- 11 -
NEW IN MODULES
New Oxygen features
Charles Pegge
With the wettest Summer on record - our
western seaboard has become the favourite
tourist spot for all the rain-clouds in the
North Atlantic. As the wind roars through
the trees, rain lashes the windows and gutters
overflow; these are ideal conditions for
software development - in front of a nice
warm computer.
So Oxygen has come quite a bit further in
the last month or so. I have taken the radical
step of introducing OOP infrastructure into
the assembler. Now you would think that
Object Oriented programming is a genre that
should be confined to high level
programming. But it turns out that assembler
can produce very elegant OOP with a little
help from the preprocessor, avoiding much
of the clutter that occurs when OOP is
attempted in languages which are not
adapted to it.
OOP is the most recent , but there has been
another major development - Assembling to
sand-alone EXE and DLL files directly from
thinBasic without a linker or any other
intermediaries. It is also possible to generate
functions in Oxygen and call them directly
from thinBasic as though they were from an
external DLL. But these are subjects for
another article.
We can start with some of the minor
features.
Defs
Defs is an efficient way to consolidate
families of equates, using dotted names. For
example:
defs key a 97 b 98 c 99 d 100 ...
These can then be referred to as key.a key.b
key.c etc.
With
The with instruction is used to avoid
repeating long names when doing multiple
assignments.
with myobject.material.color
.red=.5
.green=.4
.blue=.6
.alpha=.9
with ``
with can optionally use quotes.
I have not made with a block structure. It
simply holds the prefix until a new one is
given or until it is nulled. This gives it
considerable flexibility. With can take a
whole name or part of a name involved in
the assignment - anything you wish to avoid
repeating. Quotes are optional but allow the
possibility of prefixing several words.
This is some basic code for testing these new
features and showing how the syntax words:
Unions
type tvec 4 x 4 y 4 z 4 w = 4 red
4 green 4 blue 4 alpha
Any number of unions are possible in a type
statement. Here is another in block form
type tvec4
(
4 x 4 y 4 z 4 w
=
4 red 4 green 4 blue 4 alpha
=
4 cyan 4 magenta 4 yellow 4 alpha
=
16 v
)
- 12 -
NEW IN MODULES
Assignments
Most programming involves moving values
from one variable to another - so building
the assignment operator in the Assembler
can greatly decrease the line count. Though
the '=' sign still has a restricted meaning, its
functionality has been extended to include
most 32 bit assignments via the eax register.
a=b
is translated as:
mov eax,b
mov a,eax
String literals are represented by their
starting address, so it is possible to say
var 4 a
a=`hello world`
a can then be used directly many SDK calls
which take a string pointer. String literals are
always terminated with 2 nulls.
Addresses
c=&d
this is the equivalent of ThinBasic
c=varptr(d)
Indirection
**a=*b
the PowerBasic equivalent of this is
@@a=@b
New Macro Encodings
%l Line break - you can now write a def on
a single line that expands into several lines
of code.
%h Ascii character (in 2 digit hexadecimal
for example: %h82 for 'é'.
Object Oriented Programming
Unlike previous models this does not rely on
user defined macros. It uses virtual tables
and pointers in a similar manner to COM.
OOP is built into the Asmosphere
preprocessor, taking care of all the tricky
bits.
This model also supports multiple
inheritance - i.e. a class can be derived from
several ancestors.
The extra notation required is minimal. In
fact the words class and object are not used
at all. The system is an extension of type.
To declare a class, the type statement is
divided into 2 parts: The first part contains
methods, inherited structures and class static
members. The second part contains the set of
properties assigned to each object, (inherited
properties are automatically appended)
This is a class defined in a single line: !
denotes a method. Slash / divides the two
parts of the class, and inherited types must
be followed by a comma as usual.
type ClassAB
(
classA, ; inherited
class
classB, ; inherited
class
4 methodA! ; method pointer
added for this class (note the !
suffix)
4 methodB! ; another
4 fstatic ; a static
class member ( no ! suffix)
- 13 -
NEW IN MODULES
; this becomes accessible as
'ClassAB_table.fstatic'
; object members:
/
; pointer to the
; virtual function table
; (always present)
4 pvft
4 va ; a property
4 vb ; another
property
4 vc ; ...
)
this is immediately followed by a scoped
class builder and a set of methods
(
build classAB
exit
var ClassAB this
methodA:
...
ret 4
methodB:
...
ret 4
...
)
to create an object:
var ClassAB myObject
myObject.vft=&ClassAB_table
which gives us an empty object except for its
virtual table pointer, which allows the object
to call any of its methods, (including
inherited methods)
edx=myObject.methodA ...
Because all the methods are defined inside
the brackets, they are not visible to the rest
of the program.
Below is a simple piece used to test the basic
functionality of the model.
indexers `esi` offset 0 ascending
esi=dataspace 0x100
type ClassA
(
4 a1!
4 a2!
4 fstatic
/
4 pvft
4 pep
4 pip
)
(
build ClassA
exit
var classA this
a1:
this=&ClassA_Table
ret 4
a2:
this=&ClassA_Table
inc this.pip
ret 4
)
type ClassB
(
4 b1!
4 b2!
/
4 pvft
4 pep
4 pip
)
(
build ClassB
exit
var classB this
b1:
this=&ClassB_Table
mov eax,66
ret 4
b2:
this=&ClassB_Table
inc this.pip
ret 4
)
type classAB
(
classA,
4 fna!
4 fnb!
4 fnc!
4 fstatic
classB,
/
4 pvft
4 aa
- 14 -
NEW IN MODULES
4 bb
)
(
build classAB
exit
var ClassAB this
o2 /+4
fna:
this=&ClassAB_Table
inc this.aa
[#vv]=sizeof ClassAB
ret 4
fnb:
this=&ClassAB_Table
ret 4
fnc:
this=&ClassAB_Table
ret 4
)
var classAB myobject
myobject=&ClassAB_table
; test method fna
ecx=myobject.fna
; test method fnb
ecx=myobject.fnb
; test inherited method
[#vv]=myobject.b1
ret
The invisible work done by the system takes
care of constructing tables of function
pointers (virtual functions) and organising
the elements of inherited structures in
general. This was really too complicated to
handle in macros so it made sense to
internalise it.
Method Overloading
Extending the OOP facilities, it is now
possible to deploy several versions of the
same method, each taking different
parameters. During assembly, the
preprocessor checks the param signature
against the function signatures for a match.
If no match is found then an error will
eventually be flagged by the linker.
This mechanism also ensures that the correct
param types are used with all methods,
whether there are multiple versions of the
method or not.
These are examples of method signatures:
(commas are ignored)
myfunc: ( a ) ...
myfunc: ( a,a ) ...
myfunc: ( a, a, a ) ...
myfunc: ( a a a ) ..
myfunc: ( long long ) ..
myfun: ( long double)
...
ret 16
In the body of the type declaration, methods
have a pling postfix so the format is:
myfunc! (long long)
myfunc! (long double)
Here comes real example of overloading:
; signatures
; with
;
indexers `esi` offset 0 ascending
esi=dataspace 0x100
var 4 ab
type a 4 v
var a abc
type ClassA 4 a1! 4 a2! 4 fstaticA
/ 4 pvft 4 pp1 4 pp2
(
build ClassA
exit
var classA this
a1:
this=&ClassA_Table
ret 4
a2:
this=&ClassA_Table
inc this.pp1
ret 4
)
type ClassB 4 b1! (a a) 4 b2! / 4
pvft 4 pp1 4 pp2
(
build ClassB
exit
var classB this
b1: (a a)
this=&ClassB_Table
mov eax,66
- 15 -
NEW IN MODULES
ret 12
b2:
this=&ClassB_Table
inc this.pp1
ret 4
)
type classAB
(
classA ova
classB,
4 fna!
4 fnb! ()
4 fnc! ()
4 fnc! ( a )
4 fnc! ( a a )
4 fnc! ( a a a )
4 fstatic
/
4 pvft
4 aa
4 bb
)
(
build classAB
exit
var ClassAB this
fna:
this=&ClassAB_Table
inc this.aa
[#vv]=sizeof ClassAB
ret 4
fnb:()
this=&ClassAB_Table
ret 4
fnc: ()
[#vv]=-5
ret 4
fnc: (a)
[#vv]=10
ret 8
fnc: (a,a)
[#vv]=2
ret 12
fnc: (a,a,a)
this=&ClassAB_Table
[#vv]=42
ret 16
)
var classAB myobject
myobject=&ClassAB_table
; test method fna
edi=myobject.fna
; test method fnb
edi=myobject.fnb
; test method fnc with sig 3
edi=myobject.fnc abc 1 2
; test method fnc with sig 2
edi=myobject.fnc 1 1
; test method fnc with sig 1
edi=myobject.fnc abc
; test method fnc with sig 0
edi=myobject.fnc
; test inherited method
[#vv]=myobject.b1 1 1
; class static
classA_table.fstaticA =1
; class static
classAB_table.fstatic =1
; class static inherited
classAB_table.ova.fstaticA=1
; own property
myobject.aa=3
; inherited property
myobject.pp1=2
; inherited property with extended
; name
myobject.ova.pp1=2
; same as above using WITH prefix
; alternative ..
with myobject.ova
; same as above using WITH quoted
prefix
with `myobject.ova`
.pp1=2
; clear WITH
; alternative ..
with
; clear WITH by empty quotes
with ``
ret
- 16 -
NEW IN MODULES
Adaptations for COM programmming
The quest is to produce tidy COM in
Assembler using the Oxygen OOP model.
This is really more of a versatility test than a
proper implementation of COM at this stage.
One of the little problems that arose when I
tried Oxygen's OOP model with COM is that
multiple inheritance proved to be a
mismatch, so I introduced a way of
indicating single inheritance using the
keyword of.
type IUnknown
(
4 QueryInterface (a p)
4 AddRef ()
4 Release()
4 Counter
/
4 pvft
)
type IKnown
(
of IUnknown,
/
4 test_interface
4 other_interface
)
To help with virtual structures and
pointering, a variable can now be associated
with a specific register and have a structure
associated with it. Example:
var IUnknown this [ecx]
...
mov ecx,[esp+4]
...
this.addref
...
The provided sample code1 does very little
other than to test the COM structures and
pointering are performing correctly. An
1 SampleScripts/Oxygen/test_COM.tBasic
interface is switched by calling
QueryInterface, with one of the GUIDs. A
pointer to the new interface itest is granted.
This interface is adopted then released.
Private Members
These are elements which are invisible to
derived classes - but still exist in the object.
So they are only accessible using methods
inherited from the ancestral class.
Any element can be made private by
appending a dash (minus sign) after the
element definition.
Abstract Members
These are denoted by any element that has a
size of zero!
Derived types must provide matching real
elements to replace these before they can be
used to instantiate objects. They specify the
general structure of a type before getting
down to specific implementations.
These I hope complete the Oxygen OOP
facilities. But there is much to explore and so
many different combinations.
What's Next...
The next step for Oxygen will be an internal
one. So far, the largest program has been
about 61k of source code and the test suite is
about 200k. Oxygen needs to undergo 'case-
hardening' to ensure that it can handle much
larger programs and catch all the obvious
errors that might occur during project
development. One idea is to develop a
'CodeBlaster' to generate large volumes of
source code, and see what gets past the error
checking. This can be highly automated in
thinBasic - and largely unbiased as to what
kind of code is generated. Some evolutionary
feedback in the system, will steer it towards
producing plausible code.
- 17 -
NEW IN MODULES
Matrix calculus using Math module
Petr Schreiber
One of the most interesting things which you
learned in you math class (or maybe it still
awaits you) is the concept of matrix.
Although this matrix is not the one making
you look cool in dark glasses, it is still very
powerful thing thanks to versatility of its
possible uses.
This article will use the word matrix in
meaning of 2D matrix.
What is the matrix?
You can imagine matrix as organized set of
numbers. In case of most common 2D
matrices we can see numbers are organized
in rows and columns.
Each member of matrix is identified by
index, let’s see how on following picture
describing matrix 3 by 3.
You can observe that first index of element
represents row the number is placed in and
second number the column.
Matrix filled with numbers can look like
this:
To represent matrix in ThinBasic, you can
use array. To create matrix like the above
you can simply use array commands.
DIM myMatrix(3, 3) AS EXT
myMatrix(1, 1) = 1, 2, 3
myMatrix(2, 1) = 4, 5, 6
myMatrix(3, 1) = 7, 8, 9
How math module supports matrices?
Math module in ThinBasic now brings
mechanisms to perform operations with
matrices using very simple notation of mat
statement.
You can sum, subtract or multiply matrices
using the appropriate operator.
MAT A() = B() + C()
MAT A() = B() – C()
MAT A() = B() * C()
Division of matrices isn’t usually done using
slash symbol in math, and it is defined as
following.
A = B C-1
If you do not remember, that “-1” means
inverted matrix.
Matrix inversion is supported by math
module as well, using INV keyword. So to
divide matrices B and C to produce matrix
A, you would use following notation.
MAT C() = INV(C())
MAT A() = B() * C()
That is not all; you can similarly transpose
matrices as well.
MAT A() = TRN(B())
Math module helps you with matrix filling as
well. You can make matrix identity (all
zeros, just on the diagonal with number one).
MAT A() = IDN
You can also set all elements of matrix to
specific number. To zero whole matrix, you
can pick ZER keyword.
MAT A() = ZER
- 18 -
NEW IN MODULES
To fill matrix with number or result of
expression, you can use more versatile CON
keyword as well.
MAT A() = CON(3.14)
But what is all this good for?
As I said in the introduction, matrices can be
used to achieve wide range of tasks.
You can find example of practical use of
matrix in every math book, let’s see some
real world example now.
Something like being trapped by evil
magicianTM
occurs to most of us once or
twice a year.
You know for sure how it goes in such a
case, but for those lucky to be not captured
by evil magicianTM
yet, let’s see how it
usually goes.
Magician: Earthworm!
You: Me?
Magician: Yes!
You: Uh!
Magician: Solve following problem or I will
change you to something nasty!
“There is garden, where they have apples,
oranges and bananas. I, the mighty warlock,
want to keep in shape so I always take some
fruits to keep me fresh. To maximize the
refreshing effect, I must eat 23 fruits in total.
I always eat one orange less than bananas
and twice as many apples as bananas. How
many fruits of each kind I eat to keep me
looking as superb as I do look now?
This kind of task can be solved using brute
force algorithm (which we do not consider
elegant enough) or as a system of equations.
You might remember systems of equations
can be solved using matrices. And ThinBasic
supports matrix math. So we can make
magician angry just by using PC with
ThinBasic installed.
If you rewrite task rules into equations, using
symbol “a” for apples, “o” for oranges and
“b” for bananas, you get following system:
a + o + b = 23
o = b – 1
a = 2 * b
Once you rewrite this system to have just
unknowns on one left and the rest on the
right side:
1a + 1o + 1b = 23
0a + 1o - 1b = –1
1a + 0o - 2b = 0
...you can use power of matrices to solve this
system in no time. Matrix approach to solve
system of linear equations is following:
Ax = b
x = A-1 b
So you just create one matrix for left side
unknowns coefficients (A), one for right side
numbers (b) and one for the result (x).
DIM A(3, 3) AS EXT
DIM b(3, 1) AS EXT
DIM x(3, 1) AS EXT
A(1,1) = 1, 1, 1
A(2,1) = 0, 1,-1
A(3,1) = 1, 0,-2
b(1,1) = 23
b(2,1) = -1
b(3,1) = 0
To re-implement math formula we need to
do just two basic steps. The first is to
calculate inverse matrix of A:
MAT A() = INV( A() )
Then we can finally calculate the unknowns
using following statement.
- 19 -
NEW IN MODULES
MAT x() = A() * b()
Using two mat statements we just solved evil
magician’s task. Matrix array x now contains
result – 12 oranges, 5 apples and 6 bananas.
What is even better, this two lines of code
will serve us to solve any properly defined
system of equations – 2, 5 or 1000
unknowns, matrices will do the job for us in
no time.
Sure thing such a trivial task I demonstrated
can be solved using pen and paper in
acceptable time as well. But imagine more
complex equations, where making a mistake
thanks to inattention is much more probable.
In such a cases matrix statements are
something you can simply rely on.
- 20 -
NEW IN MODULES
TBGL and UI cooperation for
rendering to dialog canvas
Petr Schreiber
TBGL module provided support for
hardware accelerated graphic rendering for
long time; it was used in multiple games and
graphic demos.
All rendering was made into separate
window, which could be optionally created
in full screen mode.
In case you needed to create tools like
editors, requiring some controls, you had
only two options:
Render GUI using TBGL commands
Use TBGL window and separate
modeless dialog to hold the controls
Both approaches are usable, but have their
issues. First mentioned custom GUI
rendering has disadvantage of eating part of
rendering performance for drawing of
controls on screen, the second approach was
more usable, but definitely not standard in
world of Windows programs.
TBGL control canvas basics
TBGL 0.2.2 preview, bundled with current
ThinBasic, adds one more powerful feature
to your arsenal – possibility to render
graphics inside standard dialog created using
UI module, which represents the best way to
handle tools like editors or model viewers.
To maintain all perfect features UI control
handling provides, such as automatic
resizing, TBGL canvas for drawing is
implemented on following principle.
Create control which will serve as
canvas for TBGL drawing
Bind this control to TBGL system
If control changes update graphics
proportions
If it is no longer needed to render to
dialog, release the control from
rendering
This means that you can design dialog layout
using UI commands as you are used to.
Including styles, dimensioning, resize
anchors and others.
3D graphics in dialog
To be able to render to such a control, you
will need its handle. This is identifier, which
can be retrieved both during control creation,
or any time later using control handle UI
command.
hCtrl = CONTROL ADD LABEL ...
Once the handle is obtained, you can bind it
for rendering, using TBGL command.
TBGL_BindCanvas( hCtrl )
To check whether control is bound to
rendering or not, you can use
TBGL_CanvasBound( hCtrl )
Once the control is bound, you can
immediately render to it, create entity
systems ... simply work the same way you
are used to work with TBGL window.
If you no longer need to render to control
canvas, simply call
- 21 -
NEW IN MODULES
TBGL_ReleaseCanvas( hCtrl )
Attention - unlike TBGL_DestroyWindow
you know from older module revisions,
TBGL_ReleaseCanvas does not destroy the
control it hijacked for rendering, it simply
stops drawing to it and deallocates all
resources created during the work with the
control. Similarly to TBGL window, it is
possible to have only one TBGL canvas
inside dialog, but you can still define
multiple viewports inside the rendering area
for multiple views of same or even different
scene.
As you can see, all canvas commands have
control handle as parameter, which means
first step to multiple canvas support in some
of following TBGL module revisions.
Best practices for using canvas
When designing computer games in
ThinBasic, we usually follow the rule of
“more frames per second, better”, because it
is presumed you play the game and do not
run any other programs, therefore you try to
use all horsepower of hardware you run on,
just placing doevents to main loop to let PC
rest a bit.
In case of windowed applications, especially
various editors and viewers, we should think
a bit differently. It is highly probable user of
our program will have other tasks running as
well, like graphic editor for example.
To not hog PC too much, in most
applications it will be needed to render
image only in following situations:
Window is being repainted
Control is being sized
That basically means handling WM_PAINT,
WM_SIZE and WM_SIZING in dialog
callback. After receiving WM_PAINT, your
program will just react with calling the
procedure handling repainting.
In case of WM_SIZE and WM_SIZING, you
will need to call
TBGL_UpdateCanvasProportions _
( hCtrl )
As TBGL canvas control will be resized or
repainted in case the same occurs to its
parent dialog, you can handle all those
events in dialog callback2.
It is recommended to set colour of control
you will use for rendering to equivalent of
TBGL_BackColor used in your program, for
better results during resizing.
This could look like in following example.
CALLBACK FUNCTION dlgCallback()
SELECT CASE CBMSG
CASE %WM_PAINT
RenderMyImage()
CASE %WM_SIZE, %WM_SIZING
TBGL_UpdateCanvasProportions_
(hCtrl)
RenderMyImage()
END SELECT
END FUNCTION
Of course, you are not limited to events
mentioned, in case you for example drag
something on TBGL canvas, you can update
canvas more frequently.
When we need continuous refresh
There are cases you do not need refresh
canvas once in a while, but continuously –
for purpose of animation for example.
In such a case, if you still want to design
CPU friendly application, you can take
advantage of timers.
2 If you are not familiar with term „dialog callback“,
please see the article Callbacks – new approach for
UI programming
- 22 -
NEW IN MODULES
You can imagine timer as message emitter,
which sends WM_TIMER event to dialog in
specified discrete moments. So once timer
ticks, you can call the rendering functions.
Even lot of times per second, it will still
mean low use of CPU.
Integrated timers are one of the new features
of UI module. You just create timer with
specified period on the beginning, and
destroy it on the end.
Following dialog callback shows again a
typical handling of mentioned situation with
help of timers.
CALLBACK FUNCTION dlgCallback()
SELECT CASE CBMSG
CASE %WM_INITDIALOG
DIALOG SET TIMER CBHNDL, _
%myTimer, _
%timeOut, _
%NULL
CASE %WM_SIZE, %WM_SIZING
TBGL_UpdateCanvasProportions_
(hCtrl)
RenderMyImage()
CASE %WM_TIMER
RenderMyImage()
CASE %WM_CLOSE
DIALOG KILL TIMER CBHNDL, _
%myTimer
END SELECT
END FUNCTION
You can even handle canvas binding and
releasing in the dialog callback, that is up to
you personal preference.
In case you forget to release canvas before
script ends, module garbage collector takes
care of releasing necessary data
automatically.
- 23 -
NEW IN MODULES
Introducing FileLine module
Petr Schreiber
File processing is one of most common tasks
in programming. ThinBasic provides File
module for general purpose file manipulation
already.
There are situations, when you need to
process line based ASCII file formats. That
sounds like a trivial task, but it has one
possible catch – there is lot of operating
systems around, each handling lines
differently.
This makes your head spin when you come
in touch with file formats used on Windows,
Macs and Unix. Each of mentioned systems
marks end of line differently. Typical multi
OS file format is Wavefront OBJ, used to
represent 3D models.
And that is when FileModule comes in – it
allows reading files line by line, does not
matter on which system they were produced.
Using File module you could do the same,
but you would have to detect line end
sequence on your own and then filter special
characters out for each line.
Typical use
Let’s have a look at minimal example
reading file and writing it line by line to
console.
USES “FileLine”, “Console”
DIM hFile AS DWORD
DIM sLine AS STRING
hFile = FileLine_Open(“C:\test.txt”)
IF hFile THEN
WHILE NOT FileLine_IsEOF(hFile)
FileLine_LineInput(hFile, sLine)
PRINTL sLine
WEND
END IF
FileLine_Close(hFile)
WAITKEY
You can see the file is simply opened by its
file name, no need to specify mode or
anything as FileLine module is currently
designed for reading files only.
If opening the file was successful, returned
handle is nonzero value. Handle serves as
file identifier for all consequent operations;
it is shorter than file name.
Lines from the file can be safely read until
end of file is found.
Conclusion
FileLine module is tool serving well for
specific area of file interactions. It is very
useful for processing ASCII based files
produced by application from unknown
operating system.
It can do the job well when you scan text
files for specific information as well.
Have a look to ThinBasic help file for more
functions provided by FileModule.
Did you know?
Windows world uses both
carriage return and line feed
characters to end the line.
Users of Linux are used to just
line feed, while Macs rely on
single carriage return character.
- 24 -
ARTICLES
Articles
Oxygen is one of the special
modules in ThinBasic. But
assembly programming might not
be so simple to understand for
newcomers. This is why Journal
brings you series of articles on the
topic of x86 assembly, written by
the creator of Oxygen module
himself.
For anybody interested in advanced
graphic techniques we prepared
article on creating motion blur with
ThinBasic and TBGL, hope you
will find interesting.
Programming in x86 Assembly code
Charles Pegge
This is a series of short articles of an introductory nature,
exploring low-level programming - where there is a clear
connection between the code and the hardware that interacts
with it. Assuming you already program in Basic this wont, I
hope be too challenging.
Introducing the x86 CPU registers
Programmers are not always clear what a CPU register might
be. (Is it something to do with the Windows Registry?). In fact
it is one of the fundamental components that make a CPU
work. From an electronics point of view, a register is like a
single memory location, usually 32 bits wide in our case.
Registers simply hold data for arithmetical and logical
operations, and for transferring data to and from memory. With
current CPU architectures all data has to pass through registers
even if it is only being transferred from one memory location
to another.
The x86 has many registers, even registers which are totally
inaccessible to the programmer, but we can focus on the most
common ones used in the main CPU leaving aside the Floating
Point Processor and SIMD extensions for the time being.
You can think of these as very local integer variables. Each
register started out in the early days having a distinct role to
play and their own set of special features. The advent of 32 bit
processing made the architecture more uniform, but the
ancestral genes and tradition remain firmly embedded in the
system.
If you call a function outside your program, the EAX register
is often used to return a result, and the original contents of the
ECX and EDX registers will be lost. By convention publicly
available functions must preserve or restore the original values
for the EBX, ESI, EDI and EBP registers.
In 32 bit mode, there are only eight registers you need to know
about. They can all do arithmetic and logic but some are
specialized or have a traditional use.
- 25 -
ARTICLES
EAX - the busiest of them all is often used
as the accumulator for arithmetical
operations.
ECX - often used for counting in a looping
operation. There are a few legacy
instructions which automatically increment
or decrement the ecx register, but these are
microcoded and no more efficient than doing
the job using more elementary instructions.
EDX - When using mul, imul, div or idiv;
EDX is used with EAX as an extended (64
bit) register. With multiplication, EDX
catches the overflow from EAX. With
division EDX is used to contain the upper 32
bits of the operand to be divided. After the
division EDX will hold the remainder
(modulus).
EBX - traditionally used o hold the base
address for an array of variables.
ESP - The stack pointer. Only used for stack
operations - never to be trifled with. To
obtain stack space for local variables, you
subtract the number of bytes you require -
which should always be in multiples of four.
The best way to do this is first preserve the
original EBP value by pushing it onto the
stack, then move the ESP value into the EBP
register then subtract the required bytes from
the ESP register. The EBP register is the
used to index locations in this newly
allocated space (Using negative offsets).
When this local space is no longer required
the value in EBP is passed back to ESP then
the original EBP value is popped back into
EBP.
EBP - Companion of ESP above, invariably
used to hold the base offset for local
variables. It is generally understood that its
original value is always preserved between
function calls - no matter what operating
system is being used.
ESI - Often used for indexing strings, and
used in conjunction with its twin EDI for
copying blocks of data from one location to
another.
EDI - twin of ESI. Some legacy operations
involve the use of EAX ESI EDI and ECX to
copy strings.
The lower 16 bits of these registers are
accessible for 16 bit operations: Just remove
the E, and you get the original 8086 16 bit
registers: AX CX DX BX SP BP SI DI.
Some registers have direct access to the first
2 bytes by name: (L lower H higher)
AL AH CL CH DL DH BL BH
32 bits
AH AL
AX
EAX
Scheme of EAX register
Two further registers play an important part
in programming:
EIP - the instruction pointer is used to feed
instructions from memory to the CPU. You
cannot change its contents in the same way
as the other registers, but call or jump
instructions do this implicitly. Changing the
sequence of instructions conditionally or
unconditionally throughout the program.
EFLAGS - holds various bit flags which flip
in response to various events, for example if
an arithmetical operation results in a
negative value then the Sign flag is set. If a
result is zero then the zero flag is set. This
allows conditional jumps like jg, jl, jz or jnz
to be activated.
- 26 -
ARTICLES
Using the registers to access memory
Most of the time, fixed addresses are not
used in assembly programming (except for
small micro-controllers). A base address is
set in a register then a fixed offset is used to
access an individual variable among a set of
variables. If you see the name of a register
enclosed in square brackets [..] it means that
the register hold the base address of a
memory location.
Did you know?
Oxygen module allows even
compiling EXE and DLL files
on the fly. If you will follow
series of articles on assembly in
ThinBasic, you will be able to
write your own compiler or
even create ThinBasic module
directly from ThinBasic.
Did you know?
You can find various samples
on assembly programming in
SampleScrips/Oxygen, under
ThinBasic installation folder.
Provided samples cover both
basic use and advanced topics
like collision detection.
- 27 -
ARTICLES
Faking motion blur with fixed
pipeline
Petr Schreiber
If you study look of real time graphics in last
few years, you definitely noticed that there is
a big push towards post processing rendered
picture in some way. Glow, HDR, depth of
field and radial blur can be seen very often
even in main stream games, which is thing
hardly imaginable 10 years ago3.
This article will demonstrate you technique
to bring motion blur, one of the visually
most interesting effects, to your ThinBasic
scripts.
What is motion blur?
Motion blur is something we could describe
as phenomenon, manifesting as blurring of
objects during fast movement. The
propagation of blur as such is always
proportional to the speed and direction of
moving object.
Pessimist would describe motion blur as
defect, preventing us from seeing moving
objects sharp. But using motion blur has
some benefits, as it serves well to emphasize
object speed.
Train captured by inexperienced photographer
3 With few exceptions like the game Outcast
Goals we want to achieve
Purpose of this article is to show motion blur
post processing technique, which should
have following characteristics:
Be compatible with most hardware
Be “automatic”, not requiring to
know object speeds
Be independent on amount of
geometry rendered
Look a bit as real motion blur :)
There is a lot of approaches to produce
motion blur on today’s hardware; like using
accumulation buffer to accumulate geometry
and render it at once, slightly shifted, or
various shader based approaches. Sadly
neither of these two fulfils all requirements
we pointed out a while ago.
The road we will take
We will try to use similar approach to the
one we used to replicate glow and radial blur
in ThinBasic before – rendering to texture.
In fact we will need to use more than one
texture.
The basic idea is – motion blur manifests as
blurred object, which means we see “ghosts”
of the object from last frames. To replicate
this, we need to keep few last frames in
memory and render them one over each
other.
We will test the effect on basic scene with
aircraft in canyon.
Preparing textures
There is little problem with limitation of
older hardware, which needs us to use power
of two textures. As we usually render to
window of arbitrary size (for example
640x480), we need to handle this somehow.
Solution is to create texture with sides of
power of two, which fits best the target
resolution. TBGL can help you on this, just
- 28 -
ARTICLES
use following to convert resolution to its
nearest power of two representation.
DIM w, h AS LONG
TBGL_EvaluatePOTMatch(640,480,w,h)
In this case command will return 512 and
256 to passed variables, as nearest safe
match.
As you can see, there will be noticeable loss
of detail for such an image. This situation
gets better with higher resolutions, where
NPOT4 / POT pixel ratio gets lower.
Once we have the recommended POT
texture dimensions, we can use
TBGL_MakeTexture to pre-allocate space for
rendering to texture.
But we should not forget owners of newer
cards, which support textures of any
proportions.
To check whether architecture we run script
on can handle arbitrary texture, we can take
advantage of new command, which serves to
retrieve info on texturing subsystem -
TBGL_TexturingQuery. This function
currently allows retrieving following
information:
maximum texture width
maximum texture height
whether NPOT is supported
4 Not power of two
Here is example how to retrieve mentioned
data for textures.
DIM texInfo AS TBGL_tTexturingInfo
TBGL_TexturingQuery( texInfo )
Please note that you do not need to seek
definition for TBGL_tTexturingInfo
anywhere, thanks to flexible way ThinBasic
modules work, TBGL prepares everything
for you.
Now the texInfo variable contains data in
UDT members maxWidth, maxHeight and
NPOTSupport. The last one contains true in
case you can get NPOT textures, the first
two obviously maximum dimensions in
pixels.
With all the information we just learned we
can determine safe size for texture on any
GPU architecture.
Initial setup
Now we know how to determine correct
texture dimension, but there is a more to
decide:
how many last frames to keep
how often to capture them
According to my tests, to not hog memory
too much and hold acceptable performance,
we would like to use something between 4 to
8 frames.
The second problem is trickier, it is not
possible to just capture last 8 frames and
draw them one over other. That would result
in different output on different PCs as it
would be frame rate dependant. So it is
better to determine time interval, typically in
milliseconds, in which we will capture the
frames. To evaluate time I recommend using
ThinBasic HiResTimer commands.
Be careful to not specify timeout below
precision of timer. Too big timeout can hurt
the quality of output as well. As we will
- 29 -
ARTICLES
render just the captured frames in the end,
timeout delimits the frame rate up to some
level. To get smooth speed, time value
should not exceed 33ms.
So when we put it all together, function to
init motion blur could look like following: SUB MotionBlur_Init( xRes AS LONG, _
yRes AS LONG, _
nTexture AS LONG,
nTimeout AS LONG )
„ -- Validate xRes and yRes
„ -– Allocate textures
„ –- Save timeout
END SUB
Capturing the frames
To render to texture, we need to do just few
basic steps – setup viewport to texture size,
render scene and copy it to appropriate
texture slot.
Sample frame representing aircraft in canyon
If you are using entity based approach, the
capture can get as easy as following:
IF MotionBlur_BeginPass() THEN
TBGL_ClearFrame
TBGL_SceneRender(%SCENE1)
MotionBlur_EndPass()
END IF
In this case MotionBlur_BeginPass() just
checks if it is time to render, and if yes then
it sets viewport to size of texture.
MotionBlur_EndPass() selects the
appropriate texture for this pass (by looping
through our pre-allocated textures) and
copies viewport contents to it.
During the capture process nothing is drawn
to the screen, all is just moved in memory as
we do not call TBGL_DrawFrame.
Rendering final composition
Once we have the capturing system done, we
can focus on how to combine textures to
produce the motion blur effect. As we need
to combine multiple images to one, it is clear
we will use some sort of blending. To be
more precise, we should follow these steps:
set up 2D mode using
TBGL_RenderMatrix2D
enable blending
disable depth mask
render all captured frames, stretched
over whole screen
cleanup state changes
Sounds very simple, doesn’t it? Well, there
is one catch. If we would render polygons
with textures at full color, we would get
terribly bright image.
Wrong output of layer rendering at full color bright
To prevent this issue, we just need to scale
brightness of each layer according to number
of textures used. If we draw for example 8
blended polygons over each other, we need
- 30 -
ARTICLES
to set colour of each of them to 1/8th
of
brightness. That means not render them all
with default white colour (255, 255, 255),
but evaluate each colour component as
256/number of passes.
Output of proper frame layer blending
That will darken each of the layers but
thanks to the blending of the colours we will
get correctly exposed image in the end.
Comparing reality and computer graphics
But does this effect really represent motion
blur correctly for used objects? The only
answer is real world test. For this purpose I
created model5 of aircraft you now know
from the canyon images.
Final wooden model levitating thanks to thinWire :)
Original design of aircraft is by Mike
Hartlef. I tried to keep look of computer
5 Noble name for pieces of wood sticked together
with glue
model and 3D model identical at least in the
most characteristic shapes and proportions.
As I surprisingly do not have much rock
canyons of tiny model dimensions in nearest
environs, I found the nearest matching
location to do the job and replicated it in 3D
for reference.
Setup of test scene
First task to solve was to think of scene setup
basic enough to prepare in virtual and real
world.
I decided to use scene, where airplane
trajectory is set as straight line at fixed
distance from background wall. Whole scene
is captured by camera looking at the object
from side.
With this in mind, I could attempt to perform
test in real world first. As my model for
unknown reason did not featured
antigravitation device, I had to help it with
wooden beam, attached to positioning
device, where digital camera also lied. I set
up camera to take single picture during 1/30s
time. Longer time was chosen because else it
would be hard to get the blur effect.
During the shooting, the positioning device
was set to constant speed, so we got sharp
airplane and blurred background. You can
see the result on attached picture.
- 31 -
ARTICLES
Photo of real model
1/30s time exposure can be approximated in
script via capturing 8 frames during 4 ms
interval. Then it was just needed to move
aircraft at the same speed as real model, and
we could finally see the resultant rendering.
Screenshot from the testing script
Summary
We got some motion blur finally - the
approach described here is compatible with
older hardware, but thanks to some
branching it allows to take advantage of new
cards as well. It does not depend on the
amount of geometry rendered as all blurring
is done using simple 2D transformation.
Thanks to the same fact it does not require to
have any information on object speeds.
With one eye closed and with finger in other
we can say that the real world and rendered
images are similar up to some level, and
therefore our experiment ends with success.
Summary seems quite optimistic so far, to be
correct I must admit it has few problems as
well.
The main issue is related to use of blending.
This algorithm is quite fill rate hungry,
which means there is noticeable performance
hit with growing resolution.
On high end cards it won’t be noticeable, but
especially on “value” cards and onboard
chips you can notice the dependency speed-
resolution can be cruel. This side effect can
be eliminated by lowering the number of
textures we render to, sacrificing final
quality.
The result of this approach you can see on
the front page of this issue as well. If you are
interested in full source code including the
scene, you can get it on ThinBasic forum as
usual. As the script relies on latest version of
TBGL module, you will need at least
ThinBasic 1.7.0.0 to use it.
- 32 -
INSIDER
Insider
If you ever wondered how
ThinBasic works inside, or how the
modules are developed, this new
section will satisfy your needs.
Support for module development is
one of the key features of
ThinBasic as a such, after reading
through articles in this section you
will be able to develop your own
module and customize ThinBasic to
suit your needs best.
ThinBasic inside
Eros Olmi
This is the first of a group of articles where we will discuss
about how thinBasic works from an engine and module point
of view. We have repeated many times that thinBasic is a
modular language. But what does modular language mean? We
will see in depth what this means and how thinBasic modules
work together allowing:
1. extreme flexibility in developing thinBasic applications
2. possibility for 3rd
party developers to create
personalized modules
3. improve thinBasic functionalities with a distributed
development concept
Figure 1.1: main thinBasic infrastructure
Figure 1.1 summarizes thinBasic modular software
architecture. There are basically 3 software layers each with
different duties.
- 33 -
INSIDER
1.1 Application layer
It is the execution process, the main application. This layer must accomplish 3 steps:
initialize the underline Core layer,
tells to Core Layer which thinBasic application (we will call it “script” for the rest of
the article) to execute
de-initialize the Core engine.
ThinBasic provides 2 different applications to complete the above steps:
thinBasic.exe (a 32bit GUI - Graphical User Interface – application)
thinBasicC.exe (a 32bit Console – text mode – application featuring standard input,
output)
We will see in future articles that the application layer process can be substituted by
any 3rd
party application able to call dll functions.
1.2 Core layer
This is the most important part of thinBasic programming language. It is the glue that keeps
together all the different parts that play a role in thinBasic execution. It contains the main
thinBasic Core language and is in charge of:
load and parse the script
manage all memory handling (allocation / de-allocation of variables, function stacks,
internal structures in place for module handling)
expose a series of functions interface used by modules to communicate with main
thinBasic engine
module handling on request (find the correct module dll, opening it, link the keywords with
the module functions, call functions implemented by modules, releasing modules when
needed)
This layer is inside a single dll: thinCore.dll. You can consider this dll the heart of thinBasic
language.
1.3 Module layer
Here we are: Module Layer. This layer is a set of official or user modules developed by
thinBasic development team or by any thinBasic user using thinBasic SDK. A module is a
single dll containing a set of functionalities that are loaded at script run-time when the Core
Layer requests for them.
In thinBasic terminology “module” term is used instead of “dll” mainly because a module is a
special dll that must follow some rules we will se in future articles. For the moment it is
sufficient to know that a thinBasic module must export 2 specific functions:
LoadLocalSymbols: this function must be mandatory exported by every thinBasic module
and is called automatically by Core Layer when the module is loaded. This function
serves two main reasons: initialize any data needed by the module, let Core Layer know
about new functionalities developed inside the module
- 34 -
INSIDER
UnLoadLocalSymbols: this function is not mandatory. It will be called automatically by
Core Layer when module is released so it can contains any code needed to de-initialize
module data (if any)
2. Script execution
Look Figure 2.1. The following is a step-by-step description of what happen when a script
(that doesn’t need any module) is executed:
1. Main Process loads thinCore.dll and calls thinBasic_Init function. This function
prepares the Core Layer for a script execution. It is a quite complex step. All keywords
implemented by thinBasic Core engine are linked to the relevant compiled function in
charge to manage them. Internal memory structures are prepared to receive execution,
variables, stacks
2. Main Process calls thinBasic_Run function passing the name of the script to be
executed plus a series of other parameters used to check that the Main Process is
authorized to execute a script. Here control is passed to the Core Engine and will
remain there till the end of the execution process
3. Core Layer makes a first parsing pass: pre-parsing. This step is targeted to resolve
some script declarations. In particular three aspects are important in this phase:
identification of needed modules (in this example no modules are needed),
identification of UDT (user defined types) and identification of Functions and Subs
(including any function parameters)
4. Real script execution starts. Every token (sequence of chars, a keyword, a quoted
string, a number, …) is parsed, recognized, executed
5. Execution ends. Core engine stops it and pass control back to Main Process
6. Main Process calls thinBasic_Release function. This function de-allocates all memory
structures used for script execution, releases used memory, releases modules (not in
this example) and ends the process.
Case 2: a script asking to use a module, in this example FILE module, because a functionality
to load file content into a string buffer is needed.
- 35 -
INSIDER
Figure 2.2: logic execution flow of a script that needs a thinBasic module.
Look Figure 2.2. The following is a step-by-step description of what happen when a script
that needs a module is executed (description of steps covered in above Case 1 will not be
repeated here):
1. Main Process loads thinCore.dll and calls thinBasic_Init function.
2. Main Process calls thinBasic_Run function
3. Core Layer makes a first parsing pass: pre-parsing.
4. During pre-parsing, Core Engine has identified that script needs a module named
“FILE”. To request a module is very simple: USES keyword followed by a string
expression representing the module name, that’s all. ThinBasic will identify module
dll on disk and will load it. How thinBasic searches and finds modules will be
covered in chapter 3
5. Immediately after, Core Engine will call the special function LoadLocalSymbolds
that MUST be present in any thinBasic module. This function, among other usage, is
in change to instruct Core Engine about the new keywords the module implements,
how to call them, what kind of parameter the keyword return
6. Real script execution starts. Every token (sequence of chars, a keyword, a quoted
string, a number, …) is parsed, recognized, executed
- 36 -
INSIDER
7. Execution ends. Core engine stops it and pass control back to Main Process
8. Main Process calls thinBasic_Release function
9. Because a module was loaded, thinBasic_Release, will check if the loaded module
have exported a function called UnLoadLocalSymbol. If yes, it will be invoked by
Core Engine. This process is repeated for all the modules loaded. After this step is
performed, process is ended.
There are thinBasic scripts that do not require any module (Case 1) or there are scripts that
require just one module (Case 2) but there are scripts that can require many different modules.
In this case the process is exactly like the one described into Case 2 but repeated for every
single module the script needs.
3. Where are thinBasic modules located and how Core Engine finds them?
During thinBasic installation process, all thinBasic modules are placed into \thinBasic\Lib\
directory. So you can assume the above directory as the default one for all thinBasic modules.
But, in order to simplify thinBasic programming and avoid what was known DLL hell (see
Wikipedia at http://en.wikipedia.org/wiki/DLL_hell, thinBasic modules are, at the end, just
standard DLLs) that directory is not the only one checked by thinBasic engine when it search
for modules to load. The following table summarize the sequence used by thinBasic when
main Core engine is asked to load a module.
To understand path examples, imagine your script is located into C:\MyWorks\tbScripts\
directory, thinBasic is installed in C:\thinBasic\ directory and script is trying to load FILE
module with the following code: USES "FILE"
Sequence Path used for module search Example
1 Source script path C:\MyWorks\tbScripts\
2 Source script path + Lib C:\MyWorks\tbScripts\Lib\
3 Source script path + Bin C:\MyWorks\tbScripts\Bin\
4 Source script path + Mod C:\MyWorks\tbScripts\Mod\
5 Source script path + Lib + "thinBasic_" +
ModuleName
C:\MyWorks\tbScripts\Lib\thinBasic_File\
6 Source script path + Bin + "thinBasic_" +
ModuleName
C:\MyWorks\tbScripts\Bin\thinBasic_File\
7 Source script path + Mod + "thinBasic_" +
ModuleName
C:\MyWorks\tbScripts\Mod\thinBasic_File\
11 thinBasic path C:\thinBasic\
12 thinBasic path + Lib C:\thinBasic\Lib\
13 thinBasic path + Bin C:\thinBasic\Bin\
14 thinBasic path + Mod C:\thinBasic\Mod\
15 thinBasic path + Lib + "thinBasic_" +
ModuleName
C:\thinBasic\Lib\thinBasic_File\
16 thinBasic path + Bin + "thinBasic_" +
ModuleName
C:\thinBasic\Bin\thinBasic_File\
17 thinBasic path + Mod + "thinBasic_" +
ModuleName
C:\thinBasic\Mod\thinBasic_File\
Following the above sequence, thinBasic Core engine will use the first path where the
requested module will be found. This method adds precision and freedom at the same time.
Precision because programmer exactly knows what thinBasic does internally and because
- 37 -
INSIDER
sequence gives higher priority to the script path instead to the interpreter path. Freedom
because there are many options to choose from.
4. Developing a thinBasic module: thinBasic SDK
So, we have seen how a thinBasic modules work, how they interact with thinBasic Core
Layer. But how to develop a thinBasic module? Next TBJ issues will cover this part in depth
showing how to develop a thinBasic module using different programming languages. In this
issue we will just have an overview at what we mean by “thinBasic SDK” (thinBasic
Software Development Kit).
ThinBasic SDK is a set of functionalities exposed by thinBasic Core Layer (thinCore.dll)
meant to allow programmers to add new keywords to thinBasic language. ThinBasic SDK not
only let you say what keyword you want to add but will let you “parse” source code the way
you prefer without any limitation.
Let’s have a look at a possible real example.
Imagine you want to develop a module that implements a new keyword whose name is
MyReverseMID$ and this new function has to extract a piece of text from a string reversing its
content. Now you decide that the new keyword will have the following syntax:
OutString = MyReverseMID$ ( InString, PosStart, ByLen )
where InString will be the input string to work with, PosStart will be the first byte from
which to start extracting the string and ByLen will be the number of bytes to keep.
As you can see we have decided the name of the new keyword, how many parameters it will
have, and the type of parameters (one string and two numbers). We have decided that
parameters must be enclosed into parenthesis but we could have decided that no parentheses
are needed or just optional. The complete syntax is up to you (as programmer). No
impositions will be forced by thinBasic in choosing how to develop your keyword.
Now let’s see what thinBasic SDK gives to the programmer to be able to parse the above
syntax. The first pass is to identify each of the tokens that play a role in the syntax. We have
to think that one of the very first steps thinBasic performs when looking at source code is
splitting it into pieces called tokens. Each token is than analyzed ad resolved at script runtime.
The following is a PowerBasic example of the parsing process of the syntax we have
previously decided: first column have each of the tokens.
- 38 -
INSIDER
Syntax broken
into single
tokens
Line PowerBasic source code that would be present in an hypothetical
user module
( 1 If thinBasic_CheckOpenParens_Mandatory then
InString 2 thinBasic_ParseString InString
, 3 if thinBasic_CheckComma_Mandatory then
PosStart 4 thinBasic_ParseNumber PosStart
, 5 if thinBasic_CheckComma_Mandatory then
ByLen 6 thinBasic_ParseNumber ByLen
) 7 If thinBasic_CheckCloseParens_Mandatory then
8 ‘---Do whatever job with the variables
Function = strreverse$(mid$(InString, PosStart, ByLen))
9 End if
10 End if
11 End if
12 End if
We can note immediately some important points:
there is a one to one link between the single syntax tokens and the
programming steps needed to parse them
for each token type present in the syntax (delimiters or numeric or string
expressions) thinBasic SDK has the right interface function to handle it
very human understandable interface function names will keep your
development easy to read and to maintain
whatever complex will be the code written inside the thinBasic script using
your brand new keyword, you code will be the same. InString will be parsed
exactly the same regardless it will be a single variable, a quoted string or a
complex string expression. The same for all other numeric parameters. Your
module code will be the same. thinBasic Core Engine will take care of all the
steps needed to give you one string or one number or whatever other
expression
- 39 -
PROGGIES
Proggies
This new section will inform you
on topics related to software created
with ThinBasic.
You will find here both
presentations of new software and
description of their functionality.
First program which made it into
this section is famous TAB,
program to author text adventures.
It is one of the biggest applications
made in ThinBasic up to date, and
in the last time it is experiencing
increased interest, resulting in
growing user community.
Understanding adventure coding in ThinBASIC
Adventure Builder
Philip Richmond
TAB is a text adventure/interactive fiction maker for Windows
in Alpha Development and made with thinBASIC.
Homepage: http://tab.thinbasic.com/
TAB consists of two programs:
The Editor: Create, build and test your text adventure.
The Player: Standalone distributable to play your
finished adventures.
The Editor allows you to enter the various types of data the
adventure requires such as Locations, Objects, Characters,
Messages, Game Settings and Vocabulary and so on...
More importantly it is in here that you can also construct the
responses to player commands and build the many puzzles,
problems and quests that the player will encounter as he/she
journeys through the game world.
This is done by using a condition/action language and by
devising appropriate coding entries according to a strict format
and syntax.
It is a little like simple if/then constructs.
The format of each coding entry must contain 3 tags enclosed
in square brackets:
[start]......[acts].......[end]
After a [start] tag you must enter the conditions that make the
entry work.
The first condition in a Response entry is always a words
condition.
If there are any other conditions required then the # (hash
symbol) is used to separate them.
When the required entry conditions have been typed in the
[acts] tag is inserted.
After this any action commands are listed.
If there is more than one action then the # (hash symbol) is
used to separate them.
- 40 -
PROGGIES
When the entry actions are finished, the
entry can be closed with an [end] tag.
Example entry format:
[start]words_conditions#condition2#c
ondition3[acts]action1#action2[end]
Roughly translated this means:
If the words conditions match and condition
1 is true and condition 2 is true then do
action 1 then do action 2 then exit the coding
list.
Recently introduced for more flexibility and
power is the extension to use else if and then
if.
Example using else and then coding format:
[start]words
conditions#condition#condition[acts]
action#action#else#condition#conditi
on[acts]action#action#else#condition
#condition[acts]action#action#else#[
acts]action#then#condition[acts]acti
on[end]
Therefore you can have multiple conditions
and actions in respect of the same starting
"words conditions".
This can save writing several individual
entries to build up a particular game puzzle
and also cuts down on typing too!
Think of action "#else#" as meaning else if.
The last use of action "#else#" in this
example shows how to do an else do type of
statement.
In other words if none of the previous entry
conditions and actions were executed then
this default action in respect of the valid
words conditions will always be done.
Notice there are no conditions before the
[acts] tag for this "catch-all" usage of
"#else#"
TAB also allows all conditions to be
prefixed by "or_" for or checking...
Example using or conditions:
[start]words
conditions#or_condition#or_condition
#condition[acts]action#action#else#c
ondition#condition[acts]action#then#
or_condition#or_condition[acts]actio
n[end]
In this second example the "then" action is
only carried out if the set of actions
following the "else" were done.
I'll finish this article by providing 2 actual
code snippets from the tutorial game in
TAB.
[start]eat
sandwich#here1[acts]destroy1#cmessYu
m Yum... You greedily consume the
egg sandwich.[end]
[start]eat
sandwich#absent1[acts]cmessYou don't
seem to own a sandwich![end]
You can probably figure out what these
entries do and they are quite simple to create
and type in. They could also be combined
using #else#, like so:
[start]eat
sandwich#here1[acts]destroy1#cmessYu
mYum... You greedily consume the egg
sandwich.#else#absent1[acts]cmessYou
don't seem to own a sandwich![end]
- 41 -
BITS AND PIECES
Bits and pieces
This section shows various quick
hints and brief tips on ThinBasic
usage.
While topics discussed here are not
big enough for complete article,
hints posted here can make your
programming more elegant and
efficient.
Alias keyword
Michael Clease
A keyword for a keyword, although flexible it cannot change
the way a keyword is called just what it is called.
Let’s use the FILE_OPEN keyword from the FILE module
n = FILE_OPEN(FileName, Mode, [RecordSize])
Now my Alias
ALIAS FILE_OPEN AS OpenFile
n = OpenFile(FileName, Mode, [RecordSize])
As you can see the keyword is now OpenFile but the
parameters are unchanged, that is very important to
remember.
Alias can also be used to make keywords smaller thus
making scripts smaller
Alias QueryPerformanceFrequency AS QPF
Alias QueryPerformanceCounter AS QPC
Alias HiResTimer_Get AS HiTimeGet
Casting data type
Petr Schreiber
When you need to specify precision of numeric literals, most
BASICs provide mechanism where you add some special
character after literal, like “!” or “#”.
ThinBasic provides slightly different approach. It is a bit
wordier, but clear as it doesn’t make numeric expression
look like swearword from comics.
You simply enclose numeric literal or even expression into
brackets, and add AS <type>.
Here is little example:
howMany = 5 + ( variable * 5.5 ) AS LONG