Game Development Community

Semi-random thoughts about terrain deformation

by Stephen Zepp · in Torque Game Engine · 11/04/2004 (4:25 pm) · 52 replies

I'm brainstorming a design to be able to ghost run-time terrain deformations across a network, and wanted to throw some ideas out for discussion.

I think the primary problem will be keeping the bandwidth down as low as possible per deformation event while making sure you can get a variety of deformation types. My first thought to do that would be to have a set of deformation shape bitmaps that are small greyscale images of a deformation event, with the "non-changed" terrain height indicated by probably midscale, and terrain height increases higher color, while terrain height decreases on the lower side of the scale.

We could possibly create a deformation event object which would be created server side (based on client or server actions) that would contain:

--deformation event location (possibly just a single terrain location where the bitmap would be applied)
--deformation timeframe (instant, possibly a short, or even long term duration?)--by this I mean how long it takes from start of the deformation to the end of the deformation process. Alternatively, if it was a "temporary" deformation, you could have an expiration time where the deformation would "go away".

You would then want the client to apply the deformation once it is updated by the server to their current terrain map using techniques from terrainActions.c in the editor base code.

The basic flow for a deformation event would be:
1. Client (through a server command possibly), or server (through script/code event generation) would request a terrain deformation of a specific type.
2. Server would create a ghosted deformation event, assign an appropriate bitmap (all clients must already have each of the various bitmaps), set the deformation event location, and (possibly) the duration of the event process
3. Server would ghost the deformation event to the clients
4. Client would receive a deformation event, and apply it
5. Deformation would be rendered.

This is an open invitation to brainstorm--if you want to bash the idea, I would at least ask that you provide an alternative implementation concept!
Page «Previous 1 2 3 Last »
#1
11/04/2004 (5:19 pm)
I like the idea. As a matter of fact, I brought up a very similar idea at IGC 2003. To expand on it further, here's a few features it could sport:

1) For the bitmap, you can probably make mini .ter files and transmit them(there's a thread around here with the format, and it's amazingly simple to write).

2) Masking: You can use alpha map-like masks to prevent players from digging into metal, or for reducing the amount of deformation for areas that would be made of harder material such as rock or clay. So for this you would transmit both the deformation, and the mask for it.

3) Instead of transmitting the deformation bitmap, the deformations can be a set of procedural "brushes" that only need the settings transmitted. This would save on time, and might be more flexible. For masking, you can implement a solution where the textures used in the landscape can be queried and have deformations applied to the applicable heightfield posts in realtime.

That's all I have for now, but I'm sure I can think of more... :)
#2
11/04/2004 (5:40 pm)
Are these impacts variable? What I mean is, can we expect all ZR-1000 missles (a name I made up) to create a consistent impact on a surface? If thats the case, then a bitmap wouldn't even have to be sent across the network. Just like lightmaps, an impactmap would reside on each client. Then all you have to send across the network is the location.

If the impacts are variable, you can still use this method. The impactmap is used as a template basically (similar to light intensity). Along with the location, the server also sends an intensity value, to tell the client how hard to press the impactmap into the terrain.

Not sure how all of this affects cheat-ability.
#3
11/04/2004 (5:45 pm)
I'm pretty sure the hackers will find a way to exploit it, just like everything else in a game ;)

One thing I thought of when you mentioned lightmaps is, would these terrain deformations look funky because of the fact that the lighting is precomputed? I guess we won't know until someone tests it and sees.
#4
11/04/2004 (5:54 pm)
Yes, the deformations will look funky until the shadows are applied. I'm not sure if the engine is capable of re-lighting a specific local area (the area of impact).

Terrain deformation is pretty difficult to maintain though. Sometimes weird shapes occur. And how would this affect LOD?
#5
11/05/2004 (3:16 am)
Re-lighting: we know that you can relight a scene in the editors with a keypress. While I haven't looked at that specific code yet, I would assume for the sake of discussion that we can modify the existing re-lighting functionality to re-light a smaller area. If not, I would guess that the lighting pack has some way to handle shadows changing based on light sources moving--might be worthwhile looking at that if someone has the pack.

@David D: I know you have a day/night cycle in your current game, and I'm assuming that you handle re-lighting somehow on the fly. If that assumption is true, what type of technique do you use?

transmitting bitmaps: While I don't see a huge problem with sending small bitmaps across the connection, my original assumption was to have them already on the client (as part of your distribution). We have a 4k per sec software coded bandwidth limit (if I remember correctly), so my goal is to limit as much as possible what is transmitted down the wire when a deformation event occurs. Call it an impact map like Randall said--good term.

brushes: I hadn't gotten that far in the discussion, but my project actually has much more of a need for the procedural concept that Ted mentioned--instead of missile impacts, we're looking at things like players building a foundation for a building (levelling terrain to a fixed height), digging moats, etc. For those cases, instead of an impact map, we could transmit params within the object to allow the client to procedurally generate the impact map--something like: type-rectangular, width, length, height-delta, deformation locus: x,y: x1,y1: x2,y2: x3,y3.

maintaining: Shadowbane had a really big problem with this--you would plant a building, and not only could wierd things happen with the immediate terrain around you (deformation NOT applied, deformation applied wrong, etc.), but as you mentioned, the deformation would sometimes blink in and out every couple of seconds, or appear differently from different positions on the map. Frankly, I think it was just poor implementation, but I'm not positive. It would require some excellent dev and testing.

LOD: IIRC, terrain LOD is dynamic? If that is the case, the underlying/existing LOD code for terrain should work transparently I would think?

deformation variance: We can certainly come up with various techniques to make a specific impact map "fuzzy", and/or provide a variety of impact maps that are selected randomly for a specific impact (in the case of you missile impact guys).

Persistence: I think we will want to write deformation events to a local file on the client with a server generated DeformationEventID (NOT the object id--that can change from server instance to server instance). That would allow for a client coming back the next day, and not having to re-ghost all the deformation objects again and again--they could simply load them from the file during initial bootup. For those with server side persistent stores, you would save them off as appropriate to your db, or to a file on the server.
#6
11/05/2004 (7:03 am)
I have written a TerrainDeformer class. It does not yet do relighting or texture manipulation. It's a rudamentary terrain deformer object. It seems to work single player. I have yet to test it multiplayer as I am not working on it full time. If you're interested in the source, please email me. I don't want to make it a resource until I know it works.

Robert

P.S. could use a hand with multiplayer testing if anyone wants to volunteer, email me or find me in the GG irc channel. I'm usually there.
#7
11/05/2004 (7:17 am)
This was basically a "prep a design for next month's work" type thread for me, but I can certainly take a look and work on testing it in our dedicated client-server installation.

E-Mail on the way!
#8
12/28/2004 (5:00 am)
To revive an old thread:

I've finally gotten networked terrain deformation to the top of my task list, so I started playing around with the code that Robert sent (very nice by the way).

My specific requirements involved being able to deform a particular (small) rectangle of terrain to an exact height, to provide a level "foundation" for a building to be placed upon in real time.

The networking aspect is working great, and we get the deformation object working on both server and client (dedicated environment), but I have one remaining issue that I understand, but can't quite find the values to fix.

The basic problem is that calling getHeight(Point2I, &h); returns an absolute value within the game world, I assume taking the height scale of the terrain block as a whole, as well as the initial position of the heightmap itself. Unfortunately, setHeight appears to need a value that is scaled (probably to 0-255?), so if i simply do the following:

terrBlock->getHeight((x,y), &h);
terrBlock->setHeight(x,y), h-d); where d is a the delta I want to change the current height level by.

What it does is give me a massively high spike in terrain altitude for that point.

My root question is:

What values are consistent across the entire terrain block that indicate the min and max values for a specific terrain block, so I can scale between those values, and therefore send an appropriate height via setHeight? Each square has it's own min and max heights, but those are specific I think to that square only, and haven't been useful in any scaling attempts.

Terrdata.h shows that the class TerrainBlock has two private fields:

S32 mHeightMin;
S32 mHeightMax;

Which seems what to be what I am looking for, but before I went in and created access methods for them, I wanted to make sure that I wasn't missing something obvious.

Any thoughts?
#9
12/28/2004 (8:03 am)
Heights are stored internally in the terrain code as unsigned 16 bit integers. fixedToFloat and floatToFixed convert. The min and max data is stored in the BlockShift level GridSquare's min and max extent fields.
#10
12/28/2004 (8:47 am)
Yeah, I've been playing around with all that, trying to figure out the best way to adjust a terrain square's height. Unfortunately, somehow the following sequence causes some -really- nasty spikes, for an unknown (to me) reason:

grab the height of a point with getHeight(X,Y). Seems to return a "true" value in world coordinates, i.e., "4915".

set the height of the same coordinates to a smaller value with setHeight(X,Y, height - 10);. This causes the terrain to either jump to a HUGE graphic spike (as if the value were infinite high), or a HUGE graphic "hole" (as if the value were inifinitely low).

I've messed around with all sorts of scaling attempts, from writing a TerrainBlock::scaleHeight() method to using techniques like
F32 height;
      bool res = terrBlock->getHeight(Point2F(terrPos.x, terrPos.y), &height);
      if(res)
      {
         terrPos.z = height;
         terrPos.convolve(terrBlock->getScale());
         terrBlock->getTransform().mulP(terrPos);
      }
and using terrPos.z as the height to subtract from, but no dice ;) The bit about unsigned does point me to a possible issue however--I've been clamping to a signed int instead of unsigned--will give that a shot.

Thanks!
#11
12/28/2004 (11:28 am)
That doesn't work at all, height isn't scaled in the default terrain.

I think you're getting your spaces confused, between 16 bit integer heightspace and 32 bit floating point heightspace.
#12
12/28/2004 (11:35 am)
I would agree with that assessment!

I still can't figure out why you can't simply use a value returned from getHeight, subtract a small number from it, and then send it back in setHeight.

Giving it a break for the night and coming back to it tomorrow, hopefully a fresh outlook will make something obvious.
#13
12/28/2004 (11:58 am)
This the the processTick() function for the object Stephen is referring to. When I made this, I just wanted to get the networking in, and see if I could do the most basic deforming. What I had planned for this was deformation profiles, which could define a shape for the divit. For now this just effects one point. This just gets the height at a particular point on the terrain, and adjusts it to what you pass in when you create the divit. It's networked so it occurs on the client and server, and players who join games can see the divits that were there before they joined. I made a small demo where if you shoot at the ground, you get both an explosion plus the terrain sinks in a little bit. You can walk around in the hole. There is no relighting or texture considerations yet.

void TerrainDeformer::processTick(const Move* move)
{
Parent::processTick(move);

if (!mTerrainDeformed) {

TerrainBlock *obj = dynamic_cast(Sim::findObject("Terrain"));
//NetConnection * toServer = NetConnection::getServerConnection();
//NetConnection * toClient = NetConnection::getLocalClientConnection();
//S32 index = toClient->getGhostIndex(obj);
//TerrainBlock *terrBlock = (dynamic_cast(toServer->resolveGhost(index)));
TerrainBlock *terrBlock = obj;
if (!terrBlock)
return;

Point3F p = mPosition;
F32 r = mRadius; //mDataBlock->radius;
F32 d = mDepth; //
F32 type = mDataBlock->type;
//
const MatrixF & mat = terrBlock->getTransform();
Point3F origin;
mat.getColumn(3, &origin);
F32 squareSize = (F32) terrBlock->getSquareSize();
F32 halfSquareSize = squareSize / 2;
float x = (p.x - origin.x + halfSquareSize) / squareSize;
float y = (p.y - origin.y + halfSquareSize) / squareSize;
Point2I pos2i((S32)mFloor(x), (S32)mFloor(y));

//
F32 h;
terrBlock->getHeight(Point2F(p.x, p.y), &h);
terrBlock->setHeight(pos2i, h - d);
terrBlock->updateGrid(pos2i, pos2i);

MatrixF xform(true);
mPosition.z = h;
xform.setColumn(3, mPosition);
setTransform(xform);

mTerrainDeformed = true;
}
}

Certainly not perfect. I got good results with one person in multiplayer, and not with another. So there is more work to be done. The depth of the divit in the terrain seemed to vary. Maybe I did not allocate enough bits for the depth parameter in the networking code. You might check that Stephen.

Robert
#14
12/28/2004 (6:05 pm)
FYI, Robert's code was the original I was working with. I've modified it to handle deforming a procedurally generated rectangle (this is for making "building foundations" for the RTS-SK, but can be used for a variety of things as well). I've already adjusted the networking code (it needed a bit of tuning, mostly variable types and pack settings as Robert mentioned).

Please note, this is a prototype/testing imp, and very much not elegant! Trying to get basic functionality to work (deform a small rectangle by a small amount) before I get too far into making it elegant.

Here is one iteration of my current processTick():

void TerrainDeformer::processTick(const Move* move)
{
   Parent::processTick(move);

   if (!mTerrainDeformed) 
   {
    Con::printf("TerrainDeformer::processTick--we have a deform object! position: (%f %f %f) (xSize: %i, ySize: %i, depth %i)",
                 mPosition.x, mPosition.y, mPosition.z, mXSize, mYSize, mDepth);



     TerrainBlock *terrBlock = isServerObject() ?         
                               dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain")) :
                               gClientSceneGraph->getCurrentTerrain();
     if (!terrBlock)
     {
       Con::printf("TerrainDeformer::processTick()--No terrain block found!");
       return;
     }


     // Our deformation objects are rectangular, and defined by their world position, and their mXSize,
     // mYSize. To apply the full deformation, we need to iterate over the box's bounds, using the position
     // of the object as the center of the rectangle
     const MatrixF & mat = terrBlock->getTransform();
     Point3F origin;
     mat.getColumn(3, &origin);

     F32 squareSize = (F32) terrBlock->getSquareSize();
     F32 halfSquareSize = squareSize / 2;

     F32 boundsXStart = (mPosition.x - origin.x - mXSize/2 + halfSquareSize) / squareSize;
     F32 boundsYStart = (mPosition.y - origin.y - mYSize/2 + halfSquareSize) / squareSize;
     F32 boundsXStop  = (mPosition.x - origin.x + mXSize/2 + halfSquareSize) / squareSize;
     F32 boundsYStop  = (mPosition.y - origin.y + mYSize/2 + halfSquareSize) / squareSize;

	   F32 r = mRadius; //mDataBlock->radius;
	   S32 d = mDepth; //

     // this is our iteration loop to adjust all the heights:
     for (S32 xAdjustLoc = (S32)mFloor(boundsXStart); xAdjustLoc <= mFloor(boundsXStop); xAdjustLoc++)
     {
       for (S32 yAdjustLoc = (S32)mFloor(boundsYStart); yAdjustLoc <= mFloor(boundsYStop); yAdjustLoc++)
       {
         Point2I pos2i((S32)mFloor(xAdjustLoc), (S32)mFloor(yAdjustLoc));

         U32 h;   
         h = terrBlock->getHeight(xAdjustLoc, yAdjustLoc);
         terrBlock->setHeight(pos2i, (U32)(floatToFixed(h - d)));         
         Con::printf("--Changed point %i, %i from height %i to %i. ", xAdjustLoc, yAdjustLoc,
                       (U32)floatToFixed(h), (U32)floatToFixed(h - d) );       
         // check to see if we can move the bottom update grid and transform stuff outside the loops
	       terrBlock->updateGrid(pos2i, pos2i);
         MatrixF xform(true);
         mPosition.z = h;
         xform.setColumn(3, mPosition);
         setTransform(xform);
       }
     }
	   mTerrainDeformed = true;
	   setProcessTick(false);
   }
 }

As in 95% of my iterations so far, pretty much any value for "d" causes an "infinite spike" to be created, of the appropriate X,Y dimensions and in the right place, but with a wildly offscale terrain height value.
#15
12/28/2004 (6:09 pm)
I've written a set of script wrappers to allow the client (dedicated environment) to enter a "ForceDeform(xSize, ySize, depth)" console command, and then click on the terrain. The data is sent via commandToServer to request the deformation once the terrain is clicked to indicate where. A common report from both client and server consoles:

==>forceDeform(60,30,2);
keyboard0 input device acquired.
TerrainDeformer::processTick--we have a deform object! position: (-22.040001 11.710000 255.650085) (xSize: 60, ySize: 30, depth 2)
--Changed point 121, 128 from height 8215 to 8151.
--Changed point 121, 129 from height 8227 to 8163.
--Changed point 121, 130 from height 8239 to 8175.
--Changed point 121, 131 from height 8251 to 8187.
--Changed point 122, 128 from height 8207 to 8143.
--Changed point 122, 129 from height 8219 to 8155.
--Changed point 122, 130 from height 8231 to 8167.
--Changed point 122, 131 from height 8243 to 8179.
--Changed point 123, 128 from height 8199 to 8135.
--Changed point 123, 129 from height 8211 to 8147.
--Changed point 123, 130 from height 8223 to 8159.
--Changed point 123, 131 from height 8234 to 8170.
--Changed point 124, 128 from height 8191 to 8127.
--Changed point 124, 129 from height 8203 to 8139.
--Changed point 124, 130 from height 8215 to 8151.
--Changed point 124, 131 from height 8226 to 8162.
--Changed point 125, 128 from height 8183 to 8119.
--Changed point 125, 129 from height 8195 to 8131.
--Changed point 125, 130 from height 8206 to 8142.
--Changed point 125, 131 from height 8218 to 8154.
--Changed point 126, 128 from height 8175 to 8111.
--Changed point 126, 129 from height 8187 to 8123.
--Changed point 126, 130 from height 8198 to 8134.
--Changed point 126, 131 from height 8209 to 8145.
--Changed point 127, 128 from height 8167 to 8103.
--Changed point 127, 129 from height 8178 to 8114.
--Changed point 127, 130 from height 8189 to 8125.
--Changed point 127, 131 from height 8200 to 8136.
--Changed point 128, 128 from height 8159 to 8095.
--Changed point 128, 129 from height 8169 to 8105.
--Changed point 128, 130 from height 8180 to 8116.
--Changed point 128, 131 from height 8191 to 8127.
--Changed point 129, 128 from height 8152 to 8088.
--Changed point 129, 129 from height 8159 to 8095.
--Changed point 129, 130 from height 8169 to 8105.
--Changed point 129, 131 from height 8180 to 8116.
keyboard0 input device unacquired.
keyboard0 input device acquired.
keyboard0 input device unacquired.
==>forceDeform(10,10,.5);
Syntax error in input.
keyboard0 input device acquired.
keyboard0 input device unacquired.
==>forceDeform(10,10,0.5);
keyboard0 input device acquired.
TerrainDeformer::processTick--we have a deform object! position: (168.139893 -69.039940 247.413666) (xSize: 10, ySize: 10, depth 0)
--Changed point 148, 119 from height 7918 to 7918.
--Changed point 148, 120 from height 7935 to 7935.
--Changed point 149, 119 from height 7909 to 7909.
--Changed point 149, 120 from height 7926 to 7926.
--Changed point 150, 119 from height 7900 to 7900.
--Changed point 150, 120 from height 7917 to 7917.

NOTE: Even in the last example, where the depth got reduced to zero, we still got the huge spike, which implies to me that you simply cannot send the same value back to setHeight that you got from getHeight. It appears they use different scales, but I haven't been able to nail down how to properly scale the value sent to setHeight consistently.

Also note that even though we report in a depth of 2 being sent from the client in the first report, the height values are changed by much more than 2. Haven't figured that one out yet either!
#16
12/29/2004 (5:51 am)
Quick update: I've finally made some progress on this--most of the issue was related to two things:

--read/write float vs read/write SignedFloat, and sane values for the bitSize (I was using 32, under the assumption that I could optimize net traffic later. Bad assumption, 32 as a bitSize causes an overflow and I didn't realize it was happening).

--partially due to trying to figure out what was happening above, I had several iterations of "unsane" types in the chain between pulling out the height value (which requires Point2F points), and setting the height value (which requires Point2I points). Thrashing back and forth between these two issues kept me from getting a working implementation of any sort.

The current issue now is trying to gain better detail control over the amount of deformation. The current iteration doesn't seem to allow any negative (sinking) deformations at all, regardless of the sign of the depth value, and it also always deforms to a single altitude change. I think this has to do with the way I'm calling update grid, and/or all the interior checks that getHeight performs on the area immediately around the actual point.
#17
12/29/2004 (1:27 pm)
Been following this, very interesting... wish I had the time to research how terrain is handled and assist you, might do that, though not sure if its enough priority for my project, though would love to just go through and figure out how terrain works and document it all, for this, terrain modification, paging etc
#18
12/29/2004 (1:28 pm)
Anyone out there interested in this topic, and very familiar with how terrain height adjustments actually work? I've been playing around with this for a few days, and still getting very bad granularity in the amount of change to the terrain altitude performed, and am looking for someone to give the code a test run and see if they can find anything.

Specifically, regardless of the altitude delta, I always get a visual change of at least 1 unit of altitude. Compared to how the raise terrain/lower terrain tools operate in the terrain editors, I can't see how they get such fine granularity in how terrain is raised or lowered. As far as I've been able to trace my code is doing everything in the same manner as the editor code, but obviously I'm missing something!

I can email the files as they exist, so that I don't spam the boards here with lots of code/code blocks!

Other than the raise/lower granularity, things are working like a champ--I've got several dataBlock modifiable procedural deformation parameters, and the code is highly extensible now--just missing the granularity adjustment to make it ready for resource release!

Edit: Or, to restate this from the opposite direction--does anyone know specifically how/why the raise/lower terrain tools in the terrain editor are capable of such fine detail/granularity?
#19
12/29/2004 (2:49 pm)
I don't have any specific input at this time but I'll take a look at the code if you want to send it my way. Maybe I'll see something you're missing.
#20
12/29/2004 (4:24 pm)
@Stephen
Have you been looking at the terrain editor settings , parameter setHeight .
This one add dirt or excavate depending on the present height.
So if you have a setHeight at 50.2 or 49.8 and the presents terrain is 50 you adjust it +- 0.2 when using the setHeight editing function .
Maybe you know how this is working , but i type anyway :)
Page «Previous 1 2 3 Last »