AddMesh Weirdness

BlitzMax Forums/MiniB3D Module/AddMesh Weirdness

GNS(Posted 2010) [#1]
I've been tinkering with miniB3D (small fixes version) the last few days and ran into some really strange AddMesh() behavior that I hope someone can help me solve.

Here's the test case code:


And the required media (simply put these files in the same location as the above .bmx):

.b3d model

Using the default settings, the code above will read, pixel by pixel, the testMap.png file. Whenever it finds a "land" pixel (pixel with RGB = (0,255,0)) it creates a singleTileTest.b3d mesh at the corresponding location. This is referred to as the multi-surface test (or doMultiSurfaceTest()).

By setting USE_SINGLE_SURFACE_TEST to 1, the program reads testMap.png, places a singleTileTest.b3d mesh at the corresponding location and then uses AddMesh() to make batch everything into a single mesh/surface. The weirdness begins here (at least, it does for me). Vertex coordinates go wonky, the tiles overlap each other and, well, see for yourself.

Further strangeness can be seen by setting both USE_SINGLE_SURFACE_TEST to 1 and SEE_MORE_WEIRD_THINGS to 2. This will cause the program to read every other pixel of testMap.png, place a singleTileTest.b3d at the corresponding location and then AddMesh() everything together. The strange thing is, this works perfectly.

My best guess is I'm running into some kind of vertices-per-surface limit because changing the testMap.png to have less "land" pixels, or reducing the complexity of the singleTileTest.b3d model seems to solve the problem.

Thanks for the help, folks!


GNS(Posted 2010) [#2]
Some more info:

It does appear I'm running up against a vertices-per-surface limit. The limit seems to be 65535.

The singleTileTest.b3d model contains 33 vertices. Adding 1,985 singleTileTest meshes to the finalTiles mesh via AddMesh() results in finalTiles having 65,505 vertices in it's only surface (or, just below the 65,535 limit). Adding 1,986 singleTiletest meshes to finalTiles results in finalTiles having 65538 vertices. At this point Blitz3D will crash with a MAV on RenderWorld. MiniB3D doesn't crash but renders the finalTiles mesh with disorted polys.

Just based on some quick Googling, I'm assuming this is a hardware limit. Although klepto2 mentions (in this thread) his 'Extended' version can far exceed this limit. Not sure what to think.

Seeing as this is a per-surface limit the solution seems to be having AddMesh() create new surfaces if/when the max verts in a surface is reached. Need to do some more testing.


Kryzon(Posted 2010) [#3]
Hahaha nice joke by Bradford there...

@Thread: I think this is the culprit:
' arrays containing vertex and triangle info

Field tris:Short[0] '<---------------------
Field vert_coords#[0]
Field vert_tex_coords0#[0]
Field vert_tex_coords1#[0]
Field vert_norm#[0]
Field vert_col#[0]
From TSurface.BMX

I'm sure it'd be too good to be true, but try changing that to an Int (or Long, but that might be a bit much), then test again with your crash-code.


GNS(Posted 2010) [#4]
Hahaha, yeah, that joke gave me a good laugh.

I did try switching Short to Int (and stepping through the miniB3D source to change the other references to that array). In the end I got it to compile and run but nothing would render. Haven't attempted to solve that problem.

Edit: I'm currently working around this problem by tracking how many meshes have been placed (meshCount) and calculating how many total verts have been added to the finalTiles "batch." If adding another mesh to the finalTiles "batch" would cause it to go above the 65535 limit, I start AddMesh()'ing to another new mesh (i.e. finalTiles[1], finalTiles[2], etc.).

This method ensures no single mesh will have more than the max number of vertices added to it. Also, because it's using separate meshes the level is automatically broken up into large sections which might be culled.

As a stress test for this multi-mesh method, I tried a heightmap sized to 256x256. This created 65,536 singleTileTest meshes and AddMesh()'d them to 33 separate "batch" meshes. I think at one point it was rendering over 800k polys at ~300 FPS. A heightmap that large is far more landmass than I'll ever need for my game but it was certainly interesting.

Ideally AddMesh() should auto-create new surfaces if one is "filled" so instead of using multiple meshes you'd simply have multiple surfaces in the same mesh. I'll have to look into that later on.


kfprimm(Posted 2010) [#5]
There's a few things you have to do in order to make the switch to integers. The glDrawElements call in TMesh.bmx needs to have it's type parameter switched from GL_UNSIGNED_SHORT to GL_UNSIGNED_INT. I believe the math routines stored in the C/C++ files also have the indices arrays passed as shorts. Switch those to integers and that should help. Remember to switch their BlitzMax declarations (possibly located in MiniB3D.bmx?) as well.

This ought to work and apologize in advance if it does not. This is all off of memory.


GNS(Posted 2010) [#6]
I did go through and switch datatypes (from short to int). I managed to get it to compile, run and partially render a single mesh with 540k verts added to it. Unfortunately only part of the mesh would render.

While the multi-mesh method I mentioned above works, I'd really prefer if the program used a single mesh with multiple surfaces. The idea being I can "paint" a level map (pixel by pixel), load the image into the program and have it spit out a multi-surface .b3d.

What I really need is the ability to add meshes to specific surfaces.


Kryzon(Posted 2010) [#7]
What I really need is the ability to add meshes to specific surfaces.


That is not very hard. You can adapt the AddMesh in TMesh.BMX. Although, makes me think why do you have to use AddMesh at all - you are making a function from the ground up, those test functions of yours.
It's similar to terrain functions - you can make a function that reads those maps and sorts a surface for every different color. Also, it would be wise to weld your vertices to keep the vertex count low so you don't abuse the VBO bandwidth (meaning, don't create new adjacent tiles, but rather create triangles from the previous vertices).

Before the program takes place, you know how many different colors your tiles can have (let's say 8 colors total, for example). Therefore....
- Generate 8 empty surfaces for your map mesh.
- Start reading the pixmap like you are doing. 
	- For every pixel color that you just read, find the correspondent index of one of those 8 surfaces that should 
	  only store tiles of that specific color (since it's one surface for each color).
		  - If it's the first tile for that surface, then create it.
		  - If it's not the first, then check to see if the new tile is adjacent to this surface's last created one, so you can use some of the previous vertices to create it. 
In the end, you should have strips of adjacent tiles for large areas of the same color, tiles that are welded and not "open". At different surface intersections you will have same-position vertices, but you'd only fix that if you made everything just one surface and mapped each tile to a UV that corresponded to it's texture in an texture atlas (which is an even more advanced option to optimization).

It is going to take a bit of work coming up with this, so, good luck and patience to you.


Corum(Posted 2010) [#8]
This thread is very interesting.
About vertices weld, I lurked into B3D code archives, and found this one.
Maybe it could be useful for your purposes.
BTW, I'm curious to know how your're going to workaround this problem.
Please, keep us informed. ;)

EDIT: Anyway, i second the Kryzon suggestion: do it by yourself in a Terrain-like approach. I'd like to make it by assigning a single texture to the final mesh, containing the whole tiles graphics, sorted as a big array and, if possible, just play with UV coordinates of the single quad (terrain tile), in order to place them on the desired tile into the big texture array.
Fog-of-war and seasonal-daytime friendly. :P
Could it be possible?

EDIT#2: Yes, it's possible, as Kryzon explained (much better than me.) :)


GNS(Posted 2010) [#9]
Kryzon has the right idea, it's just a bit beyond what I need. For my purposes I have an arbitrary number of meshes (tiles) and simply needed a way of reading a heightmap, placing the appropriate tile at the corresponding location and then batching everything together to keep state changes to a minimum.

There's nothing complicated about reading the heightmap and placing tiles everywhere (in fact, that's exactly what doMultiSurfaceTest() does). The problem I ran into was a hard limit on the number of vertices per surface, which AddMesh() doesn't account for. Further, there's no way of telling AddMesh() which surface to use on the destination mesh. In my case I was creating thousands of tiles and AddMesh() was attempting to add all of their geometry data to a single surface which just isn't possible.

I'll most likely look into creating a slightly more intelligent version of AddMesh() (maybe call it AddMeshSafe() :]) which counts the vertices in both the source mesh and destination mesh. If adding the source mesh data would put the destination mesh over the 65535 limit, it'll create a new surface and add the source mesh data to that.

It's not a one-size-fits-all solution but it should work for what I need.


Kryzon(Posted 2010) [#10]
That's a very good safety measure, and it should even be included in the original AddMesh from miniB3D\Blitz3D :)