Yeah, I guess I should have said Merry Christmas and all that too... Unfortunately mine was completely ruined this year by a certain bitch. Hope all of you had far better holidays.
[b]Big-Picture Goals[/b]:
First of all, I am not working on the Toolkit to work on the Toolkit. My development this last month began with the Engine. I made a considerable amount of progress and was finally able to implement some long-awaited features for artists. Unfortunately these features must also be implemented in the Toolkit before I can even begin testing in the Engine... so that's why I have once again returned to the land of QT.
[u]Tile Flipping and Rotating[/u]
has actually been implemented within the engine, FINALLY. I am VERY pleased with with my implementation. Believe it or not, since this data effects the entire map, it could potentially have serious implications on Area sizes within the engine... I devised a solution that would require a single extra byte per tile location (represented as uint16 now, rather than unsigned char), and would not break any existing levels. I have done this by using 3 of the 8 new bits to represent tile orientation flags. These flags are implemented per-platform at the LibGyro level:
typedef enum _GY_RECT_TEX_ORIENT { GY_RECT_TEX_ORIENT_NORMAL, GY_RECT_TEX_ORIENT_FLIP_HORIZ, GY_RECT_TEX_ORIENT_FLIP_VERT, GY_RECT_TEX_ORIENT_ROT_90, GY_RECT_TEX_ORIENT_ROT_180, GY_RECT_TEX_ORIENT_ROT_270 } GY_RECT_TEX_ORIENT;
These flags may now be used with the LibGyro Rect API to tell libGyro how to orient the texture coordinates of the following rect:
void gyVidRectListBegin(const GYColor4 *const color); void gyVidRectListEnd(void); void gyVidRectTexOrient(const GY_RECT_TEX_ORIENT orient); void gyVidRect(const unsigned int frame);
From an application standpoint, with a little bit of crazy C-style bit manipulation and unsafe enumeration casting, we arrive at a pristine solution that is both quick and memory efficient.
[i]Object-layer rendering loop. Notice the call to gyVidRectTexOrient[/i]
gyVidTexBind(_curLevel->objectsheet); gyVidRectListBegin(&terrainColor); for(unsigned j = startY; j <= endY; ++j) { for(unsigned i = startX; i <= endX; ++i) { if(_curArea->objectLayer[0][j] != 0) { gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_OBJECT1, 32.0f, 32.0f, 1.0f); gyVidRectTexOrient((GY_RECT_TEX_ORIENT)((_curArea->tileLayer[0][j]>>8) & 0x0007)); gyMatPop(); ++_renderCount; } if(_curArea->objectLayer[1][j] != 0) { gyMatPush(); gyMatTranslateScale(i*32.0f, j*32.0f, ELYSIAN_ZCOORD_OBJECT2, 32.0f, 32.0f, 1.0f); gyVidRectTexOrient((GY_RECT_TEX_ORIENT)((_curArea->tileLayer[0][j]>>8) & 0x0007)); gyMatPop(); ++_renderCount; } } } gyVidRectListEnd();
And of course, since the existing levels did not have any special orientation, those bits will be 0, which will map into the first enumeration of NORMAL... So we haven't broken anything. :D
[u]Entity vs Terrain Collision[/u]
I am well aware of how ridiculously long this has taken to fucking implement. You can blame it all my overly ambitious plan for our physics engine... Not only do we need to efficiently check for OBB vs OBB collision between free-standing entities, but we also had to devise an algorithm to check each AABB/OOBB against its neighboring terrain tiles... This has proven to be suuuch a pain in the ass. I have two moderately complex ideas that I'm toying with. The basic algorithm is complete, but I am unable to do serious testing until I am able to lay tile solidity within the Toolkit... hence I've had to drop the engine and implement it in the Toolkit before further development.
[i]My work so far on Entity vs Terrain collision. Shit works, but I need a better test bed with the Toolkit...[/i]
[b]Toolkit Development and Achieving My Fucking Goals[/b]:
[u]Unfucked toolkit_debug.txt[/u]
I actually didn't realize that I had fucked it up so badly... but good ol' Jarrod pointed out the fact. It was because I was constructing/initializing an ofstream object at static/global scope before the rest of the C++ standard library... fixed. It should be logging pristinely.
[u]Big Ass Surprise[/u]
In order to implement terrain collision, I had to implement some mechanism for displaying/editing tile/object attributes. I realized that "Entity View" could be greatly improved upon. Since Entity is no longer the only type of object that can be selected (you can now select terrain), I have opted to change "Component View" to "Selection View."
Selection view will now be a property editor for any "Selectable."
[list][*] Entities
[*] Terrain
[*] Projects
[*] Levels
[*] Areas[/list]
One of the biggest issues that I have always had with the "Entity/Component" view has been the nonorthogonality of its widgets... I have had to hand-code each and every "widget" for the attributes...
Borrowing from both Unity3D and QT Designer, I have devised a new "Property Editor" for use with the Toolkit. I am far from done with it, but I have implemented some basic functionality. I will be slowly converting all of the Entity stuff to use this paradigm...
[i]----------- If you don't give a shit about code, PLEASE skip this part, because it's pretty advanced ----------[/i]
[u]PropertyStackModel[/u]
Borrowing heavily from my Debug Log, I have opted to make our property editor "Stack based" as well. I came to a profound realization the other day that using an internal stack is the best EVER way to populate a tree...
The PropertyStackModel inherits from QT's QStandardItemModel. It extends QT's model through a stack API and gives you access to convenience functions for adding labels and properties to the model with a single line:
class PropertyStackModel: public QStandardItemModel { private: static bool _staticInit; static void _StaticConstructor(void); static QColor _bgRowColor[PROPERTY_STACK_MODEL_COLOR_DEPTH][2]; QStandardItem *_curParent; public: PropertyStackModel(QObject *parent=0); virtual QVariant data(const QModelIndex &index, int role) const; virtual void PopulateModel(void)=0; void ClearStack(void); void InsertHeaders(void); void PushLabel(const QString label); void PopLabel(void); void AddProperty(const QString name, QStandardItem *const item); };
That's cute and everything... but there's still a HUGE problem. QT's QStandardModel is populated with QStandardItem objects, each encapsulating its own piece of data and exposing get/set functions. So how the fuck do we pull that off? 99% of our data in the Toolkit is in the elysian:: namespace from the Engine. It cannot be encapsulated by QT datatypes, because there is no QT within the engine... So how the fuck do we keep our data in-synch between these abstract "model items" and the actual elysian:: AssetIO data?
Well, bitches, after many hours of pondering this predicament, the solution presented itself in probably one of the most useful and powerful applications that I have ever coded with C++ templates... Rather than encapsulating the data within the QT items, I devised a class template to tie the item's get/set functions to the engine's get/set functions... [i]I give you the PropertyStandardItem[/i] class template:
templateclass PropertyStandardItem: public QStandardItem { private: C *const _objInst; T (C::*const _getAccessor)(void) const; void (C::*const _setAccessor)(const T); public: PropertyStandardItem(C *const object, T (C::*const get)(void)const, void (C::*const set)(const T)=NULL): _objInst(object), _getAccessor(get), _setAccessor(set) { setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | ((_setAccessor)? Qt::ItemIsEditable : Qt::NoItemFlags)); } virtual int type(void) const { return QStandardItem::UserType; } virtual QVariant data(int role) const { switch((Qt::ItemDataRole)role) { case Qt::DisplayRole: case Qt::EditRole: return (_objInst->*_getAccessor)(); default: return QStandardItem::data(role); } } virtual void setData(const QVariant &value, int role) { switch((Qt::ItemDataRole)role) { case Qt::DisplayRole: case Qt::EditRole: (_objInst->*_setAccessor)(value.value ()); default: return QStandardItem::setData(value, role); } } };
When instantiated, this template dynamically inherits from QStandardItem and accepts both a get/set callback pointer-to-member function for the actual data. It does not store the data, rather it references it through existing accessor methods.
This class template also automagically sets properties lacking a set function to read-only (via the default argument of NULL for the set function).
So here is a very rough implementation of the Area model using this paradigm. You can at least notice that it's super pretty and takes almost no time at all. Everything is handled automatically by the stack and template:
void Area::PopulateModel(void) { ClearStack(); InsertHeaders(); PushLabel("Area"); AddProperty("name", new PropertyStandardItem<QString, Area>(this, &Area::getName, &Area::setName)); AddProperty("path", new PropertyStandardItem<QString, Area>(this, &Area::getLocation)); PushLabel("size"); AddProperty("width", new PropertyStandardItem(this, &Area::getMapWidth, &Area::setMapWidth)); AddProperty("height", new PropertyStandardItem (this, &Area::getMapHeight, &Area::setMapHeight)); PopLabel(); PopLabel(); }
[i]------------------------------------- Continue Reading After Here --------------------------[/i]
And here is the result:
EDIT: The orange background was just me flexing my dick at you... just wanted to show that I could still modify QT's item properties easily with the class template.
I'm sure that this will 1) save us a SHITLOAD of time from having to handcode widgets and 2) make our Toolkit look more professional, because the look of each selectable is consistent with the new property editor.
[b]So Now What?[/b]
The main portion of this question will be answered by Tyler's impending post. Jarrod, I would like for me and you to compile a list of must-haves before the next Toolkit release. I would like for you to begin working on bug-fixes and other features while I finish these babies up. Hopefully after this, we'll be able to meet up, get everything stable, and release the Toolkit, the video, and the site. As I have said before, I am ready to get back on my own level. I'm out for blood, bitches, and the sooner I'm on Youtube, the sooner my thirst will be quenched. ;)