Creating a cone shaped mesh through code, how do I dynamically get the triangle points?

I am trying to create a mesh in code that has a cone shape to it. It adjusts it’s shape based on raycasts. I intend to use it as a sort of security camera detection cone.

I think the only problem I have left is that I can’t figure out what the triangle points are supposed to be? I can’t seem to get that to work dynamically based on the size.

Some other questions though, will this be too resource intensive? Is there a better way to do this kind of dynamic mesh shaping?

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

public class test3 : MonoBehaviour
{

    public int size = 5;
    public float fwdMultiplier = 2;
    public float maxRayDist = 10;
    public LayerMask mask;

    Mesh mesh;
    public List<Vector3> verticies = new List<Vector3>();
    public List<int> triangles = new List<int>();

    private void Start()
    {
        mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;
    }

    public void Update()
    {
        CreateShape();
        UpdateMesh();
    }

    private void CreateShape()
    {
        verticies.Clear();
        triangles.Clear();

        verticies.Add(new Vector3(0, 0, 0)); //the tip of the cone aka the gameobject current position

        for (int x = -size; x <= size; x++)
        {
            for (int y = -size; y <= size; y++)
            {
                RaycastHit hit;
                Ray ray = new Ray(transform.position, transform.forward * fwdMultiplier + transform.TransformDirection(x, y, 0));

                if (Physics.Raycast(ray, out hit, maxRayDist, mask))
                {
                    Debug.DrawLine(transform.position, hit.point, Color.red);
                    if (x == -size || x == size || y == -size || y == size)
                    {
                        verticies.Add(transform.InverseTransformPoint(hit.point));
                    }
                }
                else
                {
                    Debug.DrawRay(transform.position, transform.forward * fwdMultiplier + transform.TransformDirection(x, y, 0), Color.green);
                    if (x == -size || x == size || y == -size || y == size)
                    {
                        verticies.Add(transform.InverseTransformPoint(transform.position + ray.direction * maxRayDist));
                    }
                }
            }
        }

        //The problem area. Not sure how to handle dynamically setting the triangles up.
        for (int i = 1; i < verticies.Count - 1; i++)
        {
            triangles.Add(0);
            triangles.Add(i);
            triangles.Add(i + 1);
        }
    }

    void UpdateMesh()
    {
        mesh.Clear();

        mesh.vertices = verticies.ToArray();
        mesh.triangles = triangles.ToArray();

        mesh.RecalculateNormals();
    }
}



Answer
Add a comment

While looking for a solution to my problem I encountered this video. At 8:30 it seems similar to what I’m trying to do.

HA! My favorite thing of all… I love procgen, especially procgen in Unity. Unity makes it so easy!

I actually have some code you’re welcome to use, abuse, peruse, whatever. Check out my MakeGeo project… it even has a UV cone maker in it already.

MakeGeo is presently hosted at these locations:

https://bitbucket.org/kurtdekker/makegeo

2 Likes

You never know until you try, then just use the profiler to find what your issue is.

I do know that without looking at the above video, here is what I would do:

  • fabricate a cone based on the “regard field” of a given entity (that won’t change)
  • angle it around in different directions with their gaze
  • use math (raycasts and angle-checks) to actually decide if something is “in sight”

I’m saying I would not use trigger volumes for this.

1 Like

There’s another one here with a capped bottom.

I think Kurt’s code looks nicer but the caps are separate objects.

2 Likes

You can start with a circle. Find N points of a simple regular polygon.
To do this, you need to understand the unit circle and some basic trigonometry.

Vector2[] GetBasePoints(int vertices, float radius);
  const float TAU = 2f * MathF.PI;
  var pts = new Vector3[vertices];
  var step = TAU / vertices; // angular step between two vertices
  for(int i = 0; i < vertices; i++) {
    pts[i] = radius * trig(i * step); // convert polar coordinate to cartesian space
  }
  return pts;
}

Where

static Vector2 trig(float rad) => new Vector2(MathF.Cos(angle), MathF.Sin(angle));

Now you can introduce a middle vertex and construct 3D vertices out of this

Vector3[] BuildConeVertices(Vector2[] baseVerts, float coneHeight) {
  if(baseVerts is null || baseVerts < 3) throw new InvalidOperationException("Requires at least 3 base vertices.");
  var verts = new Vector3[baseVerts.Length + 1];
  verts[0] = new Vector3(0f, coneHeight, 0f);
  for(int i = 0; i < baseVerts.Length; i++) {
    verts[i+1] = new Vector3(baseVerts[i].x, 0f, baseVerts[i].y);
  }
  return verts;
}

Finally you can form the actual triangles.
However to achieve flat shading, you want to split vertices, so we’ll create a new list out of it.

void ConstructCone(Vector3[] coneVerts, List<Vector3> finalVerts, List<int> triangles) {
  if(coneVerts is null || coneVerts < 4) throw new InvalidOperationException("Requires at least 4 vertices.");
  if(finalVerts is null || triangles is null) throw new ArgumentNullException();

  finalVerts.Clear();
  triangles.Clear();

  var rimVertices = coneVerts.Length - 1;
 
  for(int i = 1; i <= rimVertices; i++) {
    int a = i, b = i < rimVertices - 1? i + 1 : 1;
    AddTriangle(coneVerts[a], coneVerts[b], coneVerts[0]);
  }

  void AddTriangle(Vector3 t1, Vector3 t2, Vector3 t3) {
    finalVerts.Add(t1);
    finalVerts.Add(t2);
    finalVerts.Add(t3);
    triangles.Add(finalVerts.Count - 3);
    triangles.Add(finalVerts.Count - 2);
    triangles.Add(finalVerts.Count - 1);
  }
}

Then you combine all of this (it’s slightly suboptimal in this example, but you can rearrange)

Mesh CreateConeMesh(string name, int sides, Vector3 apex, Quaternion rotation, float baseRadius, float height) {
  var baseVerts = GetBasePoints(sides, baseRadius);
  var coneVerts = BuildConeVertices(baseVerts, height);

  var verts = new List<Vector3>();
  var tris = new List<int>();
  ConstructCone(coneVerts, verts, tris);
 
  for(int i = 0; i < verts.Count; i++) {
    verts[i] = (apex - coneVerts[0]) + rotation * (verts[i] - coneVerts[0]);
  }

  Mesh mesh = new Mesh();
  mesh.name = name;
  mesh.SetVertices(verts);
  mesh.SetTriangles(0, tris);
  mesh.RecalculateNormals();
 
  return mesh;
}

This is without the cap on the bottom, you can solve that with another triangle fan, or you can properly connect the base vertices.

I apologize if this doesn’t work immediately, I’ve coded this in the browser directly.

1 Like