package com.vitco.layout.content.mainview;
import com.jidesoft.action.CommandMenuBar;
import com.threed.jpct.Config;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.util.Light;
import com.vitco.core.CameraChangeListener;
import com.vitco.core.EngineInteractionPrototype;
import com.vitco.core.data.container.Voxel;
import com.vitco.core.world.WorldManager;
import com.vitco.manager.action.types.StateActionPrototype;
import com.vitco.manager.action.types.SwitchActionPrototype;
import com.vitco.manager.async.AsyncAction;
import com.vitco.manager.pref.PrefChangeListener;
import com.vitco.manager.thread.LifeTimeThread;
import com.vitco.manager.thread.ThreadManagerInterface;
import com.vitco.settings.VitcoSettings;
import org.springframework.beans.factory.annotation.Autowired;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.Random;
/**
* Creates the main view instance and attaches the specific user interaction.
*/
public class MainView extends EngineInteractionPrototype implements MainViewInterface {
private ThreadManagerInterface threadManager;
// set the action handler
@Autowired
public final void setThreadManager(ThreadManagerInterface threadManager) {
this.threadManager = threadManager;
}
protected MainView() {
super(-1);
}
// --------------
// we don't have an ghost overlay to draw
@Override
protected SimpleVector[][] getGhostOverlay() {
return new SimpleVector[0][];
}
@Override
protected boolean updateGhostOverlay() {
return false;
}
// -----------------------
@Override
protected Voxel[] getVoxels() {
return data.getVisibleLayerVoxel();
}
@Override
protected Voxel[][] getChangedVoxels() {
return data.getNewVisibleLayerVoxel("main_view");
}
@Override
protected Voxel[][] getChangedSelectedVoxels() {
return data.getNewSelectedVoxel("main_view");
}
// true if the "grid mode is on"
private boolean gridModeOn = true;
// true if the "light is on"
private boolean staticLightOn = false;
private void moveLightBehindCamera(Light light) {
SimpleVector direction = camera.getPosition().normalize();
direction.scalarMul(2000000f);
light.setPosition(direction);
}
// true if using "bounding box"
private boolean useBoundingBox = true;
@Override
public final JPanel build() {
// make sure we can see into the distance
world.setClippingPlanes(Config.nearPlane, VitcoSettings.MAIN_VIEW_ZOOM_OUT_LIMIT * 2);
selectedVoxelsWorld.setClippingPlanes(Config.nearPlane,VitcoSettings.MAIN_VIEW_ZOOM_OUT_LIMIT*2);
// toggle shader
// enable/disable shader
actionManager.registerAction("toggle_shader_enabled", new AbstractAction() {
private boolean enabled = false;
@Override
public void actionPerformed(ActionEvent e) {
enabled = !enabled;
container.enableShader(enabled);
if (enabled) {
console.addLine("Shader is enabled.");
} else {
console.addLine("Shader is disabled.");
}
forceRepaint();
}
});
// start/stop test mode (rapid camera rotation)
actionManager.registerAction("toggle_rapid_camera_testing",new AbstractAction() {
private boolean active = false;
private final Random rand = new Random();
private LifeTimeThread thread;
private float dirx = 0f;
private float diry = 0f;
private final float maxSpeed = 1f;
private final float speedChange = 0.1f;
@Override
public void actionPerformed(ActionEvent e) {
active = !active;
if (active) {
thread = new LifeTimeThread() {
@Override
public void loop() throws InterruptedException {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
dirx = Math.min(maxSpeed, Math.max(-maxSpeed, dirx + (rand.nextFloat() - 0.5f)*speedChange));
diry = Math.min(maxSpeed, Math.max(-maxSpeed, diry + (rand.nextFloat() - 0.5f)*speedChange));
camera.rotate(dirx, diry);
forceRepaint();
}
});
synchronized (this) {
thread.wait(50);
}
}
};
threadManager.manage(thread);
console.addLine("Test activated.");
} else {
threadManager.remove(thread);
console.addLine("Test deactivated.");
}
}
});
// lighting
final Light dark_light = WorldManager.addLight(world, SimpleVector.ORIGIN, -10);
final Light light1 = WorldManager.addLight(world, new SimpleVector(-1500000, -2000000, -1000000), 3);
final Light light2 = WorldManager.addLight(world, new SimpleVector(1500000, 2000000, 1000000), 3);
camera.addCameraChangeListener(new CameraChangeListener() {
@Override
public void onCameraChange() {
if (!staticLightOn) {
moveLightBehindCamera(dark_light);
}
}
});
if (!preferences.contains("light_mode_active")) {
preferences.storeBoolean("light_mode_active", staticLightOn);
}
// react to changes on the light status
preferences.addPrefChangeListener("light_mode_active", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
staticLightOn = (Boolean) o;
if (staticLightOn) {
dark_light.disable();
light1.enable();
light2.enable();
world.setAmbientLight(0, 0, 0);
} else {
dark_light.enable();
light1.disable();
light2.disable();
world.setAmbientLight(60, 60, 60);
}
moveLightBehindCamera(dark_light);
forceRepaint();
}
});
// react to changes on the light status
preferences.addPrefChangeListener("light_mode_active", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
staticLightOn = (Boolean) o;
if (staticLightOn) {
dark_light.disable();
light1.enable();
light2.enable();
world.setAmbientLight(0, 0, 0);
} else {
dark_light.enable();
light1.disable();
light2.disable();
world.setAmbientLight(60, 60, 60);
}
moveLightBehindCamera(dark_light);
forceRepaint();
}
});
// register the toggle light mode action (always possible)
actionManager.registerAction("toggle_light_mode", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
preferences.storeBoolean("light_mode_active", !staticLightOn);
}
@Override
public boolean getStatus() {
return !staticLightOn;
}
});
if (!preferences.contains("grid_mode_active")) {
preferences.storeBoolean("grid_mode_active", gridModeOn);
}
// react to changes on the grid status
preferences.addPrefChangeListener("grid_mode_active", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
gridModeOn = (Boolean) o;
world.setBorder(gridModeOn);
forceRepaint();
}
});
// register the toggle grid mode action (always possible)
actionManager.registerAction("toggle_grid_mode", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
preferences.storeBoolean("grid_mode_active", !gridModeOn);
}
@Override
public boolean getStatus() {
return gridModeOn;
}
});
// camera settings
camera.setFOVLimits(VitcoSettings.MAIN_VIEW_ZOOM_FOV,VitcoSettings.MAIN_VIEW_ZOOM_FOV);
camera.setFOV(VitcoSettings.MAIN_VIEW_ZOOM_FOV);
camera.setZoomLimits(VitcoSettings.MAIN_VIEW_ZOOM_IN_LIMIT, VitcoSettings.MAIN_VIEW_ZOOM_OUT_LIMIT);
camera.setView(VitcoSettings.MAIN_VIEW_CAMERA_POSITION); // camera initial position
// =============== BOUNDING BOX
// add the bounding box (texture)
final Object3D[] boundingBox = {WorldManager.getGridPlane()};
final int[] boundingBoxId = {world.addObject(boundingBox[0])};
// listen to bounding box size changes and change texture object (only in main view)
// Note: This doesn't need a repaint since that is done in a more general listener
preferences.addPrefChangeListener("bounding_box_size", new PrefChangeListener() {
@Override
public void onPrefChange(Object newValue) {
// generate new bounding box
boundingBox[0] = WorldManager.getGridPlane();
boundingBox[0].setVisibility(useBoundingBox);
// remove old bounding box
world.removeObject(boundingBoxId[0]);
// add new bounding box
boundingBoxId[0] = world.addObject(boundingBox[0]);
}
});
// listen to bounding box visibility changes
preferences.addPrefChangeListener("use_bounding_box", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
useBoundingBox = (Boolean)o;
container.setDrawBoundingBox(useBoundingBox); // overlay part
boundingBox[0].setVisibility(useBoundingBox); // texture part
// redraw container
container.doNotSkipNextWorldRender();
forceRepaint();
}
});
// make sure the preference is set
if (!preferences.contains("use_bounding_box")) {
preferences.storeBoolean("use_bounding_box", useBoundingBox);
}
actionManager.registerAction("toggle_bounding_box", new StateActionPrototype() {
@Override
public void action(ActionEvent actionEvent) {
preferences.storeBoolean("use_bounding_box", !useBoundingBox);
}
@Override
public boolean getStatus() {
return useBoundingBox;
}
});
// ===============
// user mouse input - change camera position
MouseAdapter mouseAdapter = new MouseAdapter() {
@Override
public void mouseWheelMoved(final MouseWheelEvent e) { // scroll = zoom in and out
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
int rotation = e.getWheelRotation();
if (rotation < 0) {
camera.zoomIn(Math.abs(rotation) * VitcoSettings.MAIN_VIEW_ZOOM_SPEED_SLOW);
} else {
camera.zoomOut(rotation * VitcoSettings.MAIN_VIEW_ZOOM_SPEED_SLOW);
}
voxelAdapter.replayHover();
container.doNotSkipNextWorldRender();
forceRepaint();
}
});
}
private Point leftMouseDown = null;
private Point rightMouseDown = null;
@Override
public void mousePressed(final MouseEvent e) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
switch (e.getModifiers() & (MouseEvent.BUTTON1_MASK | MouseEvent.BUTTON2_MASK | MouseEvent.BUTTON3_MASK)) {
case MouseEvent.BUTTON1_MASK: case MouseEvent.BUTTON2_MASK: leftMouseDown = e.getPoint(); break;
case MouseEvent.BUTTON3_MASK: rightMouseDown = e.getPoint(); break;
default: break;
}
}
});
}
@Override
public void mouseReleased(final MouseEvent e) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
switch (e.getModifiers() & (MouseEvent.BUTTON1_MASK | MouseEvent.BUTTON2_MASK | MouseEvent.BUTTON3_MASK)) {
case MouseEvent.BUTTON1_MASK: case MouseEvent.BUTTON2_MASK: leftMouseDown = null; break;
case MouseEvent.BUTTON3_MASK: rightMouseDown = null; break;
default: break;
}
}
});
}
@Override
public void mouseDragged(final MouseEvent e) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
if (leftMouseDown != null) {
camera.rotate(e.getX() - leftMouseDown.x, e.getY() - leftMouseDown.y);
leftMouseDown.x = e.getX();
leftMouseDown.y = e.getY();
container.doNotSkipNextWorldRender();
forceRepaint();
} else if (rightMouseDown != null) {
camera.shift(e.getX() - rightMouseDown.x, e.getY() - rightMouseDown.y, VitcoSettings.MAIN_VIEW_SIDE_MOVE_FACTOR);
rightMouseDown.x = e.getX();
rightMouseDown.y = e.getY();
container.doNotSkipNextWorldRender();
forceRepaint();
}
}
});
}
};
container.addMouseWheelListener(mouseAdapter);
container.addMouseMotionListener(mouseAdapter);
container.addMouseListener(mouseAdapter);
abstract class CameraAction extends SwitchActionPrototype {
protected abstract void step();
private LifeTimeThread thread = null;
@Override
public void switchOn() {
if (thread == null) {
thread = new LifeTimeThread() {
@Override
public void loop() throws InterruptedException {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
step();
voxelAdapter.replayHover();
forceRepaint();
}
});
synchronized (this) {
thread.wait(25);
}
}
};
threadManager.manage(thread);
}
}
@Override
public void switchOff() {
if (thread != null) {
threadManager.remove(thread);
thread = null;
}
}
}
// register zoom buttons
actionManager.registerAction("mainview_zoom_in", new CameraAction() {
@Override
protected void step() {
camera.zoomIn(VitcoSettings.MAIN_VIEW_BUTTON_ZOOM_SPEED);
}
});
actionManager.registerAction("mainview_zoom_out", new CameraAction() {
@Override
protected void step() {
camera.zoomOut(VitcoSettings.MAIN_VIEW_BUTTON_ZOOM_SPEED);
}
});
// register rotate buttons
actionManager.registerAction("mainview_rotate_left", new CameraAction() {
@Override
protected void step() {
camera.rotate(VitcoSettings.MAIN_VIEW_BUTTON_ROTATE_SIDEWAYS_SPEED, 0);
}
});
actionManager.registerAction("mainview_rotate_right", new CameraAction() {
@Override
protected void step() {
camera.rotate(-VitcoSettings.MAIN_VIEW_BUTTON_ROTATE_SIDEWAYS_SPEED, 0);
}
});
actionManager.registerAction("mainview_rotate_up", new CameraAction() {
@Override
protected void step() {
camera.rotate(0, VitcoSettings.MAIN_VIEW_BUTTON_ROTATE_OVER_SPEED);
}
});
actionManager.registerAction("mainview_rotate_down", new CameraAction() {
@Override
protected void step() {
camera.rotate(0, -VitcoSettings.MAIN_VIEW_BUTTON_ROTATE_OVER_SPEED);
}
});
// register move buttons
actionManager.registerAction("mainview_move_left", new CameraAction() {
@Override
protected void step() {
camera.shift(1, 0, VitcoSettings.MAIN_VIEW_BUTTON_MOVE_SIDEWAYS_SPEED);
}
});
actionManager.registerAction("mainview_move_right", new CameraAction() {
@Override
protected void step() {
camera.shift(-1, 0, VitcoSettings.MAIN_VIEW_BUTTON_MOVE_SIDEWAYS_SPEED);
}
});
actionManager.registerAction("mainview_move_up", new CameraAction() {
@Override
protected void step() {
camera.shift(0, 1, VitcoSettings.MAIN_VIEW_BUTTON_MOVE_OVER_SPEED);
}
});
actionManager.registerAction("mainview_move_down", new CameraAction() {
@Override
protected void step() {
camera.shift(0, -1, VitcoSettings.MAIN_VIEW_BUTTON_MOVE_OVER_SPEED);
}
});
// register reset action
actionManager.registerAction("reset_main_view_camera", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
camera.setView(VitcoSettings.MAIN_VIEW_CAMERA_POSITION);
container.doNotSkipNextWorldRender();
forceRepaint();
}
});
actionManager.registerAction("center_main_view_camera", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
camera.setCenterShift(new SimpleVector(
-preferences.loadInteger("currentplane_sideview3") * VitcoSettings.VOXEL_SIZE,
-preferences.loadInteger("currentplane_sideview2") * VitcoSettings.VOXEL_SIZE,
-preferences.loadInteger("currentplane_sideview1") * VitcoSettings.VOXEL_SIZE
));
container.doNotSkipNextWorldRender();
forceRepaint();
}
});
// register "align view to side plane" actions
actionManager.registerAction("align_main_to_sideview1", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
SimpleVector pos = new SimpleVector(VitcoSettings.SIDE_VIEW1_CAMERA_POSITION);
pos.makeEqualLength(camera.getPosition());
camera.setView(pos);
forceRepaint();
}
});
actionManager.registerAction("align_main_to_sideview2", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
SimpleVector pos = new SimpleVector(VitcoSettings.SIDE_VIEW2_CAMERA_POSITION);
pos.makeEqualLength(camera.getPosition());
camera.setView(pos);
forceRepaint();
}
});
actionManager.registerAction("align_main_to_sideview3", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
SimpleVector pos = new SimpleVector(VitcoSettings.SIDE_VIEW3_CAMERA_POSITION);
pos.makeEqualLength(camera.getPosition());
camera.setView(pos);
forceRepaint();
}
});
// register button action for wireframe toggle
actionManager.registerAction("main_window_toggle_wireframe", new StateActionPrototype() {
private boolean useWireFrame = false; // always false on startup
@Override
public void action(ActionEvent actionEvent) {
useWireFrame = !useWireFrame;
container.useWireFrame(useWireFrame);
forceRepaint();
}
@Override
public boolean getStatus() {
return useWireFrame;
}
});
// holds menu and render area (container)
final JPanel wrapper = new JPanel();
wrapper.setLayout(new BorderLayout());
// prevent "flickering" when swapping windows
preferences.addPrefChangeListener("engine_view_bg_color", new PrefChangeListener() {
@Override
public void onPrefChange(Object o) {
wrapper.setBackground((Color) o);
}
});
// create menu
CommandMenuBar menuPanel = new CommandMenuBar();
//menuPanel.setOrientation(1); // top down orientation
menuPanel.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
menuGenerator.buildMenuFromXML(menuPanel, "com/vitco/layout/content/mainview/toolbar.xml");
// so the background doesn't show
menuPanel.setOpaque(true);
// add to wrapper
wrapper.add(menuPanel, BorderLayout.SOUTH);
wrapper.add(container, BorderLayout.CENTER);
return wrapper;
}
}