Sfoglia il codice sorgente

SDK
- Clean up AssetDataObject / SpatialAssetDataObject / AssetData, add javadoc
- Add AssetDataPropertyChangeListener interface and functionality
- Improve storage of ORIGINAL_XXX data
- Add SpatialUtil

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10303 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

nor..67 12 anni fa
parent
commit
d4657268b4

+ 57 - 4
jme3-core/src/com/jme3/gde/core/assets/AssetData.java

@@ -32,13 +32,16 @@
 package com.jme3.gde.core.assets;
 
 import com.jme3.asset.AssetKey;
+import com.jme3.export.Savable;
 import java.io.BufferedOutputStream;
 import java.io.BufferedInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
 import java.util.logging.Level;
@@ -62,6 +65,7 @@ import org.openide.util.Mutex.Action;
 public class AssetData {
 
     private static final Logger logger = Logger.getLogger(AssetData.class.getName());
+    private final List<AssetDataPropertyChangeListener> listeners = new ArrayList<AssetDataPropertyChangeListener>();
     private final Mutex propsMutex = new Mutex();
     private final Properties props = new Properties();
     private AssetDataObject file;
@@ -81,14 +85,28 @@ public class AssetData {
         this.extension = extension;
     }
 
-    public void setExtension(String extension) {
-        this.extension = extension;
-    }
+    /**
+     * Sets the extension of the assetData properties file, normally not
+     * necessary to use this, it will be .[originalsuffix]data, for example
+     * .j3odata.
+     *
+     * @param extension
+     */
+//    public void setExtension(String extension) {
+//        this.extension = extension;
+//    }
 
     public AssetKey<?> getAssetKey() {
         return file.getAssetKey();
     }
 
+    /**
+     * Applies the supplied keys data to the assets assetKey so it will be
+     * loaded with these settings next time loadAsset is actually loading the
+     * asset from the ProjectAssetManager.
+     *
+     * @param key
+     */
     public void setAssetKey(AssetKey key) {
         file.setAssetKeyData(key);
     }
@@ -101,10 +119,23 @@ public class AssetData {
         file.setSaveCookie(cookie);
     }
 
-    public Object loadAsset() {
+    /**
+     * Loads the asset from the DataObject via the ProjectAssetManager in the
+     * lookup. Returns the currently loaded asset when it has been loaded
+     * already, close the asset using closeAsset().
+     *
+     * @return
+     */
+    public Savable loadAsset() {
         return file.loadAsset();
     }
 
+    /**
+     * Saves this asset, when a saveExtension is set, saves it as a brother file
+     * with that extension.
+     *
+     * @throws IOException
+     */
     public void saveAsset() throws IOException {
         file.saveAsset();
     }
@@ -152,6 +183,7 @@ public class AssetData {
             }
         });
         writeProperties();
+        notifyListeners(key, ret, value);
         return ret;
     }
 
@@ -233,4 +265,25 @@ public class AssetData {
             }
         });
     }
+
+    protected void notifyListeners(String property, String before, String after) {
+        synchronized (listeners) {
+            for (Iterator<AssetDataPropertyChangeListener> it = listeners.iterator(); it.hasNext();) {
+                AssetDataPropertyChangeListener assetDataPropertyChangeListener = it.next();
+                assetDataPropertyChangeListener.assetDataPropertyChanged(property, before, after);
+            }
+        }
+    }
+
+    public void addPropertyChangeListener(AssetDataPropertyChangeListener listener) {
+        synchronized (listeners) {
+            listeners.add(listener);
+        }
+    }
+
+    public void removePropertyChangeListener(AssetDataPropertyChangeListener listener) {
+        synchronized (listeners) {
+            listeners.remove(listener);
+        }
+    }
 }

+ 81 - 21
jme3-core/src/com/jme3/gde/core/assets/AssetDataObject.java

@@ -33,13 +33,16 @@ package com.jme3.gde.core.assets;
 
 import com.jme3.asset.AssetEventListener;
 import com.jme3.asset.AssetKey;
+import com.jme3.asset.BlenderKey;
 import com.jme3.export.Savable;
 import com.jme3.export.binary.BinaryExporter;
 import com.jme3.gde.core.scene.ApplicationLogHandler.LogLevel;
 import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.scene.Spatial;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -75,8 +78,15 @@ import org.openide.util.lookup.ProxyLookup;
 public class AssetDataObject extends MultiDataObject {
 
     protected static final Logger logger = Logger.getLogger(AssetDataObject.class.getName());
-    protected final Lookup lookup;
     protected final InstanceContent lookupContents = new InstanceContent();
+    protected final AbstractLookup contentLookup;
+    protected final Lookup lookup;
+    protected final AssetData assetData;
+    protected final ProjectAssetManager assetManager;
+    protected final AssetListListener listListener;
+    protected final List<FileObject> assetList = new LinkedList<FileObject>();
+    protected final List<AssetKey> assetKeyList = new LinkedList<AssetKey>();
+    protected final List<AssetKey> failedList = new LinkedList<AssetKey>();
     protected SaveCookie saveCookie = new SaveCookie() {
         public void save() throws IOException {
             //TODO: On OpenGL thread? -- safest way.. with get()?
@@ -92,23 +102,20 @@ public class AssetDataObject extends MultiDataObject {
     protected AssetKey assetKey;
     protected Savable savable;
     protected String saveExtension;
-    protected AbstractLookup contentLookup;
-    protected AssetListListener listListener;
-    protected List<FileObject> assetList = new LinkedList<FileObject>();
-    protected List<AssetKey> assetKeyList = new LinkedList<AssetKey>();
-    protected List<AssetKey> failedList = new LinkedList<AssetKey>();
 
     public AssetDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
         super(pf, loader);
-        contentLookup = new AbstractLookup(getLookupContents());
-        lookupContents.add(new AssetData(this));
+        contentLookup = new AbstractLookup(lookupContents);
+        assetData = new AssetData(this);
+        lookupContents.add(assetData);
         lookup = new ProxyLookup(getCookieSet().getLookup(), contentLookup);
         listListener = new AssetListListener(this, assetList, assetKeyList, failedList);
+        assetManager = findAssetManager();
+        //assign savecookie (same as method)
         setSaveCookie(saveCookie);
-        findAssetManager();
     }
 
-    protected void findAssetManager() {
+    private ProjectAssetManager findAssetManager() {
         FileObject file = getPrimaryFile();
         ProjectManager pm = ProjectManager.getDefault();
         while (file != null) {
@@ -119,7 +126,7 @@ public class AssetDataObject extends MultiDataObject {
                         ProjectAssetManager mgr = project.getLookup().lookup(ProjectAssetManager.class);
                         if (mgr != null) {
                             getLookupContents().add(mgr);
-                            return;
+                            return mgr;
                         }
                     }
                 } catch (IOException ex) {
@@ -128,7 +135,7 @@ public class AssetDataObject extends MultiDataObject {
             }
             file = file.getParent();
         }
-//        getLookupContents().add(new ProjectAssetManager(file.getParent()));
+        return null;
     }
 
     @Override
@@ -163,9 +170,16 @@ public class AssetDataObject extends MultiDataObject {
         setModified(false);
     }
 
+    /**
+     * Loads the asset from the DataObject via the ProjectAssetManager in the
+     * lookup. Returns the currently loaded asset when it has been loaded
+     * already, close the asset using closeAsset().
+     *
+     * @return
+     */
     public synchronized Savable loadAsset() {
-        if (isModified() && savable != null) {
-            return savable;
+        if (savable != null) {
+            return (Spatial) savable;
         }
         ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
         if (mgr == null) {
@@ -190,6 +204,12 @@ public class AssetDataObject extends MultiDataObject {
         return savable;
     }
 
+    /**
+     * Saves this asset, when a saveExtension is set, saves it as a brother file
+     * with that extension.
+     *
+     * @throws IOException
+     */
     public synchronized void saveAsset() throws IOException {
         if (savable == null) {
             logger.log(Level.WARNING, "Trying to write asset failed, asset data null!\nImport failed?");
@@ -222,14 +242,47 @@ public class AssetDataObject extends MultiDataObject {
             }
         }
         progressHandle.finish();
-        logger.log(LogLevel.USERINFO, "File {0} saved successfully", getPrimaryFile().getNameExt());
         setModified(false);
+        logger.log(LogLevel.USERINFO, "File {0} saved successfully", getPrimaryFile().getNameExt());
     }
 
+    /**
+     * Closes this asset so that loadAsset will cause it to be loaded
+     */
     public synchronized void closeAsset() {
+        ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
+        if (mgr != null) {
+            logger.log(Level.INFO, "Closing asset {0}, deleting from cache.", getName());
+            mgr.deleteFromCache(getAssetKey());
+            //delete referenced assets too
+            for (Iterator<AssetKey> it = assetKeyList.iterator(); it.hasNext();) {
+                AssetKey assetKey1 = it.next();
+                mgr.deleteFromCache(assetKey1);
+            }
+        } else {
+            logger.log(Level.WARNING, "Closing asset {0} with no ProjectAssetManager assigned..?", getName());
+        }
         savable = null;
     }
 
+    /**
+     * Returns the AssetKey of this asset type. When extending AssetDataObject
+     * or a subtype the class should override this so the key type and
+     * properties can be recognized properly:
+     * <pre>
+     * public synchronized MyKeyType getAssetKey() {
+     *     //return key if already set
+     *     if(super.getAssetKey() instanceof MyKeyType){
+     *         return (MyKeyType)assetKey;
+     *     }
+     *     //set own key type and return
+     *     assetKey = new MyKeyType(super.getAssetKey().getName());
+     *     return (MyKeyType)assetKey;
+     * }
+     * </pre>
+     *
+     * @return
+     */
     public synchronized AssetKey<?> getAssetKey() {
         if (assetKey == null) {
             ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
@@ -242,6 +295,13 @@ public class AssetDataObject extends MultiDataObject {
         return assetKey;
     }
 
+    /**
+     * Applies the supplied keys data to the assets assetKey so it will be
+     * loaded with these settings next time loadAsset is actually loading the
+     * asset from the ProjectAssetManager.
+     *
+     * @param key
+     */
     public synchronized void setAssetKeyData(AssetKey key) {
         try {
             BeanUtils.copyProperties(getAssetKey(), key);
@@ -287,9 +347,9 @@ public class AssetDataObject extends MultiDataObject {
             if (pm == null || loadingThread != Thread.currentThread()) {
                 return;
             }
-            FileObject obj = pm.getAssetFileObject(ak);
-            if (obj != null && !assetList.contains(obj)) {
-                assetList.add(obj);
+            FileObject fObj = pm.getAssetFileObject(ak);
+            if (fObj != null && !assetList.contains(fObj)) {
+                assetList.add(fObj);
                 assetKeyList.add(ak);
             }
         }
@@ -299,9 +359,9 @@ public class AssetDataObject extends MultiDataObject {
             if (pm == null || loadingThread != Thread.currentThread()) {
                 return;
             }
-            FileObject obj = pm.getAssetFileObject(ak1);
-            if (obj != null && assetList.contains(obj)) {
-                assetList.remove(obj);
+            FileObject fObj = pm.getAssetFileObject(ak1);
+            if (fObj != null && assetList.contains(fObj)) {
+                assetList.remove(fObj);
                 assetKeyList.remove(ak1);
             }
             if (!failedList.contains(ak1)) {

+ 43 - 0
jme3-core/src/com/jme3/gde/core/assets/AssetDataPropertyChangeListener.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2003-2012 jMonkeyEngine
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.assets;
+
+/**
+ * Allows listening for property changes in AssetData objects. The call is not
+ * necessarily coming from the main AWT thread but from the import thread.
+ *
+ * @author normenhansen
+ */
+public interface AssetDataPropertyChangeListener {
+
+    public void assetDataPropertyChanged(String property, String before, String after);
+}

+ 8 - 51
jme3-core/src/com/jme3/gde/core/assets/SpatialAssetDataObject.java

@@ -33,11 +33,9 @@ package com.jme3.gde.core.assets;
 
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.ModelKey;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.gde.core.util.SpatialUtil;
 import com.jme3.scene.Spatial;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.openide.DialogDisplayer;
@@ -81,7 +79,7 @@ public class SpatialAssetDataObject extends AssetDataObject {
 
     @Override
     public synchronized Spatial loadAsset() {
-        if (isModified() && savable != null) {
+        if (savable != null) {
             return (Spatial) savable;
         }
         ProjectAssetManager mgr = getLookup().lookup(ProjectAssetManager.class);
@@ -98,7 +96,7 @@ public class SpatialAssetDataObject extends AssetDataObject {
             listListener.stop();
             savable = spatial;
             if (!(this instanceof BinaryModelDataObject)) {
-                storeOriginalPathUserData();
+                SpatialUtil.storeOriginalPathUserData(spatial);
             }
             lock.releaseLock();
             return spatial;
@@ -135,56 +133,15 @@ public class SpatialAssetDataObject extends AssetDataObject {
             AssetData properties = targetModel.getLookup().lookup(AssetData.class);
             if (properties != null) {
                 if (properties.getProperty("ORIGINAL_PATH") == null) {
-                    properties.setProperty("ORIGINAL_PATH", mgr.getRelativeAssetPath(outFile.getPath()));
+                    String path = mgr.getRelativeAssetPath(getPrimaryFile().getPath());
+                    properties.setProperty("ORIGINAL_PATH", path);
+                    logger.log(Level.INFO, "Set original path for {0} to {1}", new Object[]{getName(), path});
                 }
+            } else {
+                logger.log(Level.WARNING, "New object {0} has no AssetData?", getName());
             }
         } catch (Exception ex) {
             Exceptions.printStackTrace(ex);
         }
     }
-
-    /**
-     * Stores ORIGINAL_NAME and ORIGINAL_PATH UserData to all Geometry in
-     * loaded spatial
-     */
-    protected void storeOriginalPathUserData() {
-        final ArrayList<String> geomMap = new ArrayList<String>();
-        Spatial spat = (Spatial) savable;
-        if (spat != null) {
-            spat.depthFirstTraversal(new SceneGraphVisitorAdapter() {
-                @Override
-                public void visit(Geometry geom) {
-                    StringBuilder geometryIdentifier = new StringBuilder();
-                    Spatial curSpat = geom;
-                    String geomName = curSpat.getName();
-                    if (geomName == null) {
-                        logger.log(Level.WARNING, "Null geometry name!");
-                        geomName = "null";
-                    }
-                    geom.setUserData("ORIGINAL_NAME", geomName);
-                    logger.log(Level.FINE, "Set ORIGINAL_NAME for {0}", geomName);
-                    while (curSpat != null) {
-                        String name = curSpat.getName();
-                        if (name == null) {
-                            logger.log(Level.WARNING, "Null spatial name!");
-                            name = "null";
-                        }
-                        geometryIdentifier.insert(0, name);
-                        geometryIdentifier.insert(0, '/');
-                        curSpat = curSpat.getParent();
-                    }
-                    String id = geometryIdentifier.toString();
-                    if (geomMap.contains(id)) {
-                        logger.log(Level.WARNING, "Cannot create unique name for Geometry {0}: {1}", new Object[]{geom, id});
-                    }
-                    geomMap.add(id);
-                    geom.setUserData("ORIGINAL_PATH", id);
-                    logger.log(Level.FINE, "Set ORIGINAL_PATH for {0}", id);
-                    super.visit(geom);
-                }
-            });
-        } else {
-            logger.log(Level.SEVERE, "No geometry available when trying to scan initial geometry configuration");
-        }
-    }
 }

+ 216 - 0
jme3-core/src/com/jme3/gde/core/util/SpatialUtil.java

@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2003-2012 jMonkeyEngine
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.gde.core.util;
+
+import com.jme3.gde.core.scene.ApplicationLogHandler.LogLevel;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.SceneGraphVisitor;
+import com.jme3.scene.SceneGraphVisitorAdapter;
+import com.jme3.scene.Spatial;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Various utilities, mostly for operating on Spatials recursively.
+ *
+ * @author normenhansen
+ */
+public class SpatialUtil {
+
+    private static final Logger logger = Logger.getLogger(SpatialUtil.class.getName());
+    //TODO: use these variables
+    public static final String ORIGINAL_NAME = "ORIGINAL_NAME";
+    public static final String ORIGINAL_PATH = "ORIGINAL_PATH";
+
+    /**
+     * Gets a "pathname" for the given Spatial, combines the Spatials and
+     * parents names to make a long name. This "path" is stored in geometry
+     * after the first import for example.
+     *
+     * @param spat
+     * @return
+     */
+    public static String getSpatialPath(Spatial spat) {
+        StringBuilder geometryIdentifier = new StringBuilder();
+        while (spat != null) {
+            String name = spat.getName();
+            if (name == null) {
+                logger.log(Level.WARNING, "Null spatial name!");
+                name = "null";
+            }
+            geometryIdentifier.insert(0, name);
+            geometryIdentifier.insert(0, '/');
+            spat = spat.getParent();
+        }
+        String id = geometryIdentifier.toString();
+        return id;
+    }
+
+    /**
+     * Stores ORIGINAL_NAME and ORIGINAL_PATH UserData to all Geometry in loaded
+     * spatial
+     *
+     * @param spat
+     */
+    public static void storeOriginalPathUserData(Spatial spat) {
+        //TODO: only stores for geometry atm
+        final ArrayList<String> geomMap = new ArrayList<String>();
+        if (spat != null) {
+            spat.depthFirstTraversal(new SceneGraphVisitorAdapter() {
+                @Override
+                public void visit(Geometry geom) {
+                    Spatial curSpat = geom;
+                    String geomName = curSpat.getName();
+                    if (geomName == null) {
+                        logger.log(Level.WARNING, "Null geometry name!");
+                        geomName = "null";
+                    }
+                    geom.setUserData("ORIGINAL_NAME", geomName);
+                    logger.log(Level.FINE, "Set ORIGINAL_NAME for {0}", geomName);
+                    String id = SpatialUtil.getSpatialPath(curSpat);
+                    if (geomMap.contains(id)) {
+                        logger.log(Level.WARNING, "Cannot create unique name for Geometry {0}: {1}", new Object[]{geom, id});
+                    }
+                    geomMap.add(id);
+                    geom.setUserData("ORIGINAL_PATH", id);
+                    logger.log(Level.FINE, "Set ORIGINAL_PATH for {0}", id);
+                    super.visit(geom);
+                }
+            });
+        } else {
+            logger.log(Level.SEVERE, "No geometry available when trying to scan initial geometry configuration");
+        }
+    }
+
+    /**
+     * Finds a previously marked spatial in the supplied root Spatial, creates
+     * the name and path to be looked for from the given needle Spatial.
+     *
+     * @param root
+     * @param needle
+     * @return
+     */
+    public static Spatial findTaggedSpatial(final Spatial root, final Spatial needle) {
+        if (needle == null) {
+            logger.log(Level.WARNING, "Trying to find null needle for {0}", root);
+            return null;
+        }
+        final String name = needle.getName();
+        final String path = getSpatialPath(needle);
+        if (name == null || path == null) {
+            logger.log(Level.INFO, "Trying to find tagged spatial with null name spatial for {0}.", root);
+        }
+        final Class clazz = needle.getClass();
+        String rootName = root.getUserData("ORIGINAL_NAME");
+        String rootPath = root.getUserData("ORIGINAL_PATH");
+        if (name.equals(rootName) && path.equals(rootPath)) {
+            return root;
+        }
+        final SpatialHolder holder = new SpatialHolder();
+        root.depthFirstTraversal(new SceneGraphVisitor() {
+            public void visit(Spatial spatial) {
+                String spName = spatial.getUserData("ORIGINAL_NAME");
+                String spPath = spatial.getUserData("ORIGINAL_PATH");
+                if (name.equals(spName) && path.equals(spPath) && clazz.isInstance(spatial)) {
+                    if (holder.spatial == null) {
+                        holder.spatial = spatial;
+                    } else {
+                        logger.log(Level.WARNING, "Found spatial {0} twice in {1}", new Object[]{path, root});
+                    }
+                }
+            }
+        });
+        return holder.spatial;
+    }
+
+    /**
+     * Finds a spatial in the given Spatial tree with the specified name and
+     * path, the path and name are constructed from the given (sub-)spatial(s)
+     * and is not read from the UserData of the objects. This is mainly used to
+     * check if the original spatial still exists in the original file.
+     *
+     * @param root
+     * @param name
+     * @param path
+     */
+    public static Spatial findSpatial(final Spatial root, final String name, final String path) {
+        if (name.equals(root.getName()) && getSpatialPath(root).equals(path)) {
+            return root;
+        }
+        final SpatialHolder holder = new SpatialHolder();
+        root.depthFirstTraversal(new SceneGraphVisitor() {
+            public void visit(Spatial spatial) {
+                if (name.equals(spatial.getName()) && getSpatialPath(spatial).equals(path)) {
+                    if (holder.spatial == null) {
+                        holder.spatial = spatial;
+                    } else {
+                        logger.log(Level.WARNING, "Found spatial {0} twice in {1}", new Object[]{path, root});
+                    }
+                }
+            }
+        });
+        return holder.spatial;
+    }
+
+    public static void updateOriginalMeshData(final Spatial root, final Spatial original) {
+        original.depthFirstTraversal(new SceneGraphVisitorAdapter() {
+            @Override
+            public void visit(Geometry geom) {
+                //will always return same class type, so casting is safe
+                Geometry spat = (Geometry) findTaggedSpatial(root, geom);
+                if (spat != null) {
+                    spat.setMesh(geom.getMesh().deepClone());
+                    logger.log(LogLevel.USERINFO, "Updated mesh for geometry {0}", geom.getName());
+                } else {
+//                    addNewOriginal()
+                }
+            }
+        });
+        return;
+    }
+
+    private void addNewOriginalGeometry(final Spatial root, final Geometry original) {
+
+        return;
+    }
+
+    public void clearRemovedOriginals(final Spatial root, final Spatial original) {
+
+        return;
+    }
+
+    private static class SpatialHolder {
+
+        Spatial spatial;
+    }
+}