# Palomino Modules

©2006 Jim E. Brooks http://www.palomino3d.org http://www.jimbrooks.org/sim

See also Palomino Design and Palomino Implementation.

# Contents

- Introduction
- Interface between 3D Engine and Modules
- Classes
- Events and Listeners
- State-machine
- Game design issues
- GUI
- Chase plane
- Game module: Blue Zaxx
- Assorted game ideas

## Introduction

Game elements were influenced by video games such as Blue Max (Atari/C64), Zaxxon, Microprose F-19, Terminal Velocity (DOS), Rogue Squadron, and by movies such as Star Wars, Iron Eagle, and Top Gun.

Palomino is the name of the core 3D renderer (3D engine). Game code is separated as "game modules".

## Game design issues

The biggest issues facing a flight-simulator video game are the player getting lost and not knowing what the goals are. These issues can be solved by a map screen and a goals screen (they could be combined). The map screen would show a flight path and targets. The goals screen would show targets destroyed, list of accomplished mission objectives, etc.

## Interface between 3D Engine and Modules

### Overview

Palomino is comprised of 3 major modules: the 3D engine, the base game module, and a specific game module. The modules are stacked in a hierarchy: higher modules can directly call base modules but not v.v. The idea is that base modules shouldn't know about the higher modules.

The interface is comprised of C++ classes, a few C++ global objects, and an event/listener mechanism. The event/listener mechanism allows base modules to indirectly call higher modules.

The engine supplies base C++ clases for the base game module. In turn, the base game supplies base C++ classes for specific game modules.

### Interface

- EngineEvents global object (gEngineEvents)

The base game module, a specific game module, and the Engine can broadcast and listen to events using the Event C++ class templates. The mechanism serves as an interface between the modules.

- Engine global object (gEng).

- World global object (gWorld).

The World object is a global. A game module derives from World class and constructs the World object. The engine doesn't know who makes or changes it -- the engine just renders it. The engine defers to a game module to populate a Quadrant with Objects. Several classes (Object etc) know about the World object and use it.

- Game module hooks Animate() by Game::Animate().

The engine calls the game module to perform animation.

## Classes

### Dyna-derived classes

## Events and Listeners

Events are broadcast to listeners. Listeners register themselves with the Event object. Uninteresting events can be ignored by simply not listerning for them.

Obviously, the event/listener mechanism is well-suited for asynchronous events, but it's useful in other ways.

Events objects can be centralized in a C++ struct which makes them immediately recognizable as a high-level interface:

    class EngineEvents
    {
    public:
        Event         mEngineReady;       // other modules should listen for this, not gfxsysReady
        Event         mGfxsysReady;
        Event         mExit;
        Event         mAnimate;
        Event         mTick;
    ...
    };

And it provides flexibility. The event sender doesn't need to know who is listening. This solves the problem in a module hierarchy where a base module can't know which functions to call in a higher module. In the following example, the base game module waits for the Engine to become ready. The Engine, which knows nothing about the higher modules, broadcasts an "engine ready" event.

        gEngineEvent.mEngineReady.Listen( Listener_GameBase_EngineReady, start );
    ...
    void
    Listener_GameBase_EngineReady( void )
    {
        gGame.mInputQueue.Enable( &gJoy );
        gGame.Start();  // engine is ready, let's play!
    }

## State-machine

### Rationale for a state-machine

- The motivation for using a state-machine arose from how to implement the AI. AI is driven by a timer, must hold state across frames, and suspend its execution. The AI, implemented as a state-machine, can be progammed using data structures, as opposed to implementing it using explicit code which would be messy and inflexible.
- A maneuver (rotation), controlled by the AI, will be executed in discrete increments that spans multiple frames. Hence the suspend instruction. This is the key requirement that led to a state-machine.
- The pursue AI evaluates a condition (eg the angle between the pursuer and target), initiates a maneuver, suspends execution, then jumps back to re-evaluate the condition. This is the reason for the evaluate, conditional jump, and suspend instructions. (evaluating is done by a C function which returns a bool)
- The pursue AI must check for a collision-pending condition, and if true, calls the collision-avoidance AI. The collision-avoidance AI is a subroutine, which, when the collision is avoided, returns back to the caller. Hence the call/return instructions.

### Features of the state-machine

- simple virtual processor
- 1-bit flag register
- procedure stack
- virtual instructions are coded as C arrays
- execution can be suspended
- instructions to call C functions which return a bool
- procedures (can be called or jumped to)
- instructions can execute conditionally (for speed and terseness)
- labels
- simple run-time compiler (resolves labels)

### State-machine instructions

State-machine code is written as a C array using macros provided by gfx_machine.hh.
*_T/F means to conditionally execute the instruction if the FLAGS register is true/false.
DEF_*() macros are helpers to define SM instructions (assembly-level).
MNEM_* are placeholders for instructions the SM compiler must resolve.
OPC_* are enums of SM opcodes (machine-level).

List of state-machine opcodes (very subject-to-change).

- OPC_FUNC[_T,_F]
- OPC_JUMP[_T,_F]
- OPC_CALL[_T,_F]
- OPC_RETURN[_T,_F]
- OPC_PROC_BEGIN, OPC_PROC_END
- OPC_SUSPEND
- OPC_QUIT
- OPC_NOP, OPC_NOP2
- MNEM_LABEL
- MNEM_RERUN

### Writing state-machine code

Documented here is a guide and the rules for writing state-machine code. For exact details, see gfx_machine.cc.

- Rules for defining a procedure:

- Every procedure must begin with a DEF_PROC_START() macro.

The macro expands OPC_PROC_INFO which has the procedure name and the compiled flag.

- Every procedure must end with a DEF_PROC_END() macro.

The macro expands a MNEM_PROC_END which is a terminator that stops the compiler.

- Calling procedures:

If two procedures references each other (circular reference), the solution is to put each into a separate C++ source file and write extern declarations to each other.

- Jumping inside a procedure:

C++ doesn't support a declaring an array with references to itself:

    Machine::Opcode smproc[] =
    {
        // if ( cond )
        //     func1()
        // func2()
        OPC_EVAL, cond,
        OPC_JNZ, &machine[ index of OPC_EVAL,func2 ],
        OPC_EVAL, func1,
        OPC_EVAL, func2,
        OPC_RETURN
    };

This is solved by run-time compiling using named labels.

    Machine::Opcode smproc[] =
    {
        DEF_PROC_NAME( "example" ),
        OPC_EVAL, cond,
        DEF_JNZ( "func2" ),
        OPC_EVAL, func1,
        DEF_LABEL( "func2" ),
        OPC_EVAL, func2
        OPC_RETURN
    };

DEF_PROC_NAME(), behind the scenes, allocates a hidden element which is a flag to indicate if the procedure has been compiled. It expands to something like:

        OPC_COMPILE_FLAG, false, //<-- compiled flag
        OPC_NAME, "example",

DEF_JNZ_REL() is a macro which expands OPC_JNZ_REL into the array following by a pointer to the label's string. The string must be compiled into a long (indexed by elements, not bytes) that is relative to the start of the procedure.

### Compiling

Compiling resolves label addresses which are the targets of jump instructions.

Compiling does these steps:

- Scans for MNEM_LABEL, stores the labeled addresses, overwrites with OPC_NOP.

      MNEM_LABEL, "again",
      OPC_NOP,    OPC_NOP,    // post-compile

- Scans for MNEM_JZ/JNZ/JMP, overwrites the string pointers with labeled addresses.

      MNEM_JMP, "again"
      OPC_JMP, (an address cast as a long),     // post-compile

### Example of state-machine code

    Machine::Opcode gSmprocPursue[] =
    {
    DEF_PROC_BEGIN( "Pursue" ),          // every procedure must begin with DEF_PROC_BEGIN()

        DEF_CALL( sSmprocPursueSub ),    // call a SM procedure

        DEF_FUNC( PursueIfRoll ),        // call a C function, returns a bool into 1-bit true/false FLAGS register
        DEF_JUMP_F( "SkipRoll" ),        // jump if flag is false (if PursueIfRoll() returns false)
        DEF_FUNC( PursueIfInlineWithTarget ),
        DEF_JUMP_T( "SkipRoll" ),
            DEF_FUNC( PursueRoll ),      // state-machine is suspended
            OPC_SUSPEND,
            DEF_RERUN(),                 // re-run this procedure
        DEF_LABEL( "SkipRoll" ),         // defines a label

        DEF_FUNC( PursueIfPitch ),
        DEF_JUMP_F( "SkipPitch" ),
            DEF_LABEL( "Pitch" ),
            DEF_FUNC( PursuePitch ),
            OPC_SUSPEND,
            DEF_RERUN(),
        DEF_LABEL( "SkipPitch" ),

        DEF_FUNC( PursueIfYaw ),
        DEF_JUMP_F( "SkipYaw" ),
            DEF_LABEL( "Yaw" ),
            DEF_FUNC( PursueYaw ),
            OPC_SUSPEND,
            DEF_RERUN(),
        DEF_LABEL( "SkipYaw" ),
        ...

    DEF_PROC_END()
    };

## GUI

Palomino has its own GUI inside of a viewport.

GUI functionality:

- rendered using alpha-blending (transparency)
- multiple screens (sets of GUI objects)
- object and screen visibility (hide/show)
- text rendering
- text message that time-out
- loadable fonts
- buttons, button callbacks

The GUI is driven by a one-second timer and mouse input. It is rendered after the 3D scene in order to overlay it.

In order to co-operate with the application, the Gui class requires the application to install timer and mouse event handlers and pass those events to Gui methods. Like the 3D simulation code, the GUI code is split into gfxsys dependent/independent parts.

A "screen" is a set of GUI objects. The visibility of either an individual GUI object or a GUI screen can be turned on or off.

The font code originated from the open-source GL-117 flight simulator, and was developed into a Font and FontDesc classes (gfx/src/gfx_font*). Fonts are stored in TGA files and rendered using textures.

When the application creates a button, a callback function can be registered to be called when the button is clicked. Buttons are rendered in OpenGL 2D mode.

The Gui class is responsible for rendering GuiObject objects. GuiObject objects include buttons and messages (text that times-out). But text isn't a GuiObject, ie, Gui::Print() just renders text without creating a GuiObject for the text. To keep text showing, the application must repeatedly call Gui::Print() every frame. The reason is if a line of text were to persist across frames, the Gui class would have to return a handle which the application would later need to stop showing the text (this approach would be more complicated).

## Chase plane

Since every aircraft needs its own independent chase-plane view, every Craft object has its own ChasePlaneCraft object. The ChasePlaneCraft class consists of a distance (from the target) and an ephemeral matrix derived from the target (which is synced with the target every frame if the chase-plane view is selected). Since some code manipulates Craft and Eye objects as base Dyna objects (eg sim_keyboard.cc), the Eye class has a dummy/NOP ChasePlaneBase class.

## Game module: Blue Zaxx

This game module is a mixture of influences from Blue Max and Zaxxon.

## Assorted game ideas

- Standard game elements: radar and map.
- When hit, a craft may shudder, smoke, windshield may crack, etc
- Player can gain better crafts after reaching higher levels.
- Invisibility or cloaking (render craft as wireframe or translucent).
- 3D shields
  When the player is hit and has shields remaining, render a blue transparent plasma halo. When the shields are becoming low, change the color to red.
- Enemy concentrations: tanks, SAMs, jeeps, trucks, etc.
- Battles in progress, player called to assist.
- When player approaches, randomly animate enemy aircraft taking-off or landing.
- Depending on the altitude, player may be pounced by enemy craft, or fired at by Flak or SAMs (Zaxxon).
- Player can get fuel, weapons, or repair by returning to base. While on the runway, player risks being attaked by a pursuing enemy (Blue Max).
- replay
- views: chase, target, from enemy, etc.
- Player can hear roar of enemy's engine when near (athough unrealistic).
- 3D globe radar

A 3D globe radar is meant to give the player an all-around perspective of the surrounding threats. It compensates for the limitation in reality of the user looking at a 2D computer monitor. Imagine a globe drawn as just two opposing circles: latitude and longitude. The player's craft is at the center. Since the player is viewing the 3D globe in front of him at the monitor, targets that are further behind the player's craft will be rendered larger as they're closer to him.

- Closeup of jet taking off:

air shimmers/blurs from jet exhaust
afterburner animation
craft rocking up/down when movement begin

- craft control surface animation

When a F-14 rolls sharply, the tailplanes very noticably rotate, much more than ailerons.

Last modified: Wed Aug 16 16:42:26 EDT 2006