How to flush mesh batch in editor OR how to draw a mesh for single frame in editor?

Short version: I’m drawing mesh from EditorWindow in edit mode, scene view and the mesh is not drawn for single frame, but instead it remains in scene until certain event (like saving scene) occurs - how can I make the mesh to be rendered for single frame? (For next frame I move it, so I need to be rendered in different place so original mesh is not needed to be rendered anymore.)

See the image bellow: I want to draw the blue box only once!

Long version: I want to add following functionality: user selects a Game Object (from either current scene or prefabs) and then puts it into the scene. But when the object is put there (and only at that time), I want to run some code to transform it (especially position, but I have other reasons (currently not implemented) why drag and drop from Prefabs is not enough).

For this purpose I created a EditorWindow object which creates gui to select the object to be added (I plan in future to make a list for simple fast selection) and this window also renders the mesh as preview when user moves mouse over scene view. For this reason, I do not want to create the object before user confirms it by clicking.

This is code I use (please note that instantiating the new object and transforming it is not implemented yet):

using UnityEngine;
using System.Collections;
using UnityEditor;

[ExecuteInEditMode]
public class BlockAdderWindow : EditorWindow
{
	struct CurrentSelection
	{
		public bool isSelected;
		public Vector3 position;
	}
	
	[SerializeField]
	Material previewMaterial;
	
	[SerializeField]
	bool enabled;
	
	[SerializeField]
	GameObject parentObject = null;
	
	[SerializeField]
	GameObject templateObject = null;
	
	Mesh renderMesh;
	
	CurrentSelection currentSelection;
	
	[MenuItem("Window/Block Adder")]
	static void Init()
	{
		EditorWindow.GetWindow(typeof(BlockAdderWindow)).Show();
	}
	
	void OnEnable()
	{
		SceneView.onSceneGUIDelegate += OnSceneGUI;
	}
	
	void OnDisable()
	{
		SceneView.onSceneGUIDelegate -= OnSceneGUI;
	}
	
	void OnSceneGUI(SceneView sceneView)
	{
		if(enabled)
		{
			Camera camera = sceneView.camera;
			Vector2 mousePosition = Event.current.mousePosition;
			mousePosition.y = sceneView.camera.pixelHeight - mousePosition.y;
			CalcSelection(camera, mousePosition);
		}
		
		RenderSelection();
	}
	
	void Update()
	{
		//RenderSelection();
	}

	void OnGUI()
	{
		EditorGUILayout.BeginVertical();
		{
			GameObject oldTemplate = templateObject;
			
			enabled = EditorGUILayout.Toggle("Enabled", enabled);
			previewMaterial = EditorGUILayout.ObjectField("Preview material", previewMaterial, typeof(Material), true) as Material;
			parentObject = EditorGUILayout.ObjectField("Parent", parentObject, typeof(GameObject), true) as GameObject;
			templateObject = EditorGUILayout.ObjectField("Template", templateObject, typeof(GameObject), true) as GameObject;
			
			renderMesh = EditorGUILayout.ObjectField("Render mesh", renderMesh, typeof(Mesh), true) as Mesh;
			
			if( (oldTemplate != templateObject) || ((templateObject != null) && (renderMesh == null)) )
			{
				renderMesh = null;
				
				if(templateObject)
				{
					MeshFilter meshFilter = templateObject.GetComponent<MeshFilter>();
					
					if(meshFilter)
					{
						renderMesh = meshFilter.sharedMesh;
					}
				}
				
				enabled = enabled && (renderMesh != null);
			}
		}
		EditorGUILayout.EndVertical();
	}
	
	void CalcSelection(Camera camera, Vector2 mousePosition)
	{
		//currentSelection.isSelected = false;
		
		if(camera != null)
		{
			Ray ray = camera.ScreenPointToRay(mousePosition);
			RaycastHit hit;
			
			if(Physics.Raycast(ray, out hit))
			{
				currentSelection.isSelected = true;
				currentSelection.position = hit.point;
				//Debug.Log("Selected " + hit.collider.gameObject.name);
			}
		}
	}
	
	void RenderSelection()
	{
		if(enabled && currentSelection.isSelected)
		{
			Graphics.DrawMesh(renderMesh, currentSelection.position,
				Quaternion.identity, previewMaterial, 0);
		}
	}
}

Problem is with line Graphics.DrawMesh(…) - it does not draw the mesh for single frame only - instead, it remains there so when user moves with mouse, it looks like adding multiple objects:

Image on left displays standard scene, image on right displays the problem - I moved the mouse over the scene (only one blue box is expected).

All the meshes disappears when:

  • I save the scene
  • I select item which is displayed in inspector but it wasn’t selected before
  • Game is started

When the game is running and I switch to Scene View, the code works as expected: only one mesh in a frame is rendered.

I created simple project for reproducing the error, you can [19196-littleproject.zip|19196].

To reproduce the problem, follow these steps:

  • Open the project
  • Open scene Assets/Scenes/Scene
  • Open my window: Window / Block Adder
  • Drag and drop Game Object named “Box” to “Template” field in the Block Adder Window
  • Drag and drop Assets/Materials/preview_mat to “Preview material” field in the Block Adder Window
  • Toggle “Enabled” (in the Block Adder Window) to enable the functionality
  • Move the mouse over the scene in Scene View Window.
  • Observe the issue.

My environment:

  • Windows 7 Professional SP1
  • Unity 4.3.0f4 (Free version currently)

Did you ever make any progress on this? I've been having this issue for a while. Initially, I switched to using OnDrawGizmosSelected which does clear the meshes each frame, but has other side effects (such as rendering after OnGUI).

@guavaman I apologize for not answering to you sooner - for some reason, I received e-mail notification about this now, three days later. Anyway, answer is no, but (during work on other project) I found work-around: create game object for this purpose (displaying the mesh and everything) in root of the scene with hide-flags set to hide-and-dont-save (important note: it must be in root, otherwise when scene is saved, any attempt to load the scene will end with crash (it's a Unity bug).

3 Answers

3

I found the solution:

MonoBehaviour.OnRenderObject

Make all your Graphics.DrawMeshNow calls from within there and it will work as expected.

@guavaman I apologize for late reply - see my comment above. Thank you, I can confirm it's working! Note: with new Unity (currently 4.3.4f1) the behavior is little different: the scene is unchanged during mouse move until you press a button. It can be solved by SceneView.RepaintAll(); and combined with your fix it works well.

Btw. how can I accept this answer? I cannot find the button anywhere...

Here’s a workaround (Unity 5.6.1f1) that allows you to use regular Graphics.DrawMesh instead of DrawMeshNow. It relies on checking when the “frame” (you don’t really have the frame loop in editor) changed and submitting new draw calls only then.

int lastRenderedFrame;

void Start() {
    SceneView.onSceneGUIDelegate += OnSceneGUI;
}

void OnSceneGUI(SceneView view)
{
    if (Event.current.type == EventType.Layout)
    {
        // Force the view to update.
        EditorUtility.SetDirty(gameObject);

        // Only update if we know that the queue was cleared.
        if (lastRenderedFrame != Time.renderedFrameCount)
        {
            drawMyMeshes(); // Uses Graphics.DrawMesh()
            lastRenderedFrame = Time.renderedFrameCount;
        }
    }
}

This mess actually works for me. Hoped to see a prettier solution. Not a usual one. ))) And yes it's 2023 now.

This answer is for people who are running into the same problem but are drawing inside the OnToolGUI() method of an EditorTool!


I am using the new EditorTool clas for creating a custom tool that can place blocks in a grid. I am drawing previews of the blocks with Graphics.DrawMesh() but i was running into similar problems as the OP. I have found many threads discussing this problem but none of the solutions worked for me. That is, until I tried the answer of @pvaananen in this thread. I can’t seem to reply directly to his/her comment so I’m posting this here.


So: If you are trying to draw meshes directly inside the OnToolGUI() of an EditorTool, try the answer of @pvaananen! I’ve replaced the EditorUtility.SetDirty(gameObject); part with EditorUtility.SetDirty(this); and that seems to do the trick.