Game Development Community

Procedural road object (help?)

by Dan Keller · in Torque Game Engine · 03/18/2008 (2:30 pm) · 62 replies

I made an object that automatically renders a curved road from a path. Well, it's more of a proof of concept, but it works fairly well. I'm having two problems, though:
*it ignores fog when rendering and sometimes doesn't render at all
*I have absolutely no idea how to get collision working.

Both of these are areas of the engine I know very little about. So here are the files. Can anyone help me on this?

fxRoad.h
fxRoad.cc
And yes, I know the object is re-calculated every frame, that's temporary.

Oh and thanks to Melv May for the fxRenderObject code that this is based on.

66.49.245.246/road.jpg
#41
01/19/2010 (9:36 pm)
So does anyone know the purpose of the PathManager? It seems pretty useless to me. All it does is store all the paths on the server. If I were me, which I am, I'd store all the data that's in PathEntry in the Path object itself.

It also strikes me that there's no way of knowing a path's length, only its total time. Which is a bugger, since I wanted to do resolution-dependent detail (i.e., create a vertex every 2 metres). I guess it'll have to be time-dependent detail (create a vertex every 100ms), and you control that detail through the nodes' msToNext parameter.
Actually, that works well, since on straight stretches you can set the detail down and on curvy bits you can scale it up.
#42
01/20/2010 (7:18 pm)
Progress!

img188.imageshack.us/img188/2723/proceduralpath1.jpg
I rewrote the point generation code, so I can now read it. It's pretty simple - it just takes the transform of each point in the path and sticks one point out to the left and right. So now the path is built completely dependently on the rotations of the path nodes.

I'm still sorting out the looping bit - the PathManager isn't exactly easy to work with.
EDIT: Looping is solved.

Also, texture coordinates are just kludged with one repetition of the texture per 'quad'.


EDIT: The next problem is that... you guessed it! Sometimes it doesn't render :P. If I go into the editor and click on the road to force an update, it's all fine. It's as if the bounding box isn't being updated when the mission loads... which it should be, since all the road points are in the right places!

EDIT: Maybe it's client/server differences? When the fxRoad is created originally, it's got no path... so its object box doesn't fit its nodes. Then when I click on it in the editor, it's updated server-side, and that seems to let me view it properly.
Hmm. And since fxRoad doesn't process ticks, there's no way of checking on a constant basis whether we need to update - we only get event-based shots.

Is there like an 'on mission loaded' callback for SimObjects? :P
#43
01/20/2010 (11:38 pm)
That was stupid. Problem solved - I wasn't calling resetWorldBox after changing the object box extent in update() :P.
EDIT: No, wait... the problem still exists, because the road on the server has no vertices set up (because for some reason, when it is creates, its path has an invalid ID). That's probably a bad thing for stuff like collision.
I guess I'll just need to call update before trying to get collision info out of it.
But the server won't know to get any collision info out of it because its bounding box is 0 in all directions. Arg. /EDIT
EDIT: pathOnMissionLoadDone! I might just jump into that and use it to update all roads server-side.
EDIT: I just cloned that function, in the interests of maintaining a bit of separation, and call roadOnMissionLoadDone from scripts after pathOnMissionLoadDone. This is totally not ideal, but I don't see any way around it short of making fxRoad a GameBase so that it can process ticks. That may have its own advantages, but I don't think tick processing is a very useful feature for what fxRoad is designed to be.

In other news - I fixed the looping problem by forcing the path to generate points at 0 and 1 positions between every node - so I'm actually doubling up on points. However, this will hopefully mean I can split up each path segment (the vertices between two path nodes) for rendering purposes with no visual side-efffects, and then toggle rendering them for efficiency.

I just have no idea how to go about culling pieces of an object for visibility purposes, once the entire object is visible.
#44
01/21/2010 (5:43 am)
More progress - borders are back in.
img33.imageshack.us/img33/3066/proceduralroadfirstbord.jpg
I managed to work it such that less vertex data is stored - instead of a Vector for the top, left and right borders, there's just a vector for the top and the bottom - since essentially the road is a trapezoidal cross-section, so we only need to store 4 vertices for each slice (two in each list to keep things simpler... but I might consider moving everything to one list. Anything to be gained by doing this?).

Vertex texture coordinates are a pain. I'm really not sure what needs to happen to make it nice :P. Ah well.

Also - does anyone know how I might get lighting on the primitive triangle strips I'm drawing?

Other news: the path is officially variable-resolution. By varying the msToNext member of a path node, you effectively alter the resolution of the mesh. So if you have a long straight section you can do it all as two triangles.
img33.imageshack.us/img33/7259/proceduralroadvariabler.jpg
This works in tandem with the 'detail' member of the path itself. I don't know if that's desirable, unless there's a real need to put multiple road objects on the same path.





EDIT
Borders are now textured. I eventually realised it was a silly notion that I only needed to store one texture coordinate list per vertex list :P. Anyway, now you can texture each border uniquely:
img251.imageshack.us/img251/7014/proceduralroadbordertex.jpg
And I'm working on putting a bottom texture on.
#45
01/21/2010 (5:00 pm)
You can get discontinuities in the top surface texture along turns. For MeshRoad I subdivide the road lengthwise several times ( I think this is either hardcoded or a metric of the greatest width along the road ).

Therefore you are specifying uvs at several points along a single width-wise cross-section of the road. The more division the less texture discontinuity is apparent.
#46
01/21/2010 (8:58 pm)
Thanks for the idea... I'm not sure how I'm going to deal with texture coords eventually, for now they're just stretched with one full texture per quad. Which is why the top surface texture is so compressed along the length of the road.

EDIT: Per-node scaling is working. The road now widens according to the scale.x of each node (with lovely sinusoidal interpolation), and the border lengthens according to the scale.z (however, its angle doesn't change - I wonder whether that's desirable).

Anybody know if there's a way to make objects unselectable in the world editor? It's annoying trying to edit road nodes when the bounding box of the road itself encompasses them, meaning that's always selected unless you get the camera inside it. You should only be able to select the road through the editor tree, since you only really need to do that once to update it after messing with its nodes.

I'm thinking of using the onEditorEnable/disable callbacks... let's see...

EDIT: Don't you hate it when you click 'rebuild' instead of 'build'? :P

EDIT: No dice just killing the world box when the editor's enabled - the editor seems to use the object box to calculate collisions?
No, it shouldn't... since the box I see in the editor is world-aligned. Hrmph.



EDIT: Decided that was less important than getting collision working. The first step was to create a bounding box for each segment, to optimise collision detection. Without further ado-
img683.imageshack.us/img683/3825/proceduralroadsegmentbo.jpg
#47
01/22/2010 (10:14 pm)
Okay, I managed to solve the editor problem by defining collideBox in fxRoad and just returning false - turns out collideBox is used by the editor, so getting rid of it means that pesky bounding box doesn't get in the way.

On the other hand, castRay is proving much more difficult to implement. It seems like for some reason it's not even being called. That indicates to me that the world box isn't being set correctly on the server, but that can't be the case since I can breakpoint the update function and watch it being set properly. Also, since it's rendering properly on the client, I know that it's being set correctly there - and the client-side projectile should raycast it and register a collision even if the server-side projectile doesn't.

I thought originally it was typemasks, but Projectiles search for StaticObjectType and fxRoad includes StaticObjectType. Hm.

EDIT: Okay, so I discovered that somehow the world box wasn't being correctly set on the server... I needed to setTransform() after changing the world box for some reason. So now castRay is called on the road, but I'm not getting any collision. I have to assume now that it's an error in my collision routine :P.
#48
01/23/2010 (5:30 am)
I realised when I was frantically looking for alTypes.h that someone already implemented simple triangle raycasting for me, in util/triRayCheck.h. So I replaced my code with that and it all works beautifully, after a little messing around with odd normals due to the winding of the triangles. Now to implement convex collisions.

I also worked out how to render lines over my polygons, so I've got a nice debug visualisation:
img39.imageshack.us/img39/3180/proceduralroadwireoverl.jpg
The render loop for that is completely whacked, due to the way I store vertices:
//
         glBegin(GL_LINE_STRIP);
         U32 j = 0;
         U32 stage = 0;
         while(j != mSegments[i]->verts.size() - 2)
         {
            glVertex3f(mSegments[i]->verts[j].x, mSegments[i]->verts[j].y, mSegments[i]->verts[j].z);
            switch(stage)
            {
            case 0:
               j += 1;
               break;
            case 1:
               j += 2;
               break;
            case 2:
               j -= 3;
               break;
            case 3:
               j += 2;
               break;
            }
            stage++;
            if(stage > 3)
               stage = 0;
         }
         glEnd();
You'll also notice that I've kind of taken a step back in that I've removed the borders from the road... I refactored the road to properly render in segments, as well as hooking that up to dynamic segment switching. So if you made a bridge out of an fxRoad and wanted to blow it up, you could just switch off the appropriate segments and they'd be gone, but the rest of the road would remain.


EDIT. I'm thinking the way to do collision with a complex object like this (basically needs to simulate poly-soup collision) is to create a new Convex class that just stores one triangle, and return a bunch of those to getConvex queries. Is this massively inefficient?
#49
01/23/2010 (7:12 am)
!!!

img96.imageshack.us/img96/2677/proceduralroadconvexcol.jpg
Collision success! I'm not quite sure that there aren't any horrible nasty bugs in the collision, but it seems to work pretty well! I'm shocked and amazed that something as complex as creating a new Convex class and hooking it into fxRoad worked on the first try! (Well, second - I had to wind the vertices the other way to get the normals right.)

Though I do have a memory leak, I'm pretty sure - can someone check buildConvex for me? I don't know how all that stuff works, what to unlink, what to delete, etc.
void fxRoad::buildConvex(const Box3F& box,Convex* convex)
{
   // Let's just get rid of all existing convexes from us
   CollisionWorkingList* cwl = 0;
   Convex* cc = 0;
   CollisionWorkingList& wl = convex->getWorkingList();
   for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
   {
      if (itr->mConvex->getType() == TriangleConvexType &&
         static_cast<TriangleConvex*>(itr->mConvex)->getObject() == this)
      {
         cwl = itr;
         cc = itr->mConvex;
         itr = itr->wLink.mNext;
         cwl->unlink();
      }
   }

   //Start the process of adding convexes to the list. First determine which segments are affected
   ...
}



EDIT: Now I think I have to implement buildPolyList to enable shadows to be cast on the road... hmm...

EDIT: Yup, that was it :D
img199.imageshack.us/img199/8218/proceduralroadshadowcol.jpg

EDIT: usability upgrade: now instead of selecting the road to update changes you've made to the path, I jumped into updatePath and checked to see if there were any fxRoad objects in its list - if there are, then update them! This means you get near-instantaneous feedback on changed you make to the path (not real-time like T3D's road editor - but as soon as you apply a change to a path node the road is updated).
Also, I just realised, it means roadOnMissionLoad is covered by pathOnMissionLoad, so I can get rid of that function! It was bothering me.
#50
01/24/2010 (2:21 am)
Hey Daniel,

This is a great thread you're producing, I'm not sure I'd have the patience to document the thoughts, processes and code as I work through something.

You're coming up with a great end result, let me know if I can help with anything.

In terms of your memory leak, are you sure you shouldn't be using Convex::collectGarbage or Convex::nukeList?

Cheers.
#51
01/24/2010 (3:19 am)
Well, the purpose of buildConvex is to add convex hulls to represent the road's collision. In most buildConvex methods I've seen, we're only adding a single convex to the list, and we do a check at the start to see if it's already on the list, in which case we of course don't add a new one.

Since I add multiple convexes (one for each triangle), my idea was to just find all the convexes already on the list that belong to us and get rid of them, since I'm not sure how to go about identifying them in such a way that I could avoid adding multiples.

I'll have a look at those methods you suggested, though - thanks!

About my stream-of-consciousness posting... I'm glad at least one person isn't annoyed by it ;P.

EDIT: I think I've got what I needed... deleting a Convex seems to deal with whatever linked list stuff is going on in CollisionWorkingList, so I just have to delete the appropriate convexes.
//
   // Let's just get rid of all existing convexes from us
   CollisionWorkingList* cwl = 0;
   CollisionWorkingList& wl = convex->getWorkingList();
   for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
   {
      if (itr->mConvex->getType() == TriangleConvexType &&
         static_cast<TriangleConvex*>(itr->mConvex)->getObject() == this)
      {
         cwl = itr->wLink.mNext;
         delete itr->mConvex;
         itr = cwl;
      }
   }




I have to give props to Matt Fairfax whose excellent tutorial has gotten me this far. That sort of stuff is exactly what Torque needs!
#52
01/24/2010 (6:05 pm)
This is a great feature, awesome work guys...

Edit: Going to check this out soon.
#53
10/21/2010 (6:40 pm)
I love the progress you have made on this resource. Have you posted your final changes anywhere?
#54
10/21/2010 (11:48 pm)
Nope - it's not finished yet! :P The road itself is pretty okay - there are some collision oddities, probably because of my chosen method of returning multiple separate convex triangles. But right now I'm trying to figure out the lighting. I can get point lights to affect it, but for some reason the sun and ambient aren't showing up.

If people want it, I may just release what I've got and we can all pull together for the home stretch ;P.
#55
10/22/2010 (6:00 am)
I'd love to help and see what you have so far, fxDecals don't produce a very nice road effect lol.
#56
10/22/2010 (6:22 am)
Okay, I've uploaded the code here.

The merge should be fairly simple. I forgot to upload my barebones.mis with example path and road, but here's a path object you can add to the path that's already in the mission:

new fxRoad(road) {
         canSaveDynamicFields = "1";
         position = "382.207 312.281 219.3";
         rotation = "1 0 0 0";
         scale = "1 1 1";
         width = "4";
         borderWidth = "1";
         height = "0.5";
         Texture = "~/data/terrains/envpack/sand.jpg";
         LeftBorderTexture = "~/data/terrains/envpack/snow1.jpg";
         RightBorderTexture = "~/data/terrains/envpack/snow2.jpg";
         Path = "Path1";
         Detail = "100";
      };
Just add that inside the definition of Path1, before any of the PathNodes are declared.
#57
10/22/2010 (9:31 am)
Appreciate the quick response, I'll go ahead and merge this in tonight(morning) and see what I can do with it... I'll post results tomorrow night(morning, lol).
#58
10/23/2010 (11:41 am)
Tried compiling stock tge and it gave me the error:
Cannot open include file: 'collision/triangleConvex.h'
#59
10/23/2010 (2:24 pm)
Thank you for releasing. I am not going to be ready to test it for probably another month but I will monitor progress.
#60
10/23/2010 (9:44 pm)
Oh man! I totally forgot about the triangleConvex class :P. Give me a sec to re-upload. I may as well include barebones this time around as well.

torque.abigholeintheweb.com/public_system/useruploads/TGE152_fxroad_BETA2.zip

Hopefully that's it... don't want to be spamming eb's upload system.