- Home /
Alternative Ways of Finding Vertices
I know one way of finding vertices that meet a certain criteria is to run a while loop through a mesh's vertices and use if statements to see which ones meet the criteria, but if there are extremely large amounts of vertices, this could be extremely taxing. For example, if I wanted to see whether certain vertices were within a certain distance from an object, or were within another object's bounds, I would have to do this:
int i = 0;
while(i < vertices.Length){
if(otherCollider.bounds.Contains(vertices[i])){
//blah blah
}
}
(Note: Yes I'm aware that that bit of code wouldn't actually work, but you get the idea)
And if the mesh had 4000 vertices, it would run pretty slow. So what else could I do to find these same vertices?
Edit:
Here's the code at hand if anyone would like to see it (refer to comment thread in one of the answers):
public class TerrainEditorLayer {
public string name = "New Layer";
public string layerTag = "Layer Tag";
public Transform terrainLayer;
public float layerHeight = 0;
public Vector2 terrainSize = new Vector2(10,10);
public Texture baseTexture;
public Texture paintTexture;
}
public class TerrainEditor : MonoBehaviour {
public List<TerrainEditorLayer> layers;
public Collider brush;
public float brushSize = 100;
public float opacity = 100;
public bool continuous;
public bool smooth;
private bool digTool;
public int terrainLayer;
private int layerMask;
private RaycastHit hit;
private Collider[] sHit;
private Collider other;
private Transform finder;
public Transform brushProjector;
void Awake () {
layerMask = 1 << terrainLayer;
finder = transform;
}
void Start () {
foreach(TerrainEditorLayer layer in layers){
layer.terrainLayer.tag = layer.layerTag;
layer.terrainLayer.position = new Vector3(0, layer.layerHeight, 0);
}
}
void Update () {
if(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)){
digTool = true;
}else{
digTool = false;
}
if(continuous){
if(Input.GetButton("Fire1")){
if(Physics.Raycast(finder.position, finder.forward, out hit, 1000)){
foreach(TerrainEditorLayer layer in layers){
if(hit.collider.tag == layer.layerTag){
if(brush){
brush.transform.position = hit.point;
}
Deform(layer, FindNextLayer(layer));
}
}
}
}
}else{
if(Input.GetButtonDown("Fire1")){
if(Physics.Raycast(finder.position, finder.forward, out hit, 1000)){
foreach(TerrainEditorLayer layer in layers){
if(hit.collider.tag == layer.layerTag){
if(brush){
brush.transform.position = hit.point;
}
Deform(layer, FindNextLayer(layer));
}
}
}
}
}
}
void Deform (TerrainEditorLayer currentLayer, TerrainEditorLayer nextLayer) {
sHit = Physics.OverlapSphere(hit.point, (brushSize/100), layerMask);
foreach(Collider terrain in sHit){
if(terrain.GetComponent<MeshFilter>() && terrain.CompareTag(currentLayer.layerTag)){
Mesh mesh = terrain.GetComponent<MeshFilter>().mesh;
Vector3[] terrainVertices = mesh.vertices;
Vector3[] terrainNormals = mesh.normals;
Vector3 vertPoint = terrain.transform.InverseTransformPoint(hit.point);
int i = 0;
while(i < terrainVertices.Length){
if((terrainVertices[i] - vertPoint).magnitude < (brushSize/10)){
Vector3 digDir = new Vector3(0,0,0);
if(digTool){
digDir = terrain.transform.TransformDirection(Vector3.up);
}else{
digDir = terrain.transform.TransformDirection(-Vector3.up);
}
Vector3 vertMove = new Vector3(0,0,0);
if(smooth){
if(digTool){
digDir = terrain.transform.TransformDirection(Vector3.up);
vertMove = digDir * ((opacity/100) * Time.deltaTime) + (terrainNormals[i]/-100);
}else{
digDir = terrain.transform.TransformDirection(-Vector3.up);
vertMove = digDir * ((opacity/100) * Time.deltaTime) + (terrainNormals[i]/100);
}
terrainVertices[i] += vertMove;
}else{
if(digTool){
digDir = terrain.transform.TransformDirection(Vector3.up);
vertMove = digDir * ((opacity/100) * Time.deltaTime);
}else{
digDir = terrain.transform.TransformDirection(-Vector3.up);
vertMove = digDir * ((opacity/100) * Time.deltaTime);
}
terrainVertices[i] += vertMove;
}
}
i++;
}
mesh.vertices = terrainVertices;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
terrain.transform.GetComponent<MeshCollider>().enabled = false;
terrain.transform.GetComponent<MeshCollider>().sharedMesh = mesh;
terrain.transform.GetComponent<MeshCollider>().enabled = true;
}
}
}
TerrainEditorLayer FindNextLayer (TerrainEditorLayer currentLayer) {
float closestDepth = Mathf.Infinity;
TerrainEditorLayer closestLayer = new TerrainEditorLayer();
foreach(TerrainEditorLayer layer in layers){
if((currentLayer.layerHeight - layer.layerHeight) > (currentLayer.layerHeight - closestDepth)){
closestDepth = layer.layerHeight;
closestLayer = layer;
}
}
return closestLayer;
}
i havent played around with verticles but could you not find the vertical to begin with and save it as a varible, then access it when needed ins$$anonymous$$d of searching for it at run time.
Well in my case, I only need to access the vertices during runtime, and the vertices that need to be accessed could be different every time.
what you could do then is divide the objects verticles into smaller groups. so then if you hit something it will only search the verticles assigned to that area.
Yeah thats the current approach I'm trying, but I'm hoping to find another way. The problem is when you split an object up, you get more draw calls as it renders each object separately. What I was hoping to do was edit each of the mesh's vertices, and once thats done, create a new mesh using those vertex coordinates for all of the mesh's, and then render that mesh. In that way, their will be 4 separate mesh's but it will render them as a whole. I can't seem to do that though.
It seems like you are trying to simply merge the meshes together into one. That's what CombineChildren script form Unity's StandardAssets does if I'm not mistaken.
Have a look at that.
Also, if I'm not mistaken doing something like this has limitations such as number of vertices per mesh, single material and so on.
Answer by alexfeature · Jan 03, 2013 at 09:22 PM
I don't have anything constructive to suggest code wise but I would like to say that unless you try stuff you will not actually know how fast or slow something is.
I recall reading somewhere on the main unity site (I think David H. said this) that Unity arrays are extremely fast and that Unity is capable of looping through upwards of 1 million vertices per second or something like that. Can't remember the actual numbers now.
Regardless, 4k array of VectorX's is no resource hog by any stretch of imagination.
I'm not good at algebra, let alone geometry, but I would imagine checking a bounding box against a bounding box is far more efficient. Building a virtual bounding grid around complex meshes (think culling grids for occlusion calculations) would greatly reduce the number of checks you would need to do.
Maybe someone a bit more experienced in this area will be able to suggest a better approach.
Just my 2 cents.
If it was 1 million vertices per second, I would have no problem at all. I was getting performance hits somewhere around 600+ vertices.
Sounds odd, I find it a bit hard to believe. Of course it all depends on what exactly you are checking for besides the contains bit.
I would imagine accessing any property such as .transform.xxx would cause unnecessary overhead .
Also, going through all the vertices from beginning to end seems redundant.
What exactly is your usage scenario? Perhaps there is a point of reference that can reduce the scope of your search?
Things are drastically, drastically slower if you try to access the vertices directly from the mesh every time. Not sure what you're doing, but you should be grabbing a copy of the verts and looping through that:
Vector3[] verts = mesh.vertices;
for (int i = 0; i < verts.length; i++)
do something...
if you're currently looping through the mesh.vertices every time, that should speed things up by a factor of a hundred to a thousand.
Same for transforms, as alex mentioned.
Then your overhead has to be in checking against the bounding box - $$anonymous$$d posting that code? Accessing 600 vertices from the list should be sub-millisecond time. Are you doing anything like converting from local to world space on each vertex?
Also, are you going to be accessing the vertices multiple times over the course of your game? If you're not memory constrained, you could build some additional data structures to, for instance, hold the indices of vertices organized by their X, Y, and Z position, and use that to decrease the amount of computation at check time.
So that sounds like about 3 ms of increase. Not huge, but definitely noticeable. I wonder, though, how much of that is co$$anonymous$$g from defor$$anonymous$$g the mesh, how much from running the calcs, etc.
You can use System.Diagnostics.Stopwatch to time the actual calculations and get a more exact feel for how long things are taking. Try creating a new Stopwatch, and then calling Start() where you want to start, Stop() after you do your calculations, and elapsed$$anonymous$$illiseconds (or elapsedTicks) to see how long things are taking. That should help you find where the bottlenecks are.
Answer by PaulR · Jan 07, 2013 at 11:03 AM
You want to:
Cache "`terrain.transform.TransformDirection(Vector3.up)`" etc... - No need to recalculate that for each vertex in range
Change this "`(terrainVertices[i] - vertPoint).magnitude < (brushSize/10)`" to use sqrMagnitude. You're doing a sqrt per vertex.
Cache this too "`((opacity/100) * Time.deltaTime)`" since it's constant for the loop
- You could refactor your loop to remove a bunch of branches. Eg, have 1 loop for when smooth is on, and one for when it's off. It doesn't change during the loop so no need to check it each time. So you'd end up with 4 loops:
smooth && dig
smooth && !dig
!smooth && dig
!smooth && !dig
Remove this "`Vector3 digDir = new Vector3(0,0,0);`" - since you're just overwriting the value after anyway
Move "'Vector3 vertMove = new Vector3(0,0,0); '" out of the loop
Obviously, measure performance before and after each change, and make sure it is actually quicker, etc.. - So you should end up with something like (untested):
foreach(Collider terrain in sHit)
{
if(terrain.GetComponent<MeshFilter>() && terrain.CompareTag(currentLayer.layerTag))
{
Mesh mesh = terrain.GetComponent<MeshFilter>().mesh;
Vector3[] terrainVertices = mesh.vertices;
Vector3[] terrainNormals = mesh.normals;
Vector3 vertPoint = terrain.transform.InverseTransformPoint(hit.point);
Vector3 vertMove;
Vector3 digDir;
int i = terrainVertices.Length;
float sqrdBushSize = (brushSize/10.0f) * (brushSize/10.0f);
float strength = ((opacity/100) * Time.deltaTime);
if(digTool)
digDir = terrain.transform.TransformDirection(Vector3.up) * strength;
else
digDir = terrain.transform.TransformDirection(-Vector3.up) * strength;
switch( (smooth?1:0) + (digTool?2:0) )
{
case 0: // !smooth && !digTool
while(--i)
{
if( (terrainVertices[i] - vertPoint).sqrMagnitude > sqrdBushSize)
continue;
terrainVertices[i] += digDir;
}
break;
case 1: // smooth && !digTool
while(--i)
{
if( (terrainVertices[i] - vertPoint).sqrMagnitude > sqrdBushSize)
continue;
terrainVertices[i] += digDir + (terrainNormals[i] * 0.01f);
}
break;
case 2: // !smooth && digTool
while(--i)
{
if( (terrainVertices[i] - vertPoint).sqrMagnitude > sqrdBushSize)
continue;
terrainVertices[i] += digDir;
}
break;
case 3: // smooth && digTool
while(--i)
{
if( (terrainVertices[i] - vertPoint).sqrMagnitude > sqrdBushSize)
continue;
terrainVertices[i] += digDir + (terrainNormals[i] * 0.01f);
}
break;
}
}
}
Doesn't make a significant difference but makes feel stupid for not doing all of that in the first place. Thanks!
Breaking concise loops into multiplicity to benefit performance is the compiler's job, and the effect on readability and maintainability may be worse than the $$anonymous$$or gain granted in performance. Branches aren't very heavy in this sort of system, and they're orders of magnitude below the cost of the most expensive operations; as such I'd focus on structure rather than micro-optimizations.