Table of Contents

Project and Unproject

This C# Intermediate tutorial covers projecting and unprojecting coordinates from 3D to 2D and vice versa.

Explanation

When we want to 'convert' 3D coordinates to a 2D screen, we speak 'Projecting'. The other way around is called 'Unprojecting'. Both scenarios are fairly common in 3D games.

The 3D to 2D or projecting happens for instance when you have a 3d quest marker. When the target you need to travel to is somewhere in front of you in the world, then you want to draw a 2D quest marker on screen that gives you an indication of where in the 3D world that target is located.

From 2D to 3D is often used to convert a mouse coordinate into the looking direction of the camera. This can be used for firing a weapon or setting a target on a map when playing a strategy game.

Project

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Graphics;

namespace CSharpIntermediate.Code
{
    public class ProjectDemo : SyncScript
    {
        public Entity projectSphere;
        public Entity projectSphereChild;
        private CameraComponent camera;

        public override void Start()
        {
            camera = Entity.Get<CameraComponent>();
        }

        public override void Update()
        {
            var backBuffer = GraphicsDevice.Presenter.BackBuffer;
            var sphereProjection = Vector3.Project(projectSphere.Transform.WorldMatrix.TranslationVector, 0, 0, backBuffer.Width, backBuffer.Height,0, 8, camera.ViewProjectionMatrix);
            var sphereChildProjection = Vector3.Project(projectSphereChild.Transform.WorldMatrix.TranslationVector, 0, 0, backBuffer.Width, backBuffer.Height,0, 8, camera.ViewProjectionMatrix);

            // Similar method using Viewports
            //var viewport = new Viewport(0, 0, backBuffer.Width, backBuffer.Height);
            //var sphereProjection = viewport.Project(projectSphere.Transform.WorldMatrix.TranslationVector, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);
            //var sphereChildProjection = viewport.Project(projectSphereChild.Transform.WorldMatrix.TranslationVector, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);

            DebugText.Print($"Parent", new Int2(sphereProjection.XY()));
            DebugText.Print($"Child", new Int2(sphereChildProjection.XY()));
        }
    }
}

Unproject

// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Graphics;
using Stride.Input;
using Stride.Physics;

namespace CSharpIntermediate.Code
{
    public class UnprojectDemo : SyncScript
    {
        private CameraComponent camera;
        public Entity sphereToClone;

        public override void Start()
        {
            camera = Entity.Get<CameraComponent>();
        }

        public override void Update()
        {
            if (Input.IsMouseButtonPressed(MouseButton.Left))
            {
                var backBuffer = GraphicsDevice.Presenter.BackBuffer;
                var viewport = new Viewport(0, 0, backBuffer.Width, backBuffer.Height);

                var nearPosition = viewport.Unproject(new Vector3(Input.AbsoluteMousePosition, 0.0f), camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);
                var farPosition = viewport.Unproject(new Vector3(Input.AbsoluteMousePosition, 1.0f), camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);

                var hitResult = this.GetSimulation().Raycast(nearPosition, farPosition);

                // If there is a hitresult, clone the sphere and place it on that position
                if (hitResult.Succeeded)
                {
                    var sphereClone = sphereToClone.Clone();
                    sphereClone.Transform.Position = hitResult.Point;
                    Entity.Scene.Entities.Add(sphereClone);
                }
            }
        }
    }
}