So, I’m getting a weird bug. I’m currently trying to implement billboarding spatials. I know I should do this in the shaders, but I have no experience in GLSL and have no idea how to write shaders.
However, if I billboard every object in the world, the game runs pretty slow. So, I’m currently checking wheter the spatial is being culled or not. This way, I only rotate the objects that are being rendered and, as such, improve performance.
The problem is, I’m getting an IllegalStateException:
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: props.TallGrass
And I’m not modifying the scene from another thread. The code is running in the update() method of my appState, so I think that should be thread-safe.
My VisualAppState:
public class VisualAppState extends AbstractAppState {
private SimpleApplication app;
private EntityData ed;
private EntitySet entities;
public final HashMap<EntityId, Spatial> models;
public final HashMap<String, BatchNode> batchedNodes;
private ModelLoader modelLoader;
public VisualAppState() {
this.models = new HashMap<EntityId, Spatial>();
this.batchedNodes = new HashMap<String, BatchNode>();
}
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = (SimpleApplication)app;
this.ed = this.app.getStateManager().getState(EntityDataState.class).getEntityData();
this.entities = this.ed.getEntities(Transform.class, Model.class);
this.modelLoader = new ModelLoader(this.app.getAssetManager());
}
@Override
public void update(float tpf) {
if(entities.applyChanges()) {
removeModels(entities.getRemovedEntities());
addModels(entities.getAddedEntities());
updateModels(entities.getChangedEntities());
}
for(BatchNode batchNode : batchedNodes.values()) {
if(batchNode.getUserData("batched").equals(false)) {
batchNode.setUserData("batched", true);
batchNode.batch();
}
}
for(Spatial s : batchedNodes.get(ModelLoader.Models.P_TallGrass.name).getChildren()) {
if(!s.checkCulling(app.getCamera())) {
s.lookAt(app.getCamera().getLocation(), Vector3f.UNIT_Y);
}
}
}
private void removeModels(Set<Entity> entities) {
for(Entity e : entities) {
Spatial s = models.remove(e.getId());
BatchNode batchNode = batchedNodes.get(s.getName());
s.removeFromParent();
if(batchNode.getChildren().isEmpty()) {
batchNode.removeFromParent();
batchedNodes.remove(s.getName());
}
}
}
private void addModels(Set<Entity> entities) {
for(Entity e : entities) {
Spatial s = createVisual(e);
models.put(e.getId(), s);
updateModelSpatial(e, s);
//this.app.getRootNode().attachChild(s);
BatchNode batchNode = batchedNodes.get(s.getName());
if(batchNode == null) {
batchNode = new BatchNode();
batchedNodes.put(s.getName(), batchNode);
this.app.getRootNode().attachChild(batchNode);
}
batchNode.setUserData("batched", false);
batchNode.attachChild(s);
}
}
private void updateModels(Set<Entity> entities) {
for(Entity e : entities) {
Spatial s = models.get(e.getId());
updateModelSpatial(e, s);
}
}
private void updateModelSpatial(Entity e, Spatial s) {
Transform t = e.get(Transform.class);
s.setLocalTranslation(t.getPosition());
s.setLocalRotation(t.getRotation());
s.setLocalScale(t.getScale());
}
private Spatial createVisual(Entity e) {
Model m = e.get(Model.class);
return modelLoader.load(m.getModel());
}
@Override
public void cleanup() {
super.cleanup();
}
}
The entity “props.TallGrass” is added trough GameplayAppState:
public class GameplayAppState extends AbstractAppState{
private SimpleApplication app;
private AssetManager assetManager;
private Node rootNode;
private Camera cam;
private FlyByCamera flyCam;
@Override
public void initialize(AppStateManager stateManager, Application app) {
// Sets local variables
this.app = (SimpleApplication) app;
this.assetManager = this.app.getAssetManager();
this.rootNode = this.app.getRootNode();
this.cam = this.app.getCamera();
this.flyCam = this.app.getFlyByCamera();
// Loads the terrain
Spatial terrain = assetManager.loadModel("Scenes/Terrain/Terrain.j3o");
rootNode.attachChild(terrain);
// Adds normals
//TangentBinormalGenerator.generate(terrain);
// Sets the camera's position and velocity
cam.setLocation(new Vector3f(0, 3, 0));
flyCam.setMoveSpeed(16);
// Adds lights
DirectionalLight sun = new DirectionalLight();
sun.setDirection(new Vector3f(-1, -10, -5));
sun.setColor(ColorRGBA.White);
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.Gray);
rootNode.addLight(sun);
rootNode.addLight(ambient);
// Gets the Terrain object
Node temp = (Node)terrain;
Terrain ground = (Terrain)temp.getChild("Terrain");
temp.getChild("Terrain").getControl(TerrainLodControl.class).setCamera(cam);
// Gets the entityData
EntityData ed = stateManager.getState(EntityDataState.class).getEntityData();
for(int i = 0; i < 1000; i++) {
int x = FastMath.nextRandomInt(-256, 256);
int z = FastMath.nextRandomInt(-256, 256);
float y = ground.getHeight(new Vector2f(x, z));
EntityId id = ed.createEntity();
ed.setComponents(id,
new Transform(new Vector3f(x, y, z)),
new Model(Models.P_Tree1.name));
}
for(int i = 0; i < 1000; i++) {
int x = FastMath.nextRandomInt(-256, 256);
int z = FastMath.nextRandomInt(-256, 256);
float y = ground.getHeight(new Vector2f(x, z));
EntityId id = ed.createEntity();
ed.setComponents(id,
new Transform(new Vector3f(x, y, z)),
new Model(Models.P_TallGrass.name));
}
// Continues to initialize
super.initialize(stateManager, app);
}
@Override
public void update(float tpf) {
}
@Override
public void cleanup() {
// Continues to cleanup
super.cleanup();
}
}
On a similar note, I am also getting randomly an exception saying “Compare function result changed!”. I wasn’t able to replicate that exception now, but as soon as it comes out again, I’ll add to the thread.
Thanks,
Ev1lbl0w