Java tutorial
/* Copyright (c) 2012-2014 Jesper qvist <jesper@llbit.se> * * This file is part of Chunky. * * Chunky is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chunky is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with Chunky. If not, see <http://www.gnu.org/licenses/>. */ package se.llbit.chunky.renderer.ui; import java.awt.Color; import java.awt.Component; import java.awt.Desktop; import java.awt.Dimension; import java.awt.FileDialog; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParseException; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.GroupLayout; import javax.swing.GroupLayout.Alignment; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSlider; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.AbstractDocument; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.PersistentSettings; import se.llbit.chunky.main.Chunky; import se.llbit.chunky.renderer.Postprocess; import se.llbit.chunky.renderer.RenderConstants; import se.llbit.chunky.renderer.RenderContext; import se.llbit.chunky.renderer.RenderManager; import se.llbit.chunky.renderer.RenderState; import se.llbit.chunky.renderer.RenderStatusListener; import se.llbit.chunky.renderer.projection.ProjectionMode; import se.llbit.chunky.renderer.scene.Camera; import se.llbit.chunky.renderer.scene.CameraPreset; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.renderer.scene.SceneManager; import se.llbit.chunky.renderer.scene.Sky; import se.llbit.chunky.renderer.scene.Sky.SkyMode; import se.llbit.chunky.renderer.scene.Sun; import se.llbit.chunky.resources.Texture; import se.llbit.chunky.ui.CenteredFileDialog; import se.llbit.chunky.world.Chunk; import se.llbit.chunky.world.ChunkPosition; import se.llbit.chunky.world.ChunkView; import se.llbit.chunky.world.Icon; import se.llbit.chunky.world.World; import se.llbit.json.JsonMember; import se.llbit.json.JsonObject; import se.llbit.log.Log; import se.llbit.math.QuickMath; import se.llbit.math.Ray; import se.llbit.math.Vector3d; import se.llbit.math.Vector4d; import se.llbit.ui.Adjuster; /** * Render Controls dialog. * @author Jesper qvist <jesper@llbit.se> */ @SuppressWarnings("serial") public class RenderControls extends JDialog implements ViewListener, RenderStatusListener { private static final int[] dumpFrequencies = { 50, 100, 500, 1000, 5000 }; private final RenderManager renderMan; private final SceneManager sceneMan; private final Chunk3DView view; private final Chunky chunky; /** * Number format for current locale. */ private final NumberFormat numberFormat = NumberFormat.getInstance(); private final JSlider skymapRotationSlider = new JSlider(); private final JSlider lightProbeRotationSlider = new JSlider(); private final JSlider skyboxRotationSlider = new JSlider(); private final JButton loadSkymapBtn = new JButton(); private final JButton loadLightProbeBtn = new JButton(); private final JPanel simulatedSkyPanel = new JPanel(); private final JPanel skymapPanel = new JPanel(); private final JPanel lightProbePanel = new JPanel(); private final JPanel skyGradientPanel = new JPanel(); private final JPanel skyboxPanel = new JPanel(); private final JComboBox canvasSizeCB = new JComboBox(); private final JComboBox cameraPreset = new JComboBox(); private final JComboBox customPreset = new JComboBox(); private final JComboBox projectionMode = new JComboBox(); private final JButton startRenderBtn = new JButton(); private final JCheckBox enableEmitters = new JCheckBox(); private final JCheckBox directLight = new JCheckBox(); private final JButton saveSceneBtn = new JButton(); private final JButton loadSceneBtn = new JButton(); private final JButton openSceneDirBtn = new JButton(); private final JButton saveFrameBtn = new JButton(); private final JCheckBox stillWaterCB = new JCheckBox(); private final JTextField sceneNameField = new JTextField(); private final JLabel sceneNameLbl = new JLabel(); private final JCheckBox biomeColorsCB = new JCheckBox(); private final JButton stopRenderBtn = new JButton(); private final JCheckBox atmosphereEnabled = new JCheckBox(); private final JCheckBox transparentSky = new JCheckBox(); private final JCheckBox volumetricFogEnabled = new JCheckBox(); private final JCheckBox cloudsEnabled = new JCheckBox(); private final RenderContext context; private final JButton showPreviewBtn = new JButton(); private final JLabel renderTimeLbl = new JLabel(); private final JLabel sppLbl = new JLabel(); private final JProgressBar progressBar = new JProgressBar(); private final JLabel progressLbl = new JLabel(); private final JComboBox postprocessCB = new JComboBox(); private final JComboBox skyModeCB = new JComboBox(); private final JButton changeSunColorBtn = new JButton("Change Sun Color"); private final JLabel etaLbl = new JLabel(); private final JCheckBox waterWorldCB = new JCheckBox(); private final JCheckBox waterColorCB = new JCheckBox("Use custom water color"); private final JButton waterColorBtn = new JButton("Change Water Color"); private final JTextField waterHeightField = new JTextField(); private final JButton applyWaterHeightBtn = new JButton("Apply"); private final DecimalFormat decimalFormat = new DecimalFormat(); private final JCheckBox saveDumpsCB = new JCheckBox(); private final JComboBox dumpFrequencyCB = new JComboBox(); private final JCheckBox saveSnapshotsCB = new JCheckBox("Save snapshot for each dump"); private final JLabel dumpFrequencyLbl = new JLabel(" frames"); private final JTextField cameraX = new JTextField(); private final JTextField cameraY = new JTextField(); private final JTextField cameraZ = new JTextField(); private final JTextField cameraYaw = new JTextField(); private final JTextField cameraPitch = new JTextField(); private final JTextField cameraRoll = new JTextField(); private final JButton mergeDumpBtn = new JButton("Merge Render Dump"); private final JCheckBox shutdownWhenDoneCB = new JCheckBox("Shutdown computer when render completes"); private final JRadioButton v90Btn = new JRadioButton("90"); private final JRadioButton v180Btn = new JRadioButton("180"); private final JTabbedPane tabbedPane = new JTabbedPane(); private final Adjuster skyHorizonOffset = new Adjuster("Horizon offset", "Moves the horizon below the actual horizon", 0.0, 1.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sky().setHorizonOffset(newValue); } @Override public void update() { set(renderMan.scene().sky().getHorizonOffset()); } }; private final Adjuster targetSPP = new Adjuster("Target SPP", "The target Samples Per Pixel", 100, 100000) { { setClampMax(false); setClampMin(false); setLogarithmicMode(); } @Override public void valueChanged(double newValue) { int value = (int) newValue; renderMan.setTargetSPP(value); startRenderBtn.setEnabled(renderMan.getCurrentSPP() < value); } @Override public void update() { set(renderMan.scene().getTargetSPP()); } }; private final Adjuster waterOpacity = new Adjuster("Water Opacity", "Decides how opaque the water surface appears", 0.0, 1.0) { @Override public void valueChanged(double newValue) { renderMan.scene().setWaterOpacity(newValue); } @Override public void update() { set(renderMan.scene().getWaterOpacity()); } }; private final Adjuster waterVisibility = new Adjuster("Water Visibility", "Visibility depth under water", 0.0, 20.0) { { setClampMax(false); } @Override public void valueChanged(double newValue) { renderMan.scene().setWaterVisibility(newValue); } @Override public void update() { set(renderMan.scene().getWaterVisibility()); } }; private final Adjuster numThreads = new Adjuster("Render threads", "Number of rendering threads", RenderConstants.NUM_RENDER_THREADS_MIN, 20) { { setClampMax(false); } @Override public void valueChanged(double newValue) { int value = (int) newValue; PersistentSettings.setNumThreads(value); renderMan.setNumThreads(value); } @Override public void update() { set(PersistentSettings.getNumThreads()); } }; private final Adjuster yCutoff = new Adjuster("Y cutoff", "Blocks below the Y cutoff are not loaded", 0, Chunk.Y_MAX) { @Override public void valueChanged(double newValue) { int value = (int) newValue; PersistentSettings.setYCutoff(value); } @Override public void update() { set(PersistentSettings.getYCutoff()); } }; private final Adjuster cpuLoad = new Adjuster("CPU load", "CPU load percentage", 1, 100) { @Override public void valueChanged(double newValue) { int value = (int) newValue; PersistentSettings.setCPULoad(value); renderMan.setCPULoad(value); } @Override public void update() { set(PersistentSettings.getCPULoad()); } }; private final Adjuster rayDepth = new Adjuster("Ray depth", "Sets the recursive ray depth", 1, 25) { @Override public void valueChanged(double newValue) { renderMan.scene().setRayDepth((int) newValue); } @Override public void update() { set(renderMan.scene().getRayDepth()); } }; private final Adjuster emitterIntensity = new Adjuster("Emitter intensity", "Light intensity modifier for emitters", Scene.MIN_EMITTER_INTENSITY, Scene.MAX_EMITTER_INTENSITY) { { setLogarithmicMode(); } @Override public void valueChanged(double newValue) { renderMan.scene().setEmitterIntensity(newValue); } @Override public void update() { set(renderMan.scene().getEmitterIntensity()); } }; private final Adjuster skyLight = new Adjuster("Sky Light", "Sky light intensity modifier", Sky.MIN_INTENSITY, Sky.MAX_INTENSITY) { { setLogarithmicMode(); setSliderMin(0.01); } @Override public void valueChanged(double newValue) { renderMan.scene().sky().setSkyLight(newValue); } @Override public void update() { set(renderMan.scene().sky().getSkyLight()); } }; private final Adjuster sunIntensity = new Adjuster("Sun Intensity", "Sunlight intensity modifier", Sun.MIN_INTENSITY, Sun.MAX_INTENSITY) { { setLogarithmicMode(); } @Override public void valueChanged(double newValue) { renderMan.scene().sun().setIntensity(newValue); } @Override public void update() { set(renderMan.scene().sun().getIntensity()); } }; private final Adjuster sunAzimuth = new Adjuster("Sun azimuth", "The angle towards the sun from north", 0.0, 360.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sun().setAzimuth(QuickMath.degToRad(newValue)); } @Override public void update() { set(QuickMath.radToDeg(renderMan.scene().sun().getAzimuth())); } }; private final Adjuster sunAltitude = new Adjuster("Sun altitude", "Angle of the sun above the horizon", 0.0, 90.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sun().setAltitude(QuickMath.degToRad(newValue)); } @Override public void update() { set(QuickMath.radToDeg(renderMan.scene().sun().getAltitude())); } }; private final Adjuster fov = new Adjuster("Field of View (zoom)", "Field of View", 1.0, 180.0) { { setClampMax(false); } @Override public void valueChanged(double newValue) { renderMan.scene().camera().setFoV(newValue); } @Override public void update() { Camera camera = renderMan.scene().camera(); set(camera.getFoV(), camera.getMinFoV(), camera.getMaxFoV()); } }; private Adjuster dof; private final Adjuster subjectDistance = new Adjuster("Subject Distance", "Distance to focal plane", Camera.MIN_SUBJECT_DISTANCE, Camera.MAX_SUBJECT_DISTANCE) { { setLogarithmicMode(); } @Override public void valueChanged(double newValue) { renderMan.scene().camera().setSubjectDistance(newValue); } @Override public void update() { set(renderMan.scene().camera().getSubjectDistance()); } }; private final Adjuster exposure = new Adjuster("exposure", "exposure", Scene.MIN_EXPOSURE, Scene.MAX_EXPOSURE) { { setLogarithmicMode(); } @Override public void valueChanged(double newValue) { renderMan.scene().setExposure(newValue); } @Override public void update() { set(renderMan.scene().getExposure()); } }; private final Adjuster cloudSize = new Adjuster("Cloud Size", "Cloud Size", 1.0, 128.0) { { setLogarithmicMode(); } @Override public void valueChanged(double newValue) { renderMan.scene().sky().setCloudSize(newValue); } @Override public void update() { set(renderMan.scene().sky().cloudSize()); } }; private final Adjuster cloudXOffset = new Adjuster("Cloud X", "Cloud X Offset", 1.0, 100.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sky().setCloudXOffset(newValue); } @Override public void update() { set(renderMan.scene().sky().cloudXOffset()); } }; private final Adjuster cloudYOffset = new Adjuster("Cloud Y", "Height of the cloud layer", -128.0, 512.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sky().setCloudYOffset(newValue); } @Override public void update() { set(renderMan.scene().sky().cloudYOffset()); } }; private final Adjuster cloudZOffset = new Adjuster("Cloud Z", "Cloud Z Offset", 1.0, 100.0) { @Override public void valueChanged(double newValue) { renderMan.scene().sky().setCloudZOffset(newValue); } @Override public void update() { set(renderMan.scene().sky().cloudZOffset()); } }; private GradientEditor gradientEditor; /** * Create a new Render Controls dialog. * @param chunkyInstance * @param renderContext */ public RenderControls(Chunky chunkyInstance, RenderContext renderContext) { super(chunkyInstance.getFrame()); decimalFormat.setGroupingSize(3); decimalFormat.setGroupingUsed(true); context = renderContext; chunky = chunkyInstance; view = new Chunk3DView(this, chunkyInstance.getFrame()); renderMan = new RenderManager(view.getCanvas(), renderContext, this); buildUI(); renderMan.start(); view.setRenderer(renderMan); sceneMan = new SceneManager(renderMan); sceneMan.start(); } private void buildUI() { setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); setModalityType(ModalityType.MODELESS); if (!ShutdownAlert.canShutdown()) { // disable the computer shutdown checkbox if we can't shutdown shutdownWhenDoneCB.setEnabled(false); } addWindowListener(new WindowListener() { @Override public void windowOpened(WindowEvent e) { } @Override public void windowIconified(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } @Override public void windowClosing(WindowEvent e) { sceneMan.interrupt(); RenderControls.this.dispose(); } @Override public void windowClosed(WindowEvent e) { // halt rendering renderMan.interrupt(); // dispose of the 3D view view.setVisible(false); view.dispose(); } @Override public void windowActivated(WindowEvent e) { } }); updateTitle(); addTab("General", Icon.wrench, buildGeneralPane()); addTab("Lighting", Icon.light, buildLightingPane()); addTab("Sky", Icon.sky, buildSkyPane()); addTab("Water", Icon.water, buildWaterPane()); addTab("Camera", Icon.camera, buildCameraPane()); addTab("Post-processing", Icon.gear, buildPostProcessingPane()); addTab("Advanced", Icon.advanced, buildAdvancedPane()); addTab("Help", Icon.question, buildHelpPane()); JLabel sppTargetLbl = new JLabel("SPP Target: "); sppTargetLbl.setToolTipText("The render will be paused at this SPP count"); JButton setDefaultBtn = new JButton("Make Default"); setDefaultBtn.setToolTipText("Make the current SPP target the default"); setDefaultBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { PersistentSettings.setSppTargetDefault(renderMan.scene().getTargetSPP()); } }); targetSPP.update(); JLabel renderLbl = new JLabel("Render: "); setViewVisible(false); showPreviewBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (view.isViewVisible()) { view.hideView(); } else { showPreviewWindow(); } } }); startRenderBtn.setText("START"); startRenderBtn.setIcon(Icon.play.imageIcon()); startRenderBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { switch (renderMan.scene().getRenderState()) { case PAUSED: renderMan.scene().resumeRender(); break; case PREVIEW: renderMan.scene().startRender(); break; case RENDERING: renderMan.scene().pauseRender(); break; } stopRenderBtn.setEnabled(true); } }); stopRenderBtn.setText("RESET"); stopRenderBtn.setIcon(Icon.stop.imageIcon()); stopRenderBtn.setToolTipText("<html>Warning: this will discard the " + "current rendered image!<br>Make sure to save your image " + "before stopping the renderer!"); stopRenderBtn.setEnabled(false); stopRenderBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().haltRender(); } }); saveFrameBtn.setText("Save Current Frame"); saveFrameBtn.addActionListener(saveFrameListener); sppLbl.setToolTipText("SPP = Samples Per Pixel, SPS = Samples Per Second"); setRenderTime(0); setSamplesPerSecond(0); setSPP(0); setProgress("Progress:", 0, 0, 1); progressLbl.setText("Progress:"); etaLbl.setText("ETA:"); sceneNameLbl.setText("Scene name: "); sceneNameField.setColumns(15); AbstractDocument document = (AbstractDocument) sceneNameField.getDocument(); document.setDocumentFilter(new SceneNameFilter()); document.addDocumentListener(sceneNameListener); sceneNameField.addActionListener(sceneNameActionListener); updateSceneNameField(); saveSceneBtn.setText("Save"); saveSceneBtn.setIcon(Icon.disk.imageIcon()); saveSceneBtn.addActionListener(saveSceneListener); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup() .addGroup(layout.createSequentialGroup().addComponent(sceneNameLbl).addComponent(sceneNameField) .addPreferredGap(ComponentPlacement.RELATED).addComponent(saveSceneBtn)) .addComponent(tabbedPane) .addGroup(layout.createSequentialGroup().addGroup(targetSPP.horizontalGroup(layout)) .addPreferredGap(ComponentPlacement.RELATED).addComponent(setDefaultBtn)) .addGroup(layout.createSequentialGroup().addComponent(renderLbl) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(startRenderBtn) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(stopRenderBtn)) .addGroup( layout.createSequentialGroup().addComponent(saveFrameBtn) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(showPreviewBtn)) .addGroup( layout.createSequentialGroup().addComponent(renderTimeLbl) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(sppLbl)) .addGroup( layout.createSequentialGroup().addComponent(progressLbl) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(etaLbl)) .addComponent(progressBar)).addContainerGap()); layout.setVerticalGroup( layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(sceneNameLbl) .addComponent(sceneNameField).addComponent(saveSceneBtn)) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(tabbedPane) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE) .addGroup(targetSPP.verticalGroup(layout)).addComponent(setDefaultBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(renderLbl) .addComponent(startRenderBtn).addComponent(stopRenderBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup().addComponent(saveFrameBtn) .addComponent(showPreviewBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup().addComponent(renderTimeLbl).addComponent(sppLbl)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup().addComponent(progressLbl).addComponent(etaLbl)) .addComponent(progressBar).addContainerGap()); final JScrollPane scrollPane = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); setContentPane(scrollPane); scrollPane.getViewport().addChangeListener(new ChangeListener() { private boolean resized = false; @Override public void stateChanged(ChangeEvent e) { if (!resized && scrollPane.getVerticalScrollBar().isVisible()) { Dimension vsbPrefSize = new JScrollPane().getVerticalScrollBar().getPreferredSize(); Dimension size = getSize(); setSize(size.width + vsbPrefSize.width, size.height); resized = true; } } }); pack(); setLocationRelativeTo(chunky.getFrame()); setVisible(true); } /** * Add a tab and ensure that the icon is to the left of the text in the * tab label. * * @param title * @param icon * @param component */ private void addTab(String title, Texture icon, Component component) { int index = tabbedPane.getTabCount(); tabbedPane.add(title, component); if (icon != null) { JLabel lbl = new JLabel(title, icon.imageIcon(), SwingConstants.RIGHT); lbl.setIconTextGap(5); tabbedPane.setTabComponentAt(index, lbl); } } private JPanel buildAdvancedPane() { rayDepth.update(); JSeparator sep1 = new JSeparator(); JSeparator sep2 = new JSeparator(); numThreads.update(); cpuLoad.update(); mergeDumpBtn.setToolTipText("Merge an existing render dump with the current render"); mergeDumpBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { CenteredFileDialog fileDialog = new CenteredFileDialog(null, "Select Render Dump", FileDialog.LOAD); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".dump"); } }); fileDialog.setDirectory(PersistentSettings.getSceneDirectory().getAbsolutePath()); fileDialog.setVisible(true); File selectedFile = fileDialog.getSelectedFile(); if (selectedFile != null) { sceneMan.mergeRenderDump(selectedFile); } } }); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup().addGroup(numThreads.horizontalGroup(layout)) .addGroup(cpuLoad.horizontalGroup(layout)).addComponent(sep1) .addGroup(rayDepth.horizontalGroup(layout)).addComponent(sep2).addComponent(mergeDumpBtn) .addComponent(shutdownWhenDoneCB)) .addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(numThreads.verticalGroup(layout)).addPreferredGap(ComponentPlacement.RELATED) .addGroup(cpuLoad.verticalGroup(layout)).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(sep1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.UNRELATED).addGroup(rayDepth.verticalGroup(layout)) .addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(sep2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(mergeDumpBtn) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(shutdownWhenDoneCB).addContainerGap()); return panel; } private JPanel buildWaterPane() { JButton storeDefaultsBtn = new JButton("Store as defaults"); storeDefaultsBtn.setToolTipText("Store the current water settings as new defaults"); storeDefaultsBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { PersistentSettings.setStillWater(renderMan.scene().stillWaterEnabled()); PersistentSettings.setWaterOpacity(renderMan.scene().getWaterOpacity()); PersistentSettings.setWaterVisibility(renderMan.scene().getWaterVisibility()); PersistentSettings.setWaterHeight(renderMan.scene().getWaterHeight()); boolean useCustomWaterColor = renderMan.scene().getUseCustomWaterColor(); PersistentSettings.setUseCustomWaterColor(useCustomWaterColor); if (useCustomWaterColor) { Vector3d color = renderMan.scene().getWaterColor(); PersistentSettings.setWaterColorRed(color.x); PersistentSettings.setWaterColorGreen(color.y); PersistentSettings.setWaterColorBlue(color.z); } } }); stillWaterCB.setText("still water"); stillWaterCB.addActionListener(stillWaterListener); updateStillWater(); waterVisibility.update(); waterOpacity.update(); JLabel waterWorldLbl = new JLabel( "Note: All chunks will be reloaded after changing the water world options!"); JLabel waterHeightLbl = new JLabel("Water height: "); waterHeightField.setColumns(5); waterHeightField.setText("" + World.SEA_LEVEL); waterHeightField.setEnabled(renderMan.scene().getWaterHeight() != 0); waterHeightField.addActionListener(waterHeightListener); applyWaterHeightBtn.setToolTipText("Use this water height"); applyWaterHeightBtn.addActionListener(waterHeightListener); waterWorldCB.setText("Water World Mode"); waterWorldCB.addActionListener(waterWorldListener); updateWaterHeight(); waterColorCB.addActionListener(customWaterColorListener); updateWaterColor(); waterColorBtn.setIcon(Icon.colors.imageIcon()); waterColorBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ColorPicker picker = new ColorPicker(waterColorBtn, renderMan.scene().getWaterColor()); picker.addColorListener(new ColorListener() { @Override public void onColorPicked(Vector3d color) { renderMan.scene().setWaterColor(color); } }); } }); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup().addComponent(stillWaterCB).addGroup(waterVisibility.horizontalGroup(layout)) .addGroup(waterOpacity.horizontalGroup(layout)).addComponent(waterWorldLbl) .addComponent(waterWorldCB) .addGroup(layout.createSequentialGroup().addComponent(waterHeightLbl) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(waterHeightField, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.RELATED).addComponent(applyWaterHeightBtn)) .addGroup(layout.createSequentialGroup().addComponent(waterColorCB) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(waterColorBtn)) .addGroup(layout.createSequentialGroup() .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(storeDefaultsBtn))) .addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap().addComponent(stillWaterCB) .addPreferredGap(ComponentPlacement.RELATED).addGroup(waterVisibility.verticalGroup(layout)) .addPreferredGap(ComponentPlacement.RELATED).addGroup(waterOpacity.verticalGroup(layout)) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(waterWorldLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(waterWorldCB) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup().addComponent(waterHeightLbl) .addComponent(waterHeightField, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(applyWaterHeightBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup().addComponent(waterColorCB).addComponent(waterColorBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addPreferredGap(ComponentPlacement.UNRELATED, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE) .addComponent(storeDefaultsBtn).addContainerGap()); return panel; } private JPanel buildPostProcessingPane() { exposure.update(); JLabel postprocessDescLbl = new JLabel( "<html>Post processing affects rendering performance<br>when the preview window is visible"); JLabel postprocessLbl = new JLabel("Post-processing mode:"); for (Postprocess pp : Postprocess.values) { postprocessCB.addItem("" + pp); } updatePostprocessCB(); postprocessCB.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JComboBox source = (JComboBox) e.getSource(); renderMan.scene().setPostprocess(Postprocess.get(source.getSelectedIndex())); } }); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup().addGroup(exposure.horizontalGroup(layout)) .addGroup(layout.createSequentialGroup().addComponent(postprocessLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(postprocessCB)) .addComponent(postprocessDescLbl)) .addContainerGap()); layout.setVerticalGroup( layout.createSequentialGroup().addContainerGap().addGroup(exposure.verticalGroup(layout)) .addPreferredGap(ComponentPlacement.UNRELATED).addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(postprocessLbl) .addComponent(postprocessCB)) .addPreferredGap(ComponentPlacement.RELATED).addComponent(postprocessDescLbl) .addContainerGap()); return panel; } private JPanel buildGeneralPane() { JLabel canvasSizeLbl = new JLabel("Canvas size:"); JLabel canvasSizeAdvisory = new JLabel("Note: Actual image size may not be the same as the window size!"); canvasSizeCB.setEditable(true); canvasSizeCB.addItem("400x400"); canvasSizeCB.addItem("1024x768"); canvasSizeCB.addItem("960x540"); canvasSizeCB.addItem("1920x1080"); canvasSizeCB.addActionListener(canvasSizeListener); final JTextField canvasSizeEditor = (JTextField) canvasSizeCB.getEditor().getEditorComponent(); canvasSizeEditor.addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { } @Override public void focusGained(FocusEvent e) { canvasSizeEditor.selectAll(); } }); updateCanvasSizeField(); loadSceneBtn.setText("Load Scene"); loadSceneBtn.setIcon(Icon.load.imageIcon()); loadSceneBtn.addActionListener(loadSceneListener); JButton loadSelectedChunksBtn = new JButton("Load Selected Chunks"); loadSelectedChunksBtn.setToolTipText("Load the chunks that are currently selected in the map view"); loadSelectedChunksBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sceneMan.loadChunks(chunky.getWorld(), chunky.getSelectedChunks()); } }); JButton reloadChunksBtn = new JButton("Reload Chunks"); reloadChunksBtn.setIcon(Icon.reload.imageIcon()); reloadChunksBtn.setToolTipText("Reload all chunks in the scene"); reloadChunksBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { sceneMan.reloadChunks(); } }); openSceneDirBtn.setText("Open Scene Directory"); openSceneDirBtn.setToolTipText("Open the directory where Chunky stores scene descriptions and renders"); openSceneDirBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { if (Desktop.isDesktopSupported()) { Desktop.getDesktop().open(context.getSceneDirectory()); } } catch (IOException e) { Log.warn("Failed to open scene directory", e); } } }); openSceneDirBtn.setVisible(Desktop.isDesktopSupported()); loadSceneBtn.setToolTipText("This replaces the current scene!"); JButton setCanvasSizeBtn = new JButton("Apply"); setCanvasSizeBtn.setToolTipText("Set the canvas size to the value in the field"); setCanvasSizeBtn.addActionListener(canvasSizeListener); JButton halveCanvasSizeBtn = new JButton("Halve"); halveCanvasSizeBtn.setToolTipText("Halve the canvas width and height"); halveCanvasSizeBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int width = renderMan.scene().canvasWidth() / 2; int height = renderMan.scene().canvasHeight() / 2; setCanvasSize(width, height); } }); JButton doubleCanvasSizeBtn = new JButton("Double"); doubleCanvasSizeBtn.setToolTipText("Double the canvas width and height"); doubleCanvasSizeBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int width = renderMan.scene().canvasWidth() * 2; int height = renderMan.scene().canvasHeight() * 2; setCanvasSize(width, height); } }); JButton makeDefaultBtn = new JButton("Make Default"); makeDefaultBtn.setToolTipText("Make the current canvas size the default"); makeDefaultBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { PersistentSettings.set3DCanvasSize(renderMan.scene().canvasWidth(), renderMan.scene().canvasHeight()); } }); JSeparator sep1 = new JSeparator(); JSeparator sep2 = new JSeparator(); biomeColorsCB.setText("enable biome colors"); updateBiomeColorsCB(); saveDumpsCB.setText("save dump once every "); saveDumpsCB.addActionListener(saveDumpsListener); updateSaveDumpsCheckBox(); String[] frequencyStrings = new String[dumpFrequencies.length]; for (int i = 0; i < dumpFrequencies.length; ++i) { frequencyStrings[i] = Integer.toString(dumpFrequencies[i]); } dumpFrequencyCB.setModel(new DefaultComboBoxModel(frequencyStrings)); dumpFrequencyCB.setEditable(true); dumpFrequencyCB.addActionListener(dumpFrequencyListener); updateDumpFrequencyField(); saveSnapshotsCB.addActionListener(saveSnapshotListener); updateSaveSnapshotCheckBox(); yCutoff.update(); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup() .addGroup(layout.createSequentialGroup().addComponent(loadSceneBtn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(openSceneDirBtn)) .addGroup(layout.createSequentialGroup().addComponent(loadSelectedChunksBtn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(reloadChunksBtn)) .addComponent(sep1) .addGroup(layout.createSequentialGroup().addComponent(canvasSizeLbl) .addPreferredGap(ComponentPlacement.RELATED) .addComponent(canvasSizeCB, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.RELATED).addComponent(setCanvasSizeBtn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(makeDefaultBtn)) .addGroup(layout.createSequentialGroup().addComponent(halveCanvasSizeBtn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(doubleCanvasSizeBtn)) .addComponent(canvasSizeAdvisory).addComponent(sep2).addComponent(biomeColorsCB) .addGroup(layout.createSequentialGroup().addComponent(saveDumpsCB) .addComponent(dumpFrequencyCB, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(dumpFrequencyLbl).addGap(0, 0, Short.MAX_VALUE)) .addComponent(saveSnapshotsCB).addGroup(yCutoff.horizontalGroup(layout))) .addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup().addComponent(loadSceneBtn).addComponent(openSceneDirBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout .createParallelGroup().addComponent(loadSelectedChunksBtn).addComponent(reloadChunksBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addComponent( sep1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(canvasSizeLbl) .addComponent(canvasSizeCB, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(setCanvasSizeBtn).addComponent(makeDefaultBtn)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup().addComponent(halveCanvasSizeBtn) .addComponent(doubleCanvasSizeBtn)) .addPreferredGap(ComponentPlacement.RELATED).addComponent(canvasSizeAdvisory) .addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(sep2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(biomeColorsCB) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(saveDumpsCB) .addComponent(dumpFrequencyCB).addComponent(dumpFrequencyLbl)) .addComponent(saveSnapshotsCB).addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(yCutoff.verticalGroup(layout)).addContainerGap()); return panel; } private JPanel buildLightingPane() { changeSunColorBtn.setIcon(Icon.colors.imageIcon()); changeSunColorBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ColorPicker picker = new ColorPicker(changeSunColorBtn, renderMan.scene().sun().getColor()); picker.addColorListener(new ColorListener() { @Override public void onColorPicked(Vector3d color) { renderMan.scene().sun().setColor(color); } }); } }); directLight.setText("enable sunlight"); directLight.setSelected(renderMan.scene().getDirectLight()); directLight.addActionListener(directLightListener); enableEmitters.setText("enable emitters"); enableEmitters.setSelected(renderMan.scene().getEmittersEnabled()); enableEmitters.addActionListener(emittersListener); emitterIntensity.update(); sunIntensity.update(); skyLight.update(); sunAzimuth.update(); sunAltitude.update(); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup().addComponent(directLight).addComponent(enableEmitters) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup().addComponent(skyLight.getLabel()) .addComponent(emitterIntensity.getLabel()).addComponent(sunIntensity.getLabel()) .addComponent(sunAzimuth.getLabel()).addComponent(sunAltitude.getLabel())) .addGroup(layout.createParallelGroup().addComponent(skyLight.getSlider()) .addComponent(emitterIntensity.getSlider()).addComponent(sunIntensity.getSlider()) .addComponent(sunAzimuth.getSlider()).addComponent(sunAltitude.getSlider())) .addGroup(layout.createParallelGroup().addComponent(skyLight.getField()) .addComponent(emitterIntensity.getField()).addComponent(sunIntensity.getField()) .addComponent(sunAzimuth.getField()).addComponent(sunAltitude.getField()))) .addComponent(changeSunColorBtn)).addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(skyLight.verticalGroup(layout)).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(enableEmitters).addPreferredGap(ComponentPlacement.RELATED) .addGroup(emitterIntensity.verticalGroup(layout)).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(directLight).addPreferredGap(ComponentPlacement.RELATED) .addGroup(sunIntensity.verticalGroup(layout)).addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(sunAzimuth.verticalGroup(layout)).addPreferredGap(ComponentPlacement.RELATED) .addGroup(sunAltitude.verticalGroup(layout)).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(changeSunColorBtn).addContainerGap()); return panel; } private JPanel buildSkyPane() { JLabel skyModeLbl = new JLabel("Sky Mode:"); skyModeCB.setModel(new DefaultComboBoxModel(Sky.SkyMode.values())); skyModeCB.addActionListener(skyModeListener); updateSkyMode(); JLabel skymapRotationLbl = new JLabel("Skymap rotation:"); skymapRotationSlider.setMinimum(1); skymapRotationSlider.setMaximum(100); skymapRotationSlider.addChangeListener(skyRotationListener); skymapRotationSlider.setToolTipText("Controls the horizontal rotational offset for the skymap"); JLabel lightProbeRotationLbl = new JLabel("Skymap rotation:"); lightProbeRotationSlider.setMinimum(1); lightProbeRotationSlider.setMaximum(100); lightProbeRotationSlider.addChangeListener(skyRotationListener); lightProbeRotationSlider.setToolTipText("Controls the horizontal rotational offset for the skymap"); JLabel skyboxRotationLbl = new JLabel("Skybox rotation:"); skyboxRotationSlider.setMinimum(1); skyboxRotationSlider.setMaximum(100); skyboxRotationSlider.addChangeListener(skyRotationListener); skyboxRotationSlider.setToolTipText("Controls the horizontal rotational offset for the skymap"); updateSkyRotation(); skyHorizonOffset.update(); cloudSize.update(); cloudXOffset.update(); cloudYOffset.update(); cloudZOffset.update(); JLabel verticalResolutionLbl = new JLabel("Vertical resolution (degrees):"); ButtonGroup verticalResolution = new ButtonGroup(); v90Btn.setSelected(true); v180Btn.setSelected(false); verticalResolution.add(v90Btn); verticalResolution.add(v180Btn); v90Btn.addActionListener(v90Listener); v180Btn.addActionListener(v180Listener); updateVerticalResolution(); simulatedSkyPanel.setBorder(BorderFactory.createTitledBorder("Simulated Sky Settings")); GroupLayout simulatedSkyLayout = new GroupLayout(simulatedSkyPanel); simulatedSkyPanel.setLayout(simulatedSkyLayout); simulatedSkyLayout.setAutoCreateContainerGaps(true); simulatedSkyLayout.setAutoCreateGaps(true); simulatedSkyLayout.setHorizontalGroup(simulatedSkyLayout.createParallelGroup() .addGroup(skyHorizonOffset.horizontalGroup(simulatedSkyLayout))); simulatedSkyLayout.setVerticalGroup(simulatedSkyLayout.createSequentialGroup() .addGroup(skyHorizonOffset.verticalGroup(simulatedSkyLayout))); skymapPanel.setBorder(BorderFactory.createTitledBorder("Skymap Settings")); GroupLayout skymapLayout = new GroupLayout(skymapPanel); skymapPanel.setLayout(skymapLayout); skymapLayout.setAutoCreateContainerGaps(true); skymapLayout.setAutoCreateGaps(true); skymapLayout.setHorizontalGroup(skymapLayout.createParallelGroup().addComponent(loadSkymapBtn) .addGroup(skymapLayout.createSequentialGroup().addComponent(skymapRotationLbl) .addComponent(skymapRotationSlider)) .addGroup(skymapLayout.createSequentialGroup().addComponent(verticalResolutionLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(v90Btn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(v180Btn))); skymapLayout.setVerticalGroup(skymapLayout.createSequentialGroup().addComponent(loadSkymapBtn) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(skymapLayout.createParallelGroup(Alignment.BASELINE).addComponent(verticalResolutionLbl) .addComponent(v90Btn).addComponent(v180Btn)) .addPreferredGap(ComponentPlacement.RELATED).addGroup(skymapLayout.createParallelGroup() .addComponent(skymapRotationLbl).addComponent(skymapRotationSlider))); loadSkymapBtn.setText("Load Skymap"); loadSkymapBtn.setToolTipText("Use a panoramic skymap"); loadSkymapBtn.addActionListener(new SkymapTextureLoader(renderMan)); lightProbePanel.setBorder(BorderFactory.createTitledBorder("Spherical Skymap Settings")); GroupLayout lightProbeLayout = new GroupLayout(lightProbePanel); lightProbePanel.setLayout(lightProbeLayout); lightProbeLayout.setAutoCreateContainerGaps(true); lightProbeLayout.setAutoCreateGaps(true); lightProbeLayout.setHorizontalGroup(lightProbeLayout.createParallelGroup().addComponent(loadLightProbeBtn) .addGroup(lightProbeLayout.createSequentialGroup().addComponent(lightProbeRotationLbl) .addComponent(lightProbeRotationSlider))); lightProbeLayout.setVerticalGroup(lightProbeLayout.createSequentialGroup().addComponent(loadLightProbeBtn) .addPreferredGap(ComponentPlacement.RELATED).addGroup(lightProbeLayout.createParallelGroup() .addComponent(lightProbeRotationLbl).addComponent(lightProbeRotationSlider))); loadLightProbeBtn.setText("Load Spherical Skymap"); loadLightProbeBtn.setToolTipText("Select the spherical skymap to use"); loadLightProbeBtn.addActionListener(new SkymapTextureLoader(renderMan)); skyGradientPanel.setBorder(BorderFactory.createTitledBorder("Sky Gradient")); gradientEditor = new GradientEditor(); gradientEditor.addGradientListener(gradientListener); updateSkyGradient(); skyGradientPanel.add(gradientEditor); GroupLayout skyboxLayout = new GroupLayout(skyboxPanel); skyboxPanel.setLayout(skyboxLayout); skyboxPanel.setBorder(BorderFactory.createTitledBorder("Skybox")); JLabel skyboxLbl = new JLabel("Load skybox textures:"); JButton loadUpTexture = new JButton("Up"); loadUpTexture.setToolTipText("Load up texture"); loadUpTexture.setIcon(Icon.skyboxUp.imageIcon()); loadUpTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_UP)); JButton loadDownTexture = new JButton("Down"); loadDownTexture.setToolTipText("Load down texture"); loadDownTexture.setIcon(Icon.skyboxDown.imageIcon()); loadDownTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_DOWN)); JButton loadFrontTexture = new JButton("Front"); loadFrontTexture.setToolTipText("Load front (north) texture"); loadFrontTexture.setIcon(Icon.skyboxFront.imageIcon()); loadFrontTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_FRONT)); JButton loadBackTexture = new JButton("Back"); loadBackTexture.setToolTipText("Load back (south) texture"); loadBackTexture.setIcon(Icon.skyboxBack.imageIcon()); loadBackTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_BACK)); JButton loadRightTexture = new JButton("Right"); loadRightTexture.setToolTipText("Load right (east) texture"); loadRightTexture.setIcon(Icon.skyboxRight.imageIcon()); loadRightTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_RIGHT)); JButton loadLeftTexture = new JButton("Left"); loadLeftTexture.setToolTipText("Load left (west) texture"); loadLeftTexture.setIcon(Icon.skyboxLeft.imageIcon()); loadLeftTexture.addActionListener(new SkyboxTextureLoader(renderMan, Sky.SKYBOX_LEFT)); skyboxLayout.setAutoCreateContainerGaps(true); skyboxLayout.setAutoCreateGaps(true); skyboxLayout.setHorizontalGroup(skyboxLayout.createParallelGroup() .addGroup(skyboxLayout.createSequentialGroup().addComponent(skyboxLbl) .addGroup(skyboxLayout.createParallelGroup().addComponent(loadUpTexture) .addComponent(loadFrontTexture).addComponent(loadRightTexture)) .addGroup(skyboxLayout.createParallelGroup().addComponent(loadDownTexture) .addComponent(loadBackTexture).addComponent(loadLeftTexture))) .addGroup(skyboxLayout.createSequentialGroup().addComponent(skyboxRotationLbl) .addComponent(skyboxRotationSlider))); skyboxLayout.setVerticalGroup(skyboxLayout.createSequentialGroup().addComponent(skyboxLbl) .addGroup(skyboxLayout.createParallelGroup().addComponent(loadUpTexture) .addComponent(loadDownTexture)) .addGroup(skyboxLayout.createParallelGroup().addComponent(loadFrontTexture) .addComponent(loadBackTexture)) .addGroup(skyboxLayout.createParallelGroup().addComponent(loadRightTexture) .addComponent(loadLeftTexture)) .addGroup(skyboxLayout.createParallelGroup().addComponent(skyboxRotationLbl) .addComponent(skyboxRotationSlider))); atmosphereEnabled.setText("enable atmosphere"); atmosphereEnabled.addActionListener(atmosphereListener); updateAtmosphereCheckBox(); transparentSky.setText("transparent sky"); transparentSky.addActionListener(transparentSkyListener); updateTransparentSky(); volumetricFogEnabled.setText("enable volumetric fog"); volumetricFogEnabled.addActionListener(volumetricFogListener); updateVolumetricFogCheckBox(); cloudsEnabled.setText("enable clouds"); cloudsEnabled.addActionListener(cloudsEnabledListener); updateCloudsEnabledCheckBox(); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout .createParallelGroup() .addGroup(layout.createSequentialGroup().addComponent(skyModeLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(skyModeCB, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) .addComponent(simulatedSkyPanel).addComponent(skymapPanel).addComponent(lightProbePanel) .addComponent(skyGradientPanel).addComponent(skyboxPanel).addComponent(atmosphereEnabled) .addComponent(transparentSky).addComponent(volumetricFogEnabled).addComponent(cloudsEnabled) .addGroup(cloudSize.horizontalGroup(layout)).addGroup(cloudXOffset.horizontalGroup(layout)) .addGroup(cloudYOffset.horizontalGroup(layout)).addGroup(cloudZOffset.horizontalGroup(layout))) .addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(skyModeLbl) .addComponent(skyModeCB)) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(simulatedSkyPanel) .addComponent(skymapPanel).addComponent(lightProbePanel).addComponent(skyGradientPanel) .addComponent(skyboxPanel).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(atmosphereEnabled).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(transparentSky).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(volumetricFogEnabled).addPreferredGap(ComponentPlacement.UNRELATED) .addComponent(cloudsEnabled).addPreferredGap(ComponentPlacement.RELATED) .addGroup(cloudSize.verticalGroup(layout)).addPreferredGap(ComponentPlacement.RELATED) .addGroup(cloudXOffset.verticalGroup(layout)).addPreferredGap(ComponentPlacement.RELATED) .addGroup(cloudYOffset.verticalGroup(layout)).addPreferredGap(ComponentPlacement.RELATED) .addGroup(cloudZOffset.verticalGroup(layout)).addContainerGap()); return panel; } private JPanel buildCameraPane() { JLabel projectionModeLbl = new JLabel("Projection"); fov.update(); dof = new DoFAdjuster(renderMan); dof.update(); subjectDistance.update(); JLabel presetLbl = new JLabel("Preset:"); CameraPreset[] presets = { CameraPreset.NONE, CameraPreset.ISO_WEST_NORTH, CameraPreset.ISO_NORTH_EAST, CameraPreset.ISO_EAST_SOUTH, CameraPreset.ISO_SOUTH_WEST, CameraPreset.SKYBOX_RIGHT, CameraPreset.SKYBOX_LEFT, CameraPreset.SKYBOX_UP, CameraPreset.SKYBOX_DOWN, CameraPreset.SKYBOX_FRONT, CameraPreset.SKYBOX_BACK, }; cameraPreset.setModel(new DefaultComboBoxModel(presets)); cameraPreset.setMaximumRowCount(presets.length); final int presetHeight = cameraPreset.getPreferredSize().height; final int presetWidth = cameraPreset.getPreferredSize().width; cameraPreset.setRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); label.setPreferredSize(new Dimension(presetWidth, presetHeight)); CameraPreset preset = (CameraPreset) value; label.setIcon(preset.getIcon()); return label; } }); cameraPreset.addActionListener(cameraPresetListener); JLabel customPresetLbl = new JLabel("Custom preset:"); customPreset.setEditable(true); updateCustomPresets(); JButton savePreset = new JButton("save"); savePreset.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String name = ""; int selected = customPreset.getSelectedIndex(); if (selected == -1) { // select name name = (String) customPreset.getEditor().getItem(); name = (name == null) ? "" : name.trim(); if (name.isEmpty()) { // auto-assign name int nextIndex = customPreset.getItemCount() + 1; outer: while (true) { name = "custom-" + (nextIndex++); for (int i = 0; i < customPreset.getItemCount(); ++i) { String item = (String) customPreset.getItemAt(i); if (name.equals(item)) { continue outer; } } break; } } else { for (int i = 0; i < customPreset.getItemCount(); ++i) { String item = (String) customPreset.getItemAt(i); if (name.equals(item)) { selected = i; break; } } } if (selected == -1) { // add new preset selected = customPreset.getItemCount(); customPreset.addItem(name); } customPreset.setSelectedIndex(selected); } else { name = (String) customPreset.getSelectedItem(); } renderMan.scene().saveCameraPreset(name); } }); JButton loadPreset = new JButton("load"); loadPreset.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String name = ""; int selected = customPreset.getSelectedIndex(); if (selected == -1) { // select name name = (String) customPreset.getEditor().getItem(); name = (name == null) ? "" : name.trim(); } else { name = ((String) customPreset.getSelectedItem()).trim(); } if (!name.isEmpty()) { renderMan.scene().loadCameraPreset(name); } } }); JButton deletePreset = new JButton("delete"); deletePreset.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String name = ""; int selected = customPreset.getSelectedIndex(); if (selected == -1) { // select name name = (String) customPreset.getEditor().getItem(); name = (name == null) ? "" : name.trim(); } else { name = ((String) customPreset.getSelectedItem()).trim(); } if (!name.isEmpty()) { renderMan.scene().deleteCameraPreset(name); if (selected != -1) { customPreset.removeItemAt(selected); } else { for (int i = 0; i < customPreset.getItemCount(); ++i) { if (name.equals(customPreset.getItemAt(i))) { customPreset.removeItemAt(i); break; } } } } } }); ProjectionMode[] projectionModes = ProjectionMode.values(); projectionMode.setModel(new DefaultComboBoxModel(projectionModes)); projectionMode.addActionListener(projectionModeListener); updateProjectionMode(); JButton autoFocusBtn = new JButton("Autofocus"); autoFocusBtn.setToolTipText("Focuses on the object right in the center, under the crosshairs"); autoFocusBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().autoFocus(); dof.update(); subjectDistance.update(); } }); JButton cameraToPlayerBtn = new JButton("Camera to player"); cameraToPlayerBtn.setToolTipText("Move camera to player position"); cameraToPlayerBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().moveCameraToPlayer(); } }); JLabel posLbl = new JLabel("Position:"); cameraX.setColumns(10); cameraX.setHorizontalAlignment(JTextField.RIGHT); cameraX.addActionListener(cameraPositionListener); cameraY.setColumns(10); cameraY.setHorizontalAlignment(JTextField.RIGHT); cameraY.addActionListener(cameraPositionListener); cameraZ.setColumns(10); cameraZ.setHorizontalAlignment(JTextField.RIGHT); cameraZ.addActionListener(cameraPositionListener); updateCameraPosition(); JLabel dirLbl = new JLabel("Direction:"); cameraYaw.setColumns(10); cameraYaw.setHorizontalAlignment(JTextField.RIGHT); cameraYaw.addActionListener(cameraDirectionListener); cameraPitch.setColumns(10); cameraPitch.setHorizontalAlignment(JTextField.RIGHT); cameraPitch.addActionListener(cameraDirectionListener); cameraRoll.setColumns(10); cameraRoll.setHorizontalAlignment(JTextField.RIGHT); cameraRoll.addActionListener(cameraDirectionListener); updateCameraDirection(); JButton centerCameraBtn = new JButton("Center camera"); centerCameraBtn.setToolTipText("Center camera above loaded chunks"); centerCameraBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().moveCameraToCenter(); } }); JSeparator sep1 = new JSeparator(); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup( layout.createSequentialGroup().addContainerGap() .addGroup(layout.createParallelGroup().addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup().addComponent(posLbl).addComponent(dirLbl)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup() .addComponent(cameraX, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(cameraYaw, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup() .addComponent(cameraY, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(cameraPitch, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup() .addComponent(cameraZ, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addComponent(cameraRoll, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE))) .addGroup(layout.createSequentialGroup().addComponent(presetLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(cameraPreset)) .addGroup(layout.createSequentialGroup().addComponent(customPresetLbl) .addPreferredGap(ComponentPlacement.RELATED).addComponent(customPreset) .addPreferredGap(ComponentPlacement.RELATED).addComponent(savePreset) .addPreferredGap(ComponentPlacement.RELATED).addComponent(loadPreset) .addPreferredGap(ComponentPlacement.RELATED).addComponent(deletePreset)) .addGroup(layout.createSequentialGroup().addComponent(cameraToPlayerBtn) .addPreferredGap(ComponentPlacement.RELATED).addComponent(centerCameraBtn)) .addComponent(sep1) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup().addComponent(projectionModeLbl) .addComponent(fov.getLabel()).addComponent(dof.getLabel()) .addComponent(subjectDistance.getLabel())) .addGroup(layout.createParallelGroup().addComponent(projectionMode) .addComponent(fov.getSlider()).addComponent(dof.getSlider()) .addComponent(subjectDistance.getSlider())) .addGroup(layout.createParallelGroup().addComponent(fov.getField()) .addComponent(dof.getField()) .addComponent(subjectDistance.getField()))) .addComponent(autoFocusBtn)) .addContainerGap()); layout.setVerticalGroup(layout.createSequentialGroup().addContainerGap() .addGroup(layout .createParallelGroup(Alignment.BASELINE).addComponent(presetLbl).addComponent(cameraPreset)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(customPresetLbl) .addComponent(customPreset).addComponent(savePreset).addComponent(loadPreset) .addComponent(deletePreset)) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(posLbl).addComponent(cameraX) .addComponent(cameraY).addComponent(cameraZ)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(dirLbl) .addComponent(cameraYaw).addComponent(cameraPitch).addComponent(cameraRoll)) .addPreferredGap(ComponentPlacement.RELATED) .addGroup( layout.createParallelGroup().addComponent(cameraToPlayerBtn).addComponent(centerCameraBtn)) .addPreferredGap(ComponentPlacement.UNRELATED) .addComponent( sep1, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE) .addPreferredGap(ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(projectionModeLbl) .addComponent(projectionMode)) .addPreferredGap(ComponentPlacement.RELATED).addGroup(fov.verticalGroup(layout)) .addGroup(dof.verticalGroup(layout)).addGroup(subjectDistance.verticalGroup(layout)) .addPreferredGap(ComponentPlacement.UNRELATED).addComponent(autoFocusBtn).addContainerGap()); return panel; } private JPanel buildHelpPane() { JLabel helpLbl = new JLabel("<html>Render Preview Controls:<br>" + "<b>W</b> move camera forward<br>" + "<b>S</b> move camera backward<br>" + "<b>A</b> strafe camera left<br>" + "<b>D</b> strafe camera right<br>" + "<b>R</b> move camera up<br>" + "<b>F</b> move camera down<br>" + "<b>U</b> toggle fullscreen mode<br>" + "<b>K</b> move camera forward x100<br>" + "<b>J</b> move camera backward x100<br>" + "<br>" + "Holding <b>SHIFT</b> makes the basic movement keys so move 1/10th of the normal distance."); JPanel panel = new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout); layout.setHorizontalGroup( layout.createSequentialGroup().addContainerGap().addComponent(helpLbl).addContainerGap()); layout.setVerticalGroup( layout.createSequentialGroup().addContainerGap().addComponent(helpLbl).addContainerGap()); return panel; } protected void updateStillWater() { stillWaterCB.removeActionListener(stillWaterListener); stillWaterCB.setSelected(renderMan.scene().stillWaterEnabled()); stillWaterCB.addActionListener(stillWaterListener); } protected void updateBiomeColorsCB() { biomeColorsCB.removeActionListener(biomeColorsCBListener); biomeColorsCB.addActionListener(biomeColorsCBListener); biomeColorsCB.setSelected(renderMan.scene().biomeColorsEnabled()); } protected void updateAtmosphereCheckBox() { atmosphereEnabled.removeActionListener(atmosphereListener); atmosphereEnabled.setSelected(renderMan.scene().atmosphereEnabled()); atmosphereEnabled.addActionListener(atmosphereListener); } protected void updateTransparentSky() { transparentSky.removeActionListener(transparentSkyListener); transparentSky.setSelected(renderMan.scene().transparentSky()); transparentSky.addActionListener(transparentSkyListener); } protected void updateVerticalResolution() { v90Btn.removeActionListener(v90Listener); v180Btn.removeActionListener(v180Listener); boolean mirror = renderMan.scene().sky().isMirrored(); v90Btn.setSelected(mirror); v180Btn.setSelected(!mirror); v90Btn.addActionListener(v90Listener); v180Btn.addActionListener(v180Listener); } protected void updateVolumetricFogCheckBox() { volumetricFogEnabled.removeActionListener(volumetricFogListener); volumetricFogEnabled.setSelected(renderMan.scene().volumetricFogEnabled()); volumetricFogEnabled.addActionListener(volumetricFogListener); } protected void updateCloudsEnabledCheckBox() { cloudsEnabled.removeActionListener(cloudsEnabledListener); cloudsEnabled.setSelected(renderMan.scene().sky().cloudsEnabled()); cloudsEnabled.addActionListener(cloudsEnabledListener); } private void updateTitle() { setTitle("Render Controls - " + renderMan.scene().name()); } private final ActionListener dumpFrequencyListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { renderMan.scene().setDumpFrequency(getDumpFrequency()); } catch (NumberFormatException e1) { } updateDumpFrequencyField(); } }; private final ActionListener saveDumpsListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { boolean enabled = saveDumpsCB.isSelected(); if (enabled) { renderMan.scene().setDumpFrequency(getDumpFrequency()); } else { renderMan.scene().setDumpFrequency(0); } dumpFrequencyCB.setEnabled(enabled); saveSnapshotsCB.setEnabled(enabled); } }; private final ActionListener canvasSizeListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String size = (String) canvasSizeCB.getSelectedItem(); try { Pattern regex = Pattern.compile("([0-9]+)[xX.*]([0-9]+)"); Matcher matcher = regex.matcher(size); if (matcher.matches()) { int width = Integer.parseInt(matcher.group(1)); int height = Integer.parseInt(matcher.group(2)); setCanvasSize(width, height); } else { Log.info("Failed to set canvas size: format must be WIDTHxHEIGHT!"); } } catch (NumberFormatException e1) { Log.info("Failed to set canvas size: invalid dimensions!"); } } }; private final ActionListener sceneNameActionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JTextField source = (JTextField) e.getSource(); renderMan.scene().setName(source.getText()); updateTitle(); sceneMan.saveScene(); } }; private final DocumentListener sceneNameListener = new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { updateName(e); } @Override public void insertUpdate(DocumentEvent e) { updateName(e); } @Override public void changedUpdate(DocumentEvent e) { updateName(e); } private void updateName(DocumentEvent e) { try { Document d = e.getDocument(); renderMan.scene().setName(d.getText(0, d.getLength())); updateTitle(); } catch (BadLocationException e1) { e1.printStackTrace(); } } }; private final GradientListener gradientListener = new GradientListener() { @Override public void gradientChanged(List<Vector4d> newGradient) { renderMan.scene().sky().setGradient(newGradient); } @Override public void stopSelected(int index) { } @Override public void stopModified(int index, Vector4d marker) { } }; private final ActionListener saveSnapshotListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().setSaveSnapshots(source.isSelected()); } }; private final ActionListener saveSceneListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().setName(sceneNameField.getText()); sceneMan.saveScene(); } }; private final ActionListener saveFrameListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new Thread() { @Override public void run() { renderMan.saveSnapshot(RenderControls.this); } }.start(); } }; private final ActionListener loadSceneListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new SceneSelector(RenderControls.this, context); } }; private final ChangeListener skyRotationListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { JSlider source = (JSlider) e.getSource(); double value = (double) (source.getValue() - source.getMinimum()) / (source.getMaximum() - source.getMinimum()); double rotation = value * 2 * Math.PI; renderMan.scene().sky().setRotation(rotation); } }; private final ActionListener projectionModeListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JComboBox source = (JComboBox) e.getSource(); Object selected = source.getSelectedItem(); if (selected != null && selected instanceof ProjectionMode) { renderMan.scene().camera().setProjectionMode((ProjectionMode) selected); updateProjectionMode(); fov.update(); } } }; private final ActionListener cameraPresetListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JComboBox source = (JComboBox) e.getSource(); Object selected = source.getSelectedItem(); if (selected != null && selected instanceof CameraPreset) { CameraPreset preset = (CameraPreset) selected; preset.apply(renderMan.scene().camera()); updateProjectionMode(); fov.update(); updateCameraDirection(); } } }; private final ActionListener waterHeightListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { int waterHeight = Integer.parseInt(waterHeightField.getText()); renderMan.scene().setWaterHeight(waterHeight); sceneMan.reloadChunks(); updateWaterHeight(); } catch (Error thrown) { // ignore number format exceptions } } }; private final ActionListener waterWorldListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); if (source.isSelected()) { renderMan.scene().setWaterHeight(Integer.parseInt(waterHeightField.getText())); } else { waterHeightField.removeActionListener(waterHeightListener); waterHeightField.setText("" + renderMan.scene().getWaterHeight()); waterHeightField.addActionListener(waterHeightListener); renderMan.scene().setWaterHeight(0); } sceneMan.reloadChunks(); updateWaterHeight(); } }; private final ActionListener customWaterColorListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); boolean useCustomWaterColor = source.isSelected(); if (useCustomWaterColor) { renderMan.scene().setWaterColor(new Vector3d(PersistentSettings.DEFAULT_WATER_RED, PersistentSettings.DEFAULT_WATER_GREEN, PersistentSettings.DEFAULT_WATER_BLUE)); } renderMan.scene().setUseCustomWaterColor(useCustomWaterColor); updateWaterColor(); } }; private final ActionListener skyModeListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JComboBox source = (JComboBox) e.getSource(); renderMan.scene().sky().setSkyMode((SkyMode) source.getSelectedItem()); updateSkyMode(); RenderControls.this.pack(); } }; private final ActionListener cameraPositionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Vector3d pos = new Vector3d(renderMan.scene().camera().getPosition()); try { pos.x = numberFormat.parse(cameraX.getText()).doubleValue(); } catch (NumberFormatException ex) { } catch (ParseException ex) { } try { pos.y = numberFormat.parse(cameraY.getText()).doubleValue(); } catch (NumberFormatException ex) { } catch (ParseException ex) { } try { pos.z = numberFormat.parse(cameraZ.getText()).doubleValue(); } catch (NumberFormatException ex) { } catch (ParseException ex) { } renderMan.scene().camera().setPosition(pos); updateCameraPosition(); } }; private final ActionListener cameraDirectionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { double yaw = renderMan.scene().camera().getYaw(); double pitch = renderMan.scene().camera().getPitch(); double roll = renderMan.scene().camera().getRoll(); try { double value = numberFormat.parse(cameraPitch.getText()).doubleValue(); pitch = QuickMath.degToRad(value); } catch (NumberFormatException ex) { } catch (ParseException ex) { } try { double value = numberFormat.parse(cameraYaw.getText()).doubleValue(); yaw = QuickMath.degToRad(value); } catch (NumberFormatException ex) { } catch (ParseException ex) { } try { double value = numberFormat.parse(cameraRoll.getText()).doubleValue(); roll = QuickMath.degToRad(value); } catch (NumberFormatException ex) { } catch (ParseException ex) { } renderMan.scene().camera().setView(yaw, pitch, roll); updateCameraDirection(); } }; private final ActionListener stillWaterListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().setStillWater(stillWaterCB.isSelected()); } }; private final ActionListener atmosphereListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().setAtmosphereEnabled(source.isSelected()); } }; private final ActionListener transparentSkyListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().setTransparentSky(source.isSelected()); } }; private final ActionListener volumetricFogListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().setVolumetricFogEnabled(source.isSelected()); } }; private final ActionListener cloudsEnabledListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().sky().setCloudsEnabled(source.isSelected()); } }; private final ActionListener v90Listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (v90Btn.isSelected()) { renderMan.scene().sky().setMirrored(true); } } }; private final ActionListener v180Listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (v180Btn.isSelected()) { renderMan.scene().sky().setMirrored(false); } } }; private final ActionListener biomeColorsCBListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox) e.getSource(); renderMan.scene().setBiomeColorsEnabled(source.isSelected()); } }; private final ActionListener emittersListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().setEmittersEnabled(enableEmitters.isSelected()); } }; private final ActionListener directLightListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { renderMan.scene().setDirectLight(directLight.isSelected()); } }; private int spp = 0; private int sps = 0; protected void updateWaterHeight() { int height = renderMan.scene().getWaterHeight(); boolean waterWorld = height > 0; if (waterWorld) { waterHeightField.removeActionListener(waterHeightListener); waterHeightField.setText("" + height); waterHeightField.addActionListener(waterHeightListener); } waterWorldCB.setSelected(waterWorld); waterHeightField.setEnabled(waterWorld); applyWaterHeightBtn.setEnabled(waterWorld); } protected void updateWaterColor() { waterColorCB.removeActionListener(customWaterColorListener); boolean useCustomWaterColor = renderMan.scene().getUseCustomWaterColor(); waterColorCB.setSelected(useCustomWaterColor); waterColorBtn.setEnabled(useCustomWaterColor); waterColorCB.addActionListener(customWaterColorListener); } protected void updateSkyRotation() { skymapRotationSlider.removeChangeListener(skyRotationListener); skymapRotationSlider .setValue((int) FastMath.round(100 * renderMan.scene().sky().getRotation() / (2 * Math.PI))); skymapRotationSlider.addChangeListener(skyRotationListener); skyboxRotationSlider.removeChangeListener(skyRotationListener); skyboxRotationSlider .setValue((int) FastMath.round(100 * renderMan.scene().sky().getRotation() / (2 * Math.PI))); skyboxRotationSlider.addChangeListener(skyRotationListener); } private void updateSkyGradient() { gradientEditor.removeGradientListener(gradientListener); gradientEditor.setGradient(renderMan.scene().sky().getGradient()); gradientEditor.addGradientListener(gradientListener); } protected void updateProjectionMode() { projectionMode.removeActionListener(projectionModeListener); ProjectionMode mode = renderMan.scene().camera().getProjectionMode(); projectionMode.setSelectedItem(mode); projectionMode.addActionListener(projectionModeListener); } protected void updateSkyMode() { skyModeCB.removeActionListener(skyModeListener); SkyMode mode = renderMan.scene().sky().getSkyMode(); skyModeCB.setSelectedItem(mode); simulatedSkyPanel.setVisible(mode == SkyMode.SIMULATED); skymapPanel.setVisible(mode == SkyMode.SKYMAP_PANORAMIC); lightProbePanel.setVisible(mode == SkyMode.SKYMAP_SPHERICAL); skyGradientPanel.setVisible(mode == SkyMode.GRADIENT); skyboxPanel.setVisible(mode == SkyMode.SKYBOX); skyModeCB.addActionListener(skyModeListener); } protected void updateCanvasSizeField() { canvasSizeCB.removeActionListener(canvasSizeListener); canvasSizeCB.setSelectedItem("" + renderMan.scene().canvasWidth() + "x" + renderMan.scene().canvasHeight()); canvasSizeCB.addActionListener(canvasSizeListener); } protected void updateSaveDumpsCheckBox() { saveDumpsCB.removeActionListener(saveDumpsListener); saveDumpsCB.setSelected(renderMan.scene().shouldSaveDumps()); saveDumpsCB.addActionListener(saveDumpsListener); } protected void updateDumpFrequencyField() { dumpFrequencyCB.removeActionListener(dumpFrequencyListener); try { dumpFrequencyCB.setEnabled(renderMan.scene().shouldSaveDumps()); saveSnapshotsCB.setEnabled(renderMan.scene().shouldSaveDumps()); int frequency = renderMan.scene().getDumpFrequency(); for (int i = 0; i < dumpFrequencies.length; ++i) { if (frequency == dumpFrequencies[i]) { dumpFrequencyCB.setSelectedIndex(i); return; } } dumpFrequencyCB.setSelectedItem(Integer.toString(frequency)); } finally { dumpFrequencyCB.addActionListener(dumpFrequencyListener); } } protected void updateSaveSnapshotCheckBox() { saveSnapshotsCB.removeActionListener(saveSnapshotListener); try { saveSnapshotsCB.setSelected(renderMan.scene().shouldSaveSnapshots()); } finally { saveSnapshotsCB.addActionListener(saveSnapshotListener); } } protected void updateSceneNameField() { sceneNameField.getDocument().removeDocumentListener(sceneNameListener); sceneNameField.removeActionListener(sceneNameActionListener); sceneNameField.setText(renderMan.scene().name()); sceneNameField.getDocument().addDocumentListener(sceneNameListener); sceneNameField.addActionListener(sceneNameActionListener); } protected void updatePostprocessCB() { postprocessCB.setSelectedIndex(renderMan.scene().getPostprocess().ordinal()); } protected void updateCustomPresets() { customPreset.removeAllItems(); JsonObject presets = renderMan.scene().getCameraPresets(); for (JsonMember member : presets.getMemberList()) { String name = member.getName().trim(); if (!name.isEmpty()) { customPreset.addItem(name); } } } protected void updateCameraPosition() { cameraX.removeActionListener(cameraPositionListener); cameraY.removeActionListener(cameraPositionListener); cameraZ.removeActionListener(cameraPositionListener); Vector3d pos = renderMan.scene().camera().getPosition(); cameraX.setText(decimalFormat.format(pos.x)); cameraY.setText(decimalFormat.format(pos.y)); cameraZ.setText(decimalFormat.format(pos.z)); cameraX.addActionListener(cameraPositionListener); cameraY.addActionListener(cameraPositionListener); cameraZ.addActionListener(cameraPositionListener); if (PersistentSettings.getFollowCamera()) { panToCamera(); } onCameraStateChange(); } protected void updateCameraDirection() { cameraRoll.removeActionListener(cameraDirectionListener); cameraPitch.removeActionListener(cameraDirectionListener); cameraYaw.removeActionListener(cameraDirectionListener); double roll = QuickMath.radToDeg(renderMan.scene().camera().getRoll()); double pitch = QuickMath.radToDeg(renderMan.scene().camera().getPitch()); double yaw = QuickMath.radToDeg(renderMan.scene().camera().getYaw()); cameraRoll.setText(decimalFormat.format(roll)); cameraPitch.setText(decimalFormat.format(pitch)); cameraYaw.setText(decimalFormat.format(yaw)); cameraRoll.addActionListener(cameraDirectionListener); cameraPitch.addActionListener(cameraDirectionListener); cameraYaw.addActionListener(cameraDirectionListener); onCameraStateChange(); } private void onCameraStateChange() { chunky.getMap().repaint(); } /** * Load the scene with the given name * @param sceneName The name of the scene to load */ public void loadScene(String sceneName) { sceneMan.loadScene(sceneName); } /** * Called when the current scene has been saved */ @Override public void sceneSaved() { updateTitle(); } @Override public void onStrafeLeft() { renderMan.scene().camera().strafeLeft(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onStrafeRight() { renderMan.scene().camera().strafeRight(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onMoveForward() { renderMan.scene().camera().moveForward(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onMoveBackward() { renderMan.scene().camera().moveBackward(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onMoveForwardFar() { renderMan.scene().camera().moveForward(100); updateCameraPosition(); } @Override public void onMoveBackwardFar() { renderMan.scene().camera().moveBackward(100); updateCameraPosition(); } @Override public void onMoveUp() { renderMan.scene().camera().moveUp(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onMoveDown() { renderMan.scene().camera().moveDown(chunky.getShiftModifier() ? .1 : 1); updateCameraPosition(); } @Override public void onMouseDragged(int dx, int dy) { renderMan.scene().camera().rotateView(-(Math.PI / 250) * dx, (Math.PI / 250) * dy); updateCameraDirection(); } /** * Set the name of the current scene * @param sceneName */ public void setSceneName(String sceneName) { renderMan.scene().setName(sceneName); sceneNameField.setText(renderMan.scene().name()); updateTitle(); } /** * Load the given chunks and center the camera. * @param world * @param chunks */ public void loadFreshChunks(World world, Collection<ChunkPosition> chunks) { sceneMan.loadFreshChunks(world, chunks); } /** * Update the Show/Hide 3D view button. * @param visible */ @Override public void setViewVisible(boolean visible) { if (visible) { showPreviewBtn.setText("Hide Preview Window"); showPreviewBtn.setToolTipText("Hide the preview window"); } else { showPreviewBtn.setText("Show Preview Window"); showPreviewBtn.setToolTipText("Show the preview window"); } } protected void setCanvasSize(int width, int height) { renderMan.scene().setCanvasSize(width, height); int canvasWidth = renderMan.scene().canvasWidth(); int canvasHeight = renderMan.scene().canvasHeight(); canvasSizeCB.setSelectedItem("" + canvasWidth + "x" + canvasHeight); view.setCanvasSize(canvasWidth, canvasHeight); } /** * Method to notify the render controls dialog that a scene has been loaded. * Causes canvas size to be updated. Can be called from outside EDT. */ @Override public void sceneLoaded() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateAllSettings(); showPreviewWindow(); } }); } protected void updateAllSettings() { skyHorizonOffset.update(); dof.update(); fov.update(); subjectDistance.update(); updateProjectionMode(); updateSkyGradient(); updateSkyMode(); updateCanvasSizeField(); emitterIntensity.update(); skyLight.update(); sunIntensity.update(); sunAzimuth.update(); sunAltitude.update(); updateStillWater(); waterVisibility.update(); waterOpacity.update(); updateSkyRotation(); updateVerticalResolution(); updateBiomeColorsCB(); updateAtmosphereCheckBox(); updateTransparentSky(); updateVolumetricFogCheckBox(); updateCloudsEnabledCheckBox(); updateTitle(); exposure.update(); updateSaveDumpsCheckBox(); updateSaveSnapshotCheckBox(); updateDumpFrequencyField(); targetSPP.update(); updateSceneNameField(); updatePostprocessCB(); cloudSize.update(); cloudXOffset.update(); cloudYOffset.update(); cloudZOffset.update(); rayDepth.update(); updateWaterHeight(); updateWaterColor(); updateCameraDirection(); updateCameraPosition(); updateCustomPresets(); enableEmitters.setSelected(renderMan.scene().getEmittersEnabled()); directLight.setSelected(renderMan.scene().getDirectLight()); stopRenderBtn.setEnabled(true); } /** * Make sure the preview window is visible */ public void showPreviewWindow() { view.showView(renderMan.scene().canvasWidth(), renderMan.scene().canvasHeight(), this); } /** * Update render time status label * @param time Total render time in milliseconds */ @Override public void setRenderTime(long time) { if (renderTimeLbl == null) return; int seconds = (int) ((time / 1000) % 60); int minutes = (int) ((time / 60000) % 60); int hours = (int) (time / 3600000); renderTimeLbl .setText(String.format("Render time: %d hours, %d minutes, %d seconds", hours, minutes, seconds)); } /** * Update samples per second status label * @param sps Samples per second */ @Override public void setSamplesPerSecond(int sps) { this.sps = sps; updateSPPLbl(); } /** * Update SPP status label * @param spp Samples per pixel */ @Override public void setSPP(int spp) { this.spp = spp; updateSPPLbl(); } private void updateSPPLbl() { if (sppLbl != null) { sppLbl.setText(decimalFormat.format(spp) + " SPP, " + decimalFormat.format(sps) + " SPS"); } } @Override public void setProgress(String task, int done, int start, int target) { if (progressBar != null && progressLbl != null && etaLbl != null) { progressLbl.setText( String.format("%s: %s of %s", task, decimalFormat.format(done), decimalFormat.format(target))); progressLbl.repaint(); progressBar.setMinimum(start); progressBar.setMaximum(target); progressBar.setValue(Math.min(target, done)); progressBar.repaint(); etaLbl.setText("ETA: N/A"); } } @Override public void setProgress(String task, int done, int start, int target, String eta) { if (progressBar != null && progressLbl != null && etaLbl != null) { setProgress(task, done, start, target); etaLbl.setText("ETA: " + eta); } } @Override public void taskAborted(String task) { // TODO add abort notice etaLbl.setText("ETA: N/A"); } @Override public void taskFailed(String task) { // TODO add abort notice etaLbl.setText("ETA: N/A"); } @Override public void onZoom(int diff) { Camera camera = renderMan.scene().camera(); double value = renderMan.scene().camera().getFoV(); double scale = camera.getMaxFoV() - camera.getMinFoV(); double offset = value / scale; double newValue = scale * Math.exp(Math.log(offset) + 0.1 * diff); if (!Double.isNaN(newValue) && !Double.isInfinite(newValue)) { renderMan.scene().camera().setFoV(newValue); } fov.update(); onCameraStateChange(); } /** * @return The render context for this Render Controls dialog */ public RenderContext getContext() { return context; } @Override public void renderStateChanged(RenderState state) { switch (state) { case PAUSED: startRenderBtn.setText("RESUME"); startRenderBtn.setIcon(Icon.play.imageIcon()); stopRenderBtn.setEnabled(true); stopRenderBtn.setForeground(Color.red); startRenderBtn.setEnabled(renderMan.getCurrentSPP() < renderMan.scene().getTargetSPP()); break; case PREVIEW: startRenderBtn.setText("START"); startRenderBtn.setIcon(Icon.play.imageIcon()); stopRenderBtn.setEnabled(false); stopRenderBtn.setForeground(Color.black); break; case RENDERING: startRenderBtn.setText("PAUSE"); startRenderBtn.setIcon(Icon.pause.imageIcon()); stopRenderBtn.setEnabled(true); stopRenderBtn.setForeground(Color.red); break; } } @Override public void chunksLoaded() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateCameraPosition(); showPreviewWindow(); } }); } @Override public void renderJobFinished(long time, int sps) { if (shutdownWhenDoneCB.isSelected()) { new ShutdownAlert(this); } } protected int getDumpFrequency() { int index = dumpFrequencyCB.getSelectedIndex(); if (index != -1) { index = Math.max(0, index); index = Math.min(dumpFrequencies.length - 1, index); return dumpFrequencies[index]; } else { try { return Integer.valueOf((String) dumpFrequencyCB.getSelectedItem()); } catch (NumberFormatException e) { return 0; } } } static class SkymapTextureLoader implements ActionListener { private final RenderManager renderMan; private static String defaultDirectory = System.getProperty("user.dir"); public SkymapTextureLoader(RenderManager renderMan) { this.renderMan = renderMan; } @Override public void actionPerformed(ActionEvent e) { CenteredFileDialog fileDialog = new CenteredFileDialog(null, "Open Skymap", FileDialog.LOAD); String directory; synchronized (SkyboxTextureLoader.class) { directory = defaultDirectory; } fileDialog.setDirectory(directory); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".hdr") || name.toLowerCase().endsWith(".pfm"); } }); fileDialog.setVisible(true); File selectedFile = fileDialog.getSelectedFile(); if (selectedFile != null) { synchronized (SkyboxTextureLoader.class) { File parent = selectedFile.getParentFile(); if (parent != null) { defaultDirectory = parent.getAbsolutePath(); } } renderMan.scene().sky().loadSkymap(selectedFile.getAbsolutePath()); } } }; static class SkyboxTextureLoader implements ActionListener { private final RenderManager renderMan; private final int textureIndex; private static String defaultDirectory = System.getProperty("user.dir"); public SkyboxTextureLoader(RenderManager renderMan, int textureIndex) { this.renderMan = renderMan; this.textureIndex = textureIndex; } @Override public void actionPerformed(ActionEvent e) { CenteredFileDialog fileDialog = new CenteredFileDialog(null, "Open Skybox Texture", FileDialog.LOAD); String directory; synchronized (SkyboxTextureLoader.class) { directory = defaultDirectory; } fileDialog.setDirectory(directory); fileDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".png") || name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".hdr") || name.toLowerCase().endsWith(".pfm"); } }); fileDialog.setVisible(true); File selectedFile = fileDialog.getSelectedFile(); if (selectedFile != null) { synchronized (SkyboxTextureLoader.class) { File parent = selectedFile.getParentFile(); if (parent != null) { defaultDirectory = parent.getAbsolutePath(); } } renderMan.scene().sky().loadSkyboxTexture(selectedFile.getAbsolutePath(), textureIndex); } } }; protected AtomicBoolean resetConfirmMutex = new AtomicBoolean(false); @Override public void renderResetRequested() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (resetConfirmMutex.compareAndSet(false, true)) { new ConfirmResetPopup(RenderControls.this, new AcceptOrRejectListener() { @Override public void onAccept() { renderMan.scene().resetRender(); resetConfirmMutex.set(false); } @Override public void onReject() { renderMan.revertPendingSceneChanges(); updateAllSettings(); resetConfirmMutex.set(false); } }); } } }); } public void drawViewBounds(Graphics g, ChunkView cv) { Camera camera = renderMan.scene().camera(); int width = renderMan.scene().canvasWidth(); int height = renderMan.scene().canvasHeight(); double halfWidth = width / (2.0 * height); Ray ray = new Ray(); int[] corners = { -1, -1, -1, -1, -1, -1, -1, -1 }; camera.calcViewRay(ray, -halfWidth, -0.5); findMapPos(corners, 0, 1, ray, cv); camera.calcViewRay(ray, -halfWidth, 0.5); findMapPos(corners, 2, 3, ray, cv); camera.calcViewRay(ray, halfWidth, 0.5); findMapPos(corners, 4, 5, ray, cv); camera.calcViewRay(ray, halfWidth, -0.5); findMapPos(corners, 6, 7, ray, cv); g.setColor(Color.YELLOW); g.drawLine(corners[0], corners[1], corners[2], corners[3]); g.drawLine(corners[2], corners[3], corners[4], corners[5]); g.drawLine(corners[4], corners[5], corners[6], corners[7]); g.drawLine(corners[6], corners[7], corners[0], corners[1]); int ox = (int) (cv.scale * (ray.o.x / 16 - cv.x0)); int oy = (int) (cv.scale * (ray.o.z / 16 - cv.z0)); g.drawLine(ox - 5, oy, ox + 5, oy); g.drawLine(ox, oy - 5, ox, oy + 5); camera.calcViewRay(ray, 0, 0); Vector3d o = new Vector3d(ray.o); o.x /= 16; o.z /= 16; o.scaleAdd(1, ray.d); int x = (int) (cv.scale * (o.x - cv.x0)); int y = (int) (cv.scale * (o.z - cv.z0)); g.drawLine(ox, oy, x, y); } private void findMapPos(int[] corners, int i, int j, Ray ray, ChunkView cv) { if (ray.d.y < 0 && ray.o.y > 63 || ray.d.y > 0 && ray.o.y < 63) { double d = (63 - ray.o.y) / ray.d.y; Vector3d pos = new Vector3d(); pos.scaleAdd(d, ray.d, ray.o); corners[i] = (int) (cv.scale * (pos.x / 16 - cv.x0)); corners[j] = (int) (cv.scale * (pos.z / 16 - cv.z0)); } else { double r = ray.d.x * ray.d.x + ray.d.z * ray.d.z; if (r > Ray.EPSILON) { double cvw = cv.x1 - cv.x0; double cvh = cv.z1 - cv.z0; Vector3d o = new Vector3d(ray.o); o.x /= 16; o.z /= 16; o.scaleAdd(Math.sqrt(cvw * cvw + cvh * cvh) / Math.sqrt(r), ray.d); corners[i] = (int) (cv.scale * (o.x - cv.x0)); corners[j] = (int) (cv.scale * (o.z - cv.z0)); } } } public void panToCamera() { Vector3d pos = renderMan.scene().camera().getPosition(); chunky.setView(pos.x / 16.0, pos.z / 16.0); } public void moveCameraTo(double x, double z) { Vector3d pos = new Vector3d(renderMan.scene().camera().getPosition()); pos.x = x; pos.z = z; renderMan.scene().camera().setPosition(pos); updateCameraPosition(); } }