diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/Asset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/Asset.java index 924a1d298..1e6838e2d 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/Asset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/Asset.java @@ -19,6 +19,7 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Disposable; import com.mbrlabs.mundus.commons.assets.meta.Meta; +import com.mbrlabs.mundus.commons.scene3d.components.AssetUsage; import java.util.Map; @@ -33,7 +34,7 @@ * @author Marcus Brummer * @version 01-10-2016 */ -public abstract class Asset implements Disposable { +public abstract class Asset implements Disposable, AssetUsage { protected FileHandle file; protected Meta meta; diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java index 307fb437b..509def189 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/MaterialAsset.java @@ -36,7 +36,7 @@ */ public class MaterialAsset extends Asset { - private static final ObjectMap MAP = new ObjectMap(); + private static final ObjectMap MAP = new ObjectMap<>(); public static final String EXTENSION = ".mat"; @@ -178,4 +178,14 @@ public void dispose() { // nothing to dispose } + @Override + public boolean usesAsset(Asset assetToCheck) { + if (assetToCheck instanceof TextureAsset) { + boolean diffuseMatch = diffuseTexture != null && diffuseTexture.getFile().path().equals(assetToCheck.getFile().path()); + boolean normalMatch = normalMap != null && normalMap.getFile().path().equals(assetToCheck.getFile().path()); + + return diffuseMatch || normalMatch; + } + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/ModelAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/ModelAsset.java index cbb978450..85c80c48c 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/ModelAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/ModelAsset.java @@ -95,4 +95,19 @@ public void dispose() { } } + @Override + public boolean usesAsset(Asset assetToCheck) { + // if it's a MaterialAsset compare to the models materials + if (assetToCheck instanceof MaterialAsset) { + return defaultMaterials.containsValue(assetToCheck); + } + + // check if the materials use the asset, like a texture asset + for (Map.Entry stringMaterialAssetEntry : defaultMaterials.entrySet()) { + if (stringMaterialAssetEntry.getValue().usesAsset(assetToCheck)) { + return true; + } + } + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/PixmapTextureAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/PixmapTextureAsset.java index 221863cbb..d6e95d9e6 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/PixmapTextureAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/PixmapTextureAsset.java @@ -66,4 +66,11 @@ public void dispose() { texture.dispose(); } + @Override + public boolean usesAsset(Asset assetToCheck) { + if (assetToCheck instanceof TextureAsset){ + return texture == ((TextureAsset) assetToCheck).getTexture(); + } + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java index b4b090436..bdc6fb153 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java @@ -252,4 +252,21 @@ public void updateUvScale(Vector2 uvScale) { terrain.update(); meta.getTerrain().setUv(uvScale.x); } + + @Override + public boolean usesAsset(Asset assetToCheck) { + if (assetToCheck == splatmap) + return true; + + // does the splatmap use the asset + if (assetToCheck instanceof TextureAsset) { + for (Map.Entry texture : terrain.getTerrainTexture().getTextures().entrySet()) { + if (texture.getValue().texture.getFile().path().equals(assetToCheck.getFile().path())) { + return true; + } + } + } + + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/TextureAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/TextureAsset.java index 2adc9468a..214175774 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/TextureAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/TextureAsset.java @@ -82,4 +82,9 @@ public void dispose() { texture.dispose(); } } + + @Override + public boolean usesAsset(Asset assetToCheck) { + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/AssetUsage.java b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/AssetUsage.java new file mode 100644 index 000000000..8c8a2f585 --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/AssetUsage.java @@ -0,0 +1,15 @@ +package com.mbrlabs.mundus.commons.scene3d.components; + +import com.mbrlabs.mundus.commons.assets.Asset; + +public interface AssetUsage { + /** + * Returns true if the implementation utilizes the given asset. + * Used for safely deleting assets by checking for usages of the asset first. + * + * Ex. should return true if material asset is passed to a model that is using that asset. + * @param assetToCheck the asset to check usages on + * @return true if the asset is used in the implementation, false if not + */ + boolean usesAsset(Asset assetToCheck); +} diff --git a/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/ModelComponent.java b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/ModelComponent.java index 9f7bd8797..eb7e60143 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/ModelComponent.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/ModelComponent.java @@ -20,15 +20,19 @@ import com.badlogic.gdx.graphics.g3d.ModelInstance; import com.badlogic.gdx.graphics.g3d.Shader; import com.badlogic.gdx.utils.ObjectMap; +import com.mbrlabs.mundus.commons.assets.Asset; import com.mbrlabs.mundus.commons.assets.MaterialAsset; import com.mbrlabs.mundus.commons.assets.ModelAsset; +import com.mbrlabs.mundus.commons.assets.TextureAsset; import com.mbrlabs.mundus.commons.scene3d.GameObject; +import java.util.Objects; + /** * @author Marcus Brummer * @version 17-01-2016 */ -public class ModelComponent extends AbstractComponent { +public class ModelComponent extends AbstractComponent implements AssetUsage { protected ModelAsset modelAsset; protected ModelInstance modelInstance; @@ -39,7 +43,7 @@ public class ModelComponent extends AbstractComponent { public ModelComponent(GameObject go, Shader shader) { super(go); type = Type.MODEL; - materials = new ObjectMap(); + materials = new ObjectMap<>(); this.shader = shader; } @@ -106,4 +110,26 @@ public Component clone(GameObject go) { return mc; } + @Override + public boolean usesAsset(Asset assetToCheck) { + if (Objects.equals(assetToCheck.getID(), modelAsset.getID())) + return true; + + if (assetToCheck instanceof MaterialAsset) { + if (materials.containsValue(assetToCheck,true)) { + return true; + } + } + + if (assetToCheck instanceof TextureAsset) { + // for each texture see if there is a match + for (ObjectMap.Entry next : materials) { + if (next.value.usesAsset(assetToCheck)) { + return true; + } + } + } + + return false; + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/TerrainComponent.java b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/TerrainComponent.java index 1bd7635ee..326235a9f 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/TerrainComponent.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/scene3d/components/TerrainComponent.java @@ -18,14 +18,17 @@ import com.badlogic.gdx.graphics.g3d.Shader; import com.badlogic.gdx.math.Vector2; +import com.mbrlabs.mundus.commons.assets.Asset; import com.mbrlabs.mundus.commons.assets.TerrainAsset; import com.mbrlabs.mundus.commons.scene3d.GameObject; +import java.util.Objects; + /** * @author Marcus Brummer * @version 18-01-2016 */ -public class TerrainComponent extends AbstractComponent { +public class TerrainComponent extends AbstractComponent implements AssetUsage { private static final String TAG = TerrainComponent.class.getSimpleName(); @@ -74,4 +77,11 @@ public Component clone(GameObject go) { return null; } + @Override + public boolean usesAsset(Asset assetToCheck) { + if (Objects.equals(terrain.getID(), assetToCheck.getID())) + return true; + + return terrain.usesAsset(assetToCheck); + } } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java index 1ebbcfabb..ac39feec8 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/TerrainTexture.java @@ -77,6 +77,10 @@ public int countTextures() { return textures.size(); } + public Map getTextures() { + return textures; + } + public SplatMap getSplatmap() { return splatmap; } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt index 3e6a04023..f59e9e52a 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt @@ -16,22 +16,33 @@ package com.mbrlabs.mundus.editor.assets +import com.badlogic.gdx.Files import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.PixmapIO import com.badlogic.gdx.utils.ObjectSet +import com.kotcrab.vis.ui.util.dialog.Dialogs import com.mbrlabs.mundus.commons.assets.* import com.mbrlabs.mundus.commons.assets.meta.Meta import com.mbrlabs.mundus.commons.assets.meta.MetaTerrain +import com.mbrlabs.mundus.commons.scene3d.GameObject +import com.mbrlabs.mundus.commons.scene3d.components.AssetUsage import com.mbrlabs.mundus.editor.Mundus import com.mbrlabs.mundus.editor.events.LogEvent import com.mbrlabs.mundus.editor.events.LogType +import com.mbrlabs.mundus.commons.utils.FileFormatUtils +import com.mbrlabs.mundus.editor.core.EditorScene +import com.mbrlabs.mundus.editor.core.project.ProjectManager +import com.mbrlabs.mundus.editor.ui.UI import com.mbrlabs.mundus.editor.utils.Log import org.apache.commons.io.FileUtils import org.apache.commons.io.FilenameUtils import java.io.* import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.collections.HashSet /** * @author Marcus Brummer @@ -313,6 +324,120 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) { metaSaver.save(asset.meta) } + /** + * Delete the asset from the project + */ + fun deleteAsset(asset: Asset, projectManager: ProjectManager) { + val objectsUsingAsset = findAssetUsagesInScenes(projectManager, asset) + val assetsUsingAsset = findAssetUsagesInAssets(asset) + + if (objectsUsingAsset.isNotEmpty() || assetsUsingAsset.isNotEmpty()) { + showUsagesFoundDialog(objectsUsingAsset, assetsUsingAsset) + return + } + + // continue with deletion + assets?.removeValue(asset, true) + + if (asset.file.extension().equals(FileFormatUtils.FORMAT_3D_GLTF)) { + // Delete the additional gltf binary file if found + val binPath = asset.file.pathWithoutExtension() + ".bin" + val binFile = Gdx.files.getFileHandle(binPath, Files.FileType.Absolute) + if (binFile.exists()) + binFile.delete() + } + + if (asset.meta.file.exists()) + asset.meta.file.delete() + + if (asset.file.exists()) + asset.file.delete() + } + + /** + * Build a dialog displaying the usages for the asset trying to be deleted. + */ + private fun showUsagesFoundDialog(objectsWithAssets: HashMap, assetsUsingAsset: ArrayList) { + val iterator = objectsWithAssets.iterator() + var details = "Scenes using asset:" + + // Create scenes section + while (iterator.hasNext()) { + val next = iterator.next() + + val sceneName = next.value + val gameObject = next.key + + var moreDetails = buildString { + append("\nScene: ") + append("[") + append(sceneName) + append("] Object name: ") + append("[") + append(gameObject.name) + append("]") + } + + if (iterator.hasNext()) { + moreDetails += ", " + } + + details += (moreDetails) + } + + // add assets section + if (assetsUsingAsset.isNotEmpty()) { + details += "\n\nAssets using asset:" + + for (name in assetsUsingAsset) + details += "\n" + name + } + + Dialogs.showDetailsDialog(UI, "Before deleting an asset, remove usages of the asset and save. See details for usages.", "Asset deletion", details) + } + + /** + * Searches all assets in the current context for any usages of the given asset + */ + private fun findAssetUsagesInAssets(asset: Asset): ArrayList { + val assetsUsingAsset = ArrayList() + + // Check for dependent assets that are not in scenes + for (otherAsset in assets) { + if (asset != otherAsset && otherAsset.usesAsset(asset)) { + assetsUsingAsset.add(otherAsset) + } + } + + return assetsUsingAsset + } + + /** + * Searches all scenes in the current context for any usages of the given asset + */ + private fun findAssetUsagesInScenes(projectManager: ProjectManager, asset: Asset): HashMap { + val objectsWithAssets = HashMap() + + // we check for usages in all scenes + for (sceneName in projectManager.current().scenes) { + val scene = projectManager.loadScene(projectManager.current(), sceneName) + checkSceneForAssetUsage(scene, asset, objectsWithAssets) + } + + return objectsWithAssets + } + + private fun checkSceneForAssetUsage(scene: EditorScene?, asset: Asset, objectsWithAssets: HashMap) { + for (gameObject in scene!!.sceneGraph.gameObjects) { + for (component in gameObject.components) { + if (component is AssetUsage) { + if (component.usesAsset(asset)) + objectsWithAssets[gameObject] = scene.name + } + } + } + } + /** * Saves an existing terrainAsset asset. * diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt index 412365ee2..9c470c704 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt @@ -41,7 +41,8 @@ class MetaSaver { } json.writeObjectEnd() - json.writer.flush() + // Close stream, otherwise file becomes locked + json.writer.close() } private fun addBasics(meta: Meta, json: Json) { diff --git a/editor/src/main/com/mbrlabs/mundus/editor/core/project/ProjectManager.java b/editor/src/main/com/mbrlabs/mundus/editor/core/project/ProjectManager.java index e766783f6..0a5b2e697 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/core/project/ProjectManager.java +++ b/editor/src/main/com/mbrlabs/mundus/editor/core/project/ProjectManager.java @@ -44,6 +44,7 @@ import com.mbrlabs.mundus.editor.core.registry.Registry; import com.mbrlabs.mundus.editor.core.scene.SceneManager; import com.mbrlabs.mundus.editor.events.LogEvent; +import com.mbrlabs.mundus.editor.events.LogType; import com.mbrlabs.mundus.editor.events.ProjectChangedEvent; import com.mbrlabs.mundus.editor.events.SceneChangedEvent; import com.mbrlabs.mundus.editor.scene3d.components.PickableComponent; @@ -392,7 +393,17 @@ private void initGameObject(ProjectContext context, GameObject root) { private void initComponents(ProjectContext context, GameObject go) { Array models = context.assetManager.getModelAssets(); - for (Component c : go.getComponents()) { + Array.ArrayIterator iterator = go.getComponents().iterator(); + while(iterator.hasNext()) { + Component c = iterator.next(); + if (c == null) { + // To prevent crashing, log a warning statement and remove the corrupted component + iterator.remove(); + Log.warn(TAG, "A component for {} was null on load, this may be caused by deleting an asset that is still in a scene.", go); + Mundus.INSTANCE.postEvent(new LogEvent(LogType.ERROR, "A component for "+ go.name +" was null on load, this may be caused by deleting an asset that is still in a scene.")); + go.name = go.name.concat(" [COMPONENT ERROR]"); + continue; + } // Model component if (c.getType() == Component.Type.MODEL) { ModelComponent modelComponent = (ModelComponent) c; diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/dock/assets/AssetsDock.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/dock/assets/AssetsDock.kt index b939adf63..b345dbb6a 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/dock/assets/AssetsDock.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/dock/assets/AssetsDock.kt @@ -23,6 +23,7 @@ import com.badlogic.gdx.scenes.scene2d.InputEvent import com.badlogic.gdx.scenes.scene2d.InputListener import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Array import com.kotcrab.vis.ui.VisUI @@ -85,6 +86,19 @@ class AssetsDock : Tab(false, false), // asset ops right click menu assetOpsMenu.addItem(renameAsset) assetOpsMenu.addItem(deleteAsset) + + registerListeners() + } + + private fun registerListeners() { + deleteAsset.addListener(object : ClickListener() { + override fun clicked(event: InputEvent, x: Float, y: Float) { + currentSelection?.asset?.let { + projectManager.current().assetManager.deleteAsset(it, projectManager) + reloadAssets() + } + } + }) } private fun setSelected(assetItem: AssetItem?) { @@ -138,7 +152,7 @@ class AssetsDock : Tab(false, false), /** * Asset item in the grid. */ - private inner class AssetItem(private val asset: Asset) : VisTable() { + private inner class AssetItem(val asset: Asset) : VisTable() { private val nameLabel: VisLabel @@ -156,18 +170,20 @@ class AssetsDock : Tab(false, false), override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) { if (event!!.button == Input.Buttons.RIGHT) { + setSelected() assetOpsMenu.showMenu(UI, Gdx.input.x.toFloat(), (Gdx.graphics.height - Gdx.input.y).toFloat()) } else if (event.button == Input.Buttons.LEFT) { - if (asset is MaterialAsset || asset is ModelAsset - || asset is TextureAsset || asset is TerrainAsset) { - this@AssetsDock.setSelected(this@AssetItem) - Mundus.postEvent(AssetSelectedEvent(asset)) - } + setSelected() } } }) } + + fun setSelected() { + this@AssetsDock.setSelected(this@AssetItem) + Mundus.postEvent(AssetSelectedEvent(asset)) + } } }