Screen to world coordinates

Started by
2 comments, last by Koder4Fun 11 years, 8 months ago
Hi!
I'm rendering 3D on top of 2D, and it seems to be working, but I need to be able to render a specific mesh at, say - 10, 10 in screen coordinates.
How?

My current approach involves using Viewport.Unproject(), but it doesn't seem to be working;

[source lang="csharp"] Vector3 NearScreenPoint = new Vector3(0, 0, 0);
Vector3 FarScreenPoint = new Vector3(0, 0, 1);
Vector3 NearWorldPoint = m_Scene.SceneMgr.Device.Viewport.Unproject(NearScreenPoint,
m_Scene.SceneMgr.ProjectionMatrix, m_Scene.SceneMgr.ViewMatrix, m_Scene.SceneMgr.WorldMatrix);
Vector3 FarWorldPoint = m_Scene.SceneMgr.Device.Viewport.Unproject(FarScreenPoint,
m_Scene.SceneMgr.ProjectionMatrix, m_Scene.SceneMgr.ViewMatrix, m_Scene.SceneMgr.WorldMatrix);
Vector3 Direction = FarWorldPoint - NearWorldPoint;
Direction.Normalize();

if (m_CurrentHeadMeshes.Count > 0)
{
for (int i = 0; i < m_CurrentHeadMeshes[0].VertexTexNormalPositions.Length; i++)
{
/*Vector4 Position = new Vector4(m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position, 1);
Matrix WorldMat = Matrix.CreateWorld(new Vector3(0, 0, 0), Vector3.Forward, new Vector3(0, 1, 0));
Vector4.Transform(ref Position, ref WorldMat, out Position);

Position /= Position.W;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.X = Position.X;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.Y = Position.Y;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.Z = Position.Z;*/

m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position *= Direction;
}
}[/source]

I also want to rotate the mesh around it's X-axis, but I commented out the code for doing that since it complicated things. Anyway, here is my entire code:

[source lang="csharp"]
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SimsLib.ThreeD;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using LogThis;

namespace TSOClient.ThreeD
{
/// <summary>
/// Represents a surface for rendering 3D elements (sims) on top of UI elements.
/// </summary>
public class UI3DView : ThreeDElement
{
private List<BasicEffect> m_Effects;

private float m_Rotation;

private List<Mesh> m_CurrentHeadMeshes;
private List<Texture2D> m_HeadTextures;

private int m_Width, m_Height;
private bool m_SingleRenderer = true;

private SpriteBatch m_SBatch;

private Camera2D Cam = new Camera2D();

/// <summary>
/// Constructs a new UI3DView instance.
/// </summary>
/// <param name="Width">The width of this UI3DView surface.</param>
/// <param name="Height">The height of this UI3DView surface.</param>
/// <param name="SingleRenderer">Will this surface be used to render a single, or multiple sims?</param>
/// <param name="Screen">The ThreeDScene instance with which to create this UI3DView instance.</param>
/// <param name="StrID">The string ID for this UI3DView instance.</param>
public UI3DView(int Width, int Height, bool SingleRenderer, ThreeDScene Screen, string StrID)
: base(Screen)
{
m_Effects = new List<BasicEffect>();
m_Width = Width;
m_Height = Height;
m_SingleRenderer = SingleRenderer;

m_CurrentHeadMeshes = new List<Mesh>();
m_HeadTextures = new List<Texture2D>();

m_SBatch = new SpriteBatch(m_Scene.SceneMgr.Device);
m_Scene.SceneMgr.Device.DeviceReset += new EventHandler(GraphicsDevice_DeviceReset);
}

/// <summary>
/// Occurs when the graphicsdevice was reset, meaning all 3D resources
/// have to be recreated.
/// </summary>
private void GraphicsDevice_DeviceReset(object sender, EventArgs e)
{
for (int i = 0; i < m_Effects.Count; i++)
m_Effects = new BasicEffect(m_Scene.SceneMgr.Device, null);

m_Scene.SceneMgr.Device.VertexDeclaration = new VertexDeclaration(m_Scene.SceneMgr.Device,
VertexPositionNormalTexture.VertexElements);
m_Scene.SceneMgr.Device.RenderState.CullMode = CullMode.None;

// Create camera and projection matrix
m_Scene.SceneMgr.WorldMatrix = Matrix.Identity;
m_Scene.SceneMgr.ViewMatrix = Matrix.CreateLookAt(Vector3.Right * 5, Vector3.Zero, Vector3.Down);
m_Scene.SceneMgr.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.Pi / 4.0f,
(float)m_Scene.SceneMgr.Device.PresentationParameters.BackBufferWidth /
(float)m_Scene.SceneMgr.Device.PresentationParameters.BackBufferHeight, 1.0f, 100.0f);
}

/// <summary>
/// Loads a head mesh.
/// </summary>
/// <param name="MeshID">The ID of the mesh to load.</param>
/// <param name="TexID">The ID of the texture to load.</param>
public void LoadHeadMesh(Outfit Outf, int SkinColor)
{
Appearance Apr;

switch (SkinColor)
{
case 0:
Apr = new Appearance(ContentManager.GetResourceFromLongID(Outf.LightAppearanceID));
break;
case 1:
Apr = new Appearance(ContentManager.GetResourceFromLongID(Outf.MediumAppearanceID));
break;
case 2:
Apr = new Appearance(ContentManager.GetResourceFromLongID(Outf.DarkAppearanceID));
break;
default:
Apr = new Appearance(ContentManager.GetResourceFromLongID(Outf.LightAppearanceID));
break;
}

Binding Bnd = new Binding(ContentManager.GetResourceFromLongID(Apr.BindingIDs[0]));

if (m_CurrentHeadMeshes.Count > 0)
{
if (!m_SingleRenderer)
{
m_Effects.Add(new BasicEffect(m_Scene.SceneMgr.Device, null));
m_CurrentHeadMeshes.Add(new Mesh(ContentManager.GetResourceFromLongID(Bnd.MeshAssetID), false));
m_CurrentHeadMeshes[m_CurrentHeadMeshes.Count - 1].ProcessMesh();

m_HeadTextures.Add(Texture2D.FromFile(m_Scene.SceneMgr.Device,
new MemoryStream(ContentManager.GetResourceFromLongID(Bnd.TextureAssetID))));
}
else
{
m_Effects[0] = new BasicEffect(m_Scene.SceneMgr.Device, null);
m_CurrentHeadMeshes[0] = new Mesh(ContentManager.GetResourceFromLongID(Bnd.MeshAssetID), false);
m_CurrentHeadMeshes[m_CurrentHeadMeshes.Count - 1].ProcessMesh();

m_HeadTextures[0] = Texture2D.FromFile(m_Scene.SceneMgr.Device,
new MemoryStream(ContentManager.GetResourceFromLongID(Bnd.TextureAssetID)));

Vector3 NearScreenPoint = new Vector3(0, 0, 0);
Vector3 FarScreenPoint = new Vector3(0, 0, 1);
Vector3 NearWorldPoint = m_Scene.SceneMgr.Device.Viewport.Unproject(NearScreenPoint,
m_Scene.SceneMgr.ProjectionMatrix, m_Scene.SceneMgr.ViewMatrix, m_Scene.SceneMgr.WorldMatrix);
Vector3 FarWorldPoint = m_Scene.SceneMgr.Device.Viewport.Unproject(FarScreenPoint,
m_Scene.SceneMgr.ProjectionMatrix, m_Scene.SceneMgr.ViewMatrix, m_Scene.SceneMgr.WorldMatrix);
Vector3 Direction = FarWorldPoint - NearWorldPoint;
Direction.Normalize();

if (m_CurrentHeadMeshes.Count > 0)
{
for (int i = 0; i < m_CurrentHeadMeshes[0].VertexTexNormalPositions.Length; i++)
{
/*Vector4 Position = new Vector4(m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position, 1);
Matrix WorldMat = Matrix.CreateWorld(new Vector3(0, 0, 0), Vector3.Forward, new Vector3(0, 1, 0));
Vector4.Transform(ref Position, ref WorldMat, out Position);

Position /= Position.W;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.X = Position.X;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.Y = Position.Y;
m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position.Z = Position.Z;*/

m_CurrentHeadMeshes[0].VertexTexNormalPositions.Position *= Direction;
}
}
}
}
else
{
m_Effects.Add(new BasicEffect(m_Scene.SceneMgr.Device, null));
m_CurrentHeadMeshes.Add(new Mesh(ContentManager.GetResourceFromLongID(Bnd.MeshAssetID), false));
m_CurrentHeadMeshes[m_CurrentHeadMeshes.Count - 1].ProcessMesh();

m_HeadTextures.Add(Texture2D.FromFile(m_Scene.SceneMgr.Device,
new MemoryStream(ContentManager.GetResourceFromLongID(Bnd.TextureAssetID))));
}
}

public override void Update(GameTime GTime)
{
/*m_Rotation += 0.01f;
m_Scene.SceneMgr.WorldMatrix = Matrix.CreateRotationX(m_Rotation);*/

base.Update(GTime);
}

public override void Draw()
{
base.Draw();

for (int i = 0; i < m_Effects.Count; i++)
{
for (int j = 0; j < m_HeadTextures.Count; j++)
{
if (m_HeadTextures[j] != null)
{
m_Effects.World = m_Scene.SceneMgr.WorldMatrix;
m_Effects.View = m_Scene.SceneMgr.ViewMatrix;
m_Effects.Projection = m_Scene.SceneMgr.ProjectionMatrix;

m_Effects.Texture = m_HeadTextures[j];
m_Effects.TextureEnabled = true;

m_Effects.EnableDefaultLighting();

m_Effects.CommitChanges();

// Draw
m_Effects.Begin();

for (int k = 0; k < m_Effects.Techniques.Count; k++)
{
foreach (EffectPass Pass in m_Effects.Techniques[k].Passes)
{
Pass.Begin();

foreach (Mesh Msh in m_CurrentHeadMeshes)
{
foreach (Face Fce in Msh.Faces)
{
if (Msh.VertexTexNormalPositions != null)
{
VertexPositionNormalTexture[] Vertex = new VertexPositionNormalTexture[3];
Vertex[0] = Msh.VertexTexNormalPositions[Fce.AVertexIndex];
Vertex[1] = Msh.VertexTexNormalPositions[Fce.BVertexIndex];
Vertex[2] = Msh.VertexTexNormalPositions[Fce.CVertexIndex];

Vertex[0].TextureCoordinate = Msh.VertexTexNormalPositions[Fce.AVertexIndex].TextureCoordinate;
Vertex[1].TextureCoordinate = Msh.VertexTexNormalPositions[Fce.BVertexIndex].TextureCoordinate;
Vertex[2].TextureCoordinate = Msh.VertexTexNormalPositions[Fce.CVertexIndex].TextureCoordinate;

m_Scene.SceneMgr.Device.DrawUserPrimitives<VertexPositionNormalTexture>(
PrimitiveType.TriangleList, Vertex, 0, 1);
}
}
}

Pass.End();
m_Effects.End();
}
}
}
}
}
}
}[/source]
Also, here's my View, World and Projection:

[source lang="csharp"] m_WorldMatrix = Matrix.Identity;
m_ViewMatrix = Matrix.CreateLookAt(Vector3.Backward * 5, Vector3.Zero, Vector3.Right);
m_ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.Pi / 4.0f,
(float)Device.PresentationParameters.BackBufferWidth /
(float)Device.PresentationParameters.BackBufferHeight,
1.0f, 100.0f);[/source]

I've been Googling around for answers to this issue for some time now, but everything seems to revolve around picking, which isn't helping me.
Advertisement
If I understood correctly you want to draw 3d elements on the graphics user interface of your game.
You are right, you can use Unproject() but the parameters you feed are incorrect.

Near and far points must contain:

Vector3 NearScreenPoint = new Vector3(cx, cy, 0);
Vector3 FarScreenPoint = new Vector3(cx, cy, 1);

where cx and cy are the position in pixels referred to the current viewport (so take care of what is the current active viewport).

Now you can calculate Direction using:

Direction = Vector3.Lerp(NearScreenPoint, FarScreenPoint, dist);

where dist is a number in range [0..1] to choice the distance of your 3D object from the camera. If you use 0 or 1 you can't see the object, it's clipped away.

Now, to render the object I think is better to use a translation matrix or, more simpler, to send the Direction vector to a shader that do the per-vertex transform on the GPU adding Direction to the mesh vertex position. If you do this on CPU you waste time.

If i'm not clear please post.
Please vote usefull replies.
Marco Sacchi
Coding is a challenge ... but solving problems is the fun part
My Blog - XNA Italian portal
Thanks for your answer!
Yes, I found out it was easier to just apply a translation matrix to the world matrix, as such:

[source lang="csharp"] m_Effects.World = m_Scene.SceneMgr.WorldMatrix *
Matrix.CreateTranslation(new Vector3(m_CurrentSims[j].HeadXPos, m_CurrentSims[j].HeadYPos, 0.0f));[/source]
As is evident from that snippet, I changed the class to function more as a renderingcontext, which was more in line with what I had originally intended. I also realized pretty quickly that I needed to scale, and I had to do that using the viewmatrix. Somehow, that affected the coordinatesystem, so I had to update my coordinates for where I wanted to render the mesh as I moved the viewmatrix further away. I'm still not 100% sure how to handle scaling because of this very issue. For now, I've just hardcoded it:
[source lang="csharp"]m_Effects.View = Matrix.CreateLookAt(Vector3.Backward * 17, Vector3.Zero, Vector3.Right);[/source]
You can chain all needed matrix transforms, you only need to be care with the order of multiplication:


ComboMatrix = TranslationMatrix * ScaleMatrix * RotationMatrix;


To keep in mind the correct order read the transformations in a right to left direction, and you can see how the vertices of a mesh are transformed from the stored local-space coordinates to world-space coordinates.

This way you can calculate the matrix one time and apply it to all objects you want.
Please vote usefull replies.
Marco Sacchi
Coding is a challenge ... but solving problems is the fun part
My Blog - XNA Italian portal

This topic is closed to new replies.

Advertisement