I have written a custom terrain editor tool but it runs kind of slow and I need some help finding things I can simplify or change to make the program run a little faster. it is still not working 100% as well so if you see any reason it would be editing terrain in a square rather than a circle, or why it will only edit the center cell properly please let me know what I did wrong so i can fix it. For now it is getting slow though so before I focus on those bugs I need to focus on optimizing it.
using UnityEngine;
using UnityEditor;
using System.Collections;
public class TerrainEditor : EditorWindow
{
//Declare variables for the terrain editor window to use.
public static bool editVertices = true;
public static bool editTextures = false;
public static float gridLock = 0.25f;
public static bool showBrush = true;
public static string brushRadius = "5";
public static GameObject mouseBoundsPosition;
public static SceneView view;
public static GameObject[] Cells;
public static bool leftMouseButton = false;
//Call each frame to determine if the mouse is over the scene window.
void OnInspectorUpdate()
{
if (mouseOverWindow) mouseOverWindow.Focus();
Repaint();
}
[MenuItem("Heightmap/Terrain Editor %#e")]
public static void Init()
{
//Clear any temp files from before.
ClearTempFiles();
//Create the temp folder to store the mouses position.
GameObject tempFolder = new GameObject();
tempFolder.name = "Temp Folder";
tempFolder.tag = "Temporary";
tempFolder.transform.parent = GameObject.FindGameObjectWithTag(TerrainSystem.worldTag).transform;
//Create the mouses positional bounds to determine what points on the cell need to be edited.
mouseBoundsPosition = new GameObject();
mouseBoundsPosition.name = "Temp Mouse";
mouseBoundsPosition.tag = "Temporary";
mouseBoundsPosition.transform.parent = tempFolder.transform;
mouseBoundsPosition.AddComponent<SphereCollider>();
//Controls the creation of the window itself.
TerrainEditor editorWindow = (TerrainEditor)GetWindow(typeof(TerrainEditor), true, "Terrain Editor");
SceneView.onSceneGUIDelegate += OnScene;
editorWindow.Show();
}
void OnGUI()
{
//Creates the window.
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Map Editor");
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
brushRadius = GUILayout.TextField(brushRadius, 2);
showBrush = GUILayout.Toggle(showBrush, "Show Brush");
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
editVertices = GUILayout.Toggle(editVertices, "Edit Terrain");
EditorGUILayout.EndHorizontal();
}
public static void OnScene(SceneView sceneview)
{
//Creates the current event that is happening in the sceneview.
Event input = Event.current;
//Disables the ability to select objects in the scene.
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
//If the scene camera is not blank it will lock it to a birds eye view of the scene.
view = SceneView.currentDrawingSceneView;
if (view != null)
{
view.rotation = Quaternion.Euler(90, 0, 0);
}
//Locates every cell in the scene.
Cells = GameObject.FindGameObjectsWithTag(TerrainSystem.cellTag);
//determines if there are any cells on scene and displays an error if there are not any.
if (Cells.Length > 0)
{
//Handles the ray casting from the scenes camera to the mouses position.
Ray worldRay = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(worldRay, out hitInfo))
{
//Only functions if it hits a cell by determining what tag the hit object has.
if (hitInfo.transform.tag == TerrainSystem.cellTag)
{
//If the brush is enabled this uses the mouse position to draw the brush.
if (showBrush)
{
//Use the point at which the ray hit the cell to determine where the mouse is at.
Vector3 mousePosition = hitInfo.point;
float radius = (float.Parse(brushRadius));
Handles.color = Color.blue;
Handles.DrawWireDisc(mousePosition, Vector3.up, radius);
}
//The system of if statements that determines when and if the mouse button is clicked and when to edit terrain.
if (input.type == EventType.MouseDown || input.type == EventType.MouseDrag)
{
if (input.button == 0) if (leftMouseButton == false) leftMouseButton = true;
else if (input.button != 0 && leftMouseButton) leftMouseButton = false;
}
else if (input.type != EventType.MouseDown && leftMouseButton || input.type != EventType.MouseMove && leftMouseButton) leftMouseButton = false;
//Use the terrain handler function to raise or lower terrain.
if (leftMouseButton)
{
TerrainHandler();
}
}
}
}
//Display the error code for if there are no cells on scene.
else ErrorCodeT.x003b();
}
public void OnDestroy()
{
//Destroy the temp data and scene functions when the window is closed.
SceneView.onSceneGUIDelegate -= OnScene;
ClearTempFiles();
}
//Function used to clear temp files when the window is closed.
public static void ClearTempFiles()
{
GameObject[] tempFiles = GameObject.FindGameObjectsWithTag("Temporary");
foreach (GameObject tempFile in tempFiles)
{
DestroyImmediate(tempFile);
}
}
//Function used to handle the editing of cells.
public static void TerrainHandler()
{
//Locates every cell in the scene.
Cells = GameObject.FindGameObjectsWithTag(TerrainSystem.cellTag);
//Handles the ray casting from the scenes camera to the mouses position.
Ray worldRay = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(worldRay, out hitInfo))
{
//Use the point at which the ray hit the cell to determine where the mouse is at.
Vector3 mousePosition = hitInfo.point;
float newHeight = mousePosition.y + gridLock;
//Used to construct teh bounds inside of which vertices will be edited.
mouseBoundsPosition.transform.position = mousePosition;
float radius = (float.Parse(brushRadius));
mouseBoundsPosition.GetComponent<SphereCollider>().radius = radius;
Bounds mouseBounds = mouseBoundsPosition.GetComponent<SphereCollider>().bounds;
foreach (GameObject cell in Cells)
{
if (mouseBounds.Intersects(cell.GetComponent<MeshCollider>().bounds))
{
Mesh mesh = cell.GetComponent<MeshFilter>().sharedMesh;
Vector3[] vertices = mesh.vertices;
if (editVertices)
{
for (int i = 0; i < vertices.Length; i++)
{
if (mouseBounds.Contains(vertices[i]))
{
Vector3 newPosition = new Vector3(vertices[i].x, newHeight, vertices[i].z);
vertices[i] = newPosition;
mesh.vertices = vertices;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
}
}
}
}
}
}
}
}
That definitely helped, I was thinking it was because I was trying to find every cell in the scene, I am hoping that does not become a problem when there gets to be over 1000 cells, do you know if that will become a problem? let me know if you see anything else I can do to make this faster, or if you see why it is editing in a square and not a cicrcle, that has to be fixed soon also.
BoredMormon
what is the profiler? what are you talking about there?
and mgear
how would I go about creating my own contains method? I sort of need one at teh moment because I think the bounds method is always going to be square and I think that is my culprit for why it edits terrain in a square. not entirely sure though, but creating my own method is something I definitely want to look into and I will need help with that
and how can I optimize the part where I scan for all game objects with the cell tag? I think I could get rid of it completely, but the problem is I am only casting one ray and it can only hit one cell at a time, so when I edit the edge of the cell it is going to create holes
It appears as if the profiler is for in the game, this is an editor tool, so no I do not think that will help me out. However thanks for informing me of it, I can see it being helpful in the future.
I still would like help with my code if anyone sees any other drastic changes that can be made. I have done more than I wanted to to speed it up but I can just create a few extra nifty scripts to get around the problems I was trying to avoid.
also does anybody have any idea why it is only editing the center cell? i cannot figure that part out for some reason.
using UnityEngine;
using UnityEditor;
using System.Collections;
public class TerrainEditor : EditorWindow
{
//Declare variables for the terrain editor window to use.
public static bool editVertices = true;
public static bool editTextures = false;
public static float gridLock = 0.25f;
public static bool showBrush = true;
public static string brushRadius = "5";
public static GameObject mouseBoundsPosition;
public static SceneView view;
public static GameObject[] Cells;
public static bool leftMouseButton = false;
//Call each frame to determine if the mouse is over the scene window.
void OnInspectorUpdate()
{
if (mouseOverWindow) mouseOverWindow.Focus();
Repaint();
}
[MenuItem("Heightmap/Terrain Editor %#e")]
public static void Init()
{
//Clear any temp files from before.
ClearTempFiles();
//Create the temp folder to store the mouses position.
GameObject tempFolder = new GameObject();
tempFolder.name = "Temp Folder";
tempFolder.tag = "Temporary";
tempFolder.transform.parent = GameObject.FindGameObjectWithTag(TerrainSystem.worldTag).transform;
//Create the mouses positional bounds to determine what points on the cell need to be edited.
mouseBoundsPosition = new GameObject();
mouseBoundsPosition.name = "Temp Mouse";
mouseBoundsPosition.tag = "Temporary";
mouseBoundsPosition.transform.parent = tempFolder.transform;
mouseBoundsPosition.AddComponent<SphereCollider>();
//Controls the creation of the window itself.
TerrainEditor editorWindow = (TerrainEditor)GetWindow(typeof(TerrainEditor), true, "Terrain Editor");
SceneView.onSceneGUIDelegate += OnScene;
editorWindow.Show();
}
void OnGUI()
{
//Creates the window.
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Map Editor");
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
brushRadius = GUILayout.TextField(brushRadius, 2);
showBrush = GUILayout.Toggle(showBrush, "Show Brush");
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
editVertices = GUILayout.Toggle(editVertices, "Edit Terrain");
EditorGUILayout.EndHorizontal();
}
public static void OnScene(SceneView sceneview)
{
//Creates the current event that is happening in the sceneview.
Event input = Event.current;
//Disables the ability to select objects in the scene.
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
//If the scene camera is not blank it will lock it to a birds eye view of the scene.
view = SceneView.currentDrawingSceneView;
if (view != null)
{
view.rotation = Quaternion.Euler(90, 0, 0);
}
//Locates every cell in the scene.
Cells = GameObject.FindGameObjectsWithTag(TerrainSystem.cellTag);
//determines if there are any cells on scene and displays an error if there are not any.
if (Cells.Length > 0)
{
//Handles the ray casting from the scenes camera to the mouses position.
Ray worldRay = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
RaycastHit hitInfo;
//If the brush is enabled this uses the mouse position to draw the brush.
if (showBrush)
{
if (Physics.Raycast(worldRay, out hitInfo))
{
//Only functions if it hits a cell by determining what tag the hit object has.
if (hitInfo.transform.tag == TerrainSystem.cellTag)
{
//Use the point at which the ray hit the cell to determine where the mouse is at.
Vector3 mousePosition = hitInfo.point;
float radius = (float.Parse(brushRadius));
Handles.color = Color.blue;
Handles.DrawWireDisc(mousePosition, Vector3.up, radius);
}
}
}
//The system of if statements that determines when and if the mouse button is clicked and when to edit terrain.
if (input.type == EventType.MouseDown || input.type == EventType.MouseDrag)
{
if (input.button == 0) if (leftMouseButton == false) leftMouseButton = true;
else if (input.button != 0 && leftMouseButton) leftMouseButton = false;
}
else if (input.type != EventType.MouseDown && leftMouseButton || input.type != EventType.MouseMove && leftMouseButton) leftMouseButton = false;
//Use the terrain handler function to raise or lower terrain.
if (leftMouseButton)
{
TerrainHandler();
}
}
//Display the error code for if there are no cells on scene.
else ErrorCodeT.x003b();
}
public void OnDestroy()
{
//Destroy the temp data and scene functions when the window is closed.
SceneView.onSceneGUIDelegate -= OnScene;
ClearTempFiles();
}
//Function used to clear temp files when the window is closed.
public static void ClearTempFiles()
{
GameObject[] tempFiles = GameObject.FindGameObjectsWithTag("Temporary");
foreach (GameObject tempFile in tempFiles)
{
DestroyImmediate(tempFile);
}
}
//Function used to handle the editing of cells.
public static void TerrainHandler()
{
//Handles the ray casting from the scenes camera to the mouses position.
Ray worldRay = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
RaycastHit hitInfo;
//The part that handles the editing of terrain.
if (editVertices)
{
if (Physics.Raycast(worldRay, out hitInfo))
{
if (hitInfo.transform.tag == TerrainSystem.cellTag)
{
//Use the point at which the ray hit the cell to determine where the mouse is at.
Vector3 mousePosition = hitInfo.point;
float newHeight = hitInfo.point.y + gridLock;
//Used to construct the bounds inside of which vertices will be edited.
mouseBoundsPosition.transform.position = mousePosition;
float radius = (float.Parse(brushRadius));
mouseBoundsPosition.GetComponent<SphereCollider>().radius = radius;
Bounds mouseBounds = mouseBoundsPosition.GetComponent<SphereCollider>().bounds;
GameObject cell = hitInfo.transform.gameObject;
Mesh mesh = cell.gameObject.GetComponent<MeshFilter>().sharedMesh;
Vector3[] vertices = mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
if (mouseBounds.Contains(vertices[i]))
{
Vector3 newPosition = new Vector3(vertices[i].x, newHeight, vertices[i].z);
vertices[i] = newPosition;
}
}
mesh.vertices = vertices;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
}
}
}
}
}
Did you open the profiler? There is a button to turn on profiling the editor. This will give you a far better picture of how your tool is performing then anything we can guess at.
Shoot I see it now, k that is a nifty little tool, with my current script I am about double where I am without it, so that is cool.thanks guys.
still need help figuring out why it is not editing the cell the mouse is over, only the center cell. If anyone wants to save me from making a new thread.
Provide some more information.
How is your scene currently set up? Maybe add a few screenshots, a small .gif or video which illustrates the exact problem.
well the scene is not important, the scripts are designed to take care of everything. as long as there is one cell in the scene everything should work with my terrain system.
I used my tools to generate a 5*5 grid of cells originating at the center. the terrain editor however is only able to edit the cell placed in the center it cannot edit the rest of them just that center one, however if I hold the mouse and drag it past the edge of the center cell, it then edits the opposite edge of the cell next to it.
So I think the problem lies where I am determining what mesh is supposed to be edited, down at the bottom around line 182, the problem is either there or where I determine the mouse bounds.
Edit: currently debugging I learned that the cell at 0,0 the mesh I am retrieving says Cell.Instance, the rest of the cells say just Cell, so maybe that could be it? the problem definitely lies with how I am retrieving mesh, that or assigning it to the objects or something along those lines.
I tried to post a screen shot but I have 3 monitors and a 4k tv hung above them so that did not work out too well for me
From what you’ve written and another look at your code, could it possibly be that you never take the meshes world position into account?
The code you’ve posted only considers the positions of the vertices, and those are usually local to the object whereas the bounds you’re taking to for the contains-check is given in world space.
that is smart, and it sounds like something that might work, but how would I go about accomplishing that?
Edit: can that be it? if that was the problem then editing the other cells would just sort of place the vertices over the center cell wouldn’t it? I have done this so far and it has not made a difference in the editors behavior.
for (int i = 0; i < vertices.Length; i++)
{
if (Vector3.Distance(mousePosition,vertices[i]) < radius)
{
Vector3 worldPoint = cell.transform.TransformPoint(vertices[i]);
Vector3 newPosition = new Vector3(worldPoint.x, newHeight, worldPoint.z);
vertices[i] = cell.transform.InverseTransformPoint(newPosition); ;
}
}
Holy crap I am getting good at this programming thing, and it all makes sense now, the solution was close to what you said, it was because I was using the meshes local vertex positions and they did need to be converted to world positions, then re converted back so they could be inserted into the mesh again, however it was not the way I was editing them but the way I was detecting if the mouse was close enough to them.
the solution:
for (int i = 0; i < vertices.Length; i++)
{
Vector3 worldPoint = cell.transform.TransformPoint(vertices[i]);
if (Vector3.Distance(mousePosition,worldPoint) < radius)
{
Vector3 newPosition = new Vector3(worldPoint.x, newHeight, worldPoint.z);
vertices[i] = cell.transform.InverseTransformPoint(newPosition); ;
}
}