so I recently discovered the use Context menu in Unity. I have a mesh with a bunch of triangles that have 3 vertices each.
I have a scriptable object, LevelData
[CreateAssetMenu(fileName = "LevelData", menuName = "Tactics/LevelData", order = 0)]
public class LevelData : ScriptableObject {
public List<Tri> AllTris = new List<Tri>();
public List<Vertex> AllVerts = new List<Vertex>();
public Vertex GetVertexFromVector3(Vector3 position)
{
foreach (Vertex v in AllVerts)
{
if (v.pos == position)
{
return v;
}
}
return null;
}
}
and I set AllTris and AllVerts:
[ContextMenu("Find All Vertices")]
void GetAllVerts(){
mesh = GetComponent<MeshFilter>().sharedMesh;
levelData.AllVerts = new List<Vertex>();
for (int t = 0; t < mesh.vertices.Length; t++)
{
levelData.AllVerts.Add(new Vertex(t, mesh.vertices[t]));
}
}
[ContextMenu("Find All Triangles")]
void GetAllTris(){
mesh = GetComponent<MeshFilter>().sharedMesh;
levelData.AllTris = new List<Tri>();
List<Vertex> verts = levelData.AllVerts;
for (int t = 0; t < mesh.triangles.Length; t+=3)
{
Vertex v1 = verts[mesh.triangles[t+0]];
Vertex v2 = verts[mesh.triangles[t+1]];
Vertex v3 = verts[mesh.triangles[t+2]];
levelData.AllTris.Add(new Tri( (t+3)/3 - 1, v1, v2, v3 ));
}
}
What I was hoping was when I moved a vertex in AllVerts, that would affect that vertex in AllTris
I created this to test it:
[ContextMenu("Test relationship between AllVerts and AllTris")]
void MoveVertexAndLogTriangle(){
Debug.Log(levelData.AllTris.First().First.pos);
Vertex v1 = levelData.GetVertexFromVector3(levelData.AllTris.First().First.pos);
v1.pos += Vector3.up;
Debug.Log(levelData.AllTris.First().First.pos);
}
And this works in before I press play. It logs the vertex moving one down.
However on runtime, it doesn't work. My guess is that the link between the vertex in AllVerts and AllTris is lost. but I really dont know.
I put both of the GetAllTris and GetAllVerts in the start method, and it works as intended like this:
private void Start() {
mesh = GetComponent<MeshFilter>().mesh;
levelData.AllVerts = new List<Vertex>();
for (int t = 0; t < mesh.vertices.Length; t++)
{
//do we need an id?
levelData.AllVerts.Add(new Vertex(t, mesh.vertices[t]));
}
levelData.AllTris = new List<Tri>();
for (int t = 0; t < mesh.triangles.Length; t+=3)
{
Vertex v1 = levelData.AllVerts[mesh.triangles[t+0]];
Vertex v2 = levelData.AllVerts[mesh.triangles[t+1]];
Vertex v3 = levelData.AllVerts[mesh.triangles[t+2]];
levelData.AllTris.Add(new Tri( (t+3)/3 - 1, v1, v2, v3 ));
}
MoveVertexAndLogTriangle();
}
But I'd like to be able to precalculated all of that to save performance.
Does anyone know why this wouldn't work by loading the lists of tris and verts from a scriptable object? And is there a way around that? Thanks in advance!
Im wondering if something with Ids could work?
I can't see your implementation of Vertex
or Tri
.
But in general: When you (de)serialize something in Unity that is not of type UnityEngine.Object
but just a "normal" [Serializable]
it is always serialized BY VALUE.
Have in mind that a ScriptableObject
(as most other assets like scenes and prefabs) basically underlying just are a YAML file containing text.
Now when your ScriptableObject
is loaded from that YAML file, there is no way for the parser to tell that originally an entry within AllTris
was the same c#
instance of an element in AllVerts
.
It will just deserialize both individually as two individual instances without any connection.
Just as an example (note I'm not a YAML expert so my format might be wrong here), let's say you have your classes like
public class SomeScriptableObject : ScriptableObject
{
public List<Vertex> AllVerts = new ();
public List<Tri> AllTris = new ();
[Serializable]
public class Vertex
{
public int id;
public float x;
public float y;
public float z;
public Vertex(int i, Vector3 vert)
{
id = i;
x = vert.x;
y = vert.y
z = vert.z;
}
}
[Serializable]
public class Tri
{
public int id;
public Vertex v1;
public Vertex v2;
public Vertex v3;
public Tri(int i, Vertex v1, Vertex v2, Vertex v3)
{
id = i;
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
}
}
In YAML it will look somewhat like
AllVerts:
- {id: 0, x: 1, y: 2, z: 3}
- {id: 1, x: 2, y: 3, z: 4}
- {id: 2, x: 3, y: 4, z: 5}
- ...
AllTris:
- id: 0
v1: {id: 0, x: 1, y: 2, z: 3}
v2: {id: 1, x: 2, y: 3, z: 4}
v3: {id: 2, x: 3, y: 4, z: 5}
- ...
Now how should the serializer while parsing this know that the vertices are supposed to be linked to items within triangles?
It works for UnityEngine.Object
references as the Unity serializer specifically handles those via their GUID stored in the .meta
files (that's why all references are lost if you loose a .meta
file).
Now how to go about it then?
Tbh, it is not really clear to me yet where exactly you want to go with that. But you would have to make sure to use a different way of referencing / identifying the vertices in a unique way.
=> Why not simply stick to the way Unity does it: Instead of storing the actual Vertex
instances into the Triangle
they simply store the index of vertices in the entire vertices
array.
You could do the same and in your Tri
only pass on the three indices to your Vertex
instances within the AllVerts
.
This would probably also answer
//do we need an id?
If you simply go by index - no, not really.
Which again yields the question: What speaks against just sticking to Unity's format of vertices
as Vector3[]
and triangles
as int[]
simply referencing the vertices by index?
This format already is serializable, directly obtainable and processable by Mesh
and for now I don't see any operation you can do with your format you couldn't with Unity's.