Game Development Community

In-Layer Y-Axis Render Sort

by Paolo · in Torque Game Builder · 03/16/2006 (3:12 pm) · 37 replies

I was wondering if you guys could add a in-layer y-axis sort.

Basically, you can set Layer 0 to a default sort (where you can manipulate the in-layer order by moving things FORWARD and BACKWARD), but on Layer 1 you can set all sceneobjects within Layer 1 to sort on the y-axis for rendering.

Something like:

mySceneGraph.IsoMetricSort(Layer0, false);
mySceneGraph.IsoMetricSort(Layer1, false);


Some of the compiled code changes script suggestions on the forum don't really work out too well. The code changes to make everything sort on the y-axis affects EVERYTHING, not just within a layer. While other algorithms using TorqueScript involve double-linked list and other complex logic to create a sort order.

Since there are many games that use y-axis sorting such as RPGS, 2.5D top-down view, and Isometric games, I thought it would be good to have this functionality available for the full release of TGB.

Thanks guys for a wonderful product! I've loved T2D since it's Alpha release and can't wait for the full release of TGB!
Page «Previous 1 2
#1
03/17/2006 (12:32 pm)
Agreed, we 3/4 perspective guys need this badly.
#2
03/17/2006 (9:15 pm)
If you super desperatly need this ASAP, it should be simple to do with that code hack michael posted.

Otherwise, you can always do it my way ;)
#3
03/18/2006 (12:45 am)
As I started out with RPG making too, I think this would be a nice. And this is a small feature where I would say "Why not?". Adding some of the most common layer sorting orders to the stock is a good idea.

Maybe I will code something clean enough over the next days and send the diff to Melv.
Maybe...

We will see if it gets included in one of the upcoming releases. But I am not in the position to make any promises.
#4
03/18/2006 (2:04 pm)
Ok, this is quite a modification to enable different sorting per layer:

Here are the pieces of code to add/modify:

In t2dSceneGraph.h add the top at about line 80:
// Add this under "Structures". 
struct tRenderListLayer
{
   Vector<t2dSceneObject*>    mSceneObjectVector;
   S32 (QSORT_CALLBACK *mRenderSortFn)(const void *, const void *);
};

The same file at line about line 220, change this
typeSceneObjectVector               mLayeredRenderList[t2dSceneGraph::maxLayersSupported];  ///< Layered Render-List.
to this:
tRenderListLayer                    mLayeredRenderList[t2dSceneGraph::maxLayersSupported];  ///< Layered Render-List.

Same file, about line 310, change the setRenderSortFunction() method to this:
void setRenderSortFunction( S32 (QSORT_CALLBACK *sortFn)(const void *, const void *), const U32 layer ) { if(layer < t2dSceneGraph::maxLayersSupported) mLayeredRenderList[layer].mRenderSortFn = sortFn; };

In file t2dSceneGraph.cc at about line 60 modify the function findLayeredObjectsCallback to this:
void findLayeredObjectsCallback(t2dSceneObject* pSceneObject, void* storage)
{  
    // Cast Callback List.
    tRenderListLayer* callbackList = (tRenderListLayer*)storage;
    // Add Object to List.
    callbackList[pSceneObject->getLayer()].mSceneObjectVector.push_back(pSceneObject);
}

In t2dSceneGraph.cc at about line 3140 change the t2dSceneGraph::renderView() method to this:
void t2dSceneGraph::renderView( const RectF viewWindow, const U32 layerMask, const U32 groupMask, CDebugStats* pDebugStats )
{
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_renderView);
#endif
    // Reset Render Stats.
    pDebugStats->objectsLayerSorted = 0;
    pDebugStats->objectsPotentialRender = 0;
    pDebugStats->objectsActualRendered = 0;

    // Clear Layered Render-List.
    for ( U32 n = 0; n < t2dSceneGraph::maxLayersSupported; n++ )
       mLayeredRenderList[n].mSceneObjectVector.clear();

    // Find Objects in View Window.
    if ( mSceneContainer.findObjects( viewWindow, layerMask, groupMask, false, false, findLayeredObjectsCallback, mLayeredRenderList, NULL ) > 0 )
    {
        // Step through layers.
        for ( S32 layer = t2dSceneGraph::maxLayersSupported-1; layer >= 0 ; layer-- )
        {
            // Fetch Layer Size.
            const U32 layerSize = mLayeredRenderList[layer].mSceneObjectVector.size();

            // Are there any objects to render in this layer?
            if ( layerSize > 0 )
            {
                // Are we using layer sorting?
                if ( mUseLayerSorting && layerSize > 1 )
                {
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_layerSorting);
#endif
                    // Yes, so Quick-Sort the layer.
                    dQsort(mLayeredRenderList[layer].mSceneObjectVector.address(), layerSize, sizeof(t2dSceneObject*), mLayeredRenderList[layer].mRenderSortFn);

#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_layerSorting
#endif
                    // Increase Layer Sorts.
                    pDebugStats->objectsLayerSorted += layerSize;
                }

                // Viewport/Object View Intersection.
                RectF viewIntersection;

                // Yes, so step through objects.
                for (   typeSceneObjectVector::iterator itr = mLayeredRenderList[layer].mSceneObjectVector.begin();
                        itr < mLayeredRenderList[layer].mSceneObjectVector.end();
                        itr++ )
                {
                    // Increase Potential Renders.
                    pDebugStats->objectsPotentialRender++;

                    // Fetch more direct reference.
                    t2dSceneObject* pSceneObject2D = (*itr);

                    // Is the Object being Deleted?
                    if ( !pSceneObject2D->isBeingDeleted() )
                    {
                        // No, so is the object in view or always scoped?
                        //
                        // NOTE:-   Object must have been initially updated.
                        if ( pSceneObject2D->getInitialUpdate() && (pSceneObject2D->getIsAlwaysScope() || pSceneObject2D->getIsInViewport(viewWindow, viewIntersection)) )
                        {                      
#ifdef T2D_DEBUG_PROFILING
        PROFILE_START(T2D_t2dSceneGraph_renderObjects);
#endif
                            // Yes, so render Object.
                            pSceneObject2D->renderObject( viewWindow, viewIntersection );
#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_renderObjects
#endif
                            // Increase Actual Renders.
                            pDebugStats->objectsActualRendered++;
                        }
                    }
                }
            }
        }

        // Reset Blend Options.
        t2dSceneObject::resetBlendOptions();
    }

    // Calculate Render Hit Percentage ( if any ).
    if ( pDebugStats->objectsPotentialRender != 0 )
        pDebugStats->objectsHitPercRendered = pDebugStats->objectsActualRendered * ( 100.0f / pDebugStats->objectsPotentialRender  );
    else
        pDebugStats->objectsHitPercRendered = 100.0f;

#ifdef T2D_DEBUG_PROFILING
        PROFILE_END();   // T2D_t2dSceneGraph_renderView
#endif
}

Now you should be able to use different sorting methods in different layers. This code compiles fine but is not really tested.

-Michael

EDIT:
In t2dSceneGraph.cc you have to modify the scenegraph's constructor so it initializes the sorting function pointers. Go to t2dSceneGraph::t2dSceneGraph() and replace this line:
VECTOR_SET_ASSOCIATION( mLayeredRenderList[n] );
with these two lines:
VECTOR_SET_ASSOCIATION( mLayeredRenderList[n].mSceneObjectVector );
        mLayeredRenderList[n].mRenderSortFn = layeredRenderSort;

You can also delete every occurance of the mRenderSortFn member-variable (its declaration in t2dSceneGraph.h and its initialization in the constructor). It is not needed anymore.
#5
03/18/2006 (2:33 pm)
Wow Michael! THANKS! I'll have to test it thoroughly because this would be a great standard addition.
#6
03/18/2006 (2:36 pm)
Paolo,
this is just the c++ modification. Script-side is not in there yet. I'll add this later but right now the sun is shining to beautifully to sit in front of the computer ;)
#7
03/18/2006 (2:39 pm)
Michael,

I thought programmers never get to see the sun.... Or have girlfriends.... Or have a life.... :)

Question, are there any good resources to start deciphering the actual Torque source code? The more I look at it, the more I am awed by the genius of the programmers who created this engine.
#8
03/18/2006 (7:22 pm)
@Paolo, check the C++ tutorials on TGB's TDN page. Though I wish there were more too
#9
03/20/2006 (12:02 pm)
Ok,
after spending an hour without sun, here is the code to tie the above to the console:

t2dCustomLayerSorting.h
#ifndef _T2DCUSTOMLAYERSORTING_H_
#define _T2DCUSTOMLAYERSORTING_H_

typedef S32 (QSORT_CALLBACK *RenderSortFunction)(const void *, const void *);

class RenderSortCallback {
public:
   RenderSortCallback(const char* name, RenderSortFunction sortFn);

   static RenderSortFunction getFunction(const char* name) { return getFunctionList() ? getFunctionList()->find(name) : 0; }
	static void printAvailableSortFunctions();

private:
	struct Entry {
		Entry(const char* name, RenderSortFunction sortFn, Entry* next);

		const char*					mName;
      RenderSortFunction      mSortFn;
		Entry*						mNext;

		RenderSortFunction find(const char* name) const;
	};

   static Entry*& getFunctionList();
};

#endif

t2dCustomLayerSorting.cc
#include "platform/platform.h"
#include "console/console.h"
#include "T2D/t2dSceneObject.h"
#include "T2D/t2dSceneGraph.h"

#include "./t2dCustomLayerSorting.h"


//-----------------------------------------------------------------------------

RenderSortCallback::Entry::Entry(const char *name, RenderSortFunction sortFn, RenderSortCallback::Entry *next):
	mName(name),
	mSortFn(sortFn),
	mNext(next)
{
}

//-----------------------------------------------------------------------------

RenderSortFunction RenderSortCallback::Entry::find(const char *name) const
{
	if( dStricmp(mName,name) == 0 )
	{
      return mSortFn;
	}
	return mNext ? mNext->find(name) : 0;
}

//-----------------------------------------------------------------------------

RenderSortCallback::RenderSortCallback(const char* name, RenderSortFunction sortFn)
{
	if(getFunctionList() && getFunctionList()->find(name))
	{ 
		Con::errorf("RenderSortFunctions::RenderSortFunctions() - Error! Name %s registered twice!", name);
		AssertFatal(false,"");
	}
	else
	{
		int len = dStrlen(name);
		char* nameBuffer = new char[len+1];
		dStrcpy(nameBuffer, name);
		getFunctionList() = new Entry(nameBuffer, sortFn, getFunctionList());	
	}	
}

//-----------------------------------------------------------------------------

void RenderSortCallback::printAvailableSortFunctions()
{
   Con::printf("Available Layer Render sorting functions");
   for(RenderSortCallback::Entry* walk = getFunctionList(); walk; walk = walk->mNext)
      Con::printf("-%s", walk->mName);
}

//-----------------------------------------------------------------------------

RenderSortCallback::Entry*& RenderSortCallback::getFunctionList()
{
   static RenderSortCallback::Entry* functionList = 0;
   return functionList;
}


//-----------------------------------------------------------------------------
// CONSOLE
//-----------------------------------------------------------------------------

ConsoleMethod(t2dSceneGraph, setRenderSortFunction, void, 4, 4, "t2dSceneGraph::setRenderSortFunction(%name,%layer)")
{
   argc;
   const U32 layer = dAtoi(argv[3]);
   if( layer > t2dSceneGraph::maxLayersSupported )
   {
      Con::warnf("t2dSceneGraph::setRenderSortFunction() - Warning! %u is no valid layer.", layer);
      return;   
   }

   RenderSortFunction sortFn = RenderSortCallback::getFunction( argv[2] );
   if( !sortFn )
   {
      Con::warnf("t2dSceneGraph::setRenderSortFunction() - Warning! %s is no valid render sort function.", argv[2]);
      Con::warnf("t2dSceneGraph::setRenderSortFunction() - Call \"printAvailableRenderSortFunctions()\" for a list of available functions.");
      return;
   }

   object->setRenderSortFunction( sortFn, layer );
}

//-----------------------------------------------------------------------------

ConsoleFunction(printAvailableRenderSortFunctions,void,1,1,"Prints available functions for sorting scene-objects within a layer.")
{
   RenderSortCallback::printAvailableSortFunctions();
}


//-----------------------------------------------------------------------------
// Callbacks
//-----------------------------------------------------------------------------

static RenderSortCallback standardLayeredSort("standardLayeredSort", t2dSceneGraph::layeredRenderSort);

//-----------------------------------------------------------------------------

static S32 QSORT_CALLBACK isoRenderSortFn(const void* a, const void* b)
{
	// Sort by Y - coordinate
	const t2dSceneObject& objA = **((t2dSceneObject**)a);
	const t2dSceneObject& objB = **((t2dSceneObject**)b);

	return (objA.getPosition().mY + objA.getHalfSize().mY) - (objB.getPosition().mY + objB.getHalfSize().mY);
}

static RenderSortCallback isoRenderSortCallback("isoSortFunction",isoRenderSortFn);

//-----------------------------------------------------------------------------

static S32 QSORT_CALLBACK sortByY(const void* a, const void* b)
{
	// Sort by Y - coordinate
	const t2dSceneObject& objA = **((t2dSceneObject**)a);
	const t2dSceneObject& objB = **((t2dSceneObject**)b);

	return (objA.getPosition().mY) - (objB.getPosition().mY);
}

static RenderSortCallback sortByYCallback("sortByY", sortByY);

//-----------------------------------------------------------------------------

static S32 QSORT_CALLBACK sortByX(const void* a, const void* b)
{
	// Sort by X - coordinate
	const t2dSceneObject& objA = **((t2dSceneObject**)a);
	const t2dSceneObject& objB = **((t2dSceneObject**)b);

	return (objA.getPosition().mX) - (objB.getPosition().mX);
}

static RenderSortCallback sortByXCallback("sortByX", sortByX);

//-----------------------------------------------------------------------------

static S32 QSORT_CALLBACK isoRenderSortMod(const void* a, const void* b)
{
	// Sort by Y - coordinate
	t2dSceneObject& objA = **((t2dSceneObject**)a);
	t2dSceneObject& objB = **((t2dSceneObject**)b);

   return (objA.getPosition().mY + dAtof(objA.getDataField(StringTable->insert("renderSortMod"),0)) ) - (objB.getPosition().mY + dAtof(objA.getDataField(StringTable->insert("renderSortMod"),0)));
}

static RenderSortCallback isoSortModCallback("isoRenderSortMod",isoRenderSortMod);

//-----------------------------------------------------------------------------

Again, this code compiles fine, does not crash but is not really tested.
You can add every sorting function you want like the ones in the Callback section.

Switching sorting functions is done with:
$someSceneGraph.setRenderSortFunction( %functionName, %layer );

I hope this turns out to be useful. :)

-Michael
#10
03/20/2006 (2:36 pm)
Michael: what would be the advantage of this code over the code you posted on the other forum? from what i understand is that the other code Y sorts everything, and this is per layer?? thanks!
#11
03/20/2006 (5:26 pm)
James,

sorting always is applied within one layer. Layer 0 will always be above layer 1 - regardless of any sorting.
This code gives you the ability to sort different layers with different sorting methods. Normally all layers would be sorted by the same way. Now you can have layer 3 sorted by Y-values and layer 9 sorted by X-values.
And this code gives you the ability to switch between different sorting method from script in a cleaner way than the other.

The code in the other thread was just a quick hack. But it may well be sufficient for your needs.
#12
03/20/2006 (8:53 pm)
Sweet michael, you should try to get melv et. all to put this in the next release! i think the concept offers great utility to tgb devs.
#13
03/21/2006 (1:34 pm)
Jason,

I'll probably do some testing and then post this as a resource. But Melv said this may get included some time in the future.
#14
03/21/2006 (3:49 pm)
Now that I think about it, I knew that........ i'm a bit slow sometimes :)

I will try this one tonight!
#15
03/23/2006 (2:50 am)
@Michael: OK i finally got a chance to try that out. The code compiles without errors, and TGB starts fine, but the problem starts when you try to run a game. As soon as I click fish demo or run game it immediately freezes. the only thing i can see that Xcode is saying is:

Error from Executable Runner: Torque2D-MacCarb-Release has exited due to signal 10 (SIGBUS).

Any idea what is going on??? I triple checked what you posted above, and everything is exactly the same. thanks for all your help :)
#16
03/23/2006 (8:16 am)
Sorry James,
I just have looked into this and noticed that I forgot to post one modification. I'll add it in the above post under "EDIT".
#17
03/23/2006 (1:43 pm)
Ah awesome. thats a ton michael, you have been ridiculously helpful :)
i'll try it when i get home tonight!
#18
03/23/2006 (11:17 pm)
@Michael: haha ok its always something, i swear. its no longer freezing, but when i call the render function i'm getting an error message. i think i'm just calling it wrong. i have:

$t2dScene.setRenderSortFunction( %sortByY, 0 );

and this is coming up in the console:

T2D/gameScripts/game.cs (22): Unable to find object: ' ' attempting to call function 'setRenderSortFunction'

sorry for all the trouble ;)
#19
03/23/2006 (11:22 pm)
It has to be
t2dScene.setRenderSortFunction( "sortByY", 0 );
given your scenegraph is called 't2dScene'. Then it should work :)
#20
03/23/2006 (11:49 pm)
Oops, haha that was obvious. sorry about that.

however... this one seems to render a bit differently than the last one. the hack seems to have better handling of different sized sprites. in specific, my character would stay behind a larger sprite until the bottom his sprite was below the bottom of the large one. now, its maybe 1/2 an inch up when he comes to the front. i'm going to look at the code on both and try to understand why. thanks for everything, again :)

haha nevermind... now i understand halfsize :-)

michael, you rock my friend.
Page «Previous 1 2