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 ab0ac3737..797603b7b 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt @@ -21,6 +21,7 @@ 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.graphics.g3d.Model import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.ObjectSet import com.kotcrab.vis.ui.util.dialog.Dialogs @@ -49,6 +50,7 @@ import com.mbrlabs.mundus.editor.events.LogEvent import com.mbrlabs.mundus.editor.events.LogType import com.mbrlabs.mundus.editor.ui.UI import com.mbrlabs.mundus.editor.utils.Log +import net.mgsx.gltf.exporters.GLTFExporter import org.apache.commons.io.FileUtils import org.apache.commons.io.FilenameUtils import java.io.BufferedOutputStream @@ -272,6 +274,36 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) { return asset } + /** + * Creates a new model asset. This variant of the method + * is used when the model does not have a file, e.g. + * a model that was generated by code (planes, cubes, etc).. + * It uses GLTF exporter to create a new gltf model file. + * + * @param fileName name of the model file + * @param model the loaded model + */ + @Throws(IOException::class, AssetAlreadyExistsException::class) + fun createModelAsset(fileName: String, model: Model): ModelAsset { + val modelFilename = fileName + val metaFilename = modelFilename + ".meta" + + // create meta file + val metaPath = FilenameUtils.concat(rootFolder.path(), metaFilename) + val meta = createNewMetaFile(FileHandle(metaPath), AssetType.MODEL) + + val assetFile = FileHandle(FilenameUtils.concat(rootFolder.path(), modelFilename)) + val exporter = GLTFExporter() + exporter.export(model, assetFile) + + // load & return asset + val asset = ModelAsset(meta, assetFile) + asset.load() + + addAsset(asset) + return asset + } + /** * Creates a new terrainAsset asset. * diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/ModelImporter.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/ModelImporter.kt index 395b08ed0..2d03d6e95 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/ModelImporter.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/ModelImporter.kt @@ -95,7 +95,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti * required for resolving image/texture files. */ fun populateMaterialAsset( - importedModel: FileHandleWithDependencies, + importedModel: FileHandleWithDependencies?, assetManager: EditorAssetManager, materialToUse: Material, materialAssetToPopulate: MaterialAsset, @@ -126,7 +126,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti // Texture Attributes if (materialToUse.has(PBRTextureAttribute.BaseColorTexture)) { materialAssetToPopulate.diffuseTexture = getTextureAssetForMaterial( - importedModel, + importedModel!!, assetManager, materialToUse.id, PBRTextureAttribute.BaseColorTexture) @@ -135,7 +135,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti if (materialToUse.has(PBRTextureAttribute.NormalTexture)) { materialAssetToPopulate.normalMap = getTextureAssetForMaterial( - importedModel, + importedModel!!, assetManager, materialToUse.id, PBRTextureAttribute.NormalTexture @@ -145,7 +145,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti if (materialToUse.has(PBRTextureAttribute.EmissiveTexture)) { materialAssetToPopulate.emissiveTexture = getTextureAssetForMaterial( - importedModel, + importedModel!!, assetManager, materialToUse.id, PBRTextureAttribute.EmissiveTexture @@ -155,7 +155,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti if (materialToUse.has(PBRTextureAttribute.MetallicRoughnessTexture)) { materialAssetToPopulate.metallicRoughnessTexture = getTextureAssetForMaterial( - importedModel, + importedModel!!, assetManager, materialToUse.id, PBRTextureAttribute.MetallicRoughnessTexture @@ -165,7 +165,7 @@ class ModelImporter(private val registry: Registry) : SettingsChangedEvent.Setti if (materialToUse.has(PBRTextureAttribute.OcclusionTexture)) { materialAssetToPopulate.occlusionTexture = getTextureAssetForMaterial( - importedModel, + importedModel!!, assetManager, materialToUse.id, PBRTextureAttribute.OcclusionTexture diff --git a/editor/src/main/com/mbrlabs/mundus/editor/events/EventBus.java b/editor/src/main/com/mbrlabs/mundus/editor/events/EventBus.java index 8e015de38..1c9fd99f8 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/events/EventBus.java +++ b/editor/src/main/com/mbrlabs/mundus/editor/events/EventBus.java @@ -16,70 +16,116 @@ package com.mbrlabs.mundus.editor.events; +import com.mbrlabs.mundus.editor.utils.ReflectionUtils; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - -import com.mbrlabs.mundus.editor.utils.ReflectionUtils; +import java.util.Map; /** * Simple Event bus via reflection. - * + *

* Subscribers need to provide a public method, annotated with @Subscribe and 1 * parameter as event type. - * + *

* Inspired by the Otto Event Bus for Android. * * @author Marcus Brummer * @version 12-12-2015 */ -// TODO improve/test performance might not be that great public class EventBus { - private class EventBusExcetion extends RuntimeException { - private EventBusExcetion(String s) { + private static class EventBusException extends RuntimeException { + private EventBusException(String s) { super(s); } } - private List subscribers; + /** + * Tracks the subscriber methods for each event type. + */ + private static class SubscriberMethod { + final Object instance; + final Method method; + + SubscriberMethod(Object instance, Method method) { + this.instance = instance; + this.method = method; + } + } + + // Maps the event class to subscriber methods, cached for performance + private final Map, List> subscribersMap; public EventBus() { - subscribers = new LinkedList<>(); + subscribersMap = new HashMap<>(); } + /** + * Registers all subscriber methods of the given object. + * For performance reasons we cache the subscriber methods for each event on register. + * + * @param subscriber the object with subscriber methods + */ public void register(Object subscriber) { - subscribers.add(subscriber); + // Loop over each method in the subscriber + for (Method method : subscriber.getClass().getDeclaredMethods()) { + if (!isSubscriber(method)) continue; + + if (method.getParameterTypes().length != 1) { + throw new EventBusException("Size of parameter list of method " + method.getName() + " in " + + subscriber.getClass().getName() + " must be 1"); + } + + Class eventType = method.getParameterTypes()[0]; + + // Get the list of subscribers for this event + List subscribers = subscribersMap.computeIfAbsent(eventType, k -> new ArrayList<>()); + subscribers.add(new SubscriberMethod(subscriber, method)); + } } + /** + * Unregisters all subscriber methods of the given object. + * @param subscriber + */ public void unregister(Object subscriber) { - subscribers.remove(subscriber); + // Iterate over each event type in the map, remove if matched + for (List methods : subscribersMap.values()) { + methods.removeIf(subscriberMethod -> subscriberMethod.instance.equals(subscriber)); + } } + /** + * Posts an event to all subscribers of this event type. + * @param event the event to post + */ public void post(Object event) { - try { - final Class eventType = event.getClass(); - for (Object subscriber : subscribers.toArray()) { - for (Method method : subscriber.getClass().getDeclaredMethods()) { - if (isSubscriber(method)) { - if (method.getParameterTypes().length != 1) { - throw new EventBusExcetion("Size of parameter list of method " + method.getName() + " in " - + subscriber.getClass().getName() + " must be 1"); - } - - if (method.getParameterTypes()[0].equals(eventType)) { - // System.out.println(subscriber.getClass().getName()); - method.invoke(subscriber, eventType.cast(event)); - } - } - } + // Get the list of subscribers for this event + List subscribers = subscribersMap.get(event.getClass()); + + if (subscribers == null) return; + + // Call each subscriber method + for (SubscriberMethod subscriberMethod : subscribers) { + try { + subscriberMethod.method.invoke(subscriberMethod.instance, event); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); } - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); } } + /** + * Checks if the given method is a subscriber. + * Slow with reflection, but we only check it on register. + * + * @param method the method to check + * @return true if the method is a subscriber + */ private boolean isSubscriber(Method method) { // check if @Subscribe is directly used in class boolean isSub = ReflectionUtils.hasMethodAnnotation(method, Subscribe.class); diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/outline/OutlineRightClickMenu.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/outline/OutlineRightClickMenu.kt index 1f6e3305e..3e7d5623a 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/outline/OutlineRightClickMenu.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/outline/OutlineRightClickMenu.kt @@ -1,5 +1,10 @@ package com.mbrlabs.mundus.editor.ui.modules.outline +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.GL20 +import com.badlogic.gdx.graphics.g3d.Material +import com.badlogic.gdx.graphics.g3d.Model +import com.badlogic.gdx.graphics.g3d.attributes.IntAttribute import com.badlogic.gdx.scenes.scene2d.InputEvent import com.badlogic.gdx.scenes.scene2d.utils.ClickListener import com.badlogic.gdx.utils.Array @@ -7,16 +12,26 @@ import com.kotcrab.vis.ui.util.dialog.Dialogs import com.kotcrab.vis.ui.util.dialog.InputDialogAdapter import com.kotcrab.vis.ui.widget.MenuItem import com.kotcrab.vis.ui.widget.PopupMenu +import com.mbrlabs.mundus.commons.assets.ModelAsset +import com.mbrlabs.mundus.commons.assets.meta.MetaModel import com.mbrlabs.mundus.commons.scene3d.GameObject +import com.mbrlabs.mundus.commons.scene3d.SceneGraph import com.mbrlabs.mundus.commons.scene3d.components.Component import com.mbrlabs.mundus.commons.scene3d.components.TerrainComponent import com.mbrlabs.mundus.editor.Mundus +import com.mbrlabs.mundus.editor.assets.MetaSaver +import com.mbrlabs.mundus.editor.assets.ModelImporter import com.mbrlabs.mundus.editor.core.project.ProjectManager +import com.mbrlabs.mundus.editor.events.AssetImportEvent import com.mbrlabs.mundus.editor.events.SceneGraphChangedEvent import com.mbrlabs.mundus.editor.events.TerrainRemovedEvent +import com.mbrlabs.mundus.editor.scene3d.components.PickableModelComponent import com.mbrlabs.mundus.editor.tools.ToolManager import com.mbrlabs.mundus.editor.ui.UI import com.mbrlabs.mundus.editor.utils.Log +import com.mbrlabs.mundus.editor.utils.UsefulMeshs +import net.mgsx.gltf.scene3d.attributes.PBRColorAttribute +import net.mgsx.gltf.scene3d.attributes.PBRFloatAttribute /** * Holds code for Outlines right click menu. Separated from Outline class @@ -33,6 +48,8 @@ class OutlineRightClickMenu(outline: Outline) : PopupMenu() { private val outline: Outline private val projectManager: ProjectManager = Mundus.inject() private val toolManager: ToolManager = Mundus.inject() + private val modelImporter: ModelImporter = Mundus.inject() + init { this.outline = outline @@ -261,34 +278,85 @@ class OutlineRightClickMenu(outline: Outline) : PopupMenu() { private val addEmpty: MenuItem = MenuItem("Add Empty") private val addTerrain: MenuItem = MenuItem("Add Terrain") private val addWater: MenuItem = MenuItem("Add Water") + private val addPlane: MenuItem = MenuItem("Add Plane") + private val addCube: MenuItem = MenuItem("Add Cube") init { addItem(addEmpty) addItem(addTerrain) addItem(addWater) + addItem(addPlane) + addItem(addCube) // add empty addEmpty.addListener(object : ClickListener() { override fun clicked(event: InputEvent?, x: Float, y: Float) { val sceneGraph = projectManager.current().currScene.sceneGraph - val id = projectManager.current().obtainID() - // the new game object - val go = GameObject(sceneGraph, GameObject.DEFAULT_NAME, id) + val go = createGameObject(sceneGraph) + // update outline - if (selectedGO == null) { - // update sceneGraph - Log.trace(TAG, "Add empty game object [{}] in root node.", go) - sceneGraph.addGameObject(go) - // update outline - outline.addGoToTree(null, go) - } else { - Log.trace(TAG, "Add empty game object [{}] child in node [{}].", go, selectedGO) - // update sceneGraph - selectedGO!!.addChild(go) - // update outline - val n = outline.tree.findNode(selectedGO!!) - outline.addGoToTree(n, go) + updateOutline(sceneGraph, go) + } + }) + + addPlane.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + val fileName = "standard_plane.gltf" + val assetManager = projectManager.current().assetManager + var modelAsset = assetManager.findAssetByFileName(fileName) as ModelAsset? + + val sceneGraph = projectManager.current().currScene.sceneGraph + val go = createGameObject(sceneGraph) + + if (modelAsset == null) { + // Create new material + val material = Material("plane_material") + setDefaultValues(material) + modelAsset = createModelAsset(fileName, UsefulMeshs.createPlane(material, 5f)) } - Mundus.postEvent(SceneGraphChangedEvent()) + + // Create model component + val modelComponent = PickableModelComponent(go) + + // Set model and add to game object + modelComponent.setModel(modelAsset, true) + go.addComponent(modelComponent) + modelComponent.encodeRaypickColorId() + + Mundus.postEvent(AssetImportEvent(modelAsset)) + + // update outline + updateOutline(sceneGraph, go) + } + }) + + addCube.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + val fileName = "standard_cube.gltf" + val assetManager = projectManager.current().assetManager + var modelAsset = assetManager.findAssetByFileName(fileName) as ModelAsset? + + val sceneGraph = projectManager.current().currScene.sceneGraph + val go = createGameObject(sceneGraph) + + if (modelAsset == null) { + // Create new material + val material = Material("cube_material") + setDefaultValues(material) + modelAsset = createModelAsset(fileName, UsefulMeshs.createCube(material, 5f)) + } + + // Create model component + val modelComponent = PickableModelComponent(go) + + // Set model and add to game object + modelComponent.setModel(modelAsset, true) + go.addComponent(modelComponent) + modelComponent.encodeRaypickColorId() + + Mundus.postEvent(AssetImportEvent(modelAsset!!)) + + // update outline + updateOutline(sceneGraph, go) } }) @@ -307,6 +375,61 @@ class OutlineRightClickMenu(outline: Outline) : PopupMenu() { }) } + private fun createModelAsset(fileName: String, model: Model): ModelAsset { + val assetManager = projectManager.current().assetManager + val modelAsset = assetManager.createModelAsset(fileName, model) + modelAsset.meta.model = MetaModel() + + for (mat in modelAsset.model.materials) { + val materialAsset = assetManager.createMaterialAsset(modelAsset.id.substring(0, 4) + "_" + mat.id) + + modelImporter.populateMaterialAsset(null, projectManager.current().assetManager, mat, materialAsset) + projectManager.current().assetManager.saveMaterialAsset(materialAsset) + + modelAsset.meta.model.defaultMaterials.put(mat.id, materialAsset.id) + modelAsset.defaultMaterials.put(mat.id, materialAsset) + } + + // save meta file + val saver = MetaSaver() + saver.save(modelAsset.meta) + + modelAsset.applyDependencies() + return modelAsset + } + + private fun setDefaultValues(material: Material) { + material.set(PBRColorAttribute.createBaseColorFactor(Color.GRAY)) + material.set(PBRFloatAttribute.createMetallic(0f)) + material.set(PBRFloatAttribute.createRoughness(1.0f)) + material.set(IntAttribute.createCullFace(GL20.GL_BACK)) + } + + private fun updateOutline(sceneGraph: SceneGraph, go: GameObject) { + // update outline + if (selectedGO == null) { + // update sceneGraph + Log.trace(TAG, "Add empty game object [{}] in root node.", go) + sceneGraph.addGameObject(go) + // update outline + outline.addGoToTree(null, go) + } else { + Log.trace(TAG, "Add empty game object [{}] child in node [{}].", go, selectedGO) + // update sceneGraph + selectedGO!!.addChild(go) + // update outline + val n = outline.tree.findNode(selectedGO!!) + outline.addGoToTree(n, go) + } + Mundus.postEvent(SceneGraphChangedEvent()) + } + + fun createGameObject(sceneGraph : SceneGraph): GameObject { + val id = projectManager.current().obtainID() + // the new game object + return GameObject(sceneGraph, GameObject.DEFAULT_NAME, id) + } + } //endregion Add Sub Menu diff --git a/editor/src/main/com/mbrlabs/mundus/editor/utils/UsefulMeshs.java b/editor/src/main/com/mbrlabs/mundus/editor/utils/UsefulMeshs.java index d8b086e90..494b73b59 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/utils/UsefulMeshs.java +++ b/editor/src/main/com/mbrlabs/mundus/editor/utils/UsefulMeshs.java @@ -17,6 +17,8 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Mesh; +import com.badlogic.gdx.graphics.VertexAttribute; import com.badlogic.gdx.graphics.VertexAttributes; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g3d.Material; @@ -25,7 +27,9 @@ import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder; import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder; import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.BoxShapeBuilder; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Vector3; +import net.mgsx.gltf.loaders.shared.geometry.MeshTangentSpaceGenerator; /** * @author Marcus Brummer @@ -129,4 +133,208 @@ public static Model torus(Material mat, float width, float height, int divisions return modelBuilder.end(); } + public static Model createPlane(Material mat, float size) { + // Position, Normal, TextureCoordinates, Tangents (generated later) + float[] vertices = { + -size, 0.0f, size, + 0.0f, 1.0f, -0.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, 0.0f, size, + 0.0f, 1.0f, -0.0f, + 1.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, 0.0f, -size, + 0.0f, 1.0f, -0.0f, + 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, 0.0f, -size, + 0.0f, 1.0f, -0.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + }; + short[] indices = {0, 1, 3, 0, 3, 2}; + + VertexAttributes attribs = new VertexAttributes( + new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE), + new VertexAttribute(VertexAttributes.Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE), + new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"), + new VertexAttribute(Usage.Tangent, 4, ShaderProgram.TANGENT_ATTRIBUTE) + ); + + VertexAttribute normalMapUVs = null; + for(VertexAttribute a : attribs){ + if(a.usage == VertexAttributes.Usage.TextureCoordinates){ + normalMapUVs = a; + } + } + + // Get tangents added for normal mapping + MeshTangentSpaceGenerator.computeTangentSpace(vertices, indices, attribs, false, true, normalMapUVs); + + Mesh mesh = new Mesh(false, vertices.length, indices.length, attribs); + mesh.setVertices(vertices); + mesh.setIndices(indices); + + ModelBuilder modelBuilder = new ModelBuilder(); + modelBuilder.begin(); + modelBuilder.part("plane", mesh, GL20.GL_TRIANGLES, mat); + return modelBuilder.end(); + } + + public static Model createCube(Material mat, float size) { + // Position, Normal, TextureCoordinates, Tangents (generated later) + float[] vertices = { + size, size, -size, + 0.0f, 0.0f, -1.0f, + 0.625f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, size, -size, + 0.0f, 1.0f, -0.0f, + 0.625f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, size, -size, + 1.0f, 0.0f, -0.0f, + 0.625f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, -size, + 0.0f, -1.0f, -0.0f, + 0.375f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, -size, + 0.0f, 0.0f, -1.0f, + 0.375f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, -size, + 1.0f, 0.0f, -0.0f, + 0.375f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, size, size, + 0.0f, 0.0f, 1.0f, + 0.625f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, size, size, + 0.0f, 1.0f, -0.0f, + 0.625f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, size, size, + 1.0f, 0.0f, -0.0f, + 0.625f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, size, + 0.0f, -1.0f, -0.0f, + 0.375f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, size, + 0.0f, 0.0f, 1.0f, + 0.375f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + size, -size, size, + 1.0f, 0.0f, -0.0f, + 0.375f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, -size, + -1.0f, 0.0f, -0.0f, + 0.625f, 0.75f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, -size, + 0.0f, 0.0f, -1.0f, + 0.625f, 0.75f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, -size, + 0.0f, 1.0f, -0.0f, + 0.875f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, -size, + -1.0f, 0.0f, -0.0f, + 0.375f, 0.75f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, -size, + 0.0f, -1.0f, -0.0f, + 0.125f, 0.5f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, -size, + 0.0f, 0.0f, -1.0f, + 0.375f, 0.75f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, size, + -1.0f, 0.0f, -0.0f, + 0.625f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, size, + 0.0f, 0.0f, 1.0f, + 0.625f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, size, size, + 0.0f, 1.0f, -0.0f, + 0.875f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, size, + -1.0f, 0.0f, -0.0f, + 0.375f, 1.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, size, + 0.0f, -1.0f, -0.0f, + 0.125f, 0.25f, + 0.0f, 0.0f, 0.0f, 0.0f, + + -size, -size, size, + 0.0f, 0.0f, 1.0f, + 0.375f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, + }; + short[] indices = {1, 14, 20, 1, 20, 7, 10, 6, 19, 10, 19, 23, 21, 18, 12, 21, 12, 15, 16, 3, 9, 16, 9, 22, 5, 2, 8, 5, 8, 11, 17, 13, 0, 17, 0, 4}; + + VertexAttributes attribs = new VertexAttributes( + new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE), + new VertexAttribute(VertexAttributes.Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE), + new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"), + new VertexAttribute(Usage.Tangent, 4, ShaderProgram.TANGENT_ATTRIBUTE) + ); + + VertexAttribute normalMapUVs = null; + for(VertexAttribute a : attribs){ + if(a.usage == VertexAttributes.Usage.TextureCoordinates){ + normalMapUVs = a; + } + } + + // Get tangents added for normal mapping + MeshTangentSpaceGenerator.computeTangentSpace(vertices, indices, attribs, false, true, normalMapUVs); + + Mesh mesh = new Mesh(false, vertices.length, indices.length, attribs); + mesh.setVertices(vertices); + mesh.setIndices(indices); + + ModelBuilder modelBuilder = new ModelBuilder(); + modelBuilder.begin(); + modelBuilder.part("cube", mesh, GL20.GL_TRIANGLES, mat); + return modelBuilder.end(); + } + } diff --git a/editor/src/test/java/com/mbrlabs/mundus/editor/events/EventBusTest.java b/editor/src/test/java/com/mbrlabs/mundus/editor/events/EventBusTest.java new file mode 100644 index 000000000..c61239904 --- /dev/null +++ b/editor/src/test/java/com/mbrlabs/mundus/editor/events/EventBusTest.java @@ -0,0 +1,72 @@ +package com.mbrlabs.mundus.editor.events; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author JamesTKhan + * @version June 27, 2023 + */ +public class EventBusTest { + + @Test + public void eventsReceivedTest() { + EventBus eventBus = new EventBus(); + final int[] sceneChangedEventsReceived = {0}; + final int[] sceneAddedEventsReceived = {0}; + + eventBus.register(new Object() { + @Subscribe + public void onEvent(SceneChangedEvent event) { + sceneChangedEventsReceived[0]++; + } + }); + + eventBus.register(new Object() { + @Subscribe + public void onEvent(SceneAddedEvent event) { + sceneAddedEventsReceived[0]++; + } + }); + + int sceneChangedCount = 10; + int sceneAddedCount = 5; + + for (int i = 0; i < sceneChangedCount; i++) { + eventBus.post(new SceneChangedEvent()); + } + + for (int i = 0; i < sceneAddedCount; i++) { + eventBus.post(new SceneAddedEvent(null)); + } + + // check if events were received + Assert.assertEquals(sceneChangedEventsReceived[0], sceneChangedCount); + Assert.assertEquals(sceneAddedEventsReceived[0], sceneAddedCount); + } + + @Test + public void unregisterTest() { + EventBus eventBus = new EventBus(); + final int[] sceneChangedEventsReceived = {0}; + + Object subscriber = new Object() { + @Subscribe + public void onEvent(SceneChangedEvent event) { + sceneChangedEventsReceived[0]++; + } + }; + + // register and post, should receive 1 event + eventBus.register(subscriber); + eventBus.post(new SceneChangedEvent()); + + // unregister and post, should not receive any events + eventBus.unregister(subscriber); + eventBus.post(new SceneChangedEvent()); + + // should still be 1 + Assert.assertEquals(sceneChangedEventsReceived[0], 1); + } + +} \ No newline at end of file