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