package de.venjinx.editor.debug;
import java.util.HashMap;
import org.bushe.swing.event.EventTopicSubscriber;
import com.jme3.app.Application;
import com.jme3.app.FlyCamAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.app.state.ScreenshotAppState;
import com.jme3.asset.AssetManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.FlyByCamera;
import com.jme3.input.InputManager;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.debug.Grid;
import com.jme3.scene.shape.Sphere;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.CheckBoxStateChangedEvent;
import de.lessvoid.nifty.controls.DropDownSelectionChangedEvent;
import de.lessvoid.nifty.controls.MenuItemActivatedEvent;
import de.lessvoid.nifty.controls.SliderChangedEvent;
import de.lessvoid.nifty.controls.TreeItem;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.venjinx.jme3.VoxelObjectNode;
import de.venjinx.jme3.VoxelState;
import de.venjinx.jme3.scenegraph.Scenegraph;
import de.venjinx.jme3.scenegraph.ScenegraphListener;
import de.venjinx.jme3.scenegraph.ScenegraphNode;
import de.venjinx.jme3.tests.AdvancedApplication;
import de.venjinx.util.Keys;
import de.venjinx.util.Materials;
public class DebugState extends AbstractAppState implements ActionListener,
AnalogListener,
ScreenController,
ScenegraphListener,
EventTopicSubscriber<MenuItemActivatedEvent<String>> {
private static String[] inputKeys = { Keys.SHOW_COORD_GRID, Keys.SHOW_NORMALS,
Keys.SHOW_WIREFRAME, Keys.DEBUG_CAM_ACTIVATE,
Keys.SELECT_NODE, Keys.SHOW_CONTEXT_MENU };
// Simple application stuff
private SimpleApplication simpleApp;
private AssetManager am;
private InputManager im;
private AppStateManager sm;
private Camera cam;
private FlyByCamera flyCam;
// Advanced application stuff
private Scenegraph scenegraph;
// GUI stuff
private DebugGUI gui;
private BitmapFont guiFont;
private BillboardControl bc = new BillboardControl();
private HashMap<Spatial, TreeItem<String>> treeItems = new HashMap<>();
private HashMap<String, AbstractAppState> states = new HashMap<>();
// Debug stuff
private Geometry camPosGeom;
private Geometry activeGeometry;
private ScenegraphNode debugNode = new ScenegraphNode("Debug");
private HashMap<Spatial, DebugNode> debugNodes = new HashMap<>();
private ScenegraphNode coordNode;
private DebugMouse mouse;
private DirectionalLight directionalLight;
private AmbientLight ambientLight;
private ScreenshotAppState screenshotState;
// Voxel world stuff
private VoxelState voxelState;
private Geometry worldCalcBoundGeom;
// Flags
private boolean showGrid = true;
private boolean showWire = false;
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
// Initialize simple application stuff
if (app instanceof SimpleApplication) {
simpleApp = (SimpleApplication) app;
am = app.getAssetManager();
im = app.getInputManager();
sm = app.getStateManager();
cam = app.getCamera();
DebugMaterials.setAssetManager(am);
Sphere s = new Sphere(4, 4, 1);
camPosGeom = new Geometry("Camera position", s);
camPosGeom.setMaterial(new Material(am, Materials.UNSHADED));
debugNode.attachChild(camPosGeom);
simpleApp.getRootNode().attachChild(debugNode);
setEnabled(true);
}
if (!sm.hasState(sm.getState(FlyCamAppState.class))) {
flyCam = new DebugCam(cam);
flyCam.registerWithInput(im);
flyCam.setDragToRotate(true);
} else flyCam = simpleApp.getFlyByCamera();
// Initialize advanced application stuff
if (app instanceof AdvancedApplication) {
scenegraph = ((AdvancedApplication) app).getScenegraph();
scenegraph.addScenegraphListener(this);
}
// Initialize GUI stuff
gui = new DebugGUI(simpleApp, this);
gui.getNifty().fromXml("interface/debugInterface.xml", "DebugInfo", this);
gui.getNifty().setIgnoreKeyboardEvents(true);
// gui.getNifty().setDebugOptionPanelColors(true);
guiFont = am.loadFont("Interface/Fonts/Default.fnt");
// Initialize debug stuff
coordNode = createCoordinationNode(256, 1);
debugNode.attachChild(coordNode);
debugNode.updateModelBound();
mouse = sm.getState(DebugMouse.class);
if (mouse == null) {
mouse = new DebugMouse();
Material mat = new Material(app.getAssetManager(), Materials.UNSHADED);
mat.setColor("Color", ColorRGBA.Orange);
mouse.setMaterial(mat);
sm.attach(mouse);
debugNode.attachChild(mouse.getPointer());
}
directionalLight = new DirectionalLight();
directionalLight.setName("Debug light");
directionalLight.setColor(ColorRGBA.White);
directionalLight.setDirection(new Vector3f(.5f, -1f, -.1f).normalizeLocal());
simpleApp.getRootNode().addLight(directionalLight);
ambientLight = new AmbientLight();
ambientLight.setColor(ColorRGBA.Gray);
simpleApp.getRootNode().addLight(ambientLight);
screenshotState = new ScreenshotAppState();
screenshotState.setFilePath("d:/");
sm.attach(screenshotState);
// Initialize voxel world stuff
if (sm.getState(VoxelState.class) != null) {
voxelState = sm.getState(VoxelState.class);
Sphere s = new Sphere(32, 32, voxelState.getCalcBound().getRadius());
s.setMode(Mode.Lines);
worldCalcBoundGeom = new Geometry("Voxel world calculation bound", s);
worldCalcBoundGeom.setMaterial(new Material(am, Materials.UNSHADED));
// debugNode.attachChild(worldCalcBoundGeom);
addState(voxelState);
}
loadDfltKeys();
}
@Override
public void update(float tpf) {
gui.updateInterface();
if ((voxelState != null) && voxelState.isEnabled())
camPosGeom.setLocalTranslation(cam.getLocation());
// worldCalcBoundGeom.setLocalTranslation(world.getCalcBound().getCenter());
if (!(simpleApp instanceof AdvancedApplication)) {
treeItems.clear();
TreeItem<String> sceneRootItem = createTreeItem(simpleApp.getRootNode());
TreeItem<String> viewRootItem = new TreeItem<String>();
viewRootItem.addTreeItem(sceneRootItem);
viewRootItem.setExpanded(true);
gui.scenegraphView.setRoot(viewRootItem);
}
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals(Keys.DEBUG_CAM_ACTIVATE) && !isPressed)
flyCam.setDragToRotate(!flyCam.isDragToRotate());
if (name.equals(Keys.SHOW_COORD_GRID) && isPressed)
gui.showGridCheckBox.setChecked((!showGrid));
if (name.equals(Keys.SHOW_WIREFRAME) && isPressed)
gui.showWireframeCheckBox.setChecked(!showWire);
if (name.equals(Keys.SELECT_NODE) && isPressed)
if (mouse.getHitGeometry() != null) {
activeGeometry = mouse.getHitGeometry();
TreeItem<String> item = treeItems.get(activeGeometry);
gui.scenegraphView.select(item);
}
if (name.equals(Keys.SHOW_CONTEXT_MENU) && !isPressed)
if (mouse.getHitGeometry() != null) {
activeGeometry = mouse.getHitGeometry();
gui.showContextMenu(true, activeGeometry);
}
}
@Override
public void onAnalog(String name, float value, float tpf) {
}
@Override
public void onEvent(String id, MenuItemActivatedEvent<String> event) {
VoxelObjectNode vObject;
Node parent;
DebugNode debug = debugNodes.get(activeGeometry);
if (debug == null) {
debug = new DebugNode(activeGeometry);
debugNodes.put(activeGeometry, debug);
}
gui.showContextMenu(false, activeGeometry);
debug.setLocalTranslation(activeGeometry.getWorldTranslation());
debugNode.attachChild(debug);
switch (event.getItem()) {
case DebugGUI.MENU_SHOW_BOUND:
debug.showBound();
break;
case DebugGUI.MENU_SHOW_CHUNK_BOUNDS:
parent = activeGeometry.getParent();
while (!(parent instanceof VoxelObjectNode))
parent = parent.getParent();
vObject = (VoxelObjectNode) parent;
vObject.showChunkBounds();
break;
case DebugGUI.MENU_SHOW_CHUNK_LODS:
parent = activeGeometry.getParent();
while (!(parent instanceof VoxelObjectNode))
parent = parent.getParent();
vObject = (VoxelObjectNode) parent;
vObject.showChunkLODs();
break;
case DebugGUI.MENU_SHOW_NORMALS:
debug.showNormals();
break;
case DebugGUI.MENU_SHOW_WIREFRAME:
debug.showWireframe();
break;
case DebugGUI.MENU_SHOW_VOXEL_GRID:
debug.showVoxelGrid();
break;
}
if (debug.getChildren().size() == 0)
debugNode.detachChild(debug);
}
@Override
public void onGraphChanged(GraphChangedEvent e) {
TreeItem<String> parent = treeItems.get(e.getParent());
TreeItem<String> child = treeItems.get(e.getChild());
String value = "";
switch (e.getType()) {
case ATTACHED:
if (child == null)
child = createTreeItem(e.getChild());
gui.scenegraphView.insert(parent, child);
value += "(" + (e.getParent().getChildren().size() + 1) + ") ";
break;
case DETACHED:
gui.scenegraphView.remove(child);
value += "(" + (e.getParent().getChildren().size() - 1) + ") ";
break;
}
value += e.getParent().toString();
parent.setValue(value);
gui.scenegraphView.refresh();
}
@NiftyEventSubscriber(pattern = ".*CheckBox")
public void onCheckBoxStateChanged(String id, CheckBoxStateChangedEvent event) {
boolean checked = event.isChecked();
switch (id) {
case "showGridCheckBox":
showGrid(checked);
break;
case "showWireframeCheckBox":
showWireFrame(simpleApp.getRootNode(), checked);
break;
case "pauseStateCheckBox":
states.get(gui.stateDropDown.getSelection()).setEnabled(checked);
break;
}
}
@NiftyEventSubscriber(pattern = ".*Slider")
public void onSliderStateChanged(String id, SliderChangedEvent event) {
System.out.println("slider change");
switch (id) {
case "cameraSpeedSlider":
flyCam.setMoveSpeed(event.getValue());
break;
case "viewDistanceSlider":
cam.setFrustumFar(event.getValue());
cam.update();
if (voxelState != null) {
voxelState.getCalcBound().setRadius(cam.getFrustumFar() * .9f);
Sphere s = new Sphere(32, 32, voxelState.getCalcBound().getRadius());
s.setMode(Mode.Lines);
worldCalcBoundGeom.setMesh(s);
}
break;
}
}
@NiftyEventSubscriber(pattern = ".*DropDown")
public void onDropDownSelectionChanged(String id,
DropDownSelectionChangedEvent<String> event) {
switch (id) {
case "stateSelectDropDown":
AbstractAppState state = states.get(gui.stateDropDown.getSelection());
gui.pauseStateCheckBox.setChecked(state.isEnabled());
break;
}
}
public void listBoxItemClicked() {
System.out.println("tree click");
}
public void expandButtonClicked() {
System.out.println("expand");
}
public void mouseClick() {
System.out.println("mouseClick");
}
public void downClick() {
System.out.println("rightClick");
}
public void click() {
System.out.println("---------click");
}
public void upClick() {
System.out.println("leftClick");
}
public void showGrid(boolean enabled) {
showGrid = enabled;
if (showGrid)
debugNode.attachChild(coordNode);
else debugNode.detachChild(coordNode);
}
public void showWireFrame(Node node, boolean show) {
showWire = show;
Geometry geom;
for (Spatial s : node.getChildren())
if (s instanceof Node) {
if (s != debugNode)
showWireFrame((Node) s, show);
} else
if (s instanceof Geometry) {
geom = (Geometry) s;
geom.getMaterial().getAdditionalRenderState().setWireframe(showWire);
}
}
public void addState(AbstractAppState state) {
states.put(state.getClass().getSimpleName(), state);
gui.addStateToControl(state.getClass().getSimpleName());
}
public DebugMouse getMouse() {
return mouse;
}
public ScenegraphNode getDebugNode() {
return debugNode;
}
public FlyByCamera getDebugCamera() {
return flyCam;
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (sm.hasState(sm.getState(StatsAppState.class))) {
sm.getState(StatsAppState.class).setEnabled(!enabled);
simpleApp.setDisplayFps(!enabled);
simpleApp.setDisplayStatView(!enabled);
}
if (enabled)
im.addListener(this);
else im.removeListener(this);
}
/**
* Creates one arrow for each axis colored in x=red, y=green, z=blue
* and one grid plane for each plane colored xz=red, xy=green, yz=blue.
*
* @author Torge Rothe
* @param size
* - int - The size of the arrows and grids
* @param segSize
* - int - The segment size of the grids.
* @return Node - A node with 3 direction arrows for x, y, z axis and 3 coordination
* grid planes for xz, xy, yz planes.
*/
private ScenegraphNode createCoordinationNode(int size, int segSize) {
float offset = size / 2;
Material mat;
Geometry xAxis;
Geometry yAxis;
Geometry zAxis;
Geometry xzGrid;
Geometry xyGrid;
Geometry yzGrid;
ScenegraphNode coordNode = new ScenegraphNode("Coodination Grid");
ScenegraphNode labelNode = new ScenegraphNode("Grid labels");
coordNode.attachChild(labelNode);
// create x-axis
Arrow arrowX = new Arrow(new Vector3f(size, 0.0f, 0.0f));
xAxis = new Geometry("X-Axis", arrowX);
mat = new Material(am, Materials.UNSHADED);
mat.setColor("Color", ColorRGBA.Red);
xAxis.setMaterial(mat);
// create xy-grid
Grid xyPlane = new Grid(size, size, segSize);
xyGrid = new Geometry("XY-Plane", xyPlane);
xyGrid.setMaterial(mat);
xyGrid.rotateUpTo(new Vector3f(0.0f, 0.0f, 1.0f));
xyGrid.setLocalTranslation(new Vector3f(-offset, offset, 0.0f));
// create y-axis
Arrow arrowY = new Arrow(new Vector3f(0.0f, size, 0.0f));
yAxis = new Geometry("Y-Axis", arrowY);
mat = new Material(am, Materials.UNSHADED);
mat.setColor("Color", ColorRGBA.Green);
yAxis.setMaterial(mat);
// create yz-grid
Grid yzPlane = new Grid(size, size, segSize);
yzGrid = new Geometry("YZ-Plane", yzPlane);
yzGrid.setMaterial(mat);
yzGrid.rotateUpTo(new Vector3f(1.0f, 0.0f, 0.0f));
yzGrid.setLocalTranslation(new Vector3f(0.0f, offset, -offset));
// create z-axis
Arrow arrowZ = new Arrow(new Vector3f(0.0f, 0.0f, size));
zAxis = new Geometry("Z-Axis", arrowZ);
mat = new Material(am, Materials.UNSHADED);
mat.setColor("Color", ColorRGBA.Blue);
zAxis.setMaterial(mat);
// create xz-grid
Grid xzPlane = new Grid(size, size, segSize);
xzGrid = new Geometry("XZ-Plane", xzPlane);
xzGrid.setMaterial(mat);
xzGrid.rotateUpTo(new Vector3f(0.0f, 1.0f, 0.0f));
xzGrid.setLocalTranslation(new Vector3f(-offset, 0.0f, -offset));
// attach arrows to coordination node
coordNode.attachChild(xAxis);
coordNode.attachChild(yAxis);
coordNode.attachChild(zAxis);
// attach grids to coordination node
coordNode.attachChild(xyGrid);
coordNode.attachChild(xzGrid);
coordNode.attachChild(yzGrid);
Vector3f pos = new Vector3f(0, 0, 0);
BitmapText vertId = new BitmapText(guiFont);
vertId.setSize(.1f);
vertId.setText("x0, y0, z0");
vertId.setName("Gridlabel - x0, y0, z0");
vertId.setLocalTranslation(pos);
vertId.addControl(bc.cloneForSpatial(vertId));
labelNode.attachChild(vertId);
for (int x = 1; x < offset; x++) {
pos.set(x, 0, 0);
vertId = new BitmapText(guiFont);
vertId.setSize(.2f);
vertId.setText("x" + x);
vertId.setName("Gridlabel - x" + x);
vertId.setLocalTranslation(pos);
vertId.addControl(bc.cloneForSpatial(vertId));
labelNode.attachChild(vertId);
}
for (int y = 1; y < offset; y++) {
pos.set(0, y, 0);
vertId = new BitmapText(guiFont);
vertId.setSize(.2f);
vertId.setText("y" + y);
vertId.setName("Gridlabel - y" + y);
vertId.setLocalTranslation(pos);
vertId.addControl(bc.cloneForSpatial(vertId));
labelNode.attachChild(vertId);
}
for (int z = 1; z < offset; z++) {
pos.set(0, 0, z);
vertId = new BitmapText(guiFont);
vertId.setSize(.2f);
vertId.setText("z" + z);
vertId.setName("Gridlabel - z" + z);
vertId.setLocalTranslation(pos);
vertId.addControl(bc.cloneForSpatial(vertId));
labelNode.attachChild(vertId);
}
return coordNode;
}
private TreeItem<String> createTreeItem(Spatial spatial) {
TreeItem<String> newItem, tmpItem;
String value = "";
if (spatial instanceof Node)
value += "(" + ((Node) spatial).getChildren().size() + ") ";
value += spatial.toString();
newItem = new TreeItem<>(value);
treeItems.put(spatial, newItem);
if (spatial instanceof Node) {
Node sNode = (Node) spatial;
for (Spatial sp : sNode.getChildren()) {
tmpItem = treeItems.get(sp);
if (tmpItem == null)
tmpItem = createTreeItem(sp);
newItem.addTreeItem(tmpItem);
}
}
return newItem;
}
private void loadDfltKeys() {
im.addMapping(Keys.DEBUG_CAM_ACTIVATE, Keys.KEY_LCONTROL);
im.addMapping(Keys.SHOW_COORD_GRID, Keys.KEY_G);
im.addMapping(Keys.SHOW_NORMALS, Keys.KEY_1);
im.addMapping(Keys.SHOW_WIREFRAME, Keys.KEY_2);
im.addMapping(Keys.SELECT_NODE, Keys.L_MOUSE);
im.addMapping(Keys.SHOW_CONTEXT_MENU, Keys.R_MOUSE);
im.addListener(this, inputKeys);
}
@Override
public void cleanup() {
super.cleanup();
for (String s : inputKeys)
im.deleteMapping(s);
im.removeListener(this);
simpleApp.getRootNode().removeLight(ambientLight);
simpleApp.getRootNode().removeLight(directionalLight);
simpleApp.getRootNode().detachChild(debugNode);
}
@Override
public void bind(Nifty nifty, Screen screen) {
gui.init(screen);
gui.showGridCheckBox.setChecked(showGrid);
gui.showWireframeCheckBox.setChecked(showWire);
gui.cameraSpeedSlider.setValue(flyCam.getMoveSpeed());
gui.viewDistanceSlider.setValue(cam.getFrustumFar());
TreeItem<String> sceneRootItem = createTreeItem(simpleApp.getRootNode());
TreeItem<String> viewRootItem = new TreeItem<String>();
viewRootItem.addTreeItem(sceneRootItem);
viewRootItem.setExpanded(true);
gui.scenegraphView.setRoot(viewRootItem);
}
@Override
public void onStartScreen() {
}
@Override
public void onEndScreen() {
}
}