Mir
Mir

Reputation: 323

Unity - How to set the color of an individual face when clicking a mesh?

Yesterday others on Stack Overflow helped me determine how to recolor a mesh triangle to red by clicking on it, it works great, the only problem is that the 3 vertices that get recolored are shared between triangles. This results in coloration that looks rather smeared. I'm really hoping there's a way to color only a single face (or normal if you will).

I've attached the following script to my mesh that uses a raycast to determine the surface coordinate and translate a green cube there. The gif below will better illustrate this problem.

Once again, any help or insight into this would be greatly appreciated. Thanks!

enter image description here

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MyRayDraw : MonoBehaviour
{
    public GameObject cube;
    private MeshRenderer meshRenderer;
    Mesh mesh;
    Vector3[] vertices;
    Color[] colorArray;

    private void Start()
    {
        mesh = transform.GetComponent<MeshFilter>().mesh;
        vertices = mesh.vertices;

        colorArray = new Color[vertices.Length];
        for (int k = 0; k < vertices.Length; k++)
        {
            colorArray[k] = Color.white;
        }
        mesh.colors = colorArray;
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                Snap(hit.point); // Moves the green cube

                int[] triangles = mesh.triangles;
                var vertIndex1 = triangles[hit.triangleIndex * 3 + 0];
                var vertIndex2 = triangles[hit.triangleIndex * 3 + 1];
                var vertIndex3 = triangles[hit.triangleIndex * 3 + 2];

                colorArray[vertIndex1] = Color.red;
                colorArray[vertIndex2] = Color.red;
                colorArray[vertIndex3] = Color.red;

                mesh.colors = colorArray;                
            }
            else
            {
                Debug.Log("no hit");
            }
        }
    }
}

enter image description here

Upvotes: 2

Views: 5409

Answers (1)

derHugo
derHugo

Reputation: 90679

As you say the issue is that the vertices are shared between triangles but coloring is always vertex based.

The idea for a solution is:

  • for each vertex of the hit triangle check if it is used by other triangles
  • if so copy its position to create a new separated vertex
  • update the triangle to use the newly created vertex indices
  • (evtl.) use RecalculateNormals to make the triangles face outside without having to care about the order of provided vertices
using System.Linq;
using UnityEngine;

public class MyRayDraw : MonoBehaviour
{
    public GameObject cube;

    // Better to reference those already in the Inspector
    [SerializeField] private MeshFilter meshFilter;
    [SerializeField] private MeshRenderer meshRenderer;
    [SerializeField] private MeshCollider meshCollider;

    private Mesh _mesh;

    private void Awake()
    {
        if (!meshFilter) meshFilter = GetComponent<MeshFilter>();
        if (!meshRenderer) meshRenderer = GetComponent<MeshRenderer>();
        if (!meshCollider) meshCollider = GetComponent<MeshCollider>();

        _mesh = meshFilter.mesh;

        // create new colors array where the colors will be created
        var colors = new Color[_mesh.vertices.Length];
        for (var k = 0; k < colors.Length; k++)
        {
            colors[k] = Color.white;
        }
        _mesh.colors = colors;
    }

    private void Update()
    {
        if (!Input.GetMouseButtonDown(0)) return;

        var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out var hit))
        {
            Debug.Log(hit.triangleIndex);
            //cube.transform.position = hit.point;

            // Get current vertices, triangles and colors
            var vertices = _mesh.vertices;
            var triangles = _mesh.triangles;
            var colors = _mesh.colors;

            // Get the vert indices for this triangle
            var vert1Index = triangles[hit.triangleIndex * 3 + 0];
            var vert2Index = triangles[hit.triangleIndex * 3 + 1];
            var vert3Index = triangles[hit.triangleIndex * 3 + 2];

            // Get the positions for the vertices
            var vert1Pos = vertices[vert1Index];
            var vert2Pos = vertices[vert2Index];
            var vert3Pos = vertices[vert3Index];

            // Now for all three vertices we first check if any other triangle if using it
            // by simply count how often the indices are used in the triangles list
            var vert1Occurrences = 0;
            var vert2Occurrences = 0;
            var vert3Occurrences = 0;
            foreach (var index in triangles)
            {
                if (index == vert1Index) vert1Occurrences++;
                else if (index == vert2Index) vert2Occurrences++;
                else if (index == vert3Index) vert3Occurrences++;
            }

            // Create copied Lists so we can dynamically add entries
            var newVertices = vertices.ToList();
            var newColors = colors.ToList();

            // Now if a vertex is shared we need to add a new individual vertex
            // and also an according entry for the color array
            // and update the vertex index
            // otherwise we will simply use the vertex we already have
            if (vert1Occurrences > 1)
            {
                newVertices.Add(vert1Pos);
                newColors.Add(new Color());
                vert1Index = newVertices.Count - 1;
            }

            if (vert2Occurrences > 1)
            {
                newVertices.Add(vert2Pos);
                newColors.Add(new Color());
                vert2Index = newVertices.Count - 1;
            }

            if (vert3Occurrences > 1)
            {
                newVertices.Add(vert3Pos);
                newColors.Add(new Color());
                vert3Index = newVertices.Count - 1;
            }

            // Update the indices of the hit triangle to use the (eventually) new
            // vertices instead
            triangles[hit.triangleIndex * 3 + 0] = vert1Index;
            triangles[hit.triangleIndex * 3 + 1] = vert2Index;
            triangles[hit.triangleIndex * 3 + 2] = vert3Index;

            // color these vertices
            newColors[vert1Index] = Color.red;
            newColors[vert2Index] = Color.red;
            newColors[vert3Index] = Color.red;

            // write everything back
            _mesh.vertices = newVertices.ToArray();
            _mesh.triangles = triangles;
            _mesh.colors = newColors.ToArray();

            _mesh.RecalculateNormals();
        }
        else
        {
            Debug.Log("no hit");
        }
    }
}

enter image description here


Note, however, that this works with simple coloring but might not for complex textures with UV mapping. You would have to also update the mesh.uv if using UV mapped textures.

Upvotes: 9

Related Questions