Game Development Community

Ghosting Limits

by Jesse Allen · in Torque 3D Professional · 09/08/2014 (12:47 am) · 212 replies

Greetings fellow Torque-goers! I hadn't been overly active on the boards lately, but that's usually a good thing since it means I'm busy actually developing stuff :) Finally hit a snag today, and I was hoping someone a bit more knowledgeable about these things could perhaps point me in the right direction.

I've been working with generating 1000's of cubes, which I have been largely successful with. I have scripted algorithms to do exactly what I need as far as generation is concerned. I've even managed to work out some basic culling and so on. The problem I'm having, however, is with the following error:

NetConnection::object In Scope: too many ghosts.

I did a bit of searching and I did stumble on Vince Gee's fantastic resources for Limiting Shapebase ghosting and Improved Limiting ghosting . These do appear to be useful by their own rights and I will definitely explore these options. However, these particular resources deal specifically with ghost limiting as related to view distance.

The problem I am having seems to stem from the actual hard limits set on the maximum number of allowed ghosts. I was curious:
  • Is it possible to increase the number of ghosted objects?
  • If it were possible, what sorts of problems would this increase potentially introduce?

Also, bonus points for a clear explanation on how view distance affects ghosts in the first place. If I generate in a bunch of cubes and use the command ServerConnection.getGhostsActive() I can see the current number of ghosts. Check. But if I then reduce the viewDistance to an absurdly low amount (such as 10) and run far away from the generated cubes...the command will still show the same number of ghosts, regardless of my distance from them.

Cheers guys, as always thanks in advance for any help.
#81
09/20/2014 (12:59 pm)
Alright, I went through the motions and was met with 2 oddities. One of which I believe is because I jumped the gun and didn't wait for you to explain the rest (primitive buffers etc.). I did render an extended shape consisting of 2 cubes, although the 2nd cube didn't turn out as expected. It was extended by half of what I wanted. Here's what I did:
static const Point3F cubePoints[12] = 
   {
      Point3F( 1, -1, -1), Point3F( 1, -1,  1), Point3F( 1,  1, -1), Point3F( 1,  1,  1),
      Point3F(-1, -1, -1), Point3F(-1,  1, -1), Point3F(-1, -1,  1), Point3F(-1,  1,  1),
      Point3F( -1, 1,  2), Point3F( 1,  1,  2), Point3F( 1, -1,  2), Point3F( -1, -1,  2)	  
   };
So here I added 4 new points and changed the index total to 12. Pretty confident here.
static const U32 cubeFaces[72][3] = 
   {
      { 3, 0, 3 }, { 0, 0, 0 }, { 1, 0, 1 },
      { 2, 0, 2 }, { 0, 0, 0 }, { 3, 0, 3 },
      { 7, 1, 1 }, { 4, 1, 2 }, { 5, 1, 0 },
      { 6, 1, 3 }, { 4, 1, 2 }, { 7, 1, 1 },
      { 3, 2, 1 }, { 5, 2, 2 }, { 2, 2, 0 },
      { 7, 2, 3 }, { 5, 2, 2 }, { 3, 2, 1 },
      { 1, 3, 3 }, { 4, 3, 0 }, { 6, 3, 1 },
      { 0, 3, 2 }, { 4, 3, 0 }, { 1, 3, 3 },
      { 3, 4, 3 }, { 6, 4, 0 }, { 7, 4, 1 },
      { 1, 4, 2 }, { 6, 4, 0 }, { 3, 4, 3 },
      { 2, 5, 1 }, { 4, 5, 2 }, { 0, 5, 0 },
      { 5, 5, 3 }, { 4, 5, 2 }, { 2, 5, 1 },
	  
      { 9, 0, 3 }, { 1, 0, 0 }, { 10, 0, 1 },
      { 3, 0, 2 }, { 1, 0, 0 }, { 9, 0, 3 },
      { 8, 1, 1 }, { 6, 1, 2 }, { 7, 1, 0 },
      { 11, 1, 3 }, { 6, 1, 2 }, { 8, 1, 1 },
      { 9, 2, 1 }, { 7, 2, 2 }, { 3, 2, 0 },
      { 8, 2, 3 }, { 7, 2, 2 }, { 9, 2, 1 },
      { 10, 3, 3 }, { 6, 3, 0 }, { 11, 3, 1 },
      { 1, 3, 2 }, { 6, 3, 0 }, { 10, 3, 3 },
      { 9, 4, 3 }, { 11, 4, 0 }, { 8, 4, 1 },
      { 10, 4, 2 }, { 11, 4, 0 }, { 9, 4, 3 },
      { 3, 5, 1 }, { 4, 5, 2 }, { 1, 5, 0 },
      { 7, 5, 3 }, { 4, 5, 2 }, { 3, 5, 1 }	  
   };
Here I copied the table and changed the first number in each block accordingly, also changed the number of cubeFaces to 72. Fairly confident here as well.

Then came the rest of the .cpp file which you hadn't introduced me to yet. So here I just kinda guessed at the numbers. Where I saw 36 being used I changed it to 72, assuming it was referencing the number of faces. I changed:
mPrimitiveBuffer.set( GFX, 36, 12, GFXBufferTypeStatic );
to:
mPrimitiveBuffer.set( GFX, 72, 24, GFXBufferTypeStatic );
Here just blindly doubling the 12 value without understanding what I was doing :P

Any of the for loops I changed 36 to 72. Then there was this bit about lights:
if ( matInst->isForwardLit() )
   {
      LightQuery query;
      query.init( getWorldSphere() );
		query.getLights( ri->lights, 8);
   }
...which I changed to:
if ( matInst->isForwardLit() )
   {
      LightQuery query;
      query.init( getWorldSphere() );
		query.getLights( ri->lights, 12);
   }
Again just taking a stab in the dark since the only reference I had to the number 8 originally was the cubePoint[8] index which I had already increased to [12].

The Results
In my example above I wanted to create a new cube that would be in 'front' of the first one. So if looking at the original cube, I wanted the newer one to be closer to the camera and in front. Based on the coordinates being used in the diagram you provided and in the .cpp file, by all rights this should have been the case. However, I noticed right away that it seems the y and the z are swapped in 'space' when going from .cpp to 'what you see in Torque'. So my newly added cube was on top of the first one instead of in front. Also the new cube only extended half as far as I would have wanted it to. I assume this is due to my blind usage of the contents of the .cpp file. Enjoy:
i1213.photobucket.com/albums/cc466/rockoutsolid/Development/cubeFail.png
I believe this is a good point for me to reflect and await any further guidance :) Again, thanks man. Really enjoying the exercises here.

EDIT: Oh, one other thing: I did notice a section where the bounding box was being declared but I didn't mess with it just yet. Also the material above appears that it may be flipped, the 512x512 text reads in reverse..:)
#82
09/20/2014 (1:30 pm)
Well done! I'm a bit busy tonight so I'll type up more explanations tomorrow about the things going on in createGeometry, but just to keep you going, the reason it's half the size is because the cubes are actually 2 in height/width, not 1. The bottom verts are at -1, and the top ones are at 1, so that's 2, not 1 in size. So when you made your second cube at 2, the distance between 1 and 2 is only only 1 high, and thus half the height of the regular cube. Change your 2s to 3s and it should be full size.
#83
09/20/2014 (1:35 pm)
No worries Andrew. I am truly grateful. Ah yes, I should have caught that, since the verts were being drawn in both directions. Thanks!
#84
09/21/2014 (8:24 am)
Okay, so it seems like you have a pretty good handle on what you're seeing in front of you, based on your success with adding another cube. So, to just touch on the primitive buffer, the point of it is to specify what order the vertices should be connected together to make the triangles to make up your shape. In this case, they setup their verts in the perfect order so the primitive buffer literally just has to go: 0 1 2 3 4 5 6 7 .. and so on until you run out of verts. This is what you're seeing with the primitive buffer setup. That's why it just loops through 0 to 36 and sets the numbers to the current position in the loop. It almost seems pointless but it exists because shapes aren't always as simple as cubes :P

Pretty much all of what they're doing is reusable, we just need to adapt it to work with a list of cubes instead of single cube. So, the game plan is as follows:


  1. Keep a list of cubes and their positions
  2. When building geometry, loop through each cube and add its data to the vertex buffer based on the cube layout we already have.
  3. Adjust the rest of the code to be based on how many cubes we have in the list.

In order to explain how to do this I had to try it myself first, so I just did it up a few minutes ago. I figure it's easier to just put the code up with comments on everything. So here it is: gist.github.com/andr3wmac/45f0526d9534809df9ea

Screenshot:
i.imgur.com/ldqG9Df.png
I just did the work in renderMeshExample.h/.cpp so you'll have to carry over the changes to your new class. Search the document for "andrewmac" and you'll find every change I made with comments. I adapted the cubes to be 1x1x1 and removed their half-size multiplier. This makes a normal sized cube positioned at 0,0,0. This way, by adding the position of each cube in the list to that data, we get cubes in the proper positions as they're specified.

You had almost all of the changes correct except this one:
query.getLights( ri->lights, 8);

That just so happens to be 8. So, no need to change it at all. Keep it at 8.

Try out the code, go over it, put it in piece by piece, etc. There's still some missing pieces to the whole thing, and I'll go over how to add those in as soon as I can. The two things that come to mind are different materials on different cubes, and collision. You'll also have to set the world box size based on the maximum x, y, z reached by your cubes. If the worldbox is too small you'll see the whole object disappear as you move the camera around. None of these issues are very cumbersome to correct, just take a little tweaking.

Something you should start thinking about: My cube list is made up of only positions. This is all I needed for the example. You'll likely end up implementing your own cube class or struct to store the data you want for each cube and keep a list of those. That same data per cube will have to be saved to disk, and sent over network. Break out the calculator and start figuring out how small you can get each cube while still maintaining all the per-cube features you want. I'd probably do something like use 1 byte (0 - 255) for x, 1 for y, and 1 for z, plus 1 byte for material number, that's 4 bytes per cube. If that whole space was filled, that's 16,777,216 cubes, at 4 bytes a piece is 64 MB. I think that would be a pretty big chunk of world, 64 MB seems okay. But you could do interesting things like storing the data in sections based on material numbers since a lot of those would be the same.

Hit me with whatever questions you have and I'll try to explain anything that isn't clear.
#85
09/21/2014 (9:19 am)
Very cool Andrew. I'm just heading out the door for a day with the family :) I am again very appreciative for all your effort on this to help me! When I get back in, I will eagerly study your example. This is all very exciting stuff for me, and it all forces me to consider things I otherwise would have let Torque just handle for me. I am beginning to see already where, regardless of the end goal, this kind of knowledge will serve me well in future endeavors.

Obviously I am interested in getting this stuff working for my current project goals. However, moving forward I am trying my hardest to review the wealth of information available on the C++ site Daniel linked to me earlier. I want more than anything to break this barrier so that I can be fluent with the C++ language. This is serving as a prime introduction, and I can assure you that any of your efforts will not be wasted here. I am both dedicated and driven by a near-zealous determination to see this all come to fruition. Perhaps in this case the material we're covering may be a little bit more advanced than a newcomer to C++ would normally work through - but that's a challenge I'm willing to accept. I'll do the work behind the scenes to 'catch up' if that's what I need to do. I will report on my progress probably sometime tonight. Thank you Andrew, sincerely.
#86
09/21/2014 (1:45 pm)
I'm still sad that my script didn't work - it worked fine for me :(. But this is a much better outcome. You guys are both awesome. I'm learning a lot from this process, too!
#87
09/21/2014 (2:51 pm)
Okay, I've been able to take a look at your example. While I do understand most of what you are presenting here, I can't get the stuff to compile. First, I just went ahead and stepped through your example line by line and updated my new .cpp and .h files. In my case, I'm naming the files submatrix.cpp and submatrix.h since in essence that's what it seems we are creating. Here are the errors the console gave me on compile:
15>  submatrix.cpp
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69): error C2143: syntax error : missing ';' before '.'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(70): error C2143: syntax error : missing ';' before '.'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(70): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(70): error C2086: 'int cubeList' : redefinition
15>          c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69) : see declaration of 'cubeList'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(71): error C2143: syntax error : missing ';' before '.'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(71): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(71): error C2086: 'int cubeList' : redefinition
15>          c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69) : see declaration of 'cubeList'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(72): error C2143: syntax error : missing ';' before '.'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(72): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(72): error C2086: 'int cubeList' : redefinition
15>          c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69) : see declaration of 'cubeList'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(73): error C2143: syntax error : missing ';' before '.'
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(73): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
15>c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(73): error C2086: 'int cubeList' : redefinition
15>          c:devtorque3dtorque3d-developmentmy projectscubessourcesubmatrix.cpp(69) : see declaration of 'cubeList'
The lines that the errors are referring to here are when we add:
cubeList.push_back(new Point3F(0, 0, 0));
   cubeList.push_back(new Point3F(1, 0, 0));
   cubeList.push_back(new Point3F(2, 0, 0));
   cubeList.push_back(new Point3F(3, 0, 0));
   cubeList.push_back(new Point3F(4, 0, 0));
You'll notice I changed the positions up a bit here just to be sure that I was understanding the placement correctly. In this case, I would end up seeing the cubes in a row once I get that far.

Also, I did try to mimic the examples with the utmost of scrutiny so I'm not sure exactly why I'm getting compiler errors when I'm declaring the stuff in the same exact way. I'm using VS2010 to compile.

Here's the relevant line in my submatrix.h:
Vector<Point3F*> cubeList;

I did have a couple other questions, just for a little clarity, but let's see if we can get it to compile first :)

EDIT: Okay, I just found a bracket I didn't move. Hang on, I believe I've overlooked something. I'll report back soon-ish.

EDIT2: Yep, the bracket allowed it to compile. No worries there, although my rendered object isn't what I expected again. I'm taking a closer look now to see what I missed.
#88
09/21/2014 (3:37 pm)
Alright, nailed it. Here's the row of cubes I expected:
i1213.photobucket.com/albums/cc466/rockoutsolid/Development/cubeSuccess01.png
I want to be sure I am 100% clear on what I'm creating here, so here are my inquiries:
  • Firstly, I noticed right away cubeList.push_back() being used. What is push_back and where does it originate?
  • Similarly, we were using cubeList.size() a lot and I was wondering how this function (size) worked. Is it native to C++? i.e. already included?
  • I'm still not entirely clear on primitives. You mention we have 12 prims per cube. What is a primitive and how do we determine the # for a given object?
  • The looping all makes sense to me except for one part. Here's your comment on it:
  • //	We're reusing the data in the arrays above for each cube,
          //    but they're all loaded into the vertex buffer consecutively so we
          //    need to keep track of what index in the vertex buffer we're at.
          U32 vertIdx = n * 36;
    I wasn't entirely clear here on multiplying the index by 36 and why.


EDIT: Oh wait! Just got my head out my ass and realized that the primitives are what I know as 'tris'. I see, 2 tris per cube face...or 12 primitives?
#89
09/21/2014 (5:16 pm)
Quote:
Firstly, I noticed right away cubeList.push_back() being used. What is push_back and where does it originate?
Similarly, we were using cubeList.size() a lot and I was wondering how this function (size) worked. Is it native to C++? i.e. already included?

Welcome to wonderful world of vectors. They're pretty awesome. A vector is like an array you don't know the size of. When you define it you need to tell it what kind of things it's going to be filled with. That's what I did on this line:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-h-L81-L82

I created a vector that will be filled with Point3Fs (starting size is 0), which are just positions.

You can see a list of some of the vector functions here:
www.cplusplus.com/reference/vector/vector/
(Should note, torque has its own version of Vector but it works pretty much the same)

So, push_back() adds the item to the end of the vector. push_front() will add it to the front (if you were so inclined). size() will return how many items are in the vector. clear() will empty the vector and return it back to size 0. Items can be accessed the same way you access array items, myVector[itemNumber]. This saves you from having to define a fixed number of cubes, and you can fill up the vector with as many cubes as you have, as well as remove items from the vector when you want to delete a cube. It's a very very useful class. As you add/remove cubes from your cubeList you'd just have to call createGeometry() again to rebuild the vertex and primitive buffer and then the game would update.

Quote:
I'm still not entirely clear on primitives. You mention we have 12 prims per cube. What is a primitive and how do we determine the # for a given object?
Quote:
EDIT: Oh wait! Just got my head out my ass and realized that the primitives are what I know as 'tris'. I see, 2 tris per cube face...or 12 primitives?

Nailed it. 2 tris for each face of the cube. Primitives and tris are pretty much synonymous.

Quote:
The looping all makes sense to me except for one part.

Okay, so lets say we have 2 cubes. That's 72 verts going into the vertex buffer. However, there's only 36 entries in cubeFaces and we're going to reuse cubeFaces for each cube. vertIdx is what I used to keep track of what position we're at in the vertex buffer as we're filling it in. n is what spot in the list of cubes we're at, and i keeps track of what spot in cubeFaces were at.

On cube 0 vertIdx will equal 0. Plus i, will go from 0 - 36 in the vertex buffer, while grabbing data from reading 0 - 36 from cubeFaces.

On cube 1 vertIdx will equal 36. Plus i, will go from 36 - 72 in the vertex buffer, while grabbing data from reading 0 - 36 from cubeFaces.

And so on, until the buffer is filled. Hopefully that makes sense. It's just a fancy way of going through all the spots in the vertex buffer we made while looping through all 36 entries in cubeFaces for each cube at the same time.
#90
09/21/2014 (5:36 pm)
All of that makes perfect sense to me, and thanks so much for presenting this all to me in a way that forced me into the Vector world.
Quote:
Do you think he plans it all out, or just makes it up as he goes along?
Gonna leave it up to you to catch the reference :P

Anyways, so yea I've been considering the application of this stuff for my project. By all rights, to be honest you've solved the ghosting issue and answered how to avoid scope being a problem by combining all the objects into a single 'mesh' geometry object. I've been enlightened, the world is a better place, and we can all rest assured that a C++ coder can win against this. I'm not a C++ coder though (not just yet :D) so many other things come to mind for me. You've already touched on most of it in a previous posting:
Quote:
There's still some missing pieces to the whole thing, and I'll go over how to add those in as soon as I can. The two things that come to mind are different materials on different cubes, and collision. You'll also have to set the world box size based on the maximum x, y, z reached by your cubes. If the worldbox is too small you'll see the whole object disappear as you move the camera around. None of these issues are very cumbersome to correct, just take a little tweaking.
I'm prepared, whenever you find the time, to continue down this path of uber learning! See, it's humbling because I can solve each and every one of these problems already if we were dealing with the objects in Torque as separate TSSTatics(with script). I'd generate the index and add the variables to hold stuff like materials or type(dirt, grass, air) and then iterate that list to build it all. Now that we're dealing with a single object, though, I'm having to 'speaka da C++' in order to accomplish any of this. I thought I was so clever; having all the add/removal of objects, generation of cubes, segregation of types, etc. all worked out in script. Lol, it was all a grand orchestration don't get me wrong but goodness that's all been cast aside as if it were yesterday's paper at this point. Looking forward to anything you can contribute here. Gosh, tbh at this point I feel like I need to jot down your PayPal and send you a friggin' tip :D

#91
09/21/2014 (6:20 pm)
Jesse, all the logic you've designed for choosing cube positions and materials will still be valid, and I imagine you'll end up calling methods on your new multi-cube object from script in order to build up the landscape. So all that design effort hasn't gone to waste! You're just creating the actual cube geometry in C++ now rather than in script.
#92
09/21/2014 (6:32 pm)
Well, the thing is that now the geometry is all one object :) So instead of, say, performing a raycast to see if you hit a single TSStatic cube to move it...now I'm having to perform some sort of raycast to see if I hit a cube of verts within a single object. Something I'm certain won't exist as a standard Typemask. As Andrew has eluded to, also the materials work good as is yes - so long as you want the same material on all the cubes in the object. Which, of course, isn't the case. I'll be wanting various materials to designate what is dirt, grass, air, rock, and so on.

Additionally, rather than being able to iterate through an index of separate TSStatic cube %objects in order to perform placement operations on them...Now that iteration needs to take place on a per cube basis from within the C++ code. See, we're creating an index of cubes but the whole point of all of this is to actually introduce them to Torque as a single object. This being the case, we won't be able to create the cubes as separate objects in Torque for placement. All of that is going to need to happen at a lower level in source prior to placing the 'submatrix'. I'm sure Andrew knows exactly what it is I'm on about. We'll just have to wait for him to clear it all up when he has the time :)
#93
09/21/2014 (9:16 pm)
Oh, I understand. I built something similar myself back in TGE - basically MeshRoad before T3D existed, based off another guy's resource. Had to implement collision myself and a couple of other bits and pieces. I don't want to give too much away because Andrew has you so well-covered :).
#94
09/21/2014 (9:54 pm)
Man, you've given me a fantastic resource already! The C++ site! I can't believe how much ground I'm covering there already. TorqueScript pretty much just cleared the path for me to learn this more rapidly! Like I've said before, I'm soaking this stuff up like a sponge. Finally, I feel ready. I'm actually up late tonight because I'm so excited about how much sense the docs there are actually making (course I'll be off to bed soon..). Stuff that used to look like ancient hieroglyph lol :)

You've paved the way for my future with C++ and Torque source. Man, I'm sorry about my actions before when I was blaming Torque for my personal shortcomings. It's kind of a catch-22 situation. Like I was ready to advance, to excel with Torque but I just wasn't seeing the path laid out before me. Yet as a result of my final defiant cry, Andrew answered the call. True dedication; I've seen no better display of comradery in any other community, let alone from people in general in this day and age. Well done! Haha I'm off to bed..
#95
09/22/2014 (7:01 am)
Multiple Materials

Multiple materials is actually dead simple conceptually, but I want to give a bit of in depth explanation so you can see how the whole system works. Let's start with what's actually involved in getting your single material to work.

First you need a property to let you set materials from in the editor. Currently you have one of these, and you've used it to set material to view your cubes. This is mMaterialName in the class. It's just a string with the material name. Here's where the property is exposed to torquescript:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L92-L9...

In packUpdate/unpackUpdate the material name is sent over the network (this would allow you to change the material and have everyone in the multiplayer game see it happen). Once we have an mMaterialName it calls updateMaterial to load the actual material. Here's where that magic happens:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L278-L...

The top part of the function is just making sure you aren't handing it an empty material name, that you aren't setting the material to something it already is (waste of time), and if that's all good it deletes the old material reference and loads the new one. This is not THE material for your object or anything, this just a fully loaded material you can now use, stored in: mMaterialInst.

Should note that when it uses SAFE_DELETE(mMaterialInst) it's not deleting the material from Torque, it's deleting your current reference to THAT material. I hope that makes sense. Materials only exist once in Torque, but they can be used multiple times on multiple models, so your model needs it's own link to that material, and that's a Material Instance, or mMaterialInst as the variable was called in this case. It looks this up based on the name provided.

We went over briefly before that the prepRenderImage function is called on an object and expects you to give it the information on how to render the object. This is by creating a MeshRenderInst, which is done here:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L314

Filling out the information for it (material, vert buffer, primitives), and then submitted it via this:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L378

What would happen if we made multiple MeshRenderInst, and submitted more than one from this call? It will draw each of them! Why would we want to do this? Because you can only assign one material per MeshRenderInst, and all the geometry you submit with that instance will be drawn with that one material.

I could leave it at "that's the way it is", but I'll give a quick rundown on why it works like this. When a graphics card goes to draw something based on your request it's like a painter approaching an aisle who can only hold one can of paint at a time (the can of paint being your material). So, let's say you have 50 blue cubes, and 50 red cubes. When you say "paint blue cube. paint red cube. paint blue cube. paint red cube." the painter is going to keep putting down the can of blue paint to pick up the can of red paint when he goes to paint the next request. This is slower and is actually what happens on the graphics card. When you go to draw a different material you need to put down everything you were using before, load up the new one, and then draw. Torque, and most game engines, try to minimize this by gathering a list of all things they're going to draw, each with a single material, then sort the list by material and draw them accordingly. This way, the painter picks up the blue paint, paints all the blue things, puts it down and picks up the red paint, paints all the red things. The job gets done quicker.

In order to support multiple materials, you'd have to first create multiple material instances by replicating the code in the example used to setup and handle mMaterialInst and mMaterialName. This would allow you to create multiple material slots in the object properties. In the C++ code you'd probably have like.. mMaterial2Name, mMaterial2Inst, etc. Then you could either keep a different cubeList based on material, or start to store a material number with each cube, and sort them into separate materials when you go to createGeometry. You're basically just going to be doing the same stuff you're doing to draw cubes with 1 material, all over again to draw another set of cubes with another material. You can do this for as many materials as you have.

This system has a limitation in that you have to preconfigure your object with a set of materials to be used by all the cubes inside it. There a ton of ways to expand this to be more flexible, but I'm trying to keep the example simple so it's easier to understand.

This might be a bit much, let me know how it sits.
#96
09/22/2014 (7:50 am)
Having to do the different render calls per material is why minecraft opted for a single texture atlas for all block textures.

Because all the block textures are contained in a single image, and thus material, all you need to do is change the UV offsets for your cube based on the block type(so grass has certain UV coordinates, dirt has different coordinates).

So when you go to pass it all to the graphics card, it can draw 'everything' in a single call.

I put 'everything' in quotes because there's still a numerical limit to how many primitives can be done in a single call, but it takes much fewer calls - and therefor is a lot faster - when you don't have to separate them out by material.
#97
09/22/2014 (9:34 am)
Jeff makes a very very good point I never thought of at all. I actually think you will definitely be better off using that approach. It will be significantly faster, and easier to implement. Take a look at this part:

gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L204-L...

That's where the UV coordinates are maintained for each side of the cube. This maps one side of the cube to entire size of the texture. If you were to cut those numbers in half, changing the 1's to 0.5's you should see each face of the cube displaying half of the area of the texture (1/4 technically, because its half width, half height). So the idea, as Jeff said, is to create a one big texture that contains all the textures to be used on the cubes, and that'll be your 1 material you use. Then we'll set different UV coordinates per-cube based on what material it is. Pretty simple with a little math.

Great catch Jeff, I wasn't thinking like Minecraft, and I should have been!
#98
09/22/2014 (10:22 am)
Alright, I just took a look at your initial posting and it all makes sense to me. Being well versed as I am with 3D modeling, I completely get the material instance stuff. I just now understand how to speak that to Torque specifically in code. Bravo! Conceptually, this is all sitting well with me almost immediately.

...in the middle of me typing this up to post it you replied and started to elaborate about the TexCoords stuff. That's the missing link here I believe. If you recall, I was totally on board with the Vertex # and the Normal # but the TexCoord was where I was hazy before. Now I believe you guys have cleared that all up.

The TexCoord is what I'm used to calling a UVMap in 3D apps. Just now it's in code. Nice.

The beauty of all this is that, yes, a texture atlas will work perfectly for what I'm doing. The view is isometric (maybe with a flycam) but the textures aren't going to need to be uber high res or anything like that.
Quote:Then we'll set different UV coordinates per-cube based on what material it is.
This is the part I will need a little further guidance on. Actually separating out the UV cords per material. Taking that one step further, if you wouldn't mind helping me to understand how I might add variables to my cubeList that would be great. I'm still a bit disconnected with the declaration of the stuff. Like, with this texture atlas it would be nice to have pre-declared 'types' for say dirt, grass, rock and so on. Then being able to set those UV coordinates based on which is the case. In script I already had this; I set a number to represent each type, and when I iterated the cubes I just did an if statement (which in hindsight I would have probably changed to a $Switch):
if(%obj.type == 1){material = "";}

I'm on board with everything presented here, and the in-depth material explanation really was a big part of me grasping it. Thanks a ton for that!

EDIT: Also allow me to elaborate a little bit on the way I'm envisioning this all coming into play once we're done. I am calling this new object Submatrix. This is the equivalent to a 'chunk' or similar in other cube world type games. So it's just a part of the whole world. I was thinking it would be possible to place several of these Submatrix objects to create the whole Matrix, which in essence is the entirety of the cube terrain. So each 'sub' only needs to be so big I guess. If it's accomplishing the task of reducing the ghost count significantly the bulk of its work should be done. Is this the same direction you were taking all of this Andrew, or did you envision this single object being capable of handling the entire terrain?

#99
09/22/2014 (12:10 pm)
I was thinking the same as you, these are chunks of the world, not the whole world. This helps too by allowing chunks you can't see to be not drawn.

So, because of Jeff's suggestion you don't need to change the material stuff at all, this greatly simplifies things. We'll only be changing the UV coordinates for each cube.

UV coordinates are pretty simple. Here is a shitty diagram I did up to help explain the values:
i.imgur.com/heYyZXa.png
Values range from 0 to 1. With 1 being the entire width (or height) of whatever texture is assigned to that side of the cube. If you were to assign the 4 UV coordinates shown in that diagram, (0,0) (0,1) (1,0) (1,1), you would get each side of the cube showing that entire image. All 9 colored squares. Now, if you assigned the following UV coordinates: (0,0) (0, 0.33) (0.33, 0) (0.33, 0.33) you will get 1/3rd of the height of the image, and 1/3rd of the width. This should be a grey square on every side of the cube! How would you get .. lets say.. the blue square that's 3 over and 1 down? It would be: (0.66, 0.33) (1.0, 0.33) (0.66, 0.66) (1.0, 0.66). Keep in mind these 4 coordinates are for the 4 corners of where it's gonna grab the texture from.

"But wait Andrew!", you may exclaim, "why do their UV coords have negative numbers?"
static const Point2F cubeTexCoords[4] = 
   {
      Point2F( 0, 0), Point2F( 0, -1),
      Point2F( 1, 0), Point2F( 1, -1)
   };

UV coordinates can do some neat things. For instance, if you went with 0,0 2,0 0,2 and 2,2 it would tile the texture and you'd see 36 colored cubes (9 * 4). You should recognize this concept from UV mapping stuff. If you make the area you're mapping bigger than the texture that goes on it, it starts to tile and repeat.

So what does -1 do? It's on the Y axis, so it's actually displaying the image upside down. Why? Because the way the verts are put together, they produce an upside down image. This just how the cube is being built. If you changed those -1s to 1s you'll see the texture upside down. The only place this matters is with the texture, so adding negatives to the UV coordinates is a pretty clean solution. You could rework the verts, but then your verts and everything else won't go together as nicely. Simply flipping the Y axis in the UV coordinates (technically the V axis lol) will put the image right side up and everyone is happy.

Start by making a texture similar to the one in my diagram, with simple different colors (or you could use textures if you want). Assign this for your cubes. Try changing the 4 values found in here to change your cubes to different color.:
gist.github.com/andr3wmac/45f0526d9534809df9ea#file-rendermeshexample-cpp-L206-L...

To get a handle on how they control the visuals of the texture. What you'll likely have to do in the end is create a little table that spits out UV coordinates based on which texture you want to use. This way when you go to add your cube to the vertex buffer you can look up its texture and provide the correct UV coordinates. Play with this to get a handle on how it works and I'll do up another post about how to create something to store more per-cube data.
#100
09/22/2014 (12:34 pm)
Alright, gonna test out some coordinates now to be sure I am grasping the flipped UV bit. I originally just took it literally to be:

0,0.............1,0

0,-1.............1,-1
(bahaha, tried to draw out a little square here but the forum formatting would have no part in it)

Since 1 is usually up and -1 is usually down for the y. I would have never thought of the stuff being flipped like this :) So this makes a very distinguishable difference for the 'V' axis you've mentioned. I guess I never looked too deeply into that stuff, the 3D apps always just kinda handled all that behind the scenes while I was doing all of this unawares with the GUI. Cool stuff, I'll test out some of the coordinates now.