At some time we all end up writing some kind of level editor, and one of the
issues is moving objects around the scene.
3D editors get around this by having multiple views so you can move the
object in the cardinal directions, this isn't always ideal.
My solution relies on the objects being on a defined plane, but it can be
extended to handle uneven terrain.
The idea is simple, and the code not much more complex.
Throw a ray from the mouse position through the scene. You are probably
already doing this for your picking. Find the intersection of this ray and the
plane.
Place your object there.
Simples.
In fact I do it a little differently, I add the difference between the object
intersection and the new, but that is a triviality.
The advantage of this is that it is view independant, so if you move the
camera, the code still works.
issues is moving objects around the scene.
3D editors get around this by having multiple views so you can move the
object in the cardinal directions, this isn't always ideal.
My solution relies on the objects being on a defined plane, but it can be
extended to handle uneven terrain.
The idea is simple, and the code not much more complex.
Throw a ray from the mouse position through the scene. You are probably
already doing this for your picking. Find the intersection of this ray and the
plane.
Place your object there.
Simples.
In fact I do it a little differently, I add the difference between the object
intersection and the new, but that is a triviality.
The advantage of this is that it is view independant, so if you move the
camera, the code still works.
//Intersection plane
Plane p = new Plane(Vector3.UnitY, 0);
// new mouse ray
Ray nmray = CalculateCursorRay(mpos,
editCamera.Projection, editCamera.View);
Ray omray = CalculateCursorRay(ompos,
editCamera.Projection, editCamera.View);
// find the old and new intersection points
float? op = omray.Intersects(p);
float? np = nmray.Intersects(p);
if ((op != null) && (np != null))
{
Vector3 opos = omray.Position + (float)op * omray.Direction;
Vector3 npos = nmray.Position + (float)np * nmray.Direction;
npos -= opos;
SelectedObject.Position += npos;
}
ompos = mpos;
Plane p = new Plane(Vector3.UnitY, 0);
// new mouse ray
Ray nmray = CalculateCursorRay(mpos,
editCamera.Projection, editCamera.View);
Ray omray = CalculateCursorRay(ompos,
editCamera.Projection, editCamera.View);
// find the old and new intersection points
float? op = omray.Intersects(p);
float? np = nmray.Intersects(p);
if ((op != null) && (np != null))
{
Vector3 opos = omray.Position + (float)op * omray.Direction;
Vector3 npos = nmray.Position + (float)np * nmray.Direction;
npos -= opos;
SelectedObject.Position += npos;
}
ompos = mpos;
The cursor ray calculation is standard, but it is here for reference
private Ray CalculateCursorRay(Vector2 mpos,
Matrix projectionMatrix, Matrix viewMatrix)
{
// create 2 positions in screenspace using the cursor // position. 0 is as close as possible to the camera,
// 1 is as far away as possible.
Vector3 nearSource = new Vector3(mpos, 0f);
Vector3 farSource = new Vector3(mpos, 1f);
// use Viewport.Unproject to tell what those two screen
// space positions would be in world space. we'll need
// the projection matrix and view matrix, which we have
// We also need a world matrix, which can just be identity.
Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, ProjectionMatrix, viewMatrix, Matrix.Identity);
Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource, ProjectionMatrix, viewMatrix, Matrix.Identity);
// find the direction vector that goes from the nearPoint // to the farPoint and normalize it....
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
// and then create a new ray using nearPoint as the
// source.
return new Ray(nearPoint, direction);
}
Matrix projectionMatrix, Matrix viewMatrix)
{
// create 2 positions in screenspace using the cursor // position. 0 is as close as possible to the camera,
// 1 is as far away as possible.
Vector3 nearSource = new Vector3(mpos, 0f);
Vector3 farSource = new Vector3(mpos, 1f);
// use Viewport.Unproject to tell what those two screen
// space positions would be in world space. we'll need
// the projection matrix and view matrix, which we have
// We also need a world matrix, which can just be identity.
Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, ProjectionMatrix, viewMatrix, Matrix.Identity);
Vector3 farPoint = GraphicsDevice.Viewport.Unproject(farSource, ProjectionMatrix, viewMatrix, Matrix.Identity);
// find the direction vector that goes from the nearPoint // to the farPoint and normalize it....
Vector3 direction = farPoint - nearPoint;
direction.Normalize();
// and then create a new ray using nearPoint as the
// source.
return new Ray(nearPoint, direction);
}
This is a very simple piece of code, but the number of times I see people
struggling with it, I thought it was a good idea to blog it for other people to
reference.
struggling with it, I thought it was a good idea to blog it for other people to
reference.