How can I find a position on a 3d plane or terrain from a tile on a 2d tilemap position?

The main goal is when I click with the mouse on the tile map and generate a cube that it will generate another cube to the position that it should be on the 3d plane or terrain.

For example in the grid tilemap the top left cube will be position at 29,29 and now I want to convert this position to the top left position on a 3d plane or terrain and create a cube there too.

This is the mono script that draw gizmos and draw the tile map :

using UnityEngine;

public class TileTest : MonoBehaviour
{
    public int Rows;
    public int Columns;
    public float TileWidth = 1;
    public float TileHeight = 1;

    [HideInInspector]
    public Vector3 MarkerPosition;

    private void OnDrawGizmosSelected()
    {
        var mapWidth = this.Columns * this.TileWidth;
        var mapHeight = this.Rows * this.TileHeight;
        var position = this.transform.position;

        Gizmos.color = Color.white;
        Gizmos.DrawLine(position, position + new Vector3(mapWidth, 0, 0));
        Gizmos.DrawLine(position, position + new Vector3(0, mapHeight, 0));
        Gizmos.DrawLine(position + new Vector3(mapWidth, 0, 0), position + new Vector3(mapWidth, mapHeight, 0));
        Gizmos.DrawLine(position + new Vector3(0, mapHeight, 0), position + new Vector3(mapWidth, mapHeight, 0));

        Gizmos.color = Color.grey;
        for (float i = 1; i < this.Columns; i++)
        {
            Gizmos.DrawLine(position + new Vector3(i * this.TileWidth, 0, 0), position + new Vector3(i * this.TileWidth, mapHeight, 0));
        }

        for (float i = 1; i < this.Rows; i++)
        {
            Gizmos.DrawLine(position + new Vector3(0, i * this.TileHeight, 0), position + new Vector3(mapWidth, i * this.TileHeight, 0));
        }
        Gizmos.color = Color.red;
        Gizmos.DrawWireCube(this.MarkerPosition, new Vector3(this.TileWidth, this.TileHeight, 1) * 1.1f);
    }

    public void GenerateNewMap()
    {

    }

    public void DestroyMap()
    {

    }
}

And this is the editor script that use the mouse position to create cubes when clicking on a tile in the tilemap grid :

using System;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TileTest))]
public class TileMapEditor : Editor
{
    private Vector3 mouseHitPos;

    private void OnSceneGUI()
    {
        if (this.UpdateHitPosition())
        {
            SceneView.RepaintAll();
        }

        this.RecalculateMarkerPosition();

        Event current = Event.current;

        if (this.IsMouseOnLayer())
        {
            if (current.type == EventType.MouseDown || current.type == EventType.MouseDrag)
            {
                if (current.button == 1)
                {
                    this.Erase();
                    current.Use();
                }
                else if (current.button == 0)
                {
                    this.Draw();
                    current.Use();
                }
            }
        }

        Handles.BeginGUI();
        GUI.Label(new Rect(10, Screen.height - 90, 100, 100), "LMB: Draw");
        GUI.Label(new Rect(10, Screen.height - 105, 100, 100), "RMB: Erase");
        Handles.EndGUI();
    }

    private void OnEnable()
    {
        Tools.current = Tool.View;
        Tools.viewTool = ViewTool.FPS;
    }

    private void Draw()
    {
        var map = (TileTest)this.target;
        var tilePos = this.GetTilePositionFromMouseLocation();
        var cube = GameObject.Find(string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y));

        if (cube != null && cube.transform.parent != map.transform)
        {
            return;
        }

        if (cube == null)
        {
            cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        }

        var tilePositionInLocalSpace = new Vector3((tilePos.x * map.TileWidth) + (map.TileWidth / 2), (tilePos.y * map.TileHeight) + (map.TileHeight / 2));
        cube.transform.position = map.transform.position + tilePositionInLocalSpace;

        cube.transform.localScale = new Vector3(map.TileWidth, map.TileHeight, 1);

        cube.transform.parent = map.transform;

        cube.name = string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y);
    }

    private void Erase()
    {
        var map = (TileTest)this.target;
        var tilePos = this.GetTilePositionFromMouseLocation();
        var cube = GameObject.Find(string.Format("Tile_{0}_{1}", tilePos.x, tilePos.y));

        if (cube != null && cube.transform.parent == map.transform)
        {
            UnityEngine.Object.DestroyImmediate(cube);
        }
    }

    private Vector2 GetTilePositionFromMouseLocation()
    {
        var map = (TileTest)this.target;
        var pos = new Vector3(this.mouseHitPos.x / map.TileWidth, this.mouseHitPos.y / map.TileHeight, map.transform.position.z);

        pos = new Vector3((int)Math.Round(pos.x, 5, MidpointRounding.ToEven), (int)Math.Round(pos.y, 5, MidpointRounding.ToEven), 0);

        var col = (int)pos.x;
        var row = (int)pos.y;
        if (row < 0)
        {
            row = 0;
        }

        if (row > map.Rows - 1)
        {
            row = map.Rows - 1;
        }

        if (col < 0)
        {
            col = 0;
        }

        if (col > map.Columns - 1)
        {
            col = map.Columns - 1;
        }

        return new Vector2(col, row);
    }

    private bool IsMouseOnLayer()
    {
        var map = (TileTest)this.target;

        return this.mouseHitPos.x > 0 && this.mouseHitPos.x < (map.Columns * map.TileWidth) &&
               this.mouseHitPos.y > 0 && this.mouseHitPos.y < (map.Rows * map.TileHeight);
    }

    private void RecalculateMarkerPosition()
    {
        var map = (TileTest)this.target;
        var tilepos = this.GetTilePositionFromMouseLocation();
        var pos = new Vector3(tilepos.x * map.TileWidth, tilepos.y * map.TileHeight, 0);

        map.MarkerPosition = map.transform.position + new Vector3(pos.x + (map.TileWidth / 2), pos.y + (map.TileHeight / 2), 0);
    }

    private bool UpdateHitPosition()
    {
        var map = (TileTest)this.target;
        var p = new Plane(map.transform.TransformDirection(Vector3.forward), map.transform.position);
        var ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
        var hit = new Vector3();
        float dist;

        if (p.Raycast(ray, out dist))
        {
            hit = ray.origin + (ray.direction.normalized * dist);
        }

        var value = map.transform.InverseTransformPoint(hit);

        if (value != this.mouseHitPos)
        {
            this.mouseHitPos = value;
            return true;
        }

        return false;
    }
}

Example of a tile map :

The plane is on the right of the grid.

I want that when I click on the grid map it will create at the same time also a cube/s on the plane.
So if on the grid top left position is 29,29 then how can I calculate the relative position on the plane ?

For example I just used a cube for testing and positioned it on the plane top left position and the position is :

The cube position is : 4.46 , 0.5 , -4.43

There might be some problems like if the tilemap grid is not the same size of the plane/terrain or hoe do I look on the plane/terrain from what direction. but maybe this can be decided as rules later. or maybe it’s not that matter how do I look on it for now.

The problem is how to convert/calculate the tile map coordinates to the plane/terrain coordinates.

Simple, You need to just know a few basic facts about the 3d grid:

  • Length of a square in the grid (L)
  • Position of the 0, 0 point of the grid (the origin, O). This is a Vector3.
  • Direction of the “X” axis of the grid. This is a Vector3 as well (XDir). It should be a unit vector (Vector of length 1) such as Vector3.right
  • Direction of the “Y” axis of the grid. This is a vector3 as well (YDir). It should be a unit vector (Vector of length 1) such as Vector3.up or Vector3.forward

Then the position of any grid square (x, y) on the grid in 3d is:

Vector3 position = O + (x * L)*XDir + (y * L)* YDir;

If O is the bottom left of the grid square, then you may need to add half a square length to each edge in order to align a cube in the center of the square:

Vector3 position = O + ((x * L) + (L / 2))*XDir + ((y * L) + (L / 2))* YDir;

1 Like

Where the plane is getting in with this ? The position in the end is the position on the grid. but I want to get the position on the plane that is equivalence to the grid positions.

So if you have a default “Plane” object in Unity, you need to figure out how big it is so you can figure out where your “origin” is. You need to also decide what you consider to be 0, 0 and N, N. Is it the “bottom left” of the plane? The top left? Up to you. I think the actual Plane mesh is 10x10 unity units, and by default the normal of the plane faces straight up. Assuming no rotation, translation, or scaling of the plane, that means the origin would be at (-5, 0, -5). Your Xdir would be Vector3.right and your YDir would be Vector3.forward. Assuming you want a 10x10 grid, then the length of a square would just be one (L = 1). You can then just plug these values into that formula above and get the position of any cube.

if the plane is rotated or scaled or translated you’ll need to calculate the new origin, side length, and x/y directions based on that rotation/translation/scaling. The good news is you could just make your cubes children of the plane and ignore all of that, just setting their localPosition in code and it should work fine with the constants above.

1 Like