Placing Things in a (Kinda) Voxel World

Started by
6 comments, last by EarthBanana 8 years, 9 months ago

Hey,

I'm developing a game that is a sandbox game and I want everything to be snapped into a grid, but other than that I'm using full 3D model, not cubes or anything. All the models are different, but I've built them in mind that I'm going to be using a grid snapping system to keep building nice and neat. My dilemma is placing the objects. Because I'm not really building a full voxel world like Minecraft or similar games, I haven't figured out a solution to allowing the player to accurately place things. I tried constructing a plane at the player's feet, projecting a ray from the camera and seeing where they intersect, then placing the object there. This was really bad and made some really odd results, but it did kinda work. I was wondering if anyone else has any ideas as to how I could accomplish this. As a note, there is going to be mostly blank space in the world, so I need to allow the player to place things on nothing, which is why I can't just cast a ray and test for the nearest intersection with another object. Its also a first person game that is going to have verticality, so I need to handle that as well.

Thanks.

Advertisement

What about using colors in an offscreen buffer to index specific locations that objects can be placed at? This is a common way to handle it.

http://content.gpwiki.org/index.php/OpenGL_Selection_Using_Unique_Color_IDs

http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/

This gives you 24 bits of ID - 16777216 IDs, though we'd reserve one ID (the largest - that is, the color (255, 255, 255)) to mean "no ID"/"invalid ID"/"nothing is located under your mouse".

For example, the player is standing at a specific location, looking in a specific direction. He's holding a specific object (FooObject), which has certain behavior. FooObjects can only be placed ontop of grass terrain, but can also be placed in air, as long as it's immediately connected to a nearby cliff. From where the player is standing, there are ~10 blocks near him. Only 4 of them are grass blocks, but two of them already have objects on them. There are also three nearby empty spaces connected to the cliff, so 5 potential locations where the FooObject can be attached to.

In memory, create 5 potential "AttachmentPoints". Give each one an ID, starting at zero and counting up.

Create an off-screen buffer of some resolution or other (half the screen resolution is probably fine, but if it seems too imprecise, make it the same as the screen's resolution).

Clear the off-screen buffer to all white (255, 255, 255). For each one of your attachment points, draw those objects using the index {0, 1, 2, 3, ...} reinterpreted as a color {(0,0,1), (0,0,2), (0,0,3), ...}.

Then, using the mouse's location, get the (offscreen/invisible) pixel under the mouse, and you now have the ID of the object the player is clicking on.

This even lets you give different IDs to different sides of the same object - because attaching a block to the top of a cube is different than attaching a block to its side, and you need to know which side the player clicked on.

Because certain objects can "block" the player's view of a attachment point (think of a tree trunk halfway obscuring a placement block - you want the player to be able to place on the visible part of the block, but not be able to place "through" the tree trunk), you need some way to obscure those parts of the off-screen buffer from being colored.

You can do this the simple but slow way of redrawing the entire world to the off-screen buffer, but redrawing it as solid white to "erase" the part of the attachment points that are visually blocked, but instead of drawing every object a second time, you can just reuse the depth buffer of your regular screen rendering - either by copying the depthbuffer into your offscreen buffer, or by making your offscreen buffer be drawn into at the same time as your regular buffer is being drawn into, since shaders can output to more than one buffer at the same time.

Something like this:


//FRAGMENT SHADER
#version 330

//Textures:
uniform sampler2D DiffuseMap;

//Input:
in vec2 fDiffuseCoord;
in vec4 fColoration;
in uvec4 fObjectID;

//Output to the frame buffers:
out vec4 VisibleScreen;
out uvec4 OffscreenIDBuffer;

void main()
{
    vec4 diffuseFrag = texture2D(DiffuseMap, fDiffuseCoord);

    VisibleScreen = (diffuseFrag * fColoration);
    OffscreenIDBuffer = fObjectID;
}

Also, if you are targeting OpenGL 3.0 or higher, you could disable alpha blending for only the offscreen-buffer ( glDisablei(GL_BLEND, offscreenBuffer) ), giving you all four bytes for use with IDs - which is nice if you want to more permanently give IDs to objects - But because there shouldn't be more than a few dozen objects within click-range of the player, so you can also just assign IDs dynamically and re-use them as needed.

Just an idea that popped into my head, so I don't know if it'll work, but maybe?
If I got your description right, you got a world of voxel-ish cells where you snap your objects.
My idea was to use the scroll wheel during object placement to control a cylinder around the player. The scrolling would control the radius of the cylider. Then you should be able to use the cameras forward vector to pick the 'voxel' on the wall of the cylinder using a bit of simple trig, no?

Servant's aproach seemed more sophisticated though, and probably more tested and true. Just thought I'd mention the idea. :)

Programmer of HolyPoly games.

What about using colors in an offscreen buffer to index specific locations that objects can be placed at? This is a common way to handle it.

http://content.gpwiki.org/index.php/OpenGL_Selection_Using_Unique_Color_IDs

http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-an-opengl-hack/

This gives you 24 bits of ID - 16777216 IDs, though we'd reserve one ID (the largest - that is, the color (255, 255, 255)) to mean "no ID"/"invalid ID"/"nothing is located under your mouse".

For example, the player is standing at a specific location, looking in a specific direction. He's holding a specific object (FooObject), which has certain behavior. FooObjects can only be placed ontop of grass terrain, but can also be placed in air, as long as it's immediately connected to a nearby cliff. From where the player is standing, there are ~10 blocks near him. Only 4 of them are grass blocks, but two of them already have objects on them. There are also three nearby empty spaces connected to the cliff, so 5 potential locations where the FooObject can be attached to.

In memory, create 5 potential "AttachmentPoints". Give each one an ID, starting at zero and counting up.

Create an off-screen buffer of some resolution or other (half the screen resolution is probably fine, but if it seems too imprecise, make it the same as the screen's resolution).

Clear the off-screen buffer to all white (255, 255, 255). For each one of your attachment points, draw those objects using the index {0, 1, 2, 3, ...} reinterpreted as a color {(0,0,1), (0,0,2), (0,0,3), ...}.

Then, using the mouse's location, get the (offscreen/invisible) pixel under the mouse, and you now have the ID of the object the player is clicking on.

This even lets you give different IDs to different sides of the same object - because attaching a block to the top of a cube is different than attaching a block to its side, and you need to know which side the player clicked on.

Because certain objects can "block" the player's view of a attachment point (think of a tree trunk halfway obscuring a placement block - you want the player to be able to place on the visible part of the block, but not be able to place "through" the tree trunk), you need some way to obscure those parts of the off-screen buffer from being colored.

You can do this the simple but slow way of redrawing the entire world to the off-screen buffer, but redrawing it as solid white to "erase" the part of the attachment points that are visually blocked, but instead of drawing every object a second time, you can just reuse the depth buffer of your regular screen rendering - either by copying the depthbuffer into your offscreen buffer, or by making your offscreen buffer be drawn into at the same time as your regular buffer is being drawn into, since shaders can output to more than one buffer at the same time.

Something like this:


//FRAGMENT SHADER
#version 330

//Textures:
uniform sampler2D DiffuseMap;

//Input:
in vec2 fDiffuseCoord;
in vec4 fColoration;
in uvec4 fObjectID;

//Output to the frame buffers:
out vec4 VisibleScreen;
out uvec4 OffscreenIDBuffer;

void main()
{
    vec4 diffuseFrag = texture2D(DiffuseMap, fDiffuseCoord);

    VisibleScreen = (diffuseFrag * fColoration);
    OffscreenIDBuffer = fObjectID;
}

Also, if you are targeting OpenGL 3.0 or higher, you could disable alpha blending for only the offscreen-buffer ( glDisablei(GL_BLEND, offscreenBuffer) ), giving you all four bytes for use with IDs - which is nice if you want to more permanently give IDs to objects - But because there shouldn't be more than a few dozen objects within click-range of the player, so you can also just assign IDs dynamically and re-use them as needed.

I am using color picking temporarily in my engine for the editor portion, but I'm going to switch to using rays in the future. Color picking is kinda slow, but it does work well. Since I'm using voxels though, its kinda not needed because I can just construct a simple tree and retrace it against that, which would probably be faster in the long run. Because color picking needs objects, I think it would have a hard time figuring out where to place something that was in the air because there won't be any objects there.

Just an idea that popped into my head, so I don't know if it'll work, but maybe?
If I got your description right, you got a world of voxel-ish cells where you snap your objects.
My idea was to use the scroll wheel during object placement to control a cylinder around the player. The scrolling would control the radius of the cylider. Then you should be able to use the cameras forward vector to pick the 'voxel' on the wall of the cylinder using a bit of simple trig, no?

Servant's aproach seemed more sophisticated though, and probably more tested and true. Just thought I'd mention the idea. smile.png

I think thats what I'm going with. I decided to just use the projected ray from the player's camera with a certain length and then change that length with the scroll wheel. Its probably the best and simplest option.

I think thats what I'm going with. I decided to just use the projected ray from the player's camera with a certain length and then change that length with the scroll wheel. Its probably the best and simplest option.


Yep. You can't get around the fact that when you click on a point, there are a whole range of possible cells running in a line that the player might be clicking on. I've used a fixed length combined with scrolling in and out approach myself and its okay. Your software can't read minds and there is insufficient information from a single click to tell you where to place something.

The way I handle this in my editor is to

1) Create a copy of the object that is to be inserted and make it 60 to 70 percent transparent

2) Insert this object a set distance away from the camera (using the look/target vector)

3) Set all mouse movement to project the objects move in a single plane - The plane is determined by the camera's horizontal angle.. IE if the camera's target vector is greater than 45 degrees away from the xy plane vector, then the movement is in the xy plane.

4) After drawing the transparent version of the object, draw the object twice more using a stenciling mechanism to only draw the outline of the object - make this outline some solid color. On the second draw turn off depth checking so that the outline is always drawn.

5) Change the overall object color to transparent red if the user cannot insert the object at the current location.

In essence, you use whatever object you are trying to insert as an "object brush" that moves in a single plane following the users mouse movements. You can then set up a key (or a couple keys) to allow the user to change the plane of movement.

The outline of the "brush" will always be shown in the editor even when behind other stuff so the user is not confused, and you can change the color to red (or whatever color) to indicate that the object is not insert-able at its current location. When the user clicks in a valid location, you create an object in that spot and snap the object's position to whatever grid you want to use.

The way I handle this in my editor is to

1) Create a copy of the object that is to be inserted and make it 60 to 70 percent transparent

2) Insert this object a set distance away from the camera (using the look/target vector)

3) Set all mouse movement to project the objects move in a single plane - The plane is determined by the camera's horizontal angle.. IE if the camera's target vector is greater than 45 degrees away from the xy plane vector, then the movement is in the xy plane.

4) After drawing the transparent version of the object, draw the object twice more using a stenciling mechanism to only draw the outline of the object - make this outline some solid color. On the second draw turn off depth checking so that the outline is always drawn.

5) Change the overall object color to transparent red if the user cannot insert the object at the current location.

In essence, you use whatever object you are trying to insert as an "object brush" that moves in a single plane following the users mouse movements. You can then set up a key (or a couple keys) to allow the user to change the plane of movement.

The outline of the "brush" will always be shown in the editor even when behind other stuff so the user is not confused, and you can change the color to red (or whatever color) to indicate that the object is not insert-able at its current location. When the user clicks in a valid location, you create an object in that spot and snap the object's position to whatever grid you want to use.

What kid of editor did you make? When I was writing the editor for my engine, I just used the standard 3 arrows in the XYZ and did the math for that. IMO thats the best solution, but I feel like that wouldn't be immersive enough to give to a player.


What kid of editor did you make? When I was writing the editor for my engine, I just used the standard 3 arrows in the XYZ and did the math for that. IMO thats the best solution, but I feel like that wouldn't be immersive enough to give to a player.

Yeah for a engine editor - I would agree the object handle widgets are probably the best. But.. for object insertion this method works. In some editors, the object will just be inserted at 0,0,0 when you add it to the map. In others, an intersection is determined between the target vector and the scene and the object is inserted somehow according to this (as Servant of the Lord mentioned).

I chose this method for insertion because my map editor aims to have a feel that is less 3d editorish and more like a painting program or something similar. Once the objects are inserted in to the map, the normal rotation, movement, and scaling widgets are available but just for the purpose of inserting in to the map I use this brush approach.

I think it works well though - If I remember I think in the unreal toolkit you drag and drop items in to the map and placement is done somewhat like Servant's post, or this may have actually been TES construction set. I remember when I was first trying to figure out how I wanted stuff to be inserted I downloaded a whole plethora of different editors and tried them out to get a feel for what I wanted to do.

I ended up at the brush idea mostly because I wanted the user to be able to "paint" hexagon tiles using different brush shapes. And I wanted it to be easy for them to see where they could insert or not insert stuff in to the map.

This topic is closed to new replies.

Advertisement