package com.vitco.core; import com.threed.jpct.Config; import com.threed.jpct.Logger; import com.threed.jpct.SimpleVector; import com.vitco.Main; import com.vitco.core.container.DrawContainer; import com.vitco.core.data.Data; import com.vitco.core.data.container.Voxel; import com.vitco.core.world.AbstractCWorld; import com.vitco.core.world.CWorld; import com.vitco.layout.content.ViewPrototype; import com.vitco.layout.content.mainview.components.BoundingBoxDimChooser; import com.vitco.manager.action.types.StateActionPrototype; import com.vitco.manager.async.AsyncAction; import com.vitco.manager.async.AsyncActionManager; import com.vitco.manager.pref.PrefChangeListener; import com.vitco.settings.DynamicSettings; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; /** * Rendering functionality of this World (data + overlay). Uses a DrawContainer. * * Defines the basic objects: data, world, camera. */ public abstract class EngineViewPrototype extends ViewPrototype { // var & setter protected AsyncActionManager asyncActionManager; @Autowired public final void setAsyncActionManager(AsyncActionManager asyncActionManager) { this.asyncActionManager = asyncActionManager; } // var & setter protected Data data; @Autowired public final void setData(Data data) { this.data = data; } // the world-required objects protected final AbstractCWorld world; protected final AbstractCWorld selectedVoxelsWorld; protected final CCamera camera; // information about this container protected final int side; private boolean localMouseDown = false; private static boolean globalMouseDown = false; // reference private final EngineViewPrototype thisInstance = this; // the container that we draw on (instance) protected final DrawContainer container; // true if high resolution rendering active private static boolean highQualityActive = true; // =============================== // Access underlying container // =============================== // access to the underlying z buffer public final int[] getZBuffer() { return container.getZBuffer(); } // access to the underlying pixels public final int[] getPixels() { return container.getPixels(); } // access to the container dimensions public final int getHeight() { return container.getHeight(); } public final int getWidth() { return container.getWidth(); } // get the image rendered in container in high quality public BufferedImage getImage() { return container.getImage(); } // get the depth image public BufferedImage getDepthImage() { return container.getDepthImage(); } // ============================== // updating of world with voxels // ============================== // overlay ghost voxels protected abstract SimpleVector[][] getGhostOverlay(); // true if overlay has changed since last call protected abstract boolean updateGhostOverlay(); // voxel data getter to be defined protected abstract Voxel[] getVoxels(); // voxel data getter to be defined (for changed voxels since last call) protected abstract Voxel[][] getChangedVoxels(); // changed selected voxels since last call protected abstract Voxel[][] getChangedSelectedVoxels(); // helper - make sure the voxel objects in the world are up to date // and also trigger refresh for redraws private void updateWorldWithVoxels() { // only retrieve the changed voxels Voxel[][] changed = getChangedVoxels(); if (changed[0] == null) { // rebuild world.clear(); } else { // remove individual voxel for (Voxel remove : changed[0]) { world.clearPosition(remove); } } for (Voxel added : changed[1]) { world.updateVoxel(added); } asyncActionManager.addAsyncAction(new AsyncAction("asyncWorld" + side) { @Override public void performAction() { container.doNotSkipNextWorldRender(); forceRepaint(); if (!world.refreshWorld()) { asyncActionManager.addAsyncAction(this); } } }); // only retrieve the changed voxels changed = getChangedSelectedVoxels(); if (changed[0] == null) { // rebuild selectedVoxelsWorld.clear(); } else { // remove individual voxel for (Voxel remove : changed[0]) { selectedVoxelsWorld.clearPosition(remove); } } for (Voxel added : changed[1]) { selectedVoxelsWorld.updateVoxel(added); } asyncActionManager.addAsyncAction(new AsyncAction("asyncSelWorld" + side) { @Override public void performAction() { container.doNotSkipNextWorldRender(); forceRepaint(); if (!selectedVoxelsWorld.refreshWorld()) { asyncActionManager.addAsyncAction(this); } } }); } // true iff the world does not need to be updated with voxels private boolean worldVoxelCurrent = false; // force update of world before next draw protected final void invalidateVoxels() { if (localMouseDown) { // instant update needed for interaction refreshVoxels(true); } else { worldVoxelCurrent = false; } } // force true: make sure the voxels are valid "right now", no matter what private void refreshVoxels(boolean force) { if (!worldVoxelCurrent || force) { worldVoxelCurrent = true; updateWorldWithVoxels(); } } // ============================== // END: updating of world with voxels // ============================== // makes sure the repaint doesn't get called too often // (only one in queue at a time) // Note: we still need this even though repaint() is thread safe, // as it still pushes another event on the Swing queue protected final void forceRepaint() { if (!container.isRepainting()) { container.setRepainting(true); final boolean skipNextWorldRender = container.isSkipNextWorldRender(); final boolean doNotSkipNextWorldRender = container.isDoNotSkipNextWorldRender(); container.resetSkipRenderFlags(); asyncActionManager.addAsyncAction(new AsyncAction("repaint" + side) { @Override public void performAction() { if (skipNextWorldRender) { container.skipNextWorldRender(); } if (doNotSkipNextWorldRender) { container.doNotSkipNextWorldRender(); } container.render(); // this is thread save (but will execute with a delay!) // AWT event queue is ok as long as this method is fast to execute container.repaint(); } @Override public boolean ready() { return !globalMouseDown || localMouseDown; } }); } else { container.bufferWorldRenderFlags(); container.setNeedRepainting(true); } } // true if the action was executed private static boolean initialized = false; @PostConstruct public final void startup() { // handle quality changes // ------------------------ // only execute once if (!initialized) { initialized = true; // load/initialize the render quality setting if (preferences.contains("high_quality_active")) { highQualityActive = preferences.loadBoolean("high_quality_active"); } else { preferences.storeBoolean("high_quality_active", highQualityActive); } // change variables accordingly (this is added first and hence always "notified" first) preferences.addPrefChangeListener("high_quality_active", new PrefChangeListener() { @Override public void onPrefChange(Object o) { DynamicSettings.setSamplingMode(highQualityActive); } }); // register button change actionManager.registerAction("toggle_render_quality", new StateActionPrototype() { @Override public void action(ActionEvent actionEvent) { // toggle and store highQualityActive = !highQualityActive; preferences.storeBoolean("high_quality_active", highQualityActive); } @Override public boolean getStatus() { return highQualityActive; } }); // load the bounding box size (if stored) if (preferences.contains("bounding_box_size")) { int[] bounding_box_size = (int[]) preferences.loadObject("bounding_box_size"); DynamicSettings.setPlaneSizeX(bounding_box_size[0]); DynamicSettings.setPlaneSizeY(bounding_box_size[1]); DynamicSettings.setPlaneSizeZ(bounding_box_size[2]); } // register component to change bounding box size complexActionManager.registerAction("resize_bounding_box_component", new BoundingBoxDimChooser(DynamicSettings.VOXEL_PLANE_SIZE_X, DynamicSettings.VOXEL_PLANE_SIZE_Y, DynamicSettings.VOXEL_PLANE_SIZE_Z, langSelector) { private void updateBoundingBox() { // store size in preferences preferences.storeObject("bounding_box_size", new int[]{ DynamicSettings.VOXEL_PLANE_SIZE_X, DynamicSettings.VOXEL_PLANE_SIZE_Y, DynamicSettings.VOXEL_PLANE_SIZE_Z }); } // -- below are change listeners @Override public void onXChange(int newVal) { DynamicSettings.setPlaneSizeX(newVal); updateBoundingBox(); } @Override public void onYChange(int newVal) { DynamicSettings.setPlaneSizeY(newVal); updateBoundingBox(); } @Override public void onZChange(int newVal) { DynamicSettings.setPlaneSizeZ(newVal); updateBoundingBox(); } }); } // initialize the container // --------------------------- // register bg color change preferences.addPrefChangeListener("engine_view_bg_color", new PrefChangeListener() { @Override public void onPrefChange(Object newValue) { container.setBgColor((Color)newValue); forceRepaint(); } }); // register preview plane change preferences.addPrefChangeListener("engine_view_voxel_preview_plane", new PrefChangeListener() { @Override public void onPrefChange(Object newValue) { container.setPreviewPlane((Integer)newValue); } }); // register camera change listener camera.addCameraChangeListener(new CameraChangeListener() { @Override public void onCameraChange() { container.setCameraChanged(true); } }); // initialize basic parameters container.setCamera(camera); container.setSelectedVoxelsWorld(selectedVoxelsWorld); container.setWorld(world); container.setAsyncActionManager(asyncActionManager); container.setData(data); // final initialization container.init(); // force rebuilding and repainting of this container when the quality changes // Note: This requires the async action manager to be set (!) preferences.addPrefChangeListener("high_quality_active", new PrefChangeListener() { @Override public void onPrefChange(Object o) { container.refreshBuffer(); forceRepaint(); } }); // force repainting of this container when the bounding box size changes preferences.addPrefChangeListener("bounding_box_size", new PrefChangeListener() { @Override public void onPrefChange(Object newValue) { forceRepaint(); } }); } @PreDestroy public final void cleanup() { container.cleanup(); } // only perform these actions once (even if the class is instantiated several times) static { Config.fadeoutLight = false; //Config.maxPolysVisible = 10000; Config.useMultipleThreads = true; Config.maxNumberOfCores = Runtime.getRuntime().availableProcessors(); Config.loadBalancingStrategy = 1; // default 0 // usually not worth it (http://www.jpct.net/doc/com/threed/jpct/Config.html#useMultiThreadedBlitting) Config.useMultiThreadedBlitting = true; //default false // not really needed (and is very slow when using images, i.e. large textures 512x512) // Config.mipmap = true; // disable anti-aliasing for textures Config.texelFilter = false; Config.mtDebug = false; if (Main.isDebugMode()) { // for debug mode show also warnings Logger.setLogLevel(Logger.LL_ERRORS_AND_WARNINGS); } else { Logger.setLogLevel(Logger.LL_ONLY_ERRORS); } } // constructor protected EngineViewPrototype(Integer side) { // make sure side defaults to -1 if (side == null || side < 0 || side > 2) { side = -1; } this.side = side; // initialize the container container = new DrawContainer(side) { @Override protected void forceRepaint() { thisInstance.forceRepaint(); } @Override protected boolean updateGhostOverlay() { return thisInstance.updateGhostOverlay(); } @Override protected SimpleVector[][] getGhostOverlay() { return thisInstance.getGhostOverlay(); } @Override protected void refreshVoxels(boolean b) { thisInstance.refreshVoxels(b); } }; // handle mouse events for this container container.addMouseListener(new MouseAdapter() { private void handleMouseState(final MouseEvent e, final boolean flag) { asyncActionManager.addAsyncAction(new AsyncAction() { @Override public void performAction() { // only consider mouse events that don't involve middle mouse (camera) if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) == 0) { localMouseDown = globalMouseDown = flag; } } }); } @Override public void mousePressed(MouseEvent e) { handleMouseState(e, true); } @Override public void mouseReleased(MouseEvent e) { handleMouseState(e, false); } }); // NOTE: The sum should be not more than 40k (!) // define the max poly count for this world Config.maxPolysVisible = side == -1 ? 10000 : 2500; // set up world objects world = new CWorld(true, side, false); // define the max poly count for this selected world Config.maxPolysVisible = side == -1 ? 10000 : 2500; // no culling, since we want to see all selected voxels (in the main view only) selectedVoxelsWorld = new CWorld(side != -1, side, true); // define camera camera = new CCamera(); world.setCameraTo(camera); selectedVoxelsWorld.setCameraTo(camera); // lighting (1,1,1) = true color world.setAmbientLight(1, 1, 1); } }