Bei LoA kommt es an einigen Stellen bei manchen Spielern, insbesondere beim Übergang von Außenwelt in Portalräume, zu schweren Rucklern. Das Problem scheint sich lösen zu lassen, wenn ich bei (allen / den problematischen?) Lichtern das flag zCVobLight_bitfield_isStatic entferne.
Jetzt möchte ich gerne ein bisschen besser verstehen, was passiert sein könnte, bzw. wie das alles funktioniert.
Die Klasse zCVobLight hat das Attribut bitfield, für das es unter anderem ein Flag zCVobLight_bitfield_isStatic gibt. Aus Ikarus:
Code:
//--------------------------------------
// Light
//--------------------------------------
const int zCVobLight_bitfield_isStatic = ((1 << 1) - 1) << 0;
const int zCVobLight_bitfield_rangeAniSmooth = ((1 << 1) - 1) << 1;
const int zCVobLight_bitfield_rangeAniLoop = ((1 << 1) - 1) << 2;
const int zCVobLight_bitfield_colorAniSmooth = ((1 << 1) - 1) << 3;
const int zCVobLight_bitfield_colorAniLoop = ((1 << 1) - 1) << 4;
const int zCVobLight_bitfield_isTurnedOn = ((1 << 1) - 1) << 5;
const int zCVobLight_bitfield_lightQuality = ((1 << 4) - 1) << 6;
const int zCVobLight_bitfield_lightType = ((1 << 4) - 1) << 10;
const int zCVobLight_bitfield_m_bCanMove = ((1 << 1) - 1) << 14;
const int zCVobLight_lightData_colorAniList_array_offset = 300; //0x12C
class zCVobLight {
//zCVob {
//zCObject {
var int _vtbl;
var int _zCObject_refCtr;
var int _zCObject_hashIndex;
var int _zCObject_hashNext;
var string _zCObject_objectName;
//}
var int _zCVob_globalVobTreeNode;
var int _zCVob_lastTimeDrawn;
var int _zCVob_lastTimeCollected;
var int _zCVob_vobLeafList_array;
var int _zCVob_vobLeafList_numAlloc;
var int _zCVob_vobLeafList_numInArray;
var int _zCVob_trafoObjToWorld[16];
var int _zCVob_bbox3D_mins[3];
var int _zCVob_bbox3D_maxs[3];
var int _zCVob_bsphere3D_center[3];
var int _zCVob_bsphere3D_radius;
var int _zCVob_touchVobList_array;
var int _zCVob_touchVobList_numAlloc;
var int _zCVob_touchVobList_numInArray;
var int _zCVob_type;
var int _zCVob_groundShadowSizePacked;
var int _zCVob_homeWorld;
var int _zCVob_groundPoly;
var int _zCVob_callback_ai;
var int _zCVob_trafo;
var int _zCVob_visual;
var int _zCVob_visualAlpha;
var int _zCVob_m_fVobFarClipZScale;
var int _zCVob_m_AniMode;
var int _zCVob_m_aniModeStrength;
var int _zCVob_m_zBias;
var int _zCVob_rigidBody;
var int _zCVob_lightColorStat;
var int _zCVob_lightColorDyn;
var int _zCVob_lightDirectionStat[3];
var int _zCVob_vobPresetName;
var int _zCVob_eventManager;
var int _zCVob_nextOnTimer;
var int _zCVob_bitfield[5];
var int _zCVob_m_poCollisionObjectClass;
var int _zCVob_m_poCollisionObject;
//Ein Licht Vob kann verschiedene Farben und Reichweite haben.
//Schließlich gibt es animierte Lichter!
//zCVobLightData lightData;
//zCArray<zVALUE> rangeAniScaleList; //zREAL ~ zVALUE
/*0x120*/ var int lightData_rangeAniScaleList_array; //zVALUE*
/*0x124*/ var int lightData_rangeAniScaleList_numAlloc; //int
/*0x128*/ var int lightData_rangeAniScaleList_numInArray; //int
//zCArray<zCOLOR> colorAniList;
/*0x12C*/ var int lightData_colorAniList_array; //zCOLOR*
/*0x130*/ var int lightData_colorAniList_numAlloc; //int
/*0x134*/ var int lightData_colorAniList_numInArray; //int
/*0x138*/ var int lensFlareFXNo; //int
/*0x13C*/ var int lensFlareFX; //zCLensFlareFX*
/*0x140*/ var int lightColor; //zCOLOR //Alphakanal hier irrelevant
/*0x144*/ var int range; //zVALUE
/*0x148*/ var int rangeInv; //zVALUE
/*0x14C*/ var int rangeBackup; //zVALUE
//Daten zur Lichtanimation
//Zustand der Reichweitenanimation
/*0x150*/ var int rangeAniActFrame; //zVALUE
/*0x154*/ var int rangeAniFPS; //zVALUE
//Zustand der Farbanimation
/*0x158*/ var int colorAniActFrame; //zVALUE
/*0x15C*/ var int colorAniFPS; //zVALUE
// spotLights? Ich kenne das Feature nicht.
/*0x160*/ var int spotConeAngleDeg; //zREAL
//Siehe Auflistung oben
/*0x164*/ var int bitfield;
//zTRayTurboValMap<zCPolygon*, int>affectedPolyMap;
/*
struct zSNode
{
KEY m_Key ;
ELEMENT m_Element ;
unsigned long m_u32Hash ;
zSNode* m_pNext ;
}; */
//zCArray<zSNode*> m_arrNodes;
/*0x168*/ var int affectedPolyMap_m_arrNodes_array; //zSNode**
/*0x16C*/ var int affectedPolyMap_m_arrNodes_numAlloc; //int
/*0x170*/ var int affectedPolyMap_m_arrNodes_nunInArray; //int
/*0x174*/ var string lightPresetInUse; //zSTRING
};
Jetzt ist mir aufgefallen, dass einige Lichter einen Wert im bitfield stehen haben, den es eigentlich gar nicht geben sollte (hätte ich zumindest gedacht). Wenn alle flags, die in der Ikarus-Datei definiert sind, gesetzt sind, dann hat die Variable bitfield den Wert 32767. Wenn ich aber alle Lichter durchgehe, dann ist schon direkt zu Spielbeginn der größte Wert vom bitfield viel, viel größer. Speichert das Spiel dort also noch sehr viel mehr ab?
In Spielständen von zwei unterschiedlichen Spielern waren es z.B.
Als nächstes werde ich vermutlich versuchen, die Lichter zu finden, die tatsächlich dynamisch sein sollten, und nur bei diesen Lichtern das static flag zu entfernen. Da ist nun aber die Frage, wie ich die dynamischen Lichter am besten identifizieren kann. Ich habe mir mal ein wenig Licht abgespeichert mit LeGo's wrap Funktion:
Was mir hier potenziell problematisch erscheint, ist dass beim ersten Licht LIGHTPRESETINUSE auf "FIRESMALL" steht, das Licht aber wohl als statisch eingestellt ist. Kann es da einen Konflikt geben, der zu Problemen führt? Welche Infos verwendet das Spiel tatsächlich?
Meine Hoffnung wäre es, über solche Eigenschaften herauszufinden, welche Lichter eigentlich dynamisch sein sollten. Aber funktioniert das überhaupt so?
Ich sehe keine Anzeichen dafür, dass Sektenspinner irgendwelche Bitflags übersehen hat, die Liste in der zCVob.d scheint komplett zu sein. Aus dem Bauch heraus würde ich sagen, dass das vielleicht ein Überbleibsel von nicht initialisiertem Speicher ist? D.h. da alle Lese- und Schreiboperationen bei dem Bitfield nur auf die ersten 15 Bits zugreifen, wurden die anderen einfach nie auf 0 gesetzt.
Meine Hoffnung wäre es, über solche Eigenschaften herauszufinden, welche Lichter eigentlich dynamisch sein sollten. Aber funktioniert das überhaupt so?
Ich denke, dazu sollten wir erst einmal rausbekommen, was der Unterschied zwischen einem dynamischen und einem statischen Licht ist.
Ich hätte erwartet, dass statische Lichter von der Engine besser optimiert werden, so dass sie bessere Performance bieten als dynamische, dafür aber nicht in allen Kontexten eingesetzt werden können. Das könnte z.B. dadurch erfolgen, dass die Lichter nur in die Lightmap "gebakt" werden und so die Grafikkarte zur Laufzeit nicht belasten, aber logischerweise nur das statische Worldmesh beleuchten und keine Vobs.
Zumal die Performance aber deinen Tests zufolge bei dynamischen Lichtern scheinbar besser ist, widerspricht das aber meiner vorherigen Einschätzung. Also was genau sind statische Lichter?
Mir ist aufgefallen, dass das bei einem bekanntermaßen problematischen Licht auf 0 steht. Bei anderen Lichtern scheint dort ein Pointer zum Poly unter dem Licht zu liegen. Direkt nach Spielstart gibt es kein Licht, bei dem _ZCVOB_GROUNDPOLY auf 0 ist. Bei Spielständen, bei denen bereits ein Problem aufgetreten ist, gibt es 2 oder 3 Lichter mit _ZCVOB_GROUNDPOLY == 0. Wenn ich diese Lichter lösche, dann tritt das Problem nicht mehr auf.
Ich habe mal testweise bei den Lichtern mit _ZCVOB_GROUNDPOLY == 0 den Wert auf den Wert des vorherigen Lichts gesetzt. (Vorheriges Licht in der Vobliste)
Das hat das Problem nicht behoben. [tt_ZCVOB_GROUNDPOLY[/tt] scheint also vielleicht eher ein Indikator dafür zu sein, dass das Licht problematisch ist, aber nicht direkt das Problem zu verursachen? Oder vielleicht liegt es daran, dass ich dort tatsächlich das richtige Poly eintragen müsste. Hat jemand eine Idee, wie ich daran komme? Im Forum hatte Sektenspinner mal geschrieben, dass man es wohl über trace rays ermitteln könne, allerdings kenne ich mich damit noch überhaupt nicht aus.
Als Alternative würde es mich interessieren, ob / wie ich an gleicher Stelle ein neues Licht-Objekt einfügen könnte (um das alte Licht zu ersetzen). MEM_InsertVob fügt wohl ein oCMob ein, hier hätte ich gerne ein cZVobLight. Hat jemand eine Idee, wie man das machen könnte?
In der Beschreibung der Fast Dynamic Lights von Sektenspinner heißt es
Zitat von Sektenspinner
Every light that is supposed to be managed by this system is not set into the
world directly as a lightvob (zCVobLight) but as a lightspawner instead.
A lightspawner is an oCMobFire that spawns a single vob of type zCVobLight
(or a vobtree with a zCVobLight as the parent vob) when receiving a trigger.
was für mich so klingt, als müsste die Engine eigentlich dazu in der Lage sein. In FDL wird ein Licht aktiviert, in dem der lightspawner getriggert wird:
Mache ich einen Fehler bei der Anzeige der Position oder ist das wirklich die Position des Lichtvobs?
Wenn ich mir stattdessen ein neues Lichtvob an gleicher Stelle erzeuge:
Code:
var int vobPtr_new;
// create new trafo
var int trafo[16]; NewTrafo(_@(trafo));
/* Store the positional information of a trafo-matrix into a vector
func void TrfToPos(var int trafoPtr, var int posPtr) */
var int pos_new[3];
TrfToPos(_@(vob._zCVob_trafo), _@(pos_new));
/* Store positional and/or directional information into a trafo-matrix
func void PosDirToTrf(var int posPtr, var int dirPtr, var int trafoPtr) */
PosDirToTrf(_@(pos_new), 0, _@(trafo));
/* InsertObject(var string class, var string objName, var string visual, var int trfPtr, var int parentPtr); */
vobPtr_new = InsertObject("zCVobLight", "newlight", "", _@(trafo), 0);
//vobPtr_new = InsertObjectWP("zCVobLight", "newlight", "", "LOA_STADT_MARKT_04", 0);
var zCVobLight vob_new; vob_new = _^(vobPtr_new);
dann wird mir als Position des neuen Lichts angezeigt:
Wenn ich den Spieler auf dieselbe Position setze, wie das neue Objekt, dann bin ich auch in der Tat am Ursprung des Koordinaten-Systems.
Wenn ich den Spieler auf dieselbe Position setze, wie das kaputte Objekt, dann stürze ich irgendwo ins bodenlose -- da ist schwer zu erkennen, wo genau ich bin.
Code:
Spoiler:(zum lesen bitte Text markieren)
mal testweise die Position noch etwas verschoben, aber macht auch keinen Unterschied, wenn ich nichts hinzu addiere
Code:
var int heroPtr; heroPtr = _@(hero);
var oCNpc her; her = _^(heroPtr);
her._zCVob_trafoObjToWorld[3] = addf(vob._zCVob_trafoObjToWorld[3], mkf(1000));
her._zCVob_trafoObjToWorld[7] = addf(vob._zCVob_trafoObjToWorld[7], mkf(1000));
her._zCVob_trafoObjToWorld[11] = addf(vob._zCVob_trafoObjToWorld[11], mkf(1000));
Was genau bedeutet das für die Position des kaputten Lichts? Ist dort die trafo-Matrix kaputt? Oder ist die Rotation des Objekts hier noch irgendwie wichtig? Beim Erzeugen des neuen Objekts habe ich ja nur Position, aber nicht Rotation übernommen.
Das Floatpaket hat glaube ich auch die Funktion printf, denn was du da angezeigt bekommst sieht größtenteils so aus als würde es nicht in einen Integer passen.
Davon abgesehen klingt es aber so, als wären die Lichter tatsächlich an der falschen Stelle. Hast du die Positionen mal auf etwas halbwegs sinnvolles (z.B. Ursprung) gesetzt? Vielleicht führen die riesigen Koordinaten zu einer "worst-case" Belegung der internen Datenstrukturen (da kenne ich mich zu wenig aus, um was genaueres zu vermuten), so dass dann bei einem Portalraumwechsel teure Operationen (viel allozieren oder O(n) Access-time in einem der Trees?) ausgeführt werden.
Das war jetzt zwar wahrlich im Dunkeln gestochert, aber falls das Setzen der Position hilft, ist die Idee vielleicht gar nicht so falsch.
Edit: Nicht vergessen dass man beim Verschieben von Vobs auch noch sowas wie VobPositionUpdated() aufrufen sollte - Sekti hatte mal so eine Funktion gepostet. Sonst wird das Vob vermutlich intern nicht umsortiert.
Edit2: Letztendlich wird der verlässlichste Weg sein, einen Spielstand mit kaputten Vobs während eines Portalraumwechsels zu profilen (Performancemessung). Das habe ich aber tatsächlich noch nie gemacht und ich weiß auch nicht wie realistisch das in einer Anwendung wie Gothic ist.
Die Position mit printf angezeigt ist tatsächlich anders, viel zu groß:
205252165632.000000
3941.98793
823219650560.000000
da ist schon lange keine Welt mehr...
Bei zwei weiteren Lichtern wird mir angezeigt
-1.#IND00
-1.#IND00
-1.#IND00
bzw.
-1.#QNAN0
-1.#QNAN0
-1.#QNAN0
Was hat es damit wohl auf sich?
Damit werde ich vermutlich auch kein neues Licht sinnvoll als Ersatz am "richtigen" Ort einfügen können -- zumindest wüsste ich nicht, wie ich hier an den alten / richtigen Ort kommen kann. Ich werde dann wohl die paar Lichter einfach löschen.
Wenn ich die Position neu setze und dann VobPositionUpdated aufrufe, dann sind die Ruckler an Eingängen zu Portalräumen weg. Allerdings führt immer noch ein funktionierendes (?) Licht an einer Stelle zu massiven Rucklern. Also ein Licht an mir bekannter Stelle, dass nicht verschoben wurde.
Falls jemand Ideen hat, woran es liegen kann oder was ich ausprobieren sollte, bin ich noch sehr interessiert. Ansonsten werde ich als nächstes vermutlich einfach probieren, die paar fraglichen Lichter zu löschen.
Das sind besondere Werte bei Fließkommazahlen, die bei bestimmten Rechnungen auftreten. NaN steht für "Not a Number" und IND anscheinend für etwas vergleichbares, die sind zum Beispiel das Ergebnis, wenn man 0/0 berechnet mit Floats. Da ist also definitiv irgendwas schiefgegangen, die Frage ist nur wann und wo - ist die ZEN schon kaputt oder wird das z.B. durch Scripte ausgelöst?
Edit: IND00 ist anscheinend auch eine Darstellung von positiver Unendlichkeit, also wenn eine positive Zahl durch 0.0 geteilt wird.
Das sind besondere Werte bei Fließkommazahlen, die bei bestimmten Rechnungen auftreten. NaN steht für "Not a Number" und IND anscheinend für etwas vergleichbares, die sind zum Beispiel das Ergebnis, wenn man 0/0 berechnet mit Floats. Da ist also definitiv irgendwas schiefgegangen, die Frage ist nur wann und wo - ist die ZEN schon kaputt oder wird das z.B. durch Scripte ausgelöst?
Edit: IND00 ist anscheinend auch eine Darstellung von positiver Unendlichkeit, also wenn eine positive Zahl durch 0.0 geteilt wird.
Wenn ich ein neues Spiel starte (so dass alle Lichter aus der .zen geladen werden), dann gibt es noch kein Licht mit groundpoly=0 - sprich alle Lichter sind noch über dem Mesh. Irgendetwas passiert also im Spiel, was die Lichter beeinflusst.
Die Lichter (und viele andere Objekte) werden wohl in der World.sav des Spielstands mitgespeichert. Bei Spielern, bei denen diese Probleme auftreten, scheinen sie im 3. Kapitel zu passieren. Aber eben auch nicht bei allen Spielern. Bisher konnte ich das Problem noch nicht in einem neuen Spielstand erzeugen. Die Trigger, etc., die es zu dem Zeitpunkt gibt, habe ich schon ausprobiert, soweit ich mich an sie erinnern konnte.
Eine grobe Vermutung ist, dass es irgendwie mit fehlerhaften Portalen zusammenhängt. Es gibt einen fehlerhaften Portalraum, in dem das Problem zuerst bemerkt wurde. Vielleicht gibt es da also irgendwelche Zusammenhänge, wenn man in einem fehlerhaften Portalraum das Spiel speichert, Gothic beendet, Spielstand lädt, etc. Allerdings konnte ich auch damit den Fehler nicht erzeugen.