Home

Index Zurück Weiter


7 CD-i-Implementation des Lehrprogramms

Das Ziel und die Struktur des CIMEDIA-Lehrprogramms wurden im Kapitel 2 präsentiert. Im Kapitel 5.4 wurde anhand konkreter Beispiele dargelegt, wie die benötigten RTF-Dateien mit verschiedenen MetaCDL-Befehlen erzeugt werden. Im vorhergehenden Kapitel 6 wurde die CDiC-Bibliothek mit einigen ausgewählten Klassen und Methoden vorgestellt.

In den nun folgenden Abschnitten werden einige ausgewählte Beispiele erläutert. Sie zeigen einerseits die konkrete Verwendung der CDiC-Bibliothek und andererseits auch einige interessante Teile der CIMEDIA-Applikation. Dank der Leistungsfähigkeit der CDiC-Klassen konnte diese umfangreiche CD-i-Applikation mit nur 5000 Zeilen C-Code implementiert werden.

Wir betrachten zuerst die zur Initialisierung der CDiC-Bibliothek und der Applikation notwendigen Schritte. Danach zeigen wir, wie eine Benutzerschnittstelle mit Hilfe der CDiC-Klassen implementiert wird. Abschliessend werden wir einige der interessantesten Datenstrukturen und Algorithmen präsentieren, die zur Implementation der Interviews, des Fabrikrundgangs und der Flugansicht entwickelt wurden.

7.1 Initialisierung

Die CD-i-Applikation startet, wie alle C-Programme, mit der main() Funktion.

// File: main.c
main()
{
cdiGlobalsCreate(); // create the CDiC globals
cdiGlobalsSetDebug(CDi_DEBUG_APPLICATION); // set current debug mode
cdiDispatch(CDi_EVENT_APPLICATION_CREATE, // add applicationInit to
applicationInit, CDi_NULL); // dispatcher.
cdiDispatcherLoop(); // the main loop
}

Zuerst werden mit cdiGlobalsCreate() alle globalen Variablen der CDiC-Bibliothek erzeugt und initialisiert. Diese werden in der Kontainerklasse CdiGlobals zusammengefasst und verwaltet [262].

Die Klasse CdiDebug vereinfacht die Fehlersuche, indem sie dem Programmierer via Terminal wichtige Informationen präsentiert. So können etwa Angaben über die auftretenden Ereignisse, die momentane Speicherbelegung, verschiedene wichtige Funktionsargumente oder auch Angaben über die Laufzeiten der Ereignisfunktionen angefordert werden. Mit der Funktion cdiGlobalsSetDebug() können diese Informationen nach den verschiedensten Kriterien ausgewählt werden.

Bevor cdiDispatcherLoop() die Endlosschleife des CdiDispatchers startet, wird ihm die erste Ereignisfunktion (applicationInit) übergeben, die einige Objekte der Applikation erzeugt. Im nächsten Codeauszug werden die wesentlichen Aufgaben dieser Initialisierungsfunktion aufgezeigt.

// File: cimedia.c
static short ourCursorHandBit[] = // get cursor pattern 'hand'
{
#include "hand.icon" // include iconformat 1
};
applicationInit(CdiInt event, void* parameter)
{
// init. some application global vars.
gCimScreen = cdiScreenCreate(CDi_RESOLUTION_LOW); // the cimedia screen
gCimMemoryPlane0 = cdiMemoryCreate( // get memory for plane A
CDi_ROUNDSECT2(280 * (CDi_SCREEN_X>>1)),
CDi_MEMORY_VIDEO_A);

gCimFileUi = cdiFileOpenRealTime("Ui.rtf"); // open userinterface file
gCimCursorHand = cdiCursorCreate(); // create cursor
cdiCursorSetPattern(gCimCursorHand, ourCursorHandBit);
cdiCursorSetHitPoint(gCimCursorHand, 8, 8);
// start with intro1 (Vorspann)
cdiDispatch(CDi_EVENT_APPLICATION_CREATE, uiIntro1, 0);
cdiDispatch(CDi_EVENT_APPLICATION_SHOW, uiIntro1, 0);
}

Zuerst wird ein [263] CdiScreen (gCimScreen) in der normalen PAL-Auflösung erstellt. Gleich anschliessend werden die grössten Speicherblöcke (gerundet auf die Form-2-Sektorgrösse) mit Hilfe der Funktion cdiMemoryCreate() angefordert [264].

Das Öffnen einer RTF-Datei benötigt, bedingt durch den langsamen CD-Leser, relativ viel Zeit. Da die Daten der Benutzerschnittstellen immer wieder gelesen werden müssen, wird nun die entsprechende Datei (Ui.rtf) mit cdiFileOpenRealTime() geöffnet und erst bei Programmende wieder geschlossen.

In der CDiC-Bibliothek werden zwei Cursorformen [265] zur Verfügung gestellt. Für jede zusätzliche Form muss die Definitionsdatei des Cursors [266] in eine Variable eingelesen werden, die danach von der Funktion cdiCursorSetPattern() verwendet wird. Falls nötig, kann anschliessend der Referenzpunkt mit cdiCursorSetHitPoint() in die Mitte verschoben werden.

Jetzt sind alle allgemeinen Objekte der Applikation erzeugt. Die erste Benutzerschnittstelle wird aufgerufen, indem ihr die Ereignisse CREATE und SHOW übermittelt werden.

7.2 Benutzerschnittstelle

Das Hauptmenü [267] von CMEDIA ist eine typische Benutzerschnittstelle. Exemplarisch für alle anderen betrachten wir nun im folgenden, wie dieses Menü implementiert wird.

7.2.1 C-Deklarationsdateien

// File: uiSelect.c
#include "Cdi.h" // use CDiC lib.
#include "CdlUi.h" // MetaCDL scene 'UI'
#include "ui.h" // our userinterface defs

Zunächst müssen die benötigten C-Deklarationsdateien eingefügt werden.

7.2.2 Statische Objekte

Nun müssen alle statischen Objekte definiert werden. Dies sind insbesondere alle Elemente der CdiUiObject-Liste.

static CdiSubScreen* ourSubScreenUi = CDi_NULL; // ui subscreen
static CdiUiObjectList* ourObjectList = CDi_NULL; // list of UiObjects
static CdiUiObject* ourClut = CDi_NULL; // clut for all UiObjects
static CdiUiObject* ourClickSelect = CDi_NULL; // the click soundmap
static CdiUiObject* ourUiObjIn = CDi_NULL; // Intro-button
static CdiUiObject* ourUiObjGu = CDi_NULL; // Guide-button
static CdiUiObject* ourUiObjHe = CDi_NULL; // Heli-button
static CdiUiObject* ourUiObjNa = CDi_NULL; // Navigation-button
static CdiUiObject* ourUiObjLo = CDi_NULL; // Look-button
static CdiUiObject* ourUiObjCr = CDi_NULL; // Credits-button
static CdiUiObject* ourUiObjEx = CDi_NULL; // Exit-button
static CdiEventFct ourNextFct = CDi_NULL; // the next event function

Alle Elemente der Benutzerschnittstelle sind vom Typ CdiUiObject. Es spielt also keine Rolle, ob später daraus eine Befehls-, eine Umschaltfläche, eine Soundmap oder eine Farbtabelle definiert wird [268].

7.2.3 Ereignisfunktionen

Bei der Definition der Befehlsschaltflächen müssen Ereignisfunktionen angegeben werden, die nach einem Klick auf dem entsprechenden Knopf aufgerufen werden. Im nächsten Codeauszug werden die Ereignisfunktionen der Schaltflächen 'Einleitung' [269] und 'Begleitung' [270] präsentiert.

inHandler(CdiInt event, void* parameter) // Intro-button
{
CDi_DEBUG_EVENT(CDi_DEBUG_APPLICATION, inHandler); // show debug info
ourNextFct = uiIntro1; // set next event fct (uiIntro1)
cdiDispatch(CDi_EVENT_APPLICATION_DESTROY, uiSelect, CDi_NULL);
}
guHandler(CdiInt event, void* parameter) // Guide-button
{
CDi_DEBUG_EVENT(CDi_DEBUG_APPLICATION, guHandler); // show debug info
ourNextFct = uiGuide; // set next event fct (uiGuide)
cdiDispatch(CDi_EVENT_APPLICATION_DESTROY, uiSelect, CDi_NULL);
}

Die Funktionsnamen uiIntro() und uiGuide() sind die Eintrittspunkte in die entsprechenden Benutzerschnittstellen. In der Ereignisfunktion einer Schaltfläche wird zuerst der nächste Eintrittspunkt in der Variablen ourNextFct zwischengespeichert. Anschliessend wird der Funktion uiSelect() ein DESTROY-Ereignis übermittelt.

Der Eintrittspunkt des Hauptmenüs ist die Ereignisfunktion uiSelect(), die im wesentlichen auf die Ereignisse CREATE, SHOW und DESTROY der Ereignisklasse APPLICATION reagiert.

uiSelect(CdiInt event, void* parameter) // main menu
{
CDi_DEBUG_EVENT(CDi_DEBUG_APPLICATION, uiSelect); // show debug info
switch (CDi_EVENT_GET_CLASS(event)) { // get event class
case CDi_EVENT_CLASS_APPLICATION: // event class application
switch (event) { // get event info
case CDi_EVENT_APPLICATION_CREATE: // create subscreen
ourSubScreenUi = cdiSubScreenCreate(CDi_PLANE_A, // in plane A
CDi_PICTURE_TYPE_CLUT8, // set coding to CLUT8
CDi_SCREEN_OFFSET_Y, CDi_SAVE_SCREEN_Y, // subscreen offset and height
CDi_PICTURE_ALIGN_FORM2, gCimMemoryPlane0); // subscreen memory
createUiObjects(); // init UiObject list
break;
case CDi_EVENT_APPLICATION_SHOW: // show menu
cdiUiObjectListLoad(ourObjectList); // load UiObject from disc
break;
case CDi_EVENT_APPLICATION_DESTROY:
cdiScreenRemoveAll(gCimScreen); // remove subScreen
cdiSubScreenDestroy(CDi_EVENT_APPLICATION_DESTROY, ourSubScreenUi);
cdiDispatch(CDi_EVENT_APPLICATION_CREATE, ourNextFct, CDi_NULL);
cdiDispatch(CDi_EVENT_APPLICATION_SHOW, ourNextFct, CDi_NULL);
break;
}
break; // CLASS_APPLICATION
case CDi_EVENT_CLASS_UI_OBJECT: // event class UiObject
switch (event) {
case CDi_EVENT_UI_OBJECT_OK: // afterloading
cdiScreenAppend(gCimScreen, ourSubScreenUi); // append subScreen to screen
cdiScreenShow(gCimScreen); // show global screen
break;
}
break; // CLASS_UI_OBJECT
}
}

Nach dem CREATE-Ereignis wird der Bildstreifen (ourSubScreenUi) erzeugt und die Funktion createUiObjects() aufgerufen, die je nach verwendetem Design der Benutzerschnittstelle die richtige CdiUiObject-Liste generiert [271].

Die Daten werden dabei jedoch noch nicht aus der RTF-Datei (ui.rtf) geladen. Dieser Ladevorgang wird erst durch das Ereignis SHOW ausgelöst, indem die Funktion cdiUiObjectListLoad() aufgerufen wird. Die entstehende Wartezeit wird durch das spezielle Layout der Datei ui.rtf auf das Minimum reduziert.

Das Ereignis OK der Ereignisklasse UI_OBJECT bestätigt uiSelect(), dass alle Daten korrekt gelesen wurden. Danach wird ourSubScreenUi in die Bildschirmliste eingefügt und das Hauptmenü mit cdiScreenShow() auf dem Monitor dargestellt.

Empfängt uiSelect() das DESTROY-Ereignis, dann wird ourSubScreenUi wieder zerstört und die nächste Benutzerschnittstelle, deren Eintrittspunkt in ourNextFct gespeichert wurde, durch Übermittlung der Ereignisse CREATE und SHOW aufgerufen.

7.2.4 Aufbau der CdiUiObject-Liste

Betrachten wir uns nun noch den Code zur Definition der CdiUiObject-Liste des CIMEDIA-Hauptmenüs im Felldesign. Die Bild- und Tondaten wurden durch ein MetaCDL-Skript in der Szene FESE [272] eingefügt.

#define NUM_OF_UI_OBJ 13 // number of ui objects
createObjectsLookFe() // called from createUiObjects()
{
ourObjectList = cdiUiObjectListCreate( // create the UiObject-list
NUM_OF_UI_OBJ, // fixed size => less dynamic // allocation
gCimFileUi, // RTF-file with object data
ourSubScreenUi, // subScreen to paint in
gCimCursorArrowGreen, // cursor on subScreen
CDL_FIRSTSECTOR_SCENE_FESE, // start pos. in file (scene FESE)
CDL_CHAN_IDAT_FESE_FESEALL_cl8, // channel of base pict
CDL_SIZE_FESE_FESEALL_cl8, // size in form 2 sectors
uiSelect, CDi_NULL); // event fct after loading

Mit cdiUiObjectListCreate() wird eine Liste erzeugt, in der sofort 13 (noch leere) Elemente der Benutzerschnittstelle initialisiert werden [273]. Da jede Benutzerschnittstelle auf einem Hintergrundbild (feseall.cl8) basiert, wird zusätzlich noch dessen genaue Position und Grösse in der RTF-Datei angegeben.

Im nächsten Schritt wird die Farbtabelle definiert, die alle Bilder der Benutzerschnittstelle verwenden müssen.

ourClut = cdiUiObjectCreateClut(ourObjectList, // make clut object
CDL_CHAN_PLTE_FESE_FESEALL_cl8); // channel of clut

Die Angabe der Sektorgrösse (CDL_SIZE) entfällt hier, da immer genau ein Sektor verwendet wird [274]. Nun folgt die Definition der Soundmap, die nach einem Klick ertönen soll.

// make soundmap object
ourClickSelect = cdiUiObjectCreateSoundmap(ourObjectList,
D_CMONO, // audio level-c-mono
CDL_CHAN_AUDIO_FESE_LION_acm, CDL_SIZE_FESE_LION_acm); // filename: lion.acm

Am Beispiel der Befehlsschaltfläche 'Einleitung' wird nun gezeigt, wie die einzelnen Steuerelemente erzeugt werden.

// make Intro push button
ourUiObjIn = cdiUiObjectCreatePushButton(ourObjectList,
UI_FESE_IN_X, UI_FESE_IN_Y, // x,y position
UI_FESE_OF_W, UI_FESE_OF_H, // width, height
gCimCursorArrowRed, // cursor on this push button
ourClickSelect, // soundmap after click
inHandler); // event fct after click

Die Funktion cdiUiObjectCreatePushButton() generiert eine Schaltfläche mit den angegebenen Geometriedaten. Die weiteren Argumente bestimmen den Cursor, die Soundmap und die Ereignisfunktion, die für dieses Steuerelement verwendet werden.

Bisher wurden der Schaltfläche ourUiObjIn noch keine Bilddaten zugeordnet. Diese Aufgabe übernimmt die Funktion cdiUiObjectAddPicturesPushButton().

// set picturelist
cdiUiObjectAddPicturesPushButton(ourObjectList, ourUiObjIn,
CDL_CHAN_IDAT_FESE_FESEINON_cl8, CDL_SIZE_FESE_FESEINON_cl8, // pressed
CDL_CHAN_IDAT_FESE_FESEINOF_cl8, CDL_SIZE_FESE_FESEINOF_cl8, // normal
CDi_NULL, CDi_NULL); // inactive

Normalerweise wird diese Schaltfläche mit den Daten aus der Asset-Datei feseinof.cl8 dargestellt. Falls der Benutzer die Maustaste drückt, dann wird das Bild aus feseinon.cl8 an die entsprechende Stelle im Hintergrundbild kopiert. Für Schaltflächen, die zeitweilig inaktiv gesetzt werden, kann ein drittes Bild angegeben werden.

7.3 Interviews

Alle Bild- und Tondaten der Interviews sind in einer RTF-Datei gespeichert, die im Abschnitt 5.4.2.1 beschrieben wurde. Bei der Wiedergabe wird grundsätzlich immer zuerst ein Bild dargestellt und anschliessend die dazugehörige Tonsequenz abgespielt. Durch die Anordnung der Bilder in den verschiedenen Kanälen kann die Steuerung der Bilddarstellung sehr einfach realisiert werden.

// File: Scenen.c
sceneFileHandler(CdiInt event, void* parameter)
{
switch (CDi_EVENT_GET_CLASS(event)) {
case CDi_EVENT_CLASS_FILE_PCL: // PCL event
switch (CDi_EVENT_GET_INFO(event)) { // channel no. in event info
case CHANNEL_PLTE_CLUT7:
... // install clut in fct
break;
case CHANNEL_CLUT7:
... // show clut7 pict in plane A
break;
case CHANNEL_DYUV:
... // show dyuv pict in plane B
break;
case CHANNEL_TRANSP7:
... // mix clut7 pict in plane A with plane B
break;
}
break;
}
}

Die Ereignisfunktion sceneFileHandler() wird nach jedem PCL-Signal aufgerufen, also immer nachdem der Puffer eines Kanals vollständig gefüllt wurde. Da die Kanalnummer als Ereignisinformation übermittelt wird und bei der Definition der RTF-Datei ein festes Schema zur Verteilung der Kanäle verwendet wurde, kann sehr einfach untersucht werden, welches Bild gerade gelesen wurde. Dieses wird auf dem Monitor präsentiert.

Zur Verwaltung der Interview-RTF-Datei werden verschiedene Datenstrukturen benötigt. Die wichtigste dieser Strukturen ist InterviewData.

typedef struct{
CdiInt firstVideoSector; // first video sector in rtf
CdiInt firstAudioSector; // first audio sector in rtf
CdiInt lastAudioSector; // last audio sector in rtf
CdiInt audioChannel; // used audio channel in rtf
CdiInt dyuvSize; // dyuv buffer size in form 2 sectors
CdiInt clut7TranspSize; // clut7(T) buffer size in form 2 sectors
CdiInt clut7Size; // clut7 buffer size in form 2 sectors
ClickControl* clickControl; // pointer to clickControl block
}InterviewData;

Für jedes durchgeführte Interview wird ein derartiger 'InterviewData'-Block erstellt. Der nächste Codeauszug zeigt den Interviewblock der Berufsberaterin mit Herrn Barmet.

InterviewData interviewData_BERUF_BARMET_ = { // guide:Beruf - empl.:Barmet
CDL_FIRSTSECTOR_SCENE_BERUF_BARMET_, // first video sector in rtf
CDL_FIRSTSECTOR_AUDIO_SCENE_BERUF_BARMET_, // first audio sector in rtf
CDL_LASTSECTOR_AUDIO_SCENE_BERUF_BARMET_, // last audio sector in rtf
CDL_AUDIO_CHAN_SCENE_BERUF_BARMET_, // used audio channel in rtf
CDL_DYUV_SIZE_SCENE_BERUF_BARMET_, // dyuv buffer size in form 2 sectors
CDL_CLUT7_TRANSP_SIZE_SCENE_BERUF_BARMET_, // clut7(T) buffer size in form 2 sectors
CDL_CLUT7_SIZE_SCENE_BERUF_BARMET_, // clut7 buffer size in form 2 sectors
clickControl_BERUF_BARMET_ // pointer to clickControl block
};

Falls ein Interview gestartet werden soll, dann können alle hierzu notwendigen Daten dem entsprechenden 'InterviewData'-Block entnommen werden. Zuerst wird ab der Position firstVideoSector ein Bild geladen und daraufhin die Tonsequenz ab firstAudioSector gestartet. Diese wird durch ein EOR-Bit im Subheader der RTF-Datei beendet. Das nächste Bild kann jetzt ab der Position des letzten Bildes geladen werden. Wie in einem Pingpong-Spiel wechseln sich Bild- und Tonwiedergabe ab, bis die Sektorposition lastAudioSector überschritten wird. Damit ist das Interview beendet.

Die Angaben über die Kanalnummer der Tonspur (audioChannel) und die Sektorgrössen der Bildtypen werden zur Initialisierung des Lesevorgangs eingesetzt.

Während der Wiedergabe eines Interviews kann der Benutzer mit einem Mausklick zur nächsten Frage springen. Dies unterbricht den oben erwähnten Lesevorgang. Zu seiner Fortsetzung werden die Anfangspositionen der Bild- und Tonsequenz der nächsten Frage benötigt.

typedef struct {
CdiInt firstVideo; // first video sector for question
CdiInt firstAudio; // first audio sector for question
}ClickControl;

Diese statischen Daten werden mit dem MetaCDL-Befehl InsertClickFileClut7 automatisch erzeugt. Der folgende Codeauszug zeigt den Beginn einer derartigen Struktur.

ClickControl clickControl_BERUF_BARMET_[] = { // guide:Beruf - empl.:Barmet
CDL_FIRSTSECTOR_IDAT_BERUF_BARMET__TAEGARBE_cl7, // first video sector for question 1
CDL_FIRSTSECTOR_AUDIO_BERUF_BARMET__F01_acm, // first audio sector for question 1
CDL_FIRSTSECTOR_IDAT_BERUF_BARMET__REGELMAE_cl7, // first video sector for question 2
CDL_FIRSTSECTOR_AUDIO_BERUF_BARMET__F02_acm, // first audio sector for question 2
... // all other questions ...

Führt der Benutzer einen Klick aus, nachdem die Frage F01.acm (aber noch nicht F02.acm) gestellt wurde, dann wird das Interview ab der Position des Bildes REGELMAE.CL7 fortgeführt.

Der Benutzer startet ein Interview, indem er einen Raum betritt. In der 'InterviewControl'-Datenstruktur werden für jeden Fabrikraum eine eindeutige Nummer (id) und je Begleiter ein Zeiger auf den entsprechenden 'InterviewData'-Block gespeichert.

typedef struct{
CdiInt id;
InterviewData* beruf; // Berufsberaterin
InterviewData* cim; // CIM-Ingenieur
InterviewData* mto; // Arbeitspsychologe
InterviewData* smuv; // Gewerkschafter
}InterviewControl;

Der nächste Codeauszug zeigt die 'InterviewControl'-Daten der Räume '6' und '7'. Im Raum mit der Nummer '6' haben alle Begleiter (BERUF, CIM, MTO, SMUV) ein Gespräch durchgeführt. Je nachdem, welcher Begleiter das Interview durchführt, werden in diesem Raum die Personen Meury, Schmid, Sattes oder Boll befragt. Im Raum '7' haben zwei Begleiter (Arbeitspsychologe, Gewerkschafter) keine Interviews durchgeführt. Daher werden die entsprechenden Zeiger mit '0' initialisiert.

InterviewControl interviewControl[] = {
... // room 1-5
6, &interviewData_BERUF_MEURY_, &interviewData_CIM_SCHMID_, // room 6
&interviewData_MTO_SATTES_, &interviewData_SMUV_BOLL_,

7, &interviewData_BERUF_KALT_, &interviewData_CIM_KALT_, 0, 0, // room 7
... // room 8-31
};

Diese Datenstruktur wurde mit Hilfe einer Datenbank [275] erzeugt.

7.4 Fabrikrundgang

Zur Implementation des Fabrikrundgangs wurde ein von der HyperStudio AG produzierter 160 MByte grosser Quicktime-Film in 97 Teilsequenzen zerlegt. Die Startpositionen der Sequenzen wurden erfasst und zur Erstellung einer Verzeichnisstruktur (ein Verzeichnis pro Filmsequenz) verwendet. Nach der Konvertierung des Quicktime-Films in mehrere tausend DYUV-Bilder mussten diese in das richtige Verzeichnis verschoben werden. Anschliessend wurden die Bilder aller 97 Verzeichnisse mit dem MetaCDL-Befehl InsertCdiMovieDyuvEor in einer RTF-Datei gespeichert. Am Ende jeder Sequenz wurde durch den MetaCDL-Befehl das EOR-Bit gesetzt. Dadurch kann die Wiedergabe einer Filmsequenz beendet werden.

Am Ende jeder Sequenz kann der Benutzer eine Entscheidung fällen, in welche Richtung er sich bewegen will. Er kann zwischen den Möglichkeiten 'Links', 'Rechts', 'Geradeaus', und 'Zurück' wählen. Je nachdem, wo er sich gerade befindet, wird daraufhin entweder der Rundgang in der entsprechenden Richtung fortgesetzt, oder er öffnet eine Türe und startet damit das Interview im gewählten Raum.

Zur Kontrolle dieses Rundgangs wird eine 'FabrikControl'-Datenstruktur definiert, die im folgenden Codeauszug präsentiert wird.

// File: Fabrik.c
typedef struct {
CdiInt id; // movie no.
CdiInt first; // first sector in rtf
CdiInt last; // last sector in rtf
CdiInt back; // movie no. (back)
CdiInt left; // (-) room / (+) movie no. (left)
CdiInt straight; // (-) room / (+) movie no. (straight)
CdiInt right; // (-) room / (+) movie no. (right)
}FabrikControl;

Jeder Filmsequenz wird eine eindeutige Nummer (id) zugeordnet. Die Daten aus first und last speichern die jeweilige Anfangs- und Endposition. Alle anderen Werte (back, left, straight und right) definieren, in welche Richtungen der Weg fortgesetzt werden kann. Im nächsten Codeauszug werden die Daten der Filmsequenzen '5' bis '8' dargestellt.

extern FabrikControl fabrikControl[] = {
... // movie 1-4
5,CDL_FIRSTSECTOR_MOVIE_FABRIK_5,CDL_LASTSECTOR_MOVIE_FABRIK_5,53,-14,6,-31,
6,CDL_FIRSTSECTOR_MOVIE_FABRIK_6,CDL_LASTSECTOR_MOVIE_FABRIK_6,54,-15,7,-30,
7,CDL_FIRSTSECTOR_MOVIE_FABRIK_7,CDL_LASTSECTOR_MOVIE_FABRIK_7,55,-16,8,0,
8,CDL_FIRSTSECTOR_MOVIE_FABRIK_8,CDL_LASTSECTOR_MOVIE_FABRIK_8,56,-17,9,0,
... // movie 9-97
};

Die positiven Nummern definieren die nächste Filmsequenz, wogegen die negativen Werte die Raumnummer angeben. Wird eine '0' gesetzt, dann kann der Weg in dieser Richtung nicht weitergeführt werden. Beispielsweise kann am Ende der Filmsequenz '5' der Weg mit der Sequenz '53' wieder rückwärts beschritten werden oder aber in gerader Richtung mit dem Filmteil '6' weiterverfolgt werden. Auf der linken Seite befindet sich der Raum '14', und rechts ist die Türe des Raumes '31' sichtbar. Auch diese Struktur wurde mit einer Datenbank entwickelt.

Da nicht alle Begleiter in allen Räumen Interviews durchgeführt haben, sind immer einige Türen unsichtbar, d.h. sie können nicht geöffnet werden. So kann der Benutzer zum Beispiel nur dann in den Raum 7 eintreten, wenn er den CIM-Ingenieur oder die Berufsberaterin als Begleiter gewählt hat [276]. Der Algorithmus zur Filmwiedergabe muss daher überprüfen, ob am Ende einer Filmsequenz gleich eine weitere gestartet werden muss.

// File: uiNavig.c
ourMovieHandler(CdiInt event, void *parameter)
{
switch (CDi_EVENT_GET_CLASS(event)) {
case CDi_EVENT_CLASS_MOVIE:
switch (event) {
case CDi_EVENT_MOVIE_PLAY_OK: // end of last sequence
currentControl = getCurrentControl(); // get current fabrikControl
if ( (no interview available) and (only one direction available) ){
start movie at CDL_FIRSTSECTOR position in this direction
} else { // set buttons inactive
if (currentControl->left == 0) cdiUiObjectSetInactive(ourUiObjLeft);
if (currentControl->right == 0) cdiUiObjectSetInactive(ourUiObjRight);
if (currentControl->straight == 0)
cdiUiObjectSetInactive(ourUiObjStraight);
}
break;
}
break;
}
}

Befinden sich beispielsweise links und rechts (für den aktuellen Begleiter) unsichtbare Türen, dann wird ohne Benutzeraktion der Rundgang in gerader Richtung weitergeführt. Falls mindestens zwei Richtungen zur Auswahl stehen [277], dann wird der Film nicht fortgesetzt, sondern dem Benutzer die Richtungsangabe überlassen. Allerdings müssen vorher noch mit cdiUiObjectSetInactive() alle Schaltflächen inaktiv gesetzt werden, die in eine ungültige Richtung führen.

Die folgende Ereignisfunktion wird ausgeführt, wenn der Benutzer die 'Links'-Schaltfläche ausgewählt hat.

leftHandler(CdiInt event, void* parameter)
{
currentControl = getCurrentControl(); // get current fabrikControl
if(currentControl->left > 0) { // >0: => continue movie
start movie at CDL_FIRSTSECTOR position in left direction
}
if(currentControl->left < 0) { // <0: => show interview
start interview with interviewData from index -(currentControl->left)
}
}

Wenn der left-Wert in der aktuellen Zeile der 'FabrikControl'-Tabelle positiv ist, dann wird die Filmsequenz gestartet, die dieser Nummer entspricht. Ist der Wert negativ, dann definiert sein Betrag die Raumnummer (InterviewControl.id), die verwendet werden soll, um das Interview abzuspielen.

7.5 Flugansicht

Zur Programmierung der Flugansicht wird das Übersichtsbild in rechteckige Zellen eingeteilt. Die Nummern im rechten Teil der Abbildung 7-1 bezeichnen 81 einfache (d.h. nicht zusammengesetzte) derartige Bereiche.

Abbildung 7-1: Aufteilung der Übersicht in einzelne Bereiche

Die Bezeichnungen zusammengesetzter Zellen bestehen aus den Nummern in der linken oberen und der rechten unteren Ecke. Beispielsweise bezeichnet '0020' den 3*3-Bereich oben links. Das Übersichtsbild im linken Teil der Abbildung 7-1 wird in dreizehn zusammengesetzte Zellen [278] eingeteilt, für die je ein Vergrösserungsbild existiert. Im linken Teil der Abbildung 7-2 wird die Vergrösserung des Bereichs '0020' dargestellt.

Abbildung 7-2: Flugansicht mit dem Bild '0020'

In der 'HotCells'-Datenstruktur werden die zur Implementierung notwendigen Informationen gespeichert.

typedef struct {
CdiHotspot* hotspot; // hotspot
CdiInt id; // interviewControl id (=room no.)
CdiInt x; // position x
CdiInt y; // position y
CdiInt w; // width
CdiInt h; // height
}HotCells;

Die Geometriedaten (x, y, w, h) werden zur Erzeugung der verschiedenen CdiHotspot-Objekte benötigt. Nach einem Klick auf einem CdiHotspot können mit Hilfe der Raumnummer (id) die benötigten Interviewdaten bestimmt werden. In der 'HotCells'-Struktur cells0080 (Übersichtsbild) werden alle Raumnummern mit '0' initialisiert. Im folgenden Codeauszug werden die Daten des Bereichs '0020' dargestellt. Die Raumnummern stammen aus dem rechten Teil der Abbildung 7-2.

static HotCells cells0020[] = { // upper left 3*3 block
CDi_NULL, 2, 42, 13, 39, 48, // room 2
CDi_NULL, 3, 82, 12, 39, 49, // room 3
CDi_NULL, 5, 151, 23, 33, 38, // room 5
CDi_NULL, 11, 81, 111, 38, 33, // room 11
CDi_NULL, 10, 81, 80, 38, 30, // room 10
CDi_NULL, 9, 122, 79, 60, 23 // room 9
};

Anhand dieser Daten können nun die einzelnen CdiHotspot-Objekte erzeugt werden. Da manche Räume, je nach aktuellem Begleiter, nicht betretbar sind, dürfen nur diejenigen CdiHotspots generiert werden, deren 'InterviewControl'-Element einen 'InterviewData'-Block des aktuellen Begleiters enthält.

Die Bilder der Flugansicht wurden von der HyperStudio AG in vier Varianten entworfen, so dass alle betretbaren Räume farbig und alle anderen nur schwarzweiss dargestellt werden. Für jeden Begleiter wird daher ein eigenes Bilderset gespeichert. Jedes dieser Sets besteht gemäss der Numerierung in Abbildung 7-1 aus 81 einzelnen Bilddateien. Damit diese nicht zur Laufzeit von der CD-i-Applikation zusammengesetzt werden müssen, wurden am IAM für jeden Begleiter vierzehn einzelne Bilder [279] erstellt.

Um die gewünschten Bilddaten laden zu können, wird für jeden Begleiter eine Datenstruktur definiert, in der die Anfangspositionen aller Bilder gespeichert werden. Da alle denselben Kanal belegen, ist die Angabe der Kanalnummer nicht notwendig. Im folgenden wird ein Teil dieser Datenstruktur (für den CIM-Ingenieur) wiedergegeben.

static CdiInt ourCIHE[] = // guide: cim
{
CDL_FIRSTSECTOR_IDAT_CIHE_CIHE0080_yuv, // first sector cell pict 0080
CDL_FIRSTSECTOR_IDAT_CIHE_CIHE0020_yuv, // first sector cell pict 0020
CDL_FIRSTSECTOR_IDAT_CIHE_CIHE0323_yuv, // first sector cell pict 0323
...
};

Die Ereignisfunktion hotHandler() wird aufgerufen, wenn der Benutzer auf einem Bereich (CdiHotspot) einen Klick ausführt.

hotHandler(CdiInt event, void* parameter) // after click on cell
{
clickedCell = getLastClickedCell(parameter);

if clickedCell is part of cells0080 { // belongs to overview
load cell picture for current guide // zoom in
} else {
get interviewData and show interview // enter room
}
}

Zuerst wird der angeklickte Bereich ermittelt. Falls dieser zum Übersichtsbild gehört, dann wird das entsprechende Vergrösserungsbild geladen. Ansonsten wird der 'InterviewData'-Block des angeklickten Raumes bestimmt und dann dieses Interview wiedergegeben.


Fussnoten

[262] Während dem Schreiben einer globalen (32 Bit) Variable dürfen keine Signale behandelt werden. Sonst könnte es passieren, dass in einer Signalroutine die noch nicht vollständig definierte Variable schon verwendet wird (16-Bit-Datenbus). Ebenso muss das Lesen derjenigen Variablen abgeschirmt sein, die durch Signalroutinen gesetzt werden können.

[263] Die gleichzeitige Verwendung mehrerer Bildschirme ist mit der CDiC-Bibliothek möglich. In der CIMEDIA-Applikation wird aber nur ein globaler Bildschirm eingesetzt.

[264] Vergleiche hierzu Abschnitt 4.4.7.

[265] cursorArrow und cursorBusy.

[266] In diesem Beispiel "hand.icon".

[267] Vergleiche hierzu Abbildung A-4.

[268] Vergleiche hierzu Abschnitt 7.2.4.

[269] Die Schaltfläche 'Einleitung' startet den Vorspann (Intro).

[270] Die Schaltfläche 'Begleitung' führt zur Begleiterwahl (Guide).

[271] Vergleiche hierzu Abschnitt 7.2.4.

[272] Fell Select.

[273] Die Anzahl der Listenelemente wird übergeben, damit nur einmal dynamisch Speicher reserviert werden muss.

[274] In einer Farbtabelle können maximal 256 Einträge zu je 4 Byte gespeichert werden. Die Sektorgrösse ist mit 2324 Byte mehr als doppelt so gross.

[275] Eingesetzt wurde das Programm Access der Firma Microsoft.

[276] Vergleiche hierzu Abschnitt 7.3.

[277] Die Richtung 'Rückwärts' wird dabei nicht mitgezählt.

[278] Das Übersichtsbild wird in die dreizehn Bereiche' 0080', '0020', '0323', '0626', '2747', '3050', '3353', '5474', '5777', '6080', '0525', '3252', '5979' und '2343' eingeteilt.

[279] Ein Übersichts- und dreizehn Vergrösserungsbilder.


Home

Index Zurück Weiter