You can create a listener for that event.
Edit: To get the parent you can either call getParent on the node itself or getParent on the TreeNode.
You can create a listener for that event.
Edit: To get the parent you can either call getParent on the node itself or getParent on the TreeNode.
So is that even listener called when click on a menu item?
No, just when the scene tree selection changes. To “do something” when someone clicks on a menu item you write the event handler for the menu item.
Like here I add my own menu item to every right-click menu on a Node.
https://github.com/jayfella/TestDevkitPlugin/blob/master/src/main/java/plugin/devkit/testplugin/MyTestPlugin.java#L72
I see what you mean now. I need to add a method in the SceneTree service to get the currently selected node. That will probably be easiest.
Edit: I’ve pushed it to master. I’ve pushed a few changes over the past day or two. I’ll push a release tomorrow.
Awesome, yeah that is what I was looking for.
Could you provide the main frame from a service to be able to add extra windows?
I’ve updated the libraries.
plugins {
id "com.jayfella.jme-devkit" version "0.0.17"
}
dependencies {
implementation "com.jayfella:jme-swing-devkit:1.0.5"
}
@NyouB yes I’ll do that now. I should be able to provide an example in the test plugin and push the changes live shortly.
plugins {
id "com.jayfella.jme-devkit" version "0.0.18"
}
dependencies {
implementation "com.jayfella:jme-swing-devkit:1.0.6"
}
And I’ve pushed an example to the TestDevKitPlugin repository to show how to manage the life of a window.
And if you want to use a checkbox for a window (like the debug lights window) the code is here.
https://github.com/jayfella/jme-swing-devkit/blob/master/src/main/java/com/jayfella/importer/Main.java#L260
It works perfectly !
Adding support to run AppStates in the devkit.This will allow you to use AppStates to create game objects, for example a terrain generator, etc.This will be available on the next update.
The system is controlled using Annotations. An AppState marked with the @DevkitAppState
annotation will be picked up by the SDK as available to run. In the AppState class you add annotations to getters to decide which properties are modifiable.
For example (and there will be more as time goes by):
See the class below for the example class displayed in the image above.
package com.jayfella.simple;
import com.jayfella.importer.annotations.ButtonProperty;
import com.jayfella.importer.annotations.ColorProperty;
import com.jayfella.importer.annotations.DevkitAppState;
import com.jayfella.importer.annotations.FloatProperty;
import com.jayfella.importer.service.SceneTreeService;
import com.jayfella.importer.service.ServiceManager;
import com.jayfella.importer.tree.spatial.NodeTreeNode;
import com.jayfella.importer.tree.spatial.SpatialTreeNode;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.material.Material;
import com.jme3.material.Materials;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import javax.swing.*;
@DevkitAppState
public class SimpleTestAppState extends BaseAppState {
private Geometry geometry;
private SpatialTreeNode treeNode;
private float quadSize = 1.0f;
private final ColorRGBA color = new ColorRGBA(1,1,1,1);
public SimpleTestAppState() {
}
@Override
protected void initialize(Application app) {
geometry = new Geometry("Test Geometry", new Quad(quadSize, quadSize));
geometry.setMaterial(new Material(app.getAssetManager(), Materials.UNSHADED));
geometry.getMaterial().setColor("Color", color);
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
NodeTreeNode treeRootNode = ServiceManager.getService(SceneTreeService.class).getRootNodeTreeNode();
SwingUtilities.invokeLater(() -> treeNode = ServiceManager.getService(SceneTreeService.class)
.addSpatial(geometry, treeRootNode));
}
@Override
protected void onDisable() {
SwingUtilities.invokeLater(() -> ServiceManager.getService(SceneTreeService.class)
.removeTreeNode(treeNode));
}
@FloatProperty(min = 1.0f, max = 10.0f, step = 0.1f)
public float getQuadSize() {
return quadSize;
}
public void setQuadSize(float quadSize) {
this.quadSize = quadSize;
Quad quad = (Quad) geometry.getMesh();
quad.updateGeometry(quadSize, quadSize);
}
@ColorProperty
public ColorRGBA getColor() {
return color;
}
public void setColor(ColorRGBA color) {
this.color.set(color);
}
@ButtonProperty
public void doSomething() {
System.out.println("I got pressed!");
}
}
Continuing the DevKitAppState setup, I have added support for tabs so the GUI can be organized properly. This test case generates terrain into the scene - which you can save (and thus reload without having to re-generate),
package devkit.appstate;
import com.jayfella.fastnoise.FastNoise;
import com.jayfella.importer.appstate.annotations.*;
import com.jayfella.importer.service.SceneTreeService;
import com.jayfella.importer.service.ServiceManager;
import com.jayfella.importer.tree.spatial.NodeTreeNode;
import com.jayfella.importer.tree.spatial.SpatialTreeNode;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import javax.swing.*;
import java.util.Random;
@DevKitAppState(tabs = { "Size", "Mountains", "Rocks", "Details" } )
public class TerrainGenerator extends BaseAppState {
private final FastNoise mountainNoise = new FastNoise();
private float mountainHeight = 32f;
private final FastNoise rockNoise = new FastNoise();
private float rockHeight = 4f;
private final FastNoise detailNoise = new FastNoise();
private float detailHeight = 0.5f;
private Node terrainNode = new Node("Terrain");
private int sizeX = 8, sizeZ = 8;
private CellSize cellSize = CellSize.Size_16;
private NodeTreeNode terrainTreeNode;
private SceneTreeService sceneTreeService;
private Material material;
private int seed = 0;
private final Random random = new Random();
public TerrainGenerator() {
}
@Override
protected void initialize(Application app) {
setSeed(seed);
mountainNoise.SetFrequency(0.008f);
rockNoise.SetFrequency(0.04f);
detailNoise.SetFrequency(0.2f);
material = createTerrainMaterial();
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
SwingUtilities.invokeLater(() -> {
sceneTreeService = ServiceManager.getService(SceneTreeService.class);
NodeTreeNode treeRoot = sceneTreeService.getRootNodeTreeNode();
terrainTreeNode = (NodeTreeNode) sceneTreeService.addSpatial(terrainNode, treeRoot);
});
}
@Override
protected void onDisable() {
SwingUtilities.invokeLater(() -> {
sceneTreeService.removeTreeNode(terrainTreeNode);
});
}
@IntegerProperty
public int getSeed() {
return seed;
}
public void setSeed(int seed) {
this.seed = seed;
random.setSeed(seed);
mountainNoise.SetSeed(random.nextInt());
rockNoise.SetSeed(random.nextInt());
detailNoise.SetSeed(random.nextInt());
}
@IntegerProperty(tab = "Size", min = 1, max = 128)
public int getSizeX() {
return sizeX;
}
public void setSizeX(int sizeX) {
this.sizeX = sizeX;
}
@IntegerProperty(tab = "Size", min = 1, max = 128)
public int getSizeZ() {
return sizeZ;
}
public void setSizeZ(int sizeZ) {
this.sizeZ = sizeZ;
}
@EnumProperty(tab = "Size")
public CellSize getCellSize() {
return cellSize;
}
public void setCellSize(CellSize cellSize) {
this.cellSize = cellSize;
}
@ButtonProperty
public void generate() {
// clear the children.
SwingUtilities.invokeLater(() -> {
int childCount = terrainTreeNode.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
sceneTreeService.removeTreeNode((SpatialTreeNode) terrainTreeNode.getChildAt(i));
}
});
// center the terrain
terrainNode.setLocalTranslation(
-cellSize.getSize() * sizeX / 2.0f,
0,
-cellSize.getSize() * sizeZ / 2.0f
);
int size = cellSize.getSize() + 3;
for (int xCell = 0; xCell < sizeX; xCell++) {
for (int zCell = 0; zCell < sizeZ; zCell++) {
float[] heightmap = new float[size * size];
int xWorld = xCell << cellSize.getBitshift();
int zWorld = zCell << cellSize.getBitshift();
for (int x = 0; x < size; x++) {
for (int z = 0; z < size; z++) {
double noise = getNoise(xWorld + x, zWorld + z);
int index = x + z * size;
heightmap[index] = (float) noise;
}
}
Mesh mesh = new HeightMapMesh(heightmap);
Geometry geometry = new Geometry("Cell: " + xCell + "," + zCell, mesh);
geometry.setLocalTranslation(xWorld, 0, zWorld);
geometry.setMaterial(material);
SwingUtilities.invokeLater(() -> sceneTreeService.addSpatial(geometry, terrainTreeNode));
}
}
}
// Mountains
@FloatProperty(tab = "Mountains", min = 0.0001f, max = 0.1f, step = 0.001f)
public float getMountainFrequency() { return mountainNoise.GetFrequency(); }
public void setMountainFrequency(float frequency) { mountainNoise.SetFrequency(frequency); }
@FloatProperty(tab = "Mountains", min = 4.0f, max = 64.0f, step = 0.1f)
public float getMountainHeight() { return mountainHeight; }
public void setMountainHeight(float mountainHeight) { this.mountainHeight = mountainHeight; }
// Rocks
@FloatProperty(tab = "Rocks", min = 0.0001f, max = 0.1f, step = 0.001f)
public float getRockFrequency() { return rockNoise.GetFrequency(); }
public void setRockFrequency(float frequency) { rockNoise.SetFrequency(frequency); }
@FloatProperty(tab = "Rocks", min = 1.0f, max = 8.0f, step = 0.1f)
public float getRockHeight() { return rockHeight; }
public void setRockHeight(float rockHeight) { this.rockHeight = rockHeight; }
// Details
@FloatProperty(tab = "Details", min = 0.0001f, max = 1f, step = 0.001f)
public float getDetailFrequency() { return detailNoise.GetFrequency(); }
public void setDetailFrequency(float frequency) { detailNoise.SetFrequency(frequency); }
@FloatProperty(tab = "Details", min = 0.001f, max = 1.0f, step = 0.001f)
public float getDetailHeight() { return detailHeight; }
public void setDetailHeight(float detailHeight) { this.detailHeight = detailHeight; }
private double getNoise(float x, float z) {
return mountainNoise.GetNoise(x, z) * mountainHeight
+ rockNoise.GetNoise(x,z) * rockHeight
+ detailNoise.GetNoise(x,z) * detailHeight;
}
private Material createTerrainMaterial() {
AssetManager assets = getApplication().getAssetManager();
Material terrainMaterial = new Material(assets, "MatDefs/TrilinearLighting.j3md");
terrainMaterial.setFloat("Shininess", 0);
terrainMaterial.setColor("Diffuse", ColorRGBA.White);
terrainMaterial.setColor("Ambient", ColorRGBA.White);
terrainMaterial.setBoolean("UseMaterialColors", true);
terrainMaterial.setVector3("WorldOffset", new Vector3f());
// Setup the trilinear textures for the different axis,
// X, Y, and Z. We use the regular diffuse map for the top
// texture.
Texture texture;
texture = assets.loadTexture("Textures/grass.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("DiffuseMap", texture);
texture = assets.loadTexture("Textures/grass-flat.jpg");
texture.setWrap(Texture.WrapMode.Repeat);
terrainMaterial.setTexture("DiffuseMapLow", texture);
texture = assets.loadTexture("Textures/brown-dirt-norm.jpg");
texture.setWrap(Texture.WrapMode.Repeat);
terrainMaterial.setTexture("NormalMap", texture);
texture = assets.loadTexture("Textures/brown-dirt2.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("DiffuseMapX", texture);
//texture = assets.loadTexture("Textures/test-norm.png");
texture = assets.loadTexture("Textures/brown-dirt-norm.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("NormalMapX", texture);
texture = assets.loadTexture("Textures/brown-dirt2.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("DiffuseMapZ", texture);
//texture = assets.loadTexture("Textures/test-norm.png");
texture = assets.loadTexture("Textures/brown-dirt-norm.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("NormalMapZ", texture);
// Now the default down texture... we use a separate one
// and DiffuseMap will be used for the top
texture = assets.loadTexture("Textures/canvas128.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("DiffuseMapY", texture);
//texture = assets.loadTexture("Textures/test-norm.png");
texture = assets.loadTexture("Textures/brown-dirt-norm.jpg");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("NormalMapY", texture);
// We will need a noise texture soon, might as well set it
// now
texture = assets.loadTexture("Textures/noise-x3-512.png");
texture.setWrap(WrapMode.Repeat);
terrainMaterial.setTexture("Noise", texture);
return terrainMaterial;
}
public enum CellSize {
Size_8(8, 3),
Size_16(16, 4),
Size_32(32, 5),
Size_64(64, 6);
private final int size;
private final int bitshift;
CellSize(int size, int bitshift) {
this.size = size;
this.bitshift = bitshift;
}
public int getSize() {
return size;
}
public int getBitshift() {
return bitshift;
}
}
}
The plugin system is really usefull. I had some spare time to work on a ecs plugin this week-end
I would like to create an entity automatically when a spatial or any node is added to the scene graph. Is there any way to listen those kind of event ?
You can’t subscribe to an event when any item was added, but your code creates entities, so you can create an event yourself and trigger it when an entity is created.
There is a basic example here:
https://github.com/jayfella/event-manager
You would call ServiceManager.getService(SimpleEventManager.class)
to get an instance of the event manager in the DevKit.
I hope that helps.
Adding some DevKitAppStates.
The Filter State allows you to load and save filters in a specific order. It also creates a settings page for each filter - and those settings are saved. You can then load that straight into your game.
The great thing is that the filters are visible in the DevKit so you can customize the filters exactly how you want them and just load that state in your game.
The second thing is the beginnings of a 2D heightmap terrain generator. Select the size, add some noise, add a few layers of noise, etc… and click generate and it will generate. It will add it to the scene - and you can save it as a regular j3o.
And just to note - Sponsorship and donations drive JME forward. Please donate if you feel you are able
I’ve updated the devkt:
id "com.jayfella.jme-devkit" version "0.0.20"
dependencies {
implementation "com.jayfella:jme-swing-devkit:1.0.8"
}
I’ve also created a plugin for filters: It basically allows you to load/save filters, retaining filter order and settings. I’ve also written an extension for this plugin to add the ShaderBlowEx filters.
Just add it as a dependency to your project and go to “Window → Run AppState” in the DevKit.
dependencies {
implementation "com.jayfella:devkit-filters-plugin:1.0.0"
}
I’ve also created a terrain generator which is yet to be released - stay tuned!
I’ll be creating a new thread shortly officially releasing the DevKit and showcasing the whole thing.
Is there a good way to force the property pane refresh? For example, I want to add properties to a pane when new particle influencers are added.
Yes. You can replace sections entirely - which is what I do. I just rebuild that section.
Here is an example of how the material tab is updated when a user chooses a different material on a geometry.
is it a standalone application like jmonkeybuilder written by @javasabr?
Yes. You just add the gradle plugin to your build.gradle
file and run the task runSdk
. It can be run from any IDE or from command line.
plugins {
id 'java'
id "com.jayfella.jme-devkit" version "0.0.21"
}
From the command-line you could run the following command in your project folder:
./gradlew runSdk
In IntelliJ you can run the task from within the IDE. On the Gradle tab (usually on the right-hand side), find the task and double-click it.
I’m sure other IDE’s provide this function too somewhere.
And so on… .It’s just gradle task being run.
Tried giving the new plugin based SDK a shot, but…
> Task :runSdk FAILED
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/grizeldi/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-log4j12/1.7.25/110cefe2df103412849d72ef7a67e4e91e4266b4/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/grizeldi/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.5/d1e34a4525e08873703fdaad6c6284f944f8ca8f/log4j-slf4j-impl-2.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
24 Sep 2020 17:52:04 [ INFO | Main ] Engine Version: jMonkeyEngine 3.3.2-stable
24 Sep 2020 17:52:04 [ INFO | Main ] Operating System: Linux amd64
24 Sep 2020 17:52:04 [ INFO | DevKitConfig ] Reading configuration file: devkit/devkit-config.json
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.jayfella.devkit.service.ServiceManager.registerService(ServiceManager.java:104)
at com.jayfella.devkit.Main.<init>(Main.java:75)
at com.jayfella.devkit.Main.main(Main.java:60)
Caused by: java.lang.SecurityException: sealing violation: can't seal package org.lwjgl.opengl: already loaded
at java.net.URLClassLoader.getAndVerifyPackage(URLClassLoader.java:407)
at java.net.URLClassLoader.definePackageInternal(URLClassLoader.java:420)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:452)
at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
Caused by: java.lang.SecurityException: sealing violation: can't seal package org.lwjgl.opengl: already loaded
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.jme3.system.JmeDesktopSystem.newContextLwjgl(JmeDesktopSystem.java:196)
at com.jme3.system.JmeDesktopSystem.newContext(JmeDesktopSystem.java:279)
at com.jme3.system.JmeSystem.newContext(JmeSystem.java:159)
at com.jme3.app.LegacyApplication.createCanvas(LegacyApplication.java:510)
at com.jayfella.devkit.service.JmeEngineService.<init>(JmeEngineService.java:27)
at com.jayfella.devkit.service.impl.JmeEngineServiceImpl.<init>(JmeEngineServiceImpl.java:19)
... 7 more
24 Sep 2020 17:52:04 [ ERROR | Main ] Unable to create instance of JmeEngineService. Exiting.
Execution failed for task ':runSdk'.
> Process 'command '/usr/lib/jvm/java-8-openjdk-amd64/bin/java'' finished with non-zero exit value 255
Intellij or command line gradle, ubuntu 20.04. Happens both with java 8 and 11.