Hydrogen Framework  1.3.1
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events
hMeshCombiner.cs
1 #region Copyright Notice & License Information
2 //
3 // hMeshCombiner.cs
4 //
5 // Author:
6 // Matthew Davey <matthew.davey@dotbunny.com>
7 //
8 // Copyright (c) 2014 dotBunny Inc. (http://www.dotbunny.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 #endregion
28 
29 using UnityEngine;
30 using System.Collections;
31 using System.Threading;
32 using System.Collections.Generic;
33 
34 /// <summary>
35 /// A drop in implementation of how to interact with the Hydrogen.Threading.Jobs.MeshCombiner. This is meant really as
36 /// an example of one way of using it, but you will probably want to create your own method to further optimize the
37 /// workflow.
38 /// </summary>
39 [AddComponentMenu ("Hydrogen/Singletons/Mesh Combiner")]
40 public class hMeshCombiner : MonoBehaviour
41 {
42  /// <summary>
43  /// An instance of the MeshCombiner.
44  /// </summary>
46  /// <summary>
47  /// This is used in our example to throttle things a bit when accessing Unity objects.
48  /// </summary>
49  /// <remarks>It seems at 180, its a nice sweet spot for the meshes in our example scene.</remarks>
50  public int ThrottleRate = 180;
51  /// <summary>
52  /// Should this input manager survive scene switches?
53  /// </summary>
54  public bool Persistent = true;
55  /// <summary>
56  /// Internal fail safe to maintain instance across threads.
57  /// </summary>
58  /// <remarks>
59  /// Multithreaded Safe Singleton Pattern.
60  /// </remarks>
61  /// <description>
62  /// http://msdn.microsoft.com/en-us/library/ms998558.aspx
63  /// </description>
64  static readonly System.Object _syncRoot = new System.Object ();
65  /// <summary>
66  /// Internal reference to the static instance of the Mesh Combiner component.
67  /// </summary>
68  static volatile hMeshCombiner _staticInstance;
69  /// <summary>
70  /// Is the MeshCombiner thread running?
71  /// </summary>
72  bool _threadRunning;
73  /// <summary>
74  /// Internal reference of what transform a combine operation should parent its meshes too.
75  /// </summary>
76  Dictionary<int, Transform> _parentLookup = new Dictionary<int, Transform> ();
77 
78  /// <summary>
79  /// Gets the input manager instance, creating one if none is found.
80  /// </summary>
81  /// <value>
82  /// The Input Manager.
83  /// </value>
84  public static hMeshCombiner Instance {
85  get {
86  if (_staticInstance == null) {
87  lock (_syncRoot) {
88  _staticInstance = FindObjectOfType (typeof(hMeshCombiner)) as hMeshCombiner;
89 
90  // If we don't have it, lets make it!
91  if (_staticInstance == null) {
92  var go = GameObject.Find (Hydrogen.Components.DefaultSingletonName) ??
93  new GameObject (Hydrogen.Components.DefaultSingletonName);
94 
95  go.AddComponent<hMeshCombiner> ();
96  _staticInstance = go.GetComponent<hMeshCombiner> ();
97  }
98  }
99  }
100  return _staticInstance;
101  }
102  }
103 
104  /// <summary>
105  /// Does an Input Manager already exist?
106  /// </summary>
107  public static bool Exists ()
108  {
109  return _staticInstance != null;
110  }
111 
112  /// <summary>
113  /// Combine all active meshes under the root object.
114  /// </summary>
115  /// <remarks>
116  /// You do not need to wait till completion to call this again with more meshes to combine, however the thread
117  /// will not call back to Unity till all meshes have been processed in the queue.
118  /// </remarks>
119  /// <param name="rootObject">The "root" GameObject.</param>
120  /// <param name="outputParent">Sets the output parent transform.</param>
121  /// <param name="disableRootObject">
122  /// If set to <c>true</c> disable root object (and its children) after iterating
123  /// through its children..
124  /// </param>
125  public void Combine (GameObject rootObject, Transform outputParent, bool disableRootObject)
126  {
127  // Do it!
128  StartCoroutine (AddMeshes (rootObject, outputParent));
129 
130  // Disable our example dat
131  if (disableRootObject) {
132  rootObject.SetActive (false);
133  }
134  }
135 
136  /// <summary>
137  /// This function is called in the example after the MeshCombiner has processed the meshes, it starts a Coroutine
138  /// to create the actual meshes based on the flat data. This is the most optimal way to do this sadly as we cannot
139  /// create or touch Unity based meshes outside of the main thread.
140  /// </summary>
141  /// <param name="hash">Instance Hash.</param>
142  /// <param name="meshOutputs">.</param>
143  public void ThreadCallback (int hash, Hydrogen.Threading.Jobs.MeshCombiner.MeshOutput[] meshOutputs)
144  {
145  // This is just a dirty way to see if we can squeeze jsut a bit more performance out of Unity when
146  // making all of the meshes for us (instead of it being done in one call, we use a coroutine with a loop.
147  _threadRunning = false;
148  StartCoroutine (CreateMeshes (hash, meshOutputs));
149  }
150 
151  /// <summary>
152  /// Process meshFilters in Unity's main thread, as we are required to by Unity. At least we've rigged it as a
153  /// coroutine! Right? OK I know I really wish we could have used mesh data in a thread but properties die as well.
154  /// </summary>
155  /// <returns>IEnumartor aka Coroutine</returns>
156  /// <remarks>
157  /// For the sake of the demo we are going to need to roll over the "Target" to find all the
158  /// meshes that we need to look at, but in theory you could do this without having to load the
159  /// object by simply having raw mesh data, or any other means of accessing it.
160  /// </remarks>
161  IEnumerator AddMeshes (GameObject rootObject, Transform outputParent)
162  {
163  // Yes We Hate This - There Are Better Implementations
164  MeshFilter[] meshFilters = rootObject.GetComponentsInChildren<MeshFilter> ();
165 
166  // Loop through all of our mesh filters and add them to the combiner to be combined.
167  for (int x = 0; x < meshFilters.Length; x++) {
168 
169  if (meshFilters [x].gameObject.activeSelf) {
170  Combiner.AddMesh (meshFilters [x],
171  meshFilters [x].renderer,
172  meshFilters [x].transform.localToWorldMatrix);
173  }
174 
175  // We implemented this as a balance point to try and break some of the processing up.
176  // If we were to yield every pass it was taking to long to do nothing.
177  if (x > 0 && x % ThrottleRate == 0) {
178  yield return new WaitForEndOfFrame ();
179  }
180  }
181 
182  // Start the threaded love
183  if (Combiner.MeshInputCount > 0) {
184  _threadRunning = true;
185  _parentLookup.Add (Combiner.Combine (ThreadCallback), outputParent);
186  }
187  yield return new WaitForEndOfFrame ();
188  }
189 
190  /// <summary>
191  /// Unity's Awake Event
192  /// </summary>
193  protected void Awake ()
194  {
195  // Should this gameObject be kept around :) I think so.
196  if (Persistent)
197  DontDestroyOnLoad (gameObject);
198  }
199 
200  /// <summary>
201  /// Process the MeshDescription data sent back from the Combiner and make it appear!
202  /// </summary>
203  /// <param name="hash">Instance Hash.</param>
204  /// <param name="meshDescriptions">MeshDescriptions.</param>
205  /// <param name="materials">Materials.</param>
206  IEnumerator CreateMeshes (int hash, Hydrogen.Threading.Jobs.MeshCombiner.MeshOutput[] meshOutputs)
207  {
208  // Make our meshes in Unity
209  for (int x = 0; x < meshOutputs.Length; x++) {
210  var meshObject = new GameObject ();
211 
212  var newMesh = Combiner.CreateMeshObject (meshOutputs [x], true);
213 
214  meshObject.name = newMesh.Mesh.name;
215  meshObject.AddComponent<MeshFilter> ().sharedMesh = newMesh.Mesh;
216  meshObject.AddComponent<MeshRenderer> ().sharedMaterials = newMesh.Materials;
217 
218  if (_parentLookup.ContainsKey (hash)) {
219  meshObject.transform.parent = _parentLookup [hash];
220  }
221 
222  meshObject.transform.position = Vector3.zero;
223  meshObject.transform.rotation = Quaternion.identity;
224 
225 
226  // Fake Unity Threading
227  if (x > 0 && x % ThrottleRate == 0) {
228  yield return new WaitForEndOfFrame ();
229  }
230  }
231 
232  if (_parentLookup.ContainsKey (hash)) {
233  _parentLookup.Remove (hash);
234  }
235 
236  // Clear previous data (for demonstration purposes)
237  // It could be useful to keep some mesh data in already parsed, then you could use the RemoveMesh function
238  // to remove ones that you want changed, without having to reparse mesh data.
239  Combiner.ClearMeshes ();
240  }
241 
242  /// <summary>
243  /// Unity's LateUpdate Event
244  /// </summary>
245  void LateUpdate ()
246  {
247  // If we have a MeshCombiner lets run the Check()
248  if (_threadRunning) {
249  // Funny thing about this method of doing this; lots of Thread based solutions in Unity have an
250  // elaborate manager that does this for you ... just saying.
251  Combiner.Check ();
252  }
253  }
254 }
A Multi-Threaded Mesh Combiner that runs in another thread. (Yes! It is just that cool!) ...
Definition: MeshCombiner.cs:38
A drop in implementation of how to interact with the Hydrogen.Threading.Jobs.MeshCombiner. This is meant really as an example of one way of using it, but you will probably want to create your own method to further optimize the workflow.
void Combine(GameObject rootObject, Transform outputParent, bool disableRootObject)
Combine all active meshes under the root object.
bool Persistent
Should this input manager survive scene switches?
static bool Exists()
Does an Input Manager already exist?
int ThrottleRate
This is used in our example to throttle things a bit when accessing Unity objects.
void Awake()
Unity&#39;s Awake Event
Hydrogen.Threading.Jobs.MeshCombiner Combiner
An instance of the MeshCombiner.
void ThreadCallback(int hash, Hydrogen.Threading.Jobs.MeshCombiner.MeshOutput[] meshOutputs)
This function is called in the example after the MeshCombiner has processed the meshes, it starts a Coroutine to create the actual meshes based on the flat data. This is the most optimal way to do this sadly as we cannot create or touch Unity based meshes outside of the main thread.
static hMeshCombiner Instance
Gets the input manager instance, creating one if none is found.