@@ -155,86 +155,267 @@ public static FbxRepresentation Find(FbxRepresentation rep, string key) {
155
155
}
156
156
157
157
/// <summary>
158
- /// Recursively perform the update.
158
+ /// This class is responsible for figuring out what work needs to be done
159
+ /// during an update. It's also responsible for actually doing the work.
159
160
///
160
- /// In terms of a 3-way merge, "oldFbx" is the "base".
161
- /// "newFbx" and "prefabInstance" are "theirs" and "ours".
161
+ /// Key assumption: upon importing, the names in the fbx model are unique.
162
162
///
163
- /// Whether the fbx or the prefab counts as "ours" depends on who the
164
- /// user is: a 3d modeler that updated the FBX, or the game programmer
165
- /// who's updating the prefab.
166
- /// So we don't use 3-way merge terminology.
163
+ /// TODO: handle conflicts. For now, we just clobber the prefab.
167
164
/// </summary>
168
- public static void TreeDiff ( FbxRepresentation oldFbx ,
169
- Transform newFbx ,
170
- Transform prefabInstance ,
171
- string indent = "" )
165
+ public class UpdateList
172
166
{
173
- // TODO: a better tree diff algorithm, e.g. Demaine et al 2009.
174
- //
175
- // For now we half-ass it: there's no possibility of handling
176
- // renaming other than delete-and-add. Demaine handles it.
177
- // There's also no possibility of nicely handling hierarchy
178
- // changes, but that's essentially impossible (NP-hard, no PTAS),
179
- // so it's not likely worth even trying unless we feel like doing
180
- // serious algorithmics.
181
- // 1. For each child in the union of the old, new, and prefab:
182
- // 7- old and new and prefab => recur.
183
- // 6- old and new and !prefab => skip (deleted by user)
184
- // 5- old and !new and prefab => delete from prefab
185
- // 4- old and !new and !prefab => skip, we happen to match
186
- // 3- !old and new and prefab => recur, we happen to match
187
- // 2- !old and new and !prefab => instantiate the new into the prefab
188
- // 1- !old and !new and prefab => skip (added by user)
189
- // 0- !old and !new and !prefab => not possible given the loop
190
- var names = FbxRepresentation . CopyKeySet ( oldFbx ) ;
191
- foreach ( Transform child in newFbx ) { names . Add ( child . name ) ; }
192
- foreach ( Transform child in prefabInstance ) { names . Add ( child . name ) ; }
193
-
194
- indent += " " ;
195
- foreach ( var name in names ) {
196
- var oldChild = FbxRepresentation . Find ( oldFbx , name ) ;
197
- var newChild = newFbx . Find ( name ) ;
198
- var prefabChild = prefabInstance . Find ( name ) ;
199
- int index = ( ( oldChild == null ) ? 0 : 4 )
200
- | ( ( newChild == null ) ? 0 : 2 )
201
- | ( ( prefabChild == null ) ? 0 : 1 ) ;
202
- switch ( index ) {
203
- case 7 :
204
- //Debug.Log(indent + "recur into " + name);
205
- TreeDiff ( oldChild , newChild , prefabChild , indent ) ;
206
- break ;
207
- case 6 :
208
- //Debug.Log(indent + "skip user-deleted " + name);
209
- break ;
210
- case 5 :
211
- //Debug.Log(indent + "delete " + name);
212
- GameObject . DestroyImmediate ( prefabChild . gameObject ) ;
213
- break ;
214
- case 4 :
215
- //Debug.Log(indent + "skip deleted in both new and prefabInstance " + name);
216
- break ;
217
- case 3 :
218
- //Debug.Log(indent + "accidental match; recur into " + name);
219
- TreeDiff ( oldChild , newChild , prefabChild , indent ) ;
220
- break ;
221
- case 2 :
222
- //Debug.Log(indent + "instantiate into prefabInstance " + name);
223
- prefabChild = GameObject . Instantiate ( newChild ) ;
224
- prefabChild . parent = prefabInstance ;
225
- prefabChild . name = newChild . name ;
226
- prefabChild . localPosition = newChild . localPosition ;
227
- prefabChild . localRotation = newChild . localRotation ;
228
- prefabChild . localScale = newChild . localScale ;
229
- break ;
230
- case 1 :
231
- //Debug.Log(indent + "skip user-added node " + name);
232
- break ;
233
- default :
234
- // This shouldn't happen.
235
- throw new System . NotImplementedException ( ) ;
167
+
168
+ // We build up a flat list of names for the nodes of the old fbx,
169
+ // the new fbx, and the prefab. We also figure out the parents.
170
+ class Data {
171
+ Dictionary < string , string > m_parents ;
172
+
173
+ public Data ( ) {
174
+ m_parents = new Dictionary < string , string > ( ) ;
175
+ }
176
+
177
+ public void AddNode ( string name , string parent ) {
178
+ m_parents . Add ( name , parent ) ;
179
+ }
180
+
181
+ public bool HasNode ( string name ) {
182
+ return m_parents . ContainsKey ( name ) ;
183
+ }
184
+
185
+ public string GetParent ( string name ) {
186
+ string parent ;
187
+ if ( m_parents . TryGetValue ( name , out parent ) ) {
188
+ return parent ;
189
+ } else {
190
+ return null ;
191
+ }
192
+ }
193
+
194
+ public static HashSet < string > GetAllNames ( params Data [ ] data ) {
195
+ var names = new HashSet < string > ( ) ;
196
+ foreach ( var d in data ) {
197
+ foreach ( var k in d . m_parents . Keys ) {
198
+ names . Add ( k ) ;
199
+ }
200
+ }
201
+ return names ;
202
+ }
203
+ }
204
+
205
+ /// <summary>
206
+ /// Data for the hierarchy of the old fbx file, the new fbx file, and the prefab.
207
+ /// </summary>
208
+ Data m_old , m_new , m_prefab ;
209
+
210
+ /// <summary>
211
+ /// Names of all nodes -- old, new, or prefab.
212
+ /// </summary>
213
+ HashSet < string > m_allNames = new HashSet < string > ( ) ;
214
+
215
+ /// <summary>
216
+ /// Names of the new nodes to create in step 1.
217
+ /// </summary>
218
+ HashSet < string > m_nodesToCreate = new HashSet < string > ( ) ;
219
+
220
+ /// <summary>
221
+ /// Information about changes in parenting for step 2.
222
+ /// Map from name of node in the prefab to name of node in prefab or newNodes.
223
+ /// This is all the nodes in the prefab that need to be reparented.
224
+ /// </summary>
225
+ Dictionary < string , string > m_reparentings = new Dictionary < string , string > ( ) ;
226
+
227
+ /// <summary>
228
+ /// Names of the nodes to destroy in step 3.
229
+ /// Actually calculated early.
230
+ /// </summary>
231
+ HashSet < string > m_nodesToDestroy = new HashSet < string > ( ) ;
232
+
233
+ /// <summary>
234
+ /// Components to destroy in step 4a.
235
+ /// The string is the name in the prefab; we destroy the first
236
+ /// component that matches the type.
237
+ /// </summary>
238
+ //Dictionary<string, List<System.Type>> m_componentsToDestroy = new Dictionary<string, List<System.Type>>();
239
+
240
+ /// <summary>
241
+ /// List of components to update or create in steps 4b and 4c.
242
+ /// The string is the name in the prefab.
243
+ /// The component is a pointer to the component in the FBX.
244
+ /// If the component doesn't exist in the prefab, we create it.
245
+ /// If the component exists in the prefab, we update it.
246
+ /// TODO: handle conflicts!
247
+ /// </summary>
248
+ //Dictionary<string, List<Component>> m_componentsToUpdate = new Dictionary<string, List<Component>>();
249
+
250
+ void SetupOld ( FbxRepresentation oldFbx , string parent = null ) {
251
+ if ( m_old == null ) { m_old = new Data ( ) ; }
252
+ foreach ( var name in FbxRepresentation . CopyKeySet ( oldFbx ) ) {
253
+ m_old . AddNode ( name , parent ) ;
254
+ SetupOld ( FbxRepresentation . Find ( oldFbx , name ) , name ) ;
255
+ }
256
+ }
257
+
258
+ static void SetupFromTransform ( Data data , Transform xfo , string parent ) {
259
+ foreach ( Transform child in xfo ) {
260
+ data . AddNode ( child . name , parent ) ;
261
+ SetupFromTransform ( data , child , child . name ) ;
262
+ }
263
+ }
264
+
265
+ void SetupNew ( Transform newFbx ) {
266
+ m_new = new Data ( ) ;
267
+ SetupFromTransform ( m_new , newFbx , null ) ;
268
+ }
269
+
270
+ void SetupPrefab ( FbxPrefab prefab ) {
271
+ m_prefab = new Data ( ) ;
272
+ SetupFromTransform ( m_prefab , prefab . transform , null ) ;
273
+ }
274
+
275
+ void ClassifyDestroyCreateNodes ( )
276
+ {
277
+ foreach ( var name in m_allNames ) {
278
+ var isOld = m_old . HasNode ( name ) ;
279
+ var isNew = m_new . HasNode ( name ) ;
280
+ var isPrefab = m_prefab . HasNode ( name ) ;
281
+ if ( isOld && ! isNew && isPrefab ) {
282
+ // This node got deleted in the DCC, so delete it.
283
+ m_nodesToDestroy . Add ( name ) ;
284
+ } else if ( ! isOld && isNew && ! isPrefab ) {
285
+ // This node was created in the DCC but not in Unity, so create it.
286
+ m_nodesToCreate . Add ( name ) ;
287
+ }
288
+ }
289
+ }
290
+ bool ShouldDestroy ( string name ) {
291
+ return m_nodesToDestroy . Contains ( name ) ;
292
+ }
293
+ bool ShouldCreate ( string name ) {
294
+ return m_nodesToCreate . Contains ( name ) ;
295
+ }
296
+
297
+ /// <summary>
298
+ /// Discover what needs to happen.
299
+ ///
300
+ /// Four-step program:
301
+ /// 1. Figure out what nodes exist (we have that data, just need to
302
+ /// make it more convenient to query).
303
+ /// 2. Figure out what nodes we need to create, and what nodes we
304
+ /// need to destroy.
305
+ /// 3. Figure out what nodes we need to reparent.
306
+ /// todo 4. Figure out what nodes we need to update or add components to.
307
+ /// </summary>
308
+ public UpdateList (
309
+ FbxRepresentation oldFbx ,
310
+ Transform newFbx ,
311
+ FbxPrefab prefab )
312
+ {
313
+ SetupOld ( oldFbx ) ;
314
+ SetupNew ( newFbx ) ;
315
+ SetupPrefab ( prefab ) ;
316
+ m_allNames = Data . GetAllNames ( m_old , m_new , m_prefab ) ;
317
+
318
+ // Set up m_nodesToDestroy and m_nodesToCreate.
319
+ ClassifyDestroyCreateNodes ( ) ;
320
+
321
+ // Among prefab nodes we're not destroying, see if we need to change their parent.
322
+ // Cases for the parent:
323
+ // old new prefab
324
+ // a a a => no action
325
+ // a x a => doesn't matter (a is being destroyed)
326
+ // a b a => switch to b
327
+ // a b c => conflict! switch to b for now (todo!)
328
+ // x a x => create, and parent to a. This is the second loop below.
329
+ // x a a => no action
330
+ // x a b => conflict! switch to a for now (todo!)
331
+ // x x a => no action. Todo: what if a is being destroyed? conflict!
332
+ foreach ( var name in Data . GetAllNames ( m_prefab ) ) {
333
+ var prefabParent = m_prefab . GetParent ( name ) ;
334
+ var oldParent = m_old . GetParent ( name ) ;
335
+ var newParent = m_new . GetParent ( name ) ;
336
+
337
+ if ( oldParent != newParent && prefabParent != newParent ) {
338
+ // Conflict in this case, we'll need to resolve it:
339
+ // if (oldParent != prefabParent && !ShouldDestroy(prefabParent))
340
+
341
+ // But unconditionally switch to the new parent for
342
+ // now, unless we're already there.
343
+ m_reparentings . Add ( name , newParent ) ;
344
+ }
345
+ }
346
+ // All new nodes need to be reparented (we didn't loop over them because we
347
+ // only looped over the stuff in the prefab now).
348
+ foreach ( var name in m_nodesToCreate ) {
349
+ m_reparentings . Add ( name , m_new . GetParent ( name ) ) ;
236
350
}
237
351
}
352
+
353
+ public bool NeedsUpdates ( ) {
354
+ return m_nodesToDestroy . Count > 0
355
+ || m_nodesToCreate . Count > 0
356
+ || m_reparentings . Count > 0
357
+ // || m_componentsToDestroy.Count > 0
358
+ // || m_comopnentsToUpdate.Count > 0
359
+ ;
360
+ }
361
+
362
+ /// <summary>
363
+ /// Then we act -- in a slightly different order:
364
+ /// 1. Create all the new nodes we need to create.
365
+ /// 2. Reparent as needed.
366
+ /// 3. Delete the nodes that are no longer needed.
367
+ /// todo 4. Update the components:
368
+ /// 4a. delete components no longer used
369
+ /// 4b. create new components
370
+ /// 4c. update component values
371
+ /// (A) and (B) are largely about meshfilter/meshrenderer,
372
+ /// (C) is about transforms (and materials?)
373
+ /// </summary>
374
+ public void ImplementUpdates ( FbxPrefab prefabInstance )
375
+ {
376
+ // Gather up all the nodes in the prefab.
377
+ var prefabNodes = new Dictionary < string , Transform > ( ) ;
378
+ foreach ( var node in prefabInstance . GetComponentsInChildren < Transform > ( ) ) {
379
+ prefabNodes . Add ( node . name , node ) ;
380
+ }
381
+
382
+ // Create new nodes.
383
+ foreach ( var name in m_nodesToCreate ) {
384
+ prefabNodes . Add ( name , new GameObject ( name ) . transform ) ;
385
+ Debug . Log ( "Created " + name ) ;
386
+ }
387
+
388
+ // Implement the reparenting in two phases to avoid making loops, e.g.
389
+ // if we're flipping from a -> b to b -> a, we don't want to
390
+ // have a->b->a in the intermediate stage.
391
+
392
+ // First set the parents to null.
393
+ foreach ( var kvp in m_reparentings ) {
394
+ var name = kvp . Key ;
395
+ prefabNodes [ name ] . parent = null ;
396
+ }
397
+
398
+ // Then set the parents to the intended value.
399
+ foreach ( var kvp in m_reparentings ) {
400
+ var name = kvp . Key ;
401
+ var parent = kvp . Value ;
402
+ Transform parentNode ;
403
+ if ( parent == null ) {
404
+ parentNode = prefabInstance . transform ;
405
+ } else {
406
+ parentNode = prefabNodes [ parent ] ;
407
+ }
408
+ prefabNodes [ name ] . parent = parentNode ;
409
+ Debug . Log ( "Parented " + name + " under " + parentNode . name ) ;
410
+ }
411
+
412
+ // Destroy the old nodes.
413
+ foreach ( var toDestroy in m_nodesToDestroy ) {
414
+ GameObject . DestroyImmediate ( prefabNodes [ toDestroy ] . gameObject ) ;
415
+ }
416
+
417
+ // TODO: update components!
418
+ }
238
419
}
239
420
240
421
/// <summary>
@@ -266,29 +447,34 @@ void CompareAndUpdate()
266
447
return ;
267
448
}
268
449
269
- // todo: only instantiate if there's a change
270
- var oldRep = GetFbxHistory ( ) ;
271
- if ( oldRep == null || oldRep . c == null ) { oldRep = null ; }
450
+ // First write down what we want to do.
451
+ var updates = new UpdateList ( GetFbxHistory ( ) , m_fbxModel . transform , this ) ;
452
+
453
+ // If we don't need to do anything, jump out now.
454
+ if ( ! updates . NeedsUpdates ( ) ) {
455
+ return ;
456
+ }
272
457
273
- // Instantiate the prefab and compare & update .
458
+ // We want to do something, so instantiate the prefab, work on the instance, then copy back .
274
459
var prefabInstance = UnityEditor . PrefabUtility . InstantiatePrefab ( this . gameObject ) as GameObject ;
275
460
if ( ! prefabInstance ) {
276
461
throw new System . Exception ( string . Format ( "Failed to instantiate {0}; is it really a prefab?" ,
277
462
this . gameObject ) ) ;
278
463
}
279
- TreeDiff ( oldRep , m_fbxModel . transform , prefabInstance . transform ) ;
464
+ var fbxPrefab = prefabInstance . GetComponent < FbxPrefab > ( ) ;
465
+
466
+ updates . ImplementUpdates ( fbxPrefab ) ;
280
467
281
468
// Update the representation of the history to match the new fbx.
282
- var fbxPrefab = prefabInstance . GetComponent < FbxPrefab > ( ) ;
283
469
var newFbxRep = FbxRepresentation . FromTransform ( m_fbxModel . transform ) ;
284
470
var newFbxRepString = newFbxRep . ToJson ( ) ;
285
471
fbxPrefab . m_fbxHistory = newFbxRepString ;
286
472
287
473
// Save the changes back to the prefab.
288
- UnityEditor . PrefabUtility . ReplacePrefab ( prefabInstance . gameObject , this . transform ) ;
474
+ UnityEditor . PrefabUtility . ReplacePrefab ( prefabInstance , this . transform ) ;
289
475
290
476
// Destroy the prefabInstance.
291
- GameObject . DestroyImmediate ( prefabInstance . gameObject ) ;
477
+ GameObject . DestroyImmediate ( prefabInstance ) ;
292
478
}
293
479
294
480
/// <summary>
0 commit comments