APOCALYX 3D Engine1
The Book
by
Leonardo Boselli2
Copyright c 2006 Leonardo Boselli. All right reserved.
Version 0.8.7 of July 20, 2006
1
2
internet: http://apocalyx.sourceforge.net
e-mail: tetractys@users.sourceforge.net
2
Figure 1: APOCALYX Logo
Abstract
APOCALYX is mainly a LUA1 scripted 3D engine that includes several libraries
useful for the development of games or simulations.
The chapters of this book describe in a detailed way some aspects of the
engine and provide useful information for the correct use of some of its features,
but for a complete description of all the capabilities of the engine refer to the
manual2 . The descriptions of the manual are very brief but every available
function is at least cited. On the contrary, the aim of this book is to provide
ideas, suggestions and useful tips to apply at best the available features.
I begin by describing the configuration and the very basic capabilities of the
engine. Then, considering some typical applications, I analyze more deeply several aspects of game development. Finally, some advanced topics are discussed
always with practical examples.
The developed arguments are suitable for a wider audience, from very beginners to a bit more experienced programmers, even without specific knowledge
in game development. The APOCALYX engine here described, thanks to the
included programming languages, both scripted or compiled, and the built-in
multi-purpose libraries, is tailored for a large number of different applications.
The user’s programming skills can take advantage of the very simple to manage
scripting languages, but also can refine the code where high performances are
necessary through compilation or even including assembled machine instructions. Of particular interest are the third-party libraries already included in the
engine.
In conclusion, the most interesting thing developed in this book is not in the
description of the engine itself, but in the clear explanation of the pieces that
build up a game application. Thus the main effort is devoted in the analysis of
the practical examples, from the first ideas, through the technical developments,
to the final refinements.
I hope that the described topics are going to be useful, especially for beginners, to program their first games with less effort than is usually expected: A
small step towards a more advanced approach in the game development.
1 visit http://www.lua.org for more details about the LUA language
2 open
the file APOCALYX-manual-index.htm distributed with the engine package at
http://apocalyx.sourceforge.net
Contents
1 Introduction
1.1 APOCALYX . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Programming Games . . . . . . . . . . . . . . . . . . . . . . . . .
1.2.1 A Bit of History . . . . . . . . . . . . . . . . . . . . . . .
1.2.2 Why SMALL Language Scripts? . . . . . . . . . . . . . .
1.2.3 Brief Description of the Game . . . . . . . . . . . . . . . .
1.2.4 The Story So Far... . . . . . . . . . . . . . . . . . . . . . .
1.3 Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4 Downloads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.1 The Engine . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2 Demos . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3 Games . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.5 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
8
9
10
11
14
16
21
21
21
22
23
I
25
Basic Topics
2 The Game Loop
27
2.1 The Scene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.1 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.2 Finalization . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.3 The Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.1.4 Keys Pressed . . . . . . . . . . . . . . . . . . . . . . . . . 27
3 Backgrounds
31
4 Overlays
35
5 World Views and Viewports
39
6 Meshes
53
6.1 Dynamic Meshes . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.2 Mesh Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
7 Materials
59
7.1 Diffuse, Gloss and Environment Texture . . . . . . . . . . . . . . 59
7.2 Old-style Bump Mapping . . . . . . . . . . . . . . . . . . . . . . 59
8 The Particle System
63
3
4
CONTENTS
9 Model Loading and Animation
69
9.1 MD3 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
9.1.1 Comments to the ModelMD3.lua source . . . . . . . . . 69
9.1.2 Preparing MD3 models for loading . . . . . . . . . . . . . 80
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 80
Preparing the MD3 files . . . . . . . . . . . . . . . . . . . 80
The MDX Format . . . . . . . . . . . . . . . . . . . . . . 81
9.2 MD2 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9.2.1 Comments to the ModelMD2.lua source . . . . . . . . . 82
9.2.2 Preparing MD2 models for loading . . . . . . . . . . . . . 85
9.3 Cal3D Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.3.1 Comments to the ModelCal3D.lua source . . . . . . . . 86
9.3.2 Preparing Cal3D models for loading . . . . . . . . . . . . 89
10 Levels Loading
91
10.1 BSP Levels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.1.1 Comments to the BspLevel.lua source . . . . . . . . . . 91
10.1.2 Notes on Quake3 BSP Levels Editing . . . . . . . . . . . 101
10.1.3 The BSX Format . . . . . . . . . . . . . . . . . . . . . . . 102
10.2 Outdoor Levels . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.2.1 Terrains . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.2.2 Height Fields . . . . . . . . . . . . . . . . . . . . . . . . . 107
10.2.3 Patches . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
11 Particle-Based Physics Simulator
141
11.1 Fundamental Principles . . . . . . . . . . . . . . . . . . . . . . . 141
11.2 Environment Interaction . . . . . . . . . . . . . . . . . . . . . . . 146
11.2.1 Obstructions . . . . . . . . . . . . . . . . . . . . . . . . . 146
11.2.2 Flag Waver . . . . . . . . . . . . . . . . . . . . . . . . . . 151
11.3 Other Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
12 3D Sound and Sound Capture
185
12.1 3D Sound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
12.2 Sound Capture . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
II
Advanced Topics
187
13 Vertex and Fragment Programs
189
13.1 Reflective and Refractive Sphere . . . . . . . . . . . . . . . . . . 189
13.1.1 Theoretical Introduction
Refraction in Glass Spheres . . . . . . . . . . . . . . . . . 189
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 189
Light Propagation Laws . . . . . . . . . . . . . . . . . . . 190
A Simplified Model . . . . . . . . . . . . . . . . . . . . . . 191
Light through a Sphere . . . . . . . . . . . . . . . . . . . 192
The position of the fragment . . . . . . . . . . . . . . . . 192
13.1.2 The intersection with the sphere . . . . . . . . . . . . . . 192
Apply the reflection law . . . . . . . . . . . . . . . . . . . 193
Apply the refraction law . . . . . . . . . . . . . . . . . . . 194
CONTENTS
5
Apply the refraction law again . . . . . . . . . . . . . . . 194
The Fragment Program . . . . . . . . . . . . . . . . . . . 195
Final Results . . . . . . . . . . . . . . . . . . . . . . . . . 195
13.1.3 The Script . . . . . . . . . . . . . . . . . . . . . . . . . . 196
13.1.4 The Fragment Program . . . . . . . . . . . . . . . . . . . 201
14 Vertex and Fragment Shaders
207
15 Collision Detection and Physics
209
15.1 ColDet Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
15.2 ODE Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
16 Interpreters and Compilers
219
16.1 TinyC Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
16.1.1 C Functions in the Game Loop . . . . . . . . . . . . . . . 219
LUA Script . . . . . . . . . . . . . . . . . . . . . . . . . . 219
C Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
16.1.2 Loading DLLs . . . . . . . . . . . . . . . . . . . . . . . . 223
LUA Script . . . . . . . . . . . . . . . . . . . . . . . . . . 223
C Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
16.2 AngelScript Interpreter . . . . . . . . . . . . . . . . . . . . . . . 225
16.3 Csl Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
16.4 SMALL Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . 228
17 Artificial Intelligence
229
17.1 Finite State Machines . . . . . . . . . . . . . . . . . . . . . . . . 229
17.2 Path Finding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
17.3 Steering Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . 234
18 Networking
241
18.1 Basic Communications . . . . . . . . . . . . . . . . . . . . . . . . 241
18.2 UDP Protocol with RakNet . . . . . . . . . . . . . . . . . . . . . 242
18.2.1 The Server . . . . . . . . . . . . . . . . . . . . . . . . . . 242
18.2.2 The Client . . . . . . . . . . . . . . . . . . . . . . . . . . 243
18.3 Voice over IP with RakNet . . . . . . . . . . . . . . . . . . . . . 244
18.3.1 The Server . . . . . . . . . . . . . . . . . . . . . . . . . . 244
18.3.2 The Client . . . . . . . . . . . . . . . . . . . . . . . . . . 246
III
Complex Examples
249
19 Urban Tactics
251
20 Hoverjet Racing
285
21 Zeke on Your Six!
333
22 Dragon’s Ride
359
6
CONTENTS
A Legal Stuff
389
A.1 Legal disclaimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
A.2 Terms for use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
A.3 Libraries and Tools Copyrights . . . . . . . . . . . . . . . . . . . 389
A.4 Donations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
A.5 Contact information . . . . . . . . . . . . . . . . . . . . . . . . . 396
Chapter 1
Introduction
1.1
APOCALYX
APOCALYX is a 3D engine based on OpenGL, OpenAL and other free
libraries suitable in particular for the development of Programming Games, the
genre of games like GUN-TACTYX1 or JROBOTS2 , where the player programs
the AI of his own team of bots to make it fight against teams written by other
players.
about
APOCALYX
Figure 1.1: GUN-TACTYX Programming Game
The engine already includes features such as the rendering of sky boxes,
cloud layers, lens flares, flat terrains and height fields even with reflections, infinite terrains, wavy surfaces, sprites, billboards, meshes with
diffuse, gloss, environment and bump mapping, particles emitters, planar shadows, the loading of 3DS and OBJ meshes, the animation of MD2,
MD3 and Cal3D models, the loading of BSP levels with collision detection and lightmaps, the support for OpenGL vertex and fragment programs and the support of the GLSL language for vertex and fragment
shaders, the playback of MIDI and MP3 soundtracks, the spatialization of
3D sound, the capture of sound samples, the management of compressed
1 visit the site http://guntactyx.gameprog.it for more details about GUN-TACTYX
2 visit the site http://jrobots.sourceforge.net for more details about JROBOTS
7
main features
8
AI interface
LUA scripts
open source
CHAPTER 1. INTRODUCTION
data and a particle-based physics engine suitable for the simulation of flags,
cloths and articulated, soft or rigid bodies.
The engine also includes and provides easy access to libraries such as the ODE
physics engine, the ColDet Collision Detector, the Cal3D Animation
Library, the LUA scripting language to describe and control the scenes
without any recompilation, the Tiny C compiler for fast compilation of C
time-critical code and the C Scripting Language, the AngelScript Language and the SMALL language for fine control of bots’ AI.
Then, to help the development of interesting AI, the engine also uses the MicroPather library (for A* path finding algorithms), the OpenSteer library
(for the implementation of steering behaviors) and a library to define and
control finite state machines.
The engine is completely scripted and no recompilation is needed to create
new contents. You must simply program your scripts using the LUA language,
or other languages available, and add your media files.
The current implementation of the engine supports Win98 (or higher) with an
accelerated graphic card and OpenGL drivers installed (the more recent the
better).
Even if normal users can completely program the engine with scripts, also
the C++ sources are provided for the needs of power users. They can be fully
recompiled under the Code::Blocks IDE3 with the Free Borland C++
Command Line Compiler 5.5.14 installed.
Both the tools are completely free. You should download the Code::Blocks
version without the default compiler installed, then you must download the
Free Borland C++ Command Line Compiler 5.5.1 from Borland. The complete
installation istructions are included in the documentation of Code::Blocks.
1.2
What are
Programming
Games?
Programming Games
In the previous section I cited the genre of games know as Programming Games.
To provide a background to the engine, motivating the reason why it was developed, here I explain what Programming Games are and describe in particular
one of them, GUN-TACTYX.
Readers interested only in engine should skip the next paragraphs and move
directly to the in chapter 1.3 at page 16.
Figure 1.2: GUN-TACTYX Programming Game
3 to know more about this interesting IDE visit the site http://www.codeblocks.org
4 visit http://www.borland.com/bcppbuilder/freecompiler/ to download the compiler
1.2. PROGRAMMING GAMES
1.2.1
9
A Bit of History
GUN-TACTYX (read it like Gun Tactics) is a ¨CROBOTS-like game with
QUAKE3-style graphics¨. It belongs to the genre of Programming Games like
Tom Poindexter’s CROBOTS, an old 1985 DOS game, where some robots
fight in an arena, firing missiles and avoiding enemies’ projectiles. Such kind of
games is involving, but is not interactive, in fact the player must develop the AI
algorithms of his own robot using a programming language and then can follow
its fights against other robots, possibly written by other players.
A great deal of these kind of games is available for download on the internet,
most of them for free. An on-line example is my JROBOTS5 , now at SourceForge but previously hosted by CFXweb.net since year 2000, that is a clone
of CROBOTS with interesting features. While a player of CROBOTS had to
use a subset of C to program his robot (using only integer math because of the
limitations of 1985 PCs), a player of JROBOTS uses the Java language and the
robot consists of a single Java class6 for more details about the Java language).
A player can upload the corresponding file to the on-line arena to compete in
monthly tournaments against robots developed by other players.
One of the most appealing characteristics of JROBOTS is the cooperative play,
in fact, while the original CROBOTS had only one-against-all kind of matches,
JROBOTS supports matches among team of robots, from doubles to four teams
of eight robots each at a time.
During the past tournaments, since April 2000, a large number of interesting jrobots were uploaded online and featured several different strategies that
evolved with time. Some time ago (September 2003) the arena was split in
two categories, veterans and cadets, and the major league was dominated by
IonStorm, the strongest and more tuned jrobot ever seen in the arena, written
by Alan Lund from Eau Claire, WI, USA. Other remarkable jrobots are KillerBees, written by Walter Nisticò from Italy, that dominated the pre-IonStorm
era, while more recently appeared Tango, written by Caos from Argentina.
The story does not stop here and further brilliant exploits are expected for the
future of JROBOTS.
Even if JROBOTS is a great game in its genre, it has some limitations.
First of all, for security reasons the players can’t use any of the Java APIs,
because the tournaments run on-line in Java applets hosted by the browser of
the visitors. The latter could be a feature of the game, just like the reduced size
(20Kb) of the class file that contains the code of the robots and other rules of
the same kind that makes the game more difficult for the players, but another
issue menaced the reliability of the game since the early beginning.
Every robot loaded in the arena has a Java thread assigned that runs its code.
The environment of the Java Virtual Machine doesn’t provide any effective tool
to control the execution of Java threads, so different CPUs, OPSYSes and JVMs
ran the simulation of the matches more or less differently favoring from time to
time one or the other jrobot and invalidating the reliability of any testing phase
conducted on a single machine. The problem was caused by the thread-based
design of the game, rather than turn-based as other game of this kind are, but
fortunately a solution was found thanks to some of the most effective JROBOTS
5 visit the site http://jrobots.sourceforge.net for more details about JROBOTS
6 visit http://www.javasoft.com
CROBOTS
JROBOTS
KillerBees,
IonStorm
and Tango
JROBOTS
limitations
10
CHAPTER 1. INTRODUCTION
players7 . They developed a ¨Virtual Clock Generator¨ that ticks the time of
the simulations and smoothes their evoution on different machines.
Anyway, since the beginning I wished to develop another game with the
strongest points of JROBOTS, but at the same time graphically appealing,
with an easy to control execution environment and a more realistic simulator.
After a long list of failed attempts, the answer to my wishes seems to be GUNTACTYX.
To get the complete history of JROBOTS and more recent news or to
participate to the monthly on-line tournaments, visit its site at SourceForge
(http://jrobots.sourceforge.net).
1.2.2
SMALL language
step by step instructions
and memory management
the console
Why SMALL Language Scripts?
In GUN-TACTYX the player must develop the AI algorithms of his team of
bots using the SMALL scripting language. This paragraph describes briefly the
use of SMALL to create scripts that control the bots’ behavior.
The SMALL language is a powerful scripting language easily extendable and
embeddable in other applications. Here the description of SMALL is limited
to the features that are useful to create GUN-TACTYX scripts, but plenty of
other possibilities are available8 .
A SMALL interpreter can run instruction by instruction the bytecode generated by a compiler and provides a fine control of the execution. It is very easy
to execute the code for any amount of ticks of (virtual) CPU clock, suspend the
execution starting the run of other code, resume it and simulate the heaviness
(in clock ticks) of particular functions. As a consequence, all the bots of the
game use the same amount of time to perform the same actions, saving the
reliability of the simulation. Turn-based mechanisms, such as voluntary calls to
functions that release the execution of the bot’s code, are no more needed.
Another advantage of a SMALL interpreter consists in the memory management. Every bot has a fixed amount of memory to store all the information that
it needs (stack and heap space). It is no more possible for a bot to use large sizes
of the application memory, mining the reliability of the main program itself.
The SMALL compiler (¨sc.exe¨) translates sources written in a C-like language (¨.sma¨ extension) in a particular bytecode (¨.amx¨ extension). That
kind of C-like language is very easy to learn for people who already knows Java,
C, C++, C# or other programming languages, and may introduce inexperienced
people to procedural programming.
GUN-TACTYX includes a command-line console from which you can compile and execute (GUN-TACTYX unspecific) SMALL scripts. To activate the
console, press the ’F3’ key during the execution of the application. When a
script is in execution, you can read all the messages generated on the console.
The available commands are:
• h : to read the complete list of the commands
• d : to list files in current directory
• d dir name : to change directory
7 they are the already cited Walter Nisticò in collaboration with Alan Lund
8 to
know more about
http://www.compuphase.com
SMALL
visit
the
site
of
ITB
CompuPhase
at
1.2. PROGRAMMING GAMES
11
• p : to list SMALL scripts in directory
• e file name : to edit a text file.
• x file name : to execute a SMALL script
• s file name : to compile a script (¨.sma¨ extension). The output has
¨.amx¨ extension. Remember that SMALL scripts need to be compiled
before execution. You can also use the command line compiler ¨sc.exe¨
included in the ¨\bots¨ folder.
• g file name : to execute external commands.
1.2.3
Brief Description of the Game
The game consists in a fight among teams (from 2 up to 4) of warriors (from 64
bots down to 1). The warriors move in a complex environment with walls that
may be flat or include ramps, bridges and two or more floors. A warrior slides
along the walls maintaining its movement attitude, but it suddenly stops when
hits another warrior.
Warriors have a brain that consists in a CPU with a clock frequency of 10KHz
and a RAM with a memory size of 32Kb. They are equipped with one gun
that fires bullets and grenades. The number of bullets loaded at the beginning
is 50 with a maximum of 100, while grenades are limited to a maximum load
of 3 and initially the gun has only one of them. Recharges are provided on the
battlefield, as well as barrels of bullets.
Every warrior has an initial amount of health, energy and armor. Health starts
from the maximum, 100, and decreases when the warrior hits a bullet, 50 points
per hit, or a grenade explodes nearby. In the latter case the health lost depends
on the distance from the explosion up to 5 meters. When a warrior reach zero or
less health, it is disabled. Energy starts from 50, while the maximum is 100. It
decreases 2 units per second when the warrior runs, increases 1 unit per second
when the warrior is standing or is crouched and keep its current value when the
bot walks forward, backward or crouched. When the energy reaches zero, the
warrior can’t run, but can still walk. The Armor value starts from 0, while the
maximum is 100. Armor gives protection from hits decreasing the damage proportionally to its value, but every hit reduces at the same time the armor value.
Recharges for health (medikits), energy (food) and armor (full-metal jackets) are
available on the battlefield. When a recharge is raised by a bot, it disappears
from the battlefield but is respawned after a certain amount of time.
Warriors may rotate their forward direction around their vertical axes, rotate
and bend the torso up to specific values, rotate and bend the head up to specific values. The gun and its aim device are linked to the torso and follow its
movement, while other sensors to watch and hear and the device to speak are
linked to the head.
Among the others, some of the main functions available to warriors to interact
with each other and with the environment are:
• bool:say(int:word)
used to communicate with other bots. A warrior can ¨say¨ a word, that
corresponds to an integer value, every half a second.
the rules
the interface with
the environment
12
CHAPTER 1. INTRODUCTION
• float:hear(&item,&sound,&float:yaw,&float:pitch,&float:id)
useful to hear gun shots, grenade explosions or words spoken by nearby
warriors. The nearest sound is reported with its type, distance and direction (yaw and pitch angles). Every ¨hear¨ takes 0.04 seconds.
• float:sight()
used to get the distance of the walls in the direction in front of the head.
Every ¨sight¨ takes 0.01 seconds.
• float:aim(&item)
the same as sight(), but gets the distance of the walls in the direction of
the gun or the distance of the nearest warrior between the gun and the
wall using a sort of laser beam (the aim device). Every ¨aim¨ takes 0.04
seconds.
• int:watch(&item,&float:dist,&float:yaw,&float:pitch,&float:id)
useful to see the objects, not walls, around the warrior. Only the nearest
object farther than a given distance is reported with its type, distance and
direction. An object must be in front of the head within a characteristic
angle of view (60 degrees or PI/3 radians) to be seen. Every ¨watch¨ takes
0.04 seconds.
A match lasts for a certain duration (from 1 minute up to 1 hour) and this and
other values may be changed by the user. There are several condition for victory
the first of which can be selected by the player. They apply in the following
order:
conditions
for victory
The winner is the team that...
1. a) ¨terminates the enemy chiefs¨, that are the warriors that carry the
team sign. All the chiefs of the enemy teams must be disabled. Chiefs
can’t drop the sign so they are easily recognizable and their team mates
must protect them, while they also try to hit enemies.
b) ¨terminates the enemy teams¨. All the warriors of the enemy teams
must be disabled to win. When this goal is active the chiefs does not hold
a sign but a weapon.
c) ¨captures the enemy sign¨. The warriors of a single team must capture
the sign of all the other teams to win, or alternatively disable all the enemies. The chiefs hold a sign at the beginning but can drop it to raise a
weapon.
or, if the timeout is reached, ...
2. has more team mates at the end of the match
3. has terminated more enemies at the end of the match
4. has more overall health at the end of the match
1.2. PROGRAMMING GAMES
13
If none of the above conditions matches, the match resolves in a tie.
The winning conditions described above apply to 3 different modes of play:
Fight mode, Soccer mode and Race mode.
1. Fight mode and its characteristics were already explained at the beginning of this section. Warriors hold a weapon so they can shoot bullets
or grenades. Health, energy and the other warrior’s properties change
according to the rules cited above. This was the first mode of play implemented in GUN-TACTYX and the game includes 3 levels (arena, plaza
and maze) that are tailored for fights.
2. Soccer mode is a particular kind of play where warriors don’t hold any
weapon (but weapons may appear in the level and warriors may use them).
When this mode is active, a ball appears in the middle of the level at the
point of coordinate (0,0,0). Warriors can hit the ball simply moving towards it. When a warrior hits the ball with its bounding box, the simulator
resolves the collision according to these rules:
a) if the ball hits the warrior’s torso, it simply bounces following the laws
of elastic collisions, considering the normal vector that goes from the center of the warrior to the ball. The speed of the warrior is taken in account
to compute the final speed of the ball.
b) if the ball hits the warrior’s legs, it bounces as above but also an additional ¨kick¨ velocity is added. The direction of that velocity is given
on the horizontal plane by the normal vector that goes from the center
of the warrior to the ball, while the vertical component is given by the
inclination of the warrior’s torso. If the torso points up with a certain
angle, the ¨kick¨ velocity will acquire an initial angle that points up. The
speed of the ¨kick¨ is selected by the warrior through the function setKickSpeed() and its default maximum value is 5 m/s. When a ¨kick¨ is
performed, the ball bounces around losing a percentage of its speed equal
to getGroundElasticity() after every collision with the geometry of the
level. The elasticity has a default value of 80%.
The objective of every team of warriors is to send the ball in the goal
area of enemy teams. The enemy area is located at the starting point of
a team and is sphere shaped. A warrior can retrieve information about
enemy goal areas through the function getGoalLocation(), with index 1, 2
or 3 as argument for the first, second and third enemy teams, and getGoalSize(), that returns a default value of 7.5 m for the diameter of the sphere.
When the ball enters the goal area of a team, the warrior of that team
nearest to the goal area is immediately disabled and the ball becomes a
grenade that explodes in 3 seconds, while another ball suddenly appears
in the middle of the field. The ¨kills¨ are assigned to the warrior that last
touched the ball. Other behaviors of this mode are similar to the Fight
mode, including the rules to find out the winner. This was the second
mode of play implemented in GUN-TACTYX and the game includes only
one level (field) that is tailored for Soccer matches.
3. Race mode is another particular kind of play where warriors don’t hold any
weapon (but weapons may appear in the level and warriors may use them).
When this mode is active, the objective of the warriors is to move around
the origin (the point with all coordinates equal to zero) in the counter-
fight mode
soccer mode
race mode
14
CHAPTER 1. INTRODUCTION
clockwise direction (watching from the top) avoiding the obstacles of the
level. When a warrior move in the wrong direction, it loses health, while
moving in the right direction increases its health. A complete turn around
the origin gives an overall amount of health equal to 100. When a warrior
reaches 100 points of health, it does not increase its health any more but
all the other warriors decrease their health of the lacking amount. Another
rule that is different from the other modes of play is the following: When
two warriors collide, the one with the lowest health is disabled. Other
behaviors of this mode are similar to the Fight mode, including the rules
to find out the winner. This was the third mode of play implemented
in GUN-TACTYX and the game does not include yet any level tailored
for Race matches or examples of tactics suitable for this modality in the
sample scripts. A level for Race matches should provide an obstacle in
the middle around which the warriors must run: A possible choice already
included in the game may be the maze level, but a more complex path,
several obstacles and energy recharges make matches more interesting.
To change mode or goal, enter the options panel, pressing ’0’ (zero) when
the game shows the main panel, and change the corresponding entries according
to your needs.
1.2.4
The Story So Far...
The author of the animated model that you can see GUN-TACTYX is Grant
Struthers. The model is in MD3 format (the format created for the old Quake
III Arena). He created that model for a graphic course he attended and then
uploaded it to a large MD3 models repository, known as Polycount9 . When I
discovered such a particular model, I decided that it fits very well the design of
the game, so I asked for permission to use it and Grant kindly gave it.
The name of the model is Wrokdam and its story, motivating its strangeness,
is tell in the design document that accompanies the model. Here is the original
novel shortly followed by an appendix that explains how Wrokdam enters GUNTACTYX.
Figure 1.3: Wrokdam in GUN-TACTYX
• Wrokdam by Grant Struthers10
Wrokdam
Wrokdam Von Gadmoore was a legendary arena gladiator in his time.
9 visit http://www.polycount.com to see how large and interesting the repository is
10 e-mail: TheGragster@yahoo.com
1.2. PROGRAMMING GAMES
15
He had no greater love than the glory of combat and spent his entire life
shaping his body and mind into the perfect weapon. Tragically, a warrior’s
prime fades quickly.
Wrokdam could feel the years catching up and it infuriated him. He swore
he would never allow weakness to prevail even if his body betrayed him.
He decided to combat death itself through the use of cybernetics and
began replacing parts of his body he felt had grown too weak to be useful
in battle. He thought he had found true immortality. He was terribly
mistaken.
As time passed, Wrokdam slowly slipped into madness. His mind could
not endure the time his new body could and he soon lost himself to the
machine.
Now the monster known as Wrokdam wanders the world in search of
combat and no one knows if any part of his soul still lives.
Figure 1.4: Wrokdam in action
• GUN-TACTYX by Leonardo Boselli11
In a few decades people lost memory of Wrokdam and his past glory
faded away. The cyber-gladiator’s story became a legend known only by
the oldest arena warriors, until Wrokdam was found still fighting in a
provincial arena on a far planet at the border of the Empire.
A young researcher of ZYX Corporation apprehended the military application of a body so highly trained for combat, so the mad warrior was
captured and transferred to a gun-fight training area known as GUNTACTYX.
The researcher discovered that Wrokdam’s body could become the perfect
combat machine, but his brain was too weak, slow and mad to drive
effectively his powerful devices. A hard decision was taken and an artificial
brain was installed in place of Wrokdam’s carbon-based one. Then his
body was cloned hundreds of time to form teams of clones fighting each
other to pick out the perfect warrior through natural selection.
Only one thing lacks to make Wrokdam the strongest arena gladiator of
every time: The best gun-fighting algorithm for his artificial brain!
GUN-TACTYX: Now it’s your play!
11 e-mail: boselli@uno.it
the story
continues
16
CHAPTER 1. INTRODUCTION
• Epilog
The reader may ask where Wrokdam’s original brain is now, but the answer is obvious if you know that ZYX Corporation’s philosophy is: ¨Throw
Away Nothing¨.
Wrokdam’s brain is now employed as a consultant at ZYX Corporation
and mainly keeps up running commentary of the fights among the clones
taking place in the arenas of the GUN-TACTYX area. His deep experience
in gun tactics is very useful to the developers that tune up the fighting
algorithms.
I don’t know if he is really dissatisfied with his new condition, but I’m
sure that he is a little envious of those artificially brained fighters that
resemble the aspect of a warrior once know as Wrokdam: A legendary
arena gladiator in his time.
the epilog
1.3
Features
The following is a short list of some of the features of the engine. More details
on the use of these features will be discussed in the following chapters.
OpenGL Support
The engine is based on OpenGL12 . Version 1.5 is better to access some
advanced features, but 1.1 should be supported as well. The renderer
uses some common OpenGL extensions to improve the rendering speed,
but none of them is really required. Be sure to install the latest OpenGL
drivers for your accelerated graphic card: Almost the totality of the problems you can experience with APOCALYX is caused by old drivers rather
than my code.
Figure 1.5: OpenGL logo
GLSL Support
The OpenGL Shading language (GLSL) is fully supported. Almost every
kind of object that appear on the scene may be rendered with vertex and
fragment programs or the more advanced vertex and fragment shaders.
When the graphic card does not support those features, multi-pass meshes
with diffuse, gloss, environment and bump mapping is still possible.
Skies and Cloud Layers
The engine includes methods to render skies and cloud layers. Sky Boxes,
Half Sky Boxes, Mirrored Sky Boxes, dynamic Sky Domes and Starfields
are available. Several backgrounds may be attached to the world at the
same time specifying the required blending and orientation.
12 visit http://www.opengl.org
1.3. FEATURES
17
Lens Flares
The engine supports sun lens flares to improve the realism of outdoor
scenes. This effect takes into account any occlusion, that prevents the sun
to be seen by the camera, reading the Z buffer, so there is non need of
particular object management to show or hide the lens flare.
Flat Terrains
The engine renders a simple kind of flat terrain. It can be opaque, transparent or reflective and animated textures may be attached to it.
Height Field Terrains
The engine includes also a more realistic kind of terrain realized through
the use of height fields. The heights are defined using grayscale images.
The applied textures are two: A large one that specify the color (including
shadows) of the complete surface and a detailed one that is tiled. Height
fields may increase their transparency downwards and a plane can trim
the lowest triangles to create mirrored surfaces. A simple level of detail
(LOD) is implemented.
Infinite Terrains
The engine even includes a kind of terrain larger than height fields. Its
pattern is repeated to fill a virtual infinite world. The heights are again
defined using grayscale images with two applied textures perturbed by
noise (two levels of detail for the terrain surface) and a color grid to
specify overall color and shadowing.
Wavy Surfaces
The engine support a particular kind of terrain that simulates the presence
of ocean waves. The renderer uses a technique based on FFT to compute
the waves in a fast and realistic way.
Overlay with Multiple Viewports
An overlay is superimposed to the window opened on the main 3D world.
The overlay can render texts, sprites and other 2D objects including viewports showing 3D worlds different from the main one.
Scene Graph
The engine adds all the objects to be rendered to the a scene graph and
orders them according to their distance from the camera. The ordering
is important to render opaque and transparent objects in a fast and safe
way. When a BSP level is loaded, objects are rendered according their
position in the BSP tree for optimal performances.
Hierarchic Objects
The engine includes the possibility to render objects linked to each other.
The definition of a hierarchy makes possible the articulation of complex
movements. In particular MD3 and Cal3D skeletal models provide support
for easy animation.
Textures Types
The engine defines different types of texture, in particular collection of
textures, that can be easily animated, and cube maps.
18
CHAPTER 1. INTRODUCTION
Materials Management
The engine defines the properties of different materials. You can specify
ambient, diffuse, emissive and specular colors, and diffuse (texture for
the diffuse color), gloss (texture that specify the reflection properties of
the surface), environment (texture that reflects the environment around
the mesh) and bump (texture that defines the bumpiness of the surface)
mappings. In alternative, more complex programmable materials can be
defined through the use of vertex and fragment program or GLSL vertex
and fragment shaders.
Bumped Materials
As a heritage of the past, the engine supports also a basic form of bump
mapping called emboss bump mapping. It is not a very realistic technique
but it is fast on slower graphics card. The bumpiness is computed taking
into account the nearest light source.
Sprites & Billboards
The engine renders couple of textured triangles is several ways according
to their position in front of the camera. There are sprites, that always face
the camera, and billboards that always face the camera rotating around
a fixed axis. A praticular type of billboard, the tree, adds a streching
movement to simulate wind effects.
Particle System
The engine includes a particle system. The main interpolated parameters
of this system are: speed, acceleration, color, transparency and size, plus
a circular movement for particular effects. A texture, even animated, the
life length and a speed random factor are other parameters that can be
specified.
3DS and OBJ Models Loader
The engine includes a loader of 3DS and OBJ files and converts them to
static meshes.
MD2 Models Loader and Animator
The engine includes obviously a loader of MD2 files (Quake2 animated
models format) and can animate them.
MD3 Models Loader and Animator
The engine includes also a loader of MD3 files (Quake3 animated models
format) and can animate them. Animation can be played even in reverse
order.
Cal3D Models Loader and Animator
The engine supports skeletal animated models in Cal3D format 13 thanks
to the Character Animation Library included.
Planar Shadows
The engine renders planar shadows generated by a directional light shining
on meshes and animated models.
13 visit http://www.cal3d.org
1.3. FEATURES
19
BSP Levels Loader
The engine includes in addition a loader of BSP files (Quake3 levels format). The Quake3 shaders and the animated meshes of the scenes are not
supported, but visibility information, light maps, light volumes, transparent textures and the bsp-tree collisions are. An editor for Quake3 BSP
levels is GtkRadiant14 .
Particle-Based Physics Simulator
The engine supports a particle based physics engine suitable for the simulation of flags, cloths, articulated, soft or rigid bodies.
ODE Physics Simulator
The engine supports a more traditional physics engine suitable for the simulation of articulated rigid bodies thanks to the Open Dynamics Engine15
included.
Collision Detection
The engine detects collision between meshes thanks to the ColDet16 collision detection library.
3D math
The engine includes basic functions that perform 3D math computations.
LUA Scripts
The engine supports the LUA language17 (version 5.1) through an interface that expose all the most important functions of the engine. The
LUA interface is the main way to access the engine functionalities, so no
recompilation is needed to produce demos, games or 3D applications.
C Compiler
The engine includes the Tiny C compiler. It performs a very fast compilation of ANSI C sources, so time critical code can be loaded and compiled
on the fly for fast execution just like a script is loaded and then more
slowly interpreted.
FASM Support
In the speed of compiled C code or the instructions of the Tiny C inline
assembler are not enough, the engine can load ELF object modules written
with the flat assembler, a fast assembler with interesting features.
Scripting Languages
The engine supports also the C Scripting Language18 (CSL), the AngelScript language19 and the SMALL language20 . The latter manages
the allocated memory and run step by step the scripts for a fine control
of the execution. This features makes the development of Programming
Games very easy. In particular, the bots’ AI can access the properties
14 visit http://www.qeradiant.com
15 visit http://www.ode.org
16 visit http://www.coldet.org
17 visit http://www.lua.org
18 visit http://csl.sourceforge.net
19 visit http://www.angelcode.com
20 visit http://www.compuphase.com
20
CHAPTER 1. INTRODUCTION
of the world in a fair way and matches that involve several teams can be
replayed with the same exact results providing only the original seed for
random numbers generation.
Scripts Console
The engine includes a console from which it’s very easy to compile, execute
and read the output of LUA scripts.
Path Finding
The MicroPather library21 is included to provide easy access to path finding using the A* algorithm.
Steering Behaviors
The OperSpeer library22 is provided to implement steering and flocking
behaviors .
Finite State Machines
The engine includes a mechanism to define and control Finite State Machines.23
3D Sound
Thanks to OpenAL24 , spatialized 3D sound is supported. The engine adds
the capability to select the sounds to be played according to their loudness
and distance from the listener. OpenAL supports also the functions to
perform sound capture.
In alternative, a version of the engine based on FMOD is available on
request.
Figure 1.6: OpenAL logo
Music
MIDI soundtracks and MP3 playback are supported.
Compressed Data Management
Thanks to ZLIB, the engine reads data from ZIP files.
JPG, PNG, TGA Image Loader
Thanks to JPEGlib and PNGlib, the engine reads JPEG, PNG and TGA
images from which several kind of textures can be created.
Completed Projects
Demos apart, a complete game is based on APOCALYX: The GUNTACTYX Programming Game25 , a CROBOTS-like game with QUAKE3style graphics.
21 visit http://micropather.sourceforge.net
22 visit http://opensteer.sourceforge.net
23 visit http://fsm.sourceforge.net
24 visit http://www.openal.org
25 visit http://guntactyx.gameprog.it
1.4. DOWNLOADS
1.4
21
Downloads
In this section I describe the main files available for download at the APOCALYX site. Visit frequently the URL http://apocalyx.sourceforge.net to verify if
any updated or new packages were added in the meantime to the list.
1.4.1
The Engine
The Executable Apocalyx-exe-0.8.7.zip
The package contains the executable of the engine, the documentation
and some support files.
executable
The Sources Apocalyx-src-0.8.7.zip
The package contains the sources of the engine and all the needed libraries. To recompile you need the Code::Blocks IDE26 with the Free
Borland C++ Command Line Compiler 5.5.127 installed. No other
compilers or IDEs are supported.
sources
Remember that you don’t need the C++ sources to create programs with
APOCALYX. The engine should be used through scripts written in LUA or in
the other supported languages. The sources are provided only for those who
want to see the inner workings or improve the capabilities of the engine.
1.4.2
Demos
The following demos require the APOCALYX 3D Engine package listed above
to be executed, so you must download it first. Then unzip it and one or more of
the packages listed below in the same folder and run the file APOCALYX.exe
If you want to modify the demos, remember that the ¨*.dat¨ files are simply
ZIP files the extension of which was renamed (they contain the media files
needed by the demos) and the ¨*.lua¨ files are text files (they contain the
code of the scripts). I suggest to modify the LUA sources the Scintilla Text
Editor28 because of you powerful capabilities.
1. Hoverjet Racing HoverjetRacing-lua-0.8.5.zip
A simple racing demo.
It features skybox, lens flare, cloud layer, infinite terrain, ocean waves,
height fields, particle system, sprites, billboards, particle-based physics
engine, simple shadows, 3DS models, overlay texts, 3D sound, MIDI music
and more.
2. Urban Tactics UrbanTactics-lua-0.8.0.zip
A simple third person shooter.
It features skybox, lens flare, bsp level, particle system, animated sprites,
simple shadows, particle-based physics engine, MD3 and MD2 models,
overlay texts, 3D sound, MIDI music and more.
26 visit http://www.codeblocks.org/
27 visit URL http://www.borland.com/bcppbuilder/freecompiler/
28 visit http://www.scintilla.org
demos
22
CHAPTER 1. INTRODUCTION
3. Zeke on Your Six! ZekeOnYourSix-lua-0.8.2.zip
A simple flight simulator.
It features skybox, lens flare, infinite terrain, particle system, particlebased physics engine, simple shadows, 3DS models, overlay texts, 3D
sound, MIDI music and more.
4. Physics Demo PhysicsDemo-lua-0.8.3.zip
A simple simulator of cloths and rigid/soft bodies.
It features skybox, lens flare, flat terrain, particle-based physics engine,
3DS models and more.
5. Demo Pack 0 DemoPack0-lua-0.8.0.zip
A collection of several demos featuring some of the engine capabilities.
The package contains the following 13 demos:
ModelCal3D (skybox, cloud layer, flat terrain with reflections, Cal3D
model, planar shadow), ModelMD3 (skybox, lens flare, flat terrain with
reflections, MD3 models, planar shadow), ModelMD2 (skybox, lens flare,
flat terrain with reflections, MD2 models, planar shadow), Models (skybox, starfield, lens flare, flat terrain with reflections, MD3 models, 3DS
models, flag simulator, fire light, 3D sound), ClothDemo (skybox, lens
flare, flat terrain with reflections, 3DS model, flag simulator, 3D sound),
ClothDemo2 (skybox, lens flare, flat terrain with reflections, 3DS models,
cloth simulator, MIDI music), OceanWaves (skybox, lens flare, height
field, billboards, ocean waves), Volcano (skybox, lens flare, height field,
particle system, ocean waves), OuterSpace (starfield, lens flare, 3DS
models, MIDI music), FireAndSmoke (skybox, lens flare, 3DS model,
particle system, 3D sound), BumpMapping(starfield, light, 3DS model,
old-style emboss bump mapping, MIDI music), IslesFlyThrough (skybox,
lens flare, flat terrain with animation and reflection, 3DS models, height
fields, billboards, 3D sound), BspLevel (skybox, lens flare, bsp level, particle system, MD3 model, 3D sound).
6. Demo Pack 1 DemoPack1-lua-0.8.5.zip
Another collection of several demos featuring some of the advanced engine
capabilities.
The package contains the following 3 demos (more coming):
RefractiveSphere (explains how to create a material with a fragment
program attached), TinyCInit (describes a way to mix code written in
C and LUA ), LoadDLL (shows different ways to load functions from
DLLs).
1.4.3
games
Games
GUN-TACTYX GunTactyx-exe-1.1.5.zip
The package contains the (standalone) executable of the game, the documentation and some support files.
As described earlier, GUN-TACTYX (gun-tactics) is a programming game
based on the concept of the 1985 Tom Poindexter’s CROBOTS but completely
1.5. ACKNOWLEDGEMENTS
23
in 3D: That’s why I call it a ¨CROBOTS-like game with QUAKE3-style graphics¨.
Some teams of warriors fight in an arena, firing bullets and grenades and avoiding enemies’ projectiles. At the end only one team will survive! This game is
involving but not interactive: The player must develop the AI algorithms of his
own team of bots with a simple C-like scripting language, then he can upload
it to the online repository to challenge bots written by other players.
Don’t miss to visit the official site of the game: GUN-TACTYX at GameProg.it
http://guntactyx.gameprog.it.
1.5
Acknowledgements
I wish to thank all the people who helped in developing this engine.
First of all, I must thank Matteo ¨Fuzz¨ Perenzoni and his Apocalypse
demo written for the NeHe’s 2001 Apocalypse Contest29 : The sources of
his demo were the starting point for the APOCALYX 3D renderer and many
nice effects.
Then, I must thank a lot of great internet resources about game programming:
News Portals, such as GameDev30 , FlipCode31 , CFXweb32 , OpenGL.org33
and Game Programming Italia34 ,
Game Tutorials Sites, such as NeHe35 , Game Tutorials36 , SULACO37
and Newsgroups, such as the italian ICGS38 .
29 at the NeHe’s site visit http://nehe.gamedev.net/data/contests/contest.asp?contest=03
30 see URL http://www.gamedev.net
31 see URL http://www.flipcode.net
32 see URL http://www.cfxweb.net
33 see URL http://www.opengl.org
34 see URL http://www.gameprog.it
35 see URL http://nehe.gamedev.net
36 see URL http://www.gametutorials.com
37 see URL http://www.sulaco.co.za
38 point your News Reader to news://it.comp.giochi.sviluppo
24
CHAPTER 1. INTRODUCTION
Part I
Basic Topics
25
Chapter 2
The Game Loop
2.1
The Scene
2.1.1
Initialization
2.1.2
Finalization
2.1.3
The Loop
2.1.4
Keys Pressed
----EMBOSS BUMP MAPPING
----Simple Bump Mapping Sample
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----GLOBALS---speed = 0
----CAMERA---setAmbient(.25,.25,.25)
setPerspective(60,.5,1500)
enableFog(500, 0,0,0)
local camera = getCamera()
camera:reset()
camera:setPosition(0,0,-6)
empty()
----STARFIELD---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local starfield = StarField(50,6000,zip:getTexture("stars.jpg"),20)
setBackground(starfield)
----MUSIC---soundTrack = zip:getMusic("dvorak.mid")
27
28
CHAPTER 2. THE GAME LOOP
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
----PLANET---local planet = zip:getBumpedMesh("sphere.3ds")
local material = planet:getBumpedMaterial()
material:setBumpedTexture(zip:getBumpedTexture("logoBump.png"))
material:setGlossTexture(zip:getTexture("logoBump.png"))
material:setEnvironmentTexture(zip:getTexture("stars.jpg"),0.25)
material:setShininess(128)
planet:move(0,-1,0)
addObject(planet)
----STAR---star = Light(zip:getTexture("light.jpg"),1)
star:move(-8,0,0)
addLight(star)
----SET HELP---local help = {
"[MOUSE] Change Direction",
"[ UP ] Increase Speed",
"[DOWN ] Decrease Speed",
"[LEFT ] Roll Left",
"[RIGHT] Roll Right",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpUser()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
camera:moveForward(speed*timeStep)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
2.1. THE SCENE
rotateView = 1
end
end
----MOVE LIGHT---local starStep = 5.0265*timeStep
local starAngle = 0.6283*timeStep
star:moveForward(starStep)
star:rotStanding(starAngle)
----MOVE CAMERA (KEYBOARD)---if isKeyPressed(38) then --> VK_UP
speed = speed + 15*timeStep;
if speed > 50 then
speed = 50
end
end
if isKeyPressed(40) then --> VK_DOWN
speed = speed - 15*timeStep;
if speed < -50 then
speed = -50
end
end
if isKeyPressed(37) then --> VK_LEFT
camera:roll(-0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:roll(0.4*timeStep)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:yaw(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS----
29
30
speed = nil
rotateView = nil
star = nil
----STOP MUSIC---if soundTrack then
soundTrack:stop()
soundTrack:delete()
soundTrack = nil
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
CHAPTER 2. THE GAME LOOP
Chapter 3
Backgrounds
----SKYDOME
----Particle System Example
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----CAMERA---setAmbient(.3,.3,.3)
setPerspective(60,.5,3000)
enableFog(750, .5,.5,.75)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
empty()
----SKYBOX---local zip = Zip("DemoPack1.dat")
sunAngle = 0
sky = SkyDome(24,6,100)
local sunY, sunZ = 0,math.sin(sunAngle),math.cos(sunAngle)
sky:setSun( 1,1,1, 1, 0,sunY,sunZ )
sky:setAtmosphere( 0.25,0.25,1, 0.8,0.5, -15,20)
sky:setHaze( 1,1,1, 0.5,0.5, -15,5 )
sky:setRedShift( -10,15 )
sky:update()
setBackground(sky)
----CLOUDLAYER---local cloudsImage = zip:getImage("cloudlayer.jpg")
local alphaImage = zip:getImage("cloudlayer.png")
cloudsImage:addAlpha(alphaImage)
local cloudsTexture = Texture(cloudsImage,1)
cloudsImage:delete()
alphaImage:delete()
local material = Material()
material:setEmissive(0.9,0.9,0.6)
31
32
CHAPTER 3. BACKGROUNDS
material:setEnlighted(false)
material:setDiffuseTexture(cloudsTexture)
cloudLayer = CloudLayer(material,4000,100,10)
cloudLayer:setSpeed(10,10)
setCloudLayer(cloudLayer)
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,sunY,sunZ,
zip:getTexture("lensflares.png"),
4, 0.1
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(.7,.7,.7)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
setTerrain(terrain)
terrainMaterial:delete()
----HELP---local help = {
"[MOUSE] Look Around",
"[CLICK] Raise/Lower Sun",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[LEFT ] Rotate Left",
"[RIGHT] Rotate Right",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
if isMouseLeftPressed() then
sunAngle = sunAngle+6.28*timeStep/90
elseif isMouseRightPressed() then
33
sunAngle = sunAngle-6.28*timeStep/90
end
local sunY = math.sin(sunAngle)
local sunZ = math.cos(sunAngle)
sky:setSun( 1,1,1, 1, 0,sunY,sunZ )
sky:update()
sun:setDirection(0,sunY,sunZ)
enableFog(750, sky:getFogColor())
sun:setColor(sky:getSunColor())
cloudLayer:setColor(sky:getCloudColor())
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
dofile("main.lua")
34
CHAPTER 3. BACKGROUNDS
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
----GLOBALS---sunAngle = nil
cloudLayer = nil
sky = nil
sun = nil
rotateView = nil
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
Chapter 4
Overlays
--PRIMITIVES 2D DEMO
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
--INIT FUNCTION-function init()
--CAMERA INIT
local camera = getCamera() --Get the camera
camera:reset() --Reset
emptyOverlay() -- empty 2D overlay
empty() -- empty 3D world
--2D Primitives (POINTS)
local coords = {-20,-20, -20,20, 20,20, 20,-20,}
-- Creates the coordinates used to define vertices (a square)
local colors = {1,0,0,1, 1,1,0,1, 1,0,1,1, 0,1,1,1,}
-- Creates the colors used to color vertices (red, yellow, magenta, cyan)
overlayPoints = OverlayPoints(coords,colors) -- the points
overlayPoints:setSmooth(true) -- rendered as smooth points
overlayPoints:setSize(8) -- size is 8
overlayPoints:setLocation(200,400) -- move to location 100,100
overlayPoints:setRotation(30/180*3.1415) -- rotate by 30 degrees
addToOverlay(overlayPoints) -- add to overlay
--2D Primitives (LINES)
coords = {-40,-40, -40,40, 40,40, 40,-40,}
-- colors the same as above
local indexes = {0,1,2,3,}
overlayLines = OverlayLines(indexes,coords,colors) -- the points
overlayLines:setModeLineStrip() -- specifies that the lines are connected...
overlayLines:setModeLineLoop() -- ... and the loop is closed
overlayLines:setSmooth(true) -- rendered as smooth lines
overlayLines:setSize(4) -- width of line is 4
overlayLines:setStipple(0xff00,1)
-- the stipple specify the pattern of the line: in this case is a dashed line
-- because only the set bits of the pattern are drawn (0xff00 in exhadecimal
-- means 16 bits set and 16 bits cleared). The second arg is a scale factor
-- for the pattern
35
36
CHAPTER 4. OVERLAYS
overlayLines:setLocation(400,400) -- move to location 400,400
overlayLines:setRotation(45/180*3.1415) -- rotate by 45 degrees
addToOverlay(overlayLines) -- add to overlay
--2D Primitives (LINES)
-- coords the same as above
local colors = {1,0,0,1, 1,1,0,1, 1,0,1,1, 0,1,1,0.5,}
-- Creates the colors used to color vertices
-- (red, yellow, magenta, cyan half transparent)
-- indexes the same as above
overlayPolys = OverlayPolys(indexes,coords,colors) -- the points
overlayPolys:setColor(1,1,1,0)
-- specifies the color of the vertexes, but in this case the colors are already
-- given by the "colors" table, so only the alpha component is important
-- to specify that there is transparency
overlayPolys:setModeTriangFan() -- rendered as a fan of triangles
overlayPolys:setSmooth(true) -- rendered as smooth lines
overlayPolys:setLocation(600,400) -- move to location 600,400
overlayPolys:setRotation(-15/180*3.1415) -- rotate by -15 degrees
addToOverlay(overlayPolys) -- add to overlay
--HELP TEXT-local help = {
"2D Primitives Test",
"",
"Use the mouse to move the lines",
"Press ’ENTER’ to go back to demos menu",
}
setHelp(help)
showHelpUser()
hideConsole()
end --INIT
--UPDATE FUNCTION-function update()
local camera = getCamera()
local dx, dy = getMouseMove()
local x, y = overlayLines:getLocation()
overlayLines:setLocation(x+dx,y+dy)
end
--KEY PRESS DETECTION
function keyDown(key)
if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
37
--CLEANUP AND END
function final()
overlayPoints = nil
overlayLines = nil
overlayPolys = nil
emptyOverlay() -- empty 2D overlay
empty() -- empty 3D world
end
--SETUP THE SCENE (RUN)-setScene(Scene(init,update,final,keyDown))
38
CHAPTER 4. OVERLAYS
Chapter 5
World Views and Viewports
----Viewports DEMO
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----SOME GLOBALS---MAX_AVATARS = 9
shotEmitter = {}
avatar = {}
linkTransform = Transform()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
enableFog(200, .522,.373,.298)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.8,-5)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nRequires ’DemoPack0.dat’ to work.\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("skyboxTop2.jpg"),
zip:getTexture("skyboxLeft2.jpg"),
zip:getTexture("skyboxFront2.jpg"),
zip:getTexture("skyboxRight2.jpg"),
zip:getTexture("skyboxBack2.jpg")
}
isDay = 1
skyBackground = MirroredSky(skyTxt)
skyBackground:rotStanding(3.1415)
setBackground(skyBackground)
39
40
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
starBackground = StarField(50,6000,zip:getTexture("stars.jpg"),20)
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, -0.9659,
zip:getTexture("lensflares.png"),
5, 0.1
)
sun:setColor(0.855,0.475,0.298);
setSun(sun)
----FIRELIGHT---fireLight = FireLight(zip:getTexture("light.jpg"),1.5)
fireLight:setAttenuation(0,0,0.02)
fireLight:setIntensities(0.75,0.25,0, 1,0.75,0)
addLight(fireLight)
fireLight:hide()
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(1,1,1)
terrainMaterial:setDiffuse(0,0,0)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
ground = FlatTerrain(terrainMaterial,500,125)
setTerrain(ground)
terrainMaterial:delete()
----EMITTERS---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
fireEmitter = Emitter(15,0.75,3)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(-1,1,0, 0.3)
fireEmitter:setColor(1,1,0,1, 1,0,0,0)
fireEmitter:setSize(.5,.1)
fireEmitter:setGravity(0,0,0, 0,0,0)
fireEmitter:reset()
addObject(fireEmitter)
local fireSound = zip:getSample3D("fire.wav");
fireSound:setLooping(1)
fireSound:setVolume(255)
fireSound:setMinDistance(4)
local fireSource = Source(fireSound,fireEmitter);
addSource(fireSource)
for ct = 0, MAX_AVATARS-1 do
shotEmitter[ct] = Emitter(5,0.1,3)
shotEmitter[ct]:setTexture(fireTexture,1)
shotEmitter[ct]:setVelocity(10,1,0, 0)
shotEmitter[ct]:setColor(1,1,1,1, 1,0.5,0,0)
shotEmitter[ct]:setSize(.75,.75)
shotEmitter[ct]:setGravity(0,0,0, 0,0,0)
41
shotEmitter[ct]:setOneShot()
shotEmitter[ct]:reset()
addObject(shotEmitter[ct])
shotEmitter[ct]:hide()
end
fireTexture:delete()
----MODELS---torso = 4 ---> TORSO_STAND
legs = 5
---> LEGS_IDLE
weapon = zip:getModel("gun.md3","gun.jpg")
weapon:rescale(0.04)
local avatarCt = 0
avatar[0] = zip:getBot("wrokdam.mdl")
avatar[0]:rescale(0.04)
avatar[0]:pitch(-1.5708)
avatar[0]:rotStanding(1.5708)
avatar[0]:move(0,1,0)
avatar[0]:getUpper():link("tag_weapon",weapon)
avatar[0]:setUpperAnimation(torso)
avatar[0]:setLowerAnimation(legs)
addObject(avatar[0])
for ct = avatarCt+1, MAX_AVATARS/3-1 do
avatar[ct] = Bot(avatar[0])
avatar[ct]:pitch(-1.5708)
avatar[ct]:rotStanding(1.5708)
avatar[ct]:move(3*(ct-avatarCt),1,0)
addObject(avatar[ct])
end
avatarCt = MAX_AVATARS/3
local torsoRed = Material()
torsoRed:setDiffuseTexture(zip:getTexture("torsoRed.jpg"))
local bodyRed = Material()
bodyRed:setDiffuseTexture(zip:getTexture("bodyRed.jpg"))
avatar[avatarCt] = Bot(avatar[0])
avatar[avatarCt]:getUpper():setMaterial(torsoRed)
avatar[avatarCt]:getLower():setMaterial(bodyRed)
avatar[avatarCt]:pitch(-1.5708)
avatar[avatarCt]:rotStanding(1.5708)
avatar[avatarCt]:move(0,1,2)
addObject(avatar[avatarCt])
for ct = avatarCt+1, 2*MAX_AVATARS/3-1 do
avatar[ct] = Bot(avatar[avatarCt])
avatar[ct]:pitch(-1.5708)
avatar[ct]:rotStanding(1.5708)
avatar[ct]:move(3*(ct-avatarCt),1,2)
addObject(avatar[ct])
end
avatarCt = 2*MAX_AVATARS/3
local torsoBlue = Material()
torsoBlue:setDiffuseTexture(zip:getTexture("torsoBlue.jpg"))
42
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
local bodyBlue = Material()
bodyBlue:setDiffuseTexture(zip:getTexture("bodyBlue.jpg"))
avatar[avatarCt] = Bot(avatar[0])
avatar[avatarCt]:getUpper():setMaterial(torsoBlue)
avatar[avatarCt]:getLower():setMaterial(bodyBlue)
avatar[avatarCt]:pitch(-1.5708)
avatar[avatarCt]:rotStanding(1.5708)
avatar[avatarCt]:move(0,1,4)
addObject(avatar[avatarCt])
for ct = avatarCt+1, MAX_AVATARS-1 do
avatar[ct] = Bot(avatar[avatarCt])
avatar[ct]:pitch(-1.5708)
avatar[ct]:rotStanding(1.5708)
avatar[ct]:move(3*(ct-avatarCt),1,4)
addObject(avatar[ct])
end
torch = zip:getMesh("torch.3ds")
torch:pitch(1.5708)
torch:move(-0.15,0,-0.5)
avatar[0]:getUpper():link("tag_weapon",torch)
pole = zip:getMesh("pole2.3ds")
pole:pitch(1.5708)
pole:move(-0.15,0,-0.5)
avatar[MAX_AVATARS-1]:getUpper():link("tag_weapon",pole)
local shotSound = zip:getSample3D("shot.wav");
shotSound:setVolume(255)
shotSound:setMinDistance(8)
shotSource = Source(shotSound,avatar[math.floor(MAX_AVATARS/2)]);
shotSource:getSound3D():stop()
addSource(shotSource)
local reloadSound = zip:getSample3D("reload.wav");
reloadSound:setVolume(255)
reloadSound:setMinDistance(4)
reloadSource = Source(reloadSound,avatar[math.floor(MAX_AVATARS/2)]);
reloadSource:getSound3D():stop()
addSource(reloadSource)
----FLAG SIMULATOR---local posHighX, posHighY, posHighZ = -0.15, 0, 5.5
local posLowX, posLowY, posLowZ = -0.15, 0, 2.5
if avatar[MAX_AVATARS-1]:getLinkTransform("tag_weapon",linkTransform) then
posHighX, posHighY, posHighZ = linkTransform:multiply(
posHighX, posHighY, posHighZ
)
posLowX, posLowY, posLowZ = linkTransform:multiply(
posLowX, posLowY, posLowZ
)
end
simulator = Simulator()
windSpeed = 2
environment = StaticEnvironment(-windSpeed,0,windSpeed, 0.01)
43
local SIDE_CT = 17
local SIDE_LEN = 3
cloth = Cloth(
SIDE_CT, SIDE_CT,
---> width, height
posLowX,posLowY,posLowZ, ---> origin
0,SIDE_LEN/SIDE_CT,0,
---> uGen
-SIDE_LEN/SIDE_CT,0,0,
---> vGen
0.01,
---> mass
simulator, environment
)
cloth:setRelaxationCycles(4)
cloth:addNail(0, posLowX,posLowY,posLowZ)
cloth:addNail(SIDE_CT-1, posHighX,posHighY,posHighZ)
local flagMaterial = Material()
flagMaterial:setAmbient(0.7,0.7,0.7)
flagMaterial:setDiffuse(1,1,1)
flagMaterial:setSpecular(1,1,1)
flagMaterial:setShininess(128)
flagMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
flagMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),0.25)
clothModel = cloth:getMesh()
clothModel:setMaterial(flagMaterial)
addObject(clothModel)
flagMaterial:delete()
local flagSound = zip:getSample3D("flag.wav");
flagSound:setLooping(1)
flagSound:setVolume(255)
flagSound:setMinDistance(4)
local flagSource = Source(flagSound,clothModel);
addSource(flagSource)
----VIEWPORTS---setClear(1,1,0)
local w, h = getDimension()
setViewport(w/8,h/8,6*w/8,6*h/8)
vp1 = OverlayWorldView(400,300)
vp1:setLocation(400,200)
vp1:setPerspective(60,.25,1000)
vp1:getCamera():setPosition(0,1.8,20)
vp1:getCamera():rotStanding(3.1415)
addToOverlay(vp1)
vp1 = OverlayViewport(200,200)
vp1:setLayer(-1)
vp1:setLocation(100,100)
vp1:setPerspective(70,0.1,100)
local vp1sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, -0.9659
)
vp1sun:setColor(0.855,0.475,0.298);
vp1:setSun(vp1sun)
44
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
vp1model = zip:getModel("gun.md3","gun.jpg")
vp1model:rescale(0.04)
vp1model:move(0,0,1.5)
vp1model:pitch(-1.57)
vp1:addObject(vp1model)
addToOverlay(vp1)
vp2 = OverlayViewport(200,200)
vp2:setLocation(100,300)
vp2:setPerspective(70,0.1,1000)
vp2:getCamera():setPosition(0,1,-2)
local vp2SkyTxt = {
zip:getTexture("skyboxTop2.jpg"),
zip:getTexture("skyboxLeft2.jpg"),
zip:getTexture("skyboxFront2.jpg"),
zip:getTexture("skyboxRight2.jpg"),
zip:getTexture("skyboxBack2.jpg")
}
local vp2Background = MirroredSky(vp2SkyTxt)
vp2:setBackground(vp2Background)
local vp2Material = Material()
vp2Material:setAmbient(1,1,1)
vp2Material:setDiffuse(0,0,0)
vp2Material:setDiffuseTexture(zip:getTexture("wood.jpg",1))
local vp2ground = FlatTerrain(vp2Material,500,125)
vp2:setTerrain(vp2ground)
vp2:enableFog(200, .522,.373,.298)
addToOverlay(vp2)
vp3 = OverlayViewport(200,200)
vp3:setLocation(300,100)
vp3:setPerspective(70,0.1,100)
vp2:getCamera():setPosition(0,1,-2)
vp3:setAmbient(1,1,0.5)
local vp3Background = StarField(50,6000,zip:getTexture("stars.jpg"),20)
vp3:setBackground(vp3Background)
local vp3mesh = zip:getMesh("foot.3ds")
vp3mesh:move(0,0,5)
vp3:addObject(vp3mesh)
addToOverlay(vp3)
vp4 = OverlayViewport(200,200)
vp4:setLayer(-1)
vp4:setLocation(300,300)
vp4:setColor(0.5,0,0)
vp4:setOrtho(1000)
vp4:setAmbient(1,1,0.5)
vp4model = zip:getBot("wrokdam.mdl")
vp4model:rescale(2)
vp4model:pitch(-1.5708)
vp4model:rotStanding(1.5708)
vp4model:move(0,-1*32,2*32)
vp4:addObject(vp4model)
45
addToOverlay(vp4)
----HELP---local help = {
"The model of this tutorial was made by:",
" Grant Struthers <TheGragster@yahoo.com>",
"The weapon of this tutorial was made by:",
" Janus <janus@planetquake.com>",
"[ MOUSE ] Look around",
"[ UP/DOWN ] Move Forward/Back",
"[PREV/NEXT] Raise/Lower View",
"[ Z key ] Shoot/Recharge",
"[ X key ] Change Animations",
"[ C key ] Death Animation",
"[ A,S,Q,W ] Rotate Torso/Head",
"[ D,F,E,R ] Bend Torso/Head",
"[ 0,1...6 ] Select Wind Speed",
"[ 7,8,9 ] Select Flag Elasticity",
"[
INS
] Day/Night Transition",
"[
DEL
] Show/Hide Reflections",
"[ SPACE ] Rotate Scene",
"[ ENTER ] Demos Menu",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
----VIEWPORTS---if timeStep > 0.1 then
timeStep = 0.1
end
vp1model:rotStanding(1.57*timeStep)
vp2:getCamera():rotAround(0.25*timeStep)
vp3:getCamera():rotAround(-0.25*timeStep)
vp4model:rotStanding(-1.57*timeStep)
local posX, posY
local sizeX, sizeY
posX, posY = vp2:getLocation()
sizeX, sizeY = vp2:getDimension()
posX = posX+timeStep*100*math.random(-2,2)
posY = posY+timeStep*100*math.random(-2,2)
sizeX = sizeX+timeStep*100*math.random(-2,2)
if sizeX < 0 then
sizeX = 0
46
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
end
sizeY = sizeY+timeStep*200*math.random(-2,2)
if sizeY < 0 then
sizeY = 0
end
vp2:setLocation(posX,posY)
vp2:setDimension(sizeX,sizeY)
posX, posY = vp3:getLocation()
sizeX, sizeY = vp3:getDimension()
posX = posX+timeStep*100*math.random(-2,2)
posY = posY+timeStep*100*math.random(-2,2)
sizeX = sizeX+timeStep*100*math.random(-2,2)
if sizeX < 0 then
sizeX = 0
end
sizeY = sizeY+timeStep*100*math.random(-2,2)
if sizeY < 0 then
sizeY = 0
end
vp3:setLocation(posX,posY)
vp3:setDimension(sizeX,sizeY)
----DEFAULT---local fwdSpeed = 0
local rotSpeed = 0
if legs == 1 then ---> LEGS_WALKCR
fwdSpeed = 2.5
rotSpeed = 0.31415
elseif legs == 2 then ---> LEGS_WALK
fwdSpeed = 2.5
rotSpeed = 0.6283
elseif legs == 3 then ---> LEGS_RUN
fwdSpeed = 5
rotSpeed = 0.6283
elseif legs == 4 then ---> LEGS_BACK
fwdSpeed = -3.5
rotSpeed = 0.31415
elseif legs == 7 then ---> LEGS_TURN
rotSpeed = 1.5708
end
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:walk(fwdSpeed*timeStep);
avatar[ct]:rotStanding(rotSpeed*timeStep);
local stopped = avatar[ct]:getLower():getStoppedAnimation()
stopped = avatar[ct]:getUpper():getStoppedAnimation()
if stopped == 2 then ---> TORSO_DROP
avatar[ct]:setUpperAnimation(3) ---> TORSO_RAISE
elseif (stopped == 1) or (stopped == 3) then ---> TORSO_ATTACK or RAISE
avatar[ct]:setUpperAnimation(4) ---> TORSO_STAND
end
end
47
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
local posX,posY,posZ = avatar[0]:getPosition()
camera:pointTo(posX,posY+1,posZ)
end
----MOVE CAMERA (KEYBOARD)---local moveSpeed = 15
local climbSpeed = 15
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(moveSpeed*timeStep)
elseif isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-moveSpeed*timeStep)
elseif isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
elseif isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
elseif isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
elseif isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
local posX,posY,posZ = camera:getPosition()
if posY < 0.5 then
camera:setPosition(posX,0.5,posZ)
end
end
----MODEL MOVEMENT---if isKeyPressed(string.byte("A")) then
local angle = 3.1415*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getUpper():addYawAngle(angle,1.047)
end
elseif isKeyPressed(string.byte("S")) then
local angle = -3.1415*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getUpper():addYawAngle(angle,1.047)
end
elseif isKeyPressed(string.byte("Q")) then
local angle = 3.1415*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getHead():addYawAngle(angle,1.5708)
end
elseif isKeyPressed(string.byte("W")) then
local angle = -3.1415*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getHead():addYawAngle(angle,1.5708)
end
elseif isKeyPressed(string.byte("D")) then
local angle = 1.57*timeStep
48
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getUpper():addPitchAngle(angle,0.5236)
end
elseif isKeyPressed(string.byte("F")) then
local angle = -1.57*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getUpper():addPitchAngle(angle,0.5236)
end
elseif isKeyPressed(string.byte("E")) then
local angle = 1.57*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getHead():addPitchAngle(angle,0.5236)
end
elseif isKeyPressed(string.byte("R")) then
local angle = -1.57*timeStep
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:getHead():addPitchAngle(angle,0.5236)
end
end
if avatar[0]:getLinkTransform("tag_weapon",linkTransform) then
local posX,posY,posZ = -0.15,0,1.25
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
fireEmitter:setPosition(posX,posY,posZ)
fireLight:setPosition(posX,posY,posZ)
end
if avatar[MAX_AVATARS-1]:getLinkTransform("tag_weapon",linkTransform) then
local posX,posY,posZ = -0.15,0,5.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(1,posX,posY,posZ)
posX,posY,posZ = -0.15,0,2.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(0,posX,posY,posZ)
end
local simTime = timeStep
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
----MOVE CAMERA (MOUSE)---if not rotateView then
local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
end
49
----FINALIZATION---function final()
----VIEWPORTS---setViewport(0,0,getDimension())
setClear(0,0,0)
vp1 = nil
vp2 = nil
vp3 = nil
vp4 = nil
vp1model = nil
vp4model = nil
----DELETE GLOBALS---linkTransform:delete()
if environment then
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
shotSource = nil
reloadSource = nil
windSpeed = nil
rotateView = nil
torso = nil
legs = nil
----EMPTY WORLD---sun = nil
fireLight = nil
fireEmitter = nil
for ct = 0, MAX_AVATARS-1 do
avatar[ct] = nil
shotEmitter[ct] = nil
end
MAX_AVATARS = nil
avatar = nil
shotEmitter = nil
if weapon then
weapon:delete()
weapon = nil
end
if pole then
pole:delete()
pole = nil
end
if torch then
torch:delete()
torch = nil
50
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
end
ground = nil
cloth = nil
if isDay then
starBackground:delete()
else
skyBackground:delete()
end
skyBackground = nil
starBackground = nil
isDay = nil
disableFog()
emptyOverlay()
empty()
end
----KEYBOARD---function keyDown(key)
if key == string.byte(" ") then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----WIND SPEED & RELAXATION---local asciiBase = string.byte("0")
for ct = 0, 6 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
windSpeed = ct*0.5
environment:setWind(-windSpeed,0,windSpeed)
break
end
end
for ct = 7, 9 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
local val = 4
if ct == 7 then
val = 2
elseif ct == 9 then
val = 8
end
cloth:setRelaxationCycles(val)
break
end
end
----VARIOUS SCENE MODIFIERS----
51
if key == 46 then --> VK_DELETE
releaseKey(46)
if ground:isReflective() then
ground:setOpaque()
else
ground:setReflective()
end
elseif key == 45 then --> VK_INSERT
releaseKey(45)
if isDay then
isDay = nil
setBackground(starBackground,nil)
enableFog(200, 0,0,0)
setAmbient(0.2,0.2,0.2)
sun:hide()
fireLight:show()
clothModel:getMaterial():setEnvironment(0.1)
else
isDay = 1
setBackground(skyBackground,nil)
enableFog(200, 0.522,0.373,0.298)
setAmbient(0.5,0.5,0.5)
sun:show()
fireLight:hide()
clothModel:getMaterial():setEnvironment(0.25)
end
elseif key == string.byte("Z") then
releaseKey(string.byte("Z"))
torso = torso+1
if torso >= 5 then ---> MAX_TORSO_ANIMATIONS
torso = 1 ---> TORSO_ATTACK
elseif torso == 3 then ---> TORSO_RAISE
torso = 1 ---> TORSO_ATTACK
end
for ct = 1, MAX_AVATARS-2 do
avatar[ct]:setUpperAnimation(torso)
end
if torso == 1 then ---> TORSO_ATTACK
shotSource:getSound3D():play()
for ct = 1, MAX_AVATARS-2 do
if avatar[ct]:getLinkTransform("tag_weapon",linkTransform) then
shotEmitter[ct]:set(linkTransform)
shotEmitter[ct]:moveSide(1)
shotEmitter[ct]:show()
shotEmitter[ct]:reset()
end
end
elseif torso == 2 then ---> TORSO_DROP
reloadSource:getSound3D():play()
end
52
CHAPTER 5. WORLD VIEWS AND VIEWPORTS
elseif key == string.byte("X") then
releaseKey(string.byte("X"))
legs = legs+1
if legs >= 8 then ---> MAX_LEGS_ANIMATIONS
legs = 1 ---> LEGS_WALKCR
end
for ct = 0, MAX_AVATARS-1 do
avatar[ct]:setLowerAnimation(legs)
end
elseif key == string.byte("C") then
releaseKey(string.byte("C"))
legs = 0 ---> LEGS_DEATH
for ct = 1, MAX_AVATARS-2 do
avatar[ct]:setLowerAnimation(legs)
avatar[ct]:setUpperAnimation(legs)
end
avatar[0]:setLowerAnimation(5) ---> LEGS_IDLE
avatar[0]:setUpperAnimation(4) ---> TORSO_STAND
avatar[MAX_AVATARS-1]:setLowerAnimation(5) ---> LEGS_IDLE
avatar[MAX_AVATARS-1]:setUpperAnimation(4) ---> TORSO_STAND
end
----LOAD MAIN MENU---if key == string.byte("\r") then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
Chapter 6
Meshes
6.1
Dynamic Meshes
----INITIALIZATION---function init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.5,3000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,0,-10)
empty()
hideConsole()
----FOOT---if not fileExists("DemoPack1.dat") then
showConsole()
error("\nERROR: File ’DemoPack1.dat’ not found.")
end
local zip = Zip("DemoPack1.dat")
local vertices = {
1,-1,-1, -1,-1,-1, -1,-1, 1,
1,-1, 1,
1, 1,-1, -1, 1,-1, -1, 1, 1,
1, 1, 1
}
local normals = {}
local mappings = {
0,0, 0,1, 1,1, 1,0,
0,1, 1,1, 1,0, 0,0
}
local triangles = {
0,3,1, 3,2,1, 5,4,0, 5,0,1, 5,1,2, 6,5,2,
7,6,2, 7,2,3, 7,3,4, 4,3,0, 7,4,5, 7,5,6
}
local shape = Shape(vertices,normals,mappings,triangles)
local material = Material()
material:setDiffuse(1,0.5,0.25)
53
54
CHAPTER 6. MESHES
material:setDiffuseTexture(zip:getTexture("agate.jpg"))
local mesh = Mesh(shape,material)
addObject(mesh)
--HELP TEXT-local help = {
"Shape Maker",
"",
"Press ’ENTER’ to go back to demos menu",
}
setHelp(help)
showHelpUser()
hideConsole()
end
----LOOP---function update()
getCamera():rotAround(.25*getTimeStep())
end
----FINALIZATION---function final()
empty()
end
----KEYDOWN---function keyDown(key)
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
6.2
Mesh Loading
----OUTER SPACE
----Space Sim Environment
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----GLOBALS---speed = 0
----CAMERA----
6.2. MESH LOADING
55
setAmbient(.25,.25,.25)
setPerspective(60,.5,3000)
enableFog(500, 0,0,0)
local camera = getCamera()
camera:reset()
camera:setPosition(0,0,-10)
empty()
----STARFIELD---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local starfield = StarField(50,6000,zip:getTexture("stars.jpg"),20)
setBackground(starfield)
----MUSIC---soundTrack = zip:getMusic("dvorak.mid")
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,.41,.91,
zip:getTexture("lensflares.png"),
5, 0.2
)
sun:setColor(1,1,.5)
setSun(sun)
----FOOT---foot1 = zip:getMesh("foot.3ds")
foot1:getMaterial():setShininess(128)
foot1:move(0,0,0)
addObject(foot1)
foot2 = foot1:clone()
foot2:getMaterial():setShininess(128)
foot2:move(-15,0,10)
addObject(foot2)
foot3 = foot1:clone()
foot3:getMaterial():setShininess(128)
foot3:move(15,0,10)
addObject(foot3)
----SET HELP---local help = {
"[MOUSE] Change Direction",
"[ UP ] Increase Speed",
"[DOWN ] Decrease Speed",
"[LEFT ] Roll Left",
"[RIGHT] Roll Right",
"[SPACE] On/Off Rotation",
56
CHAPTER 6. MESHES
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
camera:moveForward(speed*timeStep)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----MOVE FOOTS---local angle = timeStep*.25
foot1:roll(angle)
foot2:yaw(angle)
foot3:pitch(angle)
----MOVE CAMERA (KEYBOARD)---if isKeyPressed(38) then --> VK_UP
speed = speed + 15*timeStep;
if speed > 50 then
speed = 50
end
end
if isKeyPressed(40) then --> VK_DOWN
speed = speed - 15*timeStep;
if speed < -50 then
speed = -50
end
end
if isKeyPressed(37) then --> VK_LEFT
camera:roll(-0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
6.2. MESH LOADING
camera:roll(0.4*timeStep)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:yaw(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS---speed = nil
rotateView = nil
foot1 = nil
foot2 = nil
foot3 = nil
----STOP MUSIC---if soundTrack then
soundTrack:stop()
soundTrack:delete()
soundTrack = nil
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
57
58
CHAPTER 6. MESHES
Chapter 7
Materials
7.1
Diffuse, Gloss and Environment Texture
7.2
Old-style Bump Mapping
----EMBOSS BUMP MAPPING
----Simple Bump Mapping Sample
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----GLOBALS---speed = 0
----CAMERA---setAmbient(.25,.25,.25)
setPerspective(60,.5,1500)
enableFog(500, 0,0,0)
local camera = getCamera()
camera:reset()
camera:setPosition(0,0,-6)
empty()
----STARFIELD---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local starfield = StarField(50,6000,zip:getTexture("stars.jpg"),20)
setBackground(starfield)
----MUSIC---soundTrack = zip:getMusic("dvorak.mid")
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
----PLANET---59
60
CHAPTER 7. MATERIALS
local planet = zip:getBumpedMesh("sphere.3ds")
local material = planet:getBumpedMaterial()
material:setBumpedTexture(zip:getBumpedTexture("logoBump.png"))
material:setGlossTexture(zip:getTexture("logoBump.png"))
material:setEnvironmentTexture(zip:getTexture("stars.jpg"),0.25)
material:setShininess(128)
planet:move(0,-1,0)
addObject(planet)
----STAR---star = Light(zip:getTexture("light.jpg"),1)
star:move(-8,0,0)
addLight(star)
----SET HELP---local help = {
"[MOUSE] Change Direction",
"[ UP ] Increase Speed",
"[DOWN ] Decrease Speed",
"[LEFT ] Roll Left",
"[RIGHT] Roll Right",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpUser()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
camera:moveForward(speed*timeStep)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----MOVE LIGHT----
7.2. OLD-STYLE BUMP MAPPING
local starStep = 5.0265*timeStep
local starAngle = 0.6283*timeStep
star:moveForward(starStep)
star:rotStanding(starAngle)
----MOVE CAMERA (KEYBOARD)---if isKeyPressed(38) then --> VK_UP
speed = speed + 15*timeStep;
if speed > 50 then
speed = 50
end
end
if isKeyPressed(40) then --> VK_DOWN
speed = speed - 15*timeStep;
if speed < -50 then
speed = -50
end
end
if isKeyPressed(37) then --> VK_LEFT
camera:roll(-0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:roll(0.4*timeStep)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:yaw(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS---speed = nil
rotateView = nil
star = nil
----STOP MUSIC----
61
62
if soundTrack then
soundTrack:stop()
soundTrack:delete()
soundTrack = nil
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
CHAPTER 7. MATERIALS
Chapter 8
The Particle System
----FIRE & SMOKE
----Particle System Example
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----CAMERA---setAmbient(.3,.3,.3)
setPerspective(60,.5,3000)
enableFog(750, .5,.5,.75)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("SkyboxTop.jpg"),
zip:getTexture("SkyboxLeft.jpg"),
zip:getTexture("SkyboxFront.jpg"),
zip:getTexture("SkyboxRight.jpg"),
zip:getTexture("SkyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky);
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,.41,.91,
zip:getTexture("lensflares.png"),
63
64
CHAPTER 8. THE PARTICLE SYSTEM
4, 0.2
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(.7,.7,.7)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
terrain:setReflective()
setTerrain(terrain)
terrainMaterial:delete()
----FIRE---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
local fireEmitter = Emitter(10,1,15)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(3,2,.5, 0.5)
fireEmitter:setColor(1,1,0,1, 1,0,0,0)
fireEmitter:setSize(2,.5)
fireEmitter:setGravity(0,0,0, 0,0,0)
fireEmitter:move(0,1.25,.5)
fireEmitter:reset()
addObject(fireEmitter)
local fireImage = zip:getImage("fire.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
local fireEmitter = Emitter(4,1,15)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(3,2,.5, 0.5)
fireEmitter:setColor(.75,.25,0,1, 1,0,0,.5)
fireEmitter:setSize(2.5,.1)
fireEmitter:setGravity(0,0,0, 0,0,0)
fireEmitter:move(0,1.25,.5)
fireEmitter:reset()
addObject(fireEmitter)
fireTexture:delete()
local fireSound = zip:getSample3D("fire.wav");
fireSound:setLooping(1)
fireSound:setVolume(255)
fireSound:setMinDistance(10)
local fireSource = Source(fireSound,fireEmitter);
addSource(fireSource)
----SMOKE---local smokeImage = zip:getImage("smoke.png")
smokeImage:convertToRGBA()
local smokeTexture = Texture(smokeImage)
65
smokeImage:delete()
local smokeEmitter = Emitter(15,2.5,30)
smokeEmitter:setTexture(smokeTexture,1)
smokeEmitter:setVelocity(2,1,-.5, 0.5)
smokeEmitter:setColor(0,0,0,1, 0,0,0,0)
smokeEmitter:setSize(.4,2)
smokeEmitter:setGravity(0,0,0, 0,3,0)
smokeEmitter:move(0,.25,0)
smokeEmitter:reset()
addObject(smokeEmitter)
smokeTexture:delete()
----POLE---local pole = zip:getMeshes("pole.3ds")
pole:move(0,0,0)
addObject(pole)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[LEFT ] Rotate Left",
"[RIGHT] Rotate Right",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
66
CHAPTER 8. THE PARTICLE SYSTEM
end
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
----GLOBALS----
67
rotateView = nil
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
68
CHAPTER 8. THE PARTICLE SYSTEM
Chapter 9
Model Loading and
Animation
In the following section I describe how to load and animate the models in the
supported formats: MD3 from Quake III Arena1 , MD2 from Quake II and
Cal3D.
9.1
MD3 Models
This tutorial explains how to set up a basic environment with an animated MD3
model. Instructions to prepare a standard MD3 model for loading are covered
as well some tips to understand better the syntax of the LUA language.
Figure 9.1: Model in MD3 format
9.1.1
Comments to the ModelMD3.lua source
This tutorial refers to the ModelMD3.lua source included in the file DemoPack0lua-0.8.0.zip. It is better to have a copy of the source for easy reference and
the HTML manual with the list of APOCALYX API because it is too long to
describe here the meaning of all the parameters of the functions.
Now let’s read the source line by line and comment it.
1 visit MYurlwww.idsoftware.com for more details about the game
69
70
CHAPTER 9. MODEL LOADING AND ANIMATION
----MD3 MODEL LOADING Tutorial
----A loader of "pure" MD3 models
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
--[[
comments
In LUA comments are prepended by a couple of hyphens ¨-¨. Multiple comment
lines must be included between ¨–[[¨ and ¨–]]¨. So the lines listed above are
ignored by the interpreter and these multi-line comments are ignored too.
--]]
----INITIALIZATION-----[[
the game loop
LUA scripts written for APOCALYX usually contain four main functions, as
already described in in chapter 2 at page 27, that control the behaviour of a
single scene. They are named as follows:
1. init() is called when the engine initilizes the scene
2. update() is called once for every frame
3. final() is called when the engine finalizes (drops) the scene
4. keyDown() is called when a keyboard key is pressed
init()
The code that follows is included in init(), so it specifies all the actions to be
performed during initialization.
--]]
function init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
enableFog(200, .522,.373,.298)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.8,-3)
empty()
--[[
camera
initialization
The calls listed above have the following meaning. In order:
• a color for the ambient light is set (half red, half green, half blue equal to
half gray)
• the perspective of the camera has an aperture size of 60 degrees, a near
clipping plane of 0.25 (meters, if you want) and a far clipping plane of
1000 (always meters, if this is your length unit of choice).
• then the main camera object is put in the camera local variable (in LUA,
variables are specified local if they must be dropped when out of
context - in this case out of the init() function - otherwise they
are global in scope and accessible from every other function in
this module)
9.1. MD3 MODELS
71
• the camera object is reset using the reset() function (note the ¨:¨ that
specifies that the reset() function is applied to the camera object.
In C++ or Java the ¨.¨ has the same use, but in LUA the
meaning of the full stop is different and explained elsewhere)
and positioned 1.8 meters up and a 3 meters backward from the origin (in
APOCALYX the X axis points to the left, the Y axis points up
and the Z axis points forward).
• finally the function empty() is called to remove all the objects from the
world - objects possibly added to the world by a previous initialization.
--]]
if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
--[[
Now it’s time to load the resources of the demo. First of all, it’s better to verify
if the package of the resources is available and display an error on the console
if it lacks. Then the package is opened and a reference is stored in the local
variable zip.
zipped package
--]]
----SKYBOX---local skyTxt = {
zip:getTexture("skyboxTop2.jpg"),
zip:getTexture("skyboxLeft2.jpg"),
zip:getTexture("skyboxFront2.jpg"),
zip:getTexture("skyboxRight2.jpg"),
zip:getTexture("skyboxBack2.jpg")
}
local skyBackground = MirroredSky(skyTxt)
skyBackground:rotStanding(3.1415)
setBackground(skyBackground)
--[[
The first resource we are going to load is the skybox. The skybox is a simple
cube that surrounds the camera with several textures applied that simulate a
sky with clouds. The images are created in a particular way so they warp on
the cube and the observer does not realize to watch a simple cube instead of a
far realistic horizon.
To create the images for the skybox one can use Terragen2 , a beautiful program that generates realistic landscapes (registration is required for commercial
purposes). In practice, one must take five shots of the landscape generated by
Terragen with an aperture of 45 degrees. Then the five images are loaded by
the engine to create the skybox.
In this case the skybox is mirrored to simulate reflections on a planar ground.
The rotStanding() of 180 degrees (angle specified in radiants in the source) is
2 visit MYurlwww.planetside.co.uk
the skybox
72
CHAPTER 9. MODEL LOADING AND ANIMATION
necessary for the particular images chosen in the demo to make the sun appear
on the back of the camera. Finally the skybox is added to the world with setBackground().
Note that skyTxt is a list of textures passed to MirroredSky(). Lists are called
tables in LUA and are more flexible than C arrays.
--]]
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, -0.9659,
zip:getTexture("lensflares.png"),
5, 0.1
)
sun:setColor(0.855,0.475,0.298);
setSun(sun)
--[[
the sun
Now let’s add a sun with its lens flare. Simply create a Sun() object and add it
to the world with setSun(). The parameters specified are in order: the texture
of the corona, the size of the corona, the view direction from which the camera
sees the sun (3 coordinates), the texture of the lens flares (four images per
texture), the number of lens flares and, finally, their size. A color for the sun is
also specified (a bit reddish).
--]]
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(1,1,1)
terrainMaterial:setDiffuse(0,0,0)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local ground = FlatTerrain(terrainMaterial,500,125)
ground:setReflective()
ground:setShadowed()
ground:setShadowOffset(0.01)
setTerrain(ground)
--[[
the ground
After the sky and the sun, it’s the turn of the ground to put the feet of the
model on. This time we use a simple flat ground.
First we create the material to be attached to the ground. Materials are built
up from several types of colors and textures. In this case full ambient and no
diffuse color. The texture is taken from the marble.jpg image and it is going to
be tiled (that’s the meaning of the 1 as second argument).
Then the material is used to create the FlatTerrain. The size is 500 meters and
the texture is tiled on it 125 times. The terrain will be reflective and shadowed.
The shadow offset is the distance of the shadow from the ground to avoid the
bad artifacts of Z fighting.
Finally the ground is added to the world with setTerrain().
--]]
----MODELS----
9.1. MD3 MODELS
73
weapon = zip:getModel("gun.md3","gun.jpg")
weapon:rescale(0.04)
--[[
Now it’s the time of the models. First we load the gun of the warrior. The
MD3 is the model while the JPG is the texture applied to it. The rescale() is
necessary because the length unit of the model is to large for our environment.
MD3 model
loading
--]]
torso = 11 ---> TORSO_STAND
legs = 15
---> LEGS_IDLE
avatar = zip:getBot("warrior.mdl")
avatar:rescale(0.04)
avatar:pitch(-1.5708)
avatar:rotStanding(1.5708)
avatar:move(0,1,0)
avatar:getUpper():link("tag_weapon",weapon)
avatar:setUpperAnimation(torso)
avatar:setLowerAnimation(legs)
addObject(avatar)
--[[
The lines listed above load the real warrior. The calls to pitch() and rotStanding() are necessary because the axis of APOCALYX differ from those of MD3
models. The move() raises the model so it touch the ground with its feet.
Then we must link the weapon to the upper section of the bot (the place to
attach the weapon is marked by the string tag weapon) and finally we specify
the indexes of the starting animations for the upper and lower sections of the
bot (both are idle attitudes at the beginning). The addObject() function adds
the object to the world.
MD3 bot
loading
--]]
local shadow = Shadow(avatar)
addShadow(shadow)
--[[
Why not to add a shadow? The two lines above apply a planar shadow to the
object.
The initialization is almost done. The last steps are needed to define the text
to be displayed as help for the user.
--]]
----HELP---local help = {
"The model of this tutorial was made by:",
" ALPHAwolf ",
"The weapon of this tutorial was made by:",
" Janus ",
" ",
"[ MOUSE ] Look around",
"[ UP/DOWN ] Move Forward/Back",
"[PREV/NEXT] Raise/Lower View",
the shadow
74
CHAPTER 9. MODEL LOADING AND ANIMATION
"[ Z key ] Change Torso Animation",
"[ X key ] Change Legs Animation",
"[ C key ] Death Animation",
"[ Q,W,E,R ] Rotate/Bend Head",
"[ A,S,D,F ] Rotate/Bend Torso",
"[ SPACE ] Rotate Scene",
" ",
"[ENTER] Main Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
--[[
Note that the text is passed to setHelp() as a table of strings.
--]]
----DELETE ZIP---zip:delete()
end
--[[
The initialization is finally done. The zip must be deleted so it releases some
memory resources. The final end closes the init() function.
update()
Now let’s consider the update() function. It is called once per frame, so it
manages the evolution of the world initialized by init().
The first lines move and rotate the bot according to the index of the legs animation, so it can run or walk on the ground realistically.
--]]
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
local fwdSpeed = 0
local rotSpeed = 0
if legs == 6 then ---> LEGS_WALKCR
fwdSpeed = 2.5
rotSpeed = 0.31415
elseif legs == 7 then ---> LEGS_WALK
fwdSpeed = 2.5
rotSpeed = 0.6283
elseif legs == 8 then ---> LEGS_RUN
fwdSpeed = 5
rotSpeed = 0.6283
elseif legs == 9 then ---> LEGS_BACK
fwdSpeed = -3.5
rotSpeed = 0.31415
elseif legs == 10 then ---> LEGS_SWIM
9.1. MD3 MODELS
75
fwdSpeed = 2.5
rotSpeed = 0.31415
elseif legs == 17 then ---> LEGS_TURN
rotSpeed = 1.5708
end
avatar:walk(fwdSpeed*timeStep);
avatar:rotStanding(rotSpeed*timeStep);
--[[
The function walk() moves the bot forward to the specified distance, while
rotStanding() rotates it around its vertical axis. Note that the local variable
timeStep contains the elapsed time from the last rendering. The use of speeds
(for forward movement and rotations) to specify motion is the best choice to
achieve frame rate independence.
The following lines instead are needed to complete actions broken in more animations.
model animation
--]]
local stopped = avatar:getLower():getStoppedAnimation()
if stopped == 11 then ---> LEGS_JUMP
avatar:setLowerAnimation(12) ---> LEGS_LAND
legs = 12
elseif stopped == 13 then ---> LEGS_JUMPB
avatar:setLowerAnimation(14) ---> LEGS_LANDB
legs = 14
end
stopped = avatar:getUpper():getStoppedAnimation()
if stopped >= 6 then ---> TORSO_GESTURE
avatar:setUpperAnimation(11) ---> TORSO_STAND
end
--[[
When an animation ends, the getStoppedAnimation() function returns its index,
so the programmer can start the animation that should follow. For example,
after a jump, the bot must land.
The automatic bot movements are now completely defined. It’s the turn of the
camera.
--]]
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
local posX,posY,posZ = avatar:getPosition()
camera:pointTo(posX,posY+1,posZ)
end
--[[
The lines above control the movement of the camera when automatic rotation
about the origin is requested. The function pointTo() points the camera to the
specified point, while rotAround() rotates it around the origin.
The user can control the camera using some keyboard keys.
camera control
76
CHAPTER 9. MODEL LOADING AND ANIMATION
--]]
----MOVE CAMERA (KEYBOARD)---local moveSpeed = 15
local climbSpeed = 15
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(moveSpeed*timeStep)
elseif isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-moveSpeed*timeStep)
elseif isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
elseif isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
elseif isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
elseif isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
local posX,posY,posZ = camera:getPosition()
if posY < 0.5 then
camera:setPosition(posX,0.5,posZ)
end
end
--[[
model control
The lines above apply a movement to the camera according to the pressed key.
Again speeds combined with the duration of the timeStep guarantee frame rate
independence.
A similar structure permits the control of the bot’s sections bends and rotations
as follows.
--]]
----MODEL MOVEMENT---if isKeyPressed(string.byte("D")) then
local angle = 3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854,-0.5236)
elseif isKeyPressed(string.byte("F")) then
local angle = -3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854,-0.5236)
elseif isKeyPressed(string.byte("A")) then
local angle = 3.1415*timeStep
avatar:getUpper():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("S")) then
local angle = -3.1415*timeStep
avatar:getUpper():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("E")) then
local angle = 3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("R")) then
local angle = -3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("Q")) then
local angle = 3.1415*timeStep
9.1. MD3 MODELS
77
avatar:getHead():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("W")) then
local angle = -3.1415*timeStep
avatar:getHead():addYawAngle(angle,1.5708)
end
--[[
The yaw usually represent a rotation around the vertical axis, while pitch a
rotation around a horizontal axis.
Finally we want to control the camera with the mouse too.
mouse
management
--]]
----MOVE CAMERA (MOUSE)---if not rotateView then
local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
end
--[[
The function getMouseMove() returns the movement of the mouse and the orientation of the camera follows it.
Another important function, update(), is finally closed. Let’s consider the last
two.
The final() function deletes all the objects and clears the memory. It’s called
when a scene is changed and all the initialized objects must be finalized.
--]]
----FINALIZATION---function final()
----DELETE GLOBALS---rotateView = nil
torso = nil
legs = nil
----EMPTY WORLD---sun = nil
avatar = nil
if weapon then
weapon:delete()
weapon = nil
end
disableFog()
empty()
end
--[[
final()
78
deleting variables
from memory
keyDown()
CHAPTER 9. MODEL LOADING AND ANIMATION
As you can see, a lot of global variables defined in init() (they were global by
default because they were not defined local) are cleared. In LUA a variable is
removed from memory when its content becomes nil. Other objects needs to
be specifically deleted with delete(): In this case only the ¡i¿weapon¡/i¿ object
because it was linked to the bot but not added to the world. The objects that
were added to the world are deleted automatically with a call to empty().
The last function is keyDown(). It manages the pressed keys and performs
several actions.
--]]
----KEYBOARD---function keyDown(key)
if key == 32 then --> SPACE
releaseKey(32)
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
--[[
auto-rotation
When the space (ascii code 32) is pressed, the automatic rotation of the camera
begins or ends according to the current state.
--]]
----VARIOUS SCENE MODIFIERS---if key == string.byte("Z") then
releaseKey(string.byte("Z"))
if(torso >= 6) then ---> TORSO_GESTURE
torso = torso+1
else
legs = 15 ---> LEGS_IDLE
avatar:setLowerAnimation(legs)
torso = 11 ---> TORSO_STAND
end
if torso >= 13 then ---> MAX_TORSO_ANIMATIONS
torso = 6 ---> TORSO_GESTURE
end
avatar:setUpperAnimation(torso)
--[[
bot’s torso
animation
When Z is pressed, a torso animation is chosen.
--]]
elseif key == string.byte("X") then
releaseKey(string.byte("X"))
if(legs >= 6) then ---> LEGS_WALKCR
legs = legs+1
else
torso = 11 ---> TORSO_STAND
9.1. MD3 MODELS
79
avatar:setUpperAnimation(torso)
legs = 15 ---> LEGS_IDLE
end
if legs >= 18 then ---> MAX_LEGS_ANIMATIONS
legs = 6 ---> LEGS_WALKCR
end
avatar:setLowerAnimation(legs)
--[[
When X is pressed, a legs animation is chosen.
bot’s legs
animation
--]]
elseif key == string.byte("C") then
releaseKey(string.byte("C"))
if legs < 4 then ---> LEGS_DEAD3
torso = torso+2
legs = legs+2
else
torso = 0 ---> BOTH_DEATH1
legs = 0 ---> BOTH_DEATH1
end
avatar:setLowerAnimation(legs)
avatar:setUpperAnimation(torso)
end
--[[
When C is pressed, a death animation is chosen.
bot’s death
animation
--]]
----LOAD MAIN MENU---if key == 13 then
releaseKey(13)
if fileExists("main.lua") then
final()
dofile("main.lua")
end
end
end
--[[
Finally, when ¨enter¨ is pressed (ascii code 13), the main.lua script is executed.
This ends the description of the main functions necessary to the engine. The
lines above define the four functions, but the engine does not know yet their
meaning, so the following line is very important.
--]]
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
When it is executed, a Scene() object is created and specified as the current
scene with setScene(). The arguments are those four functions described above.
scene setting
80
CHAPTER 9. MODEL LOADING AND ANIMATION
This means that, if you pass the functions in the correct order, you can choose
the names that you prefer.
After the scene is defined, the engine begins to execute the init() function to
initialize the world and then the update() once per frame. When a key is pressed,
keyDown() is executed and, when the scene is substituted by another one, the
final() function is called before.
9.1.2
Preparing MD3 models for loading
Introduction
This section explains how to prepare an MD3 model for loading in APOCALYX.
MD3 is a very common Quake3 format and there is plenty of free models already
available for download (visit for example MYurlwww.polycount.com).
To introduce the argument, let’s make some step backward. After downloading and unzipping the file DemoPack0-lua-0.8.0.zip, you get several files
the most important of which for this tutorial are:
1. ModelMD3.lua, that contains the code of the demo. It is a simple text file
the contents of which were covered in the previous section.
2. DemoPack0.dat, that includes the resources (images, models etc.) needed
by the demos. It is a ZIP file the extension of which was renamed to
prevent unexperienced users to unzip it.
the models’
resources in
the zipped
package
If you open DemoPack0.dat with an unzip utility, you’ll find a lot of files in
it. The most important to follow this section are:
1. warrior.mdl, that contains information about the model structure and the
animation parameters;
2. lower.md3, upper.md3, head.md3, that are the 3 pieces in which a Quake3
bot is broken;
3. body.jpg, head.jpg, that are the images to be applied as textures to the
model.
Now let’s see how to prepare all these files starting from a bot modelled for
Quake3 or one of its MODs3 .
Preparing the MD3 files
pieces to build
an MD3 model
The files needed from a standard ¨*.pk3¨ file to reconstruct a bot are:
1. lower.md3, upper.md3, head.md3 (or equivalent names) taken from the
subdirectory of the model. They represent 3 different sections of the
model: The legs, the torso and the head, of course. Remember that even
the ¨*.pk3¨ files are simple ZIP the extension of which was renamed, so
you can read their contents and browse their subdirectories with an unzip
utility.
3 remember to check the license of the resources before using them in your
products, in particular when commercial use is planned - for example, Quake3 resources
cannot be used without a license from ID Software
9.1. MD3 MODELS
81
2. body.jpg, head.jpg (or equivalent names), that are the textures applied to
the sections. APOCALYX supports only one texture per section.
3. animation.cfg, that is a file that accompanies the model files and specifies
all the animation data.
Once you get all these files you can simply zip the first two types in the resource file that the script will use. In the case of this demo that file is simply
DemoPack0.dat. The third type instead (animation.cfg) must be renamed to
¨warrior.mdl¨ or anything else and edited as described here:
First of all, you must add a single line that follows this simple simple format:
lower.md3 body.jpg upper.md3 * head.md3 head.jpg
where ¨lower.md3¨ is the model for the legs, ¨body.jpg¨ is the texture to be
applied to the legs, ¨upper.md3¨ is the model for the torso, ¨*¨ means that the
torso uses the same texture of the legs (but you can specify whatever image you
want), ¨head.md3¨ is the model for the head, ¨head.jpg¨ is the texture for the
head (use another ¨*¨ if all the 3 pieces share the same texture).
Then you need to remove from warrior.mdl some lines (not useful for the engine)
and keep only the ones that specify the animation frames. The text will look
something like:
lower.md3 body.jpg upper.md3 * head.md3 head.jpg
0
30
0
25
// BOTH_DEATH1
29
1
0
25
// BOTH_DEAD1
30
30
0
25
// BOTH_DEATH2
... and so on.
Remember to leave not any blank line in the text otherwise the
parser will complain.
Finally, the warrior.mdl must be zipped in the resource file with the already
zipped models and textures.
The MDX Format
If you don’t need all the animations included in a Quake3 MD3 model, you may
consider to use my MD3toMDX.exe utility.
This program converts the MD3 files that made up a complete model (legs, torso
and head) and their associated ¨animation.cfg¨ file in a collection of 3 MDX
files. MDX is a format used by APOCALYX to reduce the size of existent MD3
files stripping unnecessary animations for a particular application.
So MDX are simple MD3 from which some of the animations are removed. I
want to keep the data of my demos as small as possible to reduce the size of
downloaded files, thus I remove from the MD3 files all the unnnecessary animations for the demos. This means that if you put a full MD3 version in place
of a MDX, my demos don’t work as inteded because the ordinal number of the
animations changes. MDX includes even the animation data, so there is no need
for an animation.cfg file.
To get an MDX you must:
MD3 to MDX format
82
CHAPTER 9. MODEL LOADING AND ANIMATION
1. Get the file MD3toMDX.exe
2. Put the 3 MD3 files (head, legs and torso) in the same folder of MD3toMDX.exe
3. Put a file with extension ¨.mdl¨ in the same folder of MD3toMDX.exe
The last file must use the following format: the first line is the same as the
one described in the previous section for warrior.mdl and the other lines
are taken from ¨animation.cfg¨. The ¨.mdl¨ file must be edited to remove
unwanted animations: When a line starts with an asterisk, the animation
is removed from the model (in the following example, BOTH DEATH1
and BOTH DEAD1 are removed, while BOTH DEATH2 is kept and so
on)
lower.md3 lowerTxt.jpg upper.md3 * head.md3 headTxt.jpg
*0 30 0 25 // BOTH_DEATH1
*29 1 0 25 // BOTH_DEAD1
30 30 0 25 // BOTH_DEATH2
4. Write at the command line:
MD3toMDX fileName.mdl
Three MDX files will appear in the folder (with names matching the previous MD3 files but smaller in size)
5. To load MDX models using a script, the ¨.mdl¨ does not need any more
the 25 lines that describe the animation data and you can remove them
9.2
MD2 Models
This section explains how to load and animate an MD2 model in a basic environment. The tutorial about MD3 models is referenced to skip common parts.
Figure 9.2: Model in MD2 format
9.2.1
Comments to the ModelMD2.lua source
First of all you should read carefully the tutorial about MD3 models because a
lot of code regarding the set up of the environment is the same. We cover here
9.2. MD2 MODELS
83
only the differences related to the model format side.
This tutorial refers to the ModelMD2.lua sources included in the file DemoPack0lua-0.8.0.zip. Again it is better to keep a copy of the source at hand for easy
reference. Also the HTML manual with the list of APOCALYX API may be
useful to better understand the meaning of some of the function parameters.
Now let’s read the source and comment it. Skipped parts of the code are
marked with –[cut]–.
----MD2 Model
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
--[cut]---[[
As we already learned in the tutorial about MD3 models, the engine needs four
functions to realize the rendering cycle of the scene. Now we cut the code for
the camera, skybox, sun, terrain and help lines because already described, thus
we jump directly to the code of the model.
init()
]]-----MODELS---animation = 0
weapon = zip:getBasicModel("ogroweapon.md2","ogroweapon.jpg")
weapon:rescale(0.04)
weapon:pitch(-1.5708)
weapon:rotStanding(1.5708)
weapon:move(0,1,0)
weapon:setAnimation(animation)
addObject(weapon)
local shadow = Shadow(weapon)
shadow:setMaxRadius(weapon:getMaxRadius()*3)
addShadow(shadow)
--[[
First of all we load the weapon of the model. MD2 models are made of a single
piece (MD3 are broken in 3 pieces instead - legs, torso and head) so they are less
flexible but more simple to manage. Anyway the distinction between weapon
and character is still valid so weapon change is easy.
Following line by line the code we see that
1. animation is a global variable that stores the index of the current animation
2. MD2 models are loaded with getBasicModel() (not getModel() like MD3)
and the two strings as argument specify respectively the MD2 model file
and the image file from which the texture is derived. An optional third
argument may specify another grayscale image with the alpha channel of
the texture.
3. then follow some rotations, translations and rescaling to move the model
in place. Again there is the same problem about axis orientations already
explained for MD3s.
MD2 model
loading
84
CHAPTER 9. MODEL LOADING AND ANIMATION
4. finally we choose the current animation, add the model to the world with
addObject() and apply a planar shadow.
Note that the camera uses a clipping algorithm to decide if a model is
in view or not. That algorithm uses for the models the maxRadius property
computed on loading, but it must be set explicitly for shadows. In the code,
the maxRadius of the model was increased and passed to the shadow because
shadows could still be in view when the model is out of view.
Le’s now consider the character.
]]-model = zip:getBasicModel("ogro.md2","ogro.jpg")
model:rescale(0.04)
model:pitch(-1.5708)
model:rotStanding(1.5708)
model:move(0,1,0)
model:setAnimation(animation)
addObject(model)
shadow = Shadow(model)
shadow:setMaxRadius(model:getMaxRadius()*3)
addShadow(shadow)
--[cut]-end
--[[
As you can easily see, we follow the same steps already made for the weapon.
In practice, the ensamble character plus weapon is a superposition of two independent models that appear linked when synchronized. No API is currently
available to manage the two pieces as one, but it’s not difficult to write a helper
direclty in LUA.
That ends the init() function and nothing really new appears in update() or
final.
]]-----LOOP---function update()
--[cut]-end
----FINALIZATION---function final()
----DELETE GLOBALS---animation = nil
--[cut]-end
--[[
In reality the ModelMD2 demo lacks the control code to move the model around
when a walking or run animation is at work. Anyway the code is very similar to
the one of ModelMD3, one must only choose carefully the linear and rotational
speeds according to the index of the animation. This could be a useful exercise
for the reader.
9.2. MD2 MODELS
differences
between
MD2 and MD3
models
85
Tip: compare the sources of ModelMD2.lua and ModelMD3.lua to figure out
which modifications are needed. Remember that the walk() function is not
available for BasicModels so you must move forward the model using move().
Anyway you must consider that, because of the axis convention problem already
mentioned above, you must get the forward direction of the model using the
function getSideDirection() instead of the more obvious getViewDirection().
Let’s now consider the final function: keyDown(). The animation of the model
is chosen with the Z key.
keyDown()
]]-----KEYBOARD---function keyDown(key)
--[cut]-if key == string.byte("Z") then
releaseKey(string.byte("Z"))
animation = animation+1
if animation >= 20 then ---> MAX_ANIMATIONS
animation = 0
end
model:setAnimation(animation)
weapon:setAnimation(animation)
end
--[cut]-end
The idea behind the code is very simple. Every time the Z key is pressed a
new animation is performed in sequence until the maximum number is reached
(usually 20).
The animations of MD2 models usually have a fixed meaning, because they
are used as standard models in Quake2, but sometimes their authors become
creative, so the meaning may vary. It is better to verify case by case which
index corresponds to which kind of animation.
9.2.2
Preparing MD2 models for loading
This section explains how to prepare an MD2 model for loading in APOCALYX
and luckily the procedure is simpler than the one for MD3 models. MD2 is a
very common Quake2 format and there is plenty of free models already available
for download (visit for example http://www.polycount.com).
Again, to introduce the argument, let’s make some step backward. After
downloading and unzipping the file DemoPack0-lua-0.8.0.zip, you get several
files:
1. ModelMD2.lua, that contains the code of this demo. a simple text file the
contents of which were explained in the previous section.
2. DemoPack0.dat, that includes the resources (images, models etc.) needed
by the demos, a ZIP file renamed.
If you open DemoPack0.dat with an unzip utility, you’ll find a lot of files in
it among which:
the models’
resources in
the zipped
package
86
CHAPTER 9. MODEL LOADING AND ANIMATION
1. ogro.md2, that contains information about the model structure;
2. ogro.jpg, that is the image to be applied as a texture to the model.
The files above are taken from a standard ¨*.pak¨ file (again a ZIP file
renamed) from Quake2 or one of its MODs (remember to check the license
of the resources before using them in your products, in particular
when commercial use is planned - for example, Quake2 resources can
not be used without a license from ID Software).
Usually the file of the model is called tris.MD2 and stored deep in the directory
structure of the ¨*.pak¨ file togheter with its textures in TGA format.
After retrieving the files of interest, you can rename and put them directly
in the resource file ready to be read by your LUA scripts.
9.3
Cal3D Models
This section explains how to load and animate a Cal3D model in a basic environment. The tutorials about MD3 and MD2 models are referenced when
possible.
Figure 9.3: Models in MD3 format
9.3.1
Comments to the ModelCal3D.lua source
Before reading this tutorial, it is necessary to follow carefully the tutorial about
MD3 models because some parts regarding the set up of the basic environment
are very similar. Only the differences about the model management are treated
here.
This document refers to the ModelCal3D.lua sources included in the file
DemoPack0-lua-0.8.0.zip. It’s recommended to keep a paper copy of the
source at hand for easy reference. It’s better also to keep open the HTML manual with the list of APOCALYX API to understand the use of some function
calls.
It’s time to read the source and comment it. Skipped lines are marked with
–[cut]–.
----Cal3D Model
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
9.3. CAL3D MODELS
87
----INITIALIZATION---function init()
--[cut]---[[
As usual the engine needs four functions to render the scene. In the initialization
we cut the already explained code for the camera, skybox, sun, terrain and help
lines, but consider an element of the environment not analyzed before: The
cloud layer.
init()
]]-----CLOUDLAYER---local cloudsImage = zip:getImage("cloudlayer2.jpg")
local alphaImage = zip:getImage("cloudlayer2.png")
cloudsImage:addAlpha(alphaImage)
local cloudsTexture = Texture(cloudsImage,1)
cloudsImage:delete()
alphaImage:delete()
local material = Material()
material:setEmissive(1,1,1)
material:setEnlighted(false)
material:setDiffuseTexture(cloudsTexture)
local cloudLayer = CloudLayer(material,2000,100,6)
cloudLayer:setSpeed(10,10)
cloudLayer:setColor(0.8,0.6,0.3)
setCloudLayer(cloudLayer)
--[cut]---[[
A cloud layer is a simple plane, apparently infinite, placed at a certain height,
over which is applied a transparent texture with cloud patterns. The cloud layer
can move in a given direction and even be animated.
The lines listed above perform the following actions:
• First of all, the texture is created. The texture is built from two images,
a color one and an alpha layer for the transparency.
• Then the texture becomes the diffuse texture of a material. This material is not enlighted: This means that light sources does not affect its
appearance.
• Finally the material is passed to the constructor of the cloud layer. The
cloud layer of the demo has a side of 2000 meters length, the height is 100
meters and the texture is tiled 6 times.
• At the end, after giving a reddish color with setColor() (this call affects
the emissive component of the material), the cloud layer is added to the
world with a call to setCloudLayer(). More layers may be superimposed
with a call to addCloudLayer().
Now let’s consider the main character of this tutorial: The Cal3D model.
the cloud layer
88
CHAPTER 9. MODEL LOADING AND ANIMATION
]]-----MODELS---animation = 0
model = zip:getAdvancedModel("paladin.cfg")
model:pitch(-3.1415/2);
model:rotStanding(3.1415);
model:scale(1/40.0);
model:setScaled()
model:setCulled(false)
model:setMaxRadius(2)
model:move(0,0,0)
model:disableSprings()
addObject(model)
--[cut]-end
--[[
Cal3D models
loading
The lines above are very similar to those of the MD2 or MD3 models, but there
are some differences.
• The model is loaded with getAdvancedModel(), not getModel() or getBasicModel(). That function is devoted to Cal3D models.
• Again some rotation are needed to place the model in its right orientation.
The different convention of the axis strikes again.
• Even this time the model needs a scaling factor, but now the coordinates of
the vertices are not trasformed (at least in the current version of APOCALYX) so we must specify that a rescaling of the normals is needed too with
a call to setScaled(). This step is not necessary for MD2 or MD3 models.
In addition the maxRadius property must be explicitly set because it is
not automatically computed yet.
• Another difference is the call to move(). This time the origin of the
model is placed between its feet, so no translation is necessary (the call
move(0,0,0) may be removed since a model is placed there by default).
• Two final notes: setCulled() specifies that both front and back faces of
triangles must be rendered (a particularity of the model of the demo)
and disableSprings() requests that the inner physics engine of the Cal3D
models must be disabled (quite annoying for this particular model the bad
behavior of its cloths).
• As usual, the model is finally added to the world with addObject().
]]-----LOOP---function update()
--[cut]-end
----FINALIZATION----
9.3. CAL3D MODELS
89
function final()
--[cut]-end
--[[
As already happened in the MD2 models tutorial, no interesting stuff is found
in update() and final().
Let’s consider finally the simple lines that control the choice of the current
animation.
]]-----KEYBOARD---function keyDown(key)
--[cut]-if key >= string.byte("0") and key <= string.byte("9") then
local anim = key-string.byte("0")
if anim < model:getAnimationsCount() then
model:clearCycle(animation)
model:blendCycle(anim)
animation = anim
end
end
--[cut]-end
As you can see, every time a key between ¨0¨ and ¨9¨ (a number key) is pressed
a new animation is performed. If the key does not correspond to an existing
animation, it is ignored. Then the old animation is cleared with clearCycle()
and the new one is applied with blendCycle().
The control of Cal3D models may be more sophisticated. Animation can be
cleared with a delay factor while another animation fades in, thus transitions
between animation are very smooth. In addition more animation can be blended
togheter during the same action. These advanced features are not shown here,
but they are controlled by additional parameters of clearCycle() and blendCycle() that specify the weight of a given animation and the delay to reach its
maximum weight. This topics will be covered in another tutorial.
9.3.2
Preparing Cal3D models for loading
This section explains how to package Cal3D models for loading in APOCALYX.
Cal3D is the format of the Character Animation Library, an open source project
by Bruno Heidelberger4 . APOCALYX includes one of the latest versions of the
Cal3D - Character Animation Library.
As usual, to introduce the argument, let’s make some step backward. After
downloading and unzipping the file DemoPack0-lua-0.8.0.zip, you get several
files among which:
1. ModelCal3D.lua, the code of this demo, a text file the contents of which
were explained in the previous section.
4 visit http://www.cal3d.org for more details about the format
keyDown()
90
CHAPTER 9. MODEL LOADING AND ANIMATION
2. DemoPack0.dat, that includes the resources (images, models etc.) needed
by the demos, a ZIP file renamed.
the models’
resources in
the zipped
package
If you open DemoPack0.dat with an unzip utility, you’ll find a lot of files in it.
The Cal3D model needs a lot of them:
1. paladin.cfg, that contains the information needed to reconstruct the whole
model;
2. paladin.csf, the skeleton of the model;
3. several paladin *.caf, the animation data of the different actions (walk,
run etc.);
4. several paladin *.cmf, the data of the meshes;
5. several paladin *.crf, the data of the materials;
6. several paladin *.jpg, the images of the textures.
The Cal3D library can use a binary format (like the one of the files listed above)
or a readable XML format. APOCALYX does not support the second type when
the files are zipped in a package, so be careful to verify the format of the files
when you zip them toghether.
The most important file, from the programmer point of view, is the paladin.cfg.
It is not automatically generated by modelling applications and must follow this
format:
# model: paladin
skeleton=paladin.csf
animation=paladin_walk.caf
animation=paladin_idle.caf
# some lines skipped
mesh=paladin_body.cmf
mesh=paladin_cape.cmf
# some lines skipped
material=paladin_cape.crf
material=paladin_head.crf
# some lines skipped
The tags skeleton, animation, mesh and material specify the name of the file
that the engine must look for when loading the model. Lines beginning with
¨#¨ are comments and ignored. Apart cfg, No other files (csf, caf, cmf, crf)
need to be modified before packaging, if they are already in binary format.
Chapter 10
Levels Loading
10.1
BSP Levels
The file that describes the geometry of a level uses the BSP format (a Quake3
format) and with a simple procedure is possible to get such a file from existing
MODs of Quake3. The engine does not include a complete viewer of BSP files, in
fact only static textures and lightmaps are attached to the geometry and several
features, such as Quake3 shaders and moveable entities, are ignored. Ready-touse resources apart, the levels may also be created with a specific editor know
as GtkRadiant1 and the procedure to load the personalized levels is identical to
the one for existing levels, of course.
Figure 10.1: Levels in BSP format
10.1.1
Comments to the BspLevel.lua source
Some parts of this tutorial use algorithms similar to the ones already used for
the tutorial about loading and animation of MD3 models. In fact, in this demo
we make an MD3 model move through a BSP scene, so you are going to learn
how to use the collision detection features hardwired in BSP levels.
This document refers to the BslLevel.lua sources included in the file DemoPack0lua-0.8.0.zip.
1 visit http://www.qeradiant.com to get a free, full-working copy of the editor, but be sure
to download the version specific for Quake3
91
92
CHAPTER 10. LEVELS LOADING
Let’s read the source and comment it. Again skipped lines are marked with
–[cut]–.
----BSP LEVEL DEMO
----A viewer of BSP levels
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
---------------------MENU SCENE------------------------MENU INITIALIZATION---function MENU_init()
selectedLevel = -1
if fileExists("pak0.pk3") then
if not levelNames then
local levelsCount = 1
levelNames = {}
zip = Zip("pak0.pk3")
zip:gotoFirstFile()
repeat
local fileName = zip:getZippedFileName()
if string.find(fileName,".bsp",1,1) then
levelNames[levelsCount] = fileName
levelsCount = levelsCount+1
end
until not zip:gotoNextFile()
end
end
if levelNames then
local help = {"Choose a BSP level:"}
local levelsCount = table.getn(levelNames)
for ct = 1, levelsCount do
if ct < 10 then
help[ct+1] = "["..ct.."] "..levelNames[ct]
else
help[ct+1] = "["..string.char(string.byte("A")+ct-10).."] "..levelNames[ct]
end
end
help[levelsCount+2] = " "
help[levelsCount+3] = "[ENTER] Demos Menu"
setHelp(help)
showHelpUser()
hideConsole()
else
setScene(Scene(BSP_init,BSP_update,BSP_final,BSP_keyDown))
end
end
----MENU LOOP----
10.1. BSP LEVELS
93
function MENU_update()
end
----MENU FINALIZATION---function MENU_final()
----DELETE GLOBALS-------EMPTY WORLD---empty()
end
----MENU KEYBOARD---function MENU_keyDown(key)
selectedLevel = key-string.byte("0")
if selectedLevel > 9 then
selectedLevel = selectedLevel-string.byte("A")+string.byte("0")+10
end
if selectedLevel < 1 or (selectedLevel > table.getn(levelNames)) then
selectedLevel = -1
if key == 13 then ---> RETURN
releaseKey(13)
levelNames = nil
selectedLevel = nil
if fileExists("main.lua") then
final()
dofile("main.lua")
end
end
else
setScene(Scene(BSP_init,BSP_update,BSP_final,BSP_keyDown))
end
end
--------------------------BSP LEVEL SCENE-----------------------------BSP INITIALIZATION---function BSP_init()
showLoadingScreen()
----CAMERA---setAmbient(.2,.2,.2)
setPerspective(60,3,12000)
local camera = getCamera()
camera:reset()
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
94
CHAPTER 10. LEVELS LOADING
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("skyboxTop2.jpg"),
zip:getTexture("skyboxLeft2.jpg"),
zip:getTexture("skyboxFront2.jpg"),
zip:getTexture("skyboxRight2.jpg"),
zip:getTexture("skyboxBack2.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky)
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, 0.9659,
zip:getTexture("lensflares.png"),
5, 0.2
)
sun:setColor(0.855,0.475,0.298);
setSun(sun)
----EMITTER---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
shotEmitter = Emitter(5,.1,3)
shotEmitter:setTexture(fireTexture,1)
shotEmitter:setVelocity(300,0,0, 0)
shotEmitter:setColor(1,1,1,1, 1,.5,0,0)
shotEmitter:setSize(22.5,22.5)
shotEmitter:setGravity(0,0,0, 0,0,0)
shotEmitter:setOneShot()
shotEmitter:reset()
addObject(shotEmitter)
shotEmitter:hide()
fireTexture:delete()
----BSP---if selectedLevel < 0 then
bsp = zip:getLevel("maze.bsx",2.5)
else
local pak = Zip("pak0.pk3")
bsp = pak:getLevel(levelNames[selectedLevel],2.5)
pak:delete()
end
bsp:setShowUntexturedMeshes()
bsp:setShowUntexturedPatches()
bsp:setDefaultTexture(zip:getTexture("bricks.jpg",1))
setScenery(bsp)
----MODELS---flyModeActive = 0
runModeActive = 1
10.1. BSP LEVELS
isWalking = 0
isRotating = 0
linkTransform = Transform()
legs = 5
---> LEGS_IDLE
weapon = zip:getModel("gun.md3","gun.jpg")
avatar = zip:getBot("wrokdam.mdl",1)
avatar:pitch(-1.5708)
local startX, startY, startZ = bsp:getStartingPosition()
avatar:move(startX,startY,startZ)
avatar:move(0,0,0)
avatar:getUpper():link("tag_weapon",weapon)
avatar:setUpperAnimation(4) ---> TORSO_STAND
avatar:setLowerAnimation(legs)
addObject(avatar)
setListenerScale(32)
local shotSound = zip:getSample3D("shot.wav");
shotSound:setVolume(255)
shotSound:setMinDistance(8)
shotSource = Source(shotSound,avatar);
shotSource:getSound3D():stop()
addSource(shotSource)
local runSound = zip:getSample3D("run.wav");
runSound:setLooping(1)
runSound:setVolume(255)
runSound:setMinDistance(8)
runSource = Source(runSound,avatar);
runSource:getSound3D():stop()
addSource(runSource)
----HELP---local help = {
"The model of this tutorial was made by:",
" Grant Struthers <TheGragster@yahoo.com>",
"The weapon of this tutorial was made by:",
" Janus <janus@planetquake.com>",
" ",
"[
MOUSE ] Look around",
"[LEFT CLICK] Shoot",
"[ UP/DOWN ] Move Forward/Back",
"[LEFT/RIGHT] Rotate Left/Right",
"[
SPACE ] Walk/Run Mode",
"[
DEL
] Fly/Follow Model",
"[ BACKSPACE] Restart Model",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP----
95
96
CHAPTER 10. LEVELS LOADING
zip:delete()
end
----BSP LOOP---function BSP_update()
local camera = getCamera()
local timeStep = getTimeStep()
if avatar:getUpper():getStoppedAnimation() == 1 then ---> TORSO_ATTACK
avatar:setUpperAnimation(4) ---> TORSO_STAND
end
if
isMouseLeftPressed() and
(avatar:getUpperAnimation() ~= 1) ---> TORSO_ATTACK
then
avatar:setUpperAnimation(1) ---> TORSO_ATTACK
shotSource:getSound3D():play()
if avatar:getLinkTransform("tag_weapon",linkTransform) then
shotEmitter:set(linkTransform)
shotEmitter:moveSide(30)
shotEmitter:show()
shotEmitter:reset()
end
end
local rotAngle = .15*timeStep
if flyModeActive == 1 then
if isKeyPressed(38) or isKeyPressed(40) then ---> VK_UP || VK_DOWN
local posX,posY,posZ = camera:getPosition()
local moveSpeed = 300
local k = timeStep*moveSpeed
if isKeyPressed(40) then ---> VK_UP
k = -k
end
local velX,velY,velZ = camera:getViewDirection()
posX,posY,posZ =
bsp:slideCollision(posX,posY,posZ,k*velX,k*velY,k*velZ,14,14,14)
camera:setPosition(posX,posY,posZ)
end
local dx, dy = getMouseMove()
if dx ~= 0 then
camera:rotStanding(-dx*rotAngle)
end
if dy ~= 0 then
camera:pitch(-dy*rotAngle)
end
else
local velX,velY,velZ
local posX,posY,posZ = avatar:getPosition()
if isKeyPressed(38) or isKeyPressed(40) then ---> VK_UP || VK_DOWN
if legs == 5 then ---> LEGS_IDLE
isWalking = 1
10.1. BSP LEVELS
97
runSource:getSound3D():play()
if isKeyPressed(38) then ---> VK_UP
if runModeActive == 1 then
legs = 3 ---> LEGS_RUN
avatar:setLowerAnimation(legs)
else
legs = 2 ---> LEGS_WALK
avatar:setLowerAnimation(legs)
end
else
legs = 4 ---> LEGS_BACK
avatar:setLowerAnimation(legs)
end
end
local moveSpeed = 30*timeStep
if isKeyPressed(40) then ---> VK_DOWN
moveSpeed = -7*moveSpeed
elseif runModeActive == 1 then
moveSpeed = 10*moveSpeed
else
moveSpeed = 5*moveSpeed
end
velX,velY,velZ = avatar:getSideDirection()
velX = velX*moveSpeed
velY = velY*moveSpeed-5.8
velZ = velZ*moveSpeed
else
if isWalking == 1 then
legs = 5 ---> LEGS_IDLE
avatar:setLowerAnimation(legs)
isWalking = 0
runSource:getSound3D():stop()
end
velX,velY,velZ = 0,-5.8,0
end
posX,posY,posZ = bsp:slideCollision(posX,posY,posZ,velX,velY,velZ,14,28,14)
avatar:setPosition(posX,posY,posZ);
if isKeyPressed(37) or isKeyPressed(39) then ---> VK_LEFT || VK_RIGHT
if (isWalking == 0) and (isRotating == 0) then
isRotating = 1
avatar:setLowerAnimation(7) ---> LEGS_TURN
end
if isKeyPressed(37) then ---> VK_LEFT
avatar:rotStanding(1.5708*timeStep)
end
if isKeyPressed(39) then ---> VK_RIGHT
avatar:rotStanding(-1.5708*timeStep)
end
else
if isRotating == 1 then
98
CHAPTER 10. LEVELS LOADING
isRotating = 0
avatar:setLowerAnimation(5) ---> LEGS_IDLE
end
end
local dx,dy = getMouseMove()
if dx ~= 0 then
local headAngle = avatar:getHead():getYawAngle()
if headAngle ~= 0 then
local newHeadAngle = headAngle-dx*rotAngle
if headAngle*newHeadAngle < 0 then
avatar:getHead():setYawAngle(0)
avatar:getUpper():addYawAngle(newHeadAngle-headAngle,1.047)
else
avatar:getHead():addYawAngle(-dx*rotAngle,1.5708)
end
else
local diff = avatar:getUpper():addYawAngle(-dx*rotAngle,1.047)
if diff ~= 0 then
avatar:getHead():addYawAngle(diff,1.5708)
end
end
end
if isWalking == 1 then
local headAngle = avatar:getHead():getYawAngle()
local upperAngle = avatar:getUpper():getYawAngle()
local yawAngle = headAngle+upperAngle
if yawAngle ~= 0 then
local rotation = timeStep*3.1415
if yawAngle > 0 then
rotation = -rotation
end
avatar:rotStanding(-rotation)
if headAngle ~= 0 then
local newHeadAngle = headAngle+rotation
if headAngle*newHeadAngle < 0 then
avatar:getHead():setYawAngle(0)
avatar:getUpper():addYawAngle(newHeadAngle-headAngle,1.047)
else
avatar:getHead():addYawAngle(rotation,1.5708)
end
else
local diff = avatar:getUpper():addYawAngle(rotation,1.047)
if diff ~= 0 then
avatar:getHead():addYawAngle(diff,1.5708)
end
end
end
end
if dy ~= 0 then
local headAngle = avatar:getHead():getPitchAngle()
10.1. BSP LEVELS
99
if headAngle ~= 0 then
local newHeadAngle = headAngle-dy*rotAngle
if headAngle*newHeadAngle < 0 then
avatar:getHead():setPitchAngle(0)
avatar:getUpper():addPitchAngle(
newHeadAngle-headAngle,1.047,-.5236
)
else
avatar:getHead():addPitchAngle(
-dy*rotAngle,.5236,-.7854
)
end
else
local diff = avatar:getUpper():addPitchAngle(
-dy*rotAngle,1.047,-.5236
)
if diff ~= 0 then
avatar:getHead():addPitchAngle(diff,.5236,-7854)
end
end
end
if isMouseRightPressed() then
if
(avatar:getUpper():getPitchAngle() ~= 0) or
(avatar:getUpper():getYawAngle() ~= 0) or
(avatar:getHead():getPitchAngle() ~= 0) or
(avatar:getHead():getYawAngle() ~= 0)
then
avatar:getUpper():setPitchAngle(0)
avatar:getUpper():setYawAngle(0)
avatar:getHead():setPitchAngle(0)
avatar:getHead():setYawAngle(0)
end
end
camera:set(avatar)
camera:rotateT(avatar:getUpper())
camera:rotateT(avatar:getHead())
camera:exchangeYZX()
local posX,posY,posZ = camera:getPosition()
posY = posY+30
local velX,velY,velZ = camera:getViewDirection()
velX = -90*velX
velY = -90*velY
velZ = -90*velZ
posX,posY,posZ = bsp:checkCollision(posX,posY,posZ,velX,velY,velZ,3,3,3)
camera:setPosition(posX,posY,posZ)
end
end
----BSP FINALIZATION----
100
CHAPTER 10. LEVELS LOADING
function BSP_final()
----DELETE GLOBALS---selectedLevel = nil
levelNames = nil
runSource = nil
shotSource = nil
legs = nil
linkTransform = nil
flyModeActive = nil
runModeActive = nil
isWalking = nil
isRotating = nil
----EMPTY WORLD---bsp = nil
avatar = nil
shotEmitter = nil
if weapon then
weapon:delete()
weapon = nil
end
empty()
end
----BSP KEYBOARD---function BSP_keyDown(key)
----VARIOUS SCENE MODIFIERS---if key == 46 then ---> VK_DELETE
releaseKey(46)
flyModeActive = 1-flyModeActive
if flyModeActive == 1 then
local camera = getCamera()
camera:set(avatar)
camera:exchangeYZX()
camera:move(0,60,0)
end
elseif key == 32 then ---> SPACE
releaseKey(32)
runModeActive = 1-runModeActive
elseif key == 8 then ---> VK_BACK
releaseKey(8)
local posX,posY,posZ = bsp:getStartingPosition()
avatar:setPosition(posX,posY,posZ)
end
----LOAD MAIN MENU---if key == 13 then ---> RETURN
releaseKey(13)
if levelNames then
empty()
setScene(Scene(MENU_init,MENU_update,MENU_final,MENU_keyDown))
else
10.1. BSP LEVELS
101
levelNames = nil
selectedLevel = nil
if fileExists("main.lua") then
final()
dofile("main.lua")
end
end
end
end
----------------------SCENE SETUP---------------------setScene(Scene(MENU_init,MENU_update,MENU_final,MENU_keyDown))
Figure 10.2: Levels in BSP format
10.1.2
Notes on Quake3 BSP Levels Editing
If you want to create your own levels with GtkRadiant, you must consider only a
few particularities. The engine recognizes points marked as ¨info player deathmatch¨
as starting points for the bots2 . Additional items that may represent medikits,
energy, armors, bullets and grenades recharges are: ¨item health¨, ¨item regen¨,
¨item armor body¨, ¨ammo bullets¨ and ¨ammo grenades¨. Other info marks
are simply ignored.
Another particularity you must consider is the lighting in BSP levels. Pointlight objects apart, trated transparently to the user by the editor during lightmap
creation, to simulate the sun the modeller must create a Quake3 shader as follows: first of all one can use whatever sky texture, but the q3map sun data must
be similar to
textures/my_tex/sky {
surfaceparm noimpact
surfaceparm nolightmap
// surfaceparm sky
2 this info mark, as well as the others described below, may be read through several functions
of the LUA API interface and freely interpreted by the programmer as she prefers
info marks
sun lighting
102
CHAPTER 10. LEVELS LOADING
q3map_sun 1 1 1 100 270 15
q3map_surfacelight 80
qer_editorimage textures/guntactyx/sky.tga
skyparms - 512 - {
map textures/guntactyx/sky.tga
tcMod scale 3 2
tcMod scroll 0.15 0.15
depthWrite
}
}
The modeller must change the q3map sun parameter to make the sun direction
coincide with the sun loaded by the engine.
Once a map is compiled with GtkRadiant or an existent level is extracted
from a ¨*.pk3]¨ file, the ¨*.bsp]¨ file so obtained is the one that must be provided
to the engine. No complex procedures are needed this time to prepare the file
for loading.
10.1.3
BSP to BSX
conversion
The BSX Format
As APOCALYX is not a complete BSP viewer, a lot of information id the level
are not necessary. Thus it is possible to reduce the site of the file stripping out
unnecessary information. Also the lighmaps, that are images in TGA format,
can be extracted from the file. The modified format that the engine accepts has
extension BSX. The utility that operates this conversion is BSPtoBSX.exe.
When you have your level nameḃsp file, put it in the folder of the program
¨BSPtoBSX.exe¨ and run it from the command line as follows:
BSPtoBSX level\_name.bsp
You should get the files: level name.bsx and several level name XX.tga. The
latter are the images that contain the lightmaps.
Completed these steps, you can put all the files that you have obtained in
your own resource package. You must also include the texture applied to the
geometry of the level, specifying their correct paths, as specified by the editor.
10.2
Outdoor Levels
10.2.1
Terrains
----VOLCANO
----Ocean waves & Particles emitters
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----SUPPORT FUNCTION----Create an isle
function generateIsle(
zip, terrainTextureName, detailedTexture, detailRepeat, heightFieldName,
waterLevel, width, depth, height, x, y
)
----LOAD IMAGES AND CREATE TEXTURES---local terrainImage = zip:getImage(terrainTextureName)
10.2. OUTDOOR LEVELS
local heightImage = zip:getImage(heightFieldName)
terrainImage:addAlpha(heightImage,64,9)
local terrainTexture = Texture(terrainImage)
terrainImage:delete()
----TERRAIN MATERIAL---local terrainMaterial = Material()
terrainMaterial:setDiffuseTexture(terrainTexture)
terrainMaterial:setGlossTexture(detailedTexture)
----CREATE HEIGHTFIELD---local heightField = HeightField(
heightImage,terrainMaterial,width,depth,height,0,8
)
terrainTexture:delete()
terrainMaterial:delete()
heightField:move(x,-waterLevel,y)
addObject(heightField)
return isle
end
----INITIALIZATION---function init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.5,3000)
enableFog(700, 0.5,0.5,0.75)
local camera = getCamera()
camera:reset()
camera:setPosition(0,30,-200)
camera:pitch(0.2)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("skyboxTop2.jpg"),
zip:getTexture("skyboxLeft2.jpg"),
zip:getTexture("skyboxFront2.jpg"),
zip:getTexture("skyboxRight2.jpg"),
zip:getTexture("skyboxBack2.jpg")
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(.5,.5,.75)
setBackground(sky)
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, 0.9659,
103
104
CHAPTER 10. LEVELS LOADING
zip:getTexture("lensflares.png"),
5, 0.2
)
sun:setColor(0.855,0.475,0.298);
setSun(sun)
----WATER MATERIAL---local waterMaterial = Material()
waterMaterial:setAmbient(1,1,0.75,0.5)
waterMaterial:setDiffuse(0.125,0.25,0.75,0.5)
waterMaterial:setSpecular(1,1,0,1)
waterMaterial:setShininess(128)
waterMaterial:setDiffuseTexture(zip:getTexture("water00.jpg",1))
----OCEAN---local waveAmplitude = .00003
local waveDisplacement = 5
local windX, windZ = 20, 20
local surfaceTileSide = 200
local gridSize = 8
local surfaceTilesCount = 7
local textureTilesCount = 10
local ocean = Ocean(
waterMaterial,waveAmplitude,waveDisplacement,windX,windZ,
surfaceTileSide,gridSize,surfaceTilesCount,textureTilesCount
)
ocean:setTransparent()
setTerrain(ocean)
waterMaterial:delete()
----GENERATE ISLE---local detailedTexture = zip:getTexture("detail.jpg",1)
local isle = generateIsle(
zip,"volcano.jpg",detailedTexture,8,"volcano.png",15,
150,150,60,0,0
)
----FIRE---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
local fireEmitter = Emitter(10,1,15)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(20,15,2.5, 4)
fireEmitter:setColor(1,1,0,1, 1,0,0,0)
fireEmitter:setSize(10,2.5)
fireEmitter:setGravity(0,0,0, 0,0,0)
fireEmitter:move(0,40,-.5)
fireEmitter:reset()
addObject(fireEmitter)
local bombEmitter = Emitter(10,10,100)
bombEmitter:setTexture(fireTexture,1)
bombEmitter:setVelocity(0,40,0, 10)
10.2. OUTDOOR LEVELS
bombEmitter:setColor(.25,0,0,1, 1,1,0,0)
bombEmitter:setSize(1,1)
bombEmitter:setGravity(0,-15,0, 0,-15,0)
bombEmitter:move(0,40,-.5)
bombEmitter:reset()
addObject(bombEmitter)
fireTexture:delete()
local fireSound = zip:getSample3D("fire.wav");
fireSound:setLooping(1)
fireSound:setVolume(255)
fireSound:setMinDistance(30)
local fireSource = Source(fireSound,fireEmitter);
addSource(fireSource)
----SMOKE---local smokeImage = zip:getImage("smoke.png")
smokeImage:convertToRGBA()
local smokeTexture = Texture(smokeImage)
smokeImage:delete()
local smokeEmitter = Emitter(40,4,100)
smokeEmitter:setTexture(smokeTexture,1)
smokeEmitter:setVelocity(5,12,-3, 2.5)
smokeEmitter:setColor(0,0,0,0.8, 0.25,0.25,0.25,0)
smokeEmitter:setSize(2,10)
smokeEmitter:setGravity(0,0,0, 0,0,0)
smokeEmitter:move(0,35,0)
smokeEmitter:reset()
addObject(smokeEmitter)
smokeTexture:delete()
----SET HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[LEFT ] Rotate Left",
"[RIGHT] Rotate Right",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP----
105
106
CHAPTER 10. LEVELS LOADING
function update()
local camera = getCamera()
local timeStep = getTimeStep()
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----MOVE CAMERA (KEYBOARD)---local speed = 75;
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 50
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
10.2. OUTDOOR LEVELS
107
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS---rotateView = nil
----STOP MUSIC---if soundTrack then
soundTrack:stop()
soundTrack:delete()
soundTrack = nil
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
10.2.2
Height Fields
----ISLES FLY-THROUGHT
----A "next to come" flight-simulator
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----SUPPORT FUNCTION----Generate Trees Material
function generateTreesMaterial(zip, fileName)
local treesMaterial = Material()
treesMaterial:setAmbient(1,1,1)
treesMaterial:setDiffuse(1,1,1)
treesMaterial:setDiffuseTexture(zip:getTexture(fileName));
return treesMaterial
end
----SUPPORT FUNCTION----Generate Isle
function generateIsle(
zip, terrainTextureName, detailedTexture, detailRepeat, heightFieldName,
waterLevel, width, depth, height, x, y, treesCount, treesMaterial,
minTreesHeight
)
108
CHAPTER 10. LEVELS LOADING
----LOAD IMAGES AND CREATE TEXTURES---local terrainImage = zip:getImage(terrainTextureName)
local heightImage = zip:getImage(heightFieldName)
local terrainTexture = Texture(terrainImage)
terrainImage:delete()
----TERRAIN MATERIAL---local terrainMaterial = Material()
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(terrainTexture)
terrainMaterial:setGlossTexture(detailedTexture)
----CREATE HEIGHTFIELD---local heightField = HeightField(
heightImage,terrainMaterial,width,depth,height,waterLevel,8
)
heightField:move(x,-waterLevel,y)
addObject(heightField)
terrainMaterial:delete()
terrainTexture:delete()
----TREES CREATION---local trees = Trees(treesCount,2,treesMaterial,2,0.1)
local treesSize = 6
local counter = 0
while counter < treesCount do
local xx = math.random()*width-width*0.5
local yy = math.random()*depth-depth*0.5
local h = heightField:getHeightAtRelative(xx,yy)
if h > minTreesHeight then
counter = counter + 1
trees:addTree(
xx,h,yy,treesSize,treesSize,math.mod(counter,4),counter<treesCount*2
)
end
end
trees:setTransparent()
trees:move(x,treesSize*.5-waterLevel,y)
addObject(trees)
treesMaterial:delete()
return heightField, trees
end
----SUPPORT FUNCTION----Clone Meshes
function cloneMeshes(meshes)
local meshesClone = Objects()
local mesh = meshes:getFirstMesh()
while mesh do
meshesClone:add(mesh:clone())
mesh = meshes:getNextMesh()
end
return meshesClone
end
10.2. OUTDOOR LEVELS
----INITIALIZATION---function init()
----GLOBALS---speed = 0
lastHeight = 0
----SET HELP---local help = {
"[MOUSE] Change Direction",
"[ UP ] Increase Speed",
"[DOWN ] Decrease Speed",
"[LEFT ] Roll Left",
"[RIGHT] Roll Right",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpUser()
hideConsole()
----CAMERA---setListenerScale(1)
setAmbient(0.5,0.5,0.5)
setPerspective(60,0.5,4000)
enableFog(1000, .4,.4,1)
local camera = getCamera()
camera:reset()
camera:move(650,10,0)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("SkyboxTop.jpg"),
zip:getTexture("SkyboxLeft.jpg"),
zip:getTexture("SkyboxFront.jpg"),
zip:getTexture("SkyboxRight.jpg"),
zip:getTexture("SkyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky);
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.41, 0.91,
zip:getTexture("lensflares.png"),
4, 0.2
109
110
CHAPTER 10. LEVELS LOADING
)
setSun(sun)
----WATER MATERIAL---local waterImages = {
zip:getImage("water00.jpg"),
zip:getImage("water01.jpg"),
zip:getImage("water02.jpg"),
zip:getImage("water03.jpg"),
zip:getImage("water04.jpg"),
zip:getImage("water05.jpg"),
zip:getImage("water06.jpg"),
zip:getImage("water07.jpg"),
zip:getImage("water08.jpg"),
zip:getImage("water09.jpg"),
zip:getImage("water10.jpg"),
zip:getImage("water11.jpg"),
zip:getImage("water12.jpg"),
zip:getImage("water13.jpg"),
zip:getImage("water14.jpg"),
zip:getImage("water15.jpg")
}
local waterTexture = AnimatedTexture(waterImages,1.5,1)
for ct = 1, 16 do
waterImages[ct]:delete()
end
local waterMaterial = Material()
waterMaterial:setAmbient(0.7,0.7,0.7)
waterMaterial:setDiffuse(0.0,0.5,0.9)
waterMaterial:setDiffuseTexture(waterTexture)
----SEA---local sea = FlatTerrain(waterMaterial,3000.0,200,1)
sea:setReflective()
setTerrain(sea)
waterMaterial:delete()
----GENERATE ISLES---local detailedTexture = zip:getTexture("detail.jpg",1)
isle1, trees1 = generateIsle(
zip, ---- ZIP file
"terrain1.jpg", ---- ground texture
detailedTexture, ---- detailedTexture
16, ---- detailedTiles
"terrain1.png", ---- heightfield map
4, ---- water level
256, ---- width
256, ---- depth
32, ---- height
500, ---- position x
0, ---- position z
128, ---- trees count
generateTreesMaterial(zip,"trees1.png"), ---- trees material
10.2. OUTDOOR LEVELS
111
26 ---- trees min height
)
isle2, trees2 = generateIsle(
zip,"terrain2.jpg",detailedTexture,8,"terrain2.png",4,
256,256,32,1000,500,128,generateTreesMaterial(zip,"trees2.png"),24
)
isle3, trees3 = generateIsle(
zip,"terrain3.jpg",detailedTexture,16,"terrain3.png",4,
256,256,12,1000,-500,128,generateTreesMaterial(zip,"trees3.png"),4
)
----WIND & WATER SOUND---waterSample = zip:getSample("water.wav")
waterSample:setLooping(1)
waterSound = waterSample:createSound()
waterSound:play()
waterSound:setVolume(255)
windSample = zip:getSample("wind.wav")
windSample:setLooping(1)
windSound = windSample:createSound()
windSound:play()
windSound:setVolume(32)
----CARRIER---local carrier = zip:getMeshes("carrier.3ds")
carrier:move(600.0,-2.0,300.0)
carrier:rotStanding(1.5708)
addObject(carrier)
alarmSample = zip:getSample3D("alarm.wav");
alarmSample:setLooping(1)
alarmSample:setVolume(255)
alarmSample:setMinDistance(50)
local source = Source(alarmSample,carrier);
addSource(source)
----AIRPLANES---motorSample = zip:getSample3D("motor.wav");
motorSample:setLooping(1)
motorSample:setVolume(255)
motorSample:setMinDistance(25)
zero1 = zip:getMeshes("zero.3ds")
zero1:move(600,15,300)
zero1:rotStanding(1.5708)
addObject(zero1)
local sourceZero1 = Source(motorSample,zero1)
addSource(sourceZero1)
zero2 = cloneMeshes(zero1)
zero2:move(625,30,325)
zero2:rotStanding(1.5708)
addObject(zero2)
local sourceZero2 = Source(motorSample,zero2)
addSource(sourceZero2)
zero3 = cloneMeshes(zero1)
112
CHAPTER 10. LEVELS LOADING
zero3:move(615,25,275)
zero3:rotStanding(1.5708)
addObject(zero3)
local sourceZero3 = Source(motorSample,zero3)
addSource(sourceZero3)
wild1 = zip:getMeshes("wildcat.3ds")
wild1:move(400,55,310)
wild1:rotStanding(1.5708)
addObject(wild1)
local sourceWild1 = Source(motorSample,wild1)
addSource(sourceWild1)
wild2 = cloneMeshes(wild1)
wild2:move(450,75,320)
wild2:rotStanding(1.5708)
addObject(wild2)
local sourceWild2 = Source(motorSample,wild2)
addSource(sourceWild2)
wild3 = cloneMeshes(wild1)
wild3:move(425,45,330)
wild3:rotStanding(1.5708)
addObject(wild3)
local sourceWild3 = Source(motorSample,wild3)
addSource(sourceWild3)
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
camera:moveForward(speed*timeStep)
----MOVE CAMERA (KEYBOARD)---if isKeyPressed(38) then --> VK_UP
speed = speed + 15*timeStep;
if speed > 75 then
speed = 75
end
end
if isKeyPressed(40) then --> VK_DOWN
speed = speed - 15*timeStep;
if speed < 0 then
speed = 0
end
end
if isKeyPressed(37) then --> VK_LEFT
camera:roll(-0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:roll(0.4*timeStep)
10.2. OUTDOOR LEVELS
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
if dx ~= 0 then
camera:yaw(-dx*0.15*timeStep)
end
if dy ~= 0 then
camera:pitch(-dy*0.15*timeStep)
end
----CHECK CAMERA HEIGHT---local posX, posY, posZ = camera:getPosition()
if isle1:includes(posX,posY,posZ) then
local h = isle1:getHeightAtAbsolute(posX,posZ)+5.5
if posY < h then
posY = h
camera:setPosition(posX,posY,posZ)
end
elseif isle2:includes(posX,posY,posZ) then
local h = isle2:getHeightAtAbsolute(posX,posZ)+5.5
if posY < h then
posY = h
camera:setPosition(posX,posY,posZ)
end
elseif isle3:includes(posX,posY,posZ) then
local h = isle3:getHeightAtAbsolute(posX,posZ)+5.5
if posY < h then
posY = h
camera:setPosition(posX,posY,posZ)
end
end
if posY < 5.5 then
posY = 5.5
camera:setPosition(posX,posY,posZ)
elseif posY > 2500 then
posY = 2500
camera:setPosition(posX,posY,posZ)
end
----CHANGE SOUNDS ACCORDING TO CAMERA HEIGHT---if posY ~= lastHeight then
lastHeight = posY
local windVol = lastHeight*5
113
114
CHAPTER 10. LEVELS LOADING
if windVol > 255 then
windVol = 255
end
windSound:setVolume(windVol)
waterSound:setVolume(255-windVol)
end
----MOVE AIRPLANES---local airplaneStep = 50*timeStep
local airplaneAngle = 0.1*timeStep
zero1:moveForward(airplaneStep)
zero1:rotStanding(airplaneAngle)
zero2:moveForward(airplaneStep)
zero2:rotStanding(airplaneAngle)
zero3:moveForward(airplaneStep)
zero3:rotStanding(airplaneAngle)
wild1:moveForward(airplaneStep)
wild1:rotStanding(airplaneAngle)
wild2:moveForward(airplaneStep)
wild2:rotStanding(airplaneAngle)
wild3:moveForward(airplaneStep)
wild3:rotStanding(airplaneAngle)
end
----FINALIZATION---function final()
----DELETE GLOBALS---speed = nil
lastHeight = nil
isle1 = nil
isle2 = nil
isle3 = nil
trees1 = nil
trees2 = nil
trees3 = nil
zero1 = nil
zero2 = nil
zero3 = nil
wild1 = nil
wild2 = nil
wild3 = nil
if alarmSample then
alarmSample:delete()
alarmSample = nil
end
if motorSample then
motorSample:delete()
motorSample = nil
end
if waterSound then
waterSound:stop()
10.2. OUTDOOR LEVELS
115
waterSound:delete()
waterSound = nil
if waterSample then
waterSample:delete()
waterSample = nil
end
end
if windSound then
windSound:stop()
windSound:delete()
windSound = nil
if windSample then
windSample:delete()
windSample = nil
end
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
10.2.3
Patches
--[[
Z E K E
O N
Y O U R
S I X !
A Simple Flight Simulator
P R E V I E W
Questions? Contact leo <boselli@uno.it>
--]]
----MODULES---MENU = {}
ALL = {}
SIM = {}
----FLIGHT SIM. Flight Model from:
----http://www.web-discovery.net/aerodynamics1.asp
----UNITS: length in feet, time in seconds, mass in slugs
----(1 slug = 32.2 lbs)
function SIM.setPosition(self,x,y,z)
self.X = z*3.2808
116
CHAPTER 10. LEVELS LOADING
self.Y = -x*3.2808
self.Z = -y*3.2808
end
function SIM.simulateAirplane(self,timeStep)
-- to be optimized
local deltaT = 0.001
steps = math.ceil(timeStep/deltaT)
local t11,t12,t13,t21,t22,t23,t31,t32,t33
for step = 1, steps do
local P, Q, R = self.P, self.Q, self.R
local U, V, W = self.U, self.V, self.W
local invU
if U == 0 then invU = 0 else invU = 1/U end
local alpha = math.atan(W*invU) ---> angle of attack
local beta = math.atan(V*invU) ---> angle of sideslip
local elevator = self.elevator
local aileron = self.aileron
local rudder = self.rudder
local liftCoefficient = self.CLo + self.CLa*alpha + self.CLde*elevator
local dragCoefficient = self.CDo + self.CDa*math.abs(alpha) + self.CDde*math.abs(el
local sideCoefficient = self.CYb*beta+self.CYdr*rudder
local rho = 0.0025 ---> air density (sl/ft^3)
local speed2 = U*U+V*V+W*W
local qbarS = rho*speed2*self.S*0.5 ---> dynamic pressure
local lift = liftCoefficient*qbarS
local drag = dragCoefficient*qbarS
local sideForce = sideCoefficient*qbarS
local sinAlpha = math.sin(alpha)
local cosAlpha = math.cos(alpha)
local Fx = lift*sinAlpha-drag*cosAlpha+self.thrust
local Fy = sideForce
local Fz = -lift*cosAlpha-drag*sinAlpha
local theta = self.theta
local phi = self.phi
local cosTheta = math.cos(theta)
local sinTheta = math.sin(theta)
local cosPhi = math.cos(phi)
local sinPhi = math.sin(phi)
local invMass = self.invMass
local g = 32.2 ---> ft/s^2
local Ua = V*R-W*Q-g*sinTheta+Fx*invMass
local Va = W*P-U*R+g*sinPhi*cosTheta+Fy*invMass
local Wa = U*Q-V*P+g*cosPhi*cosTheta+Fz*invMass
U = U+Ua*deltaT
V = V+Va*deltaT
W = W+Wa*deltaT
self.U, self.V, self.W = U, V, W
local invSpeed
if speed2 == 0 then invSpeed = 0 else invSpeed = 1/math.sqrt(speed2) end
10.2. OUTDOOR LEVELS
117
local c = self.c
local b = self.b
local L = (self.CLb*beta+self.CLp*P*b*invSpeed*0.5+self.CLr*R*b*invSpeed*0.5+self.CLda*ailero
local M = (self.CMo+self.CMa*alpha+self.CMq*Q*c*invSpeed*0.5+self.CMde*elevator)*qbarS*c --->
local N = (self.CNb*beta+self.CNp*P*b*invSpeed*0.5+self.CNr*R*b*invSpeed*0.5+self.CNda*ailero
local Ixx = self.Ixx
local Iyy = self.Iyy
local Izz = self.Izz
local Ixz = self.Ixz
local Ixz2 = Ixz*Ixz
local cc0 = 1/(Ixx*Izz-Ixz2)
local cc1 = cc0*((Iyy-Izz)*Izz-Ixz2)
local cc2 = cc0*Ixz*(Ixx-Iyy+Izz)
local cc3 = cc0*Izz
local cc4 = cc0*Ixz
local cc7 = 1/Iyy
local cc5 = cc7*(Izz-Ixx)
local cc6 = cc7*Ixz
local cc8 = cc0*((Ixx-Iyy)*Ixx+Ixz2)
local cc9 = cc0*Ixz*(Iyy-Izz-Ixx)
local cc10 = cc0*Ixx
local Pa = (cc1*R+cc2*P)*Q+cc3*L+cc4*N ---> angular acc. (rad/s^2)
local Qa = cc5*R*P+cc6*(R*R-P*P)+cc7*M ---> angular acc. (rad/s^2)
local Ra = (cc8*P+cc9*R)*Q+cc4*L+cc10*N ---> angular acc. (rad/s^2)
local Q0 = self.Q0
local Q1 = self.Q1
local Q2 = self.Q2
local Q3 = self.Q3
local invNorm = 1/math.sqrt(Q0*Q0+Q1*Q1+Q2*Q2+Q3*Q3)
Q0 = Q0*invNorm
Q1 = Q1*invNorm
Q2 = Q2*invNorm
Q3 = Q3*invNorm
local Q0Q0 = Q0*Q0
local Q1Q1 = Q1*Q1
local Q2Q2 = Q2*Q2
local Q3Q3 = Q3*Q3
local Q0Q1 = Q0*Q1
local Q0Q2 = Q0*Q2
local Q0Q3 = Q0*Q3
local Q1Q2 = Q1*Q2
local Q1Q3 = Q1*Q3
local Q2Q3 = Q2*Q3
local Qdot0 = -0.5*(Q1*P+Q2*Q+Q3*R)
local Qdot1 = 0.5*(Q0*P+Q2*R-Q3*Q)
local Qdot2 = 0.5*(Q0*Q+Q3*P-Q1*R)
local Qdot3 = 0.5*(Q0*R+Q1*Q-Q2*P)
Q0 = Q0+Qdot0*deltaT
Q1 = Q1+Qdot1*deltaT
Q2 = Q2+Qdot2*deltaT
118
CHAPTER 10. LEVELS LOADING
Q3 = Q3+Qdot3*deltaT
self.Q0, self.Q1, self.Q2, self.Q3 = Q0, Q1, Q2, Q3
t11 = Q0Q0+Q1Q1-Q2Q2-Q3Q3
t21 = 2*(Q1Q2+Q0Q3)
t31 = 2*(Q1Q3-Q0Q2)
t12 = 2*(Q1Q2-Q0Q3)
t22 = Q0Q0-Q1Q1+Q2Q2-Q3Q3
t32 = 2*(Q2Q3+Q0Q1)
t13 = 2*(Q1Q3+Q0Q2)
t23 = 2*(Q2Q3-Q0Q1)
t33 = Q0Q0-Q1Q1-Q2Q2+Q3Q3
P = P+Pa*deltaT
Q = Q+Qa*deltaT
R = R+Ra*deltaT
self.P, self.Q, self.R = P, Q, R
local temp = -t31
if temp < -1 then
temp = -1
elseif
temp > 1 then
temp = 1
end
self.theta = math.asin(temp)
self.phi = math.atan2(t32,t33)
local Uw = U*t11+V*t12+W*t13
local Vw = U*t21+V*t22+W*t23
local Ww = U*t31+V*t32+W*t33
self.X = self.X+Uw*deltaT
self.Y = self.Y+Vw*deltaT
self.Z = self.Z+Ww*deltaT
end
if steps ~= 0 then
local transform = self.transform
transform:setSideDirection(t22,t32,-t12)
transform:setUpDirection(t23,t33,-t13)
transform:setViewDirection(-t21,-t31,t11)
transform:setPosition(-self.Y*0.3048,-self.Z*0.3048,self.X*0.3048)
end
end
function SIM.createAirplane()
local airplane = { ---> Data from A4-sparrow
simulateAirplane = SIM.simulateAirplane, ---> simulator hook
setPosition = SIM.setPosition, ---> simulator hook
transform = Reference(), ---> the transform
invMass = 1/546, ---> inverse of mass (1/sl)
theta = 0, phi = 0, ---> angular orientation (rad)
X = 0, Y = 0, Z = 0, ---> position in feet
U = 400, V = 0, W = 0, ---> linear velocity (ft/s)
P = 0, Q = 0, R = 0, ---> angular velocity (rad/s)
Q0 = 1, Q1 = 0, Q2 = 0, Q3 = 0, ---> quaternion
10.2. OUTDOOR LEVELS
119
thrust = 3000, maxThrust = 5000, ---> thrust (sl*ft/s^2)
elevator = 0, maxElevator = 0.5236, ---> elevator angle (rad)
aileron = 0, maxAileron = 0.5236, ---> ailerons angle (rad)
rudder = 0, maxRudder = 0.2618, ---> rudder angle (rad)
CLo = 0.28, ---> reference lift at zero angle of attack
CLa = 3.45, ---> lift curve slope
CLde = 0.36, ---> lift due to elevator
CDo = 0.03, ---> reference drag at zero angle of attack
CDa = 0.3, ---> drag curve slope
CDde = 0.04, ---> drag due to elevator
CYb = -0.98, ---> side force due to sideslip
CYdr = 0.17, ---> side force due to rudder
CLb = -0.12, ---> dihedral effect
CLp = -0.26, ---> roll damping
CLr = 0.14, ---> roll due to yaw rate
CLda = 0.08, ---> roll due to aileron
CLdr = -0.105, ---> roll due to rudder
CMo = 0.0, ---> pitch moment coefficient
CMq = -3.6, ---> pitch moment coefficient due to pitch rate
CMa = -0.38, ---> pitch moment coefficient due to angle of attack
CMda = -1.1, ---> pitch moment coefficient due to angle of attack rate
CMde = -0.5, ---> pitch moment coefficient due to elevator
CNb = 0.25, ---> weather cocking stability
CNp = 0.022, ---> rudder adverse yaw
CNr = -0.35, ---> yaw damping
CNda = 0.06, ---> yaw due to aileron
CNdr = 0.032, ---> yaw due to rudder
Ixx = 8090, ---> roll inertia in slug/feet^2
Iyy = 25900, ---> pitch inertia in slug/feet^2
Izz = 29200, ---> yaw inertia in slug/feet^2
Ixz = 1300, ---> overall inertia along the trasversal Y-Z in slug/feet^2
S = 260.0, ---> wing surface area (ft^2)
b = 27.5, ---> wing span in feet
c = 10.8 ---> chord length in feet
}
return airplane
end
-------HUD
-------HUD SUPPORT---function SIM.setupMap(zip,mapName,tiled)
local mapImage = zip:getImage(mapName)
mapImage:convertToRGB()
local MAP_SIZE = 170
SIM.HUD.isMapShown = true
SIM.HUD.mapSprite =
120
CHAPTER 10. LEVELS LOADING
OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,tiled))
local mapSprite = SIM.HUD.mapSprite
mapImage:delete()
mapSprite:setLayer(0)
mapSprite:setColor(1,0.9,0.5,0.5)
local W, H = getDimension()
mapSprite:setLocation(W-MAP_SIZE,H-MAP_SIZE)
addToOverlay(SIM.HUD.mapSprite)
local markImage = zip:getImage("dot.png")
local markSize = markImage:getDimension()
markImage:addAlpha(markImage)
local markSprite = OverlaySprite(markSize,markSize,Texture(markImage),true)
markImage:delete()
SIM.HUD.markSprite = markSprite
markSprite:setLayer(-2)
markSprite:setColor(1,1,1)
addToOverlay(markSprite)
end
function SIM.setupStick(zip,stickName)
local stickImage = zip:getImage(stickName)
stickImage:addAlpha(stickImage)
local MAP_SIZE = 170
local STICK_SIZE = 128
SIM.HUD.isStickShown = true
SIM.HUD.stickSprite =
OverlaySprite(STICK_SIZE,STICK_SIZE,Texture(stickImage),true)
local stickSprite = SIM.HUD.stickSprite
stickImage:delete()
stickSprite:setLayer(0)
stickSprite:setColor(1,0.9,0.5)
local W, H = getDimension()
stickSprite:setLocation(W-MAP_SIZE,STICK_SIZE)
addToOverlay(SIM.HUD.stickSprite)
local posImage = zip:getImage("dot.png")
local posSize = posImage:getDimension()
posImage:addAlpha(posImage)
local posSprite = OverlaySprite(posSize,posSize,Texture(posImage),true)
posImage:delete()
SIM.HUD.posSprite = posSprite
posSprite:setLayer(-2)
posSprite:setColor(1,0,0)
addToOverlay(posSprite)
end
function SIM.setupHud(theScore,len,offset,texture,u0,v0,u1,v1)
local w, h = getDimension()
local w2, h2 = w*0.5, h*0.5
local sw, sh = 64, 32
local sw2, sh2 = sw*0.5, sh*0.5
10.2. OUTDOOR LEVELS
121
local nw, nh = 16, 16
local nw2, nh2 = nw*0.5, nh*0.5
local sprite = OverlaySprite(sw,sh,texture,true)
sprite:setTextureCoord(u0,v0,u1,v1)
sprite:setLocation(w2+offset,h-sh2)
sprite:setColor(0.75,0.75,0)
addToOverlay(sprite)
local offsetX, offsetY = w2+offset+nw2*(len+1), h-sh2-nh2-nh
for ct = 1, len do
theScore[ct] = OverlaySprite(nw,nh,texture,true)
local number = theScore[ct]
number:setTextureCoord(0,0.75,0.25,1)
number:setLocation(offsetX-nw*ct,offsetY)
number:setColor(1,1,0)
addToOverlay(number)
end
end
function SIM.setHudValue(theScore,chars,value)
local theString = string.format("%d",value)
local len = math.min(string.len(theString),chars)
for ct = 1, len do
local byte = string.byte(theString,ct)-string.byte("0")
local u, v = math.mod(byte,4)*0.25, math.floor(byte*0.25)*0.25
theScore[len-ct+1]:setTextureCoord(u,0.75-v,u+0.25,1-v)
end
for ct = len+1, chars do
theScore[ct]:setTextureCoord(0,0.75,0.25,1)
end
end
function SIM.setupHuds(zip)
local scoreImage = zip:getImage("numbers.png")
local texture = Texture(scoreImage)
scoreImage:delete()
local setupScore = SIM.setupHud
SIM.HUD.maxhHud = {}
setupScore(SIM.HUD.maxhHud,4,-160,texture,0,0,0.5,0.25)
SIM.HUD.heightHud = {}
setupScore(SIM.HUD.heightHud,4,0,texture,0.5,0,1,0.25)
SIM.HUD.speedHud = {}
setupScore(SIM.HUD.speedHud,3,160,texture,0.5,0.25,1,0.5)
SIM.setHudValue(SIM.HUD.maxhHud,4,0)
SIM.setHudValue(SIM.HUD.heightHud,4,0)
SIM.setHudValue(SIM.HUD.speedHud,3,0)
end
-------SIM
----
122
CHAPTER 10. LEVELS LOADING
----INITIALIZATION---function SIM.init()
cloudsList = {}
treesList = {}
----CAMERA---setAmbient(0.3,0.3,0.3)
local MAX_DIST = 3000
setPerspective(60,1,MAX_DIST)
local fogColor = {0.475,0.431,0.451}
enableFog(MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
local camera = getCamera()
camera:reset()
empty()
----ZIP---local zip = Zip("ZekeOnYourSix.dat")
----SKYBOX---local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
----MOON---local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,MAX_DIST-500
)
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("coarse.jpg",true))
material:setGlossTexture(zip:getTexture("detail.jpg",true))
10.2. OUTDOOR LEVELS
123
patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,128)
patches:setShadowFadeDistance(100)
patches:setShadowOffset(0.25)
patches:setShadowed()
setTerrain(patches)
heightImage:delete()
colorImage:delete()
material:delete()
----SOUNDS---local engineSample = zip:getSample3D("motor.wav");
engineSample:setLooping(true)
engineSample:setVolume(255)
engineSample:setMinDistance(100)
----SHADOW---local shadowImage = zip:getImage("shadow.png")
shadowImage:convertTo111A()
local shadowTexture = Texture(shadowImage)
shadowImage:delete()
----ENVIRONMENT---local envtext = zip:getTexture("environ1.jpg")
----ZERO---zero = zip:getMeshes("zero.3ds")
local zeroMesh = zero:getFirstMesh()
while zeroMesh do
local zmat = zeroMesh:getMaterial()
zmat:setAmbient(0.7,0.7,0.7)
zmat:setDiffuse(1,1,1)
zmat:setSpecular(1,1,0)
zmat:setShininess(96)
zmat:setEnvironmentTexture(envtext,0.25)
zeroMesh = zero:getNextMesh()
end
zero:move(0,30*32,140)
addObject(zero)
addShadow(Shadow(zero,6,6,shadowTexture))
zeroSource = Source(engineSample,zero,true)
addSource(zeroSource)
----EMITTER---local smokeImage = zip:getImage("smoke.png")
smokeImage:convertTo111A()
local smokeTexture = Texture(smokeImage)
smokeImage:delete()
smokeEmitter = Emitter(150,2,100,false)
smokeEmitter:setTexture(smokeTexture,1)
smokeEmitter:setVelocity(0,0,0, 1)
smokeEmitter:setColor(0.5,0.5,0.5,1, 1,1,1,0)
smokeEmitter:setSize(0.75,10)
smokeEmitter:setGravity(0,0,0, 0,0,0)
smokeEmitter:reset()
addObject(smokeEmitter)
124
CHAPTER 10. LEVELS LOADING
smokeTexture:delete()
----HUD---SIM.HUD = {}
SIM.HUD.maxHeight = 0
SIM.setupHuds(zip)
SIM.setupMap(zip,"map.png",true)
SIM.setupStick(zip,"stick.png")
----AIRPLANE---airplane = SIM.createAirplane()
airplane:setPosition(0,30*32,140)
offset = {view = 2, dist = 75, height = 5, forward = -75, up = 15, side = 0}
----HELP---local help = {
"[
MOUSE ] Stick Control",
"[LEFT|RIGHT] Ailerons Control",
"[ UP|DOWN ] Elevator Control",
"[ DEL|END ] Rudder Control",
"[
0
] Zero Controls",
"[NEXT|PRIOR] Thrust",
"[
S
] Show/Hide Smoke",
"[ * | /
] Zoom View",
"[ + | ] Incline View",
"[ 1 - 9
] Change View",
"[ENTER] Back to menu",
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
hideHelp()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function SIM.final()
SIM.HUD = nil
offset = nil
zero = nil
smokeEmitter = nil
airplane = nil
patches = nil
----EMPTY WORLD---disableFog()
emptyOverlay()
empty()
end
----KEYDOWN---function SIM.keyDown(key)
10.2. OUTDOOR LEVELS
if key >= 97 and key <= 105 then ---> VK_NUMPAD1-9
if key == 97 then
releaseKey(97)
offset.view = 1
offset.forward = -offset.dist*0.707
offset.side = offset.dist*0.707
offset.up = offset.height
elseif key == 98 then
releaseKey(98)
offset.view = 2
offset.forward = -offset.dist
offset.side = 0
offset.up = offset.height
elseif key == 99 then
releaseKey(99)
offset.view = 3
offset.forward = -offset.dist*0.707
offset.side = -offset.dist*0.707
offset.up = offset.height
elseif key == 100 then
releaseKey(100)
offset.view = 4
offset.forward = 0
offset.side = offset.dist
offset.up = offset.height
elseif key == 101 then
releaseKey(101)
if offset.view == 5 then
offset.view = 0
offset.up = -offset.dist
else
offset.view = 5
offset.up = offset.dist
end
offset.forward = 0
offset.side = 0
elseif key == 102 then
releaseKey(102)
offset.view = 6
offset.forward = 0
offset.side = -offset.dist
offset.up = offset.height
elseif key == 103 then
releaseKey(103)
offset.view = 7
offset.forward = offset.dist*0.707
offset.side = offset.dist*0.707
offset.up = offset.height
elseif key == 104 then
releaseKey(104)
125
126
CHAPTER 10. LEVELS LOADING
offset.view = 8
offset.forward = offset.dist
offset.side = 0
offset.up = offset.height
elseif key == 105 then
releaseKey(105)
offset.view = 9
offset.forward = offset.dist*0.707
offset.side = -offset.dist*0.707
offset.up = offset.height
end
elseif key == string.byte("S") then
if smokeEmitter:isVisible() then
smokeEmitter:hide()
else
smokeEmitter:show()
end
elseif key == 13 then
releaseKey(13)
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
end
end
----LOOP---function SIM.update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
----MOVE CONTROLS---if isMouseLeftPressed() or isKeyPressed(33) then --> VK_PRIOR
local thrust = airplane.thrust
local maxThrust = airplane.maxThrust
thrust = thrust+timeStep*maxThrust*0.1
if(thrust > maxThrust) then
thrust = maxThrust
end
airplane.thrust = thrust
elseif isMouseRightPressed() or isKeyPressed(34) then --> VK_NEXT
local thrust = airplane.thrust
local maxThrust = airplane.maxThrust
thrust = thrust-timeStep*maxThrust*0.1
if(thrust < 0) then
thrust = 0
end
airplane.thrust = thrust
end
local dx, dy = getMouseMove()
if dx ~= 0 or dy ~=0 then
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator*0.5
10.2. OUTDOOR LEVELS
elevator = elevator+dy*maxElevator*0.0015
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
local aileron = airplane.aileron
local maxAileron = airplane.maxAileron*0.25
aileron = aileron+dx*maxAileron*0.0015
if aileron > maxAileron then
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
end
if isKeyPressed(38) then --> VK_UP
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator
elevator = elevator+timeStep*maxElevator*0.2
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
elseif isKeyPressed(40) then --> VK_DOWN
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator
elevator = elevator-timeStep*maxElevator*0.2
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
end
if isKeyPressed(37) then --> VK_LEFT
local aileron = airplane.aileron
local maxAileron = airplane.maxAileron
aileron = aileron-timeStep*maxAileron*0.2
if aileron > maxAileron then
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
elseif isKeyPressed(39) then --> VK_RIGHT
local aileron = airplane.aileron
127
128
CHAPTER 10. LEVELS LOADING
local maxAileron = airplane.maxAileron
aileron = aileron+timeStep*maxAileron*0.2
if aileron > maxAileron then
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
end
if isKeyPressed(46) then --> VK_DEL
local rudder = airplane.rudder
local maxRudder = airplane.maxRudder
rudder = rudder+timeStep*maxRudder*0.2
if rudder > maxRudder then
rudder = maxRudder
end
airplane.rudder = rudder
elseif isKeyPressed(35) then --> VK_END
local rudder = airplane.rudder
local maxRudder = airplane.maxRudder
rudder = rudder-timeStep*maxRudder*0.2
if rudder < -maxRudder then
rudder = -maxRudder
end
airplane.rudder = rudder
end
if isKeyPressed(107) then ---> VK_ADD
if offset.view ~= 0 and offset.view ~= 5 then
local scale = 1+timeStep
offset.height = offset.height*scale
if offset.height <= 500 then
offset.up = offset.height
else
offset.height = 500
end
end
elseif isKeyPressed(109) then ---> VK_SUBTRACT
if offset.view ~= 0 and offset.view ~= 5 then
local scale = 1-timeStep
offset.height = offset.height*scale
if offset.height >= 0 then
offset.up = offset.height
else
offset.height = 0
end
end
end
if isKeyPressed(111) then ---> VK_DIVIDE
local scale = 1-timeStep
offset.dist = offset.dist*scale
10.2. OUTDOOR LEVELS
129
if offset.dist >= 20 then
offset.forward = offset.forward*scale
offset.side = offset.side*scale
offset.up = offset.up*scale
else
offset.dist = 20
end
elseif isKeyPressed(106) then ---> VK_MULTIPLY
local scale = 1+timeStep
offset.dist = offset.dist*scale
if offset.dist <= 500 then
offset.forward = offset.forward*scale
offset.side = offset.side*scale
offset.up = offset.up*scale
else
offset.dist = 500
end
end
if isKeyPressed(96) then ---> VK_NUMPAD0
airplane.elevator = 0
airplane.rudder = 0
airplane.aileron = 0
end
----SIMULATION---airplane:simulateAirplane(timeStep)
zero:set(airplane.transform)
local posX, posY, posZ = zero:getPosition()
if posY > SIM.HUD.maxHeight then
SIM.HUD.maxHeight = posY
SIM.setHudValue(SIM.HUD.maxhHud,4,posY)
end
SIM.setHudValue(SIM.HUD.heightHud,4,posY)
smokeEmitter:set(zero)
smokeEmitter:moveForward(-5)
camera:set(zero)
camera:moveForward(offset.forward)
camera:moveSide(offset.side)
camera:moveUp(offset.up)
local camX, camY, camZ = camera:getPosition()
local camH = patches:getHeightAt(camX,camZ)+10
if camY < camH then camera:setPosition(camX,camH,camZ) end
local posX, posY, posZ = zero:getPosition()
camera:pointTo(posX,posY,posZ)
local speedX = airplane.U*1.1
local speedY = airplane.V*1.1
local speedZ = airplane.W*1.1
local speed = math.sqrt(speedX*speedX+speedY*speedY+speedZ*speedZ)
SIM.setHudValue(SIM.HUD.speedHud,3,speed)
----UPDATE MAP---if SIM.HUD.isMapShown then
130
CHAPTER 10. LEVELS LOADING
local horViewX,horViewZ = camera:getHorizontalView()
local mapSprite = SIM.HUD.mapSprite
local TEX_SCALE = 6.510417e-5 ---> 1/(512*30)
if horViewZ ~= 0 then
mapSprite:setRotation(math.atan2(horViewX,horViewZ))
local markSprite = SIM.HUD.markSprite
local W, H = getDimension()
local MAP_SIZE = 170
local SCALE = MAP_SIZE*TEX_SCALE
local CENTER_X = W-MAP_SIZE
local CENTER_Z = H-MAP_SIZE
local x,y,z = zero:getPosition()
x, z = (camX-x)*SCALE, (z-camZ)*SCALE
if x >= -85 and x <= 85 and z >= -85 and z <= 85 then
local dx = x*horViewZ+z*horViewX
local dz = -x*horViewX+z*horViewZ
markSprite:setLocation(CENTER_X+dx,CENTER_Z+dz)
markSprite:show()
else
markSprite:hide()
end
end
local texX = camX*TEX_SCALE+0.5
local texZ = camZ*TEX_SCALE+0.5
mapSprite:setTextureCoord(texX+0.5,texZ-0.5,texX-0.5,texZ+0.5)
end
----UPDATE STICK---if SIM.HUD.isStickShown then
local posSprite = SIM.HUD.posSprite
local W, H = getDimension()
local MAP_SIZE = 170
local STICK_SIZE = 128
local CENTER_X = W-MAP_SIZE
local CENTER_Z = STICK_SIZE
local stickX = airplane.aileron/airplane.maxAileron*STICK_SIZE*2
local stickZ = airplane.elevator/airplane.maxElevator*STICK_SIZE
posSprite:setLocation(CENTER_X+stickX,CENTER_Z+stickZ)
end
----CRASH---if posY < patches:getHeightAt(posX,posZ) then
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
end
end
----INITIALIZATION SUPPORT---function ALL.showSplashImage(zip)
local splashImage = zip:getImage("logo.jpg")
showSplashImage(splashImage)
splashImage:delete()
10.2. OUTDOOR LEVELS
131
end
function ALL.playSoundtrack(zip,fileName,MODULE)
MODULE.soundTrack = zip:getMusic(fileName)
local soundTrack = MODULE.soundTrack
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
end
function ALL.stopSoundtrack(MODULE)
local soundTrack = MODULE.soundTrack
if soundTrack then
soundTrack:stop()
soundTrack:delete()
MODULE.soundTrack = nil
end
end
function ALL.createSkybox(zip)
local txtNames = {"Top", "Left", "Front", "Right", "Back"}
local skyTxt = {}
for txtIdx = 1, table.getn(txtNames) do
skyTxt[txtIdx] = zip:getTexture("skybox"..txtNames[txtIdx]..".jpg")
end
local sky = MirroredSky(skyTxt)
setBackground(sky)
end
function ALL.createSun(zip)
local sun = {
size = 0.32, distance = 3200, texture = zip:getTexture("light.jpg"),
dir = {x = 0.0, y = 0.2588, z = 0.9659},
color = {r = 0.9, g = 0.5, b = 0.2},
flares = {
count = 5, size = 0.128, texture = zip:getTexture("lensflares.png")
}
}
local theSun = Sun(
sun.texture, sun.size, sun.dir.x,sun.dir.y,sun.dir.z,
sun.flares.texture,sun.flares.count,sun.flares.size,
sun.distance
)
theSun:setColor(sun.color.r,sun.color.g,sun.color.b)
setSun(theSun)
end
function ALL.createLevel(zip)
local bsp = zip:getLevel("city.bsx",3)
bsp:setShowUntexturedMeshes()
132
CHAPTER 10. LEVELS LOADING
bsp:setShowUntexturedPatches()
bsp:setDefaultTexture(
zip:getTexture("textures/maxpayne/Brick52a.jpg",1)
)
bsp:setShadowsStatic()
setScenery(bsp)
return bsp
end
-------MENU SCENE
-------INITIALIZATION SUPPORT---function MENU.setupPointer(zip)
local pointerImage = zip:getImage("arrow.png")
local pointerSize = pointerImage:getDimension()
pointerImage:addAlpha(pointerImage)
local pointerSprite = OverlaySprite(
pointerSize,pointerSize,Texture(pointerImage),true
)
pointerImage:delete()
pointerSprite:setLayer(-1)
setPointer(pointerSprite)
local w, h = getDimension()
setPointerLocation(w/2,h/2)
showPointer()
end
function MENU.createLogo(zip)
local logoImage = zip:getImage("logo.jpg")
local alphaImage = zip:getImage("logo.png")
alphaImage:convertTo111A()
logoImage:addAlpha(alphaImage)
alphaImage:delete()
local logoSize = logoImage:getDimension()
MENU.GUI.logoSprite = OverlaySprite(
logoSize,logoSize,Texture(logoImage),true
)
local logoSprite = MENU.GUI.logoSprite
logoImage:delete()
local w, h = getDimension()
logoSprite:setLocation((w-logoSize)/2,h)
addToOverlay(logoSprite)
end
function MENU.createCredits()
local colors = {
{r = 1,
g = 1, b = 0},
10.2. OUTDOOR LEVELS
{r = 0.75, g = 1, b = 1},
{r = 1,
g = 1, b = 1}
}
local creditStrings = {
{
1, "Z E K E
O N
Y O U R
S I X",
2, "",
3, "Copyright \184 2004",
2, "Leonardo Boselli",
1, "",
3, "A Simple Flight Simulator",
2, "",
2, ""
},
{
1, "- Programming & Design -",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
1, "- Scenery & Models -",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
3, "",
3, ""
},
{
3, "Thanks to",
3, "",
2, "Matteo \"Fuzz\" Perenzoni",
3, "",
3, "for fruitful discussions on",
3, "OpenGL and 3D programming.",
3, "",
3, "The sources of his demo for",
3, "the NeHe’s Apocalypse Contest",
3, "were the first building blocks",
3, "of the APOCALYX 3D Engine."
},
{
3, "Thanks to",
3, "",
1, "TeCGraf, PUC-Rio",
3, "for the LUA script language",
2, "www.lua.org",
3, "",
1, "Borland",
3, "for their free C++ compiler",
2, "www.borland.com",
},
{
3, "Thanks to the following sites",
133
134
CHAPTER 10. LEVELS LOADING
3, "for their useful tutorials",
3, "about game programming",
3, "",
1, "NeHe Productions",
2, "nehe.gamedev.net",
1, "Game Tutorials",
2, "www.gametutorials.com",
1, "SULACO",
2, "www.sulaco.co.za",
3, "",
3, "and",
3, "",
1, "Game Programming Italia",
2, "www.gameprog.it"
},
{
3, "Thanks to these web sites",
3, "for publishing news about game",
3, "development and related stuff",
3, "",
1, "GameDev",
2, "www.gamedev.net",
1, "FlipCode",
2, "www.flipcode.org",
1, "CFXweb",
2, "www.cfxweb.net",
1, "OpenGL.org",
2, "www.opengl.org"
},
{
3, "And, finally, thanks to",
3, "ALL the people of the",
3, "italian newsgroup",
3, "",
1, "it.comp.giochi.sviluppo",
3, "",
3, "",
3, ""
}
}
local font = getMainOverlayFont()
local fontH = font:getHeight()
local w, h = getDimension()
local x = w/2+160
MENU.GUI.optionsTexts = OverlayTexts(font)
local optionsTexts = MENU.GUI.optionsTexts
local offset = 0
local playText
playText = OverlayText("[1] Flight Simulator")
playText:setScale(2)
10.2. OUTDOOR LEVELS
135
playText:setColor(1,1,0)
playText:setLocation(0,offset)
offset = offset+fontH*2
optionsTexts:add(playText)
optionsTexts:setLocation(w/2,h)
addToOverlay(optionsTexts)
MENU.GUI.credits = {}
local credits = MENU.GUI.credits
credits.status = 0
credits.index = 1
credits.texts = {}
local creditTexts = credits.texts
for creditIdx = 1, table.getn(creditStrings) do
creditTexts[creditIdx] = OverlayTexts(font)
local currentCreditText = creditTexts[creditIdx]
local textLines = creditStrings[creditIdx]
local textLinesCount = table.getn(textLines)
local y = (h+fontH*(textLinesCount-1))/2-240
for textLineIdx = 1, table.getn(textLines), 2 do
local text = OverlayText(textLines[textLineIdx+1])
local colorIdx = textLines[textLineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(x,y)
y = y-fontH
currentCreditText:add(text)
end
currentCreditText:setLocation(0,-h/2)
addToOverlay(currentCreditText)
currentCreditText:hide()
end
end
function MENU.setupCamera()
setAmbient(0.3,0.3,0.3)
local camera = {angleOfView = 60, nearClip = 3, farClip = 3000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
theCamera:reset()
theCamera:move(0,800,0)
end
function MENU.setupHelp()
hideConsole()
showHelpReduced()
local help = {
"Z E K E
O N
Y O U R
S I X",
"A Simple Flight Shooter",
"Questions? Contact leo <boselli@uno.it>",
136
CHAPTER 10. LEVELS LOADING
" ",
"[ 1 ] Simulator",
" ",
"[ F 1 ] Show/Hide Help",
}
setHelp(help)
end
----INITIALIZATION---function MENU.init()
setTitle(" Z E K E
O N
Y O U R
S I X")
MENU.GUI = {}
empty()
emptyOverlay()
local zip = Zip("ZekeOnYourSix.dat")
ALL.playSoundtrack(zip,"intro.mid",MENU.GUI)
ALL.showSplashImage(zip)
MENU.createLogo(zip)
MENU.createCredits()
----SCENERY
----SKYBOX---local MAX_DIST = 3000
local fogColor = {0.475,0.431,0.451}
enableFog(MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
----MOON---local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,MAX_DIST-500
)
10.2. OUTDOOR LEVELS
137
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("coarse.jpg",true))
material:setGlossTexture(zip:getTexture("detail.jpg",true))
local patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,128)
setTerrain(patches)
heightImage:delete()
colorImage:delete()
material:delete()
----SCENERY (END)
MENU.setupCamera()
MENU.setupPointer(zip)
MENU.setupHelp()
zip:delete()
end
----FINALIZATION---function MENU.final()
ALL.stopSoundtrack(MENU.GUI)
MENU.GUI = nil
hidePointer()
disableFog()
emptyOverlay()
empty()
end
----KEYBOARD---function MENU.keyDown(key)
if key == string.byte("1") then
releaseKey(string.byte("1"))
setScene(Scene(SIM.init,SIM.update,SIM.final,SIM.keyDown))
end
end
----UPDATE SUPPORT---function MENU.rotateCamera(timeStep)
local camera = getCamera()
local rotSpeed = -math.pi/12
camera:rotStanding(rotSpeed*timeStep)
end
function MENU.animateLogo(timeStep)
local GUI = MENU.GUI
138
CHAPTER 10. LEVELS LOADING
local logoSprite = GUI.logoSprite
local credits = GUI.credits
local creditTexts = credits.texts
local creditTextTime = credits.time
local creditTextIndex = credits.index
local creditTextStatus = credits.status
local logoSpeedX, logoSpeedY = 200, 400
local logoSize = logoSprite:getDimension()
local w, h = getDimension()
local markX = (w-logoSize-320)/2+logoSize/2
local markY = (h-480)/2+logoSize/2
local logoX, logoY = logoSprite:getLocation()
if logoY > markY then
logoY = logoY-timeStep*logoSpeedY
if logoY < markY then
logoY = markY
end
MENU.GUI.optionsTexts:setLocation(w/2,logoY+h/2)
logoSprite:setLocation(logoX,logoY)
elseif logoY == markY then
logoSprite:setLocation(logoX,markY-1)
else
if logoX > markX then
logoX = logoX-timeStep*logoSpeedX
if logoX < markX then
logoX = markX
end
logoSprite:setLocation(logoX,logoY)
elseif logoX == markX then
hideHelp()
logoSprite:setLocation(logoX-1,logoY)
creditTexts[creditTextIndex]:show()
creditTexts[creditTextIndex]:setLocation(0,-h/2)
else
local creditText = creditTexts[creditTextIndex]
if creditTextStatus == 0 then --> FADE_IN
local creditX, creditY = creditText:getLocation()
creditY = creditY+timeStep*logoSpeedX
if creditY >= 0 then
creditText:setLocation(creditX,0)
credits.time = getElapsedTime()
credits.status = 1 --> WAIT
else
creditText:setLocation(creditX,creditY)
end
elseif creditTextStatus == 1 then --> WAIT
local diff = getElapsedTime()-creditTextTime
if diff > creditText:getCount()*0.5 then
credits.status = 2 --> FADE_OUT
end
10.2. OUTDOOR LEVELS
139
elseif creditTextStatus == 2 then --> FADE_OUT
local creditX, creditY = creditText:getLocation()
creditY = creditY-timeStep*logoSpeedY
if creditY <= -h/2 then
creditText:setLocation(creditX,-h/2)
creditText:hide()
credits.index = creditTextIndex+1
if credits.index > table.getn(creditTexts) then
credits.index = 1
end
creditTexts[credits.index]:show()
credits.status = 0 --> FADE_IN
else
creditText:setLocation(creditX,creditY)
end
end
end
end
end
----UPDATE---function MENU.update()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
MENU.rotateCamera(timeStep)
MENU.animateLogo(timeStep)
local dx, dy = getMouseMove()
movePointer(dx,dy,getDimension())
local GUI = MENU.GUI
local text = GUI.optionsTexts:getTextAt(getPointerLocation())
local oldPointerText = GUI.oldPointerText
if text then
if text ~= oldPointerText then
if oldPointerText then
oldPointerText:setColor(1,1,0)
end
text:setColor(1,0.25,0.25)
GUI.oldPointerText = text
end
if isMouseLeftPressed() then
if GUI.selected == nil then
GUI.selected = true
MENU.keyDown(string.byte(text:getText(),2))
end
else
GUI.selected = nil
end
elseif oldPointerText then
oldPointerText:setColor(1,1,0)
140
CHAPTER 10. LEVELS LOADING
GUI.oldPointerText = nil
end
end
----SCENE SETUP---setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
Chapter 11
Particle-Based Physics
Simulator
11.1
Fundamental Principles
----CLOTH DEMO
----A simulator of cloths
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----GLOBALS---windSpeed = 4
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.5,3000)
enableFog(1000, .4,.4,1)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("SkyboxTop.jpg"),
zip:getTexture("SkyboxLeft.jpg"),
zip:getTexture("SkyboxFront.jpg"),
zip:getTexture("SkyboxRight.jpg"),
zip:getTexture("SkyboxBack.jpg")
}
141
142
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
local sky = MirroredSky(skyTxt)
setBackground(sky);
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.41, 0.91,
zip:getTexture("lensflares.png"),
4, 0.2
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(.7,.7,.7)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
terrain:setReflective()
setTerrain(terrain)
terrainMaterial:delete()
----FLAG SIMULATOR---simulator = Simulator()
clothEnvironment = StaticEnvironment(windSpeed,0,-windSpeed, 0)
local SIDE_CT = 17
local SIDE_LEN = 3
local posX = SIDE_LEN/2
flag = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX,6,0, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.05, ---- mass
simulator, clothEnvironment
)
for ct = 0, SIDE_CT-1, 4 do
flag:addNail(ct*SIDE_CT, 0,3+ct*3/SIDE_CT,0)
end
local baseIndex = SIDE_CT*SIDE_CT-SIDE_CT
for ct = 0, SIDE_CT-1, 4 do
flag:addNail(baseIndex+ct, ct*2.5/SIDE_CT,6,0)
end
----FLAG RENDERER---local pole = zip:getMeshes("pole.3ds")
pole:move(0,0,0)
addObject(pole)
local flagSound = zip:getSample3D("flag.wav");
flagSound:setLooping(1)
flagSound:setVolume(255)
flagSound:setMinDistance(5)
flagSource = Source(flagSound,pole);
addSource(flagSource)
11.1. FUNDAMENTAL PRINCIPLES
143
local flagMaterial = Material()
flagMaterial:setAmbient(0.7,0.7,0.7)
flagMaterial:setDiffuse(1,1,1)
flagMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
flagMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),.333)
local mesh = flag:getMesh()
mesh:setMaterial(flagMaterial)
addObject(mesh)
flagMaterial:delete()
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[LEFT ] Rotate Left",
"[RIGHT] Rotate Right",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[ZXCVB] Relaxation 1-16",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
144
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
rotateView = 1
end
end
----WIND SPEED---local asciiBase = string.byte("0")
for ct = 0, 9 do
if isKeyPressed(asciiBase+ct) then
releaseKey(asciiBase+ct)
windSpeed = ct
clothEnvironment:setWind(windSpeed,0,-windSpeed)
local sound = flagSource:getSound3D()
sound:stop()
if windSpeed > 2 then
sound:play()
end
break
end
end
----RELAXATION CYCLES---if isKeyPressed(string.byte("Z")) then
releaseKey(string.byte("Z"))
flag:setRelaxationCycles(1)
elseif isKeyPressed(string.byte("X")) then
releaseKey(string.byte("X"))
flag:setRelaxationCycles(2)
elseif isKeyPressed(string.byte("C")) then
releaseKey(string.byte("C"))
flag:setRelaxationCycles(4)
elseif isKeyPressed(string.byte("V")) then
releaseKey(string.byte("V"))
flag:setRelaxationCycles(8)
elseif isKeyPressed(string.byte("B")) then
releaseKey(string.byte("B"))
flag:setRelaxationCycles(16)
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
11.1. FUNDAMENTAL PRINCIPLES
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS---flag = nil
if clothEnvironment then
clothEnvironment:delete()
clothEnvironment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
if flagSource then
flagSource:getSound3D():stop()
flagSource = nil
end
windSpeed = nil
rotateView = nil
----EMPTY WORLD---disableFog()
145
146
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
11.2
Environment Interaction
11.2.1
Obstructions
----CLOTH DEMO 2
----A simulator of cloths
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----GLOBALS---windSpeed = 1
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.5,3000)
enableFog(1000, .4,.4,1)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.6,6)
camera:rotStanding(3.1415)
empty()
----SKYBOX---if not fileExists("DemoPack0.dat") then
showConsole()
error("\nERROR: File ’DemoPack0.dat’ not found.")
end
local zip = Zip("DemoPack0.dat")
local skyTxt = {
zip:getTexture("SkyboxTop.jpg"),
zip:getTexture("SkyboxLeft.jpg"),
zip:getTexture("SkyboxFront.jpg"),
zip:getTexture("SkyboxRight.jpg"),
zip:getTexture("SkyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky);
----MUSIC---soundTrack = zip:getMusic("sympho.mid")
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
----SUN---local sun = Sun(
11.2. ENVIRONMENT INTERACTION
147
zip:getTexture("light.jpg"),0.25,
0.0, 0.41, 0.91,
zip:getTexture("lensflares.png"),
4, 0.2
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(.7,.7,.7)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
terrain:setReflective()
setTerrain(terrain)
terrainMaterial:delete()
----CLOTH SIMULATOR---simulator = Simulator()
local boxX, boxY, boxZ = -2, .75, 0
local boxedObs = BoxedObstruction(boxX,boxY,boxZ, 1.65,.15,1.65)
clothEnvironment1 = StaticEnvironment(windSpeed,0,-windSpeed,.1,boxedObs)
local cylX, cylY, cylZ = 2, .75, 0
local cylindricalObs = CylindricalObstruction(cylX,cylY,cylZ, .85,.15)
clothEnvironment2 = StaticEnvironment(windSpeed,0,-windSpeed,.1,cylindricalObs)
local sphX, sphY, sphZ = 0, 2, -3
local sphericalObs = SphericalObstruction(sphX,sphY,sphZ, 1.05)
clothEnvironment3 = StaticEnvironment(windSpeed,0,-windSpeed,.1,sphericalObs)
local SIDE_CT = 23
local SIDE_LEN = 2.25
local posX = SIDE_LEN/2
local posZ = -posX
local posY = 1.1
local tableCloth1 = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+boxX,posY+boxY,posZ+boxZ, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.2, ---- mass
simulator, clothEnvironment1
)
local tableCloth2 = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+cylX,posY+cylY,posZ+cylZ, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.2, ---- mass
simulator, clothEnvironment2
)
local SIDE_CT = 23
local SIDE_LEN = 5
local posX = SIDE_LEN/2
148
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
local posZ = -posX
local foulard = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+sphX,posY+sphY,posZ+sphZ, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.4, ---- mass
simulator, clothEnvironment3
)
----TABLES RENDERER---local squareTable = zip:getMeshes("squareTable.3ds")
squareTable:move(-2,0,0)
addObject(squareTable)
local roundTable = zip:getMeshes("roundTable.3ds")
roundTable:move(2,0,0)
addObject(roundTable)
local sphere = zip:getMeshes("sphere.3ds")
sphere:move(0,1,-3)
addObject(sphere)
----CLOTH RENDERER---local foulardMaterial = Material()
foulardMaterial:setAmbient(0.7,0.7,0.7)
foulardMaterial:setDiffuse(1,1,1)
foulardMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
foulardMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),.333)
local tableClothMaterial = Material()
tableClothMaterial:setAmbient(0.7,0.7,0.7)
tableClothMaterial:setDiffuse(1,1,.5)
tableClothMaterial:setDiffuseTexture(zip:getTexture("net.jpg"))
local mesh = tableCloth1:getMesh()
mesh:setMaterial(tableClothMaterial)
addObject(mesh)
local mesh = tableCloth2:getMesh()
mesh:setMaterial(tableClothMaterial)
addObject(mesh)
tableClothMaterial:delete()
local mesh = foulard:getMesh()
mesh:setMaterial(foulardMaterial)
addObject(mesh)
foulardMaterial:delete()
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[LEFT ] Rotate Left",
"[RIGHT] Rotate Right",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
11.2. ENVIRONMENT INTERACTION
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpUser()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----WIND SPEED---local asciiBase = string.byte("0")
for ct = 0, 9 do
if isKeyPressed(asciiBase+ct) then
releaseKey(asciiBase+ct)
windSpeed = ct/2
clothEnvironment1:setWind(windSpeed,0,-windSpeed)
clothEnvironment2:setWind(windSpeed,0,-windSpeed)
clothEnvironment3:setWind(windSpeed,0,-windSpeed)
break
end
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
149
150
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
----DELETE GLOBALS---if clothEnvironment1 then
clothEnvironment1:delete()
clothEnvironment1 = nil
end
if clothEnvironment2 then
clothEnvironment2:delete()
11.2. ENVIRONMENT INTERACTION
clothEnvironment2 = nil
end
if clothEnvironment3 then
clothEnvironment3:delete()
clothEnvironment3 = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
rotateView = nil
----STOP MUSIC---if soundTrack then
soundTrack:stop()
soundTrack:delete()
soundTrack = nil
end
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
11.2.2
Flag Waver
----CLOTH DEMO (FLAG)
----A cloth simulator
----INITIALIZATION---function FLAG_WAVER.init()
local zip = ALL.init()
----SOME GLOBALS---linkTransform = Transform()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.8,-5)
----MODELS---torso = 0
legs = 6
---> LEGS_IDLE
avatar = zip:getBot("warrior.mdl")
avatar:rescale(0.04)
avatar:pitch(-1.5708)
avatar:rotStanding(1.5708)
avatar:move(0,1,0)
151
152
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
avatar:setUpperAnimation(torso)
avatar:setLowerAnimation(legs)
addObject(avatar)
pole = zip:getMesh("pole2.3ds")
pole:pitch(1.5708)
pole:move(-0.15,0,-0.5)
avatar:getUpper():link("tag_weapon",pole)
----FLAG SIMULATOR---local posHighX, posHighY, posHighZ = -0.15, 0, 5.5
local posLowX, posLowY, posLowZ = -0.15, 0, 2.5
if avatar:getLinkTransform("tag_weapon",linkTransform) then
posHighX, posHighY, posHighZ = linkTransform:multiply(
posHighX, posHighY, posHighZ
)
posLowX, posLowY, posLowZ = linkTransform:multiply(
posLowX, posLowY, posLowZ
)
end
simulator = Simulator()
windSpeed = 2
environment = StaticEnvironment(-windSpeed,0,windSpeed, 0.01)
local SIDE_CT = 17
local SIDE_LEN = 3
cloth = Cloth(
SIDE_CT, SIDE_CT,
---> width, height
posLowX,posLowY,posLowZ, ---> origin
0,SIDE_LEN/SIDE_CT,0,
---> uGen
-SIDE_LEN/SIDE_CT,0,0,
---> vGen
0.01,
---> mass
simulator, environment
)
cloth:setRelaxationCycles(4)
cloth:addNail(0, posLowX,posLowY,posLowZ)
cloth:addNail(SIDE_CT-1, posHighX,posHighY,posHighZ)
local flagMaterial = Material()
flagMaterial:setAmbient(0.7,0.7,0.7)
flagMaterial:setDiffuse(1,1,1)
flagMaterial:setSpecular(1,1,1)
flagMaterial:setShininess(128)
flagMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
flagMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),0.25)
local clothModel = cloth:getMesh()
clothModel:setMaterial(flagMaterial)
addObject(clothModel)
local flagSound = zip:getSample3D("flag.wav");
flagSound:setLooping(1)
flagSound:setVolume(255)
flagSound:setMinDistance(4)
flagSource = Source(flagSound,clothModel);
addSource(flagSource)
11.2. ENVIRONMENT INTERACTION
----HELP---local help = {
"[ MOUSE ] Look around",
"[ UP/DOWN ] Move Forward/Back",
"[PREV/NEXT] Raise/Lower View",
"[ X key ] Change Movement",
"[ Q,W,E,R ] Rotate/Bend Head",
"[ A,S,D,F ] Rotate/Bend Torso",
"[ 0,1...6 ] Select Wind Speed",
"[ 7,8,9 ] Select Flag Elasticity",
"[ SPACE ] Rotate Scene",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function FLAG_WAVER.update()
local camera = getCamera()
local timeStep = getTimeStep()
local fwdSpeed = 0
local rotSpeed = 0
if legs == 0 then ---> LEGS_WALKCR
fwdSpeed = 2.5
rotSpeed = 0.31415
elseif legs == 1 then ---> LEGS_WALK
fwdSpeed = 2.5
rotSpeed = 0.6283
elseif legs == 2 then ---> LEGS_RUN
fwdSpeed = 5
rotSpeed = 0.6283
elseif legs == 3 then ---> LEGS_BACK
fwdSpeed = -3.5
rotSpeed = 0.31415
elseif legs == 8 then ---> LEGS_TURN
rotSpeed = 1.5708
end
avatar:walk(fwdSpeed*timeStep);
avatar:rotStanding(rotSpeed*timeStep);
local stopped = avatar:getLower():getStoppedAnimation()
if stopped == 4 then ---> LEGS_JUMP
avatar:setLowerAnimation(5) ---> LEGS_LAND
elseif stopped == 5 then ---> LEGS_LAND
avatar:setLowerAnimation(6) ---> LEGS_IDLE
153
154
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
end
stopped = avatar:getUpper():getStoppedAnimation()
----MODEL MOVEMENT---if isKeyPressed(string.byte("A")) then
local angle = 3.1415*timeStep
avatar:getUpper():addYawAngle(angle,1.047)
elseif isKeyPressed(string.byte("S")) then
local angle = -3.1415*timeStep
avatar:getUpper():addYawAngle(angle,1.047)
elseif isKeyPressed(string.byte("D")) then
local angle = 3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("F")) then
local angle = -3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("Q")) then
local angle = 3.1415*timeStep
avatar:getHead():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("W")) then
local angle = -3.1415*timeStep
avatar:getHead():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("E")) then
local angle = 3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("R")) then
local angle = -3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
end
if avatar:getLinkTransform("tag_weapon",linkTransform) then
local posX,posY,posZ = -0.15,0,5.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(1,posX,posY,posZ)
posX,posY,posZ = -0.15,0,2.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(0,posX,posY,posZ)
end
local simTime = timeStep
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function FLAG_WAVER.final()
----DELETE GLOBALS---linkTransform:delete()
if environment then
environment:delete()
11.2. ENVIRONMENT INTERACTION
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
flagSource = nil
rotateView = nil
torso = nil
legs = nil
----EMPTY WORLD---avatar = nil
if pole then
pole:delete()
pole = nil
end
cloth = nil
end
----KEYBOARD---function FLAG_WAVER.keyDown(key)
----WIND SPEED & RELAXATION---local asciiBase = string.byte("0")
for ct = 0, 6 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
windSpeed = ct*0.5
environment:setWind(-windSpeed,0,windSpeed)
local sound = flagSource:getSound3D()
sound:stop()
if windSpeed > 2 then
sound:play()
end
break
end
end
for ct = 7, 9 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
local val = 4
if ct == 7 then
val = 2
elseif ct == 9 then
val = 8
end
cloth:setRelaxationCycles(val)
break
end
end
155
156
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
----VARIOUS SCENE MODIFIERS---if key == string.byte("X") then
releaseKey(string.byte("X"))
legs = legs+1
if legs >= 9 then ---> MAX_LEGS_ANIMATIONS
legs = 0 ---> LEGS_WALKCR
elseif legs == 5 then ---> LEGS_LAND
legs = 7 ---> LEGS_IDLECR
end
avatar:setLowerAnimation(legs)
end
ALL.keyDown(key)
end
11.3
Other Examples
----Collection of Particles-Based Physics Engine Demos
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
ALL = {}
STANDARD = {}
TABLES = {}
FLAG_WAVER = {}
JELLY_CUBE = {}
JELLY_TREE = {}
HINGES = {}
CUBES = {}
MENU = {}
----Common functions
function ALL.init()
empty()
enableFog(200, .522,.373,.298)
----SKYBOX---local zip = Zip("PhysicsDemo.dat")
local skyTxt = {
zip:getTexture("SkyboxTop.jpg"),
zip:getTexture("SkyboxLeft.jpg"),
zip:getTexture("SkyboxFront.jpg"),
zip:getTexture("SkyboxRight.jpg"),
zip:getTexture("SkyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky);
----SUN---sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.2588, 0.9659,
11.3. OTHER EXAMPLES
157
zip:getTexture("lensflares.png"),
5, 0.1
)
sun:setColor(0.855,0.475,0.298);
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(1,1,1)
terrainMaterial:setDiffuse(0,0,0)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
terrain:setReflective()
setTerrain(terrain)
return zip
end
function ALL.update(camera,timeStep)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
158
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
end
function ALL.final()
rotateView = nil
----EMPTY WORLD---disableFog()
empty()
end
function ALL.keyDown(key)
if key == string.byte("\r") then
----LOAD MAIN MENU---releaseKey(key)
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
elseif key == string.byte(" ") then
----ROTATE VIEW---releaseKey(key)
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
end
----CLOTH DEMO (STANDARD)
----A simulator of cloths
----INITIALIZATION---function STANDARD.init()
local zip = ALL.init()
----GLOBALS---windSpeed = 4
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
----FLAG SIMULATOR---simulator = Simulator()
clothEnvironment = StaticEnvironment(windSpeed,0,-windSpeed, 0)
local SIDE_CT = 17
local SIDE_LEN = 3
local posX = SIDE_LEN/2
flag = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX,6,0, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
11.3. OTHER EXAMPLES
159
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.05, ---- mass
simulator, clothEnvironment
)
for ct = 0, SIDE_CT-1, 4 do
flag:addNail(ct*SIDE_CT, 0,3+ct*3/SIDE_CT,0)
end
local baseIndex = SIDE_CT*SIDE_CT-SIDE_CT
for ct = 0, SIDE_CT-1, 4 do
flag:addNail(baseIndex+ct, ct*2.5/SIDE_CT,6,0)
end
----FLAG RENDERER---local pole = zip:getMeshes("pole.3ds")
pole:move(0,0,0)
addObject(pole)
local flagSound = zip:getSample3D("flag.wav");
flagSound:setLooping(1)
flagSound:setVolume(255)
flagSound:setMinDistance(5)
flagSource = Source(flagSound,pole);
addSource(flagSource)
local flagMaterial = Material()
flagMaterial:setAmbient(0.7,0.7,0.7)
flagMaterial:setDiffuse(1,1,1)
flagMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
flagMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),.333)
local mesh = flag:getMesh()
mesh:setMaterial(flagMaterial)
addObject(mesh)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[ZXCVB] Relaxation 1-16",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
160
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
----LOOP---function STANDARD.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function STANDARD.final()
flag = nil
if clothEnvironment then
clothEnvironment:delete()
clothEnvironment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
if flagSource then
flagSource:getSound3D():stop()
flagSource = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function STANDARD.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
clothEnvironment:setWind(windSpeed,0,-windSpeed)
local sound = flagSource:getSound3D()
sound:stop()
if windSpeed > 2 then
sound:play()
end
return
end
----RELAXATION CYCLES---if key == string.byte("Z") then
releaseKey(key)
flag:setRelaxationCycles(1)
11.3. OTHER EXAMPLES
161
elseif key == string.byte("X") then
releaseKey(key)
flag:setRelaxationCycles(2)
elseif key == string.byte("C") then
releaseKey(key)
flag:setRelaxationCycles(4)
elseif key == string.byte("V") then
releaseKey(key)
flag:setRelaxationCycles(8)
elseif key == string.byte("B") then
releaseKey(key)
flag:setRelaxationCycles(16)
end
ALL.keyDown(key)
end
----CLOTH DEMO (TABLES)
----A simulator of cloths
----INITIALIZATION---function TABLES.init()
local zip = ALL.init()
----GLOBALS---windSpeed = 1
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.6,6)
camera:rotStanding(3.1415)
----CLOTH SIMULATOR---simulator = Simulator()
local boxX, boxY, boxZ = -2, .75, 0
local boxedObs = BoxedObstruction(boxX,boxY,boxZ, 1.65,.15,1.65)
clothEnvironment1 = StaticEnvironment(windSpeed,0,-windSpeed,.1,boxedObs)
local cylX, cylY, cylZ = 2, .75, 0
local cylindricalObs = CylindricalObstruction(cylX,cylY,cylZ, .85,.15)
clothEnvironment2 = StaticEnvironment(windSpeed,0,-windSpeed,.1,cylindricalObs)
local sphX, sphY, sphZ = 0, 2, -3
local sphericalObs = SphericalObstruction(sphX,sphY,sphZ, 1.05)
clothEnvironment3 = StaticEnvironment(windSpeed,0,-windSpeed,.1,sphericalObs)
local SIDE_CT = 23
local SIDE_LEN = 2.25
local posX = SIDE_LEN/2
local posZ = -posX
local posY = 1.1
local tableCloth1 = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+boxX,posY+boxY,posZ+boxZ, ---- origin
162
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.2, ---- mass
simulator, clothEnvironment1
)
local tableCloth2 = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+cylX,posY+cylY,posZ+cylZ, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.2, ---- mass
simulator, clothEnvironment2
)
local SIDE_CT = 23
local SIDE_LEN = 5
local posX = SIDE_LEN/2
local posZ = -posX
local foulard = Cloth(
SIDE_CT, SIDE_CT, ---- width, height
posX+sphX,posY+sphY,posZ+sphZ, ---- origin
-SIDE_LEN/SIDE_CT,0,0, ---- uGen
0,0,SIDE_LEN/SIDE_CT, ---- vGen
0.4, ---- mass
simulator, clothEnvironment3
)
----TABLES RENDERER---local squareTable = zip:getMeshes("squareTable.3ds")
squareTable:move(-2,0,0)
addObject(squareTable)
local roundTable = zip:getMeshes("roundTable.3ds")
roundTable:move(2,0,0)
addObject(roundTable)
local sphere = zip:getMeshes("sphere.3ds")
sphere:move(0,1,-3)
addObject(sphere)
----CLOTH RENDERER---local foulardMaterial = Material()
foulardMaterial:setAmbient(0.7,0.7,0.7)
foulardMaterial:setDiffuse(1,1,1)
foulardMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
foulardMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),.333)
local tableClothMaterial = Material()
tableClothMaterial:setAmbient(0.7,0.7,0.7)
tableClothMaterial:setDiffuse(1,1,.5)
tableClothMaterial:setDiffuseTexture(zip:getTexture("net.jpg"))
local mesh = tableCloth1:getMesh()
mesh:setMaterial(tableClothMaterial)
addObject(mesh)
local mesh = tableCloth2:getMesh()
mesh:setMaterial(tableClothMaterial)
11.3. OTHER EXAMPLES
addObject(mesh)
local mesh = foulard:getMesh()
mesh:setMaterial(foulardMaterial)
addObject(mesh)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function TABLES.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function TABLES.final()
if clothEnvironment1 then
clothEnvironment1:delete()
clothEnvironment1 = nil
end
if clothEnvironment2 then
clothEnvironment2:delete()
clothEnvironment2 = nil
end
if clothEnvironment3 then
clothEnvironment3:delete()
clothEnvironment3 = nil
end
163
164
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function TABLES.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
clothEnvironment1:setWind(windSpeed,0,-windSpeed)
clothEnvironment2:setWind(windSpeed,0,-windSpeed)
clothEnvironment3:setWind(windSpeed,0,-windSpeed)
return
end
ALL.keyDown(key)
end
----CLOTH DEMO (FLAG)
----A cloth simulator
----INITIALIZATION---function FLAG_WAVER.init()
local zip = ALL.init()
----SOME GLOBALS---linkTransform = Transform()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.8,-5)
----MODELS---torso = 0
legs = 6
---> LEGS_IDLE
avatar = zip:getBot("warrior.mdl")
avatar:rescale(0.04)
avatar:pitch(-1.5708)
avatar:rotStanding(1.5708)
avatar:move(0,1,0)
avatar:setUpperAnimation(torso)
avatar:setLowerAnimation(legs)
addObject(avatar)
pole = zip:getMesh("pole2.3ds")
pole:pitch(1.5708)
11.3. OTHER EXAMPLES
165
pole:move(-0.15,0,-0.5)
avatar:getUpper():link("tag_weapon",pole)
----FLAG SIMULATOR---local posHighX, posHighY, posHighZ = -0.15, 0, 5.5
local posLowX, posLowY, posLowZ = -0.15, 0, 2.5
if avatar:getLinkTransform("tag_weapon",linkTransform) then
posHighX, posHighY, posHighZ = linkTransform:multiply(
posHighX, posHighY, posHighZ
)
posLowX, posLowY, posLowZ = linkTransform:multiply(
posLowX, posLowY, posLowZ
)
end
simulator = Simulator()
windSpeed = 2
environment = StaticEnvironment(-windSpeed,0,windSpeed, 0.01)
local SIDE_CT = 17
local SIDE_LEN = 3
cloth = Cloth(
SIDE_CT, SIDE_CT,
---> width, height
posLowX,posLowY,posLowZ, ---> origin
0,SIDE_LEN/SIDE_CT,0,
---> uGen
-SIDE_LEN/SIDE_CT,0,0,
---> vGen
0.01,
---> mass
simulator, environment
)
cloth:setRelaxationCycles(4)
cloth:addNail(0, posLowX,posLowY,posLowZ)
cloth:addNail(SIDE_CT-1, posHighX,posHighY,posHighZ)
local flagMaterial = Material()
flagMaterial:setAmbient(0.7,0.7,0.7)
flagMaterial:setDiffuse(1,1,1)
flagMaterial:setSpecular(1,1,1)
flagMaterial:setShininess(128)
flagMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
flagMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),0.25)
local clothModel = cloth:getMesh()
clothModel:setMaterial(flagMaterial)
addObject(clothModel)
local flagSound = zip:getSample3D("flag.wav");
flagSound:setLooping(1)
flagSound:setVolume(255)
flagSound:setMinDistance(4)
flagSource = Source(flagSound,clothModel);
addSource(flagSource)
----HELP---local help = {
"[ MOUSE ] Look around",
"[ UP/DOWN ] Move Forward/Back",
"[PREV/NEXT] Raise/Lower View",
166
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
"[ X key ] Change Movement",
"[ Q,W,E,R ] Rotate/Bend Head",
"[ A,S,D,F ] Rotate/Bend Torso",
"[ 0,1...6 ] Select Wind Speed",
"[ 7,8,9 ] Select Flag Elasticity",
"[ SPACE ] Rotate Scene",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function FLAG_WAVER.update()
local camera = getCamera()
local timeStep = getTimeStep()
local fwdSpeed = 0
local rotSpeed = 0
if legs == 0 then ---> LEGS_WALKCR
fwdSpeed = 2.5
rotSpeed = 0.31415
elseif legs == 1 then ---> LEGS_WALK
fwdSpeed = 2.5
rotSpeed = 0.6283
elseif legs == 2 then ---> LEGS_RUN
fwdSpeed = 5
rotSpeed = 0.6283
elseif legs == 3 then ---> LEGS_BACK
fwdSpeed = -3.5
rotSpeed = 0.31415
elseif legs == 8 then ---> LEGS_TURN
rotSpeed = 1.5708
end
avatar:walk(fwdSpeed*timeStep);
avatar:rotStanding(rotSpeed*timeStep);
local stopped = avatar:getLower():getStoppedAnimation()
if stopped == 4 then ---> LEGS_JUMP
avatar:setLowerAnimation(5) ---> LEGS_LAND
elseif stopped == 5 then ---> LEGS_LAND
avatar:setLowerAnimation(6) ---> LEGS_IDLE
end
stopped = avatar:getUpper():getStoppedAnimation()
----MODEL MOVEMENT---if isKeyPressed(string.byte("A")) then
local angle = 3.1415*timeStep
11.3. OTHER EXAMPLES
avatar:getUpper():addYawAngle(angle,1.047)
elseif isKeyPressed(string.byte("S")) then
local angle = -3.1415*timeStep
avatar:getUpper():addYawAngle(angle,1.047)
elseif isKeyPressed(string.byte("D")) then
local angle = 3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("F")) then
local angle = -3.1415*timeStep
avatar:getUpper():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("Q")) then
local angle = 3.1415*timeStep
avatar:getHead():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("W")) then
local angle = -3.1415*timeStep
avatar:getHead():addYawAngle(angle,1.5708)
elseif isKeyPressed(string.byte("E")) then
local angle = 3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
elseif isKeyPressed(string.byte("R")) then
local angle = -3.1415*timeStep
avatar:getHead():addPitchAngle(angle,0.7854)
end
if avatar:getLinkTransform("tag_weapon",linkTransform) then
local posX,posY,posZ = -0.15,0,5.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(1,posX,posY,posZ)
posX,posY,posZ = -0.15,0,2.5
posX,posY,posZ = linkTransform:multiply(posX,posY,posZ);
cloth:setNailPosition(0,posX,posY,posZ)
end
local simTime = timeStep
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function FLAG_WAVER.final()
----DELETE GLOBALS---linkTransform:delete()
if environment then
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
167
168
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
end
windSpeed = nil
flagSource = nil
rotateView = nil
torso = nil
legs = nil
----EMPTY WORLD---avatar = nil
if pole then
pole:delete()
pole = nil
end
cloth = nil
end
----KEYBOARD---function FLAG_WAVER.keyDown(key)
----WIND SPEED & RELAXATION---local asciiBase = string.byte("0")
for ct = 0, 6 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
windSpeed = ct*0.5
environment:setWind(-windSpeed,0,windSpeed)
local sound = flagSource:getSound3D()
sound:stop()
if windSpeed > 2 then
sound:play()
end
break
end
end
for ct = 7, 9 do
if key == asciiBase+ct then
releaseKey(asciiBase+ct)
local val = 4
if ct == 7 then
val = 2
elseif ct == 9 then
val = 8
end
cloth:setRelaxationCycles(val)
break
end
end
----VARIOUS SCENE MODIFIERS---if key == string.byte("X") then
releaseKey(string.byte("X"))
legs = legs+1
if legs >= 9 then ---> MAX_LEGS_ANIMATIONS
11.3. OTHER EXAMPLES
legs = 0 ---> LEGS_WALKCR
elseif legs == 5 then ---> LEGS_LAND
legs = 7 ---> LEGS_IDLECR
end
avatar:setLowerAnimation(legs)
end
ALL.keyDown(key)
end
----JELLY CUBE
-------INITIALIZATION---function JELLY_CUBE.init()
local zip = ALL.init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
----GLOBALS---windSpeed = 0
----FLAG SIMULATOR---simulator = Simulator()
environment = StaticEnvironment(windSpeed,0,-windSpeed, 0)
-- local PARTICLES_COUNT = 16*2+12*2
local SIDE_STEP = 0.5
local HEIGHT = 4
local particlePositions = {
SIDE_STEP*2, HEIGHT+SIDE_STEP*2, -SIDE_STEP*2,
SIDE_STEP,
HEIGHT+SIDE_STEP*2, -SIDE_STEP*2,
-SIDE_STEP,
HEIGHT+SIDE_STEP*2, -SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT+SIDE_STEP*2, -SIDE_STEP*2,
SIDE_STEP*2, HEIGHT+SIDE_STEP*2, -SIDE_STEP,
SIDE_STEP,
HEIGHT+SIDE_STEP*2, -SIDE_STEP,
-SIDE_STEP,
HEIGHT+SIDE_STEP*2, -SIDE_STEP,
-SIDE_STEP*2, HEIGHT+SIDE_STEP*2, -SIDE_STEP,
SIDE_STEP*2, HEIGHT+SIDE_STEP*2, SIDE_STEP,
SIDE_STEP,
HEIGHT+SIDE_STEP*2, SIDE_STEP,
-SIDE_STEP,
HEIGHT+SIDE_STEP*2, SIDE_STEP,
-SIDE_STEP*2, HEIGHT+SIDE_STEP*2, SIDE_STEP,
SIDE_STEP*2, HEIGHT+SIDE_STEP*2, SIDE_STEP*2,
SIDE_STEP,
HEIGHT+SIDE_STEP*2, SIDE_STEP*2,
-SIDE_STEP,
HEIGHT+SIDE_STEP*2, SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT+SIDE_STEP*2, SIDE_STEP*2,
SIDE_STEP*2, HEIGHT+SIDE_STEP, -SIDE_STEP*2,
SIDE_STEP,
HEIGHT+SIDE_STEP, -SIDE_STEP*2,
-SIDE_STEP,
HEIGHT+SIDE_STEP, -SIDE_STEP*2,
169
170
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
-SIDE_STEP*2, HEIGHT+SIDE_STEP, -SIDE_STEP*2,
SIDE_STEP*2, HEIGHT+SIDE_STEP, -SIDE_STEP,
-SIDE_STEP*2, HEIGHT+SIDE_STEP, -SIDE_STEP,
SIDE_STEP*2, HEIGHT+SIDE_STEP, SIDE_STEP,
-SIDE_STEP*2, HEIGHT+SIDE_STEP, SIDE_STEP,
SIDE_STEP*2, HEIGHT+SIDE_STEP, SIDE_STEP*2,
SIDE_STEP,
HEIGHT+SIDE_STEP, SIDE_STEP*2,
-SIDE_STEP,
HEIGHT+SIDE_STEP, SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT+SIDE_STEP, SIDE_STEP*2,
SIDE_STEP*2, HEIGHT-SIDE_STEP, -SIDE_STEP*2,
SIDE_STEP,
HEIGHT-SIDE_STEP, -SIDE_STEP*2,
-SIDE_STEP,
HEIGHT-SIDE_STEP, -SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT-SIDE_STEP, -SIDE_STEP*2,
SIDE_STEP*2, HEIGHT-SIDE_STEP, -SIDE_STEP,
-SIDE_STEP*2, HEIGHT-SIDE_STEP, -SIDE_STEP,
SIDE_STEP*2, HEIGHT-SIDE_STEP, SIDE_STEP,
-SIDE_STEP*2, HEIGHT-SIDE_STEP, SIDE_STEP,
SIDE_STEP*2, HEIGHT-SIDE_STEP, SIDE_STEP*2,
SIDE_STEP,
HEIGHT-SIDE_STEP, SIDE_STEP*2,
-SIDE_STEP,
HEIGHT-SIDE_STEP, SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT-SIDE_STEP, SIDE_STEP*2,
SIDE_STEP*2, HEIGHT-SIDE_STEP*2, -SIDE_STEP*2,
SIDE_STEP,
HEIGHT-SIDE_STEP*2, -SIDE_STEP*2,
-SIDE_STEP,
HEIGHT-SIDE_STEP*2, -SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT-SIDE_STEP*2, -SIDE_STEP*2,
SIDE_STEP*2, HEIGHT-SIDE_STEP*2, -SIDE_STEP,
SIDE_STEP,
HEIGHT-SIDE_STEP*2, -SIDE_STEP,
-SIDE_STEP,
HEIGHT-SIDE_STEP*2, -SIDE_STEP,
-SIDE_STEP*2, HEIGHT-SIDE_STEP*2, -SIDE_STEP,
SIDE_STEP*2, HEIGHT-SIDE_STEP*2, SIDE_STEP,
SIDE_STEP,
HEIGHT-SIDE_STEP*2, SIDE_STEP,
-SIDE_STEP,
HEIGHT-SIDE_STEP*2, SIDE_STEP,
-SIDE_STEP*2, HEIGHT-SIDE_STEP*2, SIDE_STEP,
SIDE_STEP*2, HEIGHT-SIDE_STEP*2, SIDE_STEP*2,
SIDE_STEP,
HEIGHT-SIDE_STEP*2, SIDE_STEP*2,
-SIDE_STEP,
HEIGHT-SIDE_STEP*2, SIDE_STEP*2,
-SIDE_STEP*2, HEIGHT-SIDE_STEP*2, SIDE_STEP*2,
}
local textureCoords = {
0,0, 0,.33, 0,.66, 0,1,
0,.33, 0,0, 0,1, 0,.66,
0,.66, 0,1, 0,0, 0,.33,
0,1, 0,.66, 0,.33, 0,0,
.33,0, .33,.33, .33,.66, .33,1,
.33,.33, .33,.66,
.33,.66, .33,.33,
.33,1, .33,.66, .33,.33, .33,0,
.66,0, .66,.33, .66,.66, .66,1,
.66,.33, .66,.66,
.66,.66, .66,.33,
11.3. OTHER EXAMPLES
171
.66,1, .66,.66, .66,.33, .66,0,
1,0, 1,.33, 1,.66, 1,1,
1,.33, 1,0, 1,1, 1,.66,
1,.66, 1,1, 1,0, 1,.33,
1,1, 1,.66, 1,.33, 1,0,
}
-- local TRIANGLES_COUNT = 18*2+24*3
local triangleIndexes = {
0,1,4, 4,1,5, 5,1,2, 5,2,6, 6,2,3, 6,3,7,
8,4,5, 8,5,9, 9,5,6, 9,6,10, 10,6,7, 10,7,11,
12,8,9, 12,9,13, 13,9,10, 13,10,14, 14,10,11, 14,11,15,
44,41,40, 44,45,41, 45,42,41, 45,46,42, 46,43,42, 46,47,43,
48,45,44, 48,49,45, 49,46,45, 49,50,46, 50,47,46, 50,51,47,
52,49,48, 52,53,49, 53,50,49, 53,54,50, 54,51,50, 54,55,51,
0,16,1, 1,16,17, 1,17,2, 2,17,18, 2,18,3, 3,18,19,
3,19,7, 7,19,21, 7,21,11, 11,21,23, 11,23,15, 15,23,27,
15,27,14, 14,27,26, 14,26,13, 13,26,25, 13,25,12, 12,25,24,
12,24,8, 8,24,22, 8,22,4, 4,22,20, 4,20,0, 0,20,16,
16,28,17, 17,28,29, 17,29,18, 18,29,30, 18,30,19, 19,30,31,
19,31,21, 21,31,33, 21,33,23, 23,33,35, 23,35,27, 27,35,39,
27,39,26, 26,39,38, 26,38,25, 25,38,37, 25,37,24, 24,37,36,
24,36,22, 22,36,34, 22,34,20, 20,34,32, 20,32,16, 16,32,28,
28,40,29, 29,40,41, 29,41,30, 30,41,42, 30,42,31, 31,42,43,
31,43,33, 33,43,47, 33,47,35, 35,47,51, 35,51,39, 39,51,55,
39,55,38, 38,55,54, 38,54,37, 37,54,53, 37,53,36, 36,53,52,
36,52,34, 34,52,48, 34,48,32, 32,48,44, 32,44,28, 28,44,40,
}
-- local STICKS_COUNT = 33*2+24*3+12*2;
local stickIndexes = {
0,1, 1,2, 2,3, 4,5, 5,6, 6,7, 8,9, 9,10, 10,11, 12,13, 13,14, 14,15,
0,4, 4,8, 8,12, 1,5, 5,9, 9,13, 2,6, 6,10, 10,14, 3,7, 7,11, 11,15,
4,1, 5,2, 6,3, 8,5, 9,6, 10,7, 12,9, 13,10, 14,11,
40,41, 41,42, 42,43, 44,45, 45,46, 46,47,
48,49, 49,50, 50,51, 52,53, 53,54, 54,55,
40,44, 44,48, 48,52, 41,45, 45,49, 49,53,
42,46, 46,50, 50,54, 43,47, 47,51, 51,55,
44,41, 45,42, 46,43, 48,45, 49,46, 50,47, 52,49, 53,50, 54,51,
0,16, 1,16, 1,17, 2,17, 2,18, 3,18, 3,19, 7,19, 7,21, 11,21, 11,23,
15,23, 15,27, 14,27, 14,26, 13,26, 13,25, 12,25, 12,24, 8,24, 8,22,
4,22, 4,20, 0,20,
16,17, 17,18, 18,19, 19,21, 21,23, 23,27,
27,26, 26,25, 25,24, 24,22, 22,20, 20,16,
16,28, 17,28, 17,29, 18,29, 18,30, 19,30, 19,31, 21,31, 21,33, 23,33,
23,35, 27,35, 27,39, 26,39, 26,38, 25,38, 25,37, 24,37, 24,36,
22,36, 22,34, 20,34, 20,32, 16,32,
28,29, 29,30, 30,31, 31,33, 33,35, 35,39,
39,38, 38,37, 37,36, 36,34, 34,32, 32,28,
28,40, 29,40, 29,41, 30,41, 30,42, 31,42, 31,43, 33,43, 33,47,
35,47, 35,51, 39,51, 39,55, 38,55, 38,54, 37,54, 37,53, 36,53, 36,52,
34,52, 34,48, 32,48, 32,44, 28,44,
172
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
}
-- local PADDINGS_COUNT = 4+8+4
local paddingIndexes = {
0,55, 12,43, 15,40, 3,52,
13,42, 14,41, 11,44, 7,48, 2,53, 1,54, 4,51, 8,47,
16,39, 28,27, 19,36, 31,24,
}
local paddingKs = {
0.75, 0.75, 0.75, 0.75,
0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75,
0.75, 0.75, 0.75, 0.75,
}
machinery = Machinery(
simulator,environment,1,
particlePositions, stickIndexes,
textureCoords, triangleIndexes,
nil, nil,
nil, nil,
nil, nil,
nil, nil,
paddingIndexes, paddingKs
)
machinery:setRelaxationCycles(1)
machinery:setAirDragEnabled(true)
local model = machinery:getMesh()
local cubeMaterial = Material()
cubeMaterial:setAmbient(0.7,0.7,0.7)
cubeMaterial:setDiffuse(1,1,1)
cubeMaterial:setDiffuseTexture(zip:getTexture("agate.jpg"))
cubeMaterial:setEnvironmentTexture(zip:getTexture("environ.jpg"),.333)
model:setMaterial(cubeMaterial)
addObject(model)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
11.3. OTHER EXAMPLES
end
----LOOP---function JELLY_CUBE.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function JELLY_CUBE.final()
machinery = nil
if environment then
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function JELLY_CUBE.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
environment:setWind(windSpeed,0,-windSpeed)
return
end
ALL.keyDown(key)
end
----JELLY TREE
-------INITIALIZATION---function JELLY_TREE.init()
local zip = ALL.init()
----CAMERA---setAmbient(.5,.5,.5)
173
174
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
----GLOBALS---windSpeed = 4
----FLAG SIMULATOR---simulator = Simulator()
environment = StaticEnvironment(windSpeed,0,-windSpeed, 0)
-- local PARTICLES_COUNT = 21;
local TRUNCK_SIZE = 0.25
local TRUNCK_HEIGHT = 2
local BRANCH_SIZE = 1.5
local BRANCH_HEIGHT = 3
local LEAF_SIZE = 2
local LEAF_HEIGHT = 4
local positions = {
BRANCH_SIZE,LEAF_HEIGHT,-BRANCH_SIZE,
0,0,TRUNCK_SIZE*2,
TRUNCK_SIZE*2,0,0,
0,0,-TRUNCK_SIZE*2,
-TRUNCK_SIZE,TRUNCK_HEIGHT,0,
0,TRUNCK_HEIGHT,TRUNCK_SIZE,
TRUNCK_SIZE,TRUNCK_HEIGHT,0,
0,TRUNCK_HEIGHT,-TRUNCK_SIZE,
0,TRUNCK_HEIGHT+TRUNCK_SIZE,0,
-BRANCH_SIZE,BRANCH_HEIGHT,-BRANCH_SIZE,
-BRANCH_SIZE,BRANCH_HEIGHT,BRANCH_SIZE,
BRANCH_SIZE,BRANCH_HEIGHT,BRANCH_SIZE,
BRANCH_SIZE,BRANCH_HEIGHT,-BRANCH_SIZE,
-LEAF_SIZE,BRANCH_HEIGHT,-LEAF_SIZE,
-LEAF_SIZE,BRANCH_HEIGHT,LEAF_SIZE,
LEAF_SIZE,BRANCH_HEIGHT,LEAF_SIZE,
LEAF_SIZE,BRANCH_HEIGHT,-LEAF_SIZE,
-BRANCH_SIZE,LEAF_HEIGHT,-BRANCH_SIZE,
-BRANCH_SIZE,LEAF_HEIGHT,BRANCH_SIZE,
BRANCH_SIZE,LEAF_HEIGHT,BRANCH_SIZE,
-TRUNCK_SIZE*2,0,0,
}
local textureCoords = {
0, 0, 1, 0,
0, 0,
1, 0,
1, 1,
0, 1,
1, 1,
0, 1,
0, 0, 1, 1,
1, 0,
1, 1,
1, 0,
1, 1,
1, 0,
1, 1,
1, 0, 0, 1,
0, 0,
0, 1,
0, 0,
}
-- local TRIANGLES_COUNT = 44
local triangles = {
20,1,5, 20,5,4,
1,2,6, 1,6,5,
6,2,3, 6,3,7,
11.3. OTHER EXAMPLES
175
7,3,20, 7,20,4,
8,7,9,
7,4,9,
9,4,8,
8,4,10,
8,10,5,
5,10,4,
8,5,11,
8,11,6,
11,5,6,
8,12,7,
8,6,12,
12,6,7,
9,14,13, 9,10,14,
10,15,14, 15,10,11,
15,11,16, 16,11,12,
12,13,16, 12,9,13,
9,17,18, 18,10,9,
19,10,18, 19,11,10,
19,12,11, 19, 0,12,
0,17,9, 0,9,12,
18,17,13, 18,13,14,
19,18,14, 19,14,15,
19,15,16, 19,16, 0,
0,16,13, 0,13,17,
}
-- local STICKS_COUNT = 52
local sticks = {
20,5, 1,6, 2,7, 3,4, 20,4, 1,5, 2,6, 3,7, 6,7, 7,4, 4,5, 5,6,
4,8, 5,8, 6,8, 7,8, 9,4, 9,7, 9,8, 10,4, 10,5, 10,8, 11,8, 11,5, 11,6,
12,6, 12,7, 12,8, 11,10, 10,9, 9,12, 12,11, 15,14, 14,13, 13,16, 16,15,
16,12, 15,11, 10,14, 9,13, 12, 0, 16, 0, 19,11, 19,15,
10,18, 18,14, 17,9, 17,13, 0,17, 17,18, 18,19, 19, 0,
}
-- local PADDINGS_COUNT = 12
local paddings = {
0,8, 19,8, 18,8, 17,8,
13,20, 13,3, 14,1, 14,20, 15,1, 15,2, 16,2, 16,3,
}
local paddingsKs = {
0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75,
0.75, 0.75,
}
-- local NAILS_COUNT = 4;
local nails = {20,1,2,3}
machinery = Machinery(
simulator,environment,2.5,
positions, sticks,
textureCoords, triangles,
nil, nil,
176
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
nil, nil,
nil, nil,
nil, nil,
paddings, paddingsKs, nails
)
machinery:setRelaxationCycles(8)
machinery:setAirDragEnabled()
local model = machinery:getMesh()
local treeMaterial = Material()
treeMaterial:setAmbient(0.7,0.7,0.7)
treeMaterial:setDiffuse(1,1,1)
treeMaterial:setDiffuseTexture(zip:getTexture("wood.jpg"))
model:setMaterial(treeMaterial)
addObject(model)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function JELLY_TREE.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function JELLY_TREE.final()
machinery = nil
if environment then
11.3. OTHER EXAMPLES
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function JELLY_TREE.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
environment:setWind(windSpeed,0,-windSpeed)
return
end
ALL.keyDown(key)
end
----HINGES
-------INITIALIZATION---function HINGES.init()
local zip = ALL.init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
----GLOBALS---windSpeed = 0
----FLAG SIMULATOR---simulator = Simulator()
environment = StaticEnvironment(windSpeed,0,-windSpeed, 0)
-- local PARTICLES_COUNT = 6
local HALF_SIZE
= 0.5
local HALF_HEIGHT = 1.5
local HEIGHT = 4
local LEFT = 1
local positions = {
0+LEFT, -HALF_HEIGHT*2+HEIGHT, 0,
HALF_SIZE+LEFT, -HALF_HEIGHT-HALF_SIZE+HEIGHT, 0,
177
178
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
0+LEFT, -HALF_HEIGHT+HEIGHT, -HALF_SIZE,
0+LEFT, -HALF_HEIGHT+HEIGHT, HALF_SIZE,
HALF_SIZE+LEFT, -HALF_HEIGHT+HALF_SIZE+HEIGHT, 0,
0+LEFT, HEIGHT, 0,
}
local textureCoords = {
0, 0,
1, 1,
1, 0,
0, 1, 0, 0, 1, 1,
}
-- local TRIANGLES_COUNT = 12
local triangles = {
0, 1, 2,
0, 2, 3,
0, 3, 1,
4, 2, 1,
4, 1, 3,
4, 5, 2,
5, 3, 2,
3, 5, 4,
}
-- local STICKS_COUNT = 12
local sticks = {
0,1, 2,0, 2,3, 3,0, 2,1, 3,1, 4,2, 4,3, 3,2, 3,5, 5,2, 5,4,
}
-- local SPRINGS_COUNT = 1;
local springs = {4,1}
local springsKs = {0.02}
-- local BUMPERS_COUNT = 1
local bumpers = {5,1}
local distX = positions[13]-positions[16]
local distY = positions[14]-positions[17]
local distZ = positions[15]-positions[18]
local bumpersLen2 = {distX*distX+distY*distY+distZ*distZ}
-- local NAILS_COUNT = 1
local nails = {5}
machinery = Machinery(
simulator,environment,5,
positions, sticks,
textureCoords, triangles,
nil, nil,
bumpers, bumpersLen2,
springs, springsKs,
nil, nil,
nil, nil, nails
)
machinery:setRelaxationCycles(4)
machinery:setAirDragEnabled()
local model = machinery:getMesh()
local hingeMaterial = Material()
hingeMaterial:setAmbient(0.7,0.7,0.7)
hingeMaterial:setDiffuse(1,1,1)
11.3. OTHER EXAMPLES
179
hingeMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
model:setMaterial(hingeMaterial)
addObject(model)
for ct = 1, table.getn(positions), 3 do
positions[ct] = positions[ct]-LEFT*2
end
-- local DAMPERS_COUNT = 1
local dampers = {4,1}
local dampersKs = {0.1}
machinery2 = Machinery(
simulator,environment,5,
positions, sticks,
textureCoords, triangles,
nil, nil,
bumpers, bumpersLen2,
springs, springsKs,
dampers, dampersKs,
nil, nil, nails
)
machinery2:setRelaxationCycles(4)
machinery2:setAirDragEnabled()
model = machinery2:getMesh()
model:setMaterial(hingeMaterial)
addObject(model)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function HINGES.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
180
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function HINGES.final()
machinery = nil
machinery2 = nil
if environment then
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function HINGES.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
environment:setWind(windSpeed,0,-windSpeed)
return
end
ALL.keyDown(key)
end
----CUBES
-------INITIALIZATION---function CUBES.init()
local zip = ALL.init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
local camera = getCamera()
camera:reset()
camera:setPosition(0,2.5,8)
camera:rotStanding(3.1415)
----GLOBALS---windSpeed = 0
11.3. OTHER EXAMPLES
181
----FLAG SIMULATOR---simulator = Simulator()
local squareTable = zip:getMeshes("squareTable.3ds")
addObject(squareTable)
local boxX, boxY, boxZ = 0, .75, 0
local boxedObs = BoxedObstruction(boxX,boxY,boxZ, 1.65,.15,1.65)
environment = StaticEnvironment(windSpeed,0,-windSpeed, 0.1, boxedObs)
-- local PARTICLES_COUNT = 8
local HALF_SIZE = 0.33
local HEIGHT = 4
local LEFT = 1
local positions = {
HALF_SIZE+LEFT, -HALF_SIZE+HEIGHT, HALF_SIZE,
-HALF_SIZE+LEFT, -HALF_SIZE+HEIGHT, HALF_SIZE,
-HALF_SIZE+LEFT, -HALF_SIZE+HEIGHT, -HALF_SIZE,
HALF_SIZE+LEFT, -HALF_SIZE+HEIGHT, -HALF_SIZE,
HALF_SIZE+LEFT, HALF_SIZE+HEIGHT, HALF_SIZE,
-HALF_SIZE+LEFT, HALF_SIZE+HEIGHT, HALF_SIZE,
-HALF_SIZE+LEFT, HALF_SIZE+HEIGHT, -HALF_SIZE,
HALF_SIZE+LEFT, HALF_SIZE+HEIGHT, -HALF_SIZE,
}
local textureCoords = {
0, 0,
0, 1,
1, 1,
1, 0,
0, 1, 1, 1, 1, 0,
0, 0,
}
-- local TRIANGLES_COUNT = 12
local triangles = {
0, 1, 2,
0, 2, 3,
4, 6, 5,
4, 7, 6,
0, 4, 5,
0, 5, 1,
1, 5, 6,
1, 6, 2,
2, 6, 7,
2, 7, 3,
0, 3, 4,
3, 7, 4,
}
-- local STICKS_COUNT = 18+6
local sticks = {
0, 1,
0, 2,
0, 3,
0, 4,
0, 5,
1, 2, 1, 5,
1, 6,
2, 3,
2, 6,
2, 7,
3, 4,
3, 7, 4, 5,
4, 6,
4, 7,
5, 6,
6, 7,
3,6, 2,5, 1,4, 0,7, 7,5, 1,3,
}
machinery = Machinery(
simulator,environment,1.5,
positions, sticks,
textureCoords, triangles
182
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
)
machinery:setRelaxationCycles(4)
machinery:setAirDragEnabled()
local model = machinery:getMesh()
local cubeMaterial = Material()
cubeMaterial:setAmbient(0.7,0.7,0.7)
cubeMaterial:setDiffuse(1,1,1)
cubeMaterial:setDiffuseTexture(zip:getTexture("foulard.jpg"))
model:setMaterial(cubeMaterial)
addObject(model)
for ct = 1, table.getn(positions), 3 do
positions[ct] = positions[ct]-LEFT*2
end
local paddingsKs = {
0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,
0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33,
0.33,0.33,0.33,0.33,
}
machinery2 = Machinery(
simulator,environment,1.5,
positions, nil,
textureCoords, triangles,
nil, nil,
nil, nil,
nil, nil,
nil, nil,
sticks, paddingsKs
)
machinery2:setRelaxationCycles(4)
machinery2:setAirDragEnabled()
model = machinery2:getMesh()
model:setMaterial(cubeMaterial)
addObject(model)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[ 0-9 ] Wind Speed",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP----
11.3. OTHER EXAMPLES
zip:delete()
end
----LOOP---function CUBES.update()
local camera = getCamera()
local timeStep = getTimeStep()
local simTime = timeStep;
if(simTime > 0.1) then
simTime = 0.1
end
simulator:runStep(simTime)
ALL.update(camera,timeStep)
end
----FINALIZATION---function CUBES.final()
machinery = nil
machinery2 = nil
if environment then
environment:delete()
environment = nil
end
if simulator then
simulator:delete()
simulator = nil
end
windSpeed = nil
ALL.final()
end
----KEY_DOWN---function CUBES.keyDown(key)
----WIND SPEED---local theKey = key-string.byte("0")
if theKey >= 0 and theKey <= 9 then
releaseKey(key)
windSpeed = theKey
environment:setWind(windSpeed,0,-windSpeed)
return
end
ALL.keyDown(key)
end
----MAIN MENU
-------INITIALIZATION----Sets the help strings
function MENU.init()
empty()
183
184
CHAPTER 11. PARTICLE-BASED PHYSICS SIMULATOR
demos = {
STANDARD,
TABLES,
FLAG_WAVER,
JELLY_CUBE,
JELLY_TREE,
HINGES,
CUBES,
}
local help = {
"[ 1 ] Standard",
"[ 2 ] Two Tables",
"[ 3 ] Flag Waver",
"[ 4 ] Jelly Cube",
"[ 5 ] Jelly Tree",
"[ 6 ] Hinges",
"[ 7 ] Cubes",
"",
"[ESC] Exit",
}
setHelp(help)
hideConsole()
showHelpUser()
end
----LOOP----Does nothing
function MENU.update()
end
----KEY_DOWN----Checks the keyboard and starts demos
function MENU.keyDown(key)
local theKey = key-string.byte("0")
if theKey >= 1 and theKey <= table.getn(demos) then
releaseKey(theKey)
local DEMO = demos[theKey]
setScene(Scene(DEMO.init,DEMO.update,DEMO.final,DEMO.keyDown))
end
end
----FINALIZATION----None
function MENU.final()
demos = nil
end
----SCENE SETUP---setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
Chapter 12
3D Sound and Sound
Capture
12.1
3D Sound
12.2
Sound Capture
----INITIALIZATION---function init()
local FREQUENCY = 22050
captureDevice = CaptureDevice(FREQUENCY*3,FREQUENCY,8)
sample = Sample3D()
sample:setVolume(255)
showConsole(false)
print("\nC A P T U R E
A U D I O")
print("press ’B’ to start microphone acquisition")
print("press ’C’ to capture sound data")
print("press ’E’ to stop microphone data")
print("press ’P’ to play sound data")
print("press ’S’ to save sound data to WAV")
print("press ’ENTER’ to go back to demos menu")
print("---------")
end
----LOOP---function update()
end
----FINALIZATION---function final()
sample:delete()
sample = nil
captureDevice:delete()
captureDevice = nil
hideConsole()
185
186
CHAPTER 12. 3D SOUND AND SOUND CAPTURE
end
----KEYDOWN---function keyDown(key)
if key == string.byte("B") then
print("Acquisition START")
captureDevice:start()
elseif key == string.byte("E") then
print("Acquisition STOP")
captureDevice:stop()
elseif key == string.byte("C") then
print("3 seconds sound captured")
captureDevice:capture()
elseif key == string.byte("P") then
print("sound data played")
captureDevice:writeToSample3D(sample)
--local sample = captureDevice:createSample3D()
sample:playAt(0,0,0)
elseif key == string.byte("S") then
print("3 seconds sound saved to \"sound.wav\"")
captureDevice:saveAsWav("sound.wav")
elseif key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
Part II
Advanced Topics
187
Chapter 13
Vertex and Fragment
Programs
13.1
Reflective and Refractive Sphere
13.1.1
Theoretical Introduction
Refraction in Glass Spheres
I present here a simple fragment program that simulates refraction effects in
a glass sphere using ray-tracing techniques. The algorithm applies the laws of
refraction to compute the direction that rays starting from the observer follow
moving through the sphere. Then the color assumed by the fragments is obtained
considering the textures applied to the simple environment that surrounds the
sphere. The techniques described here are absolutely not new, of course, but I
tried to optimize the computations for a real-time implementation on modern
GPUs using the OpenGL extension ARB fragment program.
Keywords: fragment-programs, ray-tracing, OpenGL, refraction, optics.
Introduction
Figure 13.1: Reflection and refraction of a light ray
189
190
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
In this article, that is tailored for beginners and assumes only a basic knowledge of the subject, I describe a simple algorithm implemented in a fragment
program that simulates the refraction of light through a glass sphere. The result,
based on well known techniques, is a demo performing a sphere that reflects and
refracts the light coming from a basic environment composed of a sky box and
an infinite plane.
This paper is organized as follow: In section 13.1.1 I briefly recall the laws
of the propagation of light through means of different density. Successively in
section 13.1.1 I describe the simple environment simulated by the demo. Then
the laws of Optics are applied in section 13.1.1 to derive the equations that
describe the behaviour of a ray of light that meets a transparent sphere and
in section 13.1.2 the fragment program that applies the physical laws to the
particular case in study is analyzed deeply. Finally, some screenshots of the
results are shown in section 13.1.2.
The demo was realized using the APOCALYX 3D Engine [?], an engine
based on OpenGL with basic features that I realized to develop my games and
demos. The whole features of the engine are not the subject of this article
and the only thing to know is that the engine provides, among others, the
functionalities to render on the screen a sky box, a horizontal infinite plane
and a sprite that always faces the camera. These simple features, plus some
command to control the camera through the input devices, are all what we need
to test the fragment program here described.
Light Propagation Laws
In this section I briefly recall the well known laws of light reflection and refraction. The formulae here described are used in the next section to derive the
transformations that a ray follows moving through a glass sphere. For a more
thought introduction to the argument, read any school textbook about physics,
for example [?].
First of all, when a ray of light meets the surface of separation between two
means of different densities (in our case air and glass, for example), it is partly
reflected and partly refracted (see figure ??).
The ray that is reflected lays in the same plane generated by the incoming
ray and the normal to the surface of separation and form with the normal a
reflection angle equal to the incident angle (that’s why the two angles are both
labelled in the figure with î).
The refracted ray lays in the same plane described above but its direction
follows a more complicated law, known as Snell’s law, that links the values of
the incident angle î and the refracted angle r̂ with the following formula
n=
sin(î)
sin(r̂)
(13.1)
where n is known as index of refraction. The value of n depends on the materials
divided by the surface of separation. For example, if the mean from which the
ray comes is air and the second mean is water, the value of n is about 1.33.
If the ray follows the opposite path, the value that must be considerer for the
index of refraction is its inverse 1/n = 1/1.33 ≃ 0.75, so the path is the same but
followed in the opposite verse. This means that, if we follow an imaginary ray
of light starting from the observer in a particular direction, we can discover the
13.1. REFLECTIVE AND REFRACTIVE SPHERE
191
point (and its corresponding color) from which comes the ray of light that reach
the observer from that direction. This is the principle that makes ray-tracers
work.
Those illustrated above are the two laws of Optics that we need to apply
in this situation, so now we have only to define the exact configuration of the
observer, the sphere and the sorrounding environment to apply the formulae
and optimize the computations.
A Simplified Model
Figure 13.2: Diagram of the vectors
Since I want to use fragment programs to apply ray-tracing techniques to
the simulation of the effect of light refraction, I must define a mesh that the
fragment program will paint. A mesh with multiple vertexes in shape of a sphere
is not a good choice, in fact a simple square sprite made of two triangles makes
the implementation of ray-tracing methods almost straightforward and we get
a perfect sphere as a final result.
So the better choice is a sprite that always faces the camera: This means
that the sides of the sprite are aligned with the left-right and down-up directions
of the camera, while the view direction is always perpendicular to the surface
of the sprite, that is the surface where the ray-tracer will paint the sphere.
Then we need an environment that the observer will see reflected on or
refracted through the sphere. To make things simpler, but not trivial, I consider
a sky box, that is a cube that simulates far objects around the camera, but
even a horizontal plane, that simulates an infinite flat ground. Of course, both
the objects are textured, so the problem reduces itself in finding the points
where rays coming from the camera, moving through the sphere according to
the physical laws described before, intersect the infinite plane and the distant
sky box.
The formulae needed are not very difficult to derive with a little knowledge
of vectorial algebra and they fit well in a fragment program. Let’s derive them.
192
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
Light through a Sphere
For a better understanding of the symbols used, refer to figure (??). In that
figure is displayed a sample configuration of the main vectors and points. There
~ and W
~ , while R
~ is
you can see the square sprite, generated by the vectors U
its lower left corner. You can also see the (virtual) sphere, with radius r and
~ and the observer, located in O.
~
center C,
The position of the fragment
~ point that is placed on the sprite is nothing else that a sample point that
The S
the fragment program traverses when applied to the sprite. In fact, I give as
texture coordinate (0, 0) to the left lower corner and (1, 1) to the right upper
corner of the sprite, so when the fragment program is executed appropriate
(s, t) coordinates are generated for every fragment. Those coordinates give the
~ point thanks to the following formula
S
~ = sU
~ + tW
~ +R
~
S
13.1.2
(13.2)
The intersection with the sphere
Now we need to find the point P~ , that is the intersection of the normalized view
~ (not explicitly shown in figure ??) from the observer to the point
direction V
~
~
S. First of all we need the normalized view direction V
~ ~
~ = S−O
V
~ −O
~
S
(13.3)
Then, to find the point of intersection, we can solve this system of equations
~ +O
~
P~ = tV
2
~
~
(P − C) = r2
(13.4)
~ and passing through
in fact the point P~ lays on the straight line generated by V
~
~ The four
O and, at the same time, lays of the sphere of radius r and center C.
scalar equations of system (13.4) involve a simple equation of second order that
can be solved to find the value of t and then the coordinates of P~ .
To solve the second order equation cited above, we get as usual the discriminant. A negative value for the discriminant means that no intersection exists,
so we can drop further computations. The discriminant (reduced) is given by
i2 2
∆ h~ ~
~
~ −C
~ + r2
= V · O−C
− O
4
(13.5)
The value of t is instead
*
~ − C)
~ −
t = −V · (O
r
∆
4
(13.6)
In front of the square root you see only the minus sign because we are interested
in the solution of the second order equation nearest to the observer. This is a
13.1. REFLECTIVE AND REFRACTIVE SPHERE
193
good choice to simplify a little, in fact we can suppose that the observer will
always be outside the sphere.
Finally we got the point P~ substituting t in the first equation of system
(13.4).
Now that we own the point P~ , we can get the normal vector to the surface
~ needed to apply the law of reflection and refraction. N
~ is given
of the sphere N
by
~
~
~
~
~ = P −C = P −C
(13.7)
N
r
~
P~ − C
Apply the reflection law
Here the Optics comes in. First of all we consider the reflection of the view
~ at the intersection point P~ to discover the color seen by the observer
vector V
through reflection.
With simple considerations of vectorial math applied to figure ??, we can
derive the following formula
~ref lected = V
~ − 2(V
~ ·N
~ )N
~
V
(13.8)
~ and N
~ , the reflected ray is obtained with very simple operations.
so given V
This vector is enough to retrieve the color coming from the sky box, in fact the
light from the sky box is defined as coming from an infinite distance: To see a
particular pixel in the sky box an observer needs to watch always in the same
direction, no matter the position (think for example of the parallel rays coming
from the sun).
A little more complex are the computation we must perform to find the
color coming from the horizontal infinite plane. The procedure is very similar
to the one we followed to find the intersection with the sphere, in fact we must
~ref lected (this time considering
compute the intersection of the view direction V
the starting point P~ ) with an infinite plane (the equation of which becomes
particularly simple because it passes through the origin and has vertical normal).
So the system to solve is
~ = tV
~ref lected + P~
X
(13.9)
Xy = 0
~ is the intersection we are looking for. This time there are only first
where X
order equations to solve and the result (Xy = 0 apart) is
Xx = −
Py
Vref lectedx + Px
Vref lectedy
Py
Xz = −
Vref lectedz + Pz
Vref lectedy
(13.10)
(13.11)
Since the horizontal plane is regularly tiled with squares of side a, we can
retrieve the color from these texture coordinates
1
s = f rac
Xx
(13.12)
a
1
t = f rac
Xz
(13.13)
a
194
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
where f rac is a function that returns the fractional part of the argument.
Finally, to choose which of the two colors apply (the one from the sky box
~ref lected
or the one from the plane), we simply consider if the view direction V
points up or down checking its y coordinate.
Apply the refraction law
If you find that the previous computation was long and difficult to follow, expect
more from the following subsection. To start the derivation of the formulae,
consider that the dot product of two normalized vectors is simply equal to the
cosinus of their separation angle, so the incident angle is
~ ·V
~
î = arccos −N
(13.14)
Given the incident angle, we can get the refraction angle applying the Snell’s
law (13.1)
!
sin(î)
(13.15)
r̂ = arcsin
n
and after some passages we get for the cosinus of r̂
q
1
~ ·V
~ )2
n 2 − 1 + (N
cos (r̂) =
n
(13.16)
~ref racted .
Now always considering figure ?? we can find the refracted view V
With the angle r̂ and considerations involving trigonometry and the cross product of vectors, we get
~
~
~ref racted = − cos (r̂) N
~ +N
~ × V × N sin (r̂)
V
~ ×N
~
V
(13.17)
but the modulus of the cross product of two normalized vectors is simply equal
to the sinus of their separation angle, so
~ ×N
~ = sin(î)
V
(13.18)
and finally
~ref racted = − cos (r̂) N
~ + 1N
~ × V
~ ×N
~
V
n
or, applying the rule ~a × ~b × ~c = (~a · ~c) ~b − ~a · ~b ~c,
h
i
~ref racted = − cos (r̂) N
~ +1 V
~ − N ·V
~ N
~
V
n
(13.19)
(13.20)
Apply the refraction law again
Now we have the refracted view direction but, to end the derivation, we must
still exit the sphere, so we need to find the intersection of the refracted view
with the sphere and then the new refracted ray, the one that travels outside the
sphere. Remember that now the ray goes from glass (or whatever material the
sphere is made of) to air, so the index of refraction is 1/n.
13.1. REFLECTIVE AND REFRACTIVE SPHERE
195
The computations are similar to those made before, but more simple in some
aspects, and I leave them as an execise to the interested reader. The final results
are the following:
The new point of intersection is
~ref racted · P~ − C
~ V
~ref racted + P~
P~ 0 = −2V
(13.21)
The cosinus of the new refraction angle is
r
1
~0 ·V
~ref racted )2
− 1 + (N
cos (r̂0 ) = n
n2
(13.22)
~ 0 is the normal at the new point of intersection, while the new refracted
where N
view direction is
h
i
0
0 ~0
0 ~
~ref
~0
~0
V
(13.23)
racted = cos (r̂ ) N + n V − N · Vref racted N
To find the color viewed by the observer along the refracted ray, we can
finally use a procedure similar to the one already applied for the reflected color.
The Fragment Program
The implementation of the formulae derived above is realized using the OpenGL
extension ARB fragment program, whose specification was defined by the Architecture Review Board. You can easily find the related documentation at the
OpenGL official site [?] and I suggest you to grab it if you are not familiar with
the syntax and the meaning of the instructions used in the source.
As you can see reading the code and its comments, some quantities, equal
for all the fragments, were computed outside the fragment program and passed
as local parameters. Whenever possible it was avoided to create temporary
variables and the ALIAS statement was used instead to assign no more needed
variables to new names.
The complete source code is listed in Appendix ?? at the end of the paper.
Read carefully the comments to the code for more details about the implementation of the algorithm.
Final Results
The figures ?? and ?? in Appendix ?? show some actual screenshots of the
demo. There are two different backgrounds: one with a static texture, while the
other uses an animated one. In both backgrounds the same sphere appears in
the situation of total reflection or total refraction.
The total reflection case is more familiar, in fact it is an effect realizable on
accelerated 3D graphic cards with little effort, and the sphere mirrors seamlessly
the ground and the sky. Less familiar is the case of the total refraction, but
playing a bit with the interactive demo, the reader will easily recognize the lens
effect that accompanies the refraction of light through curved surfaces.
To improve further the realism of the scene, the next step consists in adding
the shadow of the sphere and the caustics that the light draws on the ground
concentrating in particular patterns after the refraction, but this is a subject
for another paper.
196
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
Figure 13.3: Reflection and refraction
13.1.3
The Script
----REFRACTIVE SPHERE
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
if not isFragmentProgramSupported() then
showConsole()
error("\nFragment Programs not supported by your OpenGL drivers")
end
setTitle("Refractive Sphere Demo")
----GLOBALS---reflectionAmount = 0.2
refractionIndex = 1.33
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
enableFog(200, 0.6,0.7,1)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.8,-5)
empty()
----SKYBOX---if not fileExists("DemoPack1.dat") then
showConsole()
error("\nERROR: File ’DemoPack1.dat’ not found")
end
local zip = Zip("DemoPack1.dat")
local skyTxt = {
zip:getTexture("skyboxTop.jpg"),
zip:getTexture("skyboxLeft.jpg"),
zip:getTexture("skyboxFront.jpg"),
zip:getTexture("skyboxRight.jpg"),
13.1. REFLECTIVE AND REFRACTIVE SPHERE
197
zip:getTexture("skyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
sky:rotStanding(3.14159)
setBackground(sky)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,0.41,-0.91,
zip:getTexture("lensflares.png"),
5, 0.1
)
setSun(sun)
----GROUND---local groundMaterial = Material()
groundMaterial:setEnlighted(false)
groundMaterial:setEmissive(1,1,1)
groundMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",true))
local ground = FlatTerrain(groundMaterial,500,250)
setTerrain(ground)
----SPRITE/SPHERE---local file = io.open("RefractiveSphere.fp","r")
local source = file:read("*a")
file:close()
fragmentProgram = FragmentProgram(source)
local cubeMapFileNames = {
"cubemapTop.jpg", "cubemapLeft.jpg", "cubemapFront.jpg",
"cubemapRight.jpg", "cubemapBack.jpg", "cubemapTop.jpg"
}
local sphereMaterial = ProgramMaterial()
sphereMaterial:setFragmentProgram(fragmentProgram)
sphereMaterial:setDiffuseTexture(zip:getCubeMapTexture(cubeMapFileNames,false))
sphereMaterial:setGlossTexture(zip:getTexture("marble.jpg",false,false))
sphere = Sprite(0.5,0.5,sphereMaterial)
sphere:setTransparent()
sphere:move(0,1.8,0)
addObject(sphere)
----HELP---local help = {
"Title : Refractive Sphere Demo",
"Engine: http://apocalyx.sf.net",
"Author: Leonardo Boselli",
"E-mail: tetractys@users.sf.net",
" ",
"[ SPACE ] Rotate Around Sphere",
"[ MOUSE ] Look Around",
"[ UP/DOWN ] Move Camera Forward/Back",
"[LEFT/RGHT] Move Camera Left/Right",
"[PREV/NEXT] Raise/Lower Camera",
"[ W,A,S,Z ] Move Sphere Around",
198
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
"[HOME/END ] Raise/Lower Sphere",
"[1,2,3,4,5] Reflection Amount (0%-100%)",
"[ 6,7,8,9 ] Index of Refraction (n)",
"n = 1 (air), 1.33 (water), 1.52 (crown), 1.66 (flint)",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help"
}
setHelp(help)
hideConsole()
showHelpReduced()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
camera:pointTo(sphere:getPosition())
end
----MOVE CAMERA (KEYBOARD)---local moveSpeed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(moveSpeed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-moveSpeed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
if isKeyPressed(string.byte("A")) then
sphere:move(moveSpeed*timeStep,0,0)
end
if isKeyPressed(string.byte("S")) then
13.1. REFLECTIVE AND REFRACTIVE SPHERE
199
sphere:move(-moveSpeed*timeStep,0,0)
end
if isKeyPressed(string.byte("W")) then
sphere:move(0,0,moveSpeed*timeStep)
end
if isKeyPressed(string.byte("Z")) then
sphere:move(0,0,-moveSpeed*timeStep)
end
if isKeyPressed(36) then ---> VK_HOME
sphere:move(0,climbSpeed*timeStep,0)
end
local SIDE = 0.5
local R = SIDE/1.4142/2
if isKeyPressed(35) then ---> VK_END
sphere:move(0,-climbSpeed*timeStep,0)
local x, y, z = sphere:getPosition()
if y < R then
sphere:setPosition(x,R,z)
end
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 2*R then
camera:setPosition(posX,2*R,posZ)
end
----FRAGMENT PROGRAM PARAMETRERS----- constant parameters for geometry and optics
-- SIDE and R already defined
local A = 500/250
-- local parameters for fragment program
local oX, oY, oZ = camera:getPosition()
local cX, cY, cZ = sphere:getPosition()
local ocX, ocY, ocZ = oX-cX, oY-cY, oZ-cZ
fragmentProgram:apply()
fragmentProgram:setLocalParameter(0,
1/R, 1/A, R*R-(ocX*ocX+ocY*ocY+ocZ*ocZ), reflectionAmount
)
local n = refractionIndex
fragmentProgram:setLocalParameter(1, n, 1/n, n*n-1, 1/(n*n)-1)
local sideDirX, sideDirY, sideDirZ = camera:getSideDirection()
local uX, uY, uZ = -SIDE*sideDirX, -SIDE*sideDirY, -SIDE*sideDirZ
fragmentProgram:setLocalParameter(2, uX,uY,uZ,1)
200
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
local upDirX, upDirY, upDirZ = camera:getUpDirection()
local vX, vY, vZ = SIDE*upDirX, SIDE*upDirY, SIDE*upDirZ
fragmentProgram:setLocalParameter(3, vX,vY,vZ,1)
fragmentProgram:setLocalParameter(4, oX,oY,oZ,1)
local roX, roY, roZ =
cX-0.5*uX-0.5*vX-oX,
cY-0.5*uY-0.5*vY-oY,
cZ-0.5*uZ-0.5*vZ-oZ
fragmentProgram:setLocalParameter(5, roX,roY,roZ,1)
fragmentProgram:setLocalParameter(6, cX,cY,cZ,1)
fragmentProgram:setLocalParameter(7, ocX,ocY,ocZ,1)
fragmentProgram:unapply()
end
----FINALIZATION---function final()
----EMPTY WORLD---disableFog()
empty()
if fragmentProgram then
fragmentProgram:delete()
fragmentProgram = nil
end
----GLOBALS---sphere = nil
reflectionAmount = nil
refractionIndex = nil
rotateView = nil
end
----KEYBOARD---function keyDown(key)
local numberKey = key-string.byte("0")
if numberKey >= 1 and numberKey <= 5 then
reflectionAmount = (numberKey-1)*0.25
elseif numberKey >= 6 and numberKey <= 9 then
if numberKey == 6 then
refractionIndex = 1
elseif numberKey == 7 then
refractionIndex = 1.33
elseif numberKey == 8 then
refractionIndex = 1.52
elseif numberKey == 9 then
refractionIndex = 1.66
end
elseif isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
13.1. REFLECTIVE AND REFRACTIVE SPHERE
201
rotateView = 1
end
elseif isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
13.1.4
The Fragment Program
!!ARBfp1.0
# REFRACTIVE SPHERE
# Copyright (c) 2004 Leonardo Boselli (boselli@uno.it)
# written for the "Shader Triathlon - Summer 2004"
# a shader competition organized by ShaderTech.com
# This fragment program simulates a perfect sphere that refracts
# and reflects the surrounding environment. The fragment program
# must be attached to a square sprite that always faces the camera
# so that its sides are always parallel to the ’up’ and ’left’
# orientations of the camera and consequently the view direction
# is perpendicular to the sprite. The environment must be
# composed of a sky box and an infinite plane centered at
# the origin of the coordinates laying on the x and z axis.
#
# Given the sprite, the sky box and the plane so defined,
# the fregment program renders on the sprite a perfect sphere
# that reflects and refracts the sky box and the plane
# as defined by seven local parameters.
#
# First of all, texture[0] must contain the 2D texture applied
# to the infinite plane, while texture[1] must contain the
# CUBE map texture applied to the skybox.
#
# Then the following local parameters must be provided:
# PARAMETERS
PARAM geoK = program.local[0]; # geometric constants
#
.x = 1/r
# contains the inverse of the radius of the sphere
#
.y = 1/a
202
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
# contains the inverse of the length of the side of the texture
# tiles applied to the infinite plane
#
.z = r^2-(O-C)^2 (r2mOC2)
# contains the result of the difference between the square of the
# radius and the square of the length of the vector that goes
# from the center of the sphere to the position of the camera
#
.w = lrp (amount of reflection for lerp)
# contains the amount of reflection of the sphere:
# 0 means no reflection (only refraction)
# 1 means full reflection (no refraction)
PARAM optK = program.local[1]; # optic constants
#
.x = n (refraction index)
# value of the index of refraction. Interesting values are:
# 1 (air, no refraction), 1.33 (water), 1.52 (crown glass),
# 1.66 (flint glass)
#
.y = 1/n (invN - inverse of the refraction index)
# contains the inverse of the index of refraction
#
.z = n^2-1 (n2m1)
# contains the square of the index of refraction minus 1
#
.w = 1/(n^2)-1 (invN2m1)
# contains the square of the inverse of the index of
# refraction minus 1
PARAM U = program.local[2]; # sprite horiz. generator (length l)
# gives the horizontal vector that represent the horizontal side
# of the sprite. It is aligned along the right-left direction of
# the camera and its length is equal to the length of the side of
# the sprite
PARAM W = program.local[3]; # sprite vert. generator (length l)
# gives the vertical vector that represent the vertical side
# of the sprite. It is aligned along the down-up direction of the
# camera and its length is equal to the length of the side of the
# sprite
PARAM O = program.local[4]; # camera location
# contains the position of the camera
PARAM RO = program.local[5]; # R-O (sprite lower-left loc. rel. to O)
# contains the vector that goes from the position of the camera to
# the lower left corner of the sprite. This relation holds:
# R = C - (W + U) / 2
PARAM C = program.local[6]; # center of the sphere
# contains the position of the center of the sphere
PARAM OC = program.local[7]; # O-C (camera loc. relative to C)
# contains the vector that goes from the center of the sphere
# to the position of the camera
13.1. REFLECTIVE AND REFRACTIVE SPHERE
203
# The fragment program makes use of seven temporary variables
# and returns the color of the current (s,t) fragment.
# CODE
# NORMALIZED VIEW DIRECTION TO THE PIXEL
# Given the ’texcoord’s of the current fragment, compute the
# corresponding normalized view direction (V).
TEMP V;
MAD V, fragment.texcoord.x, U, RO;
MAD V, fragment.texcoord.y, W, V;
TEMP invLenV;
DP3 invLenV, V, V;
RSQ invLenV, invLenV.x;
MUL V, invLenV, V;
# SEARCH FOR VIEW-SPHERE INTERSECTIONS
# Given the view direction V, solve the second order equation
# and check if any intersection exists. Kill the fragment if
# view ray does not intersects sphere.
ALIAS delta4 = invLenV;
TEMP VdotOC;
DP3 VdotOC, V, OC;
MAD delta4, VdotOC, VdotOC, geoK.z;
KIL delta4; # NO INTERSECTIONS FOR NEGATIVE DELTA
# NEAREST INTERSECTION
# So at least an intersection exists. Find the nearer to the
# camera (P).
TEMP P;
RSQ delta4, delta4.x;
RCP delta4, delta4.x;
ADD delta4, VdotOC, delta4;
MAD P, -V, delta4, O;
# NORMAL AT INTERSECTION POINT
# Compute the normal vector (N) to the sphere at the intersection
# point.
ALIAS PC = delta4;
SUB PC, P, C;
TEMP N;
MUL N, PC, geoK.x;
ALIAS NdotV = VdotOC;
DP3 NdotV, N, V;
204
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
# PREPARE FOR FIRST REFRACTION (index n)
# Preliminary computations for refraction.
TEMP cosR;
MAD cosR, NdotV, NdotV, optK.z;
RSQ cosR, cosR.x;
RCP cosR, cosR.x;
ADD cosR, cosR, NdotV;
# REFRACTED VIEW
# Find the direction of the refracted view (Vp).
# Now the ray travels in the sphere.
ALIAS Vp = V;
MAD Vp, cosR, -N, V;
MUL Vp, Vp, optK.y;
# COMPUTE PLANE MAP REFLECTION COLOR
# Compute the reflected view and look for the color of the
# corresponding point on the infinite plane, considering
# also the fog parameters (assumed EXP2).
TEMP Vr;
ADD Vr, N, N;
MAD Vr, -Vr, NdotV, V;
ALIAS planeRefl = NdotV;
RCP planeRefl, Vr.y;
MUL planeRefl, planeRefl, P.y;
TEMP fog;
MUL fog, state.fog.params.x, planeRefl;
MUL fog, fog, fog;
POW_SAT fog, 2.7182818.x, -fog.x;
MAD planeRefl, planeRefl, -Vr.xzyw, P.xzyw;
MUL planeRefl, planeRefl, geoK.y;
FRC planeRefl, planeRefl;
TEX planeRefl, planeRefl, texture[1], 2D;
LRP planeRefl, fog, planeRefl, state.fog.color;
# REFRACTED POINT OF INTERSECTION
# Find the point of intersection (Pp) of the refracted view with
# the sphere. The ray travels in the sphere, starting from a
# point on the sphere, so that intersection always exists.
ALIAS Pp = P;
DP3 PC, PC, Vp;
ADD PC, PC, PC;
MAD Pp, -PC, Vp, P;
13.1. REFLECTIVE AND REFRACTIVE SPHERE
205
# CHOOSE THE RIGHT REFLECTIVE COLOR BETWEEN PLANE MAP AND CUBE MAP
# Back to reflection. Find the color on the cube map corresponding
# to the reflected view and choose this or the color from the
# plane according to the direction of the reflected view. If it
# points up, use cube map, while if it points down, use plane.
ALIAS cubeRefl = PC;
TEX cubeRefl, Vr, texture[0], CUBE;
ALIAS colorRefl = PC;
CMP colorRefl, Vr.y, planeRefl, cubeRefl;
# NORMAL AT REFRACTED POINT OF INTERSECTION
# Compute the normal to the sphere at the sedond point of
# intersection.
ALIAS Np = N;
SUB Np, Pp, C;
MUL Np, Np, geoK.x;
ALIAS NdotVp = NdotV;
DP3 NdotVp, Np, Vp;
# PREPARE FOR SECOND REFRACTION (index 1/n, going outside)
# The refracted ray exits the sphere so it is refrected again
# but now the value of the index of refraction is 1/n
MAD cosR, NdotVp, NdotVp, optK.w;
RSQ cosR, cosR.x;
RCP cosR, cosR.x;
SUB cosR, cosR, NdotVp;
# NEW REFRACTED VIEW
# Find the new direction of the refracted view (Vs).
# Now the ray travels outside the sphere.
ALIAS Vs = Vp;
MAD Vs, cosR, Np, Vp;
MUL Vs, Vs, optK.x;
# REFRACTED COLOR
# Given the refracted view, look for the color of the
# corresponding point on the infinite plane, considering
# also the fog parameters (assumed EXP2), and on the
# cube map of the sky box.
# If the refracted view points up, use cube map, while
# if it points down, use plane.
ALIAS cubeRefr = NdotVp;
TEX cubeRefr, Vs, texture[0], CUBE;
ALIAS planeRefr = cosR;
206
CHAPTER 13. VERTEX AND FRAGMENT PROGRAMS
RCP planeRefr, Vs.y;
MUL planeRefr, planeRefr, Pp.y;
MUL fog, state.fog.params.x, planeRefr;
MUL fog, fog, fog;
POW_SAT fog, 2.7182818.x, -fog.x;
MAD planeRefr, planeRefr, -Vs.xzyw, Pp.xzyw;
MUL planeRefr, planeRefr, geoK.y;
FRC planeRefr, planeRefr;
TEX planeRefr, planeRefr, texture[1], 2D;
LRP planeRefr, fog, planeRefr, state.fog.color;
ALIAS colorRefr = Vs;
CMP colorRefr, Vs.y, planeRefr, cubeRefr;
# LERP REFLECTIVE AND REFRACTED COLORS
# Linear interpolate between the computed refracted and
# reflected colors using the given factor and output
# the result.
LRP result.color, geoK.w, colorRefl, colorRefr;
END
Chapter 14
Vertex and Fragment
Shaders
207
208
CHAPTER 14. VERTEX AND FRAGMENT SHADERS
Chapter 15
Collision Detection and
Physics
15.1
ColDet Interface
----COLDET Demo
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
setTitle("ColDet Demo")
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.25,1000)
enableFog(200, 0.6,0.7,1)
local camera = getCamera()
camera:reset()
camera:setPosition(0,15,-5)
empty()
----SKYBOX---if not fileExists("DemoPack1.dat") then
showConsole()
error("\nERROR: File ’DemoPack1.dat’ not found")
end
local zip = Zip("DemoPack1.dat")
local skyTxt = {
zip:getTexture("skyboxTop.jpg"),
zip:getTexture("skyboxLeft.jpg"),
zip:getTexture("skyboxFront.jpg"),
zip:getTexture("skyboxRight.jpg"),
zip:getTexture("skyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
sky:rotStanding(-1.5708)
setBackground(sky)
209
210
CHAPTER 15. COLLISION DETECTION AND PHYSICS
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,0.41,-0.91,
zip:getTexture("lensflares.png"),
5, 0.1
)
setSun(sun)
----3DS TERRAIN---local terrain = zip:getMesh("terrain.3ds")
terrain:pitch(-1.57)
addObject(terrain)
terrainCollider = Collider(terrain,true)
----3DS BALL---remTimeStep = 0
velX, velY, velZ = 0, 0, 0
ball = zip:getMesh("sphere.3ds")
ball:move(0,30,10)
addObject(ball)
ballCollider = Collider(ball,true)
----HELP---local help = {
"[ SPACE ] Rotate Around Object",
"[ MOUSE ] Look Around",
"[ UP/DOWN ] Move Camera Forward/Back",
"[LEFT/RGHT] Move Camera Left/Right",
"[PREV/NEXT] Raise/Lower Camera",
"[ W,A,S,Z ] Move Object Around",
"[HOME/END ] Raise/Lower Object",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help"
}
setHelp(help)
hideConsole()
showHelpReduced()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
----COLLISION DETECTION---local STEPS_PER_SEC = 100
local SUB_TIME_STEP = 1/STEPS_PER_SEC
timeStep = timeStep+remTimeStep
if timeStep > 0.1 then
timeStep = 0.1
15.1. COLDET INTERFACE
211
end
local STEPS = math.floor(timeStep*STEPS_PER_SEC)
remTimeStep = timeStep-STEPS*SUB_TIME_STEP
for tick = 1, STEPS do
local balX, balY, balZ = ball:getPosition()
if terrainCollider:collision(ballCollider) then
local colX, colY, colZ = terrainCollider:getCollisionPoint()
local nX, nY, nZ = balX-colX, balY-colY, balZ-colZ
local n2 = nX*nX+nY*nY+nZ*nZ
local velDotNorm = velX*nX+velY*nY+velZ*nZ
if velDotNorm < 0 then
velDotNorm = 2*velDotNorm/n2
velX = velX-nX*velDotNorm
velY = velY-nY*velDotNorm
velZ = velZ-nZ*velDotNorm
balY = balY+0.5
end
end
velY = velY-9.81*SUB_TIME_STEP
balX = balX+velX*SUB_TIME_STEP
balY = balY+velY*SUB_TIME_STEP
balZ = balZ+velZ*SUB_TIME_STEP
if balX < -80 or balX > 80 then
velX = -velX
end
if balZ < -80 or balZ > 80 then
velZ = -velZ
end
ball:setPosition(balX,balY,balZ)
end
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
local x,y,z = ball:getPosition()
camera:pointTo(x,y+1,z)
end
----MOVE CAMERA (KEYBOARD)---local moveSpeed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(moveSpeed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-moveSpeed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
212
CHAPTER 15. COLLISION DETECTION AND PHYSICS
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
end
----FINALIZATION---function final()
----EMPTY WORLD---disableFog()
empty()
----COLLIDERS---if ballCollider then
ballCollider:delete()
ballCollider = nil
end
if terrainCollider then
terrainCollider:delete()
terrainCollider = nil
end
----GLOBALS---remTimeStep = nil
velX, velY, velZ = nil, nil, nil
ball = nil
rotateView = nil
end
----KEYBOARD---function keyDown(key)
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
elseif isKeyPressed(string.byte("\r")) then
15.2. ODE INTERFACE
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
15.2
ODE Interface
----BOUNCING BALL
----ODE Demo
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INITIALIZATION---function init()
----CAMERA---setAmbient(.5,.5,.5)
setPerspective(60,.5,3000)
enableFog(1000, .4,.4,1)
local camera = getCamera()
camera:reset()
camera:setPosition(0,1.6,6)
camera:rotStanding(3.1415)
empty()
----SKYBOX---if not fileExists("DemoPack1.dat") then
showConsole()
error("\nERROR: File ’DemoPack1.dat’ not found.")
end
local zip = Zip("DemoPack1.dat")
local skyTxt = {
zip:getTexture("skyboxTop.jpg"),
zip:getTexture("skyboxLeft.jpg"),
zip:getTexture("skyboxFront.jpg"),
zip:getTexture("skyboxRight.jpg"),
zip:getTexture("skyboxBack.jpg")
}
local sky = MirroredSky(skyTxt)
setBackground(sky);
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0.0, 0.41, 0.91,
zip:getTexture("lensflares.png"),
213
214
CHAPTER 15. COLLISION DETECTION AND PHYSICS
4, 0.2
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(.7,.7,.7)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("marble.jpg",1))
local terrain = FlatTerrain(terrainMaterial,3000,300)
terrain:setReflective()
setTerrain(terrain)
terrainMaterial:delete()
----SPHERE---sphere = zip:getMesh("sphere.3ds")
sphere:move(0,1,-3)
addObject(sphere)
sphere2 = zip:getMesh("sphere.3ds")
sphere2:move(0,1,-3)
addObject(sphere2)
----ODE---timeLeft = 0
odeWorld = OdeWorld()
odeWorld:setGravity(0,-9.81,0)
odeMass = OdeMass()
odeBody = odeWorld:createBody()
odeBody:setPosition(0,2,-3)
odeBody:setLinearVel(0,10,-1)
odeMass:setSphere(10,1)
odeBody:setMass(odeMass)
odeBody2 = odeWorld:createBody()
odeBody2:setPosition(0.1,5,-3)
odeBody2:setLinearVel(0,10,-1)
odeBody2:setMass(odeMass)
odeSimpleSpace = OdeSimpleSpace()
odeGeomSphere = OdeSphere(1,odeSimpleSpace)
odeGeomSphere:setBody(odeBody)
odeTriMeshData = OdeTriMeshData()
odeTriMeshData:build(sphere:getShape())
-- odeGeomSphere2 = OdeTriMesh(odeTriMeshData,odeSimpleSpace)
odeGeomSphere2 = OdeSphere(1,odeSimpleSpace)
odeGeomSphere2:setBody(odeBody2)
odeGeomPlane = OdePlane(0,1,0, 0, odeSimpleSpace)
odeJointGroup = OdeJointGroup()
odeContactsInfo = OdeContactsInfo(5)
odeContactsInfo:setBounce(0.9)
odeContactsInfo:setMu(0.75)
----HELP---local help = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
15.2. ODE INTERFACE
215
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
"[SPACE] On/Off Rotation",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help",
}
setHelp(help)
showHelpReduced()
hideConsole()
----DELETE ZIP---zip:delete()
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then
timeStep = 0.1
end
----ODE---timeStep = timeStep+timeLeft
local steps = math.floor(timeStep*1000)
timeLeft = timeStep-steps*0.001
for step = 1, steps do
odeSimpleSpace:innerCollide(odeContactsInfo,odeWorld,odeJointGroup)
odeWorld:quickStep(0.001)
odeJointGroup:empty()
end
odeBody:getTransform(sphere)
odeBody2:getTransform(sphere2)
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
216
CHAPTER 15. COLLISION DETECTION AND PHYSICS
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0,climbSpeed*timeStep,0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
if fileExists("main.lua") then
final()
dofile("main.lua")
end
return
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINALIZATION---function final()
sphere = nil
sphere2 = nil
rotateView = nil
----ODE---timeLeft = nil
odeMass:delete()
odeBody:delete()
odeBody2:delete()
odeWorld:delete()
odeTriMeshData:delete()
odeSimpleSpace:delete()
odeGeomSphere = nil
odeGeomSphere2 = nil
15.2. ODE INTERFACE
odeGeomPlane = nil
odeContactsInfo:delete()
odeJointGroup:delete()
OdeClose()
----EMPTY WORLD---disableFog()
empty()
end
----SCENE SETUP---setScene(Scene(init,update,final))
217
218
CHAPTER 15. COLLISION DETECTION AND PHYSICS
Chapter 16
Interpreters and Compilers
16.1
TinyC Compiler
16.1.1
C Functions in the Game Loop
LUA Script
----Functions mixture: "init" in C, the others in LUA
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
cc = Compiler()
ok = cc:compileFile("TinyCInit.c")
if ok then
ok = cc:link()
end
if ok then
c_init = cc:getFunction("init")
c_moveUpTransform = cc:getFunction("moveUpTransform")
end
----LOOP---function update()
local camera = getCamera()
local timeStep = getTimeStep()
----ROTATE VIEW---if rotateView then
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
end
----MOVE CAMERA (KEYBOARD)---local speed = 3
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
219
220
CHAPTER 16. INTERPRETERS AND COMPILERS
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding(0.4*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-0.4*timeStep)
end
local climbSpeed = 3
if isKeyPressed(33) then --> VK_PRIOR
c_moveUpTransform(camera:getPointer(), climbSpeed*timeStep)
end
if isKeyPressed(34) then --> VK_NEXT
c_moveUpTransform(camera:getPointer(), -climbSpeed*timeStep)
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
----FINAL---function final()
----DELETE GLOBALS---rotateView = nil
cc = nil
c_init = nil
ok = nil
----EMPTY WORLD---disableFog()
empty()
end
----KEYDOWN---function keyDown(key)
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
final()
dofile("main.lua")
return
16.1. TINYC COMPILER
end
if isKeyPressed(string.byte(" ")) then
releaseKey(string.byte(" "))
if rotateView then
rotateView = nil
else
rotateView = 1
end
end
end
----SCENE SETUP---if c_init then
setScene(Scene(c_init,update,final,keyDown))
end
C Code
#include "apocalyx.h"
#include "lua.h"
int init() {
lua_State* LS = lua_getstate();
//----CAMERA---worldSetAmbient(0.5,0.5,0.5);
worldSetPerspective(60,0.5,3000);
Camera* camera = worldGetCamera();
Transform* cameraTransform = cameraCastToTransform(camera);
transformReset(cameraTransform);
Vector* v = vectorCreate(0,1.8f,-5);
transformSetPosition(cameraTransform,v);
vectorDelete(v);
transformRotStanding(cameraTransform,3.1415);
worldEmpty();
//----SKYBOX---Zip* zip = zipCreate("DemoPack1.dat",1);
Texture* skyTxt[5] = {
zipCreateTexture(zip,"skyboxTop.jpg",0,1,0),
zipCreateTexture(zip,"skyboxLeft.jpg",0,1,0),
zipCreateTexture(zip,"skyboxFront.jpg",0,1,0),
zipCreateTexture(zip,"skyboxRight.jpg",0,1,0),
zipCreateTexture(zip,"skyboxBack.jpg",0,1,0)
};
Background* sky = mirroredSkyCreate(skyTxt);
worldSetBackground(sky,1);
worldEnableFog(750, 0.5,0.5,0.75);
//----SUN---Vector* dir = vectorCreate(0,0.41,0.91);
Sun* sun = sunCreate(
zipCreateTexture(zip,"light.jpg",0,1,0),0.25,dir,
221
222
CHAPTER 16. INTERPRETERS AND COMPILERS
zipCreateTexture(zip,"lensflares.png",0,1,0),
4, 0.2, 1000, 1
);
vectorDelete(dir);
worldSetSun(sun);
//----TERRAIN---Material* terrainMaterial = materialCreate();
materialSetAmbient(terrainMaterial,0.7f,0.7f,0.7f,1);
materialSetDiffuse(terrainMaterial,1,1,1,1);
materialSetDiffuseTexture(
terrainMaterial,zipCreateTexture(zip,"marble.jpg",1,1,0)
);
FlatTerrain* flatTerrain = flatTerrainCreate(terrainMaterial,3000,300);
materialDelete(terrainMaterial);
Terrain* terrain = flatTerrainCastToTerrain(flatTerrain);
terrainSetReflective(terrain,1);
worldSetTerrain(terrain);
//----HELP---const char* help[] = {
"[MOUSE] Look Around",
"[ UP ] Move Forward",
"[DOWN ] Move Backward",
"[PREV ] Move UP",
"[NEXT ] Move Down",
" ",
"[ENTER] Demos Menu",
"[F1] Show/Hide Help"
};
appSetConsoleVisible(0);
appSetHelpMode(2);
appSetHelp(8,help);
//----DELETE ZIP---zipDelete(zip);
return 0;
}
int moveUpTransform() {
lua_State* LS = lua_getstate();
void* ptr = lua_touserdata(LS,1);
double val = lua_tonumber(LS,2);
Transform* camera =
matrixCastToTransform(voidCastToMatrix(pointerReinterpretAsVoid(ptr)));
// It is not a good idea to create a vector this way,
// because of the performance hit of several
// creations and deletions.
// It is better to create it once for all, thus
// several functions can use it. Then it is
// deleted during the ’finalization’ phase
// only once.
Vector* vec = vectorCreate(0,val,0);
16.1. TINYC COMPILER
223
transformMove(camera, vec);
vectorDelete(vec);
return 0;
}
16.1.2
Loading DLLs
LUA Script
----Several ways to call functions stored in Dynamic Link Libraries
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INIT---function init()
showConsole(false)
---print("\n*** BEGIN ***\n")
print("\n1) Loading a DLL function through LUA using loadlib()")
local norm = package.loadlib("SampleLibrary.dll","luaNorm")
local val = norm(2,3,4)
print("The norm of (2,3,4) is ",val)
---print("\n2) Loading a DLL function through C with...")
cc = Compiler()
ok = cc:compileFile("LoadDLL.c");
if ok then
cc:addLibrary("SampleLibrary.dll")
ok = cc:link()
else
print("\nCompile error")
end
if ok then
c_function = cc:getFunction("main")
else
print("\nLink error")
end
if c_function then
c_function()
else
print("\nCompiler:getFunction() error")
end
print("\n*** END ***\n\n")
print("Press ’ENTER’ to go back to demos menu")
----HELP---local help = {
"[ENTER] Demos Menu",
}
setHelp(help)
showHelpReduced()
end
224
CHAPTER 16. INTERPRETERS AND COMPILERS
----LOOP---function update()
end
----FINAL---function final()
if cc then
cc:delete()
cc = nil
end
c_function = nil
ok = nil
end
----KEYDOWN---function keyDown(key)
----LOAD MAIN MENU---if key == string.byte("\r") then
releaseKey(string.byte("\r"))
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
C Code
//Several ways to call functions stored in Dynamic Link Libraries
//Questions? Contact: Leonardo Boselli <boselli@uno.it>
// Only a few functions definitions: No need to load long headers
void printf(const char* fmt, ...);
typedef struct Library Library;
Library* libraryLoad(const char* libName);
void libraryFree(Library* lib);
void* libraryGetProcAddress(Library* lib, const char* funcName);
typedef float (*NormFunc)(float x, float y, float z);
float norm(float x, float y, float z);
// function main() loaded and executed by LUA
int main(void) {
printf("a) ... the use of libraryGetProcAddress()\n");
Library* lib = libraryLoad("SampleLibrary.dll");
NormFunc normFunc = (NormFunc)libraryGetProcAddress(lib,"norm");
16.2. ANGELSCRIPT INTERPRETER
225
// This way there is no need to use Compiler:addLibrary() from LUA
float val = normFunc(2,3,4);
libraryFree(lib);
printf("The norm of (2,3,4) is %f\n",val);
//
printf("b) ... the use of the addLibrary() method\n");
// The linker looks for norm() in the DLLs added by LUA with Compiler:addLibrary()
val = norm(2,3,4);
printf("The norm of (2,3,4) is %f\n",val);
//
// Since the function does not return any argument to the LUA caller,
// it specifies that zero argument were pushed on the LUA stack.
// That’s why this function MUST return zero.
return 0;
}
16.2
AngelScript Interpreter
----AngelScript Demo
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INIT---function init()
showConsole(false)
print("\n *** Begin AngelScript *** \n")
scriptEngine = ScriptEngine()
local ok = scriptEngine:addScriptSection(
"moduleA", "sectionA",
[[
double calcSin(double value) {
printnum(value*value);
printchr("\n");
return sin(value);
}
]]
)
if ok then
print("Done addScriptSection\n")
ok = scriptEngine:build("moduleA")
if ok then
print("Done build\n")
local funcIndex = scriptEngine:getFunctionIDByDecl("moduleA","double calcSin(double)")
print("function index = ",funcIndex,"\n")
if funcIndex >= 0 then
scriptThread = scriptEngine:createThread(1000)
ok = scriptThread:prepare(funcIndex)
if ok then
print("Prepared\n")
ok = scriptThread:setArgDouble(0,0.4)
226
CHAPTER 16. INTERPRETERS AND COMPILERS
if ok then
print("SetArg\n")
scriptThread:execute(1000)
print("Executed 1000\n")
local ret = scriptThread:getReturnDouble()
print("The result of CalcSin is ",ret,"\n")
end
end
end
end
end
print("\n *** End AngelScript ***\n")
print("Press ’Enter’ to go back to demos menu")
----HELP---local help = {
"[ENTER] Demos Menu",
}
setHelp(help)
showHelpReduced()
end
----LOOP---function update()
end
----FINAL---function final()
if scriptThread then
scriptThread:delete()
end
if scriptEngine then
scriptEngine:delete()
end
end
----KEYDOWN---function keyDown(key)
----LOAD MAIN MENU---if key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
16.3. CSL INTERPRETER
16.3
Csl Interpreter
----CSL Demo
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
----INIT---function init()
showConsole(false)
print("\n*** Begin CslScript ***\n")
interpreter = Interpreter()
local errs = interpreter:loadString(
[[
var squareroot(var value) {
print("\nsetSeed() = ");
print(setSeed(0));
print("\nrandom() = ");
print(random(10));
print("\nrandom() = ");
print(random(10));
print("\n");
return sqrt(value);
}
]]
)
if errs == 0 then
print("Done loadString\n")
local args = {"2"}
local errs = interpreter:call("squareroot",args)
if errs == 0 then
local retVal, errs = interpreter:getResult()
if errs == 0 then
print("The result is ",retVal,"\n")
end
end
else
for ct = 0, errs-1 do
print(interpreter," ",interpreter:getError(ct))
end
end
print("\n*** End CslScript ***\n\n")
print("Press ’Enter’ to go back to demos menu")
----HELP---local help = {
"[ENTER] Demos Menu",
}
setHelp(help)
showHelpReduced()
end
----LOOP----
227
228
CHAPTER 16. INTERPRETERS AND COMPILERS
function update()
end
----FINAL---function final()
if interpreter then
interpreter:delete()
end
end
----KEYDOWN---function keyDown(key)
----LOAD MAIN MENU---if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
16.4
SMALL Interpreter
Chapter 17
Artificial Intelligence
17.1
Finite State Machines
----INITIALIZATION---function printDropSnack()
print("Snack dropped!")
end
function printDropCola()
print("Cola dropped!")
end
function init()
showConsole(false)
print("\nS N A C K
M A C H I N E")
print("Demo of several features of FSM\n")
print("The machine provides snacks at 50 cents,")
print("drinks at 60 cents and gives no change.")
print("With an excess of 3 coins the machine")
print("resets and you lose your money.")
print("Press ’A’ to insert 10 cents")
print("press ’B’ to insert 50 cents")
print("press ’C’ to drop snack")
print("press ’D’ to drop cola")
print("press ’ENTER’ to go back to demos menu")
print("---------")
fsm = FiniteStateMachine()
local var = fsm:addVariable("excess")
local state = fsm:addState("start")
local actionSet = state:addLeaveActionSet()
actionSet:setVariable("excess")
actionSet:setValue(0)
state:addTransition("total 10","pressed A")
state:addTransition("total 50","pressed B")
229
230
CHAPTER 17. ARTIFICIAL INTELLIGENCE
state = fsm:addState("total 10")
state:addTransition("total 20","pressed A")
state:addTransition("total 60","pressed B")
state = fsm:addState("total 20")
state:addTransition("total 30","pressed A")
state:addTransition("total too much","pressed B")
state = fsm:addState("total 30")
state:addTransition("total 40","pressed A")
state:addTransition("total too much","pressed B")
state = fsm:addState("total 40")
state:addTransition("total 50","pressed A")
state:addTransition("total too much","pressed B")
state = fsm:addState("total 50")
state:addTransition("total 60","pressed A")
state:addTransition("total too much","pressed B")
local transition = state:addTransition("start","pressed C")
local actionCall = transition:addActionCall()
actionCall:setFunction("printDropSnack")
state = fsm:addState("total 60")
state:addTransition("total too much","pressed A")
state:addTransition("total too much","pressed B")
transition = state:addTransition("start","pressed C")
actionCall = transition:addActionCall()
actionCall:setFunction("printDropSnack")
transition = state:addTransition("start","pressed D")
actionCall = transition:addActionCall()
actionCall:setFunction("printDropCola")
state = fsm:addState("total too much")
transition = state:addTransition("total too much","pressed A")
local actionIncr = transition:addActionIncr()
actionIncr:setVariable("excess")
actionIncr:setIncrement(1)
transition = state:addTransition("total too much","pressed B")
actionIncr = transition:addActionIncr()
actionIncr:setVariable("excess")
actionIncr:setIncrement(1)
transition = state:addTransition("start","pressed C")
actionCall = transition:addActionCall()
actionCall:setFunction("printDropSnack")
transition = state:addTransition("start","pressed D")
actionCall = transition:addActionCall()
actionCall:setFunction("printDropCola")
transition = state:addTransition("start","ANY")
local condition = transition:addCondition()
condition:setCondition("excess",4,3) ---> 4 = GREATER_EQ
fsm:setInitialState("start")
fsm:start()
end
----LOOP----
17.1. FINITE STATE MACHINES
function update()
end
----FINALIZATION---function final()
hideConsole()
fsm:stop()
fsm:delete()
fsm = nil
end
----KEYDOWN---function keyDown(key)
if key == string.byte("A") then
print("* Pressed A")
fsm:processEvent("pressed A")
fsm:processEvent("ANY")
local state = fsm:getCurrentState()
print("State : ",state:getName())
local excess = fsm:getVariable("excess")
print("Excess: ",excess)
elseif key == string.byte("B") then
print("* Pressed B")
fsm:processEvent("pressed B")
fsm:processEvent("ANY")
local state = fsm:getCurrentState()
print("State : ",state:getName())
local excess = fsm:getVariable("excess")
print("Excess: ",excess)
elseif key == string.byte("C") then
print("* Pressed C")
fsm:processEvent("pressed C")
local state = fsm:getCurrentState()
print("State : ",state:getName())
local excess = fsm:getVariable("excess")
print("Excess: ",excess)
elseif key == string.byte("D") then
print("* Pressed D")
fsm:processEvent("pressed D")
local state = fsm:getCurrentState()
print("State : ",state:getName())
local excess = fsm:getVariable("excess")
print("Excess: ",excess)
elseif key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
end
end
231
232
CHAPTER 17. ARTIFICIAL INTELLIGENCE
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
17.2
Path Finding
----BSP LEVEL DEMO
----A viewer of BSP levels
----Questions? Contact: Leonardo Boselli <boselli@uno.it>
---------------------MENU SCENE--------------------cc = Compiler()
ok = cc:compileString([[
float leastCostEstimate(int nodeA, int nodeB) {
int a = nodeA-1;
int b = nodeB-1;
int aX = a/4;
int aY = a%4;
int bX = b/4;
int bY = b%4;
float diffX = aX-bX;
float diffY = aY-bY;
return diffX*diffX+diffY*diffY;
}
int neighsCount[17] = {
0,
2, 3, 3, 2,
3, 4, 4, 3,
3, 4, 4, 3,
2, 3, 3, 2
};
int neighs[17][4] = {
{0,0,0,0},
{2, 5, 0, 0}, {1, 6, 3, 0}, {2, 7, 4, 0}, {3, 8, 0, 0},
{1, 6, 9, 0}, {2, 7, 10, 5}, {3, 8, 11, 6}, {4, 7, 12, 0},
{5,10,13, 0}, {6,11,14,9}, {7,12,15,10}, {8,11,16, 0},
{9,14, 0, 0}, {13,10,15, 0}, {14,11,16, 0}, {12,15, 0, 0}
};
float costs[17][4] = {
{0,0,0,0},
{1, 1, 0, 0}, {1, 1, 1, 0}, {1, 1, 1, 0}, {1, 1, 0, 0},
{1, 1, 1, 0}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0},
{1, 1, 1, 0}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 0},
{1, 1, 0, 0}, {1, 1, 1, 0}, {1, 1, 1, 0}, {1, 1, 0, 0}
};
int adjacentCost(int node, int** n, float** c) {
17.2. PATH FINDING
233
if(node <= 0) return 0;
*n = neighs[node];
*c = costs[node];
return neighsCount[node];
}
]])
if ok then
ok = cc:link()
end
if ok then
leastCostEstimate = cc:getFunction("leastCostEstimate")
adjacentCost = cc:getFunction("adjacentCost")
end
path = PathFound()
finder = PathFinder()
finder:setLeastCostEstimate(leastCostEstimate)
finder:setAdjacentCost(adjacentCost)
finder:solve(1,16,path)
print("\nPath. Cost = ",path:getCost(),"; Size = ",path:getSize())
for ct = 0,path:getSize()-1 do
print("node ",ct,": ",path:getNode(ct))
end
leastCostEstimate = nil
adjacentCost = nil
ok = nil
path:delete()
finder:delete()
cc:delete()
----INITIALIZATION---function init()
showConsole(false)
print("Press ’ENTER’ to go back to demos menu")
end
----LOOP---function update()
end
----FINALIZATION---function final()
end
----KEYDOWN---function keyDown(key)
if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
234
CHAPTER 17. ARTIFICIAL INTELLIGENCE
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
17.3
Steering Behaviors
--[[
S T E E R I N G
B E H A V I O R S
Questions? Contact leo at tetractys@users.sourceforge.net
--]]
----MODULES---DEMO = {}
ALL = {}
----MODELS SUPPORT---function ALL.createModel(zip,modelMD2,modelTXT,posX,posY,posZ,scale,anim)
local model = zip:getMD2Model(modelMD2,modelTXT)
material = model:getMaterial()
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,1)
material:setShininess(64)
model:rescale(0.04*scale)
model:pitch(-1.5708)
model:move(posX,posY,posZ)
model:setAnimation(anim)
addObject(model)
local shadow = Shadow(model)
shadow:setMaxRadius(model:getMaxRadius()*3)
addShadow(shadow)
return model
end
function ALL.cloneModel(model,zip,modelTXT,posX,posY,posZ)
local model2 = MD2Model(model)
if modelTXT then
local material = Material()
material:setDiffuseTexture(zip:getTexture(modelTXT))
17.3. STEERING BEHAVIORS
235
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,1)
material:setShininess(64)
model2:setMaterial(material)
end
model2:move(posX,posY,posZ)
model2:pitch(-1.5708)
addObject(model2)
local shadow = Shadow(model2)
shadow:setMaxRadius(model2:getMaxRadius()*3)
addShadow(shadow)
return model2
end
function ALL.createBuilding(zip,fileName, x,z)
local building = zip:getMesh(fileName)
building:setPosition(x,0,z)
addObject(building)
local shadow = Shadow(building)
shadow:setMaxRadius(building:getMaxRadius()*3)
addShadow(shadow)
end
----INITIALIZATION---function DEMO.init()
----ZIP---empty()
emptyOverlay()
setClear(0.75,0.75,0.5)
enableFog(275, 0.75,0.75,0.5)
if not fileExists("DemoPack1.dat") then
showConsole()
error("\nERROR: File ’DemoPack1.dat’ not found")
end
local zip = Zip("DemoPack1.dat")
----CAMERA---setAmbient(0.5,0.5,0.5)
setPerspective(60,1,250)
local camera = getCamera()
camera:reset()
camera:move(50,35,50)
camera:pointTo(80,2,30)
----SUN---local sun = Sun(zip:getTexture("light.jpg"),0.15, -0.5,0.707,-0.5, nil, 0, 0, 200)
sun:setColor(1,1,1)
setSun(sun)
----TERRAIN---local mapImage = zip:getImage("tiles.png")
local material = Material()
236
CHAPTER 17. ARTIFICIAL INTELLIGENCE
material:setDiffuseTexture(zip:getTexture("circleTextures64.jpg"))
tiled = TiledTerrain(material,8,mapImage,50,8,16)
tiled:setShadowOffset(0.05)
tiled:setShadowIntensity(0.5)
tiled:setShadowed()
setTerrain(tiled)
----MODELS---ALL.createBuilding(zip,"tower.obj", 80,30)
local name = "pknight"
originalModel = ALL.createModel(zip,
name..".md2",name..".jpg", 0,2,0, 2, 1)
originalModel:hide()
models = {}
for ct = 1, 16 do
models[ct] = ALL.cloneModel(
originalModel, zip,nil, math.random(0,60),2,math.random(0,60))
end
----VEHICLES---proximity = SteerProximityLocalityQuery(
50,2,50, 70,10,70, 20,1,20
)
vehicles = {}
for ct = 1, table.getn(models) do
local vehicle = SteerVehicle()
vehicles[ct] = vehicle
vehicle:setSpeed(15)
vehicle:setRadius(1)
vehicle:setMaxForce(50)
vehicle:setMaxSpeed(15)
vehicle:setTransform(models[ct])
vehicle:randomizeHeading()
proximity:allocate(vehicle)
end
local points = {
20,2,25, 40,2,25, 50,2,35, 70,2,30, 80,2,30, 60,2,70, 20,2,70
}
pathway = SteerPolylinePathway(points,0.5,true)
obstacle = SteerSphericalObstacle()
obstacle:setCenter(80,2,30)
obstacle:setRadius(6)
group = SteerVehicleGroup()
----HELP---local help = {
"S T E E R I N G
B E H A V I O R S",
"",
"[ MOUSE ] Look Around",
"[ ARROW ] Move/Rotate",
"[ PG_UP ] Move Up",
"[ PG_DW ] Move Down",
"[ ENTER ] Back to Demos Menu",
17.3. STEERING BEHAVIORS
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
hideHelp()
hideConsole()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function DEMO.final()
tiled = nil
originalModel = nil
models = nil
pathway:delete()
pathway = nil
obstacle:delete()
obstacle = nil
proximity:delete()
proximity = nil
group:delete()
group = nil
for ct = 1, table.getn(vehicles) do
vehicles[ct]:delete()
vehicles[ct] = nil
end
vehicles = nil
----EMPTY WORLD---empty()
emptyOverlay()
end
----LOOP---function DEMO.update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
----MOVE MODELS---for ct = 1, table.getn(models) do
local vehicle = vehicles[ct]
local ox,oy,oz = vehicle:avoidObstacle(2,obstacle)
if math.abs(ox) > 0.001 or math.abs(oz) > 0.001 then
ox, oz = ox*100, oz*100
end
local x,y,z = vehicle:getPosition()
local vx,vy,vz = vehicle:getForward()
group:clear()
vehicle:findNeighbors(x+vx*6,y+vy*6,z+vz*6, 8, group)
local nx, ny, nz = vehicle:avoidNeighbors(2,group)
237
238
CHAPTER 17. ARTIFICIAL INTELLIGENCE
if math.abs(nx) > 0.001 or math.abs(nz) > 0.001 then
nx, nz = nx*100, nz*100
end
local sign = (ct%2)*2-1
local px, py, pz = vehicle:followPath(sign, 1, pathway)
local fx,fz = ox+nx+px+vx*5, oz+nz+pz+vz*5
vehicle:applyForce(fx,0,fz, timeStep)
vehicle:updateProximity()
vx,vy,vz = vehicle:getVelocity()
vehicle:regenerateTransform(vx,vy,vz, timeStep)
vehicle:getTransform(models[ct])
models[ct]:pitch(-1.5708)
models[ct]:rotStanding(1.5708)
end
----MOVE CAMERA (KEYBOARD)---local speed = 20
if isKeyPressed(38) then --> VK_UP
camera:moveStanding(speed*timeStep)
end
if isKeyPressed(40) then --> VK_DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> VK_LEFT
camera:rotStanding( 1*timeStep)
end
if isKeyPressed(39) then --> VK_RIGHT
camera:rotStanding(-1*timeStep)
end
local climbSpeed = 20
if isKeyPressed(33) then --> VK_PRIOR
camera:move(0, climbSpeed*timeStep, 0)
end
if isKeyPressed(34) then --> VK_NEXT
camera:move(0, -climbSpeed*timeStep, 0)
end
----MOVE CAMERA (MOUSE)---local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
local posX, posY, posZ = camera:getPosition()
if posY < 1 then
camera:setPosition(posX,1,posZ)
end
end
17.3. STEERING BEHAVIORS
----KEYDOWN---function DEMO.keyDown(key)
if key == 13 then ---> ENTER
releaseKey(13)
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(DEMO.init,DEMO.update,DEMO.final,DEMO.keyDown))
239
240
CHAPTER 17. ARTIFICIAL INTELLIGENCE
Chapter 18
Networking
18.1
Basic Communications
hostName = "localhost"
pageName = "/index.php"
--INIT FUNCTION-function init()
print("\n\nRemember to set ’hostName’ in ’ReadHtmlPage.lua’\n")
showConsole(false)
host = Host(hostName)
if host then
socket = SocketStream()
if socket:connect(host) then
print("\nCONNECTED")
while true do
print("\nWAITING")
if socket:waitForEvent(1000) then
if socket:isReadEvent() then
print("\nREAD EVENT")
if socket:receive() then
print("\nRECEIVED")
print(socket:getBuffer())
else
print("\nNOT RECEIVED")
break
end
elseif socket:isConnectEvent() then
print("\nCONNECT EVENT")
socket:getFile(pageName)
elseif socket:isCloseEvent() then
print("\nCLOSE EVENT")
break
end
else
241
242
CHAPTER 18. NETWORKING
break
end
end
socket:disconnect()
socket:delete()
else
print("\nNOT CONNECTED\n")
end
host:delete()
else
print("\nHOST ERROR\n’")
end
print("\nPress ’ENTER’ to go back to demos menu")
end --INIT
--UPDATE FUNCTION-function update()
end
--KEY PRESS DETECTION
function keyDown(key)
if isKeyPressed(string.byte("\r")) then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
--CLEANUP AND END
function final()
end
--SETUP THE SCENE (RUN)-setScene(Scene(init,update,final,keyDown))
18.2
UDP Protocol with RakNet
18.2.1
The Server
----INITIALIZATION---function init()
acquireMouse(false)
packet = NetPacket()
stream = NetStream()
server = NetServer()
server:start(1,60000)
print("\nOpen another instance of APOCALYX,")
18.2. UDP PROTOCOL WITH RAKNET
print("execute the \"NetClient\" script and")
print("press any key. The code of the key")
print("will appear here thanks to RakNet.")
showConsole(false)
end
----LOOP---function update()
server:receive(packet)
if not packet:isNull() then
print("packet ID: ",packet:getID())
if packet:isData() then
packet:getStream(stream)
local v = stream:readInt()
print("key =",v)
end
server:deallocate(packet)
end
end
----FINALIZATION---function final()
stream:delete()
stream = nil
packet:delete()
packet = nil
server:disconnect()
server:delete()
server = nil
end
----KEYDOWN---function keyDown(key)
if key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
18.2.2
The Client
----INITIALIZATION---function init()
stream = NetStream(64)
243
244
CHAPTER 18. NETWORKING
client = NetClient()
client:connect("localhost",60000,60001)
showConsole(false)
acquireMouse(false)
print("\nIf you previously started ’NetServer.lua’")
print("in another window, pressing a key will")
print("be echoed on the console of the server.")
print("Press ’ENTER’ to go back to demos menu")
end
----LOOP---function update()
end
----FINALIZATION---function final()
stream:delete()
stream = nil
client:disconnect()
client:delete()
client = nil
hideConsole(false)
acquireMouse(true)
end
----KEYDOWN---function keyDown(key)
stream:initWrite()
stream:writeInt(key)
client:send(stream)
if key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
18.3
Voice over IP with RakNet
18.3.1
The Server
----INITIALIZATION---function init()
acquireMouse(false)
18.3. VOICE OVER IP WITH RAKNET
player = NetPlayer()
packet = NetPacket()
server = NetServer()
server:start(1,60000)
voice = NetVoice()
voice:init(server)
count = 0
blockSize = voice:getBlockSize()
sample = Sample3D()
print("\nWARNING: The RakVoice interface is still buggy.")
print("\nOpen another instance of APOCALYX,")
print("execute the \"NetVoiceClient\" script and")
print("talk to the microphone. The speakers")
print("will echo your voice.")
print("Press ’ENTER’ to go back to demos menu")
showConsole(false)
end
----LOOP---function update()
server:receive(packet)
if not packet:isNull() then
print("packet ID: ",packet:getID())
if packet:isVoice() then
count = count+1
print("voice packet ",count)
voice:decodePacket(packet)
else
local stream = NetStream()
stream:initWrite()
server:sendToAll(stream)
stream:delete()
end
server:deallocate(packet)
end
if voice:writeToSample3D(sample,player) then
sample:playAt(0,0,0)
end
end
----FINALIZATION---function final()
acquireMouse(true)
packet:delete()
packet = nil
player:delete()
player = nil
voice:deinit()
voice:delete()
voice = nil
245
246
CHAPTER 18. NETWORKING
count = nil
blockSize = nil
sample:delete()
sample = nil
server:disconnect()
server:delete()
server = nil
end
----KEYDOWN---function keyDown(key)
if key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
18.3.2
The Client
----INITIALIZATION---function init()
showConsole(false)
client = NetClient()
client:connect("localhost",60000,60001)
player = NetPlayer()
hasPlayer = false
voice = NetVoice()
voice:init(client)
blockSize = voice:getBlockSize()
captureDevice = CaptureDevice(blockSize*2,8000,8)
print("\nWARNING: The RakVoice interface is still buggy.")
print("\nIf you previously started ’NetVoiceServer.lua’")
print("in another window, your talk to the microphone")
print("will be echoed by the speakers.")
print("Press ’ENTER’ to go back to demos menu")
acquireMouse(false)
end
----LOOP---function update()
if hasPlayer then
if captureDevice:getAcquiredSamples() >= blockSize then
captureDevice:stop()
captureDevice:capture()
18.3. VOICE OVER IP WITH RAKNET
captureDevice:start()
voice:encodeCaptured(captureDevice,player)
end
else
local packet = NetPacket()
client:receive(packet)
if not packet:isNull() then
print("packet ID: ",packet:getID())
if packet:isData() then
print("data packet")
packet:getPlayer(player)
hasPlayer = true
captureDevice:start()
end
client:deallocate(packet)
end
end
end
----FINALIZATION---function final()
acquireMouse(true)
voice:deinit()
voice:delete()
voice = nil
blockSize = nil
captureDevice:stop()
captureDevice:delete()
captureDevice = nil
player:delete()
player = nil
hasPlayer = nil
client:disconnect()
client:delete()
client = nil
end
----KEYDOWN---function keyDown(key)
if key == string.byte("\r") then
releaseKey(string.byte("\r"))
hideConsole()
final()
dofile("main.lua")
return
end
end
----SCENE SETUP---setScene(Scene(init,update,final,keyDown))
247
248
CHAPTER 18. NETWORKING
Part III
Complex Examples
249
Chapter 19
Urban Tactics
-----------------------------------------------------U R B A N
T A C T I C S-----------------A Simple Third Person Shooter----------Questions? Contact tetractys@users.sf.net-------------------------------------------------MODULES---MENU = {}
PLAY = {}
ALL = {}
----INITIALIZATION SUPPORT---function ALL.showSplashImage(zip)
local splashImage = zip:getImage("logo.jpg")
showSplashImage(splashImage)
splashImage:delete()
end
function ALL.playSoundtrack(zip,fileName,MODULE)
MODULE.soundTrack = zip:getMusic(fileName)
local soundTrack = MODULE.soundTrack
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
end
function ALL.stopSoundtrack(MODULE)
local soundTrack = MODULE.soundTrack
if soundTrack then
soundTrack:stop()
soundTrack:delete()
MODULE.soundTrack = nil
end
251
252
CHAPTER 19. URBAN TACTICS
end
function ALL.createSkybox(zip)
local txtNames = {"Top", "Left", "Front", "Right", "Back"}
local skyTxt = {}
for txtIdx = 1, table.getn(txtNames) do
skyTxt[txtIdx] = zip:getTexture("skybox"..txtNames[txtIdx]..".jpg")
end
local sky = MirroredSky(skyTxt)
setBackground(sky)
end
function ALL.createSun(zip)
local sun = {
size = 0.32, distance = 3200, texture = zip:getTexture("light.jpg"),
dir = {x = 0.0, y = 0.2588, z = 0.9659},
color = {r = 0.9, g = 0.5, b = 0.2},
flares = {
count = 5, size = 0.128, texture = zip:getTexture("lensflares.png")
}
}
local theSun = Sun(
sun.texture, sun.size, sun.dir.x,sun.dir.y,sun.dir.z,
sun.flares.texture,sun.flares.count,sun.flares.size,
sun.distance
)
theSun:setColor(sun.color.r,sun.color.g,sun.color.b)
setSun(theSun)
end
function ALL.createLevel(zip)
local bsp = zip:getLevel("city.bsx",3)
bsp:setShowUntexturedMeshes()
bsp:setShowUntexturedPatches()
bsp:setDefaultTexture(
zip:getTexture("textures/maxpayne/Brick52a.jpg",1)
)
bsp:setShadowsStatic()
setScenery(bsp)
return bsp
end
---------------------MENU SCENE------------------------INITIALIZATION SUPPORT---function MENU.setupPointer(zip)
local pointerImage = zip:getImage("arrow.png")
253
local pointerSize = pointerImage:getDimension()
pointerImage:addAlpha(pointerImage)
local pointerSprite = OverlaySprite(
pointerSize,pointerSize,Texture(pointerImage),true
)
pointerImage:delete()
pointerSprite:setLayer(-1)
setPointer(pointerSprite)
local w, h = getDimension()
setPointerLocation(w/2,h/2)
showPointer()
end
function MENU.createLogo(zip)
local logoImage = zip:getImage("logo.jpg")
local alphaImage = zip:getImage("logo.png")
alphaImage:convertTo111A()
logoImage:addAlpha(alphaImage)
alphaImage:delete()
local logoSize = logoImage:getDimension()
MENU.GUI.logoSprite = OverlaySprite(
logoSize,logoSize,Texture(logoImage),true
)
local logoSprite = MENU.GUI.logoSprite
logoImage:delete()
local w, h = getDimension()
logoSprite:setLocation(w/2,h)
addToOverlay(logoSprite)
end
function MENU.createCredits()
local colors = {
{r = 1,
g = 1, b = 0},
{r = 0.75, g = 1, b = 1},
{r = 1,
g = 1, b = 1}
}
local creditStrings = {
{
2, "",
1, "U R B A N
T A C T I C S",
2, "",
3, "Copyright \184 2004",
2, "Leonardo Boselli",
1, "",
3, "A Simple Third Person Shooter",
2, ""
},
{
1, "- Programming & Design -",
2, "Leonardo \"leo\" Boselli",
254
CHAPTER 19. URBAN TACTICS
3, "tetractys@users.sf.net",
1, "- Warriors Models -",
2, "ALPHAwolf",
3, "ALPHAwolf@Planatquake.com",
2, "HitmanDaz",
3, "daz@darren-pattenden.cix.co.uk",
1, "- Gun Model -",
2, "Janus & Chemical Burn",
3, "janus@planetquake.com",
1, "- Textures -",
2, "Remedy Entertainment Ltd.",
3, "from MAX PAYNE’s official",
3, "textures pack (non-comm.)"
},
{
3, "Thanks to",
3, "",
2, "Matteo \"Fuzz\" Perenzoni",
3, "",
3, "for fruitful discussions on",
3, "OpenGL and 3D programming.",
3, "",
3, "The sources of his demo for",
3, "the NeHe’s Apocalypse Contest",
3, "were the first building blocks",
3, "of the APOCALYX 3D Engine."
},
{
3, "Thanks to",
3, "",
1, "TeCGraf, PUC-Rio",
3, "for the LUA script language",
2, "www.lua.org",
3, "",
1, "Borland",
3, "for their free C++ compiler",
2, "www.borland.com",
3, "",
1, "ID Software",
3, "for developing great FPS",
3, "whose file formats were",
3, "fundamental for this game"
},
{
3, "Thanks to the following sites",
3, "for their useful tutorials",
3, "about game programming",
3, "",
1, "NeHe Productions",
2, "nehe.gamedev.net",
255
1, "Game Tutorials",
2, "www.gametutorials.com",
1, "SULACO",
2, "www.sulaco.co.za",
3, "",
3, "and",
3, "",
1, "Game Programming Italia",
2, "www.gameprog.it"
},
{
3, "Thanks to these web sites",
3, "for publishing news about game",
3, "development and related stuff",
3, "",
1, "GameDev",
2, "www.gamedev.net",
1, "FlipCode",
2, "www.flipcode.org",
1, "CFXweb",
2, "www.cfxweb.net",
1, "OpenGL.org",
2, "www.opengl.org"
},
{
3, "",
3, "And, finally, thanks to",
3, "ALL the people of the",
3, "italian newsgroup",
3, "",
1, "it.comp.giochi.sviluppo",
3, ""
}
}
local font = getMainOverlayFont()
local fontH = font:getHeight()
local w, h = getDimension()
local x = w/2+160
MENU.GUI.optionsTexts = OverlayTexts(font)
local optionsTexts = MENU.GUI.optionsTexts
local playText = OverlayText("[1] PLAY GAME")
playText:setScale(2)
playText:setColor(1,1,0)
optionsTexts:add(playText)
optionsTexts:setLocation(w/2,h)
addToOverlay(optionsTexts)
MENU.GUI.credits = {}
local credits = MENU.GUI.credits
credits.status = 0
credits.index = 1
256
CHAPTER 19. URBAN TACTICS
credits.texts = {}
local creditTexts = credits.texts
for creditIdx = 1, table.getn(creditStrings) do
creditTexts[creditIdx] = OverlayTexts(font)
local currentCreditText = creditTexts[creditIdx]
local textLines = creditStrings[creditIdx]
local textLinesCount = table.getn(textLines)
local y = (h+fontH*(textLinesCount-1))/2-240
for textLineIdx = 1, table.getn(textLines), 2 do
local text = OverlayText(textLines[textLineIdx+1])
local colorIdx = textLines[textLineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(x,y)
y = y-fontH
currentCreditText:add(text)
end
currentCreditText:setLocation(0,-h/2)
addToOverlay(currentCreditText)
currentCreditText:hide()
end
end
function MENU.setupCamera(bsp)
setAmbient(0.3,0.3,0.3)
local camera = {angleOfView = 60, nearClip = 3, farClip = 12000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
theCamera:reset()
local GUI = MENU.GUI
GUI.startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
theCamera:setPosition(bsp:getStartingPosition(GUI.startIndex))
end
function MENU.setupHelp()
hideConsole()
showHelpReduced()
local help = {
"U R B A N
T A C T I C S",
"A Simple Third Person Shooter",
"Questions? Contact leo <boselli@uno.it>",
" ",
"[SPACE] Change Point of View",
"[ 1 ] Start",
" ",
"[ F 1 ] Show/Hide Help",
}
setHelp(help)
end
257
----INITIALIZATION---function MENU.init()
setTitle(" U R B A N
T A C T I C S")
MENU.GUI = {}
empty()
emptyOverlay()
if not fileExists("UrbanTactics.dat") then
showConsole()
error("\nERROR: File ’UrbanTactics.dat’ not found.")
end
local zip = Zip("UrbanTactics.dat")
ALL.playSoundtrack(zip,"intro.mid",MENU.GUI)
ALL.showSplashImage(zip)
MENU.createLogo(zip)
MENU.createCredits()
ALL.createSkybox(zip)
ALL.createSun(zip)
local GUI = MENU.GUI
GUI.bsp = ALL.createLevel(zip)
MENU.setupCamera(GUI.bsp)
MENU.setupPointer(zip)
MENU.setupHelp()
zip:delete()
end
----UPDATE SUPPORT---function MENU.rotateCamera(timeStep)
local camera = getCamera()
local rotSpeed = -math.pi/12
camera:rotStanding(rotSpeed*timeStep)
end
function MENU.animateLogo(timeStep)
local GUI = MENU.GUI
local logoSprite = GUI.logoSprite
local credits = GUI.credits
local creditTexts = credits.texts
local creditTextTime = credits.time
local creditTextIndex = credits.index
local creditTextStatus = credits.status
local logoSpeedX, logoSpeedY = 200, 400
local logoSize = logoSprite:getDimension()
local w, h = getDimension()
local markX = (w-320)/2
local markY = (h+logoSize-480)/2
local logoX, logoY = logoSprite:getLocation()
if timeStep > 0.1 then
258
CHAPTER 19. URBAN TACTICS
timeStep = 0.1
end
if logoY > markY then
logoY = logoY-timeStep*logoSpeedY
if logoY < markY then
logoY = markY
end
MENU.GUI.optionsTexts:setLocation(w/2,logoY+(h-logoSize)/2)
logoSprite:setLocation(logoX,logoY)
elseif logoY == markY then
logoSprite:setLocation(logoX,markY-1)
else
if logoX > markX then
logoX = logoX-timeStep*logoSpeedX
if logoX < markX then
logoX = markX
end
logoSprite:setLocation(logoX,logoY)
elseif logoX == markX then
hideHelp()
logoSprite:setLocation(logoX-1,logoY)
creditTexts[creditTextIndex]:show()
creditTexts[creditTextIndex]:setLocation(0,-h/2)
else
local creditText = creditTexts[creditTextIndex]
if creditTextStatus == 0 then --> FADE_IN
local creditX, creditY = creditText:getLocation()
creditY = creditY+timeStep*logoSpeedX
if creditY >= 0 then
creditText:setLocation(creditX,0)
credits.time = getElapsedTime()
credits.status = 1 --> WAIT
else
creditText:setLocation(creditX,creditY)
end
elseif creditTextStatus == 1 then --> WAIT
local diff = getElapsedTime()-creditTextTime
if diff > creditText:getCount()*0.5 then
credits.status = 2 --> FADE_OUT
end
elseif creditTextStatus == 2 then --> FADE_OUT
local creditX, creditY = creditText:getLocation()
creditY = creditY-timeStep*logoSpeedY
if creditY <= -h/2 then
creditText:setLocation(creditX,-h/2)
creditText:hide()
credits.index = creditTextIndex+1
if credits.index > table.getn(creditTexts) then
credits.index = 1
end
259
creditTexts[credits.index]:show()
credits.status = 0 --> FADE_IN
else
creditText:setLocation(creditX,creditY)
end
end
end
end
end
----UPDATE---function MENU.update()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
MENU.rotateCamera(timeStep)
MENU.animateLogo(timeStep)
local dx, dy = getMouseMove()
movePointer(dx,dy,getDimension())
local GUI = MENU.GUI
local text = GUI.optionsTexts:getTextAt(getPointerLocation())
local oldPointerText = GUI.oldPointerText
if text then
if text ~= oldPointerText then
if oldPointerText then
oldPointerText:setColor(1,1,0)
end
text:setColor(1,0.25,0.25)
GUI.oldPointerText = text
end
if isMouseLeftPressed() then
if MENU.GUI.selected == nil then
MENU.GUI.selected = true
MENU.keyDown(string.byte("1"))
end
else
MENU.GUI.selected = nil
end
elseif oldPointerText then
oldPointerText:setColor(1,1,0)
GUI.oldPointerText = nil
end
end
----FINALIZATION---function MENU.final()
ALL.stopSoundtrack(MENU.GUI)
MENU.GUI = nil
hidePointer()
260
CHAPTER 19. URBAN TACTICS
emptyOverlay()
empty()
end
----KEYBOARD---function MENU.keyDown(key)
if key == 32 then ---> KEY_SPACE
releaseKey(32)
local camera = getCamera()
local GUI = MENU.GUI
local bsp = GUI.bsp
GUI.startIndex = GUI.startIndex+1
if GUI.startIndex >= bsp:getStartingPositionsCount() then
GUI.startIndex = 0
end
camera:setPosition(bsp:getStartingPosition(GUI.startIndex))
elseif key == string.byte("1") then
releaseKey(string.byte("1"))
setScene(Scene(PLAY.init,PLAY.update,PLAY.final,PLAY.keyDown))
end
end
---------------------PLAY SCENE------------------------INITIALIZATION SUPPORT---function PLAY.setupCamera()
setAmbient(0.3,0.3,0.3)
local camera = {angleOfView = 60, nearClip = 3, farClip = 12000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
theCamera:set(PLAY.AVATAR.object)
end
function PLAY.setupScore(theScore,len,offset,texture,u0,v0,u1,v1)
local w, h = getDimension()
local w2, h2 = w/2, h/2
local sw, sh = 64, 32
local sw2, sh2 = sw/2, sh/2
local nw, nh = 16, 16
local nw2, nh2 = nw/2, nh/2
local sprite = OverlaySprite(sw,sh,texture,true)
sprite:setTextureCoord(u0,v0,u1,v1)
sprite:setLocation(w2+offset,h-sh2)
sprite:setColor(0.75,0.75,0)
addToOverlay(sprite)
local offsetX, offsetY = w2+offset+nw2*(len+1), h-sh2-nh2-nh
261
for ct = 1, len do
theScore[ct] = OverlaySprite(nw,nh,texture,true)
local number = theScore[ct]
number:setTextureCoord(0,0.75,0.25,1)
number:setLocation(offsetX-nw*ct,offsetY)
number:setColor(1,1,0)
addToOverlay(number)
end
end
function PLAY.setScoreValue(theScore,value)
local theString = string.format("%d",value)
local len = string.len(theString)
for ct = 1, len do
local byte = string.byte(theString,ct)-string.byte("0")
local u, v = math.mod(byte,4)*0.25, math.floor(byte/4)*0.25
theScore[len-ct+1]:setTextureCoord(u,0.75-v,u+0.25,1-v)
end
end
function PLAY.setupScores(zip)
local scoreImage = zip:getImage("numbers.png",0)
local texture = Texture(scoreImage)
scoreImage:delete()
local AVATAR = PLAY.AVATAR
local setupScore = PLAY.setupScore
AVATAR.highestScore = {}
setupScore(AVATAR.highestScore,5,-160,texture,0,0,0.5,0.25)
AVATAR.scoreScore = {}
setupScore(AVATAR.scoreScore,5,0,texture,0.5,0,1,0.25)
AVATAR.damageScore = {}
setupScore(AVATAR.damageScore,3,160,texture,0.5,0.25,1,0.5)
if fileExists("hiscore.txt") then
local file = ReadableTextFile("hiscore.txt")
if file then
local read = file:read()
AVATAR.highest = tonumber(read)
file:close()
end
end
PLAY.setScoreValue(AVATAR.highestScore,AVATAR.highest)
PLAY.setScoreValue(AVATAR.scoreScore,AVATAR.score)
PLAY.setScoreValue(AVATAR.damageScore,AVATAR.damage)
end
function PLAY.setupHelp()
hideHelp()
hideConsole()
local help = {
"U R B A N
T A C T I C S",
262
CHAPTER 19. URBAN TACTICS
"A Simple Third Person Shooter",
"Questions? Contact tetractys@users.sf.net",
" ",
"[
MOUSE ] Look around",
"[ L/R CLICK] Shoot Bullet/Grenade",
"[ UP/DOWN ] Move Forward/Back",
"[LEFT/RIGHT] Strafe Left/Right",
"[PRIOR/NEXT] Move Up/Down (when flying)",
"[
SPACE ] Fly/Follow Avatar",
"[
+/] Fast/Slow Motion",
"[
PAUSE ] Pause",
"[
ENTER ] Back to Menu",
"[
1
] Start Game",
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
end
function PLAY.createEmitter(zip)
local fireImage = zip:getImage("fire.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
PLAY.AVATAR.shotEmitter = Emitter(5,0.075,3)
local shotEmitter = PLAY.AVATAR.shotEmitter
shotEmitter:setTexture(fireTexture,1)
shotEmitter:setVelocity(200,0,0, 0)
shotEmitter:setColor(1,1,1,1, 1,0.5,0,0)
shotEmitter:setSize(5,5)
shotEmitter:setGravity(0,0,0, 0,0,0)
shotEmitter:setOneShot()
shotEmitter:reset()
addObject(shotEmitter)
shotEmitter:hide()
fireTexture:delete()
end
function PLAY.createAvatar(zip,bsp,shadowTexture)
PLAY.createEmitter(zip)
local AVATAR = PLAY.AVATAR
AVATAR.class = 0 ---> AVATAR
AVATAR.alive = true
AVATAR.score = 0
AVATAR.highest = 0
AVATAR.damage = 0
AVATAR.flyModeActive = 0
AVATAR.isRunning = 0
AVATAR.isStrafing = 0
AVATAR.linkReference = Reference()
263
local legs = 4 ---> LEGS_IDLE
AVATAR.legs = legs
AVATAR.weapon = zip:getModel("gun.mdx","gun.jpg")
local weapon = AVATAR.weapon
weapon:rescale(0.5)
AVATAR.object = zip:getBot("warrior.mdl",1)
local avatar = AVATAR.object
avatar:rescale(0.5)
avatar:pitch(-1.5708)
local startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
avatar:move(bsp:getStartingPosition(startIndex))
avatar:getUpper():link("tag_weapon",weapon)
avatar:setAnimationTime(0)
avatar:setUpperAnimation(2) ---> TORSO_STAND
avatar:setLowerAnimation(legs)
addObject(avatar)
addShadow(Shadow(avatar,14,14,shadowTexture))
PLAY.SIM.OBJECTS[PLAY.SIM.MAX_ENEMIES*3+1] = AVATAR
local boomSample = zip:getSample3D("boom.wav");
AVATAR.boomSample = boomSample
boomSample:setVolume(255)
boomSample:setMinDistance(500)
local shotSample = zip:getSample3D("shot.wav");
AVATAR.shotSample = shotSample
shotSample:setVolume(255)
shotSample:setMinDistance(500)
AVATAR.shotSource = Source(shotSample,avatar,false);
local shotSource = AVATAR.shotSource
addSource(shotSource)
local runSound = zip:getSample3D("run.wav");
runSound:setLooping(1)
runSound:setVolume(255)
runSound:setMinDistance(32)
AVATAR.runSource = Source(runSound,avatar,false);
local runSource = AVATAR.runSource
addSource(runSource)
end
function PLAY.createEnemyModel(zip,shadowTexture,posX,posY,posZ)
local weapon = zip:getBasicModel("marine_weapon.md2","marine_weapon.jpg")
local material = weapon:getMaterial()
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,0)
material:setShininess(64)
weapon:rescale(0.5)
weapon:pitch(-1.5708)
weapon:move(posX,posY,posZ)
weapon:setAnimation(1) ---> RUNNING
addObject(weapon)
264
CHAPTER 19. URBAN TACTICS
local marine = zip:getBasicModel("marine_warrior.md2","marine_us.jpg")
material = marine:getMaterial()
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,0)
material:setShininess(64)
marine:rescale(0.5)
marine:pitch(-1.5708)
marine:move(posX,posY,posZ)
marine:setAnimationTime(0)
marine:setAnimation(1) ---> RUNNING
addObject(marine)
addShadow(Shadow(marine,12,12,shadowTexture))
return marine,weapon
end
function PLAY.cloneEnemy(marine,weapon,zip,textureName,shadowTexture,posX,posY,posZ)
local weapon2 = BasicModel(weapon)
weapon2:pitch(-1.5708)
weapon2:move(posX,posY,posZ)
weapon2:setAnimation(1) ---> RUNNING
addObject(weapon2)
local marine2 = BasicModel(marine)
if textureName then
local material = Material()
material:setDiffuseTexture(zip:getTexture(textureName))
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,0)
material:setShininess(64)
marine2:setMaterial(material)
end
marine2:pitch(-1.5708)
marine2:move(posX,posY,posZ)
marine2:setAnimation(1) ---> RUNNING
addObject(marine2)
addShadow(Shadow(marine2,12,12,shadowTexture))
return marine2, weapon2
end
function PLAY.compareObjects(firstObj,secondObj)
local firstX = firstObj.object:getPosition()
local secondX = secondObj.object:getPosition()
return firstX < secondX
end
function PLAY.setEnemyStatus(theEnemy,status,countdown,altCountdown)
if theEnemy.status ~= status then
theEnemy.status = status
theEnemy.object:setAnimation(status)
265
if status < 15 then
theEnemy.weapon:setAnimation(status)
else
theEnemy.weapon:hide()
end
theEnemy.countdown = countdown
elseif altCountdown then
theEnemy.countdown = altCountdown
else
theEnemy.countdown = countdown
end
end
function PLAY.constructEnemy(theEnemy)
if theEnemy.alive == nil then
theEnemy.alive = true
end
if theEnemy.weight == nil then
theEnemy.weight = 1
end
if theEnemy.health == nil then
theEnemy.health = 2
end
if theEnemy.rotDir == nil then
theEnemy.rotDir = 0
end
if theEnemy.status == nil then
theEnemy.status = 1 ---> RUNNING
end
if theEnemy.countdown == nil then
theEnemy.countdown = 0
end
if theEnemy.isAvatarInSight == nil then
theEnemy.isAvatarInSight = false
end
if theEnemy.setStatus == nil then
theEnemy.setStatus = PLAY.setEnemyStatus
end
theEnemy.class = 1 ---> ENEMY
return theEnemy
end
function PLAY.createEnemies(zip,bsp,shadowTexture)
local ENEMIES = PLAY.SIM.ENEMIES
local OBJECTS = PLAY.SIM.OBJECTS
local startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
local startX,startY,startZ = bsp:getStartingPosition(startIndex)
local avatarCluster = bsp:getCluster(PLAY.AVATAR.object:getPosition())
repeat
startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
266
CHAPTER 19. URBAN TACTICS
startX,startY,startZ = bsp:getStartingPosition(startIndex)
until not bsp:checkVisibility(avatarCluster,bsp:getCluster(startX,startY,startZ))
local marine, weapon = PLAY.createEnemyModel(zip,shadowTexture,startX,startY-15,sta
ENEMIES[1] = PLAY.constructEnemy{object = marine, weapon = weapon, weight = 1}
OBJECTS[1] = ENEMIES[1]
local textureList =
{"marine_centurion.jpg", "marine_brownie.jpg", "marine_reese.jpg"}
for ct = 2, 4 do
repeat
startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
startX,startY,startZ = bsp:getStartingPosition(startIndex)
until not bsp:checkVisibility(avatarCluster,bsp:getCluster(startX,startY,startZ))
local dontRepeat = false
repeat
for enemyCt = 1, ct-1 do
local posX,posY,posZ = ENEMIES[enemyCt].object:getPosition()
if
math.abs(startX-posX) <= 16 and
math.abs(startZ-posZ) <= 16 and
math.abs(startY-posY) <= 16
then
startX = startX + 17
startZ = startZ + 17
else
dontRepeat = true
end
end
until dontRepeat
local marine2, weapon2 = PLAY.cloneEnemy(
marine,weapon,zip,textureList[ct],shadowTexture,startX,startY-15,startZ
)
ENEMIES[ct] = PLAY.constructEnemy{
object = marine2, weapon = weapon2, weight = 2^(ct-1)
}
OBJECTS[ct] = ENEMIES[ct]
table.sort(ENEMIES,PLAY.compareObjects)
end
for ct = 5, PLAY.SIM.MAX_ENEMIES do
repeat
startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
startX,startY,startZ = bsp:getStartingPosition(startIndex)
until not bsp:checkVisibility(avatarCluster,bsp:getCluster(startX,startY,startZ))
local dontRepeat = false
repeat
for enemyCt = 1, ct-1 do
local posX,posY,posZ = ENEMIES[enemyCt].object:getPosition()
if
math.abs(startX-posX) <= 16 and
math.abs(startZ-posZ) <= 16 and
math.abs(startY-posY) <= 16
267
then
startX = startX + 17
startZ = startZ + 17
else
dontRepeat = true
end
end
until dontRepeat
marine = ENEMIES[math.mod(ct,4)+1].object
local marine2, weapon2 =
PLAY.cloneEnemy(marine,weapon,zip,nil,shadowTexture,startX,startY-15,startZ)
ENEMIES[ct] = PLAY.constructEnemy{
object = marine2, weapon = weapon2, weight = 2^(math.mod(ct-5,4))
}
OBJECTS[ct] = ENEMIES[ct]
table.sort(ENEMIES,PLAY.compareObjects)
end
end
function PLAY.createBullets(zip)
local BULLETS = PLAY.SIM.BULLETS
local OBJECTS = PLAY.SIM.OBJECTS
local material = Material()
local image = zip:getImage("light.jpg")
image:addAlpha(image)
material:setDiffuseTexture(Texture(image))
image:delete()
material:setEnlighted(false)
material:setEmissive(1,0.75,0)
local MAX_BULLETS = PLAY.SIM.MAX_ENEMIES*2
for ct = 1, MAX_BULLETS do
local sprite = Sprite(20,20,material)
BULLETS[ct] =
{object = sprite, velX = 0, velY = 0, velZ = 0, type = 0, countdown = 0, class = 2}
OBJECTS[ct+PLAY.SIM.MAX_ENEMIES] = BULLETS[ct]
sprite:setTransparent()
addObject(sprite)
sprite:hide()
end
end
function PLAY.createExplosions(zip)
local EXPLOSIONS = PLAY.SIM.EXPLOSIONS
local material = Material()
local image = zip:getImage("boom.jpg")
image:addAlpha(image)
material:setDiffuseTexture(Texture(image))
image:delete()
material:setEnlighted(false)
material:setEmissive(1,1,1)
268
CHAPTER 19. URBAN TACTICS
local MAX_EXPLOSIONS = 4
for ct = 1, MAX_EXPLOSIONS do
local sprite = Sprite(160,160,material)
EXPLOSIONS[ct] =
{object = sprite, countdown = 0, class = 3}
sprite:setTransparent()
addObject(sprite)
sprite:hide()
end
end
----INITIALIZATION---function PLAY.init()
PLAY.AVATAR = {}
PLAY.SIM = {}
local SIM = PLAY.SIM
SIM.MAX_ENEMIES = 24
SIM.ENEMIES = {}
SIM.BULLETS = {}
SIM.EXPLOSIONS = {}
SIM.OBJECTS = {}
SIM.timeMultiplier = 1
SIM.aiElapsedTime = 0
SIM.physicsElapsedTime = 0
empty()
emptyOverlay()
setListenerScale(16)
if not fileExists("UrbanTactics.dat") then
showConsole()
error("\nERROR: File ’UrbanTactics.dat’ not found.")
end
local zipA = Zip("UrbanTactics.dat")
local zipB = Zip("UrbanTactics.dat")
ALL.playSoundtrack(zipB,"soundtrack.mid",PLAY.SIM)
ALL.showSplashImage(zipA)
ALL.createSkybox(zipA)
ALL.createSun(zipA)
PLAY.SIM.bsp = ALL.createLevel(zipA)
local bsp = PLAY.SIM.bsp
local shadowImage = zipB:getImage("shadow.png")
shadowImage:convertTo111A()
local shadowTexture = Texture(shadowImage)
shadowImage:delete()
PLAY.createAvatar(zipB,bsp,shadowTexture)
PLAY.createEnemies(zipB,bsp,shadowTexture)
PLAY.createBullets(zipA)
PLAY.createExplosions(zipB)
PLAY.setupScores(zipB)
PLAY.setupCamera()
269
PLAY.setupHelp()
zipA:delete()
zipB:delete()
end
----UPDATE SUPPORT---function PLAY.rotateIfNeeded(timeStep)
local avatar = PLAY.AVATAR.object
local headAngle = avatar:getHead():getYawAngle()
local upperAngle = avatar:getUpper():getYawAngle()
local yawAngle = headAngle+upperAngle
if yawAngle ~= 0 then
local rotation = timeStep*3.1415
if yawAngle > 0 then
rotation = -rotation
end
avatar:rotStanding(-rotation)
if headAngle ~= 0 then
local newHeadAngle = headAngle+rotation
if headAngle*newHeadAngle < 0 then
avatar:getHead():setYawAngle(0)
avatar:getUpper():addYawAngle(newHeadAngle-headAngle,1.047)
else
avatar:getHead():addYawAngle(rotation,1.5708)
end
else
local diff = avatar:getUpper():addYawAngle(rotation,1.047)
if diff ~= 0 then
avatar:getHead():addYawAngle(diff,1.5708)
end
end
end
end
----UPDATE---function PLAY.update()
local camera = getCamera()
local AVATAR = PLAY.AVATAR
local avatar = AVATAR.object
local SIM = PLAY.SIM
local ENEMIES = SIM.ENEMIES
local BULLETS = SIM.BULLETS
local EXPLOSIONS = SIM.EXPLOSIONS
local OBJECTS = SIM.OBJECTS
local bsp = SIM.bsp
local timeStep = getTimeStep()
local modelTimeStep
if isPaused() then
270
CHAPTER 19. URBAN TACTICS
modelTimeStep = 0
else
modelTimeStep = timeStep*SIM.timeMultiplier
local animationTime = avatar:getAnimationTime()+modelTimeStep
avatar:setAnimationTime(animationTime)
ENEMIES[1].object:setAnimationTime(animationTime)
end
local ENEMIES_AI_TIMESTEP = 0.1
SIM.aiElapsedTime = SIM.aiElapsedTime+modelTimeStep
if SIM.aiElapsedTime > ENEMIES_AI_TIMESTEP then
local timeStep = SIM.aiElapsedTime
local lookForward = 200
local lookForward2 = lookForward*lookForward
local rotSpeed = 1.57*timeStep
local avatarX,avatarY,avatarZ = avatar:getPosition()
local avatarCluster = bsp:getCluster(avatarX,avatarY,avatarZ)
for enemyCt = 1, table.getn(ENEMIES) do
local theEnemy = ENEMIES[enemyCt]
if not theEnemy.alive then
if theEnemy.object:isClipped() then
repeat
startIndex = math.random(0,bsp:getStartingPositionsCount()-1)
startX,startY,startZ = bsp:getStartingPosition(startIndex)
until not bsp:checkVisibility(avatarCluster,bsp:getCluster(startX,startY,st
for ct = 1, table.getn(ENEMIES) do
local posX,posY,posZ = ENEMIES[ct].object:getPosition()
if
math.abs(startX-posX) <= 16 and
math.abs(startZ-posZ) <= 16 and
math.abs(startY-posY) <= 16
then
startX = startX + 17
startZ = startZ + 17
end
end
theEnemy.object:setPosition(startX,startY-15,startZ)
theEnemy.object:setAnimation(1,5) ---> RUNNING
theEnemy.weapon:setPosition(startX,startY-15,startZ)
theEnemy.weapon:setAnimation(1,5) ---> RUNNING
theEnemy.weapon:show()
theEnemy.health = 3
theEnemy.alive = true
end
else
local marine = theEnemy.object
local weapon = theEnemy.weapon
local posX,posY,posZ = marine:getPosition()
local enemyCluster = bsp:getCluster(posX,posY,posZ)
if AVATAR.alive and bsp:checkVisibility(enemyCluster,avatarCluster) then
local dirX, dirY, dirZ = avatarX-posX, avatarY-posY, avatarZ-posZ
271
local viewX, viewY, viewZ = marine:getSideDirection()
local dot = viewX*dirX+viewZ*dirZ
if dot > 0 then
local finalX,finalY,finalZ,collided = bsp:checkCollision(
posX,posY,posZ,dirX,dirY,dirZ,1,1,1
)
if not collided then
if not theEnemy.isAvatarInSight then
theEnemy.isAvatarInSight = true
theEnemy:setStatus(10,1.5) ---> WAVE
else
local arg = dot/math.sqrt(dirX*dirX+dirZ*dirZ)
if arg > 1 then
arg = 1
end
angle = math.acos(arg)
if angle > rotSpeed then
angle = rotSpeed
end
if viewX*dirZ-viewZ*dirX > 0 then
angle = -angle
end
marine:rotStanding(angle)
weapon:rotStanding(angle)
end
elseif theEnemy.isAvatarInSight then
theEnemy.isAvatarInSight = false
theEnemy:setStatus(0,2) ---> STAND
end
elseif theEnemy.isAvatarInSight then
theEnemy.isAvatarInSight = false
theEnemy:setStatus(0,2) ---> STAND
end
elseif theEnemy.isAvatarInSight then
theEnemy.isAvatarInSight = false
theEnemy:setStatus(0,2) ---> STAND
end
local status = theEnemy.status
theEnemy.countdown = theEnemy.countdown-timeStep
if status == 0 then ---> STAND
if theEnemy.countdown < 0 then
status = math.random(0,2)
if status == 2 then
theEnemy:setStatus(7,1) ---> FLIP
theEnemy.rotDir = math.random(-1,1)
else
theEnemy:setStatus(status,10,5)
end
end
elseif status == 1 then ---> RUN
272
CHAPTER 19. URBAN TACTICS
local lookX,lookY,lookZ = marine:getSideDirection()
lookX = lookX*lookForward
lookY = lookY*lookForward
lookZ = lookZ*lookForward
local finalX,finalY,finalZ,collided = bsp:checkCollision(
posX,posY,posZ,lookX,lookY,lookZ,1,1,1
)
if collided then
local diffX,diffZ = finalX-posX,finalZ-posZ
local diff2 = diffX*diffX+diffZ*diffZ
if diff2 < lookForward2*0.5 then
if theEnemy.rotDir == 0 then
lookX,lookY,lookZ = marine:getUpDirection()
lookX = lookX*lookForward
lookXneg = -lookX
lookY = lookY*lookForward
lookYneg = -lookY
lookZ = lookZ*lookForward
lookZneg = -lookZ
finalX,finalY,finalZ = bsp:checkCollision(
posX,posY,posZ,lookX,lookY,lookZ,1,1,1
)
diffX,diffZ = finalX-posX,finalZ-posZ
diff2 = diffX*diffX+diffZ*diffZ
finalX,finalY,finalZ = bsp:checkCollision(
posX,posY,posZ,lookXneg,lookYneg,lookZneg,1,1,1
)
diffX,diffZ = finalX-posX,finalZ-posZ
local diff2neg = diffX*diffX+diffZ*diffZ
if diff2 < diff2neg then
theEnemy.rotDir = -1
elseif diff2 > diff2neg then
theEnemy.rotDir = 1
else
theEnemy.rotDir = math.random(-1,1)
end
end
local angle = rotSpeed*theEnemy.rotDir
marine:rotStanding(angle)
weapon:rotStanding(angle)
end
else
theEnemy.rotDir = 0
end
if theEnemy.countdown < 0 then
theEnemy:setStatus(math.random(0,1),10,5)
end
elseif status == 7 then ---> FLIP
if theEnemy.countdown < 0 then
theEnemy:setStatus(math.random(0,1),2)
273
theEnemy.rotDir = 0
end
elseif status == 10 then ---> WAVE
if theEnemy.countdown < 0 then
theEnemy:setStatus(11,1.5) ---> POINT
end
elseif status == 11 then ---> POINT
if theEnemy.countdown < 0 then
theEnemy:setStatus(14,1) ---> CROUCH_ATTACK
for ct = 1, table.getn(BULLETS) do
local bullet = BULLETS[ct]
local sprite = bullet.object
if not sprite:isVisible() then
local posX,posY,posZ = marine:getPosition()
local dirX,dirY,dirZ = marine:getSideDirection()
sprite:setPosition(posX+dirX*16,posY+dirY*16,posZ+dirZ*16)
bullet.velX,bullet.velY,bullet.velZ = dirX*480,dirY*480,dirZ*480
bullet.type = 0 ---> BULLET
sprite:show()
AVATAR.shotSample:playAt(posX,posY,posZ)
break
end
end
end
end
end
end
SIM.aiElapsedTime = 0
end
local ENEMIES_PHYSICS_TIMESTEP = 0.01
SIM.physicsElapsedTime = SIM.physicsElapsedTime+modelTimeStep
if SIM.physicsElapsedTime > ENEMIES_PHYSICS_TIMESTEP then
table.sort(ENEMIES,PLAY.compareObjects)
local timeStep = SIM.physicsElapsedTime
local runSpeed = 125*timeStep
local rotSpeed = 1.57*timeStep
for enemyCt = 1, table.getn(ENEMIES) do
local theEnemy = ENEMIES[enemyCt]
if theEnemy.alive then
local marine = theEnemy.object
local weapon = theEnemy.weapon
local status = theEnemy.status
if status == 1 then ---> RUN
local posX,posY,posZ = marine:getPosition()
local velX,velY,velZ = marine:getSideDirection()
velX = velX*runSpeed
velY = velY*runSpeed-5.8
velZ = velZ*runSpeed
local newPosX,newPosY,newPosZ = bsp:slideCollision(
posX,posY,posZ,velX,velY,velZ,8,12,8
274
CHAPTER 19. URBAN TACTICS
)
local foundCollision = false
for neighCt = enemyCt-1, 1, -1 do
local otherX,otherY,otherZ = ENEMIES[neighCt].object:getPosition()
if math.abs(otherX-newPosX) > 16 then
break
end
if math.abs(otherZ-newPosZ) <= 16 and math.abs(otherY-newPosY) <= 16 then
foundCollision = true
newPosX,newPosY,newPosZ = posX,posY,posZ
theEnemy:setStatus(7,2) ---> FLIP
theEnemy.rotDir = 1
end
end
if not foundCollision then
for neighCt = enemyCt+1, table.getn(ENEMIES) do
local otherX,otherY,otherZ = ENEMIES[neighCt].object:getPosition()
if math.abs(otherX-newPosX) > 16 then
break
end
if math.abs(otherZ-newPosZ) <= 16 and math.abs(otherY-newPosY) <= 16 th
foundCollision = true
newPosX,newPosY,newPosZ = posX,posY,posZ
theEnemy:setStatus(7,2) ---> FLIP
theEnemy.rotDir = 1
end
end
end
marine:setPosition(newPosX,newPosY,newPosZ)
weapon:setPosition(newPosX,newPosY,newPosZ)
elseif status == 7 then ---> FLIP
local angle = theEnemy.rotDir*rotSpeed/2
marine:rotStanding(angle)
weapon:rotStanding(angle)
elseif status == 14 then ---> CROUCH_ATTACK
if marine:getStoppedAnimation() == 14 then
theEnemy:setStatus(14,1)
end
if theEnemy.countdown < 0 then
theEnemy.countdown = 1
for ct = 1, table.getn(BULLETS) do
local bullet = BULLETS[ct]
local sprite = bullet.object
if not sprite:isVisible() then
local posX,posY,posZ = marine:getPosition()
local dirX,dirY,dirZ = marine:getSideDirection()
sprite:setPosition(posX+dirX*16,posY+dirY*16,posZ+dirZ*16)
bullet.velX,bullet.velY,bullet.velZ = dirX*480,dirY*480,dirZ*480
bullet.type = 0 ---> BULLET
sprite:show()
275
AVATAR.shotSample:playAt(posX,posY,posZ)
break
end
end
end
elseif status == 15 then ---> CROUCH_PAIN
if theEnemy.countdown < 0 then
theEnemy:setStatus(14,1) ---> CROUCH_ATTACK
end
elseif status >=3 and status <= 5 then ---> PAIN_*
if theEnemy.countdown < 0 then
theEnemy:setStatus(0,1) ---> STAND
end
end
end
end
for ct = 1, table.getn(EXPLOSIONS) do
local sprite = EXPLOSIONS[ct].object
if sprite:isVisible() then
local theExplosion = EXPLOSIONS[ct]
theExplosion.countdown = theExplosion.countdown-timeStep
if theExplosion.countdown < 0 then
sprite:hide()
else
local frame = math.floor(theExplosion.countdown*16)
local u, v = (3-math.mod(frame,4))*0.25, math.floor(frame/4)*0.25
sprite:setTextureCoord(u,v,u+0.25,v+0.25)
end
end
end
for ct = 1, table.getn(BULLETS) do
local bullet = BULLETS[ct]
local sprite = bullet.object
if sprite:isVisible() then
local posX,posY,posZ = sprite:getPosition()
local velX,velY,velZ = bullet.velX,bullet.velY,bullet.velZ
local newPosX,newPosY,newPosZ,collided = bsp:checkCollision(
posX,posY,posZ,velX*timeStep,velY*timeStep,velZ*timeStep,1,1,1
)
if collided then
if bullet.type == 1 then ---> GRENADE
local normalX, normalY, normalZ = bsp:getCollisionNormal()
local velDotNorm2 = -2*(velX*normalX+velY*normalY+velZ*normalZ)
velX, velY, velZ =
0.8*(velX+velDotNorm2*normalX),
0.8*(velY+velDotNorm2*normalY),
0.8*(velZ+velDotNorm2*normalZ)
newPosY = newPosY+0.02
else
sprite:hide()
276
CHAPTER 19. URBAN TACTICS
end
end
sprite:setPosition(newPosX,newPosY,newPosZ)
if bullet.type == 1 then ---> GRENADE
bullet.countdown = bullet.countdown-timeStep
if bullet.countdown < 0 then
sprite:hide()
for ct = 1, table.getn(EXPLOSIONS) do
local theExplosion = EXPLOSIONS[ct]
local sprite = theExplosion.object
if not sprite:isVisible() then
theExplosion.countdown = 1
sprite:setPosition(newPosX,newPosY,newPosZ)
sprite:setTextureCoord(0,0,0.25,0.25)
AVATAR.boomSample:playAt(newPosX,newPosY,newPosZ)
sprite:show()
break
end
end
else
bullet.velX,bullet.velY,bullet.velZ = velX,velY-157*timeStep,velZ
end
end
end
end
table.sort(OBJECTS,PLAY.compareObjects)
for ct = 1, table.getn(OBJECTS) do
local theObject = OBJECTS[ct]
if theObject.class == 2 and theObject.object:isVisible() then
local bullet = theObject.object
local posX,posY,posZ = bullet:getPosition()
for neighCt = ct-1, 1, -1 do
local theOtherObject = OBJECTS[neighCt]
local object = theOtherObject.object
local otherX,otherY,otherZ = object:getPosition()
if math.abs(otherX-posX) > 8 then
break
end
if
theOtherObject.class ~= 2 and
math.abs(otherZ-posZ) <= 8 and
math.abs(otherY-posY) <= 8
then
if theOtherObject.alive then
bullet:hide()
if theOtherObject.class == 0 then
AVATAR.damage = AVATAR.damage+10
PLAY.setScoreValue(AVATAR.damageScore,AVATAR.damage)
if AVATAR.damage >= 100 then
avatar:setUpperAnimation(0) ---> TORSO_DEAD
277
avatar:setLowerAnimation(0) ---> LEGS_DEAD
AVATAR.flyModeActive = 1
AVATAR.alive = false
if AVATAR.score > AVATAR.highest then
local file = WritableTextFile("hiscore.txt")
if file then
file:write(string.format("%d",AVATAR.score))
file:close()
end
end
end
elseif theOtherObject.class == 1 then
if theObject.type == 1 then
for ct = 1, table.getn(EXPLOSIONS) do
local theExplosion = EXPLOSIONS[ct]
local sprite = theExplosion.object
if not sprite:isVisible() then
theExplosion.countdown = 1
sprite:setPosition(posX,posY,posZ)
AVATAR.boomSample:playAt(posX,posY,posZ)
sprite:show()
theOtherObject.health = 0
break
end
end
else
theOtherObject.health = theOtherObject.health-1
end
if theOtherObject.health == 0 then
if theOtherObject.status == 14 then ---> CROUCH_ATTACK
theOtherObject:setStatus(16,1) ---> CROUCH_DEATH
else
theOtherObject:setStatus(math.random(17,19),1) ---> DEATH_*
end
theOtherObject.alive = false
AVATAR.score = AVATAR.score+theOtherObject.weight
PLAY.setScoreValue(AVATAR.scoreScore,AVATAR.score)
else
if theOtherObject.status == 14 then ---> CROUCH_ATTACK
theOtherObject:setStatus(15,1) ---> CROUCH_PAIN
else
theOtherObject:setStatus(math.random(3,5),1) ---> PAIN_*
end
end
end
end
end
end
for neighCt = ct+1, table.getn(OBJECTS) do
local theOtherObject = OBJECTS[neighCt]
278
CHAPTER 19. URBAN TACTICS
local object = theOtherObject.object
local otherX,otherY,otherZ = object:getPosition()
if math.abs(otherX-posX) > 8 then
break
end
if
theOtherObject.class ~= 2 and
math.abs(otherZ-posZ) <= 8 and
math.abs(otherY-posY) <= 8
then
if theOtherObject.alive then
bullet:hide()
if theOtherObject.class == 0 then
AVATAR.damage = AVATAR.damage+10
PLAY.setScoreValue(AVATAR.damageScore,AVATAR.damage)
if AVATAR.damage >= 100 then
avatar:setUpperAnimation(0) ---> TORSO_DEAD
avatar:setLowerAnimation(0) ---> LEGS_DEAD
AVATAR.flyModeActive = 1
AVATAR.alive = false
if AVATAR.score > AVATAR.highest then
local file = WritableTextFile("hiscore.txt")
if file then
file:write(string.format("%d",AVATAR.score))
file:close()
end
end
end
elseif theOtherObject.class == 1 then
if theObject.type == 1 then
for ct = 1, table.getn(EXPLOSIONS) do
local theExplosion = EXPLOSIONS[ct]
local sprite = theExplosion.object
if not sprite:isVisible() then
theExplosion.countdown = 1
sprite:setPosition(posX,posY,posZ)
AVATAR.boomSample:playAt(posX,posY,posZ)
sprite:show()
theOtherObject.health = 0
break
end
end
else
theOtherObject.health = theOtherObject.health-1
end
if theOtherObject.health == 0 then
if theOtherObject.status == 14 then ---> CROUCH_ATTACK
theOtherObject:setStatus(16,1) ---> CROUCH_DEATH
else
theOtherObject:setStatus(math.random(17,19),1) ---> DEATH_*
279
end
theOtherObject.alive = false
AVATAR.score = AVATAR.score+theOtherObject.weight
PLAY.setScoreValue(AVATAR.scoreScore,AVATAR.score)
else
if theOtherObject.status == 14 then ---> CROUCH_ATTACK
theOtherObject:setStatus(15,1) ---> CROUCH_PAIN
else
theOtherObject:setStatus(math.random(3,5),1) ---> PAIN_*
end
end
end
end
end
end
end
end
SIM.physicsElapsedTime = 0
end
if avatar:getUpper():getStoppedAnimation() == 1 then ---> TORSO_ATTACK
avatar:setUpperAnimation(2) ---> TORSO_STAND
end
if
(isMouseLeftPressed() or isMouseRightPressed()) and
(avatar:getUpperAnimation() ~= 1) and ---> TORSO_ATTACK
AVATAR.alive
then
avatar:setUpperAnimation(1) ---> TORSO_ATTACK
AVATAR.shotSource:getSound3D():play()
if avatar:getLinkTransform("tag_weapon",AVATAR.linkReference) then
local shotEmitter = AVATAR.shotEmitter
local linkReference = AVATAR.linkReference
shotEmitter:set(linkReference)
shotEmitter:moveSide(10)
shotEmitter:show()
shotEmitter:reset()
for ct = 1, table.getn(BULLETS) do
local bullet = BULLETS[ct]
local sprite = bullet.object
if not sprite:isVisible() then
local posX,posY,posZ = linkReference:getPosition()
local dirX,dirY,dirZ = linkReference:getSideDirection()
sprite:setPosition(posX+dirX*16,posY+dirY*16,posZ+dirZ*16)
bullet.velX,bullet.velY,bullet.velZ = dirX*480,dirY*480,dirZ*480
if isMouseLeftPressed() then
bullet.type = 0 ---> BULLET
else
bullet.type = 1 ---> GRENADE
bullet.countdown = 3
end
280
CHAPTER 19. URBAN TACTICS
sprite:show()
break
end
end
end
end
if AVATAR.flyModeActive == 1 then
if
isKeyPressed(38) or isKeyPressed(40) or ---> KEY_UP || KEY_DOWN
isKeyPressed(37) or isKeyPressed(39) or ---> KEY_LEFT || KEY_RIGHT
isKeyPressed(33) or isKeyPressed(34)
---> KEY_PRIOR || KEY_NEXT
then
local stepSpeed = 150
local step = 0
if isKeyPressed(38) then ---> KEY_UP
step = stepSpeed*timeStep
elseif isKeyPressed(40) then ---> KEY_DOWN
step = -stepSpeed*timeStep
end
local sideSpeed = 150
local side = 0
if isKeyPressed(37) then ---> KEY_LEFT
side = sideSpeed*timeStep
elseif isKeyPressed(39) then ---> KEY_RIGHT
side = -sideSpeed*timeStep
end
local climbSpeed = 75
local climb = 0
if isKeyPressed(33) then ---> KEY_PRIOR
climb = climbSpeed*timeStep
elseif isKeyPressed(34) then ---> KEY_NEXT
climb = -climbSpeed*timeStep
end
local posX,posY,posZ = camera:getPosition()
local velX,velY,velZ = camera:getViewDirection()
local sidX,sidY,sidZ = camera:getSideDirection()
posX,posY,posZ = bsp:slideCollision(
posX,posY,posZ,
step*velX+side*sidX,climb,step*velZ+side*sidZ,
7,7,7
)
camera:setPosition(posX,posY,posZ)
end
local rotAngle = 0.15*timeStep
local dx, dy = getMouseMove()
if dx ~= 0 then
camera:rotStanding(-dx*rotAngle)
end
if dy ~= 0 then
camera:pitch(-dy*rotAngle)
281
end
else
local rotAngle = 0.15*modelTimeStep
local moveSpeed = 25*modelTimeStep
local velX,velY,velZ
local posX,posY,posZ = avatar:getPosition()
if isKeyPressed(38) or isKeyPressed(40) then ---> KEY_UP || KEY_DOWN
if AVATAR.legs == 4 then ---> LEGS_IDLE
AVATAR.isRunning = 1
AVATAR.runSource:getSound3D():play()
if isKeyPressed(38) then ---> KEY_UP
AVATAR.legs = 2 ---> LEGS_RUN
avatar:setLowerAnimation(2)
else
AVATAR.legs = 3 ---> LEGS_BACK
avatar:setLowerAnimation(3)
end
end
if isKeyPressed(38) then ---> KEY_UP
moveSpeed = 5*moveSpeed
elseif isKeyPressed(40) then ---> KEY_DOWN
moveSpeed = -3.5*moveSpeed
end
velX,velY,velZ = avatar:getSideDirection()
velX = velX*moveSpeed
velY = velY*moveSpeed-5.8
velZ = velZ*moveSpeed
else
if AVATAR.isRunning == 1 then
AVATAR.legs = 4 ---> LEGS_IDLE
avatar:setLowerAnimation(4)
AVATAR.isRunning = 0
AVATAR.runSource:getSound3D():stop()
end
if isKeyPressed(37) or isKeyPressed(39) then ---> KEY_LEFT || KEY_RIGHT
if (AVATAR.isRunning == 0) and (AVATAR.isStrafing == 0) then
AVATAR.isStrafing = 1
AVATAR.runSource:getSound3D():play()
avatar:setLowerAnimation(5) ---> LEGS_TURN
end
PLAY.rotateIfNeeded(modelTimeStep)
moveSpeed = 2*moveSpeed
if isKeyPressed(39) then ---> KEY_RIGHT
moveSpeed = -moveSpeed
end
velX,velY,velZ = avatar:getUpDirection()
velX = velX*moveSpeed
velY = velY*moveSpeed-5.8
velZ = velZ*moveSpeed
else
282
CHAPTER 19. URBAN TACTICS
if AVATAR.isStrafing == 1 then
AVATAR.isStrafing = 0
AVATAR.runSource:getSound3D():stop()
avatar:setLowerAnimation(4) ---> LEGS_IDLE
end
velX,velY,velZ = 0,-5.8,0
end
end
posX,posY,posZ = bsp:slideCollision(
posX,posY,posZ,velX,velY,velZ,8,12,8
)
avatar:setPosition(posX,posY,posZ);
local dx,dy = getMouseMove()
if dx ~= 0 then
local headAngle = avatar:getHead():getYawAngle()
if headAngle ~= 0 then
local newHeadAngle = headAngle-dx*rotAngle
if headAngle*newHeadAngle < 0 then
avatar:getHead():setYawAngle(0)
avatar:getUpper():addYawAngle(newHeadAngle-headAngle,1.047)
else
avatar:getHead():addYawAngle(-dx*rotAngle,1.5708)
end
else
local diff = avatar:getUpper():addYawAngle(-dx*rotAngle,1.047)
if diff ~= 0 then
avatar:getHead():addYawAngle(diff,1.5708)
end
end
end
if AVATAR.isRunning == 1 then
PLAY.rotateIfNeeded(modelTimeStep)
end
if dy ~= 0 then
local headAngle = avatar:getHead():getPitchAngle()
if headAngle ~= 0 then
local newHeadAngle = headAngle-dy*rotAngle
if headAngle*newHeadAngle < 0 then
avatar:getHead():setPitchAngle(0)
avatar:getUpper():addPitchAngle(
newHeadAngle-headAngle,1.047,-.5236
)
else
avatar:getHead():addPitchAngle(
-dy*rotAngle,.5236,-.7854
)
end
else
local diff = avatar:getUpper():addPitchAngle(
-dy*rotAngle,1.047,-.5236
283
)
if diff ~= 0 then
avatar:getHead():addPitchAngle(diff,.5236,-7854)
end
end
end
local target = Reference()
target:set(avatar)
target:rotateT(avatar:getUpper())
target:rotateT(avatar:getHead())
target:exchangeYZX()
local posX,posY,posZ = target:getPosition()
posY = posY+25
local velX,velY,velZ = target:getViewDirection()
velX,velY,velZ = -45*velX,-45*velY,-45*velZ
posX,posY,posZ = bsp:checkCollision(posX,posY,posZ,velX,velY,velZ,4,4,4)
target:setPosition(posX,posY,posZ)
local camX,camY,camZ = camera:getPosition()
local pathX,pathY,pathZ = posX-camX,posY-camY,posZ-camZ
local dist = math.sqrt(pathX*pathX+pathY*pathY+pathZ*pathZ)
local speed;
if dist < 200 then
speed = 250
elseif dist < 400 then
speed = 500
else
speed = 1000
end
local interpolation = speed*timeStep/dist
if interpolation > 1 then
interpolation = 1
end
camera:interpolate(target,interpolation)
camera:setPosition(
camX+interpolation*pathX,
camY+interpolation*pathY,
camZ+interpolation*pathZ
)
end
end
----FINALIZATION---function PLAY.final()
ALL.stopSoundtrack(PLAY.SIM)
local AVATAR = PLAY.AVATAR
if AVATAR.weapon then
AVATAR.weapon:delete()
AVATAR.weapon = nil
end
284
CHAPTER 19. URBAN TACTICS
PLAY.AVATAR = nil
PLAY.SIM = nil
empty()
emptyOverlay()
end
----KEYBOARD---function PLAY.keyDown(key)
if key == 32 then ---> KEY_RETURN
releaseKey(32)
if PLAY.AVATAR.alive then
PLAY.AVATAR.flyModeActive = 1-PLAY.AVATAR.flyModeActive
if PLAY.AVATAR.flyModeActive == 1 then
local camera = getCamera()
local posX,posY,posZ = camera:getPosition()
camera:set(PLAY.AVATAR.object)
camera:exchangeYZX()
camera:setPosition(posX,posY,posZ)
end
end
elseif key == 107 then ---> KEY_ADD
releaseKey(107)
PLAY.SIM.timeMultiplier = PLAY.SIM.timeMultiplier*2
if PLAY.SIM.timeMultiplier > 2 then
PLAY.SIM.timeMultiplier = 2
end
elseif key == 109 then ---> KEY_SUBTRACT
releaseKey(109)
PLAY.SIM.timeMultiplier = PLAY.SIM.timeMultiplier/2
if PLAY.SIM.timeMultiplier < 0.125 then
PLAY.SIM.timeMultiplier = 0.125
end
elseif key == 13 then ---> KEY_RETURN
releaseKey(13)
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
elseif key == string.byte("1") then
releaseKey(string.byte("1"))
setScene(Scene(PLAY.init,PLAY.update,PLAY.final,PLAY.keyDown))
end
end
----------------------SCENE SETUP---------------------setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
Chapter 20
Hoverjet Racing
--[[
H O V E R J E T
R A C I N G
Questions? Contact leo at tetractys@users.sourceforge.net
--]]
----MODULES---GAME = {}
MENU = {}
ALL = {}
-------SIM SUPPORT
---function GAME.applyForces(a)
local vx0,vy0,vz0 = a:getVelocity(0)
local fric = 0.0000001
a:addForce(0,-math.abs(vx0)*vx0*fric,-math.abs(vy0)*vy0*fric,-math.abs(vz0)*vz0*fric)
local x0,y0,z0 = a:getPosition(0)
local x1,y1,z1 = a:getPosition(1)
local ID = a:getID()
local jet = GAME.jets[ID]
if ID ~= GAME.modelIndex then
local fx,fy,fz = x1-x0,y1-y0,z1-z0
local speed2 = vx0*vx0+vy0*vy0+vz0*vz0
local thrust = 0.00039
local tx,ty,tz = fx*thrust,fy*thrust,fz*thrust
a:addForce(1,tx,ty,tz)
local sx,sz = z0-z1,x1-x0
thrust = speed2*0.000000004
sx,sz = thrust*sx,thrust*sz
a:addForce(1,sx,0,sz)
285
286
CHAPTER 20. HOVERJET RACING
a:addForce(0,0,-0.003,0)
a:addForce(1,0,-0.003,0)
return
end
if jet.ENGINE_FORW == 1 then
local fx,fy,fz = x1-x0,y1-y0,z1-z0
local thrust
if jet.BOOST_ON == 1 then
thrust = 0.0008
else
thrust = 0.0004
end
local tx,ty,tz = fx*thrust,fy*thrust,fz*thrust
a:addForce(1,tx,ty,tz)
elseif jet.ENGINE_BACK == 1 then
local fx,fy,fz = x1-x0,y1-y0,z1-z0
local thrust
thrust = -0.0002
local tx,ty,tz = fx*thrust,fy*thrust,fz*thrust
a:addForce(1,tx,ty,tz)
end
if jet.ENGINE_LEFT == 1 then
local sx,sz = z1-z0,x0-x1
local thrust = 0.0002
sx,sz = thrust*sx,thrust*sz
a:addForce(1,sx,0,sz)
elseif jet.ENGINE_RIGHT == 1 then
local sx,sz = z1-z0,x0-x1
local thrust = -0.0002
sx,sz = thrust*sx,thrust*sz
a:addForce(1,sx,0,sz)
end
a:addForce(0,0,-0.003,0) -- was 0.0015
a:addForce(1,0,-0.003,0)
end
----INITIALIZATION SUPPORT---function ALL.showSplashImage(zip)
local splashImage = zip:getImage("logo.jpg")
showSplashImage(splashImage)
splashImage:delete()
end
function ALL.playSoundtrack(zip,fileName,MODULE)
MODULE.soundTrack = zip:getMusic(fileName)
MODULE.soundTrackIsPlaying = true
local soundTrack = MODULE.soundTrack
soundTrack:setLooping(1)
soundTrack:play()
287
soundTrack:setVolume(220)
end
function ALL.stopSoundtrack(MODULE)
local soundTrack = MODULE.soundTrack
MODULE.soundTrackIsPlaying = false
if soundTrack then
soundTrack:stop()
soundTrack:delete()
MODULE.soundTrack = nil
end
end
-------GAME SCENE
-------HUD SUPPORT---function GAME.setupMap(zip,mapName,tiled)
local mapImage = zip:getImage(mapName)
mapImage:convertToRGB()
local MAP_SIZE = 170
GAME.HUD.isMapShown = true
GAME.HUD.mapSprite =
OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,tiled))
local mapSprite = GAME.HUD.mapSprite
mapImage:delete()
mapSprite:setLayer(0)
mapSprite:setColor(1,0.9,0.5,0.5)
local W, H = getDimension()
mapSprite:setLocation(W-MAP_SIZE,H-MAP_SIZE)
addToOverlay(GAME.HUD.mapSprite)
local markImage = zip:getImage("dot.png")
local markSize = markImage:getDimension()
markImage:addAlpha(markImage)
local markTexture = Texture(markImage)
markImage:delete()
local JETS_COUNT = 7
for jetCt = 1, JETS_COUNT do
local markSprite = OverlaySprite(markSize,markSize,markTexture,true)
GAME.HUD.markSprite[jetCt] = markSprite
markSprite:setLayer(-1)
markSprite:setColor(1,1,0)
addToOverlay(markSprite)
end
local markSprite = GAME.HUD.markSprite[GAME.modelIndex]
markSprite:setLayer(-2)
markSprite:setColor(1,1,1)
end
288
CHAPTER 20. HOVERJET RACING
function GAME.setupHud(theScore,len,offset,texture,u0,v0,u1,v1)
local w, h = getDimension()
local w2, h2 = w*0.5, h*0.5
local sw, sh = 64, 32
local sw2, sh2 = sw*0.5, sh*0.5
local nw, nh = 16, 16
local nw2, nh2 = nw*0.5, nh*0.5
local sprite = OverlaySprite(sw,sh,texture,true)
sprite:setTextureCoord(u0,v0,u1,v1)
sprite:setLocation(w2+offset,h-sh2)
sprite:setColor(0.75,0.75,0)
addToOverlay(sprite)
local offsetX, offsetY = w2+offset+nw2*(len+1), h-sh2-nh2-nh
for ct = 1, len do
theScore[ct] = OverlaySprite(nw,nh,texture,true)
local number = theScore[ct]
number:setTextureCoord(0,0.75,0.25,1)
number:setLocation(offsetX-nw*ct,offsetY)
number:setColor(1,1,0)
addToOverlay(number)
end
end
function GAME.setHudValue(theScore,chars,value)
local theString = string.format("%d",value)
local len = math.min(string.len(theString),chars)
for ct = 1, len do
local byte = string.byte(theString,ct)-string.byte("0")
local u, v = math.mod(byte,4)*0.25, math.floor(byte*0.25)*0.25
theScore[len-ct+1]:setTextureCoord(u,0.75-v,u+0.25,1-v)
end
for ct = len+1, chars do
theScore[ct]:setTextureCoord(0,0.75,0.25,1)
end
end
function GAME.setupHuds(zip)
local scoreImage = zip:getImage("numbers.png",0)
local texture = Texture(scoreImage)
scoreImage:delete()
local setupScore = GAME.setupHud
GAME.HUD.maxhHud = {}
setupScore(GAME.HUD.maxhHud,4,-160,texture,0,0,0.5,0.25)
GAME.HUD.heightHud = {}
setupScore(GAME.HUD.heightHud,4,0,texture,0.5,0,1,0.25)
GAME.HUD.speedHud = {}
setupScore(GAME.HUD.speedHud,3,160,texture,0.5,0.25,1,0.5)
GAME.setHudValue(GAME.HUD.maxhHud,4,0)
GAME.setHudValue(GAME.HUD.heightHud,4,0)
289
GAME.setHudValue(GAME.HUD.speedHud,3,0)
end
----ISLES SUPPORT-------Generate Trees Material
function GAME.generateTreesMaterial(zip, fileName)
local treesMaterial = Material()
treesMaterial:setAmbient(1,1,1)
treesMaterial:setDiffuse(1,1,1)
treesMaterial:setDiffuseTexture(zip:getTexture(fileName));
return treesMaterial
end
----Generate Isle (Transparency)
function GAME.generateIsle(
zip, terrainTextureName, detailedTexture, detailRepeat,
heightFieldName, waterLevel, width, depth, height, x, y,
treesCount, treesMaterial, minTreesHeight
)
----LOAD IMAGES AND CREATE TEXTURES---local terrainImage = zip:getImage(terrainTextureName)
local heightImage = zip:getImage(heightFieldName)
terrainImage:addAlpha(heightImage,32,18)
local terrainTexture = Texture(terrainImage)
terrainImage:delete()
----TERRAIN MATERIAL---local terrainMaterial = Material()
terrainMaterial:setDiffuseTexture(terrainTexture)
terrainMaterial:setGlossTexture(detailedTexture)
----CREATE HEIGHTFIELD---local heightField = HeightField(
heightImage,terrainMaterial,width,depth,height,-waterLevel,8
)
heightField:move(x,-waterLevel,y)
heightField:setHintNoRotation()
addObject(heightField)
terrainMaterial:delete()
terrainTexture:delete()
----TREES CREATION---local trees = nil
if treesCount > 0 then
trees = Trees(treesCount,2,treesMaterial,2,0.1)
local treesSize = 18
local counter = 0
while counter < treesCount do
local xx = math.random()*width-width*0.5
local yy = math.random()*depth-depth*0.5
local h = heightField:getHeightAtRelative(xx,yy)
if h > minTreesHeight then
290
CHAPTER 20. HOVERJET RACING
counter = counter + 1
trees:addTree(
xx,h,yy,treesSize,treesSize,math.mod(counter,4),counter<treesCount*2
)
end
end
trees:setTransparent()
trees:move(x,treesSize*0.5-waterLevel,y)
addObject(trees)
treesMaterial:delete()
end
return heightField, trees
end
----Generate Isle 2 (Reflection)
function GAME.generateIsle2(
zip, terrainTextureName, detailedTexture, detailRepeat,
heightFieldName, waterLevel, width, depth, height, x, y
)
----LOAD IMAGES AND CREATE TEXTURES---local terrainImage = zip:getImage(terrainTextureName)
local heightImage = zip:getImage(heightFieldName)
local terrainTexture = Texture(terrainImage)
terrainImage:delete()
----TERRAIN MATERIAL---local terrainMaterial = Material()
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(terrainTexture)
terrainMaterial:setGlossTexture(detailedTexture)
----CREATE HEIGHTFIELD---local heightField = HeightField(
heightImage,terrainMaterial,width,depth,height,waterLevel,8
)
heightField:move(x,-waterLevel,y)
heightField:setHintNoRotation()
addObject(heightField)
terrainMaterial:delete()
terrainTexture:delete()
return heightField
end
----INITIALIZATION---function GAME.init()
GAME.target = Reference()
if GAME.mapIndex == nil then
GAME.mapIndex = 5
end
if GAME.modelIndex == nil then
GAME.modelIndex = 1
end
291
GAME.cloudsList = {}
----ZIP---setListenerScale(1)
empty()
emptyOverlay()
if not fileExists("HoverjetRacing.dat") then
showConsole()
error("\nERROR: File ’HoverjetRacing.dat’ not found")
end
local zip = Zip("HoverjetRacing.dat")
ALL.showSplashImage(zip)
ALL.playSoundtrack(zip,"soundtrack.mid",GAME)
----CAMERA---setAmbient(0.5,0.5,0.5)
local fogColor
if GAME.mapIndex == 1 then ---> FORBIDDEN PLANET
GAME.MAX_DIST = 3000
setPerspective(80,1,GAME.MAX_DIST)
fogColor = {0,0,0}
elseif GAME.mapIndex == 2 then ---> ROCKY MOUNTAINS
GAME.MAX_DIST = 3000
setPerspective(80,1,GAME.MAX_DIST)
fogColor = {0.475,0.431,0.451}
elseif GAME.mapIndex == 3 then ---> PACIFIC OCEAN
GAME.MAX_DIST = 3000
setPerspective(80,1,GAME.MAX_DIST)
fogColor = {0.647,0.698,0.863}
elseif GAME.mapIndex == 4 then ---> ANTARCTICA
GAME.MAX_DIST = 4500
setPerspective(80,1,GAME.MAX_DIST)
fogColor = {0.75,0.75,1}
elseif GAME.mapIndex == 5 then ---> TERRAFORMED MARS
GAME.MAX_DIST = 3000
setPerspective(80,1,GAME.MAX_DIST)
fogColor = {0.475,0.431,0.451}
end
enableFog(GAME.MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
local camera = getCamera()
camera:reset()
----SKYBOX---if GAME.mapIndex == 1 then ---> FORBIDDEN PLANET
local starfield = StarField(50,4000,zip:getTexture("stars.jpg"),20)
setBackground(starfield)
elseif GAME.mapIndex == 2 then ---> ROCKY MOUNTAINS
local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
292
CHAPTER 20. HOVERJET RACING
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
elseif GAME.mapIndex == 3 then ---> PACIFIC OCEAN
local skytype = "clear_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
elseif GAME.mapIndex == 4 then ---> ANTARCTICA
local skytype = "blue_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = MirroredSky(skyTxt)
setBackground(sky)
elseif GAME.mapIndex == 5 then ---> TERRAFORMED MARS
local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
end
----MOON---if GAME.mapIndex == 2 then ---> ROCKY
local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,GAME.MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
end
293
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,GAME.MAX_DIST-500
)
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---if GAME.mapIndex == 1 then ---> FORBIDDEN PLANET
local heightImage = zip:getImage("terrainE.png")
local colorImage = zip:getImage("terrainE.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("terrainmap.jpg",true))
material:setGlossTexture(zip:getTexture("terrainmapB.jpg",true))
GAME.patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,512)
GAME.patches:setShadowFadeDistance(50)
GAME.patches:setShadowOffset(0.25)
GAME.patches:setShadowed()
setTerrain(GAME.patches)
heightImage:delete()
colorImage:delete()
material:delete()
local obstruction = PatchedObstruction(GAME.patches,1.5)
obstruction:setParticleProjected()
GAME.environment = StaticEnvironment(0,0,0,1.5,obstruction)
GAME.environment:setTerrainFriction(false)
elseif GAME.mapIndex == 2 then ---> ROCKY MOUNTAINS
local heightImage = zip:getImage("terrainB.png")
local colorImage = zip:getImage("terrainB.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("terrainmapB.jpg",true))
material:setGlossTexture(zip:getTexture("terrainmapB.jpg",true))
GAME.patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,512)
GAME.patches:setShadowFadeDistance(50)
GAME.patches:setShadowOffset(0.25)
GAME.patches:setShadowed()
setTerrain(GAME.patches)
heightImage:delete()
colorImage:delete()
material:delete()
local obstruction = PatchedObstruction(GAME.patches,1.5)
obstruction:setParticleProjected()
GAME.environment = StaticEnvironment(0,0,0,1.5,obstruction)
GAME.environment:setTerrainFriction(false)
elseif GAME.mapIndex == 3 then ---> PACIFIC OCEAN
----WATER MATERIAL---local waterImages = {}
local imagesCount = 32
294
CHAPTER 20. HOVERJET RACING
for ct = 1, imagesCount do
local index = "0"
if ct < 10 then
index = index.."0"..ct
else
index = index..ct
end
local imageName = "c_"..index..".jpg"
waterImages[ct] = zip:getImage(imageName)
end
local waterTexture = AnimatedTexture(waterImages,3,true)
for ct = 1, imagesCount do
waterImages[ct]:delete()
end
local waterMaterial = Material()
waterMaterial:setAmbient(0.7,0.7,0.7,0.5)
waterMaterial:setDiffuse(1,1,1,0.5)
waterMaterial:setSpecular(1,1,0.5)
waterMaterial:setShininess(96)
waterMaterial:setDiffuseTexture(waterTexture)
----OCEAN---local waveAmplitude = 5e-8
local waveDisplacement = 8
local windX = -40
local surfaceTileSide = 750
local gridSize = 4
local surfaceTilesCount = 8
local textureTilesCount = 16
local ocean = Ocean(
waterMaterial,waveAmplitude,waveDisplacement,windX,0,
surfaceTileSide,gridSize,surfaceTilesCount,textureTilesCount
)
ocean:setShadowFadeDistance(50)
ocean:setShadowOffset(0.25)
ocean:setShadowsStatic()
ocean:setShadowed()
ocean:setTransparent()
setTerrain(ocean)
waterMaterial:delete()
----ISLES---local detailedTexture = zip:getTexture("detail.jpg",1)
GAME.isle1 = GAME.generateIsle(
zip, ---- ZIP file
"terrain1.jpg", ---- ground texture
detailedTexture, ---- detailedTexture
256, ---- detailedTiles
"terrain1.png", ---- heightfield map
12, ---- water level
960, ---- width
960, ---- depth
295
64, ---- height
1440, ---- position x
480, ---- position z
128, ---- trees count
GAME.generateTreesMaterial(zip,"trees1.png"), ---- trees material
32 ---- trees min height
)
GAME.isle2 = GAME.generateIsle(
zip,"terrain2.jpg",detailedTexture,256,"terrain2.png",12,
960,960,64,-480,1440,128,GAME.generateTreesMaterial(zip,"trees2.png"),32
)
GAME.isle3 = GAME.generateIsle(
zip,"terrain3.jpg",detailedTexture,256,"terrain3.png",12,
960,960,64,-1440,-480,128,GAME.generateTreesMaterial(zip,"trees3.png"),32
)
GAME.isle4 = GAME.generateIsle(
zip,"volcano.jpg",detailedTexture,256,"volcano.png",16,
960,960,120,480,-1440,0
)
ocean:addDelegate(GAME.isle1)
ocean:addDelegate(GAME.isle2)
ocean:addDelegate(GAME.isle3)
ocean:addDelegate(GAME.isle4)
GAME.environment = StaticEnvironment(0,0,0,1.5)
GAME.environment:setTerrainFriction(false)
local obstruction
obstruction = HeightFieldObstruction(GAME.isle1,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle2,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle3,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle4,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
----FIRE---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
local fireEmitter = Emitter(10,1,30)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(30,22.5,3.75, 8)
fireEmitter:setColor(1,1,0,1, 1,0,0,0)
fireEmitter:setSize(20,5)
fireEmitter:setGravity(0,0,0, 0,0,0)
fireEmitter:move(480,90,-1440)
296
CHAPTER 20. HOVERJET RACING
fireEmitter:reset()
addObject(fireEmitter)
local bombEmitter = Emitter(10,10,100)
bombEmitter:setTexture(fireTexture,1)
bombEmitter:setVelocity(0,50,0, 10)
bombEmitter:setColor(.25,0,0,1, 1,1,0,0)
bombEmitter:setSize(2,2)
bombEmitter:setGravity(0,-15,0, 0,-15,0)
bombEmitter:move(480,90,-1440)
bombEmitter:reset()
addObject(bombEmitter)
fireTexture:delete()
----SMOKE---local smokeImage = zip:getImage("smoke.png")
smokeImage:convertToRGBA()
local smokeTexture = Texture(smokeImage)
smokeImage:delete()
local smokeEmitter = Emitter(40,4,100)
smokeEmitter:setTexture(smokeTexture,1)
smokeEmitter:setVelocity(7.5,18,-4.5, 5)
smokeEmitter:setColor(0,0,0,0.8, 0.25,0.25,0.25,0)
smokeEmitter:setSize(10,25)
smokeEmitter:setGravity(0,0,0, 0,0,0)
smokeEmitter:move(480,85,-1440)
smokeEmitter:reset()
addObject(smokeEmitter)
smokeTexture:delete()
elseif GAME.mapIndex == 4 then ---> ANTARCTICA
local snow = zip:getTexture("snow.jpg",1)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(1,1,1)
terrainMaterial:setDiffuse(0,0,0)
terrainMaterial:setDiffuseTexture(snow)
local ground = FlatTerrain(terrainMaterial,GAME.MAX_DIST+1000,125)
ground:setShadowFadeDistance(50)
ground:setShadowOffset(0.25)
ground:setShadowsStatic()
ground:setShadowed()
ground:setReflective()
terrainMaterial:delete()
setTerrain(ground)
----ISLES---local detailedTexture = snow
GAME.isle1 = GAME.generateIsle2(
zip, ---- ZIP file
"terrain1.jpg", ---- ground texture
detailedTexture, ---- detailedTexture
2048, ---- detailedTiles
"terrain1.png", ---- heightfield map
297
32, ---- water level
1920, ---- width
1920, ---- depth
256, ---- height
1440, ---- position x
1440 ---- position z
)
GAME.isle2 = GAME.generateIsle2(
zip,"terrain2.jpg",detailedTexture,2048,"terrain2.png",32,
1920,1920,256,-1440,-1440
)
GAME.isle3 = GAME.generateIsle2(
zip,"terrain3.jpg",detailedTexture,2048,"terrain3.png",32,
1920,1920,256,-1440,1440
)
GAME.isle4 = GAME.generateIsle2(
zip,"volcano.jpg",detailedTexture,2048,"volcano.png",32,
1920,1920,256,1440,-1440
)
ground:addDelegate(GAME.isle1)
ground:addDelegate(GAME.isle2)
ground:addDelegate(GAME.isle3)
ground:addDelegate(GAME.isle4)
GAME.environment = StaticEnvironment(0,0,0,1.5)
GAME.environment:setTerrainFriction(false)
local obstruction
obstruction = HeightFieldObstruction(GAME.isle1,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle2,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle3,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
obstruction = HeightFieldObstruction(GAME.isle4,1.5)
obstruction:setParticleProjected()
GAME.environment:addObstruction(obstruction)
elseif GAME.mapIndex == 5 then ---> TERRAFORMED MARS
local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("terrainmap.jpg",true))
material:setGlossTexture(zip:getTexture("detail.jpg",true))
GAME.patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,512)
GAME.patches:setShadowFadeDistance(50)
GAME.patches:setShadowOffset(0.25)
GAME.patches:setShadowed()
setTerrain(GAME.patches)
heightImage:delete()
298
CHAPTER 20. HOVERJET RACING
colorImage:delete()
material:delete()
local obstruction = PatchedObstruction(GAME.patches,1.5)
obstruction:setParticleProjected()
GAME.environment = StaticEnvironment(0,0,0,1.5,obstruction)
GAME.environment:setTerrainFriction(false)
end
----CLOUDS---if GAME.mapIndex == 5 then ---> TERRAFORMED MARS
----CLOUDLAYER---local cloudsImage = zip:getImage("cloudlayer2.jpg")
local alphaImage = zip:getImage("cloudlayer2.png")
cloudsImage:addAlpha(alphaImage)
local cloudsTexture = Texture(cloudsImage,1)
cloudsImage:delete()
alphaImage:delete()
local material = Material()
material:setEmissive(0.9,0.9,0.6)
material:setEnlighted(false)
material:setDiffuseTexture(cloudsTexture)
local cloudLayer = CloudLayer(material,GAME.MAX_DIST*2,500,10)
cloudLayer:setSpeed(10,10)
setCloudLayer(cloudLayer)
elseif GAME.mapIndex ~= 1 then ---> not FORBIDDEN PLANET
for ct = 1, 8 do
local cloudImage = zip:getImage("cloud"..ct..".png")
cloudImage:convertTo111A()
local text = Texture(cloudImage)
cloudImage:delete()
local mat = Material()
mat:setEnlighted(false)
mat:setEmissive(1,1,1)
mat:setDiffuseTexture(text)
local sizeX = 512
local sizeY = 256
local cloud = FadingBillboard(1000,GAME.MAX_DIST-500,sizeX,sizeY,mat)
local radius = math.random()*1000+1000
local angle = math.random()*math.pi*2
local posX = radius*math.cos(angle)
local posY = radius*math.sin(angle)
local h = 750+math.random()*256
cloud:setPosition(posX,h,posY)
cloud:setTransparent()
addObject(cloud)
table.insert(GAME.cloudsList,cloud)
end
end
----JETS SUPPORT-------TEXTURES---local fireImage = zip:getImage("smoke.png")
299
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
local shadowImage = zip:getImage("shadow.png")
shadowImage:convertTo111A()
local shadowTexture = Texture(shadowImage)
shadowImage:delete()
local envtext = zip:getTexture("environ1.jpg")
----SOUNDS---local engineSample = zip:getSample3D("jetengine.wav");
engineSample:setLooping(true)
engineSample:setVolume(64)
engineSample:setMinDistance(64)
local crashSample = zip:getSample3D("crash.wav");
crashSample:setLooping(false)
crashSample:setVolume(255)
crashSample:setMinDistance(64)
local windSample = zip:getSample("wind.wav")
windSample:setLooping(1)
GAME.windSound = windSample:createSound()
GAME.windSound:play()
GAME.windSound:setVolume(64)
----LISTS---GAME.jets = {}
GAME.rockets = {
{ {2,0,-2}, {-2,0,-2} },
{ {0,0,-3} },
{ {1.5,0,-2}, {-1.5,0,-2} },
{ {0,0,-3} },
{ {0,0,-3} },
{ {0,1,-2} },
{ {1,0,-2}, {-1,0,-2} }
}
GAME.holder = {
{0,0,4.25},
{0,0.5,4.5},
{0,0,2.75},
{0,0,4.75},
{0,0,1.5},
{0,0.25,4.1},
{0,0.33,4},
}
----SIMULATOR---GAME.simulator = Simulator()
local stickIndexes = {0,1}
----JETS---local JETS_COUNT = 7
for jetCt = 1, JETS_COUNT do
local jet = {}
GAME.jets[jetCt] = jet
300
CHAPTER 20. HOVERJET RACING
----JET CONTROL---jet.BOOST_ON = 0
jet.ENGINE_BACK = 0
jet.ENGINE_FORW = 1
jet.ENGINE_LEFT = 0
jet.ENGINE_RIGHT = 0
----JET BURST---local jetEmitter = Emitter(15,0.1,10,true)
jetEmitter:setTexture(fireTexture,1)
jetEmitter:setVelocity(0,0,-20, 4)
jetEmitter:setColor(1,1,0.5,0.9, 1,0,0,0)
jetEmitter:setSize(.25,1)
jetEmitter:setGravity(0,0,0, 0,0,0)
jetEmitter:reset()
addObject(jetEmitter)
jetEmitter:hide()
local jetEmitter2 = Emitter(15,0.1,10,true)
jetEmitter2:setTexture(fireTexture,1)
jetEmitter2:setVelocity(0,0,-20, 4)
jetEmitter2:setColor(1,1,0.5,0.9, 1,0,0,0)
jetEmitter2:setSize(.25,1)
jetEmitter2:setGravity(0,0,0, 0,0,0)
jetEmitter2:reset()
addObject(jetEmitter2)
jetEmitter2:hide()
local dustEmitter = Emitter(15,0.4,25)
dustEmitter:setTexture(fireTexture,1)
dustEmitter:setOneShot()
dustEmitter:setVelocity(0,10,0, 10)
if GAME.mapIndex == 3 then ---> OCEAN
dustEmitter:setColor(0.75,0.55,0.35,1, 0.25,0.25,0.25,0)
elseif GAME.mapIndex == 4 then ---> ANTARCTICA
dustEmitter:setColor(0.9,0.9,1,1, 0.45,0.45,0.5,0)
else ---> otherwise
dustEmitter:setColor(0.5,0.3,0.1,1, 0.15,0.15,0.15,0)
end
dustEmitter:setSize(4,12)
dustEmitter:setGravity(0,0,0, 0,0,0)
dustEmitter:reset()
addObject(dustEmitter)
dustEmitter:hide()
local waterEmitter = Emitter(15,0.4,25)
waterEmitter:setTexture(fireTexture,1)
waterEmitter:setVelocity(0,5,0, 5)
waterEmitter:setColor(0.9,0.9,1,1, 0.45,0.45,0.5,0)
waterEmitter:setSize(4,12)
waterEmitter:setGravity(0,0,0, 0,0,0)
waterEmitter:reset()
addObject(waterEmitter)
waterEmitter:hide()
301
----ASTROS---local astro
local mat
jet.dustEmitter = dustEmitter
jet.waterEmitter = waterEmitter
jet.jetEmitter = jetEmitter
jet.jetEmitter2 = jetEmitter2
astro = zip:getMesh("astro"..jetCt..".3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
addShadow(Shadow(astro,6,6,shadowTexture))
jet.model = astro
jet.rockets = GAME.rockets[jetCt]
jet.holder = GAME.holder[jetCt]
local source
source = Source(engineSample,astro,false)
addSource(source)
jet.jetSound = source
source = Source(crashSample,astro,false)
addSource(source)
jet.crashSound = source
----PHYSICS---local startX, startY, startZ = -jetCt*10, 3, 810
local astroH
if GAME.patches then
astroH = GAME.patches:getHeightAt(startX,startZ)
else
if GAME.isle1:includes(startX,startY,startZ) then
astroH = GAME.isle1:getHeightAtAbsolute(startX,startZ)
elseif GAME.isle2:includes(startX,startY,startZ) then
astroH = GAME.isle2:getHeightAtAbsolute(startX,startZ)
elseif GAME.isle3:includes(startX,startY,startZ) then
astroH = GAME.isle3:getHeightAtAbsolute(startX,startZ)
elseif GAME.isle4:includes(startX,startY,startZ) then
astroH = GAME.isle4:getHeightAtAbsolute(startX,startZ)
else
astroH = 0
end
end
local particlePositions = {
startX,startY+astroH+5,startZ-3,
startX,startY+astroH+5,startZ+4
}
local assembly = Assembly(
GAME.simulator,GAME.environment,particlePositions,stickIndexes,GAME.applyForces
302
CHAPTER 20. HOVERJET RACING
)
assembly:setID(jetCt)
assembly:setRadius(3)
jet.assembly = assembly
jet.envHit = getElapsedTime()
jet.terHit = getElapsedTime()
jet.colHit = getElapsedTime()
end
for jetCt = 2, JETS_COUNT do
jet = GAME.jets[jetCt]
jet.jetEmitter:show()
if table.getn(jet.rockets) > 1 then
jet.jetEmitter2:show()
end
jet.jetSound:getSound3D():play()
end
----HUD---GAME.HUD = {}
GAME.HUD.markSprite = {}
GAME.HUD.maxHeight = 0
GAME.setupHuds(zip)
if GAME.mapIndex == 1 then ---> FORBIDDEN PLANET
GAME.setupMap(zip,"mapE.png",true)
elseif GAME.mapIndex == 2 then ---> ROCKY MOUNTAINS
GAME.setupMap(zip,"mapB.png",true)
elseif GAME.mapIndex == 3 then ---> PACIFIC OCEAN
GAME.setupMap(zip,"mapC.png",false)
elseif GAME.mapIndex == 4 then ---> ANTARCTICA
GAME.setupMap(zip,"mapD.png",false)
elseif GAME.mapIndex == 5 then ---> TERRAFORMED MARS
GAME.setupMap(zip,"map.png",true)
end
CAMERA = {view = 2, dist = 30, height = 15, forward = -30, up = 10, side = 0, isFre
-- CAMERA = {view = 2, dist = 20, height = 5, forward = -20, up = 5, side = 0, isFre
----HELP---local help = {
"H O V E R J E T
R A C I N G",
"",
"[NPAD1-9] Change View",
"[ / | * ] Zoom In/Out",
"[
V
] Show/Hide Map",
"[UP|DOWN] Thrust Forw/Back",
"[LFT|RGH] Thrust Left/Right",
"[ 1-9 ] Use Item",
"[
M
] Play/Stop Music",
"[ ENTER ] Back to Menu",
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
303
hideConsole()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function GAME.final()
GAME.target = nil
ALL.stopSoundtrack(GAME)
if GAME.simulator then
GAME.simulator:empty()
GAME.simulator:delete()
GAME.simulator = nil
end
if GAME.environment then
GAME.environment:delete()
GAME.environment = nil
end
if GAME.windSound then
GAME.windSound:delete()
GAME.windSound = nil
end
GAME.MAX_DIST = nil
GAME.jets = nil
GAME.rockets = nil
GAME.holder = nil
GAME.patches = nil
GAME.isle1 = nil
GAME.isle2 = nil
GAME.isle3 = nil
GAME.isle4 = nil
GAME.cloudsList = nil
GAME.HUD = nil
CAMERA = nil
----EMPTY WORLD---disableFog()
empty()
emptyOverlay()
end
----LOOP---function GAME.update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
GAME.simulator:runStep(timeStep)
----UPDATE MODEL---local JETS_COUNT = 7
for jetCt = 1, JETS_COUNT do
local jet = GAME.jets[jetCt]
304
CHAPTER 20. HOVERJET RACING
local assembly = jet.assembly
local x0,y0,z0 = assembly:getPosition(0)
local x1,y1,z1 = assembly:getPosition(1)
local posX,posY,posZ = (x1+x0)*0.5,(y1+y0)*0.5,(z1+z0)*0.5
local viewX,viewY,viewZ = x1-x0,y1-y0,z1-z0
local invView = 1/math.sqrt(viewX*viewX+viewY*viewY+viewZ*viewZ)
viewX,viewY,viewZ = viewX*invView,viewY*invView,viewZ*invView
local upX, upY, upZ
if GAME.patches then
GAME.patches:getHeightAt(posX,posZ)
upX, upY, upZ = GAME.patches:getLastNormal()
else
if GAME.isle1:includes(posX,posY,posZ) then
GAME.isle1:getHeightAtAbsolute(posX,posZ)
upX, upY, upZ = GAME.isle1:getLastNormal()
elseif GAME.isle2:includes(posX,posY,posZ) then
GAME.isle2:getHeightAtAbsolute(posX,posZ)
upX, upY, upZ = GAME.isle2:getLastNormal()
elseif GAME.isle3:includes(posX,posY,posZ) then
GAME.isle3:getHeightAtAbsolute(posX,posZ)
upX, upY, upZ = GAME.isle3:getLastNormal()
elseif GAME.isle4:includes(posX,posY,posZ) then
GAME.isle4:getHeightAtAbsolute(posX,posZ)
upX, upY, upZ = GAME.isle4:getLastNormal()
else
upX, upY, upZ = 0, 1, 0
end
end
local sideX,sideY,sideZ =
viewZ*upY-viewY*upZ,
viewX*upZ-viewZ*upX,
viewY*upX-viewX*upY
local invSide = 1/math.sqrt(sideX*sideX+sideY*sideY+sideZ*sideZ)
sideX,sideY,sideZ = sideX*invSide,sideY*invSide,sideZ*invSide
upX,upY,upZ =
viewY*sideZ-viewZ*sideY,
viewZ*sideX-viewX*sideZ,
viewX*sideY-viewY*sideX
local target = GAME.target
target:setViewDirection(viewX,viewY,viewZ)
target:setSideDirection(sideX,sideY,sideZ)
target:setUpDirection(upX,upY,upZ)
local model = jet.model
model:interpolate(target,0.1)
model:setPosition(posX,posY,posZ)
viewX, viewY, viewZ = model:getViewDirection()
sideX, sideY, sideZ = model:getSideDirection()
upX, upY, upZ = model:getUpDirection()
local rockets = jet.rockets
local NUM_ROCKETS = table.getn(rockets)
305
if NUM_ROCKETS > 1 then
local rocket = rockets[1]
local dx,dy,dz = rocket[1],rocket[2],rocket[3]
local emitter = jet.jetEmitter
emitter:set(model)
emitter:move(
dz*viewX+dx*sideX+dy*upX,
dz*viewY+dx*sideY+dy*upY,
dz*viewZ+dx*sideZ+dy*upZ
)
rocket = rockets[2]
dx,dy,dz = rocket[1],rocket[2],rocket[3]
local emitter2 = jet.jetEmitter2
emitter2:set(model)
emitter2:move(
dz*viewX+dx*sideX+dy*upX,
dz*viewY+dx*sideY+dy*upY,
dz*viewZ+dx*sideZ+dy*upZ
)
else
local rocket = rockets[1]
local dx,dy,dz = rocket[1],rocket[2],rocket[3]
local emitter = jet.jetEmitter
emitter:set(model)
emitter:move(
dz*viewX+dx*sideX+dy*upX,
dz*viewY+dx*sideY+dy*upY,
dz*viewZ+dx*sideZ+dy*upZ
)
end
if assembly:wasEnvironmentHit() then
local time = getElapsedTime()
if assembly:wasTerrainHit() then
if time > jet.terHit+1 then
jet.crashSound:getSound3D():play()
end
local emitter = jet.waterEmitter
local vx,vy,vz = assembly:getVelocity(1)
local speed = math.floor(math.sqrt(vx*vx+vy*vy+vz*vz)*3.6)
if speed >= 30 then
emitter:set(model)
emitter:move(0,-1,0)
emitter:setVelocity(0,speed*0.01,0, speed*0.015)
emitter:setSize(speed*0.005,speed*0.025)
emitter:show()
else
emitter:hide()
end
jet.terHit = time
else
306
CHAPTER 20. HOVERJET RACING
if time > jet.envHit+1 then
jet.crashSound:getSound3D():play()
local emitter
emitter = jet.waterEmitter
emitter:hide()
emitter = jet.dustEmitter
emitter:set(model)
emitter:move(0,-1,0)
emitter:show()
emitter:reset()
end
jet.envHit = time
end
else
local emitter = jet.waterEmitter
emitter:hide()
end
local collider = assembly:getColliderHit()
if collider >= 0 then
local time = getElapsedTime()
if time > jet.colHit+1 then
jet.crashSound:getSound3D():play()
end
jet.colHit = time
if collider > 0 then
GAME.jets[collider].colHit = time
end
end
end
local jet = GAME.jets[GAME.modelIndex]
local assembly = jet.assembly
local vx,vy,vz = assembly:getVelocity(1)
local speed = math.floor(math.sqrt(vx*vx+vy*vy+vz*vz)*3.6)
GAME.setHudValue(GAME.HUD.speedHud,3,speed)
local volume = speed*0.5
if(volume > 255) then
volume = 255
end
GAME.windSound:setVolume(volume)
local posX,posY,posZ = assembly:getPosition(1)
if posY > GAME.HUD.maxHeight then
GAME.HUD.maxHeight = posY
GAME.setHudValue(GAME.HUD.maxhHud,4,posY)
end
GAME.setHudValue(GAME.HUD.heightHud,4,posY)
local NUM_ROCKETS = table.getn(jet.rockets)
----CAMERA CONTROL---local camX,camY,camZ
if CAMERA.isFree then
local speed = 200
307
if isKeyPressed(38) then --> UP
camera:moveForward(speed*timeStep)
elseif isKeyPressed(40) then --> DOWN
camera:moveForward(-speed*timeStep)
end
if isKeyPressed(37) then --> LEFT
camera:moveSide(speed*timeStep)
elseif isKeyPressed(39) then --> RIGHT
camera:moveSide(speed*timeStep)
end
local climbSpeed = 200
if isKeyPressed(33) then --> PRIOR
camera:move(0,climbSpeed*timeStep,0)
elseif isKeyPressed(34) then --> NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
camX,camY,camZ = camera:getPosition()
local h
if GAME.patches then
h = GAME.patches:getHeightAt(camX,camZ)
else
if GAME.isle1:includes(camX,camY,camZ) then
h = GAME.isle1:getHeightAtAbsolute(camX,camZ)
if h < 0 then h = 0 end
elseif GAME.isle2:includes(camX,camY,camZ) then
h = GAME.isle2:getHeightAtAbsolute(camX,camZ)
if h < 0 then h = 0 end
elseif GAME.isle3:includes(camX,camY,camZ) then
h = GAME.isle3:getHeightAtAbsolute(camX,camZ)
if h < 0 then h = 0 end
elseif GAME.isle4:includes(camX,camY,camZ) then
h = GAME.isle4:getHeightAtAbsolute(camX,camZ)
if h < 0 then h = 0 end
else
h = 0
end
end
if camY < h+5 then
camY = h+5
end
camera:setPosition(camX,camY,camZ)
else
308
CHAPTER 20. HOVERJET RACING
if isKeyPressed(32) == true and jet.BOOST_ON == 0 then --> SPACE
jet.jetEmitter:setColor(1,1,1,0.9, 1,1,0,0)
if NUM_ROCKETS > 1 then
jet.jetEmitter2:setColor(1,1,1,0.9, 1,1,0,0)
end
jet.BOOST_ON = 1
elseif isKeyPressed(32) == false and jet.BOOST_ON == 1 then
jet.BOOST_ON = 0
jet.jetEmitter:setColor(1,1,0.5,0.9, 1,0,0,0)
if NUM_ROCKETS > 1 then
jet.jetEmitter2:setColor(1,1,0.5,0.9, 1,0,0,0)
end
end
if isKeyPressed(38) == true and jet.ENGINE_FORW == 0 then --> UP
jet.jetEmitter:show()
if NUM_ROCKETS > 1 then
jet.jetEmitter2:show()
end
jet.jetSound:getSound3D():play()
jet.ENGINE_FORW = 1
elseif isKeyPressed(38) == false and jet.ENGINE_FORW == 1 then
jet.ENGINE_FORW = 0
if jet.ENGINE_LEFT == 0 and jet.ENGINE_RIGHT == 0 then
jet.jetEmitter:hide()
jet.jetEmitter2:hide()
jet.jetSound:getSound3D():stop()
end
end
if isKeyPressed(37) == true and jet.ENGINE_LEFT == 0 then --> LEFT
if NUM_ROCKETS > 1 then
jet.jetEmitter2:show()
else
jet.jetEmitter:show()
end
jet.jetSound:getSound3D():play()
jet.ENGINE_LEFT = 1
elseif isKeyPressed(37) == false and jet.ENGINE_LEFT == 1 then
jet.ENGINE_LEFT = 0
if jet.ENGINE_FORW == 0 and jet.ENGINE_RIGHT == 0 then
jet.jetEmitter:hide()
jet.jetEmitter2:hide()
jet.jetSound:getSound3D():stop()
end
end
if isKeyPressed(39) == true and jet.ENGINE_RIGHT == 0 then --> RIGHT
jet.jetEmitter:show()
jet.jetSound:getSound3D():play()
jet.ENGINE_RIGHT = 1
elseif isKeyPressed(39) == false and jet.ENGINE_RIGHT == 1 then
jet.ENGINE_RIGHT = 0
309
if jet.ENGINE_LEFT == 0 and jet.ENGINE_FORW == 0 then
jet.jetEmitter:hide()
jet.jetEmitter2:hide()
jet.jetSound:getSound3D():stop()
end
end
if isKeyPressed(40) == true and jet.ENGINE_BACK == 0 then --> DOWN
jet.jetEmitter:show()
if NUM_ROCKETS > 1 then
jet.jetEmitter2:show()
end
jet.jetSound:getSound3D():play()
jet.ENGINE_BACK = 1
elseif isKeyPressed(40) == false and jet.ENGINE_BACK == 1 then
jet.ENGINE_BACK = 0
jet.jetEmitter:hide()
if NUM_ROCKETS > 1 then
jet.jetEmitter2:hide()
end
jet.jetSound:getSound3D():stop()
end
if isKeyPressed(107) then ---> ADD
if CAMERA.view ~= 0 and CAMERA.view ~= 5 then
local scale = 1+timeStep
CAMERA.height = CAMERA.height*scale
if CAMERA.height <= 500 then
CAMERA.up = CAMERA.height
else
CAMERA.height = 500
end
end
elseif isKeyPressed(109) then ---> SUBTRACT
if CAMERA.view ~= 0 and CAMERA.view ~= 5 then
local scale = 1-timeStep
CAMERA.height = CAMERA.height*scale
if CAMERA.height >= 0 then
CAMERA.up = CAMERA.height
else
CAMERA.height = 0
end
end
end
if isKeyPressed(111) then ---> DIVIDE
local scale = 1-timeStep
CAMERA.dist = CAMERA.dist*scale
if CAMERA.dist >= 20 then
CAMERA.forward = CAMERA.forward*scale
CAMERA.side = CAMERA.side*scale
CAMERA.up = CAMERA.up*scale
else
310
CHAPTER 20. HOVERJET RACING
CAMERA.dist = 20
end
elseif isKeyPressed(106) then ---> MULTIPLY
local scale = 1+timeStep
CAMERA.dist = CAMERA.dist*scale
if CAMERA.dist <= 500 then
CAMERA.forward = CAMERA.forward*scale
CAMERA.side = CAMERA.side*scale
CAMERA.up = CAMERA.up*scale
else
CAMERA.dist = 500
end
end
local target = GAME.target
target:set(jet.model)
target:moveForward(CAMERA.forward)
target:moveSide(CAMERA.side)
local posX, posY, posZ = target:getPosition()
posY = posY+CAMERA.up
local posH
if GAME.patches then
posH = GAME.patches:getHeightAt(posX,posZ)
else
if GAME.isle1:includes(posX,posY,posZ) then
posH = GAME.isle1:getHeightAtAbsolute(posX,posZ)
if posH < 0 then posH = 0 end
elseif GAME.isle2:includes(posX,posY,posZ) then
posH = GAME.isle2:getHeightAtAbsolute(posX,posZ)
if posH < 0 then posH = 0 end
elseif GAME.isle3:includes(posX,posY,posZ) then
posH = GAME.isle3:getHeightAtAbsolute(posX,posZ)
if posH < 0 then posH = 0 end
elseif GAME.isle4:includes(posX,posY,posZ) then
posH = GAME.isle4:getHeightAtAbsolute(posX,posZ)
if posH < 0 then posH = 0 end
else
posH = 0
end
end
if posY < posH+5 then target:setPosition(posX,posH+5,posZ) end
camX,camY,camZ = camera:getPosition()
local pathX,pathY,pathZ = posX-camX,posY-camY,posZ-camZ
camera:interpolate(target,0.01)
local interpolation = 0.05
camX = camX+interpolation*pathX
camY = camY+interpolation*pathY
camZ = camZ+interpolation*pathZ
if GAME.patches then
posH = GAME.patches:getHeightAt(camX,camZ)
else
311
if GAME.isle1:includes(camX,camY,camZ) then
posH = GAME.isle1:getHeightAtAbsolute(camX,camZ)
if posH < 0 then posH = 0 end
elseif GAME.isle2:includes(camX,camY,camZ) then
posH = GAME.isle2:getHeightAtAbsolute(camX,camZ)
if posH < 0 then posH = 0 end
elseif GAME.isle3:includes(camX,camY,camZ) then
posH = GAME.isle3:getHeightAtAbsolute(camX,camZ)
if posH < 0 then posH = 0 end
elseif GAME.isle4:includes(camX,camY,camZ) then
posH = GAME.isle4:getHeightAtAbsolute(camX,camZ)
if posH < 0 then posH = 0 end
else
posH = 0
end
end
if camY < posH+5 then camY = posH+5 end
camera:setPosition(camX,camY,camZ)
posX, posY, posZ = jet.model:getPosition()
camera:pointTo(posX,posY+CAMERA.up,posZ)
end
----UPDATE MAP---if GAME.HUD.isMapShown then
local horViewX,horViewZ = camera:getHorizontalView()
local mapSprite = GAME.HUD.mapSprite
local TEX_SCALE = 6.510417e-5 ---> 1/(512*30)
if horViewZ ~= 0 then
mapSprite:setRotation(math.atan2(horViewX,horViewZ))
local markSprite = GAME.HUD.markSprite
local W, H = getDimension()
local MAP_SIZE = 170
local SCALE = MAP_SIZE*TEX_SCALE
local CENTER_X = W-MAP_SIZE
local CENTER_Z = H-MAP_SIZE
for jetCt = 1, JETS_COUNT do
local x,y,z = GAME.jets[jetCt].model:getPosition()
x, z = (camX-x)*SCALE, (z-camZ)*SCALE
if x >= -85 and x <= 85 and z >= -85 and z <= 85 then
local dx = x*horViewZ+z*horViewX
local dz = -x*horViewX+z*horViewZ
markSprite[jetCt]:setLocation(CENTER_X+dx,CENTER_Z+dz)
markSprite[jetCt]:show()
else
markSprite[jetCt]:hide()
end
end
end
local texX = camX*TEX_SCALE+0.5
local texZ = camZ*TEX_SCALE+0.5
mapSprite:setTextureCoord(texX+0.5,texZ-0.5,texX-0.5,texZ+0.5)
312
CHAPTER 20. HOVERJET RACING
end
----MOVE CLOUDS---for index,cloud in ipairs(GAME.cloudsList) do
if cloud:getDistance() > GAME.MAX_DIST then
local sideX, sideY, sideZ = camera:getSideDirection()
local viewX, viewY, viewZ = camera:getViewDirection()
local dist = GAME.MAX_DIST-500
local span = math.random()*1000-500
local px = sideX*span+viewX*dist+posX
local pz = sideZ*span+viewZ*dist+posZ
local h = 750+math.random()*256
cloud:setPosition(px,h,pz)
local sizeX = 512
local sizeY = 256
cloud:setSize(sizeX,sizeY)
end
end
end
----KEYDOWN---function GAME.keyDown(key)
if key >= 97 and key <= 105 then ---> NUMPAD1-9
releaseKey(key)
if key == 97 then
CAMERA.view = 1
CAMERA.forward = -CAMERA.dist*0.707
CAMERA.side = CAMERA.dist*0.707
CAMERA.up = CAMERA.height
elseif key == 98 then
CAMERA.view = 2
CAMERA.forward = -CAMERA.dist
CAMERA.side = 0
CAMERA.up = CAMERA.height
elseif key == 99 then
CAMERA.view = 3
CAMERA.forward = -CAMERA.dist*0.707
CAMERA.side = -CAMERA.dist*0.707
CAMERA.up = CAMERA.height
elseif key == 100 then
CAMERA.view = 4
CAMERA.forward = 0
CAMERA.side = CAMERA.dist
CAMERA.up = CAMERA.height
elseif key == 101 then
if CAMERA.view == 5 then
CAMERA.view = 0
CAMERA.up = -CAMERA.dist
else
CAMERA.view = 5
CAMERA.up = CAMERA.dist
313
end
CAMERA.forward = 0
CAMERA.side = 0
elseif key == 102 then
CAMERA.view = 6
CAMERA.forward = 0
CAMERA.side = -CAMERA.dist
CAMERA.up = CAMERA.height
elseif key == 103 then
CAMERA.view = 7
CAMERA.forward = CAMERA.dist*0.707
CAMERA.side = CAMERA.dist*0.707
CAMERA.up = CAMERA.height
elseif key == 104 then
CAMERA.view = 8
CAMERA.forward = CAMERA.dist
CAMERA.side = 0
CAMERA.up = CAMERA.height
elseif key == 105 then
CAMERA.view = 9
CAMERA.forward = CAMERA.dist*0.707
CAMERA.side = -CAMERA.dist*0.707
CAMERA.up = CAMERA.height
end
elseif key == 8 then ---> BACKSPACE
releaseKey(key)
CAMERA.isFree = not CAMERA.isFree
elseif key == 13 then ---> RETURN
releaseKey(key)
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
elseif key == string.byte("M") then ---> M
releaseKey(key)
if GAME.soundTrackIsPlaying then
GAME.soundTrack:stop()
GAME.soundTrackIsPlaying = false
else
GAME.soundTrack:play()
GAME.soundTrackIsPlaying = true
end
elseif key == string.byte("V") then ---> V
releaseKey(key)
if GAME.HUD.isMapShown then
GAME.HUD.mapSprite:hide()
local JETS_COUNT = 7
for jetCt = 1, JETS_COUNT do
GAME.HUD.markSprite[jetCt]:hide()
end
GAME.HUD.isMapShown = false
else
GAME.HUD.mapSprite:show()
314
CHAPTER 20. HOVERJET RACING
local JETS_COUNT = 7
for jetCt = 1, JETS_COUNT do
GAME.HUD.markSprite[jetCt]:show()
end
GAME.HUD.isMapShown = true
end
end
end
-------MENU SCENE
-------INITIALIZATION SUPPORT---function MENU.setupPointer(zip)
local pointerImage = zip:getImage("arrow.png")
local pointerSize = pointerImage:getDimension()
pointerImage:addAlpha(pointerImage)
local pointerSprite = OverlaySprite(
pointerSize,pointerSize,Texture(pointerImage),true
)
pointerImage:delete()
pointerSprite:setLayer(-1)
setPointer(pointerSprite)
local w, h = getDimension()
setPointerLocation(w*0.5,h*0.5)
showPointer()
end
function MENU.createLogo(zip)
local logoImage = zip:getImage("logo.jpg")
local alphaImage = zip:getImage("logo.png")
alphaImage:convertTo111A()
logoImage:addAlpha(alphaImage)
alphaImage:delete()
local logoSize = logoImage:getDimension()
MENU.GUI.logoSprite = OverlaySprite(
logoSize,logoSize,Texture(logoImage),true
)
local logoSprite = MENU.GUI.logoSprite
logoImage:delete()
local w, h = getDimension()
logoSprite:setLocation(w*0.5,h)
addToOverlay(logoSprite)
end
function MENU.createTexts()
local colors = {
{r = 1, g = 1, b = 0},
315
{r = 0, g = 1, b = 1},
{r = 1, g = 1, b = 1}
}
local creditStrings = {
{
1, "H O V E R J E T
R A C I N G",
2, "",
2, "D E M O",
2, "",
3, "Copyright \184 2005",
2, "Leonardo Boselli",
2, ""
},
{
1, "Programming & Design",
3, "",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
3, "",
1, "3D Models",
3, "",
2, "Giovanni \"JohnJ\" Peirone",
3, "peirone@libero.it",
3, "",
2, "Andrea \"Motenai\" Orioli",
3, "andreone82@libero.it",
},
{
3, "Thanks to",
3, "",
2, "Matteo \"Fuzz\" Perenzoni",
3, "",
3, "for fruitful discussions on",
3, "OpenGL and 3D programming.",
3, "",
3, "The sources of his demo for",
3, "the NeHe’s Apocalypse Contest",
3, "were the first building blocks",
3, "of the APOCALYX 3D Engine."
},
{
3, "Thanks to",
3, "",
1, "TeCGraf, PUC-Rio",
3, "for the LUA script language",
2, "www.lua.org",
3, "",
1, "Borland",
3, "for their free C++ compiler",
2, "www.borland.com",
316
CHAPTER 20. HOVERJET RACING
},
{
3, "Thanks to the following sites",
3, "for their useful tutorials",
3, "about game programming",
3, "",
1, "NeHe Productions",
2, "nehe.gamedev.net",
1, "Game Tutorials",
2, "www.gametutorials.com",
1, "SULACO",
2, "www.sulaco.co.za",
3, "",
3, "and",
3, "",
1, "Game Programming Italia",
2, "www.gameprog.it"
},
{
3, "Thanks to these web sites",
3, "for publishing news about game",
3, "development and related stuff",
3, "",
1, "GameDev",
2, "www.gamedev.net",
1, "FlipCode",
2, "www.flipcode.org",
1, "CFXweb",
2, "www.cfxweb.net",
1, "OpenGL.org",
2, "www.opengl.org"
},
{
3, "And, finally, thanks to",
3, "ALL the people of the",
3, "italian newsgroup",
3, "",
1, "it.comp.giochi.sviluppo",
3, "",
3, "",
3, ""
}
}
local instructionStrings = {
1, "H O V E R J E T
R A C I N G",
3, "",
3, "Hoverjets are vehicles sustained over ground by antigravity ",
3, "engines and propelled by rockets. You can pilot your vehicle",
3, "using the arrow keys and change the view using the numpad. ",
2, "THRUST: UP = forward; LEFT|RIGHT = lateral; DOWN = backward ",
317
2, "VIEW : 1-9 = change view; /|* = zoom in/out; +|- = incline ",
3, "The F1 key shows the complete list of available keys.",
3, "",
3, "Enjoy the demo! leo",
3, "",
2, "Press ENTER to start",
}
local font = getMainOverlayFont()
local fontH = font:getHeight()
local w, h = getDimension()
MENU.GUI.optionsTexts = OverlayTexts(font)
local optionsTexts = MENU.GUI.optionsTexts
local scale = 1.5
local offset = fontH*2
local playText
playText = OverlayText("[9] Exit
")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[3] Start Race
")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[2] Choose Track ")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[1] Choose Vehicle")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[0] Instructions ")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
optionsTexts:setLocation(w*0.5,h)
addToOverlay(optionsTexts)
MENU.GUI.credits = {}
local credits = MENU.GUI.credits
credits.status = 0
318
CHAPTER 20. HOVERJET RACING
credits.index = 1
credits.texts = {}
local x = w*0.5+160
local creditTexts = credits.texts
for creditIdx = 1, table.getn(creditStrings) do
creditTexts[creditIdx] = OverlayTexts(font)
local currentCreditText = creditTexts[creditIdx]
local textLines = creditStrings[creditIdx]
local textLinesCount = table.getn(textLines)
local y = (h+fontH*(textLinesCount-1))*0.5-240
for textLineIdx = 1, table.getn(textLines), 2 do
local text = OverlayText(textLines[textLineIdx+1])
local colorIdx = textLines[textLineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(x,y)
y = y-fontH
currentCreditText:add(text)
end
currentCreditText:setLocation(0,-h*0.5)
addToOverlay(currentCreditText)
currentCreditText:hide()
end
MENU.GUI.instructionTexts = OverlayTexts(font)
local instructionCount = table.getn(instructionStrings)
y = fontH*instructionCount*0.25
for lineIdx = 1, instructionCount, 2 do
local text = OverlayText(instructionStrings[lineIdx+1])
local colorIdx = instructionStrings[lineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(0,y)
y = y-fontH
MENU.GUI.instructionTexts:add(text)
end
MENU.GUI.instructionTexts:setLocation(w*0.5,h*0.5)
addToOverlay(MENU.GUI.instructionTexts)
MENU.GUI.instructionTexts:hide()
end
function MENU.setupCamera()
setAmbient(0.3,0.3,0.3)
local camera = {angleOfView = 60, nearClip = 3, farClip = 3000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
theCamera:reset()
theCamera:move(0,800,0)
end
319
function MENU.setupHelp()
hideConsole()
showHelpReduced()
local help = {
"H O V E R J E T
R A C I N G",
" ",
"[ 0 ] Instructions",
"[ 1 ] Choose Vehicle",
"[ 2 ] Choose Track",
"[ 3 ] Start Race",
"[ 4 ] Exit",
" ",
"[ F 1 ] Show/Hide Help",
}
setHelp(help)
end
----INITIALIZATION---function MENU.init()
empty()
emptyOverlay()
MENU.GUI = {}
if not fileExists("HoverjetRacing.dat") then
showConsole()
error("\nERROR: File ’HoverjetRacing.dat’ not found")
end
local zip = Zip("HoverjetRacing.dat")
ALL.showSplashImage(zip)
ALL.playSoundtrack(zip,"intro.mid",MENU.GUI)
setTitle(" H O V E R J E T
R A C I N G")
MENU.createLogo(zip)
MENU.createTexts()
----SCENERY
----SKYBOX---local MAX_DIST = 3000
local fogColor = {0.475,0.431,0.451}
enableFog(MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
320
CHAPTER 20. HOVERJET RACING
----MOON---local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,MAX_DIST-500
)
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("terrainmapB.jpg",true))
material:setGlossTexture(zip:getTexture("terrainmapB.jpg",true))
local patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,512)
setTerrain(patches)
heightImage:delete()
colorImage:delete()
material:delete()
----ASTROS---local envtext = zip:getTexture("environ1.jpg")
local astro
local mat
MENU.GUI.jetModels = {}
astro = zip:getMesh("astro1.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
astro:hide()
MENU.GUI.jetModels[1] = astro
astro = zip:getMesh("astro2.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
321
astro:hide()
MENU.GUI.jetModels[2] = astro
astro = zip:getMesh("astro3.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
astro:hide()
MENU.GUI.jetModels[3] = astro
astro = zip:getMesh("astro4.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
astro:hide()
MENU.GUI.jetModels[4] = astro
astro = zip:getMesh("astro5.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
astro:hide()
MENU.GUI.jetModels[5] = astro
astro = zip:getMesh("astro6.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
astro:hide()
MENU.GUI.jetModels[6] = astro
astro = zip:getMesh("astro7.3ds")
mat = astro:getMaterial()
mat:setAmbient(1,1,1)
mat:setDiffuse(1,1,1)
mat:setSpecular(1,1,0.5)
mat:setShininess(96)
mat:setEnvironmentTexture(envtext,0.15)
addObject(astro)
322
CHAPTER 20. HOVERJET RACING
astro:hide()
MENU.GUI.jetModels[7] = astro
if GAME.modelIndex then
MENU.GUI.modelIndex = GAME.modelIndex
else
MENU.GUI.modelIndex = 1
end
MENU.GUI.jetData = {
{name = "MIMETIC BOLT", length = 7, width = 6, weight = 12500, rockets = 2, power
{name = "STEEL FLAME", length = 8, width = 6, weight = 10500, rockets = 1, power
{name = "PALE SAUCER", length = 5, width = 8, weight = 12000, rockets = 2, power
{name = "LETHAL BLADES", length = 8, width = 4, weight = 10200, rockets = 1, powe
{name = "FORKED ARROW", length = 9, width = 4, weight = 10050, rockets = 1, power
{name = "DEADLY ROTOR", length = 7, width = 6, weight = 11000, rockets = 1, power
{name = "FIRE SHIELD", length = 7, width = 6, weight = 11000, rockets = 2, power
}
local w,h = getDimension()
local font = getMainOverlayFont()
local fontH = font:getHeight()
MENU.GUI.dataTexts = {}
local dataTexts = MENU.GUI.dataTexts
for dataIdx = 1, table.getn(MENU.GUI.jetData) do
dataTexts[dataIdx] = OverlayTexts(font)
local data = MENU.GUI.jetData[dataIdx]
local text
local offset = 0
text = OverlayText(data.name)
text:setColor(1,1,0)
text:setScale(2)
text:setLocation(0,offset)
offset = offset-fontH*2
dataTexts[dataIdx]:add(text)
text = OverlayText(string.format("length %d m
",data.length))
text:setColor(1,1,1)
text:setLocation(0,offset)
offset = offset-fontH
dataTexts[dataIdx]:add(text)
text = OverlayText(string.format(" width %d m
",data.width))
text:setColor(1,1,1)
text:setLocation(0,offset)
offset = offset-fontH
dataTexts[dataIdx]:add(text)
text = OverlayText(string.format("weight %d Kg ",data.weight))
text:setColor(1,1,1)
text:setLocation(0,offset)
offset = offset-fontH
dataTexts[dataIdx]:add(text)
text = OverlayText(string.format(" power %dx%d KW",data.rockets,data.power))
text:setColor(1,1,1)
text:setLocation(0,offset)
323
offset = offset-fontH*2
dataTexts[dataIdx]:add(text)
text = OverlayText("ARROW KEYS to change | ENTER to select")
text:setColor(0,1,1)
text:setLocation(0,offset)
offset = offset-fontH
dataTexts[dataIdx]:add(text)
dataTexts[dataIdx]:setLocation(w*0.5,h*0.25)
addToOverlay(dataTexts[dataIdx])
dataTexts[dataIdx]:hide()
end
----MAPS
MENU.GUI.maps = {}
local MAP_SIZE = 256
local mapSprite
local mapImage
mapImage = zip:getImage("mapE.png")
mapImage:convertToRGB()
MENU.GUI.maps[1] = OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,true))
local mapSprite = MENU.GUI.maps[1]
mapImage:delete()
mapSprite:setLayer(-1)
mapSprite:setColor(0.8,0.9,1,0.5)
mapSprite:setLocation(w*0.5,h*0.667)
addToOverlay(mapSprite)
mapSprite:hide()
mapImage = zip:getImage("mapB.png")
mapImage:convertToRGB()
MENU.GUI.maps[2] = OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,true))
local mapSprite = MENU.GUI.maps[2]
mapImage:delete()
mapSprite:setLayer(-1)
mapSprite:setColor(1,0.9,0.5,0.5)
mapSprite:setLocation(w*0.5,h*0.667)
addToOverlay(mapSprite)
mapSprite:hide()
mapImage = zip:getImage("mapC.png")
mapImage:convertToRGB()
MENU.GUI.maps[3] = OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,true))
local mapSprite = MENU.GUI.maps[3]
mapImage:delete()
mapSprite:setLayer(-1)
mapSprite:setColor(0.6,0.6,1,0.5)
mapSprite:setLocation(w*0.5,h*0.667)
addToOverlay(mapSprite)
mapSprite:hide()
mapImage = zip:getImage("mapD.png")
mapImage:convertToRGB()
MENU.GUI.maps[4] = OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,true))
local mapSprite = MENU.GUI.maps[4]
324
CHAPTER 20. HOVERJET RACING
mapImage:delete()
mapSprite:setLayer(-1)
mapSprite:setColor(1,1,1,0.5)
mapSprite:setLocation(w*0.5,h*0.667)
addToOverlay(mapSprite)
mapSprite:hide()
mapImage = zip:getImage("map.png")
mapImage:convertToRGB()
MENU.GUI.maps[5] = OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,true))
local mapSprite = MENU.GUI.maps[5]
mapImage:delete()
mapSprite:setLayer(-1)
mapSprite:setColor(1,0.85,0.85,0.5)
mapSprite:setLocation(w*0.5,h*0.667)
addToOverlay(mapSprite)
mapSprite:hide()
if GAME.mapIndex then
MENU.GUI.mapIndex = GAME.mapIndex
else
MENU.GUI.mapIndex = 5
end
MENU.GUI.mapTexts = {}
local mapTexts
local mapText
mapTexts = OverlayTexts(font)
mapText = OverlayText("Forbidden Planet");
mapText:setColor(1,1,0)
mapText:setScale(2)
mapTexts:add(mapText)
mapText = OverlayText("Maze track on a far planet in the Magellan Cloud")
mapText:setLocation(0,-fontH*2)
mapText:setColor(1,1,1)
mapTexts:add(mapText)
mapText = OverlayText("ARROW KEYS to change | ENTER to select")
mapText:setLocation(0,-fontH*4)
mapText:setColor(0,1,1)
mapTexts:add(mapText)
mapTexts:setLocation(w*0.5,h*0.25)
addToOverlay(mapTexts)
mapTexts:hide()
MENU.GUI.mapTexts[1] = mapTexts
mapTexts = OverlayTexts(font)
mapText = OverlayText("Rocky Mountains");
mapText:setColor(1,1,0)
mapText:setScale(2)
mapTexts:add(mapText)
mapText = OverlayText("Hard track carved in a canyon of the Rocky Mountains")
mapText:setLocation(0,-fontH*2)
mapText:setColor(1,1,1)
mapTexts:add(mapText)
325
mapText = OverlayText("ARROW KEYS to change | ENTER to select")
mapText:setLocation(0,-fontH*4)
mapText:setColor(0,1,1)
mapTexts:add(mapText)
mapTexts:setLocation(w*0.5,h*0.25)
addToOverlay(mapTexts)
mapTexts:hide()
MENU.GUI.mapTexts[2] = mapTexts
mapTexts = OverlayTexts(font)
mapText = OverlayText("Pacific Ocean");
mapText:setColor(1,1,0)
mapText:setScale(2)
mapTexts:add(mapText)
mapText = OverlayText("Water track around four isles lost in the Pacific Ocean")
mapText:setLocation(0,-fontH*2)
mapText:setColor(1,1,1)
mapTexts:add(mapText)
mapText = OverlayText("ARROW KEYS to change | ENTER to select")
mapText:setLocation(0,-fontH*4)
mapText:setColor(0,1,1)
mapTexts:add(mapText)
mapTexts:setLocation(w*0.5,h*0.25)
addToOverlay(mapTexts)
mapTexts:hide()
MENU.GUI.mapTexts[3] = mapTexts
mapTexts = OverlayTexts(font)
mapText = OverlayText("Antarctica");
mapText:setColor(1,1,0)
mapText:setScale(2)
mapTexts:add(mapText)
mapText = OverlayText("Iced track around four huge rocks in the Antarctica")
mapText:setLocation(0,-fontH*2)
mapText:setColor(1,1,1)
mapTexts:add(mapText)
mapText = OverlayText("ARROW KEYS to change | ENTER to select")
mapText:setLocation(0,-fontH*4)
mapText:setColor(0,1,1)
mapTexts:add(mapText)
mapTexts:setLocation(w*0.5,h*0.25)
addToOverlay(mapTexts)
mapTexts:hide()
MENU.GUI.mapTexts[4] = mapTexts
mapTexts = OverlayTexts(font)
mapText = OverlayText("Terraformed Mars");
mapText:setColor(1,1,0)
mapText:setScale(2)
mapTexts:add(mapText)
mapText = OverlayText("Deep track dug on Mars just after human colonization")
mapText:setLocation(0,-fontH*2)
mapText:setColor(1,1,1)
326
CHAPTER 20. HOVERJET RACING
mapTexts:add(mapText)
mapText = OverlayText("ARROW KEYS to change | ENTER to select")
mapText:setLocation(0,-fontH*4)
mapText:setColor(0,1,1)
mapTexts:add(mapText)
mapTexts:setLocation(w*0.5,h*0.25)
addToOverlay(mapTexts)
mapTexts:hide()
MENU.GUI.mapTexts[5] = mapTexts
----SCENERY (END)
MENU.setupCamera()
MENU.setupPointer(zip)
MENU.setupHelp()
zip:delete()
----MODE
MENU.GUI.mode = -1 ---> OPTIONS
end
----FINALIZATION---function MENU.final()
ALL.stopSoundtrack(MENU.GUI)
MENU.GUI = nil
hidePointer()
disableFog()
empty()
emptyOverlay()
end
----KEYBOARD---function MENU.keyDown(key)
if MENU.GUI.mode == -1 then
local key0 = string.byte("0")
if key == key0+3 then
releaseKey(key)
GAME.modelIndex = MENU.GUI.modelIndex
GAME.mapIndex = MENU.GUI.mapIndex
setScene(Scene(GAME.init,GAME.update,GAME.final,GAME.keyDown))
elseif key >= key0 and key <= key0+2 then
releaseKey(key)
hidePointer()
MENU.GUI.mode = key-key0 ---> Instructions
MENU.GUI.optionsTexts:hide()
MENU.GUI.logoSprite:hide()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:hide()
if key == key0 then
MENU.GUI.instructionTexts:show()
elseif key == key0+1 then
MENU.GUI.jetModels[MENU.GUI.modelIndex]:show()
327
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:show()
elseif key == key0+2 then
MENU.GUI.maps[MENU.GUI.mapIndex]:show()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:show()
end
elseif key == key0+9 then
releaseKey(key)
exit()
end
elseif MENU.GUI.mode == 0 then
if key == 13 then ---> RETURN
releaseKey(key)
MENU.GUI.mode = -1
showPointer()
MENU.GUI.optionsTexts:show()
MENU.GUI.logoSprite:show()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:show()
MENU.GUI.instructionTexts:hide()
end
elseif MENU.GUI.mode == 1 then
if key == 13 then ---> RETURN
releaseKey(key)
MENU.GUI.mode = -1
showPointer()
MENU.GUI.optionsTexts:show()
MENU.GUI.logoSprite:show()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:show()
MENU.GUI.jetModels[MENU.GUI.modelIndex]:hide()
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:hide()
elseif key == 37 or key == 40 then ---> LEFT
releaseKey(key)
MENU.GUI.jetModels[MENU.GUI.modelIndex]:hide()
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:hide()
if MENU.GUI.modelIndex == 1 then
MENU.GUI.modelIndex = table.getn(MENU.GUI.jetModels)
else
MENU.GUI.modelIndex = MENU.GUI.modelIndex-1
end
MENU.GUI.jetModels[MENU.GUI.modelIndex]:show()
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:show()
elseif key == 39 or key == 38 then ---> RIGHT
releaseKey(key)
MENU.GUI.jetModels[MENU.GUI.modelIndex]:hide()
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:hide()
if MENU.GUI.modelIndex == table.getn(MENU.GUI.jetModels) then
MENU.GUI.modelIndex = 1
else
MENU.GUI.modelIndex = MENU.GUI.modelIndex+1
end
MENU.GUI.jetModels[MENU.GUI.modelIndex]:show()
328
CHAPTER 20. HOVERJET RACING
MENU.GUI.dataTexts[MENU.GUI.modelIndex]:show()
end
elseif MENU.GUI.mode == 2 then
if key == 13 then ---> RETURN
releaseKey(key)
MENU.GUI.mode = -1
showPointer()
MENU.GUI.optionsTexts:show()
MENU.GUI.logoSprite:show()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:show()
MENU.GUI.maps[MENU.GUI.mapIndex]:hide()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:hide()
elseif key == 37 or key == 40 then ---> LEFT
releaseKey(key)
MENU.GUI.maps[MENU.GUI.mapIndex]:hide()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:hide()
if MENU.GUI.mapIndex == 1 then
MENU.GUI.mapIndex = table.getn(MENU.GUI.maps)
else
MENU.GUI.mapIndex = MENU.GUI.mapIndex-1
end
MENU.GUI.maps[MENU.GUI.mapIndex]:show()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:show()
elseif key == 39 or key == 38 then ---> RIGHT
releaseKey(key)
MENU.GUI.maps[MENU.GUI.mapIndex]:hide()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:hide()
if MENU.GUI.mapIndex == table.getn(MENU.GUI.maps) then
MENU.GUI.mapIndex = 1
else
MENU.GUI.mapIndex = MENU.GUI.mapIndex+1
end
MENU.GUI.maps[MENU.GUI.mapIndex]:show()
MENU.GUI.mapTexts[MENU.GUI.mapIndex]:show()
end
end
end
----UPDATE SUPPORT---function MENU.rotateCamera(timeStep)
local camera = getCamera()
local rotSpeed = -math.pi*0.083333 ---> 1/12
camera:rotStanding(rotSpeed*timeStep)
end
function MENU.animateLogo(timeStep)
local GUI = MENU.GUI
local logoSprite = GUI.logoSprite
local credits = GUI.credits
329
local creditTexts = credits.texts
local creditTextTime = credits.time
local creditTextIndex = credits.index
local creditTextStatus = credits.status
local logoSpeedX, logoSpeedY = 200, 400
local logoSize = logoSprite:getDimension()
local w, h = getDimension()
local markX = (w-320)*0.5
local markY = (h+logoSize-480)*0.5
local logoX, logoY = logoSprite:getLocation()
if timeStep > 0.1 then
timeStep = 0.1
end
if logoY > markY then
logoY = logoY-timeStep*logoSpeedY
if logoY < markY then
logoY = markY
end
MENU.GUI.optionsTexts:setLocation(w*0.5,logoY+(h-logoSize)*0.5)
logoSprite:setLocation(logoX,logoY)
elseif logoY == markY then
logoSprite:setLocation(logoX,markY-1)
else
if logoX > markX then
logoX = logoX-timeStep*logoSpeedX
if logoX < markX then
logoX = markX
end
logoSprite:setLocation(logoX,logoY)
elseif logoX == markX then
hideHelp()
logoSprite:setLocation(logoX-1,logoY)
creditTexts[creditTextIndex]:show()
creditTexts[creditTextIndex]:setLocation(0,-h*0.5)
else
local creditText = creditTexts[creditTextIndex]
if creditTextStatus == 0 then --> FADE_IN
local creditX, creditY = creditText:getLocation()
creditY = creditY+timeStep*logoSpeedX
if creditY >= 0 then
creditText:setLocation(creditX,0)
credits.time = getElapsedTime()
credits.status = 1 --> WAIT
else
creditText:setLocation(creditX,creditY)
end
elseif creditTextStatus == 1 then --> WAIT
local diff = getElapsedTime()-creditTextTime
if diff > creditText:getCount()*0.5 then
credits.status = 2 --> FADE_OUT
330
CHAPTER 20. HOVERJET RACING
end
elseif creditTextStatus == 2 then --> FADE_OUT
local creditX, creditY = creditText:getLocation()
creditY = creditY-timeStep*logoSpeedY
if creditY <= -h*0.5 then
creditText:setLocation(creditX,-h*0.5)
creditText:hide()
credits.index = creditTextIndex+1
if credits.index > table.getn(creditTexts) then
credits.index = 1
end
creditTexts[credits.index]:show()
credits.status = 0 --> FADE_IN
else
creditText:setLocation(creditX,creditY)
end
end
end
end
end
----UPDATE---function MENU.update()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
MENU.rotateCamera(timeStep)
local mode = MENU.GUI.mode
if mode == -1 then ---> OPTIONS
MENU.animateLogo(timeStep)
local dx, dy = getMouseMove()
movePointer(dx,dy,getDimension())
local GUI = MENU.GUI
local text = GUI.optionsTexts:getTextAt(getPointerLocation())
local oldPointerText = GUI.oldPointerText
if text then
if text ~= oldPointerText then
if oldPointerText then
oldPointerText:setColor(1,1,0)
end
text:setColor(1,0.25,0.25)
GUI.oldPointerText = text
end
if isMouseLeftPressed() then
if GUI.selected == nil then
GUI.selected = true
MENU.keyDown(string.byte(text:getText(),2))
end
else
GUI.selected = nil
331
end
elseif oldPointerText then
oldPointerText:setColor(1,1,0)
GUI.oldPointerText = nil
end
elseif mode == 1 then ---> Vehicle
local model = MENU.GUI.jetModels[MENU.GUI.modelIndex]
local camera = getCamera()
model:reset()
model:pitch(math.pi*0.167)
model:setPosition(camera:getPosition())
local scale = 15
local tx,ty,tz = camera:getViewDirection()
model:move(scale*tx,scale*ty,scale*tz)
elseif mode == 2 then ---> Track
end
end
----SCENE SETUP---setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
332
CHAPTER 20. HOVERJET RACING
Chapter 21
Zeke on Your Six!
--[[
Z E K E
O N
Y O U R
S I X !
A Simple Flight Simulator
P R E V I E W
Questions? Contact leo <boselli@uno.it>
--]]
----MODULES---MENU = {}
ALL = {}
SIM = {}
----FLIGHT SIM. Flight Model from:
----http://www.web-discovery.net/aerodynamics1.asp
----UNITS: length in feet, time in seconds, mass in slugs
----(1 slug = 32.2 lbs)
function SIM.setPosition(self,x,y,z)
self.X = z*3.2808
self.Y = -x*3.2808
self.Z = -y*3.2808
end
function SIM.simulateAirplane(self,timeStep)
-- to be optimized
local deltaT = 0.001
steps = math.ceil(timeStep/deltaT)
local t11,t12,t13,t21,t22,t23,t31,t32,t33
for step = 1, steps do
local P, Q, R = self.P, self.Q, self.R
333
334
CHAPTER 21. ZEKE ON YOUR SIX!
local U, V, W = self.U, self.V, self.W
local invU
if U == 0 then invU = 0 else invU = 1/U end
local alpha = math.atan(W*invU) ---> angle of attack
local beta = math.atan(V*invU) ---> angle of sideslip
local elevator = self.elevator
local aileron = self.aileron
local rudder = self.rudder
local liftCoefficient = self.CLo + self.CLa*alpha + self.CLde*elevator
local dragCoefficient = self.CDo + self.CDa*math.abs(alpha) + self.CDde*math.abs(el
local sideCoefficient = self.CYb*beta+self.CYdr*rudder
local rho = 0.0025 ---> air density (sl/ft^3)
local speed2 = U*U+V*V+W*W
local qbarS = rho*speed2*self.S*0.5 ---> dynamic pressure
local lift = liftCoefficient*qbarS
local drag = dragCoefficient*qbarS
local sideForce = sideCoefficient*qbarS
local sinAlpha = math.sin(alpha)
local cosAlpha = math.cos(alpha)
local Fx = lift*sinAlpha-drag*cosAlpha+self.thrust
local Fy = sideForce
local Fz = -lift*cosAlpha-drag*sinAlpha
local theta = self.theta
local phi = self.phi
local cosTheta = math.cos(theta)
local sinTheta = math.sin(theta)
local cosPhi = math.cos(phi)
local sinPhi = math.sin(phi)
local invMass = self.invMass
local g = 32.2 ---> ft/s^2
local Ua = V*R-W*Q-g*sinTheta+Fx*invMass
local Va = W*P-U*R+g*sinPhi*cosTheta+Fy*invMass
local Wa = U*Q-V*P+g*cosPhi*cosTheta+Fz*invMass
U = U+Ua*deltaT
V = V+Va*deltaT
W = W+Wa*deltaT
self.U, self.V, self.W = U, V, W
local invSpeed
if speed2 == 0 then invSpeed = 0 else invSpeed = 1/math.sqrt(speed2) end
local c = self.c
local b = self.b
local L = (self.CLb*beta+self.CLp*P*b*invSpeed*0.5+self.CLr*R*b*invSpeed*0.5+self.C
local M = (self.CMo+self.CMa*alpha+self.CMq*Q*c*invSpeed*0.5+self.CMde*elevator)*qb
local N = (self.CNb*beta+self.CNp*P*b*invSpeed*0.5+self.CNr*R*b*invSpeed*0.5+self.C
local Ixx = self.Ixx
local Iyy = self.Iyy
local Izz = self.Izz
local Ixz = self.Ixz
local Ixz2 = Ixz*Ixz
local cc0 = 1/(Ixx*Izz-Ixz2)
335
local cc1 = cc0*((Iyy-Izz)*Izz-Ixz2)
local cc2 = cc0*Ixz*(Ixx-Iyy+Izz)
local cc3 = cc0*Izz
local cc4 = cc0*Ixz
local cc7 = 1/Iyy
local cc5 = cc7*(Izz-Ixx)
local cc6 = cc7*Ixz
local cc8 = cc0*((Ixx-Iyy)*Ixx+Ixz2)
local cc9 = cc0*Ixz*(Iyy-Izz-Ixx)
local cc10 = cc0*Ixx
local Pa = (cc1*R+cc2*P)*Q+cc3*L+cc4*N ---> angular acc. (rad/s^2)
local Qa = cc5*R*P+cc6*(R*R-P*P)+cc7*M ---> angular acc. (rad/s^2)
local Ra = (cc8*P+cc9*R)*Q+cc4*L+cc10*N ---> angular acc. (rad/s^2)
local Q0 = self.Q0
local Q1 = self.Q1
local Q2 = self.Q2
local Q3 = self.Q3
local invNorm = 1/math.sqrt(Q0*Q0+Q1*Q1+Q2*Q2+Q3*Q3)
Q0 = Q0*invNorm
Q1 = Q1*invNorm
Q2 = Q2*invNorm
Q3 = Q3*invNorm
local Q0Q0 = Q0*Q0
local Q1Q1 = Q1*Q1
local Q2Q2 = Q2*Q2
local Q3Q3 = Q3*Q3
local Q0Q1 = Q0*Q1
local Q0Q2 = Q0*Q2
local Q0Q3 = Q0*Q3
local Q1Q2 = Q1*Q2
local Q1Q3 = Q1*Q3
local Q2Q3 = Q2*Q3
local Qdot0 = -0.5*(Q1*P+Q2*Q+Q3*R)
local Qdot1 = 0.5*(Q0*P+Q2*R-Q3*Q)
local Qdot2 = 0.5*(Q0*Q+Q3*P-Q1*R)
local Qdot3 = 0.5*(Q0*R+Q1*Q-Q2*P)
Q0 = Q0+Qdot0*deltaT
Q1 = Q1+Qdot1*deltaT
Q2 = Q2+Qdot2*deltaT
Q3 = Q3+Qdot3*deltaT
self.Q0, self.Q1, self.Q2, self.Q3 = Q0, Q1, Q2, Q3
t11 = Q0Q0+Q1Q1-Q2Q2-Q3Q3
t21 = 2*(Q1Q2+Q0Q3)
t31 = 2*(Q1Q3-Q0Q2)
t12 = 2*(Q1Q2-Q0Q3)
t22 = Q0Q0-Q1Q1+Q2Q2-Q3Q3
t32 = 2*(Q2Q3+Q0Q1)
t13 = 2*(Q1Q3+Q0Q2)
t23 = 2*(Q2Q3-Q0Q1)
t33 = Q0Q0-Q1Q1-Q2Q2+Q3Q3
336
CHAPTER 21. ZEKE ON YOUR SIX!
P = P+Pa*deltaT
Q = Q+Qa*deltaT
R = R+Ra*deltaT
self.P, self.Q, self.R = P, Q, R
local temp = -t31
if temp < -1 then
temp = -1
elseif
temp > 1 then
temp = 1
end
self.theta = math.asin(temp)
self.phi = math.atan2(t32,t33)
local Uw = U*t11+V*t12+W*t13
local Vw = U*t21+V*t22+W*t23
local Ww = U*t31+V*t32+W*t33
self.X = self.X+Uw*deltaT
self.Y = self.Y+Vw*deltaT
self.Z = self.Z+Ww*deltaT
end
if steps ~= 0 then
local transform = self.transform
transform:setSideDirection(t22,t32,-t12)
transform:setUpDirection(t23,t33,-t13)
transform:setViewDirection(-t21,-t31,t11)
transform:setPosition(-self.Y*0.3048,-self.Z*0.3048,self.X*0.3048)
end
end
function SIM.createAirplane()
local airplane = { ---> Data from A4-sparrow
simulateAirplane = SIM.simulateAirplane, ---> simulator hook
setPosition = SIM.setPosition, ---> simulator hook
transform = Reference(), ---> the transform
invMass = 1/546, ---> inverse of mass (1/sl)
theta = 0, phi = 0, ---> angular orientation (rad)
X = 0, Y = 0, Z = 0, ---> position in feet
U = 400, V = 0, W = 0, ---> linear velocity (ft/s)
P = 0, Q = 0, R = 0, ---> angular velocity (rad/s)
Q0 = 1, Q1 = 0, Q2 = 0, Q3 = 0, ---> quaternion
thrust = 3000, maxThrust = 5000, ---> thrust (sl*ft/s^2)
elevator = 0, maxElevator = 0.5236, ---> elevator angle (rad)
aileron = 0, maxAileron = 0.5236, ---> ailerons angle (rad)
rudder = 0, maxRudder = 0.2618, ---> rudder angle (rad)
CLo = 0.28, ---> reference lift at zero angle of attack
CLa = 3.45, ---> lift curve slope
CLde = 0.36, ---> lift due to elevator
CDo = 0.03, ---> reference drag at zero angle of attack
CDa = 0.3, ---> drag curve slope
CDde = 0.04, ---> drag due to elevator
CYb = -0.98, ---> side force due to sideslip
337
CYdr = 0.17, ---> side force due to rudder
CLb = -0.12, ---> dihedral effect
CLp = -0.26, ---> roll damping
CLr = 0.14, ---> roll due to yaw rate
CLda = 0.08, ---> roll due to aileron
CLdr = -0.105, ---> roll due to rudder
CMo = 0.0, ---> pitch moment coefficient
CMq = -3.6, ---> pitch moment coefficient due to pitch rate
CMa = -0.38, ---> pitch moment coefficient due to angle of attack
CMda = -1.1, ---> pitch moment coefficient due to angle of attack rate
CMde = -0.5, ---> pitch moment coefficient due to elevator
CNb = 0.25, ---> weather cocking stability
CNp = 0.022, ---> rudder adverse yaw
CNr = -0.35, ---> yaw damping
CNda = 0.06, ---> yaw due to aileron
CNdr = 0.032, ---> yaw due to rudder
Ixx = 8090, ---> roll inertia in slug/feet^2
Iyy = 25900, ---> pitch inertia in slug/feet^2
Izz = 29200, ---> yaw inertia in slug/feet^2
Ixz = 1300, ---> overall inertia along the trasversal Y-Z in slug/feet^2
S = 260.0, ---> wing surface area (ft^2)
b = 27.5, ---> wing span in feet
c = 10.8 ---> chord length in feet
}
return airplane
end
-------HUD
-------HUD SUPPORT---function SIM.setupMap(zip,mapName,tiled)
local mapImage = zip:getImage(mapName)
mapImage:convertToRGB()
local MAP_SIZE = 170
SIM.HUD.isMapShown = true
SIM.HUD.mapSprite =
OverlaySprite(MAP_SIZE,MAP_SIZE,Texture(mapImage,tiled))
local mapSprite = SIM.HUD.mapSprite
mapImage:delete()
mapSprite:setLayer(0)
mapSprite:setColor(1,0.9,0.5,0.5)
local W, H = getDimension()
mapSprite:setLocation(W-MAP_SIZE,H-MAP_SIZE)
addToOverlay(SIM.HUD.mapSprite)
local markImage = zip:getImage("dot.png")
local markSize = markImage:getDimension()
markImage:addAlpha(markImage)
338
CHAPTER 21. ZEKE ON YOUR SIX!
local markSprite = OverlaySprite(markSize,markSize,Texture(markImage),true)
markImage:delete()
SIM.HUD.markSprite = markSprite
markSprite:setLayer(-2)
markSprite:setColor(1,1,1)
addToOverlay(markSprite)
end
function SIM.setupStick(zip,stickName)
local stickImage = zip:getImage(stickName)
stickImage:addAlpha(stickImage)
local MAP_SIZE = 170
local STICK_SIZE = 128
SIM.HUD.isStickShown = true
SIM.HUD.stickSprite =
OverlaySprite(STICK_SIZE,STICK_SIZE,Texture(stickImage),true)
local stickSprite = SIM.HUD.stickSprite
stickImage:delete()
stickSprite:setLayer(0)
stickSprite:setColor(1,0.9,0.5)
local W, H = getDimension()
stickSprite:setLocation(W-MAP_SIZE,STICK_SIZE)
addToOverlay(SIM.HUD.stickSprite)
local posImage = zip:getImage("dot.png")
local posSize = posImage:getDimension()
posImage:addAlpha(posImage)
local posSprite = OverlaySprite(posSize,posSize,Texture(posImage),true)
posImage:delete()
SIM.HUD.posSprite = posSprite
posSprite:setLayer(-2)
posSprite:setColor(1,0,0)
addToOverlay(posSprite)
end
function SIM.setupHud(theScore,len,offset,texture,u0,v0,u1,v1)
local w, h = getDimension()
local w2, h2 = w*0.5, h*0.5
local sw, sh = 64, 32
local sw2, sh2 = sw*0.5, sh*0.5
local nw, nh = 16, 16
local nw2, nh2 = nw*0.5, nh*0.5
local sprite = OverlaySprite(sw,sh,texture,true)
sprite:setTextureCoord(u0,v0,u1,v1)
sprite:setLocation(w2+offset,h-sh2)
sprite:setColor(0.75,0.75,0)
addToOverlay(sprite)
local offsetX, offsetY = w2+offset+nw2*(len+1), h-sh2-nh2-nh
for ct = 1, len do
theScore[ct] = OverlaySprite(nw,nh,texture,true)
local number = theScore[ct]
339
number:setTextureCoord(0,0.75,0.25,1)
number:setLocation(offsetX-nw*ct,offsetY)
number:setColor(1,1,0)
addToOverlay(number)
end
end
function SIM.setHudValue(theScore,chars,value)
local theString = string.format("%d",value)
local len = math.min(string.len(theString),chars)
for ct = 1, len do
local byte = string.byte(theString,ct)-string.byte("0")
local u, v = math.mod(byte,4)*0.25, math.floor(byte*0.25)*0.25
theScore[len-ct+1]:setTextureCoord(u,0.75-v,u+0.25,1-v)
end
for ct = len+1, chars do
theScore[ct]:setTextureCoord(0,0.75,0.25,1)
end
end
function SIM.setupHuds(zip)
local scoreImage = zip:getImage("numbers.png")
local texture = Texture(scoreImage)
scoreImage:delete()
local setupScore = SIM.setupHud
SIM.HUD.maxhHud = {}
setupScore(SIM.HUD.maxhHud,4,-160,texture,0,0,0.5,0.25)
SIM.HUD.heightHud = {}
setupScore(SIM.HUD.heightHud,4,0,texture,0.5,0,1,0.25)
SIM.HUD.speedHud = {}
setupScore(SIM.HUD.speedHud,3,160,texture,0.5,0.25,1,0.5)
SIM.setHudValue(SIM.HUD.maxhHud,4,0)
SIM.setHudValue(SIM.HUD.heightHud,4,0)
SIM.setHudValue(SIM.HUD.speedHud,3,0)
end
-------SIM
-------INITIALIZATION---function SIM.init()
cloudsList = {}
treesList = {}
----CAMERA---setAmbient(0.3,0.3,0.3)
local MAX_DIST = 3000
setPerspective(60,1,MAX_DIST)
local fogColor = {0.475,0.431,0.451}
enableFog(MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
340
CHAPTER 21. ZEKE ON YOUR SIX!
local camera = getCamera()
camera:reset()
empty()
----ZIP---local zip = Zip("ZekeOnYourSix.dat")
----SKYBOX---local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
----MOON---local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,MAX_DIST-500
)
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("coarse.jpg",true))
material:setGlossTexture(zip:getTexture("detail.jpg",true))
patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,128)
patches:setShadowFadeDistance(100)
patches:setShadowOffset(0.25)
patches:setShadowed()
setTerrain(patches)
heightImage:delete()
colorImage:delete()
material:delete()
----SOUNDS---local engineSample = zip:getSample3D("motor.wav");
engineSample:setLooping(true)
341
engineSample:setVolume(255)
engineSample:setMinDistance(100)
----SHADOW---local shadowImage = zip:getImage("shadow.png")
shadowImage:convertTo111A()
local shadowTexture = Texture(shadowImage)
shadowImage:delete()
----ENVIRONMENT---local envtext = zip:getTexture("environ1.jpg")
----ZERO---zero = zip:getMeshes("zero.3ds")
local zeroMesh = zero:getFirstMesh()
while zeroMesh do
local zmat = zeroMesh:getMaterial()
zmat:setAmbient(0.7,0.7,0.7)
zmat:setDiffuse(1,1,1)
zmat:setSpecular(1,1,0)
zmat:setShininess(96)
zmat:setEnvironmentTexture(envtext,0.25)
zeroMesh = zero:getNextMesh()
end
zero:move(0,30*32,140)
addObject(zero)
addShadow(Shadow(zero,6,6,shadowTexture))
zeroSource = Source(engineSample,zero,true)
addSource(zeroSource)
----EMITTER---local smokeImage = zip:getImage("smoke.png")
smokeImage:convertTo111A()
local smokeTexture = Texture(smokeImage)
smokeImage:delete()
smokeEmitter = Emitter(150,2,100,false)
smokeEmitter:setTexture(smokeTexture,1)
smokeEmitter:setVelocity(0,0,0, 1)
smokeEmitter:setColor(0.5,0.5,0.5,1, 1,1,1,0)
smokeEmitter:setSize(0.75,10)
smokeEmitter:setGravity(0,0,0, 0,0,0)
smokeEmitter:reset()
addObject(smokeEmitter)
smokeTexture:delete()
----HUD---SIM.HUD = {}
SIM.HUD.maxHeight = 0
SIM.setupHuds(zip)
SIM.setupMap(zip,"map.png",true)
SIM.setupStick(zip,"stick.png")
----AIRPLANE---airplane = SIM.createAirplane()
airplane:setPosition(0,30*32,140)
offset = {view = 2, dist = 75, height = 5, forward = -75, up = 15, side = 0}
342
CHAPTER 21. ZEKE ON YOUR SIX!
----HELP---local help = {
"[
MOUSE ] Stick Control",
"[LEFT|RIGHT] Ailerons Control",
"[ UP|DOWN ] Elevator Control",
"[ DEL|END ] Rudder Control",
"[
0
] Zero Controls",
"[NEXT|PRIOR] Thrust",
"[
S
] Show/Hide Smoke",
"[ * | /
] Zoom View",
"[ + | ] Incline View",
"[ 1 - 9
] Change View",
"[ENTER] Back to menu",
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
hideHelp()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function SIM.final()
SIM.HUD = nil
offset = nil
zero = nil
smokeEmitter = nil
airplane = nil
patches = nil
----EMPTY WORLD---disableFog()
emptyOverlay()
empty()
end
----KEYDOWN---function SIM.keyDown(key)
if key >= 97 and key <= 105 then ---> VK_NUMPAD1-9
if key == 97 then
releaseKey(97)
offset.view = 1
offset.forward = -offset.dist*0.707
offset.side = offset.dist*0.707
offset.up = offset.height
elseif key == 98 then
releaseKey(98)
offset.view = 2
offset.forward = -offset.dist
343
offset.side = 0
offset.up = offset.height
elseif key == 99 then
releaseKey(99)
offset.view = 3
offset.forward = -offset.dist*0.707
offset.side = -offset.dist*0.707
offset.up = offset.height
elseif key == 100 then
releaseKey(100)
offset.view = 4
offset.forward = 0
offset.side = offset.dist
offset.up = offset.height
elseif key == 101 then
releaseKey(101)
if offset.view == 5 then
offset.view = 0
offset.up = -offset.dist
else
offset.view = 5
offset.up = offset.dist
end
offset.forward = 0
offset.side = 0
elseif key == 102 then
releaseKey(102)
offset.view = 6
offset.forward = 0
offset.side = -offset.dist
offset.up = offset.height
elseif key == 103 then
releaseKey(103)
offset.view = 7
offset.forward = offset.dist*0.707
offset.side = offset.dist*0.707
offset.up = offset.height
elseif key == 104 then
releaseKey(104)
offset.view = 8
offset.forward = offset.dist
offset.side = 0
offset.up = offset.height
elseif key == 105 then
releaseKey(105)
offset.view = 9
offset.forward = offset.dist*0.707
offset.side = -offset.dist*0.707
offset.up = offset.height
end
344
CHAPTER 21. ZEKE ON YOUR SIX!
elseif key == string.byte("S") then
if smokeEmitter:isVisible() then
smokeEmitter:hide()
else
smokeEmitter:show()
end
elseif key == 13 then
releaseKey(13)
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
end
end
----LOOP---function SIM.update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
----MOVE CONTROLS---if isMouseLeftPressed() or isKeyPressed(33) then --> VK_PRIOR
local thrust = airplane.thrust
local maxThrust = airplane.maxThrust
thrust = thrust+timeStep*maxThrust*0.1
if(thrust > maxThrust) then
thrust = maxThrust
end
airplane.thrust = thrust
elseif isMouseRightPressed() or isKeyPressed(34) then --> VK_NEXT
local thrust = airplane.thrust
local maxThrust = airplane.maxThrust
thrust = thrust-timeStep*maxThrust*0.1
if(thrust < 0) then
thrust = 0
end
airplane.thrust = thrust
end
local dx, dy = getMouseMove()
if dx ~= 0 or dy ~=0 then
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator*0.5
elevator = elevator+dy*maxElevator*0.0015
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
local aileron = airplane.aileron
local maxAileron = airplane.maxAileron*0.25
aileron = aileron+dx*maxAileron*0.0015
if aileron > maxAileron then
345
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
end
if isKeyPressed(38) then --> VK_UP
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator
elevator = elevator+timeStep*maxElevator*0.2
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
elseif isKeyPressed(40) then --> VK_DOWN
local elevator = airplane.elevator
local maxElevator = airplane.maxElevator
elevator = elevator-timeStep*maxElevator*0.2
if elevator > maxElevator then
elevator = maxElevator
elseif elevator < -maxElevator then
elevator = -maxElevator
end
airplane.elevator = elevator
end
if isKeyPressed(37) then --> VK_LEFT
local aileron = airplane.aileron
local maxAileron = airplane.maxAileron
aileron = aileron-timeStep*maxAileron*0.2
if aileron > maxAileron then
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
elseif isKeyPressed(39) then --> VK_RIGHT
local aileron = airplane.aileron
local maxAileron = airplane.maxAileron
aileron = aileron+timeStep*maxAileron*0.2
if aileron > maxAileron then
aileron = maxAileron
elseif aileron < -maxAileron then
aileron = -maxAileron
end
airplane.aileron = aileron
end
if isKeyPressed(46) then --> VK_DEL
local rudder = airplane.rudder
346
CHAPTER 21. ZEKE ON YOUR SIX!
local maxRudder = airplane.maxRudder
rudder = rudder+timeStep*maxRudder*0.2
if rudder > maxRudder then
rudder = maxRudder
end
airplane.rudder = rudder
elseif isKeyPressed(35) then --> VK_END
local rudder = airplane.rudder
local maxRudder = airplane.maxRudder
rudder = rudder-timeStep*maxRudder*0.2
if rudder < -maxRudder then
rudder = -maxRudder
end
airplane.rudder = rudder
end
if isKeyPressed(107) then ---> VK_ADD
if offset.view ~= 0 and offset.view ~= 5 then
local scale = 1+timeStep
offset.height = offset.height*scale
if offset.height <= 500 then
offset.up = offset.height
else
offset.height = 500
end
end
elseif isKeyPressed(109) then ---> VK_SUBTRACT
if offset.view ~= 0 and offset.view ~= 5 then
local scale = 1-timeStep
offset.height = offset.height*scale
if offset.height >= 0 then
offset.up = offset.height
else
offset.height = 0
end
end
end
if isKeyPressed(111) then ---> VK_DIVIDE
local scale = 1-timeStep
offset.dist = offset.dist*scale
if offset.dist >= 20 then
offset.forward = offset.forward*scale
offset.side = offset.side*scale
offset.up = offset.up*scale
else
offset.dist = 20
end
elseif isKeyPressed(106) then ---> VK_MULTIPLY
local scale = 1+timeStep
offset.dist = offset.dist*scale
if offset.dist <= 500 then
347
offset.forward = offset.forward*scale
offset.side = offset.side*scale
offset.up = offset.up*scale
else
offset.dist = 500
end
end
if isKeyPressed(96) then ---> VK_NUMPAD0
airplane.elevator = 0
airplane.rudder = 0
airplane.aileron = 0
end
----SIMULATION---airplane:simulateAirplane(timeStep)
zero:set(airplane.transform)
local posX, posY, posZ = zero:getPosition()
if posY > SIM.HUD.maxHeight then
SIM.HUD.maxHeight = posY
SIM.setHudValue(SIM.HUD.maxhHud,4,posY)
end
SIM.setHudValue(SIM.HUD.heightHud,4,posY)
smokeEmitter:set(zero)
smokeEmitter:moveForward(-5)
camera:set(zero)
camera:moveForward(offset.forward)
camera:moveSide(offset.side)
camera:moveUp(offset.up)
local camX, camY, camZ = camera:getPosition()
local camH = patches:getHeightAt(camX,camZ)+10
if camY < camH then camera:setPosition(camX,camH,camZ) end
local posX, posY, posZ = zero:getPosition()
camera:pointTo(posX,posY,posZ)
local speedX = airplane.U*1.1
local speedY = airplane.V*1.1
local speedZ = airplane.W*1.1
local speed = math.sqrt(speedX*speedX+speedY*speedY+speedZ*speedZ)
SIM.setHudValue(SIM.HUD.speedHud,3,speed)
----UPDATE MAP---if SIM.HUD.isMapShown then
local horViewX,horViewZ = camera:getHorizontalView()
local mapSprite = SIM.HUD.mapSprite
local TEX_SCALE = 6.510417e-5 ---> 1/(512*30)
if horViewZ ~= 0 then
mapSprite:setRotation(math.atan2(horViewX,horViewZ))
local markSprite = SIM.HUD.markSprite
local W, H = getDimension()
local MAP_SIZE = 170
local SCALE = MAP_SIZE*TEX_SCALE
local CENTER_X = W-MAP_SIZE
local CENTER_Z = H-MAP_SIZE
348
CHAPTER 21. ZEKE ON YOUR SIX!
local x,y,z = zero:getPosition()
x, z = (camX-x)*SCALE, (z-camZ)*SCALE
if x >= -85 and x <= 85 and z >= -85 and z <= 85 then
local dx = x*horViewZ+z*horViewX
local dz = -x*horViewX+z*horViewZ
markSprite:setLocation(CENTER_X+dx,CENTER_Z+dz)
markSprite:show()
else
markSprite:hide()
end
end
local texX = camX*TEX_SCALE+0.5
local texZ = camZ*TEX_SCALE+0.5
mapSprite:setTextureCoord(texX+0.5,texZ-0.5,texX-0.5,texZ+0.5)
end
----UPDATE STICK---if SIM.HUD.isStickShown then
local posSprite = SIM.HUD.posSprite
local W, H = getDimension()
local MAP_SIZE = 170
local STICK_SIZE = 128
local CENTER_X = W-MAP_SIZE
local CENTER_Z = STICK_SIZE
local stickX = airplane.aileron/airplane.maxAileron*STICK_SIZE*2
local stickZ = airplane.elevator/airplane.maxElevator*STICK_SIZE
posSprite:setLocation(CENTER_X+stickX,CENTER_Z+stickZ)
end
----CRASH---if posY < patches:getHeightAt(posX,posZ) then
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
end
end
----INITIALIZATION SUPPORT---function ALL.showSplashImage(zip)
local splashImage = zip:getImage("logo.jpg")
showSplashImage(splashImage)
splashImage:delete()
end
function ALL.playSoundtrack(zip,fileName,MODULE)
MODULE.soundTrack = zip:getMusic(fileName)
local soundTrack = MODULE.soundTrack
soundTrack:setVolume(255)
soundTrack:setLooping(1)
soundTrack:play()
end
function ALL.stopSoundtrack(MODULE)
349
local soundTrack = MODULE.soundTrack
if soundTrack then
soundTrack:stop()
soundTrack:delete()
MODULE.soundTrack = nil
end
end
function ALL.createSkybox(zip)
local txtNames = {"Top", "Left", "Front", "Right", "Back"}
local skyTxt = {}
for txtIdx = 1, table.getn(txtNames) do
skyTxt[txtIdx] = zip:getTexture("skybox"..txtNames[txtIdx]..".jpg")
end
local sky = MirroredSky(skyTxt)
setBackground(sky)
end
function ALL.createSun(zip)
local sun = {
size = 0.32, distance = 3200, texture = zip:getTexture("light.jpg"),
dir = {x = 0.0, y = 0.2588, z = 0.9659},
color = {r = 0.9, g = 0.5, b = 0.2},
flares = {
count = 5, size = 0.128, texture = zip:getTexture("lensflares.png")
}
}
local theSun = Sun(
sun.texture, sun.size, sun.dir.x,sun.dir.y,sun.dir.z,
sun.flares.texture,sun.flares.count,sun.flares.size,
sun.distance
)
theSun:setColor(sun.color.r,sun.color.g,sun.color.b)
setSun(theSun)
end
function ALL.createLevel(zip)
local bsp = zip:getLevel("city.bsx",3)
bsp:setShowUntexturedMeshes()
bsp:setShowUntexturedPatches()
bsp:setDefaultTexture(
zip:getTexture("textures/maxpayne/Brick52a.jpg",1)
)
bsp:setShadowsStatic()
setScenery(bsp)
return bsp
end
-------MENU SCENE
350
CHAPTER 21. ZEKE ON YOUR SIX!
-------INITIALIZATION SUPPORT---function MENU.setupPointer(zip)
local pointerImage = zip:getImage("arrow.png")
local pointerSize = pointerImage:getDimension()
pointerImage:addAlpha(pointerImage)
local pointerSprite = OverlaySprite(
pointerSize,pointerSize,Texture(pointerImage),true
)
pointerImage:delete()
pointerSprite:setLayer(-1)
setPointer(pointerSprite)
local w, h = getDimension()
setPointerLocation(w/2,h/2)
showPointer()
end
function MENU.createLogo(zip)
local logoImage = zip:getImage("logo.jpg")
local alphaImage = zip:getImage("logo.png")
alphaImage:convertTo111A()
logoImage:addAlpha(alphaImage)
alphaImage:delete()
local logoSize = logoImage:getDimension()
MENU.GUI.logoSprite = OverlaySprite(
logoSize,logoSize,Texture(logoImage),true
)
local logoSprite = MENU.GUI.logoSprite
logoImage:delete()
local w, h = getDimension()
logoSprite:setLocation((w-logoSize)/2,h)
addToOverlay(logoSprite)
end
function MENU.createCredits()
local colors = {
{r = 1,
g = 1, b = 0},
{r = 0.75, g = 1, b = 1},
{r = 1,
g = 1, b = 1}
}
local creditStrings = {
{
1, "Z E K E
O N
Y O U R
S I X",
2, "",
3, "Copyright \184 2004",
2, "Leonardo Boselli",
1, "",
3, "A Simple Flight Simulator",
351
2, "",
2, ""
},
{
1, "- Programming & Design -",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
1, "- Scenery & Models -",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
3, "",
3, ""
},
{
3, "Thanks to",
3, "",
2, "Matteo \"Fuzz\" Perenzoni",
3, "",
3, "for fruitful discussions on",
3, "OpenGL and 3D programming.",
3, "",
3, "The sources of his demo for",
3, "the NeHe’s Apocalypse Contest",
3, "were the first building blocks",
3, "of the APOCALYX 3D Engine."
},
{
3, "Thanks to",
3, "",
1, "TeCGraf, PUC-Rio",
3, "for the LUA script language",
2, "www.lua.org",
3, "",
1, "Borland",
3, "for their free C++ compiler",
2, "www.borland.com",
},
{
3, "Thanks to the following sites",
3, "for their useful tutorials",
3, "about game programming",
3, "",
1, "NeHe Productions",
2, "nehe.gamedev.net",
1, "Game Tutorials",
2, "www.gametutorials.com",
1, "SULACO",
2, "www.sulaco.co.za",
3, "",
3, "and",
352
CHAPTER 21. ZEKE ON YOUR SIX!
3, "",
1, "Game Programming Italia",
2, "www.gameprog.it"
},
{
3, "Thanks to these web sites",
3, "for publishing news about game",
3, "development and related stuff",
3, "",
1, "GameDev",
2, "www.gamedev.net",
1, "FlipCode",
2, "www.flipcode.org",
1, "CFXweb",
2, "www.cfxweb.net",
1, "OpenGL.org",
2, "www.opengl.org"
},
{
3, "And, finally, thanks to",
3, "ALL the people of the",
3, "italian newsgroup",
3, "",
1, "it.comp.giochi.sviluppo",
3, "",
3, "",
3, ""
}
}
local font = getMainOverlayFont()
local fontH = font:getHeight()
local w, h = getDimension()
local x = w/2+160
MENU.GUI.optionsTexts = OverlayTexts(font)
local optionsTexts = MENU.GUI.optionsTexts
local offset = 0
local playText
playText = OverlayText("[1] Flight Simulator")
playText:setScale(2)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
offset = offset+fontH*2
optionsTexts:add(playText)
optionsTexts:setLocation(w/2,h)
addToOverlay(optionsTexts)
MENU.GUI.credits = {}
local credits = MENU.GUI.credits
credits.status = 0
credits.index = 1
credits.texts = {}
353
local creditTexts = credits.texts
for creditIdx = 1, table.getn(creditStrings) do
creditTexts[creditIdx] = OverlayTexts(font)
local currentCreditText = creditTexts[creditIdx]
local textLines = creditStrings[creditIdx]
local textLinesCount = table.getn(textLines)
local y = (h+fontH*(textLinesCount-1))/2-240
for textLineIdx = 1, table.getn(textLines), 2 do
local text = OverlayText(textLines[textLineIdx+1])
local colorIdx = textLines[textLineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(x,y)
y = y-fontH
currentCreditText:add(text)
end
currentCreditText:setLocation(0,-h/2)
addToOverlay(currentCreditText)
currentCreditText:hide()
end
end
function MENU.setupCamera()
setAmbient(0.3,0.3,0.3)
local camera = {angleOfView = 60, nearClip = 3, farClip = 3000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
theCamera:reset()
theCamera:move(0,800,0)
end
function MENU.setupHelp()
hideConsole()
showHelpReduced()
local help = {
"Z E K E
O N
Y O U R
S I X",
"A Simple Flight Shooter",
"Questions? Contact leo <boselli@uno.it>",
" ",
"[ 1 ] Simulator",
" ",
"[ F 1 ] Show/Hide Help",
}
setHelp(help)
end
----INITIALIZATION---function MENU.init()
354
CHAPTER 21. ZEKE ON YOUR SIX!
setTitle(" Z E K E
O N
Y O U R
S I X")
MENU.GUI = {}
empty()
emptyOverlay()
local zip = Zip("ZekeOnYourSix.dat")
ALL.playSoundtrack(zip,"intro.mid",MENU.GUI)
ALL.showSplashImage(zip)
MENU.createLogo(zip)
MENU.createCredits()
----SCENERY
----SKYBOX---local MAX_DIST = 3000
local fogColor = {0.475,0.431,0.451}
enableFog(MAX_DIST, fogColor[1],fogColor[2],fogColor[3])
local skytype = "orange_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = HalfSky(skyTxt)
sky:setGroundColor(fogColor[1],fogColor[2],fogColor[3])
setBackground(sky)
----MOON---local moon = Moon(
zip:getTexture("moon.jpg"),0.05,
0,0.342,-0.9397,MAX_DIST-500
)
moon:setColor(0.9,0.9,0.7)
setMoon(moon)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.15,
0,0.342,0.9397,
zip:getTexture("lensflares.png"),
6,0.1,MAX_DIST-500
)
sun:setColor(1,1,0.6)
setSun(sun)
----TERRAIN---local heightImage = zip:getImage("terrain.png")
local colorImage = zip:getImage("terrain.jpg")
local material = Material()
material:setDiffuseTexture(zip:getTexture("coarse.jpg",true))
material:setGlossTexture(zip:getTexture("detail.jpg",true))
local patches = Patches(heightImage,colorImage,material,512*30,32*30,8,96,128)
setTerrain(patches)
heightImage:delete()
355
colorImage:delete()
material:delete()
----SCENERY (END)
MENU.setupCamera()
MENU.setupPointer(zip)
MENU.setupHelp()
zip:delete()
end
----FINALIZATION---function MENU.final()
ALL.stopSoundtrack(MENU.GUI)
MENU.GUI = nil
hidePointer()
disableFog()
emptyOverlay()
empty()
end
----KEYBOARD---function MENU.keyDown(key)
if key == string.byte("1") then
releaseKey(string.byte("1"))
setScene(Scene(SIM.init,SIM.update,SIM.final,SIM.keyDown))
end
end
----UPDATE SUPPORT---function MENU.rotateCamera(timeStep)
local camera = getCamera()
local rotSpeed = -math.pi/12
camera:rotStanding(rotSpeed*timeStep)
end
function MENU.animateLogo(timeStep)
local GUI = MENU.GUI
local logoSprite = GUI.logoSprite
local credits = GUI.credits
local creditTexts = credits.texts
local creditTextTime = credits.time
local creditTextIndex = credits.index
local creditTextStatus = credits.status
local logoSpeedX, logoSpeedY = 200, 400
local logoSize = logoSprite:getDimension()
local w, h = getDimension()
local markX = (w-logoSize-320)/2+logoSize/2
local markY = (h-480)/2+logoSize/2
356
CHAPTER 21. ZEKE ON YOUR SIX!
local logoX, logoY = logoSprite:getLocation()
if logoY > markY then
logoY = logoY-timeStep*logoSpeedY
if logoY < markY then
logoY = markY
end
MENU.GUI.optionsTexts:setLocation(w/2,logoY+h/2)
logoSprite:setLocation(logoX,logoY)
elseif logoY == markY then
logoSprite:setLocation(logoX,markY-1)
else
if logoX > markX then
logoX = logoX-timeStep*logoSpeedX
if logoX < markX then
logoX = markX
end
logoSprite:setLocation(logoX,logoY)
elseif logoX == markX then
hideHelp()
logoSprite:setLocation(logoX-1,logoY)
creditTexts[creditTextIndex]:show()
creditTexts[creditTextIndex]:setLocation(0,-h/2)
else
local creditText = creditTexts[creditTextIndex]
if creditTextStatus == 0 then --> FADE_IN
local creditX, creditY = creditText:getLocation()
creditY = creditY+timeStep*logoSpeedX
if creditY >= 0 then
creditText:setLocation(creditX,0)
credits.time = getElapsedTime()
credits.status = 1 --> WAIT
else
creditText:setLocation(creditX,creditY)
end
elseif creditTextStatus == 1 then --> WAIT
local diff = getElapsedTime()-creditTextTime
if diff > creditText:getCount()*0.5 then
credits.status = 2 --> FADE_OUT
end
elseif creditTextStatus == 2 then --> FADE_OUT
local creditX, creditY = creditText:getLocation()
creditY = creditY-timeStep*logoSpeedY
if creditY <= -h/2 then
creditText:setLocation(creditX,-h/2)
creditText:hide()
credits.index = creditTextIndex+1
if credits.index > table.getn(creditTexts) then
credits.index = 1
end
creditTexts[credits.index]:show()
357
credits.status = 0 --> FADE_IN
else
creditText:setLocation(creditX,creditY)
end
end
end
end
end
----UPDATE---function MENU.update()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
MENU.rotateCamera(timeStep)
MENU.animateLogo(timeStep)
local dx, dy = getMouseMove()
movePointer(dx,dy,getDimension())
local GUI = MENU.GUI
local text = GUI.optionsTexts:getTextAt(getPointerLocation())
local oldPointerText = GUI.oldPointerText
if text then
if text ~= oldPointerText then
if oldPointerText then
oldPointerText:setColor(1,1,0)
end
text:setColor(1,0.25,0.25)
GUI.oldPointerText = text
end
if isMouseLeftPressed() then
if GUI.selected == nil then
GUI.selected = true
MENU.keyDown(string.byte(text:getText(),2))
end
else
GUI.selected = nil
end
elseif oldPointerText then
oldPointerText:setColor(1,1,0)
GUI.oldPointerText = nil
end
end
----SCENE SETUP---setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
358
CHAPTER 21. ZEKE ON YOUR SIX!
Chapter 22
Dragon’s Ride
--[[
D R A G O N ’ S
R I D E
Questions? Contact leo at tetractys@users.sourceforge.net
--]]
----MODULES---EDITOR = {}
GAME = {}
MENU = {}
ALL = {}
----INITIALIZATION SUPPORT---function ALL.showSplashImage(zip)
local splashImage = zip:getImage("logo.jpg")
showSplashImage(splashImage)
splashImage:delete()
end
function ALL.playSoundtrack(zip,fileName,MODULE)
MODULE.soundTrack = zip:getMusic(fileName)
MODULE.soundTrackIsPlaying = true
local soundTrack = MODULE.soundTrack
soundTrack:setLooping(1)
soundTrack:play()
soundTrack:setVolume(16)
end
function ALL.stopSoundtrack(MODULE)
local soundTrack = MODULE.soundTrack
MODULE.soundTrackIsPlaying = false
if soundTrack then
359
360
CHAPTER 22. DRAGON’S RIDE
soundTrack:stop()
soundTrack:delete()
MODULE.soundTrack = nil
end
end
----MODELS SUPPORT---function ALL.createModelAndWeapon(
zip,modelMD2,modelTXT,weaponMD2,weaponTXT,posX,posY,posZ,scale,anim
)
local weapon = ALL.createModel(zip,weaponMD2,weaponTXT,posX,posY,posZ,scale,anim)
local model = ALL.createModel(zip,modelMD2,modelTXT,posX,posY,posZ,scale,anim)
return model,weapon
end
function ALL.createModel(zip,modelMD2,modelTXT,posX,posY,posZ,scale,anim)
local model = zip:getMD2Model(modelMD2,modelTXT)
material = model:getMaterial()
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,1)
material:setShininess(64)
model:rescale(0.04*scale)
model:pitch(-1.5708)
model:move(posX,posY,posZ)
model:setAnimation(anim)
addObject(model)
local shadow = Shadow(model)
shadow:setMaxRadius(model:getMaxRadius()*3)
addShadow(Shadow(model))
return model
end
function ALL.cloneModelAndWeapon(model,weapon,zip,modelTXT,posX,posY,posZ)
local weapon2 = ALL.cloneModel(weapon,nil,nil,posX,posY,posZ)
local model2 = ALL.cloneModel(model,zip,modelTXT,posX,posY,posZ)
return model2, weapon2
end
function ALL.cloneModel(model,zip,modelTXT,posX,posY,posZ)
local model2 = MD2Model(model)
if modelTXT then
local material = Material()
material:setDiffuseTexture(zip:getTexture(modelTXT))
material:setAmbient(1,1,1)
material:setDiffuse(1,1,1)
material:setSpecular(1,1,1)
material:setShininess(64)
model2:setMaterial(material)
361
end
model2:pitch(-1.5708)
model2:move(posX,posY,posZ)
addObject(model2)
addShadow(Shadow(model2))
return model2
end
-------MENU SCENE
-------MENU INITIALIZATION SUPPORT---function MENU.setupPointer(zip)
local pointerImage = zip:getImage("arrow.png")
local pointerSize = pointerImage:getDimension()
pointerImage:addAlpha(pointerImage)
local pointerSprite = OverlaySprite(
pointerSize,pointerSize,Texture(pointerImage),true
)
pointerImage:delete()
pointerSprite:setLayer(-1)
setPointer(pointerSprite)
local w, h = getDimension()
setPointerLocation(w*0.5,h*0.5)
showPointer()
end
function MENU.createLogo(zip)
local logoImage = zip:getImage("logo.jpg")
local alphaImage = zip:getImage("logo.png")
alphaImage:convertTo111A()
logoImage:addAlpha(alphaImage)
alphaImage:delete()
local logoSize = logoImage:getDimension()
MENU.GUI.logoSprite = OverlaySprite(
logoSize,logoSize,Texture(logoImage),true
)
local logoSprite = MENU.GUI.logoSprite
logoImage:delete()
local w, h = getDimension()
logoSprite:setLocation(w*0.5,h)
addToOverlay(logoSprite)
end
function MENU.createTexts()
local colors = {
{r = 1, g = 1, b = 0},
{r = 0, g = 1, b = 1},
362
CHAPTER 22. DRAGON’S RIDE
{r = 1, g = 1, b = 1}
}
local creditStrings = {
{
1, "D R A G O N ’ S
R I D E",
2, "",
2, "D E M O",
2, "",
3, "Copyright \184 2006",
2, "Leonardo Boselli",
2, ""
},
{
1, "Programming & Design",
2, "Leonardo \"leo\" Boselli",
3, "tetractys@users.sf.net",
2, "",
1, "3D Models",
2, "Michael \"Magarnigal\" Mellor",
3, "mmellor@tantalus.com.au",
2, "Brian \"EvilBastard\" Collins",
3, "brian@zono.com",
2, "James Green",
3, "james@perilith.com",
2, "\"Hunter\"",
3, "Hunter@Polycount.com",
2, "",
1, "Tiled Textures",
3, "http://lostgarden.com",
},
{
3, "Thanks to",
3, "",
2, "Michael \"Magarnigal\" Mellor",
3, "for creating the models",
1, "Dragon-Knight & Ogro",
3, "",
2, "Brian \"EvilBastard\" Collins",
3, "for creating the models",
1, "Hueteotl & Bauul",
},
{
3, "Thanks to",
3, "",
2, "James Green",
3, "for creating",
1, "PKnight",
3, "",
2, "\"Hunter\"",
3, "for creating",
363
1, "Hobgoblin",
},
{
3, "Thanks to",
3, "",
2, "Matteo \"Fuzz\" Perenzoni",
3, "",
3, "for fruitful discussions on",
3, "OpenGL and 3D programming.",
3, "",
3, "The sources of his demo for",
3, "the NeHe’s Apocalypse Contest",
3, "were the first building blocks",
3, "of the APOCALYX 3D Engine."
},
{
3, "Thanks to",
3, "",
1, "TeCGraf, PUC-Rio",
3, "for the LUA script language",
2, "www.lua.org",
3, "",
1, "Borland",
3, "for their free C++ compiler",
2, "www.borland.com",
},
{
3, "Thanks to the following sites",
3, "for their useful tutorials",
3, "about game programming",
3, "",
1, "NeHe Productions",
2, "nehe.gamedev.net",
1, "Game Tutorials",
2, "www.gametutorials.com",
1, "SULACO",
2, "www.sulaco.co.za",
3, "",
3, "and",
3, "",
1, "Game Programming Italia",
2, "www.gameprog.it"
},
{
3, "Thanks to these web sites",
3, "for publishing news about game",
3, "development and related stuff",
3, "",
1, "GameDev",
2, "www.gamedev.net",
364
CHAPTER 22. DRAGON’S RIDE
1, "FlipCode",
2, "www.flipcode.org",
1, "CFXweb",
2, "www.cfxweb.net",
1, "OpenGL.org",
2, "www.opengl.org"
},
{
3, "And, finally, thanks to",
3, "ALL the people of the",
3, "italian newsgroup",
3, "",
1, "it.comp.giochi.sviluppo",
3, "",
3, "",
3, ""
}
}
local instructionStrings = {
2, "D R A G O N ’ S
R I D E",
3, "",
1, "A dragon without wings? Yes, dragons were difficult to be",
1, "trained so northern armies used to clip dragon’s wings to",
1, "control them better, in fact knights were more interested",
1, "in the capability of dragons to spit fire than in flying.",
3, "",
1, "In this demo you can ride a dragon to fight against hordes",
1, "of evil creatures: ogres, hueteotls, hobgoblins, renegades",
1, "and bauuls.
",
3, "",
1, " The demo was created in less than one week with the help ",
1, "of my 3D engine (APOCALYX) and free resources available on",
1, "internet, so it is quite basic in functionalities, but I’m",
1, "looking for help to improve it with more levels, textures,",
1, "enemies, weapons, powerups and all the other stuff needed ",
1, "to make it an amusing SHMUP :)",
3, "",
2, "Use arrows keys to move the dragon and hit space to fire,",
2, "avoid to collide running enemies to preserve your health,",
2, " charge standing enemies or fire them to increase score. ",
3, "",
1, "Enjoy the demo! leo",
3, "",
3, "Press ENTER to show the MENU",
}
local font = getMainOverlayFont()
local fontH = font:getHeight()
local w, h = getDimension()
MENU.GUI.optionsTexts = OverlayTexts(font)
local optionsTexts = MENU.GUI.optionsTexts
365
local scale = 1.5
local offset = fontH*2
local playText
playText = OverlayText("[9] Exit
")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[2] Play
")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[1] Gallery
")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
playText = OverlayText("[0] Instructions ")
playText:setScale(scale)
playText:setColor(1,1,0)
playText:setLocation(0,offset)
optionsTexts:add(playText)
offset = offset+fontH*2
optionsTexts:setLocation(w*0.5,h)
addToOverlay(optionsTexts)
MENU.GUI.credits = {}
local credits = MENU.GUI.credits
credits.status = 0
credits.index = 1
credits.texts = {}
local x = w*0.5+160
local creditTexts = credits.texts
for creditIdx = 1, table.getn(creditStrings) do
creditTexts[creditIdx] = OverlayTexts(font)
local currentCreditText = creditTexts[creditIdx]
local textLines = creditStrings[creditIdx]
local textLinesCount = table.getn(textLines)
local y = (h+fontH*(textLinesCount-1))*0.5-240
for textLineIdx = 1, table.getn(textLines), 2 do
local text = OverlayText(textLines[textLineIdx+1])
local colorIdx = textLines[textLineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(x,y)
y = y-fontH
366
CHAPTER 22. DRAGON’S RIDE
currentCreditText:add(text)
end
currentCreditText:setLocation(0,-h*0.5)
addToOverlay(currentCreditText)
currentCreditText:hide()
end
MENU.GUI.instructionTexts = OverlayTexts(font)
local instructionCount = table.getn(instructionStrings)
y = fontH*instructionCount*0.25
for lineIdx = 1, instructionCount, 2 do
local text = OverlayText(instructionStrings[lineIdx+1])
local colorIdx = instructionStrings[lineIdx]
text:setColor(
colors[colorIdx].r,colors[colorIdx].g,colors[colorIdx].b
)
text:setLocation(0,y)
y = y-fontH
MENU.GUI.instructionTexts:add(text)
end
MENU.GUI.instructionTexts:setLocation(w*0.5,h*0.5)
addToOverlay(MENU.GUI.instructionTexts)
MENU.GUI.instructionTexts:hide()
local indexes = {0,1,2,3,}
local coords = {-300,-200, -300,232, 300,232, 300,-200,}
local colors = {0.5,0.5,0.5,0.75, 1,1,1,0.75, 1,1,1,0.75, 0.5,0.5,0.5,0.75,}
MENU.GUI.overlayPoly = OverlayPolys(indexes,coords,colors)
MENU.GUI.overlayPoly:setColor(1,1,1,0)
MENU.GUI.overlayPoly:setModeTriangFan()
MENU.GUI.overlayPoly:setLocation(w*0.5,h*0.5)
MENU.GUI.overlayPoly:setLayer(1)
addToOverlay(MENU.GUI.overlayPoly)
MENU.GUI.overlayPoly:hide()
end
function MENU.setupHelp()
hideConsole()
showHelpReduced()
hideFramerate()
local help = {
"D R A G O N ’ S
R I D E",
" ",
"[ 0 ] Instructions",
"[ 1 ] Gallery",
"[ 2 ] Play",
"[ 9 ] Exit",
" ",
"[ F 1 ] Show/Hide Help",
}
setHelp(help)
end
367
----MENU UPDATE SUPPORT---function MENU.animateLogo(timeStep)
local GUI = MENU.GUI
local logoSprite = GUI.logoSprite
local credits = GUI.credits
local creditTexts = credits.texts
local creditTextTime = credits.time
local creditTextIndex = credits.index
local creditTextStatus = credits.status
local logoSpeedX, logoSpeedY = 200, 400
local logoSize = logoSprite:getDimension()
local w, h = getDimension()
local markX = (w-320)*0.5
local markY = (h+logoSize-480)*0.5
local logoX, logoY = logoSprite:getLocation()
if timeStep > 0.1 then
timeStep = 0.1
end
if logoY > markY then
logoY = logoY-timeStep*logoSpeedY
if logoY < markY then
logoY = markY
end
MENU.GUI.optionsTexts:setLocation(w*0.5,logoY+(h-logoSize)*0.5)
logoSprite:setLocation(logoX,logoY)
elseif logoY == markY then
logoSprite:setLocation(logoX,markY-1)
else
if logoX > markX then
logoX = logoX-timeStep*logoSpeedX
if logoX < markX then
logoX = markX
end
logoSprite:setLocation(logoX,logoY)
elseif logoX == markX then
hideHelp()
logoSprite:setLocation(logoX-1,logoY)
creditTexts[creditTextIndex]:show()
creditTexts[creditTextIndex]:setLocation(0,-h*0.5)
else
local creditText = creditTexts[creditTextIndex]
if creditTextStatus == 0 then --> FADE_IN
local creditX, creditY = creditText:getLocation()
creditY = creditY+timeStep*logoSpeedX
if creditY >= 0 then
creditText:setLocation(creditX,0)
credits.time = getElapsedTime()
credits.status = 1 --> WAIT
368
CHAPTER 22. DRAGON’S RIDE
else
creditText:setLocation(creditX,creditY)
end
elseif creditTextStatus == 1 then --> WAIT
local diff = getElapsedTime()-creditTextTime
if diff > creditText:getCount()*0.5 then
credits.status = 2 --> FADE_OUT
end
elseif creditTextStatus == 2 then --> FADE_OUT
local creditX, creditY = creditText:getLocation()
creditY = creditY-timeStep*logoSpeedY
if creditY <= -h*0.5 then
creditText:setLocation(creditX,-h*0.5)
creditText:hide()
credits.index = creditTextIndex+1
if credits.index > table.getn(creditTexts) then
credits.index = 1
end
creditTexts[credits.index]:show()
credits.status = 0 --> FADE_IN
else
creditText:setLocation(creditX,creditY)
end
end
end
end
end
----INITIALIZATION---function MENU.init()
empty()
emptyOverlay()
MENU.GUI = {mode = -1} ---> OPTIONS
if not fileExists("DragonsRide.dat") then
showConsole()
error("\nERROR: File ’DragonsRide.dat’ not found")
end
local zip = Zip("DragonsRide.dat")
ALL.showSplashImage(zip)
ALL.playSoundtrack(zip,"intro.mid",MENU.GUI)
setTitle(" D R A G O N ’ S
R I D E")
MENU.createLogo(zip)
MENU.createTexts()
----SCENERY
----CAMERA---setAmbient(0.5,0.5,0.5)
local camera = {angleOfView = 60, nearClip = .5, farClip = 1000}
setPerspective(camera.angleOfView, camera.nearClip, camera.farClip)
local theCamera = getCamera()
369
theCamera:reset()
theCamera:move(0,2,-10)
----FADER---faderAlpha = 1
fader = OverlayFader(1,1,1)
fader:setLayer(1)
addToOverlay(fader)
----SKYBOX---enableFog(500, .5,.5,.75)
local skytype = "blue_"
local skyTxt = {
zip:getTexture(skytype.."top.jpg",false,false),
zip:getTexture(skytype.."left.jpg",false,false),
zip:getTexture(skytype.."front.jpg",false,false),
zip:getTexture(skytype.."right.jpg",false,false),
zip:getTexture(skytype.."back.jpg",false,false)
}
local sky = MirroredSky(skyTxt)
setBackground(sky)
----SUN---local sun = Sun(
zip:getTexture("light.jpg"),0.25,
0,.41,.91,
zip:getTexture("lensflares.png"),
4,0.2,500
)
setSun(sun)
----TERRAIN---local terrainMaterial = Material()
terrainMaterial:setAmbient(0.4,0.4,0.4)
terrainMaterial:setDiffuse(1,1,1)
terrainMaterial:setDiffuseTexture(zip:getTexture("snow.jpg",1))
local terrain = FlatTerrain(terrainMaterial,2000,300)
terrain:setReflective()
terrain:setShadowed()
terrain:setShadowIntensity(0.5)
terrain:setShadowOffset(0.01)
setTerrain(terrain)
----MODELS---local modelNames = {"pknight","hueteotl","bauul","hobgoblin","ogro"}
animationTimers = {}
models = {}
weapons = {}
for ct = 1, table.getn(modelNames) do
local name = modelNames[ct]
models[ct], weapons[ct] = ALL.createModelAndWeapon(zip,
name..".md2",name..".jpg",
name.."_weapon.md2",name.."_weapon.jpg",
0,1,0, 1, 0)
local ang = ct*1.047
370
CHAPTER 22. DRAGON’S RIDE
local posX, posZ = 6*math.sin(ang), 6*math.cos(ang)
models[ct]:rotStanding(-1.5708)
models[ct]:rotStanding(ang)
models[ct]:hide()
models[ct]:move(posX,0,posZ)
weapons[ct]:rotStanding(-1.5708)
weapons[ct]:rotStanding(ang)
weapons[ct]:hide()
weapons[ct]:move(posX,0,posZ)
animationTimers[ct] = math.random(5,10)
end
dragon,knight = ALL.createModelAndWeapon(zip,
"dragonknight_dragon.md2","dragonknight_armour.jpg",
"dragonknight_knight.md2","dragonknight_knight.jpg",
0,2,0, 2, 0)
dragon:rotStanding(-1.5708)
knight:rotStanding(-1.5708)
dragonAnimationTimer = 10
dragonAnimation = 0
----FIRE---local fireImage = zip:getImage("smoke.png")
fireImage:convertTo111A()
local fireTexture = Texture(fireImage)
fireImage:delete()
fireEmitter = Emitter(30,1,4)
fireEmitter:setTexture(fireTexture,1)
fireEmitter:setVelocity(0,0,0, 0)
fireEmitter:setColor(1,0.7,0,1, 0,0,0,0)
fireEmitter:setSize(0.2,1)
fireEmitter:setGravity(0,0,0, 0,10,0)
fireEmitter:reset()
addObject(fireEmitter)
fireEmitter2 = Emitter(15,0.5,4)
local lightImage = zip:getImage("light.jpg")
lightImage:convertTo111A()
local lightTexture = Texture(lightImage)
lightImage:delete()
fireEmitter2:setTexture(lightTexture,1)
fireEmitter2:setVelocity(0,0,0, 0)
fireEmitter2:setColor(1,1,0,1, 1,0,0,0)
fireEmitter2:setSize(0.5,0.1)
fireEmitter2:setGravity(0,0,0, 0,0,0)
fireEmitter2:setRadius(0,0.5)
fireEmitter2:setAngularSpeed(25,25)
fireEmitter2:reset()
addObject(fireEmitter2)
----SOUNDS---local dragonSample = zip:getSample3D("jet.wav");
dragonSample:setLooping(true)
dragonSample:setVolume(64)
371
dragonSample:setMinDistance(64)
dragonSource = Source(dragonSample,dragon)
addSource(dragonSource)
local crashSample = zip:getSample3D("crash.wav");
crashSample:setLooping(false)
crashSample:setVolume(255)
crashSample:setMinDistance(64)
crashSource = Source(crashSample,dragon,false)
addSource(crashSource)
----SCENERY (END)
MENU.setupPointer(zip)
MENU.setupHelp()
zip:delete()
end
----FINALIZATION---function MENU.final()
ALL.stopSoundtrack(MENU.GUI)
faderAlpha = nil
fader = nil
fireEmitter = nil
fireEmitter2 = nil
dragon, knight = nil, nil
models = nil
weapons = nil
animationTimers = nil
dragonAnimationTimer = nil
dragonAnimation = nil
dragonSource = nil
crashSource = nil
MENU.GUI = nil
hidePointer()
disableFog()
empty()
emptyOverlay()
end
----KEYBOARD---function MENU.keyDown(key)
if MENU.GUI.mode == -1 then
local key0 = string.byte("0")
if key == key0+2 then
releaseKey(key)
setScene(Scene(GAME.init,GAME.update,GAME.final,GAME.keyDown))
elseif key == key0 then
releaseKey(key)
hidePointer()
MENU.GUI.mode = 0 ---> Instructions
372
CHAPTER 22. DRAGON’S RIDE
MENU.GUI.optionsTexts:hide()
MENU.GUI.logoSprite:hide()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:hide()
MENU.GUI.instructionTexts:show()
MENU.GUI.overlayPoly:show()
elseif key == key0+1 then
releaseKey(key)
hidePointer()
MENU.GUI.mode = 1 ---> Gallery
MENU.GUI.optionsTexts:hide()
MENU.GUI.logoSprite:hide()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:hide()
MENU.GUI.instructionTexts:hide()
MENU.GUI.overlayPoly:hide()
getCamera():moveForward(-1.5)
faderAlpha = 1
fader:setColor(1,1,1,1)
for ct = 1, table.getn(models) do
models[ct]:show()
weapons[ct]:show()
end
elseif key == key0+9 then
releaseKey(key)
exit()
end
elseif MENU.GUI.mode == 0 then
if key == 13 then ---> RETURN
releaseKey(key)
MENU.GUI.mode = -1
showPointer()
MENU.GUI.optionsTexts:show()
MENU.GUI.logoSprite:show()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:show()
MENU.GUI.instructionTexts:hide()
MENU.GUI.overlayPoly:hide()
end
elseif MENU.GUI.mode == 1 then
releaseKey(key)
MENU.GUI.mode = -1
showPointer()
MENU.GUI.optionsTexts:show()
MENU.GUI.logoSprite:show()
MENU.GUI.credits.texts[MENU.GUI.credits.index]:show()
MENU.GUI.instructionTexts:hide()
MENU.GUI.overlayPoly:hide()
getCamera():moveForward(1.5)
faderAlpha = 1
fader:setColor(1,1,1,1)
for ct = 1, table.getn(models) do
models[ct]:hide()
373
weapons[ct]:hide()
end
end
end
----UPDATE---function MENU.update()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
if faderAlpha ~= 0 then
faderAlpha = faderAlpha-timeStep/2
if faderAlpha < 0 then
faderAlpha = 0
end
fader:setColor(1,1,1,faderAlpha)
end
local mode = MENU.GUI.mode
local camera = getCamera()
local rotAngle = .13*timeStep
camera:rotAround(rotAngle)
local x1, y1, z1 = dragon:getVertexCoord(137)
local x2, y2, z2 = dragon:getVertexCoord(165)
local x3, y3, z3 = dragon:getVertexCoord(160)
local length2 = (x3-x2)*(x3-x2)+(y3-y2)*(y3-y2)+(z3-z2)*(z3-z2)
local dragonVolume = length2*500
if dragonVolume > 255 then
dragonVolume = 255
end
local dragonSound = dragonSource:getSound3D()
dragonSound:setVolume(dragonVolume)
fireEmitter:setPosition(x2,y2,z2)
local speed = length2*8
local vx, vy, vz = (x2-x1)*speed,(y2-y1)*speed,(z2-z1)*speed
fireEmitter:setVelocity(vx,vy,vz, 0.5)
fireEmitter2:setPosition(x2,y2,z2)
fireEmitter2:setVelocity(vx,vy,vz, 0.2)
local posX,posY,posZ = dragon:getPosition()
camera:pointTo(posX,posY,posZ)
local stopped = dragon:getStoppedAnimation()
if stopped ~= -1 then
dragonAnimationTimer = 10
if stopped == 7 then
dragonAnimation = 9
dragon:setAnimation(9)
knight:setAnimation(9)
else
dragonAnimation = 0
dragon:setAnimation(0)
knight:setAnimation(0)
374
CHAPTER 22. DRAGON’S RIDE
end
end
if dragonAnimation == 9 and dragonAnimationTimer < 9.5 then
dragonAnimation = 0
local crashSound = crashSource:getSound3D()
crashSound:play()
end
dragonAnimationTimer = dragonAnimationTimer-timeStep
if dragonAnimationTimer < 0 then
dragonAnimationTimer = 10
dragonAnimation = math.random(7,12)
dragon:setAnimation(dragonAnimation)
knight:setAnimation(dragonAnimation)
end
if mode == -1 then ---> OPTIONS
MENU.animateLogo(timeStep)
local dx, dy = getMouseMove()
movePointer(dx,dy,getDimension())
local GUI = MENU.GUI
local pointerX, pointerY = getPointerLocation()
local text = GUI.optionsTexts:getTextAt(pointerX-8,pointerY-8)
local oldPointerText = GUI.oldPointerText
if text then
if text ~= oldPointerText then
if oldPointerText then
oldPointerText:setColor(1,1,0)
end
text:setColor(1,0.25,0.25)
GUI.oldPointerText = text
end
if isMouseLeftPressed() then
if GUI.selected == nil then
GUI.selected = true
MENU.keyDown(string.byte(text:getText(),2))
end
else
GUI.selected = nil
end
elseif oldPointerText then
oldPointerText:setColor(1,1,0)
GUI.oldPointerText = nil
end
elseif mode == 1 then ---> GALLERY
for ct = 1, table.getn(models) do
animationTimers[ct] = animationTimers[ct]-timeStep
local stopped = models[ct]:getStoppedAnimation()
if stopped ~= -1 then
models[ct]:setAnimation(0)
weapons[ct]:setAnimation(0)
elseif animationTimers[ct] < 0 then
375
animationTimers[ct] = math.random(5,10)
local anim = math.random(7,11)
models[ct]:setAnimation(anim)
weapons[ct]:setAnimation(anim)
end
end
end
end
-------EDITOR SCENE
-------INITIALIZATION---function EDITOR.init()
----ZIP---empty()
emptyOverlay()
if not fileExists("DragonsRide.dat") then
showConsole()
error("\nERROR: File ’DragonsRide.dat’ not found")
end
local zip = Zip("DragonsRide.dat")
ALL.showSplashImage(zip)
----CAMERA---CAMERA = {isFree = true}
setAmbient(0.5,0.5,0.5)
setPerspective(60,1,250)
local camera = getCamera()
camera:reset()
local CAM_X, CAM_Y, CAM_Z = 0,10,0
camera:move(CAM_X,CAM_Y,CAM_Z)
----EDITOR---mapPosX = 0
mapPosY = 0
----TERRAIN---local mapImage = Image("tiles.png")
imageW, imageH = mapImage:getDimension()
local material = Material()
material:setDiffuseTexture(zip:getTexture("circleTextures64.jpg"))
tiled = TiledTerrain(material,8,mapImage,50,8,16)
setTerrain(tiled)
----HELP---local help = {
"T I L E D
T E R R A I N
E D I T O R",
"",
"[ ENTER ] Back to Menu",
" ",
"[F1] Show/Hide Help",
}
376
CHAPTER 22. DRAGON’S RIDE
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function EDITOR.final()
mapPosX = nil
mapPosY = nil
imageW = nil
imageH = nil
CAMERA = nil
tiled = nil
----EMPTY WORLD---disableFog()
empty()
emptyOverlay()
end
----LOOP---function EDITOR.update()
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
----CAMERA CONTROL---local camX,camY,camZ
if CAMERA.isFree then
local speed = 10
if isKeyPressed(38) then --> UP
camera:moveStanding( speed*timeStep)
elseif isKeyPressed(40) then --> DOWN
camera:moveStanding(-speed*timeStep)
end
if isKeyPressed(37) then --> LEFT
camera:moveSide( speed*timeStep)
elseif isKeyPressed(39) then --> RIGHT
camera:moveSide(-speed*timeStep)
end
local climbSpeed = 200
if isKeyPressed(33) then --> PRIOR
camera:move(0,climbSpeed*timeStep,0)
elseif isKeyPressed(34) then --> NEXT
camera:move(0,-climbSpeed*timeStep,0)
end
local dx, dy = getMouseMove()
local changeStep = 0.15*timeStep;
if dx ~= 0 then
camera:rotStanding(-dx*changeStep)
end
377
if dy ~= 0 then
camera:pitch(-dy*changeStep)
end
camX,camY,camZ = camera:getPosition()
local h = 0
if camY < h+5 then
camY = h+5
end
camera:setPosition(camX,camY,camZ)
end
end
----KEYDOWN---function EDITOR.keyDown(key)
if key == string.byte("W") then
getCamera():move(0,0,50/8)
mapPosY = mapPosY+1
if mapPosY >= imageH then
mapPosY = 0
end
elseif key == string.byte("Z") then
getCamera():move(0,0,-50/8)
mapPosY = mapPosY-1
if mapPosY < 0 then
mapPosY = imageH-1
end
elseif key == string.byte("A") then
getCamera():move(50/8,0,0)
mapPosX = mapPosX+1
if mapPosX >= imageW then
mapPosX = 0
end
elseif key == string.byte("S") then
getCamera():move(-50/8,0,0)
mapPosX = mapPosX-1
if mapPosX < 0 then
mapPosX = imageW-1
end
elseif key == string.byte("I") then
local tileX, tileY, rot = tiled:getTileAtGrid(mapPosX,mapPosY)
tileY = tileY+1
if tileY >= 8 then
tileY = 0
end
tiled:setTileAtGrid(mapPosX,mapPosY,tileX,tileY,rot)
elseif key == string.byte("M") then
local tileX, tileY, rot = tiled:getTileAtGrid(mapPosX,mapPosY)
tileY = tileY-1
if tileY < 0 then
tileY = 7
378
CHAPTER 22. DRAGON’S RIDE
end
tiled:setTileAtGrid(mapPosX,mapPosY,tileX,tileY,rot)
elseif key == string.byte("J") then
local tileX, tileY, rot = tiled:getTileAtGrid(mapPosX,mapPosY)
tileX = tileX-1
if tileX < 0 then
tileX = 7
end
tiled:setTileAtGrid(mapPosX,mapPosY,tileX,tileY,rot)
elseif key == string.byte("K") then
local tileX, tileY, rot = tiled:getTileAtGrid(mapPosX,mapPosY)
tileX = tileX+1
if tileX >= 8 then
tileX = 0
end
tiled:setTileAtGrid(mapPosX,mapPosY,tileX,tileY,rot)
elseif key == string.byte("R") then
local tileX, tileY, rot = tiled:getTileAtGrid(mapPosX,mapPosY)
rot = rot+1
if rot >= 4 then
rot = 0
end
tiled:setTileAtGrid(mapPosX,mapPosY,tileX,tileY,rot)
elseif key == string.byte(" ") then
local image = tiled:getImageFromTiles()
image:saveAsPng("tiles.png")
elseif key == string.byte("\r") then
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
end
end
-------GAME SCENE
-------HUD SUPPORT---function GAME.setupHud(theScore,len,offset,texture,u0,v0,u1,v1)
local w, h = getDimension()
local w2, h2 = w*0.5, h*0.5
local sw, sh = 64, 32
local sw2, sh2 = sw*0.5, sh*0.5
local nw, nh = 16, 16
local nw2, nh2 = nw*0.5, nh*0.5
local sprite = OverlaySprite(sw,sh,texture,true)
sprite:setTextureCoord(u0,v0,u1,v1)
sprite:setLocation(w2+offset,h-sh2)
sprite:setColor(0.75,0.75,0)
addToOverlay(sprite)
local offsetX, offsetY = w2+offset+nw2*(len+1), h-sh2-nh2-nh
379
for ct = 1, len do
theScore[ct] = OverlaySprite(nw,nh,texture,true)
local number = theScore[ct]
number:setTextureCoord(0,0.75,0.25,1)
number:setLocation(offsetX-nw*ct,offsetY)
number:setColor(1,1,0)
addToOverlay(number)
end
end
function GAME.setHudValue(theScore,chars,value)
local theString = string.format("%d",value)
local len = math.min(string.len(theString),chars)
for ct = 1, len do
local byte = string.byte(theString,ct)-string.byte("0")
local u, v = math.mod(byte,4)*0.25, math.floor(byte*0.25)*0.25
theScore[len-ct+1]:setTextureCoord(u,0.75-v,u+0.25,1-v)
end
for ct = len+1, chars do
theScore[ct]:setTextureCoord(0,0.75,0.25,1)
end
end
function GAME.setupHuds(zip)
local scoreImage = zip:getImage("numbers.png",0)
local texture = Texture(scoreImage)
scoreImage:delete()
local setupScore = GAME.setupHud
HUD.highestHud = {}
setupScore(HUD.highestHud,6,-160,texture,0,0,0.5,0.25)
HUD.scoreHud = {}
setupScore(HUD.scoreHud,6,0,texture,0.5,0,1,0.25)
HUD.damageHud = {}
setupScore(HUD.damageHud,3,160,texture,0.5,0.25,1,0.5)
HUD.highest = 0
if fileExists("hiscore.txt") then
local file = ReadableTextFile("hiscore.txt")
if file then
local read = file:read()
HUD.highest = tonumber(read)
file:close()
end
end
HUD.score = 0
HUD.damage = 0
GAME.setHudValue(HUD.highestHud,6,HUD.highest)
GAME.setHudValue(HUD.scoreHud,6,HUD.score)
GAME.setHudValue(HUD.damageHud,3,HUD.damage)
end
380
CHAPTER 22. DRAGON’S RIDE
function GAME.incScore(score)
HUD.score = HUD.score+score
GAME.setHudValue(HUD.scoreHud,6,HUD.score)
if HUD.score > HUD.highest then
HUD.highest = HUD.score
GAME.setHudValue(HUD.highestHud,6,HUD.highest)
end
return HUD.score
end
function GAME.incDamage(damage)
HUD.damage = HUD.damage+damage
GAME.setHudValue(HUD.damageHud,3,HUD.damage)
return HUD.damage
end
----INITIALIZATION---function GAME.init()
----ZIP---setListenerScale(1)
empty()
emptyOverlay()
if not fileExists("DragonsRide.dat") then
showConsole()
error("\nERROR: File ’DragonsRide.dat’ not found")
end
local zip = Zip("DragonsRide.dat")
ALL.showSplashImage(zip)
ALL.playSoundtrack(zip,"soundtrack.mid",GAME)
---HUD--HUD = {}
GAME.setupHuds(zip)
----CAMERA---setAmbient(0.5,0.5,0.5)
setPerspective(60,1,100)
local camera = getCamera()
camera:reset()
camera:move(-20,40,0)
camera:pointTo(0,0,0)
----SUN---local sun = Sun(zip:getTexture("light.jpg"),0.15, -0.5,0.707,-0.5)
sun:setColor(1,1,1)
setSun(sun)
----TERRAIN---local mapImage = zip:getImage("tiles.png")
local material = Material()
material:setDiffuseTexture(zip:getTexture("circleTextures64.jpg"))
tiled = TiledTerrain(material,8,mapImage,50,8,-1)
tiled:setShadowOffset(0.05)
tiled:setShadowIntensity(0.5)
381
tiled:setShadowed()
setTerrain(tiled)
----AVATAR---dragonAnimation = 1
dragonShotCount = 0
dragon,knight = ALL.createModelAndWeapon(zip,
"dragonknight_dragon.md2","dragonknight_armour.jpg",
"dragonknight_knight.md2","dragonknight_knight.jpg",
0,2,0, 2, dragonAnimation)
dragon:rotStanding(-1.5708)
knight:rotStanding(-1.5708)
dragonAtLeft = false
----MODELS---modelNames = {"hueteotl","pknight","ogro","hobgoblin","bauul"}
originalModels = {}
originalWeapons = {}
for ct = 1, table.getn(modelNames) do
local name = modelNames[ct]
originalModels[ct], originalWeapons[ct] = ALL.createModelAndWeapon(zip,
name..".md2",name..".jpg",
name.."_weapon.md2",name.."_weapon.jpg",
0,2,0, 2, 0)
originalModels[ct]:hide()
originalWeapons[ct]:hide()
end
animationTimers = {}
animations = {}
models = {}
weapons = {}
for ct = 1, table.getn(modelNames)*2 do
local index = math.floor((ct-1)/2+1)
animations[ct] = 0
models[ct], weapons[ct] = ALL.cloneModelAndWeapon(
originalModels[index],
originalWeapons[index],
zip,nil, 0,2,0, 2
)
local ang = math.random(0,16)/16*6.28
models[ct]:rotStanding(ang)
local mZ = 50+math.random(0,5)+10*ct
local mX = math.random(-19,31)
models[ct]:setPosition(mX,2,mZ)
weapons[ct]:rotStanding(ang)
weapons[ct]:setPosition(mX,2,mZ)
animationTimers[ct] = math.random(5,10)
end
----SHOTS---local shotMaterial = Material()
shotMaterial:setDiffuseTexture(zip:getTexture("fire.png"))
shotMaterial:setEnlighted(false)
382
CHAPTER 22. DRAGON’S RIDE
shotMaterial:setEmissive(1,1,1)
shots = {}
for ct = 1, 5 do
local object = AnimatedSprite(1.5,1.5,3,8,shotMaterial)
object:setTransparent()
addObject(object)
object:hide()
shots[ct] = object
end
----HELP---local help = {
"D R A G O N ’ S
R I D E",
"",
"[ ARROW ] Move Around",
"[ SPACE ] Spit Fire",
"[
M
] Play/Stop Music",
"[ ENTER ] Back to Menu",
" ",
"[F1] Show/Hide Help",
}
setHelp(help)
hideConsole()
----DELETE ZIP---zip:delete()
end
----FINALIZATION---function GAME.final()
ALL.stopSoundtrack(GAME)
tiled = nil
HUD = nil
shots = nil
dragon, knight = nil, nil
dragonShotCount = nil
dragonAnimation = nil
modelNames = nil
originalModels = nil
originalWeapons = nil
models = nil
weapons = nil
animations = nil
animationTimers = nil
----EMPTY WORLD---disableFog()
empty()
emptyOverlay()
end
----LOOP---function GAME.update()
383
local camera = getCamera()
local timeStep = getTimeStep()
if timeStep > 0.1 then timeStep = 0.1 end
----CAMERA CONTROL---if dragonAnimation ~= 17 then
camera:moveSide(-20*timeStep)
end
----AVATAR CONTROL---local cX, cY, cZ = camera:getPosition()
if dragonAnimation ~= 17 then
local stopped = dragon:getStoppedAnimation()
if stopped ~= -1 then
dragonAnimation = 1
dragon:setAnimation(1)
knight:setAnimation(1)
end
local step = 20*timeStep
dragon:moveSide(step)
knight:moveSide(step)
if isKeyPressed(37) then ---> KEY_LEFT
if dragonAnimation == 1 and not dragonAtLeft then
dragonAnimation = 13
dragon:setAnimation(13)
knight:setAnimation(13)
end
local step = -15*timeStep
dragon:moveSide(step)
knight:moveSide(step)
if isKeyPressed(38) then ---> KEY_UP
local step = 5*timeStep
dragon:moveUp(step)
knight:moveUp(step)
elseif isKeyPressed(40) then ---> KEY_DOWN
local step = -5*timeStep
dragon:moveUp(step)
knight:moveUp(step)
end
elseif isKeyPressed(39) then ---> KEY_RIGHT
local step = 15*timeStep
dragon:moveSide(step)
knight:moveSide(step)
if isKeyPressed(38) then ---> KEY_UP
local step = 20*timeStep
dragon:moveUp(step)
knight:moveUp(step)
elseif isKeyPressed(40) then ---> KEY_DOWN
local step = -20*timeStep
dragon:moveUp(step)
knight:moveUp(step)
end
384
CHAPTER 22. DRAGON’S RIDE
else
if isKeyPressed(38) then ---> KEY_UP
local step = 15*timeStep
dragon:moveUp(step)
knight:moveUp(step)
elseif isKeyPressed(40) then ---> KEY_DOWN
local step = -15*timeStep
dragon:moveUp(step)
knight:moveUp(step)
end
if dragonAnimation == 13 then
dragonAnimation = 1
dragon:setAnimation(1)
knight:setAnimation(1)
end
end
local dX, dY, dZ = dragon:getPosition()
dragonShotCount = dragonShotCount-timeStep
if isKeyPressed(32) and dragonShotCount < 0 then ---> 1
dragonShotCount = 0.25
for ct = 1, table.getn(shots) do
local obj = shots[ct]
if not obj:isVisible() then
obj:show()
obj:setPosition(dX,1.5,dZ+3)
break
end
end
end
for ct = 1, table.getn(shots) do
local obj = shots[ct]
if obj:isVisible() then
local x,y,z = obj:getPosition()
z = z+timeStep*40
if z-cZ > 50 then
obj:hide()
else
obj:setPosition(x,y,z+timeStep*40)
end
end
end
local diffZ = dZ-cZ
if diffZ < -25 then
dragonAtLeft = true
if dragonAnimation == 13 then
dragonAnimation = 1
dragon:setAnimation(1)
knight:setAnimation(1)
end
dragon:setPosition(dX,dY,cZ-25)
385
knight:setPosition(dX,dY,cZ-25)
else
dragonAtLeft = false
if diffZ > 25 then
dragon:setPosition(dX,dY,cZ+25)
knight:setPosition(dX,dY,cZ+25)
end
end
if dX < -19 then
dragon:setPosition(-19,dY,dZ)
knight:setPosition(-19,dY,dZ)
elseif dX > 31 then
dragon:setPosition(31,dY,dZ)
knight:setPosition(31,dY,dZ)
end
end
local dX, dY, dZ = dragon:getPosition()
----MODELS CONTROL---for ct = 1, table.getn(models) do
local mX, mY, mZ = models[ct]:getPosition()
diffZ = mZ-cZ
if diffZ < -50 then
animations[ct] = 0
models[ct]:setAnimation(0)
weapons[ct]:setAnimation(0)
mZ = cZ+50+math.random(0,10)
mX = math.random(-19,31)
models[ct]:setPosition(mX,mY,mZ)
weapons[ct]:setPosition(mX,mY,mZ)
local ang = math.random(0,16)/16*6.28
models[ct]:rotStanding(ang)
weapons[ct]:rotStanding(ang)
end
if animations[ct] < 16 then
local diffX = dX-mX
local diffZ = dZ-mZ
local viewX, viewY, viewZ = models[ct]:getSideDirection()
local dot = diffX*viewX+diffZ*viewZ
if dot > 0 and mZ-cZ < 40 and dragonAnimation ~= 17 then
if animations[ct] ~= 1 then
animations[ct] = 1
models[ct]:setAnimation(1)
weapons[ct]:setAnimation(1)
end
models[ct]:moveSide(15*timeStep)
weapons[ct]:moveSide(15*timeStep)
local cross = diffX*viewZ-diffZ*viewX
if cross > 0 then
models[ct]:rotStanding(1.57*timeStep)
weapons[ct]:rotStanding(1.57*timeStep)
386
CHAPTER 22. DRAGON’S RIDE
else
models[ct]:rotStanding(-1.57*timeStep)
weapons[ct]:rotStanding(-1.57*timeStep)
end
else
if animations[ct] ~= 1 then
animationTimers[ct] = animationTimers[ct]-timeStep
local stopped = models[ct]:getStoppedAnimation()
if stopped ~= -1 then
animations[ct] = 0
models[ct]:setAnimation(0)
weapons[ct]:setAnimation(0)
elseif animationTimers[ct] < 0 then
animationTimers[ct] = math.random(5,10)
animations[ct] = math.random(7,11)
models[ct]:setAnimation(animations[ct])
weapons[ct]:setAnimation(animations[ct])
end
else
animations[ct] = 0
models[ct]:setAnimation(0)
weapons[ct]:setAnimation(0)
end
end
end
end
----SHOT HITS---for ct = 1, table.getn(shots) do
local obj = shots[ct]
if obj:isVisible() then
local ox,oy,oz = obj:getPosition()
for idx = 1, table.getn(models) do
if animations[idx] < 16 then
local ex,ey,ez = models[idx]:getPosition()
local dx = ex-ox
local dz = ez-oz
local d2 = dx*dx+dz*dz
if d2 < 2 then
obj:hide()
animations[idx] = math.random(16,19)
models[idx]:setAnimation(animations[idx])
weapons[idx]:setAnimation(0)
local score = math.floor(idx/2)+1
GAME.incScore(score)
end
end
end
end
end
----DRAGON HITS----
387
for idx = 1, table.getn(models) do
if animations[idx] < 16 then
local ex,ey,ez = models[idx]:getPosition()
local dx = ex-dX
local dz = ez-dZ
local d2 = dx*dx+dz*dz
if d2 < 16 then
if animations[idx] == 1 then
local damage = math.floor(idx/2)+1
if GAME.incDamage(damage) >= 100 then
if HUD.score >= HUD.highest then
HUD.highest = HUD.score
local file = WritableTextFile("hiscore.txt")
if file then
file:write(string.format("%d",HUD.score))
file:close()
end
end
dragonAnimation = 17
dragon:setAnimation(17)
knight:setAnimation(17)
for ct = 1, table.getn(shots) do
shots[ct]:hide()
end
end
end
animations[idx] = math.random(16,19)
models[idx]:setAnimation(animations[idx])
weapons[idx]:setAnimation(0)
local score = math.floor(idx/2)+1
GAME.incScore(score)
end
end
end
end
----KEYDOWN---function GAME.keyDown(key)
if key == 13 then ---> ENTER or SPACE
if HUD.score >= HUD.highest then
HUD.highest = HUD.score
local file = WritableTextFile("hiscore.txt")
if file then
file:write(string.format("%d",HUD.score))
file:close()
end
end
setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
elseif key == string.byte("M") then ---> M
releaseKey(key)
388
CHAPTER 22. DRAGON’S RIDE
if GAME.soundTrackIsPlaying then
GAME.soundTrack:stop()
GAME.soundTrackIsPlaying = false
else
GAME.soundTrack:play()
GAME.soundTrackIsPlaying = true
end
end
end
----SCENE SETUP---setScene(Scene(MENU.init,MENU.update,MENU.final,MENU.keyDown))
--setScene(Scene(GAME.init,GAME.update,GAME.final,GAME.keyDown))
--setScene(Scene(EDITOR.init,EDITOR.update,EDITOR.final,EDITOR.keyDown))
Appendix A
Legal Stuff
This software is copyright c 2002-2006, Leonardo Boselli
All Rights Reserved.
A.1
Legal disclaimer
I exclude any and all implied warranties, including warranties of merchantability
and fitness for a particular purpose. I make no warranty or representation,
either express or implied, with respect to this software, its quality, performance,
merchantability, or fitness for a particular purpose. I shall have no liability for
special, incidental, or consequential damages arising out of or resulting from the
use, misuse, or modification of this software.
All trademarks are property of their respective owners.
A.2
Terms for use
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
You should have received a copy of the GNU General Public License along
with this library; if not, write to the
Free Software Foundation, Inc.,
675 Mass Ave, Cambridge, MA 02139, USA.
A.3
Libraries and Tools Copyrights
OpenGL 1.5 - 3D Graphics Library
Copyright c 1996 Silicon Graphics, Inc.
License Applicability. Except to the extent portions of this file are
made subject to an alternative license as permitted in the SGI Free
389
390
APPENDIX A. LEGAL STUFF
Software License B, Version 1.1 (the "License"), the contents of this
file are subject only to the provisions of the License. You may not use
this file except in compliance with the License. You may obtain a copy
of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
http://oss.sgi.com/projects/FreeB
Note that, as provided in the License, the Software is distributed on an
"AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
Original Code. The Original Code is: OpenGL Sample Implementation,
Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc.
Copyright in any portions created by third parties is as indicated
elsewhere herein. All Rights Reserved.
Additional Notice Provisions: This software was created using the
OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has
not been independently verified as being compliant with the OpenGL(R)
version 1.2.1 Specification.
OpenAL 1.1 - Cross Platform Audio Library
Copyright c 2005 by authors
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Or go to http://www.gnu.org/copyleft/lgpl.html
wxWindows 2.6 - GUI Framework
Copyright c 1998-2005 Julian Smart, Robert Roebling et al
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public Licence as published by
the Free Software Foundation; either version 2 of the Licence, or (at
your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
General Public Licence for more details.
You should have received a copy of the GNU Library General Public Licence
along with this software, usually in a file named COPYING.LIB. If not,
write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA.
EXCEPTION NOTICE
1. As a special exception, the copyright holders of this library give
permission for additional uses of the text contained in this release of
the library as licenced under the wxWindows Library Licence, applying
either version 3.1 of the Licence, or (at your option) any later version of
the Licence as published by the copyright holders of version
3.1 of the Licence document.
2. The exception is that you may use, copy, link, modify and distribute
under your own terms, binary object code versions of works based
on the Library.
3. If you copy code from files distributed under the terms of the GNU
A.3. LIBRARIES AND TOOLS COPYRIGHTS
General Public Licence or the GNU Library General Public Licence into a
copy of this library, as this licence permits, the exception does not
apply to the code that you add in this way. To avoid misleading anyone as
to the status of such modified files, you must delete this exception
notice from such code and/or adjust the licensing conditions notice
accordingly.
4. If you write modifications of your own for this library, it is your
choice whether to permit this exception to apply to your modifications.
If you do not wish that, you must delete the exception notice from such
code and/or adjust the licensing conditions notice accordingly.
LUA 5.1 - Script Language Library
Copyright c 1994-2006 by Lua.org, PUC-Rio
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
CSL 4.4.0 - Scripting Language Library
Copyright c 2001-2002 IBK-Landquart-Switzerland
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
AngelCode 2.4.1c - Scripting Library
Copyright c 2003-2006 Andreas Jonsson
This software is provided ’as-is’, without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
SMALL 1.8 - Scripting Language Library
Copyright c 1997-2003 by ITB CompuPhase
The software toolkit "Small" (the compiler, the abstract machine and the
documentation) are copyright (c) 1997-2003 by ITB CompuPhase. The Intel
assembler implementation of the abstract machine and the just-in-time
391
392
APPENDIX A. LEGAL STUFF
compiler (specifically the files AMXEXEC.ASM, AMXEXECN.ASM, JITR.ASM and
JITS.ASM) are copyright (c) 1998-2003 Marc Peter.
Small is distributed under the "zLib/libpng" license, which is reproduced
below:
This software is provided "as-is", without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in
a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
The zLib/libpng license has been approved by the "Open Source Initiative"
organization.
TinyCC 0.9.23 - ANSI C Compiler
Copyright c 2001-2004 Fabrice Bellard
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Flat Assembler 1.64 - x86 Assembler
Copyright c 1999-2005 Tomasz Grysztar.
This program is free for commercial and non-commercial use as long as
the following conditions are adhered to.
Copyright remains Tomasz Grysztar, and as such any Copyright notices
in the code are not to be removed.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The licence and distribution terms for any publically available
version or derivative of this code cannot be changed. i.e. this code
cannot simply be copied and put under another distribution licence
A.3. LIBRARIES AND TOOLS COPYRIGHTS
(including the GNU Public Licence).
SimulAxion 0.8 - Particle Based Physics Library
Copyright c 2001-2006 Leonardo Boselli
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the
Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Open Dynamics Engine 0.5 - Physics Library
Copyright c 2001,2002 Russell L. Smith
This library is free software; you can redistribute it and/or
modify it under the terms of EITHER:
(1) The GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at
your option) any later version. The text of the GNU Lesser
General Public License is included with this library in the
file LICENSE.TXT.
(2) The BSD-style license that is included with this library in
the file LICENSE-BSD.TXT.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
LICENSE.TXT and LICENSE-BSD.TXT for more details.
ColDet 1.1 - Collision Detection Library
Copyright c 2000 Amir Geva
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Cal3D 0.10 - Character Animation Library
Copyright c 2001-2005 Cal3D Team
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or (at
your option) any later version.
The Finite State Machine (FSM) Library
Copyright c 2002 Siemens C-LAB Paderborn
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
393
394
APPENDIX A. LEGAL STUFF
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
MicroPather - A* Path Finding Library
Copyright c 2001-2005 2000-2005 Lee Thomason
Grinning Lizard Utilities (www.grinninglizard.com)
This software is provided ’as-is’, without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
OpenSteer - Steering Behaviors for Autonomous Characters
Copyright c 2002-2003, Sony Computer Entertainment America
Original author: Craig Reynolds
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
ZLib 1.1.2 - Compression Library
Copyright c 1995-1998 Jean-loup Gailly and Mark Adler
This software is provided ’as-is’, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
LibJPEG 6a - Jpeg image format library
Copyright c 1991-1996, Thomas G. Lane
A.3. LIBRARIES AND TOOLS COPYRIGHTS
395
The authors make NO WARRANTY or representation, either express or implied,
with respect to this software, its quality, accuracy, merchantability, or
fitness for a particular purpose. This software is provided "AS IS", and you,
its user, assume the entire risk as to its quality and accuracy.
Permission is hereby granted to use, copy, modify, and distribute this
software (or portions thereof) for any purpose, without fee, subject to these
conditions:
(1) If any part of the source code for this software is distributed, then this
README file must be included, with this copyright and no-warranty notice
unaltered; and any additions, deletions, or changes to the original files
must be clearly indicated in accompanying documentation.
(2) If only executable code is distributed, then the accompanying
documentation must state that "this software is based in part on the work of
the Independent JPEG Group".
(3) Permission for use of this software is granted only if the user accepts
full responsibility for any undesirable consequences; the authors accept
NO LIABILITY for damages of any kind.
These conditions apply to any software derived from or based on the IJG code,
not just to the unmodified library. If you use our work, you ought to
acknowledge us.
Permission is NOT granted for the use of any IJG author’s name or company name
in advertising or publicity relating to this software or products derived from
it. This software may be referred to only as "the Independent JPEG Group’s
software".
We specifically permit and encourage the use of this software as the basis of
commercial products, provided that all warranty or liability claims are
assumed by the product vendor.
LibPNG 1.0.1 - PNG image format library
Copyright c 1998 Glenn Randers-Pehrson
The PNG Reference Library is supplied "AS IS". The Contributing Authors
and Group 42, Inc. disclaim all warranties, expressed or implied,
including, without limitation, the warranties of merchantability and of
fitness for any purpose. The Contributing Authors and Group 42, Inc.
assume no liability for direct, indirect, incidental, special, exemplary,
or consequential damages, which may result from the use of the PNG
Reference Library, even if advised of the possibility of such damage.
Permission is hereby granted to use, copy, modify, and distribute this
source code, or portions hereof, for any purpose, without fee, subject
to the following restrictions:
1. The origin of this source code must not be misrepresented.
2. Altered versions must be plainly marked as such and must not be
misrepresented as being the original source.
3. This Copyright notice may not be removed or altered from any source or
altered source distribution.
The Contributing Authors and Group 42, Inc. specifically permit, without
fee, and encourage the use of this source code as a component to
supporting the PNG file format in commercial products.
Media Resources
All the resources that appear in the demos (e.g. textures, models,
sound effects, midi files) may be followed by a "readme" file that
gives credits to their respective authors. Before using that
resources in your own programs, read which conditions apply,
in particular when COMMERCIAL use is planned.
If you can’t find any specific "readme" for a resource that you
wish to use, ask to me directly and I’ll try to track the history
of the resource to see if commercial use is permitted.
396
A.4
APPENDIX A. LEGAL STUFF
Donations
The APOCALYX Engine and all the related stuff (sources, demos and games)
are provided for free as an open source project. I hope that my work benefits
you as a possible learning tool of particular coding techniques or as an effective
library to be included in your own programs.
If you find my code useful and want to support further improvements of the
project, you can fund it with donations. The 5% of the amount you are going
to donate will remain to SourceForge.net for their great hosting service.
Thank you in advance!
URL for donations:
http://www.sourceforge.net/donate/index.php?group id=65456
A.5
Contact information
You may contact me in the following manners:
Send mail to
Leonardo Boselli
Via Diano Calderina, 7
18100 Imperia
ITALY
Send E-mail to
boselli@uno.it or
tetractys@users.sf.net
On the World Wide Web
APOCALYX Engine: http://apocalyx.sourceforge.net
GUN-TACTYX Game: http://guntactyx.gameprog.it
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )