Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unused Assets Tool #93

Merged
merged 7 commits into from
Jul 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions commons/src/main/com/mbrlabs/mundus/commons/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,20 @@ public class Scene implements Disposable {
protected Vector3 clippingPlaneReflection = new Vector3(0.0f, 1f, 0.0f);
protected Vector3 clippingPlaneRefraction = new Vector3(0.0f, -1f, 0.0f);

/**
* The default way to instantiate a scene. Use this constructor if you
* are using the runtime.
*/
public Scene() {
this(true);
}

/**
* Optionally allow instantiation of a scene without using any OpenGL context
* useful for when you need a scene object loaded on a different thread.
* @param hasGLContext normally this should be true, false if you are not on main thread
*/
public Scene(boolean hasGLContext) {
environment = new MundusEnvironment();
settings = new SceneSettings();

Expand All @@ -98,9 +111,10 @@ public Scene() {
environment.getAmbientLight().intensity = 0.8f;
environment.set(ColorAttribute.createAmbientLight(Color.WHITE));

initPBR();

setShadowQuality(ShadowResolution.DEFAULT_SHADOW_RESOLUTION);
if (hasGLContext) {
initPBR();
setShadowQuality(ShadowResolution.DEFAULT_SHADOW_RESOLUTION);
}

sceneGraph = new SceneGraph(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g3d.Attribute;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.UBJsonReader;
import com.mbrlabs.mundus.commons.assets.meta.Meta;
Expand All @@ -29,23 +32,30 @@
import net.mgsx.gltf.loaders.glb.GLBLoader;
import net.mgsx.gltf.loaders.gltf.GLTFLoader;
import net.mgsx.gltf.scene3d.scene.SceneAsset;
import org.w3c.dom.Attr;

import java.util.HashMap;
import java.util.Map;

import static net.mgsx.gltf.scene3d.attributes.PBRTextureAttribute.*;

/**
* @author Marcus Brummer
* @version 01-10-2016
*/
public class ModelAsset extends Asset {
protected static long TextureAttributeMask = Diffuse | Specular | Bump | Normal | Ambient | Emissive | Reflection | MetallicRoughnessTexture
| OcclusionTexture | BaseColorTexture | NormalTexture | EmissiveTexture | BRDFLUTTexture;

private Model model;

private Map<String, MaterialAsset> defaultMaterials;
private final Map<String, MaterialAsset> defaultMaterials;
private final Array<Material> initialModelMaterials; // The initial materials for the model, before mundus modifies them

public ModelAsset(Meta meta, FileHandle assetFile) {
super(meta, assetFile);
defaultMaterials = new HashMap<>();
initialModelMaterials = new Array<>();
}

public Model getModel() {
Expand All @@ -71,6 +81,8 @@ public void load() {
} else {
throw new GdxRuntimeException("Unsupported 3D model");
}

copyMaterials();
updateBoneCount();
}

Expand All @@ -84,6 +96,8 @@ public void load(AssetManager assetManager) {
} else {
throw new GdxRuntimeException("Unsupported 3D model");
}

copyMaterials();
updateBoneCount();
}

Expand Down Expand Up @@ -129,9 +143,38 @@ public boolean usesAsset(Asset assetToCheck) {
return true;
}
}

// This looks painful but all we are doing is checking if the texture asset being deleted is used
// by the original model file. Why? because even if it's not in use by the active mundus material(s)
// we still need it to load the model file properly since it's a dependency. This may also be used as
// a way to "rollback" the mundus material to the models defaults
if (assetToCheck instanceof TextureAsset) {
for (Material material : initialModelMaterials) {
Array<Attribute> attrs = material.get(new Array<Attribute>(), TextureAttributeMask);
for (Attribute attr : attrs) {
if (attr instanceof TextureAttribute) {
TextureAttribute textureAttribute = (TextureAttribute) attr;
if (textureAttribute.textureDescription.texture == ((TextureAsset) assetToCheck).getTexture()) {
return true;
}
}
}
}
}

return false;
}

/**
* Copy materials of the model before mundus has modified them
*/
private void copyMaterials() {
// Store a copy of the original unmodified model materials
for (Material material : model.materials) {
initialModelMaterials.add(new Material(material));
}
}

private void updateBoneCount() {
// Update bone count for model
if (meta != null && meta.getModel() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,13 @@ public boolean usesAsset(Asset assetToCheck) {
return true;
}
}

// Check normal maps
if (assetToCheck == splatBaseNormal || assetToCheck == splatRNormal ||
assetToCheck == splatBNormal || assetToCheck == splatGNormal || assetToCheck == splatANormal) {
return true;
}

}

return false;
Expand Down
1 change: 1 addition & 0 deletions editor/CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Added configurable camera settings Window -> Settings -> Camera
- Added Keyboard Shortcuts dialog
- Added Basic debug and wireframe render modes
- Added Utility to clean up unused assets under new Tools menu item
- Update water shader to fade out water foam over distance, to minimize ugly 1 pixel white lines from a distance.
- Fix broken checkbox to set game objects to active or inactive
- Fix File Choosers stored favorites under the wrong key name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import com.mbrlabs.mundus.commons.scene3d.components.AssetUsage
import com.mbrlabs.mundus.commons.utils.FileFormatUtils
import com.mbrlabs.mundus.commons.water.WaterFloatAttribute
import com.mbrlabs.mundus.editor.Mundus.postEvent
import com.mbrlabs.mundus.editor.core.EditorScene
import com.mbrlabs.mundus.editor.core.project.ProjectManager
import com.mbrlabs.mundus.editor.events.LogEvent
import com.mbrlabs.mundus.editor.events.LogType
Expand Down Expand Up @@ -212,6 +211,15 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) {
return textureAsset
}

private fun getStandardAssets(): Array<Asset> {
val standardAssets = Array<Asset>()
standardAssets.add(findAssetByID(STANDARD_ASSET_TEXTURE_CHESSBOARD))
standardAssets.add(findAssetByID(STANDARD_ASSET_TEXTURE_DUDV))
standardAssets.add(findAssetByID(STANDARD_ASSET_TEXTURE_WATER_NORMAL))
standardAssets.add(findAssetByID(STANDARD_ASSET_TEXTURE_WATER_FOAM))
return standardAssets
}

/**
* Creates a new model asset.
*
Expand Down Expand Up @@ -456,9 +464,41 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) {
}

/**
* Delete the asset from the project
* Searches all GameObjects and assets to find assets which are not used.
*/
fun findUnusedAssets(projectManager: ProjectManager): Array<Asset> {
val unusedAssets = Array<Asset>()
val standardAssets = getStandardAssets()

for (i in 0 until assets.size) {
val asset = assets[i]

// Do not consider standard assets as unused even if not currently used
if (standardAssets.contains(asset, true)) {
continue
}

if (asset is SkyboxAsset) {
continue // It is common to have these be unused
} else {
val objectsUsingAsset = findAssetUsagesInScenes(projectManager, asset)
val assetsUsingAsset = findAssetUsagesInAssets(asset)

if (objectsUsingAsset.isEmpty() && assetsUsingAsset.isEmpty()) {
unusedAssets.add(asset)
}
}
}

return unusedAssets
}



/**
* Delete the asset from the project if no usages are found
*/
fun deleteAsset(asset: Asset, projectManager: ProjectManager) {
fun deleteAssetSafe(asset: Asset, projectManager: ProjectManager) {
if (asset is SkyboxAsset) {
val skyboxUsages = findSkyboxUsagesInScenes(projectManager, asset)
if (skyboxUsages.isNotEmpty()) {
Expand All @@ -475,6 +515,13 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) {
}
}

deleteAsset(asset)
}

/**
* Delete asset, does not check if it is being used.
*/
fun deleteAsset(asset: Asset) {
// continue with deletion
assets?.removeValue(asset, true)

Expand Down Expand Up @@ -550,7 +597,7 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) {
/**
* Searches all assets in the current context for any usages of the given asset
*/
private fun findAssetUsagesInAssets(asset: Asset): ArrayList<Asset> {
fun findAssetUsagesInAssets(asset: Asset): ArrayList<Asset> {
val assetsUsingAsset = ArrayList<Asset>()

// Check for dependent assets that are not in scenes
Expand All @@ -566,26 +613,30 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) {
/**
* Searches all scenes in the current context for any usages of the given asset
*/
private fun findAssetUsagesInScenes(projectManager: ProjectManager, asset: Asset): HashMap<GameObject, String> {
fun findAssetUsagesInScenes(projectManager: ProjectManager, asset: Asset): HashMap<GameObject, String> {
val objectsWithAssets = HashMap<GameObject, String>()

// we check for usages in all scenes
for (sceneName in projectManager.current().scenes) {
val scene = projectManager.loadScene(projectManager.current(), sceneName)
checkSceneForAssetUsage(scene, asset, objectsWithAssets)
val gameObjects = projectManager.getSceneGameObjects(projectManager.current(), sceneName)
checkSceneForAssetUsage(sceneName, gameObjects, asset, objectsWithAssets)
}

return objectsWithAssets
}

private fun checkSceneForAssetUsage(scene: EditorScene?, asset: Asset, objectsWithAssets: HashMap<GameObject, String>) {
for (gameObject in scene!!.sceneGraph.gameObjects) {
private fun checkSceneForAssetUsage(sceneName: String, gameObjects: Array<GameObject>, asset: Asset, objectsWithAssets: HashMap<GameObject, String>) {
for (gameObject in gameObjects) {
for (component in gameObject.components) {
if (component is AssetUsage) {
if (component.usesAsset(asset))
objectsWithAssets[gameObject] = scene.name
objectsWithAssets[gameObject] = sceneName
}
}

if (gameObject.children != null) {
checkSceneForAssetUsage(sceneName, gameObject.children, asset, objectsWithAssets)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.mbrlabs.mundus.commons.assets.SkyboxAsset;
import com.mbrlabs.mundus.commons.assets.TextureAsset;
import com.mbrlabs.mundus.commons.assets.meta.MetaFileParseException;
import com.mbrlabs.mundus.commons.dto.GameObjectDTO;
import com.mbrlabs.mundus.commons.dto.SceneDTO;
import com.mbrlabs.mundus.commons.scene3d.GameObject;
import com.mbrlabs.mundus.commons.scene3d.SceneGraph;
Expand All @@ -41,6 +42,7 @@
import com.mbrlabs.mundus.editor.assets.AssetAlreadyExistsException;
import com.mbrlabs.mundus.editor.assets.EditorAssetManager;
import com.mbrlabs.mundus.editor.core.EditorScene;
import com.mbrlabs.mundus.editor.core.converter.GameObjectConverter;
import com.mbrlabs.mundus.editor.core.converter.SceneConverter;
import com.mbrlabs.mundus.editor.core.kryo.KryoManager;
import com.mbrlabs.mundus.editor.core.registry.ProjectRef;
Expand Down Expand Up @@ -479,6 +481,26 @@ public EditorScene loadScene(ProjectContext context, String sceneName) throws Fi
return scene;
}

/**
* Get all GameObjects from a scene. Partially loads the scene fast without using GL context
* so this can be called on a separate thread.
*/
public Array<GameObject> getSceneGameObjects(ProjectContext context, String sceneName) throws FileNotFoundException {
SceneDTO sceneDTO = SceneManager.loadScene(context, sceneName);

Scene scene = new Scene(false);
for (GameObjectDTO descriptor : sceneDTO.getGameObjects()) {
scene.sceneGraph.addGameObject(GameObjectConverter.convert(descriptor, scene.sceneGraph, context.assetManager.getAssetMap()));
}
for (GameObject go : scene.sceneGraph.getGameObjects()) {
initGameObject(context, go);
}

scene.dispose();
return scene.sceneGraph.getGameObjects();
}


/**
* Loads and opens scene
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mbrlabs.mundus.editor.events

/**
* @author JamesTKhan
* @version July 28, 2022
*/
class AssetDeletedEvent {

interface AssetDeletedListener {
@Subscribe
fun onAssetDeleted(event: AssetDeletedEvent)
}

}
Loading