I am getting this error because I am updating node from separate thread.
Apr 28, 2023 7:19:41 PM com.jme3.app.LegacyApplication handleError
SEVERE: Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.IllegalStateException: Scene graph is not properly updated for rendering.
State was changed after rootNode.updateGeometricState() call.
Make sure you do not modify the scene from another thread!
Problem spatial name: Root Node
at com.jme3.scene.Spatial.checkCulling(Spatial.java:367)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:792)
...
Apr 28, 2023 7:19:41 PM com.jme3.system.JmeSystemDelegate lambda$new$0
WARNING: JmeDialogsFactory implementation not found.
Uncaught exception thrown in Thread[jME3 Main,5,main]
IllegalStateException: Scene graph is not properly updated for rendering.
State was changed after rootNode.updateGeometricState() call.
Make sure you do not modify the scene from another thread!
Problem spatial name: Root Node
Here is my code. I have used separate thread to show how nodes are added one by one at some interval. How to achieve the same without separate thread?
I tried Thread.sleep without introducing separate thread but it dosen’t help.
import java.awt.DisplayMode;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.Arrow;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GridPanel;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.Panel;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.BorderLayout;
import com.simsilica.lemur.grid.ArrayGridModel;
import com.simsilica.lemur.style.BaseStyles;
public class Main extends SimpleApplication {
public static void main(String[] args) {
Main app = new Main();
app.fullscreen(new AppSettings(true));
app.start();
app.restart();
}
public void fullscreen(AppSettings settings) {
GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
DisplayMode[] modes = device.getDisplayModes();
int i = modes.length - 1; // note: there are usually several, let's pick the first
settings.setResolution(modes[i].getWidth(), modes[i].getHeight());
settings.setFrequency(modes[i].getRefreshRate());
settings.setBitsPerPixel(modes[i].getBitDepth());
settings.setFullscreen(true);
setSettings(settings);
}
private Grid grid = new Grid();
private int gridSize = 1;
private SecureRandom dice = new SecureRandom();
private ArrayList<Tile> defaultTiles = new ArrayList<>();
private Node GEOM_NODE = new Node("GEOM_NODE");
private TextField gridSizeSetting;
private void loadTiles() {
try {
String path = "C:\\Users\\Pankaj\\Documents\\JME_Projs\\WFCTest\\src\\main\\resources\\mapping.json";
Gson gson = new Gson();
Type tileListType = new TypeToken<ArrayList<Tile>>() {
}.getType();
defaultTiles = gson.fromJson(new FileReader(path), tileListType);
} catch (JsonIOException | JsonSyntaxException | FileNotFoundException e) {
e.printStackTrace();
}
}
private void clear() {
defaultTiles.clear();
grid.clear();
GEOM_NODE.detachAllChildren();
}
private void renderPattern() {
clear();
loadTiles();
try {
gridSize = Integer.parseInt(gridSizeSetting.getText().trim());
} catch (Exception e) {
}
setAssociation(defaultTiles);
Tile tile = new Tile(defaultTiles.get(0));
loadNode(tile);
grid.add(tile);
addNeighbours(tile);
// render(table.getAllTiles());
}
private void plotTiles() {
Point p = new Point();
defaultTiles.forEach((t) -> {
t.getPosition().setLocation(p);
loadNode(t);
if (p.y <= 5)
p.y += 1;
else {
p.y = 0;
p.x += 1;
}
});
render(defaultTiles);
}
private void render(Collection<Tile> tiles) {
tiles.forEach((_tile) -> {
transformNode(_tile);
GEOM_NODE.attachChild(_tile.getNode());
});
}
private void loadNode(Tile tile) {
Spatial geom = assetManager.loadModel(tile.getModel());
if (tile.getRotation() > 0) {
geom.setLocalRotation(getRotation(tile.getRotation()));
}
// attachCoordinateAxes((Node) geom);
Node node = new Node(tile.getName());
node.attachChild(geom);
addText(tile.getName(), node);
tile.setNode(node);
}
private void addNeighbours(Tile tile) {
Map<Integer, Point> openPositions = grid.findOpenPositions(tile.getPosition());
List<Tile> tmp = new LinkedList<>();
for (Map.Entry<Integer, Point> positions : openPositions.entrySet()) {
int side = positions.getKey();
if (!isUnderGrid(side, tile)) {
continue;
}
Map<Integer, Tile> neighbours = grid.findNeighbours(positions.getValue());
Set<Tile> connections = new HashSet<>(4);
neighbours.forEach((oppositSide, _tile) -> {
if (connections.isEmpty()) {
connections.addAll(_tile.getConnections(_tile.getOppositIndex(oppositSide)));
} else {
connections.retainAll(_tile.getConnections(_tile.getOppositIndex(oppositSide)));
}
});
if (!connections.isEmpty()) {
Tile n = new Tile(connections.toArray(new Tile[connections.size()])[dice.nextInt(connections.size())]);
n.getPosition().setLocation(positions.getValue());
grid.add(n);
loadNode(n);
tmp.add(n);
}
}
tmp.forEach((t) -> addNeighbours(t));
transformNode(tile);
GEOM_NODE.attachChild(tile.getNode());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private final float tileSize = 0.3048f * 2;
private void transformNode(Tile tile) {
Point point = tile.getPosition();
Vector3f trans = tile.getNode().getLocalTranslation();
tile.getNode().setLocalTranslation(trans.addLocal(tileSize * point.x, 0.0f, tileSize * point.y));
}
List<BitmapText> texts = new ArrayList<>(1000);
private void addText(String text, Node node) {
BitmapText textNode = new BitmapText(guiFont);
textNode.setSize(guiFont.getCharSet().getRenderedSize());
textNode.setText(text);
textNode.setLocalTranslation(0, 0.2f, 0);
textNode.scale(.003f);
node.attachChild(textNode);
texts.add(textNode);
Quaternion q = new Quaternion();
q.lookAt(cam.getLocation(), cam.getUp());
textNode.setLocalRotation(q);
}
private boolean isUnderGrid(int side, Tile tile) {
return switch (side) {
case 0, 2:
yield Math.abs(tile.getPosition().y) < gridSize;
case 1, 3:
yield Math.abs(tile.getPosition().x) < gridSize;
default:
yield false;
};
}
private void setAssociation(ArrayList<Tile> tiles) {
@SuppressWarnings("unchecked")
ArrayList<Tile> tileCopies = (ArrayList<Tile>) tiles.clone();
tiles.forEach((tile) -> tile.map(tileCopies));
}
private Quaternion getRotation(int rotation) {
Quaternion roll = new Quaternion();
switch (rotation) {
case 90 -> roll.fromAngleAxis(FastMath.PI / 2, new Vector3f(0, 1, 0));
case 180 -> roll.fromAngleAxis(FastMath.PI, new Vector3f(0, 1, 0));
case 270 -> roll.fromAngleAxis(FastMath.PI + (FastMath.PI / 2), new Vector3f(0, 1, 0));
}
return roll;
}
private void putShape(Node n, Mesh shape, ColorRGBA color) {
Geometry g = new Geometry("coordinate axis", shape);
Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.getAdditionalRenderState().setWireframe(true);
mat.getAdditionalRenderState().setLineWidth(4);
mat.setColor("Color", color);
g.setMaterial(mat);
n.attachChild(g);
}
private void attachCoordinateAxes(Node n) {
Arrow arrow = new Arrow(Vector3f.UNIT_X);
putShape(n, arrow, ColorRGBA.Red);
arrow = new Arrow(Vector3f.UNIT_Y);
putShape(n, arrow, ColorRGBA.Green);
arrow = new Arrow(Vector3f.UNIT_Z);
putShape(n, arrow, ColorRGBA.Blue);
}
@Override
public void simpleUpdate(float tpf) {
/*
* texts.forEach((text)->{ Quaternion q = new Quaternion();
* q.lookAt(cam.getLocation(),cam.getUp()); text.setLocalRotation(q); });
*/
}
@Override
public void simpleRender(RenderManager rm) {
// TODO: add render code
}
@Override
public void simpleInitApp() {
setDisplayStatView(false);
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
setUI();
setBG();
setLight();
resetCam();
rootNode.attachChild(GEOM_NODE);
}
private void setLight() {
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.White);
rootNode.addLight(ambient);
DirectionalLight sun = new DirectionalLight();
sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
sun.setColor(ColorRGBA.White);
rootNode.addLight(sun);
}
private void setBG() {
viewPort.setBackgroundColor(ColorRGBA.Blue);
}
private void setUI() {
GuiGlobals.initialize(this);
BaseStyles.loadGlassStyle();
GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
Container ui = new Container();
guiNode.attachChild(ui);
ui.addChild(new Label("Control"));
Container pan = new Container(new BorderLayout());
pan.addChild(new Label("Set Grid Size: "), BorderLayout.Position.West);
gridSizeSetting = pan.addChild(new TextField(Integer.toString(gridSize)), BorderLayout.Position.Center);
ui.addChild(pan);
Button loadTiles = ui.addChild(new Button("Load Tiles"));
Button renderSampleTiles = ui.addChild(new Button("Plot Tiles"));
Button renderPattern = ui.addChild(new Button("Generate Pattern"));
Button clear = ui.addChild(new Button("Clear"));
Button resetCam = ui.addChild(new Button("Reset Camera"));
clear.addClickCommands((c) -> clear());
loadTiles.addClickCommands((c) -> loadTiles());
renderSampleTiles.addClickCommands((c) -> plotTiles());
renderPattern.addClickCommands((c) -> new Thread(()->renderPattern()).start());
// renderPattern.addClickCommands((c) -> renderPattern());
resetCam.addClickCommands((c) -> resetCam());
int w = settings.getWidth();
int h = settings.getHeight();
Vector3f size = ui.getPreferredSize();
w = (int) (w - size.x);
ui.setLocalTranslation(w, h, 0);
}
private void resetCam() {
// Camera Position: (0.0, 3.1202602, 1.451486)
// Camera Rotation: (0.0015113321, 0.81680024, -0.57691467, 0.0021397257)
// Camera Direction: (0.0017516376, -0.9424546, -0.33432972)
cam.setLocation(new Vector3f(0.0f, 3.1202602f, 1.451486f));
cam.setRotation(new Quaternion(0.0015113321f, 0.81680024f, -0.57691467f, 0.0021397257f));
}
}