/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program 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 2 of the License, or (at your option) any later
* version.
*
* This program 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
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.breakout;
import static org.andork.math3d.Vecmath.newMat4f;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.andork.awt.I18n;
import org.andork.awt.I18n.Localizer;
import org.andork.awt.anim.Animation;
import org.andork.awt.anim.AnimationQueue;
import org.andork.awt.event.MouseAdapterChain;
import org.andork.awt.event.MouseAdapterWrapper;
import org.andork.awt.layout.DelegatingLayoutManager;
import org.andork.awt.layout.Drawer;
import org.andork.awt.layout.DrawerAutoshowController;
import org.andork.awt.layout.DrawerModel;
import org.andork.awt.layout.Side;
import org.andork.awt.layout.SideConstraint;
import org.andork.awt.layout.SideConstraintLayoutDelegate;
import org.andork.bind.Binder;
import org.andork.bind.BinderWrapper;
import org.andork.bind.DefaultBinder;
import org.andork.bind.QMapKeyedBinder;
import org.andork.bind.QObjectAttributeBinder;
import org.andork.bind.ui.ButtonSelectedBinder;
import org.andork.collect.CollectionUtils;
import org.andork.func.FloatUnaryOperator;
import org.andork.jogl.AutoClipOrthoProjection;
import org.andork.jogl.DefaultJoglRenderer;
import org.andork.jogl.GL3Framebuffer;
import org.andork.jogl.InterpolationProjection;
import org.andork.jogl.JoglBackgroundColor;
import org.andork.jogl.JoglScene;
import org.andork.jogl.JoglViewSettings;
import org.andork.jogl.JoglViewState;
import org.andork.jogl.PerspectiveProjection;
import org.andork.jogl.Projection;
import org.andork.jogl.awt.JoglOrbiter;
import org.andork.jogl.awt.JoglOrthoNavigator;
import org.andork.jogl.awt.anim.GeneralViewXformOrbitAnimation;
import org.andork.jogl.awt.anim.ProjXformAnimation;
import org.andork.jogl.awt.anim.RandomViewOrbitAnimation;
import org.andork.jogl.awt.anim.SpringViewOrbitAnimation;
import org.andork.jogl.awt.anim.ViewXformAnimation;
import org.andork.jogl.old.BasicJOGLObject;
import org.andork.math.misc.Fitting;
import org.andork.math3d.Fitting3d;
import org.andork.math3d.FittingFrustum;
import org.andork.math3d.LineLineIntersection2d;
import org.andork.math3d.LinePlaneIntersection3f;
import org.andork.math3d.PickXform;
import org.andork.math3d.PlanarHull3f;
import org.andork.math3d.Vecmath;
import org.andork.q.QArrayList;
import org.andork.q.QLinkedHashMap;
import org.andork.q.QMap;
import org.andork.q.QObject;
import org.andork.spatial.Rectmath;
import org.andork.swing.AnnotatingRowSorter;
import org.andork.swing.FromEDT;
import org.andork.swing.OnEDT;
import org.andork.swing.SmartComboTableRowFilter;
import org.andork.swing.async.DrawerPinningTask;
import org.andork.swing.async.SelfReportingTask;
import org.andork.swing.async.SingleThreadedTaskService;
import org.andork.swing.async.Subtask;
import org.andork.swing.async.SubtaskFilePersister;
import org.andork.swing.async.SubtaskStreamBimapper;
import org.andork.swing.async.SubtaskStreamBimapperFactory;
import org.andork.swing.async.Task;
import org.andork.swing.async.TaskService;
import org.andork.swing.async.TaskServiceBatcher;
import org.andork.swing.async.TaskServiceFilePersister;
import org.andork.swing.async.TaskServiceSubtaskFilePersister;
import org.andork.swing.table.AnnotatingJTable;
import org.andork.swing.table.AnnotatingJTables;
import org.andork.swing.table.RowFilterFactory;
import org.apache.commons.io.FileUtils;
import org.breakout.StatsModel.MinAvgMax;
import org.breakout.model.ColorParam;
import org.breakout.model.ProjectArchiveModel;
import org.breakout.model.ProjectModel;
import org.breakout.model.RootModel;
import org.breakout.model.Shot;
import org.breakout.model.Station;
import org.breakout.model.Survey3dModel;
import org.breakout.model.Survey3dModel.SelectionEditor;
import org.breakout.model.Survey3dModel.Shot3d;
import org.breakout.model.Survey3dModel.Shot3dPickContext;
import org.breakout.model.Survey3dModel.Shot3dPickResult;
import org.breakout.model.SurveyTableModel;
import org.breakout.model.SurveyTableModel.SurveyTableModelCopier;
import org.breakout.model.TransparentTerrain;
import org.breakout.update.UpdateStatusPanelController;
import org.jdesktop.swingx.JXHyperlink;
import com.andork.plot.LinearAxisConversion;
import com.andork.plot.MouseLooper;
import com.andork.plot.PlotAxis;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2ES2;
import com.jogamp.opengl.GL3;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;
public class BreakoutMainView {
private class AnimationViewSaver implements Animation {
@Override
public long animate(long animTime) {
saveViewXform();
return 0;
}
}
private class ExportProjectArchiveTask extends DrawerPinningTask {
File newProjectFile;
private ExportProjectArchiveTask(File newProjectFile) {
super(getMainPanel(), taskListDrawer.holder());
this.newProjectFile = newProjectFile;
setStatus("Saving project...");
setIndeterminate(true);
showDialogLater();
}
@Override
protected void reallyDuringDialog() throws Exception {
setStatus("Exporting project archive: " + newProjectFile + "...");
setTotal(1000);
Subtask rootSubtask = new Subtask(this);
rootSubtask.setTotal(3);
Subtask prepareSubtask = rootSubtask.beginSubtask(1);
prepareSubtask.setStatus("Preparing for export");
SurveyTableModel surveyTableModelCopy = new SurveyTableModel();
SurveyTableModelCopier copier = new SurveyTableModelCopier();
copier.copyInBackground(surveyDrawer.table().getModel(), surveyTableModelCopy, 1000, prepareSubtask);
ProjectArchiveModel projectModel = new ProjectArchiveModel(getProjectModel(), surveyTableModelCopy);
prepareSubtask.end();
rootSubtask.setCompleted(prepareSubtask.getProportion());
Subtask exportSubtask = rootSubtask.beginSubtask(2);
exportSubtask.setStatus("Exporting project to " + newProjectFile);
try {
new ProjectArchiveModelStreamBimapper(getI18n(), exportSubtask).write(projectModel,
new FileOutputStream(newProjectFile));
exportSubtask.end();
rootSubtask.setCompleted(rootSubtask.getCompleted() + exportSubtask.getProportion());
} catch (final Exception ex) {
ex.printStackTrace();
new OnEDT() {
@Override
public void run() throws Throwable {
JOptionPane.showMessageDialog(getMainPanel(),
ex.getClass().getName() + ": " + ex.getLocalizedMessage(),
"Failed to export project archive", JOptionPane.ERROR_MESSAGE);
}
};
return;
}
}
}
class FitToFilteredHandler implements ActionListener {
AnnotatingJTable table;
long lastAction = 0;
public FitToFilteredHandler(AnnotatingJTable table) {
super();
this.table = table;
}
@Override
public void actionPerformed(ActionEvent e) {
final long time = System.currentTimeMillis();
lastAction = time;
table.getAnnotatingRowSorter().invokeWhenDoneSorting(() -> {
if (time >= lastAction) {
flyToFiltered(table);
}
});
}
}
private class HoverUpdater extends Task {
Survey3dModel model3d;
MouseEvent e;
public HoverUpdater(Survey3dModel model3d, MouseEvent e) {
super("Updating mouseover glow...");
this.model3d = model3d;
this.e = e;
}
@Override
protected void execute() throws Exception {
final Shot3dPickResult picked = pick(model3d, e, hoverUpdaterSpc);
Subtask subtask = new Subtask(this);
Subtask glowSubtask = subtask.beginSubtask(1);
glowSubtask.setStatus("Updating mouseover glow");
if (picked != null) {
LinearAxisConversion conversion = new FromEDT<LinearAxisConversion>() {
@Override
public LinearAxisConversion run() throws Throwable {
Shot shot = model3d.getOriginalShots().get(picked.picked.getNumber());
hintLabel.setText(String.format(
"<html>Stations: <b>%s - %s</b> Dist: <b>%.2f</b> Azm: <b>%.2f</b>"
+ " Inc: <b>%.2f</b> <i>%s</i></html>",
shot.from.name, shot.to.name,
shot.dist, shot.azm, shot.inc, shot.desc));
LinearAxisConversion conversion = getProjectModel().get(ProjectModel.highlightRange);
LinearAxisConversion conversion2 = new LinearAxisConversion(conversion.invert(0.0), 1.0,
conversion.invert(settingsDrawer.getGlowDistAxis().getViewSpan()), 0.0);
return conversion2;
}
}.result();
model3d.updateGlow(picked.picked, picked.locationAlongShot, conversion, glowSubtask);
} else {
OnEDT.onEDT(() -> hintLabel.setText(" "));
model3d.updateGlow(null, null, null, glowSubtask);
}
if (!isCanceling()) {
autoDrawable.display();
}
}
@Override
public boolean isCancelable() {
return true;
}
}
private class ImportProjectArchiveTask extends DrawerPinningTask {
File newProjectFile;
private ImportProjectArchiveTask(File newProjectFile) {
super(getMainPanel(), taskListDrawer.holder());
this.newProjectFile = newProjectFile;
setStatus("Saving project...");
setIndeterminate(true);
showDialogLater();
}
@Override
protected void reallyDuringDialog() throws Exception {
setStatus("Importing project archive: " + newProjectFile + "...");
ProjectArchiveModel projectModel = null;
Subtask rootSubtask = new Subtask(this);
try {
projectModel = new ProjectArchiveModelStreamBimapper(getI18n(), rootSubtask)
.read(new FileInputStream(newProjectFile));
if (projectModel == null) {
return;
}
} catch (final Exception ex) {
ex.printStackTrace();
new OnEDT() {
@Override
public void run() throws Throwable {
JOptionPane.showMessageDialog(getMainPanel(),
ex.getClass().getName() + ": " + ex.getLocalizedMessage(),
"Failed to import project archive", JOptionPane.ERROR_MESSAGE);
}
};
return;
}
final ProjectArchiveModel finalProjectModel = projectModel;
new OnEDT() {
@Override
public void run() throws Throwable {
finalProjectModel.getProjectModel().set(ProjectModel.surveyFile,
getProjectModel().get(ProjectModel.surveyFile));
replaceNulls(finalProjectModel.getProjectModel(),
getRootModel().get(RootModel.currentProjectFile));
if (projectPersister != null) {
if (getProjectModel() != null) {
getProjectModel().changeSupport().removePropertyChangeListener(projectPersister);
}
finalProjectModel.getProjectModel().changeSupport()
.addPropertyChangeListener(projectPersister);
projectPersister.saveLater(finalProjectModel.getProjectModel());
}
projectModelBinder.set(finalProjectModel.getProjectModel());
surveyDrawer
.table()
.getModel()
.copyRowsFrom(finalProjectModel.getSurveyTableModel(), 0,
finalProjectModel.getSurveyTableModel().getRowCount() - 1, 0);
}
};
}
}
private class MinAvgMaxCalc {
int count = 0;
double total = 0.0;
double min = Double.NaN;
double max = Double.NaN;
public void add(double value) {
min = Vecmath.nmin(min, value);
max = Vecmath.nmax(max, value);
total += value;
count++;
}
public double getAvg() {
return total / count;
}
public QObject<MinAvgMax> toModel() {
QObject<MinAvgMax> result = MinAvgMax.spec.newObject();
result.set(MinAvgMax.min, min);
result.set(MinAvgMax.avg, getAvg());
result.set(MinAvgMax.max, max);
return result;
}
}
private class MousePickHandler extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1 || e.isAltDown()) {
return;
}
Shot3dPickResult picked = pick(model3d, e, spc);
if (picked == null) {
surveyDrawer.table().clearSelection();
} else if (e.getClickCount() == 2) {
int index = picked.picked.getNumber();
int modelRow = surveyDrawer.table().getModel().rowOfShot(index);
if (modelRow >= 0) {
QObject<SurveyTableModel.Row> row = surveyDrawer.table().getModel().getRow(modelRow);
if (row != null) {
String link = row.get(SurveyTableModel.Row.scannedNotes);
if (link != null) {
openSurveyNotes(link);
}
}
}
}
}
@Override
public void mouseMoved(MouseEvent e) {
if (model3d != null) {
HoverUpdater updater = new HoverUpdater(model3d, e);
for (Task task : rebuildTaskService.getTasks()) {
if (task instanceof HoverUpdater) {
task.cancel();
}
}
rebuildTaskService.submit(updater);
}
}
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
if (e.isAltDown()) {
for (Drawer drawer : Arrays.asList(surveyDrawer, miniSurveyDrawer, taskListDrawer, settingsDrawer)) {
drawer.holder().release(DrawerAutoshowController.autoshowDrawerHolder);
}
canvasMouseAdapterWrapper.setWrapped(windowSelectionMouseHandler);
windowSelectionMouseHandler.start(e);
return;
}
Shot3dPickResult picked = pick(model3d, e, spc);
if (picked == null) {
return;
}
ListSelectionModel selModel = surveyDrawer.table().getModelSelectionModel();
int index = picked.picked.getNumber();
int modelRow = surveyDrawer.table().getModel().rowOfShot(index);
if (modelRow >= 0) {
if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) {
if (selModel.isSelectedIndex(modelRow)) {
selModel.removeSelectionInterval(modelRow, modelRow);
} else {
selModel.addSelectionInterval(modelRow, modelRow);
}
} else {
selModel.setSelectionInterval(modelRow, modelRow);
}
int viewRow = surveyDrawer.table().convertRowIndexToView(modelRow);
if (viewRow >= 0) {
Rectangle visibleRect = surveyDrawer.table().getVisibleRect();
Rectangle cellRect = surveyDrawer.table().getCellRect(viewRow, 0, true);
visibleRect.y = cellRect.y + cellRect.height / 2 - visibleRect.height / 2;
surveyDrawer.table().scrollRectToVisible(visibleRect);
}
}
autoDrawable.display();
}
}
private class OpenProjectTask extends DrawerPinningTask {
Path newProjectFile;
Path relativizedNewProjectFile;
private OpenProjectTask(Path newProjectFile) {
super(getMainPanel(), taskListDrawer.holder());
this.newProjectFile = newProjectFile;
relativizedNewProjectFile = rootDirectory.toAbsolutePath().relativize(
newProjectFile.toAbsolutePath());
setStatus("Saving current project...");
setIndeterminate(true);
showDialogLater();
}
@Override
protected void reallyDuringDialog() throws Exception {
setStatus("Opening project: " + newProjectFile + "...");
new OnEDT() {
@Override
public void run() throws Throwable {
QObject<RootModel> rootModel = getRootModel();
rootModel.set(RootModel.currentProjectFile, relativizedNewProjectFile);
QArrayList<Path> recentProjectFiles = rootModel.get(RootModel.recentProjectFiles);
if (recentProjectFiles == null) {
recentProjectFiles = QArrayList.newInstance();
rootModel.set(RootModel.recentProjectFiles, recentProjectFiles);
}
recentProjectFiles.remove(relativizedNewProjectFile);
while (recentProjectFiles.size() > 20) {
recentProjectFiles.remove(recentProjectFiles.size() - 1);
}
recentProjectFiles.add(0, relativizedNewProjectFile);
if (getProjectModel() != null && projectPersister != null) {
getProjectModel().changeSupport().removePropertyChangeListener(projectPersister);
}
projectPersister = new TaskServiceFilePersister<QObject<ProjectModel>>(ioTaskService,
"Saving project...", QObjectBimappers.defaultBimapper(ProjectModel.defaultMapper),
newProjectFile.toFile());
}
};
QObject<ProjectModel> projectModel = null;
try {
projectModel = projectPersister.load();
if (projectModel == null) {
projectModel = ProjectModel.instance.newObject();
}
replaceNulls(projectModel, newProjectFile);
} catch (final Exception ex) {
ex.printStackTrace();
new OnEDT() {
@Override
public void run() throws Throwable {
JOptionPane.showMessageDialog(getMainPanel(),
ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage(),
"Failed to load project", JOptionPane.ERROR_MESSAGE);
}
};
return;
}
final QObject<ProjectModel> finalProjectModel = projectModel;
new OnEDT() {
@Override
public void run() throws Throwable {
finalProjectModel.changeSupport().addPropertyChangeListener(projectPersister);
projectModelBinder.set(finalProjectModel);
float[] viewXform = finalProjectModel.get(ProjectModel.viewXform);
if (viewXform != null) {
renderer.getViewSettings().setViewXform(viewXform);
}
Projection projCalculator = finalProjectModel.get(ProjectModel.projCalculator);
if (projCalculator != null) {
renderer.getViewSettings().setProjection(projCalculator);
}
if (finalProjectModel.get(ProjectModel.cameraView) == CameraView.PERSPECTIVE) {
installPerspectiveMouseAdapters();
} else {
installOrthoMouseAdapters();
}
}
};
Path surveyFile = newProjectFile.toAbsolutePath().getParent().resolve(
projectModel.get(ProjectModel.surveyFile)).normalize();
openSurveyFile(surveyFile);
}
}
private class OpenSurveyTask extends DrawerPinningTask {
Path newSurveyFile;
Path relativizedNewSurveyFile;
private OpenSurveyTask(Path newSurveyFile) {
super(getMainPanel(), taskListDrawer.holder());
this.newSurveyFile = newSurveyFile;
Path projectPath = rootDirectory.toAbsolutePath()
.resolve(getRootModel().get(RootModel.currentProjectFile)).normalize();
relativizedNewSurveyFile = projectPath.toAbsolutePath().getParent()
.relativize(newSurveyFile.toAbsolutePath());
setStatus("Saving current survey...");
setIndeterminate(true);
showDialogLater();
}
@Override
protected void reallyDuringDialog() throws Exception {
boolean changed = new FromEDT<Boolean>() {
@Override
public Boolean run() throws Throwable {
File absoluteSurveyFile = newSurveyFile.toAbsolutePath().toFile();
if (surveyPersister == null || !absoluteSurveyFile.equals(surveyPersister.getFile())) {
surveyPersister = new TaskServiceSubtaskFilePersister<SurveyTableModel>(
ioTaskService,
"Saving survey...",
new SubtaskStreamBimapperFactory<SurveyTableModel, SubtaskStreamBimapper<SurveyTableModel>>() {
@Override
public SubtaskStreamBimapper<SurveyTableModel> createSubtaskStreamBimapper(
Subtask subtask) {
return new SurveyTableModelStreamBimapper(subtask);
}
}, absoluteSurveyFile);
} else {
return false;
}
getProjectModel().set(ProjectModel.surveyFile, relativizedNewSurveyFile);
try {
surveyTableChangeHandler.setPersistOnUpdate(false);
surveyDrawer.table().getModel().clear();
} finally {
surveyTableChangeHandler.setPersistOnUpdate(true);
}
return true;
}
}.result();
if (!changed) {
return;
}
setStatus("Opening survey: " + newSurveyFile + "...");
SurveyTableModel surveyModel;
try {
surveyModel = surveyPersister.load(null);
} catch (final Exception ex) {
ex.printStackTrace();
new OnEDT() {
@Override
public void run() throws Throwable {
JOptionPane.showConfirmDialog(getMainPanel(),
ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage(),
"Failed to load survey", JOptionPane.ERROR_MESSAGE);
}
};
return;
}
final SurveyTableModel finalSurveyModel = surveyModel;
new OnEDT() {
@Override
public void run() throws Throwable {
if (finalSurveyModel != null && finalSurveyModel.getRowCount() > 0) {
try {
surveyTableChangeHandler.setPersistOnUpdate(false);
surveyDrawer.table().getModel()
.copyRowsFrom(finalSurveyModel, 0, finalSurveyModel.getRowCount() - 1, 0);
} finally {
surveyTableChangeHandler.setPersistOnUpdate(true);
}
}
}
};
}
}
class OtherMouseHandler extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
cameraAnimationQueue.clear();
}
@Override
public void mouseReleased(MouseEvent e) {
saveViewXform();
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
saveViewXform();
}
}
class SurveyTableChangeHandler extends TaskServiceBatcher<TableModelEvent> implements TableModelListener {
private boolean persistOnUpdate = true;
private boolean rebuildViewOnUpdate = true;
public SurveyTableChangeHandler(TaskService taskService) {
super(taskService, true);
}
@Override
public BatcherTask<TableModelEvent> createTask(final LinkedList<TableModelEvent> batch) {
BatcherTask<TableModelEvent> task = new BatcherTask<TableModelEvent>("Updating view") {
@Override
protected void execute() {
try {
OnEDT.onEDT(() -> taskListDrawer.holder().hold(this));
reallyExecute();
} finally {
OnEDT.onEDT(() -> taskListDrawer.holder().release(this));
}
}
@Override
public boolean isCancelable() {
return true;
}
protected void reallyExecute() {
setTotal(1000);
Subtask copySubtask = new Subtask(this);
copySubtask.setStatus("Parsing shot data");
copySubtask.setIndeterminate(false);
SurveyTableModel copy = new SurveyTableModel();
SurveyTableModelCopier copier = new SurveyTableModelCopier();
SurveyTableModel model = new FromEDT<SurveyTableModel>() {
@Override
public SurveyTableModel run() throws Throwable {
return surveyDrawer.table().getModel();
}
}.result();
copier.copyInBackground(model, copy, 1000, copySubtask);
if (copySubtask.isCanceling()) {
return;
}
copySubtask.end();
Subtask parsingSubtask = new Subtask(this);
parsingSubtask.setStatus("Parsing shot data");
parsingSubtask.setIndeterminate(false);
final List<Shot> shots = copy.createShots(parsingSubtask);
if (parsingSubtask.isCanceling()) {
return;
}
new OnEDT() {
@Override
public void run() throws Throwable {
surveyDrawer.table().getModel().setShots(shots);
}
};
final List<Shot> nonNullShots = new ArrayList<Shot>();
if (!shots.isEmpty()) {
Subtask calculatingSubtask = new Subtask(this);
calculatingSubtask.setStatus("calculating");
calculatingSubtask.setIndeterminate(true);
LinkedHashSet<Station> stations = new LinkedHashSet<Station>();
for (Shot shot : shots) {
if (shot != null) {
nonNullShots.add(shot);
stations.add(shot.from);
stations.add(shot.to);
}
}
Shot.computeConnected(stations);
LineLineIntersection2d llx = new LineLineIntersection2d();
for (Station station : stations) {
station.calcSplayPoints(llx);
}
calculatingSubtask.end();
}
updateModel(nonNullShots);
}
public void updateModel(List<Shot> shots) {
setStatus("Updating view...");
SwingUtilities.invokeLater(() -> {
if (model3d != null) {
final Survey3dModel model3d = BreakoutMainView.this.model3d;
BreakoutMainView.this.model3d = null;
autoDrawable.invoke(false, drawable -> {
scene.remove(model3d);
scene.disposeLater(model3d);
return false;
});
}
});
setStatus("Updating view: constructing new model...");
final Survey3dModel model = Survey3dModel.create(shots, 10, 3, 3, this);
if (isCanceling()) {
return;
}
setStatus("Updating view: installing new model...");
float[] bounds = Arrays.copyOf(model.getTree().getRoot().mbr(), 6);
bounds[1] = bounds[4] + 100;
bounds[4] = bounds[1] + 100;
float[][][] vertices = new float[100][100][3];
Random rand = new Random(2);
TransparentTerrain.randomVerts(vertices, bounds, rand);
TransparentTerrain terrain = new TransparentTerrain(vertices);
SwingUtilities.invokeLater(() -> {
model3d = model;
model.setParamPaint(settingsDrawer.getParamColorationAxisPaint());
// GlyphCache cache = new GlyphCache( scene , new Font(
// "Arial" , Font.PLAIN , 72 ) , 1024 , 1024 ,
// new BufferedImageIntFactory(
// BufferedImage.TYPE_INT_ARGB ) , new
// OutlinedGlyphPagePainter(
// new BasicStroke( 3f , BasicStroke.CAP_ROUND ,
// BasicStroke.JOIN_ROUND ) ,
// Color.BLACK , Color.WHITE ) );
//
// JoglText text = new JoglText.Builder( )
// .ascent( 0 , cache.fontMetrics.getAscent( ) , 0
// ).baseline( cache.fontMetrics.getAscent( ) , 0 , 0 )
// .add( "This is a test" , cache , 1f , 1f , 1f , 1f
// ).create( scene );
//
// text.use( );
// scene.add( text );
projectModelBinder.update(true);
float[] center = new float[3];
Rectmath.center(model.getTree().getRoot().mbr(), center);
orbiter.setCenter(center);
navigator.setCenter(center);
autoDrawable.invoke(false, drawable -> {
scene.add(model);
scene.initLater(model);
// scene.add( terrain );
// scene.initLater( terrain );
return false;
});
});
}
};
return task;
}
public boolean isPersistOnUpdate() {
return persistOnUpdate;
}
public boolean isRebuildViewOnUpdate() {
return rebuildViewOnUpdate;
}
public void setPersistOnUpdate(boolean persistOnUpdate) {
this.persistOnUpdate = persistOnUpdate;
}
public void setRebuildViewOnUpdate(boolean rebuildViewOnUpdate) {
this.rebuildViewOnUpdate = rebuildViewOnUpdate;
}
@Override
public void tableChanged(TableModelEvent e) {
if (persistOnUpdate && surveyPersister != null) {
surveyPersister.saveLater((SurveyTableModel) e.getSource());
}
if (rebuildViewOnUpdate) {
add(e);
}
}
}
private class TableSelectionHandler implements ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() || model3d == null) {
return;
}
final Survey3dModel model3d = BreakoutMainView.this.model3d;
List<Survey3dModel.Shot3d> shot3ds = model3d.getShots();
final SelectionEditor editor = model3d.editSelection();
ListSelectionModel selModel = (ListSelectionModel) e.getSource();
if (e.getFirstIndex() < 0) {
for (Survey3dModel.Shot3d shot3d : shot3ds) {
editor.deselect(shot3d);
}
miniSurveyDrawer.statsPanel().getModelBinder().set(StatsModel.spec.newObject());
} else {
MinAvgMaxCalc distCalc = new MinAvgMaxCalc();
MinAvgMaxCalc northCalc = new MinAvgMaxCalc();
MinAvgMaxCalc eastCalc = new MinAvgMaxCalc();
MinAvgMaxCalc depthCalc = new MinAvgMaxCalc();
QObject<StatsModel> statsModel = StatsModel.spec.newObject();
for (int i = e.getFirstIndex(); i <= e.getLastIndex()
&& i < surveyDrawer.table().getModel().getRowCount(); i++) {
Shot shot = surveyDrawer.table().getModel().shotAtRow(i);
if (shot == null) {
continue;
}
if (selModel.isSelectedIndex(i)) {
editor.select(shot3ds.get(shot.number));
if (!Double.isNaN(shot.dist)) {
distCalc.add(shot.dist);
}
if (shot.fromSplayPoints != null) {
for (float[] point : shot.fromSplayPoints) {
if (!Vecmath.hasNaNsOrInfinites(point)) {
northCalc.add(-point[2]);
eastCalc.add(point[0]);
depthCalc.add(-point[1]);
}
}
}
if (shot.toSplayPoints != null) {
for (float[] point : shot.toSplayPoints) {
if (!Vecmath.hasNaNsOrInfinites(point)) {
northCalc.add(-point[2]);
eastCalc.add(point[0]);
depthCalc.add(-point[1]);
}
}
}
} else {
editor.deselect(shot3ds.get(shot.number));
}
}
statsModel.set(StatsModel.numSelected, distCalc.count);
statsModel.set(StatsModel.totalDistance, distCalc.total);
statsModel.set(StatsModel.distStats, distCalc.toModel());
statsModel.set(StatsModel.northStats, northCalc.toModel());
statsModel.set(StatsModel.eastStats, eastCalc.toModel());
statsModel.set(StatsModel.depthStats, depthCalc.toModel());
miniSurveyDrawer.statsPanel().getModelBinder().set(statsModel);
}
rebuildTaskService.submit(task -> {
editor.commit();
List<Shot> origShots = new ArrayList<>();
Set<Survey3dModel.Shot3d> newSelectedShots = new HashSet<>();
model3d.addOriginalShotsTo(origShots);
model3d.addSelectedShotsTo(newSelectedShots);
float[] bounds = Rectmath.voidRectf(3);
float[] p = Rectmath.voidRectf(3);
for (Survey3dModel.Shot3d shot3d : newSelectedShots) {
Shot origShot = origShots.get(shot3d.getNumber());
p[0] = (float) Math.min(origShot.from.position[0], origShot.to.position[0]);
p[1] = (float) Math.min(origShot.from.position[1], origShot.to.position[1]);
p[2] = (float) Math.min(origShot.from.position[2], origShot.to.position[2]);
p[3] = (float) Math.max(origShot.from.position[0], origShot.to.position[0]);
p[4] = (float) Math.max(origShot.from.position[1], origShot.to.position[1]);
p[5] = (float) Math.max(origShot.from.position[2], origShot.to.position[2]);
Rectmath.union3(bounds, p, bounds);
}
if (!newSelectedShots.isEmpty()) {
// scale3( center , 0.5 / newSelectedShots.size( ) );
p[0] = (bounds[0] + bounds[3]) * 0.5f;
p[1] = (bounds[1] + bounds[4]) * 0.5f;
p[2] = (bounds[2] + bounds[5]) * 0.5f;
SwingUtilities.invokeLater(() -> {
orbiter.setCenter(p);
navigator.setCenter(p);
});
}
autoDrawable.display();
});
}
}
private static Shot3dPickContext hoverUpdaterSpc = new Shot3dPickContext();
private static final int SCANNED_NOTES_SEARCH_DEPTH = 10;
GLAutoDrawable autoDrawable;
GLCanvas canvas;
JoglScene scene;
JoglBackgroundColor bgColor;
DefaultJoglRenderer renderer;
DefaultNavigator navigator;
JoglOrbiter orbiter;
JoglOrthoNavigator orthoNavigator;
I18n i18n = new I18n();
PerspectiveProjection perspCalculator = new PerspectiveProjection(
(float) Math.PI / 2, 1f,
1e7f);
TaskService rebuildTaskService;
TaskService sortTaskService;
TaskService ioTaskService;
SurveyTableChangeHandler surveyTableChangeHandler;
final double[] fromLoc = new double[3];
final double[] toLoc = new double[3];
final double[] toToLoc = new double[3];
final double[] leftAtTo = new double[3];
final double[] leftAtTo2 = new double[3];
final double[] leftAtFrom = new double[3];
JPanel mainPanel;
JLayeredPane layeredPane;
MouseAdapterWrapper canvasMouseAdapterWrapper;
// normal mouse mode
MouseLooper mouseLooper;
MouseAdapterChain mouseAdapterChain;
MousePickHandler pickHandler;
DrawerAutoshowController autoshowController;
OtherMouseHandler otherMouseHandler;
WindowSelectionMouseHandler windowSelectionMouseHandler;
TableSelectionHandler selectionHandler;
RowFilterFactory<String, TableModel, Integer> rowFilterFactory;
SurveyDrawer surveyDrawer;
MiniSurveyDrawer miniSurveyDrawer;
TaskListDrawer taskListDrawer;
SettingsDrawer settingsDrawer;
Survey3dModel model3d;
float[] v = newMat4f();
int debugMbrCount = 0;
List<BasicJOGLObject> debugMbrs = new ArrayList<BasicJOGLObject>();
Shot3dPickContext spc = new Shot3dPickContext();
final LinePlaneIntersection3f lpx = new LinePlaneIntersection3f();
final float[] p0 = new float[3];
final float[] p1 = new float[3];
final float[] p2 = new float[3];
File rootFile;
Path rootDirectory;
TaskServiceFilePersister<QObject<RootModel>> rootPersister;
final Binder<QObject<RootModel>> rootModelBinder = new DefaultBinder<QObject<RootModel>>();
final Binder<QObject<ProjectModel>> projectModelBinder = new DefaultBinder<QObject<ProjectModel>>();
Binder<ColorParam> colorParamBinder = QObjectAttributeBinder.bind(
ProjectModel.colorParam,
projectModelBinder);
Binder<QMap<ColorParam, LinearAxisConversion, ?>> paramRangesBinder = QObjectAttributeBinder.bind(
ProjectModel.paramRanges,
projectModelBinder);
Binder<LinearAxisConversion> paramRangeBinder = QMapKeyedBinder.bindKeyed(
colorParamBinder,
paramRangesBinder);
TaskServiceFilePersister<QObject<ProjectModel>> projectPersister;
SubtaskFilePersister<SurveyTableModel> surveyPersister;
final AnimationQueue cameraAnimationQueue = new AnimationQueue();
NewProjectAction newProjectAction = new NewProjectAction(this);
EditSurveyScanPathsAction editSurveyScanPathsAction = new EditSurveyScanPathsAction(this);
OpenProjectAction openProjectAction = new OpenProjectAction(this);
OpenSurveyAction openSurveyAction = new OpenSurveyAction(this);
ImportProjectArchiveAction importProjectArchiveAction = new ImportProjectArchiveAction(
this);
ExportProjectArchiveAction exportProjectArchiveAction = new ExportProjectArchiveAction(
this);
ExportImageAction exportImageAction = new ExportImageAction(this);
final WeakHashMap<Animation, Object> protectedAnimations = new WeakHashMap<>();
JLabel hintLabel;
CameraView currentView;
private final PlanarHull3f hull = new PlanarHull3f();
public BreakoutMainView() {
final GLProfile glp = GLProfile.get(GLProfile.GL3);
final GLCapabilities caps = new GLCapabilities(glp);
autoDrawable = canvas = new GLCanvas(caps);
autoDrawable.display();
scene = new JoglScene();
bgColor = new JoglBackgroundColor();
scene.add(bgColor);
renderer = new DefaultJoglRenderer(scene, new GL3Framebuffer(), 1);
autoDrawable.addGLEventListener(renderer);
navigator = new DefaultNavigator(autoDrawable, renderer);
navigator.setMoveFactor(5f);
navigator.setWheelFactor(5f);
orbiter = new JoglOrbiter(autoDrawable, renderer.getViewSettings());
orthoNavigator = new JoglOrthoNavigator(autoDrawable, renderer.getViewState(), renderer.getViewSettings());
ioTaskService = new SingleThreadedTaskService();
rebuildTaskService = new SingleThreadedTaskService();
sortTaskService = new SingleThreadedTaskService();
JLabel highlightLabel = new JLabel("Highlight: ");
JLabel filterLabel = new JLabel("Filter: ");
hintLabel = new JLabel("A");
hintLabel.setForeground(Color.WHITE);
hintLabel.setBackground(Color.BLACK);
hintLabel.setOpaque(true);
Font hintFont = hintLabel.getFont();
hintLabel.setFont(hintFont.deriveFont(Font.PLAIN).deriveFont(hintFont.getSize2D() + 3f));
hintLabel.setPreferredSize(new Dimension(200, hintLabel.getPreferredSize().height));
hintLabel.setText(" ");
hintLabel.setVerticalAlignment(SwingConstants.TOP);
final Consumer<Runnable> sortRunner = r -> {
Task task = new Task("Sorting survey table...") {
@Override
protected void execute() {
r.run();
}
};
sortTaskService.submit(task);
};
OnEDT.onEDT(() -> {
surveyDrawer = new SurveyDrawer(sortRunner);
rowFilterFactory = text -> new SmartComboTableRowFilter(Arrays.asList(
new SurveyDesignationFilter(text),
// new SurveyRegexFilter(text),
new SurveyorFilter(text),
new DescriptionFilter(text)));
surveyDrawer.filterField().textComponent.getDocument().addDocumentListener(
AnnotatingJTables.createFilterFieldListener(surveyDrawer.table(),
surveyDrawer.filterField().textComponent, rowFilterFactory));
surveyDrawer.highlightField().textComponent.getDocument().addDocumentListener(
AnnotatingJTables.createHighlightFieldListener(surveyDrawer.table(),
surveyDrawer.highlightField().textComponent, rowFilterFactory, Color.YELLOW));
});
Color darkColor = new Color(255 * 3 / 10, 255 * 3 / 10, 255 * 3 / 10);
pickHandler = new MousePickHandler();
canvasMouseAdapterWrapper = new MouseAdapterWrapper();
canvas.addMouseListener(canvasMouseAdapterWrapper);
canvas.addMouseMotionListener(canvasMouseAdapterWrapper);
canvas.addMouseWheelListener(canvasMouseAdapterWrapper);
mouseLooper = new MouseLooper();
windowSelectionMouseHandler = new WindowSelectionMouseHandler(new WindowSelectionMouseHandler.Context() {
@Override
public void endSelection() {
canvasMouseAdapterWrapper.setWrapped(mouseLooper);
}
@Override
public GLAutoDrawable getDrawable() {
return autoDrawable;
}
@Override
public TaskService getRebuildTaskService() {
return rebuildTaskService;
}
@Override
public JoglScene getScene() {
return scene;
}
@Override
public Survey3dModel getSurvey3dModel() {
return model3d;
}
@Override
public JoglViewState getViewState() {
return renderer.getViewState();
}
@Override
public void selectShots(Set<Shot3d> newSelected, boolean add, boolean toggle) {
OnEDT.onEDT(() -> {
ListSelectionModel selModel = surveyDrawer.table().getModelSelectionModel();
SurveyTableModel model = surveyDrawer.table().getModel();
selModel.setValueIsAdjusting(true);
if (!add && !toggle) {
selModel.clearSelection();
}
for (Shot3d shot3d : newSelected) {
int row = model.rowOfShot(shot3d.getNumber());
if (toggle && selModel.isSelectedIndex(row)) {
selModel.removeSelectionInterval(row, row);
} else {
selModel.addSelectionInterval(row, row);
}
}
selModel.setValueIsAdjusting(false);
});
}
});
canvasMouseAdapterWrapper.setWrapped(mouseLooper);
// glWindow.addMouseListener( new NEWT2AWTMouseEventConverter( canvas ,
// canvasMouseAdapterWrapper ) );
autoshowController = new DrawerAutoshowController();
otherMouseHandler = new OtherMouseHandler();
mouseAdapterChain = new MouseAdapterChain();
mouseAdapterChain.addMouseAdapter(pickHandler);
mouseAdapterChain.addMouseAdapter(autoshowController);
mouseAdapterChain.addMouseAdapter(otherMouseHandler);
layeredPane = new JLayeredPane();
layeredPane.setLayout(new DelegatingLayoutManager() {
@Override
public void onLayoutChanged(Container target) {
Window w = SwingUtilities.getWindowAncestor(target);
if (w != null) {
w.invalidate();
w.validate();
}
target.invalidate();
target.validate();
}
});
taskListDrawer = new TaskListDrawer();
taskListDrawer.addTaskService(rebuildTaskService);
taskListDrawer.addTaskService(sortTaskService);
taskListDrawer.addTaskService(ioTaskService);
taskListDrawer.addTo(layeredPane, JLayeredPane.DEFAULT_LAYER + 5);
settingsDrawer = new SettingsDrawer(i18n, rootModelBinder, projectModelBinder);
settingsDrawer.addTo(layeredPane, 1);
surveyTableChangeHandler = new SurveyTableChangeHandler(rebuildTaskService);
surveyDrawer.table().getModel().addTableModelListener(surveyTableChangeHandler);
surveyDrawer.addTo(layeredPane, 5);
mainPanel = new JPanel(new BorderLayout());
mainPanel.add(layeredPane, BorderLayout.CENTER);
selectionHandler = new TableSelectionHandler();
surveyDrawer.table().getModelSelectionModel().addListSelectionListener(selectionHandler);
OnEDT.onEDT(() -> {
miniSurveyDrawer = new MiniSurveyDrawer(i18n, sortRunner);
miniSurveyDrawer.table().setModel(surveyDrawer.table().getModel());
miniSurveyDrawer.table().setModelSelectionModel(surveyDrawer.table().getModelSelectionModel());
miniSurveyDrawer.filterField().textComponent.getDocument().addDocumentListener(
AnnotatingJTables.createFilterFieldListener(miniSurveyDrawer.table(),
miniSurveyDrawer.filterField().textComponent, rowFilterFactory));
miniSurveyDrawer.highlightField().textComponent.getDocument().addDocumentListener(
AnnotatingJTables.createHighlightFieldListener(miniSurveyDrawer.table(),
miniSurveyDrawer.highlightField().textComponent, rowFilterFactory, Color.YELLOW));
miniSurveyDrawer.delegate().dockingSide(Side.LEFT);
miniSurveyDrawer.mainResizeHandle();
miniSurveyDrawer.addTo(layeredPane, 3);
miniSurveyDrawer.delegate()
.putExtraConstraint(Side.BOTTOM, new SideConstraint(surveyDrawer, Side.TOP, 0));
});
settingsDrawer.delegate().putExtraConstraint(Side.BOTTOM, new SideConstraint(surveyDrawer, Side.TOP, 0));
taskListDrawer.delegate().putExtraConstraint(Side.LEFT,
new SideConstraint(miniSurveyDrawer, Side.RIGHT, 0));
taskListDrawer.delegate().putExtraConstraint(Side.RIGHT,
new SideConstraint(settingsDrawer, Side.LEFT, 0));
SideConstraintLayoutDelegate spinnerDelegate = new SideConstraintLayoutDelegate();
spinnerDelegate.putExtraConstraint(
Side.LEFT, new SideConstraint(miniSurveyDrawer, Side.RIGHT, 0));
spinnerDelegate.putExtraConstraint(
Side.BOTTOM, new SideConstraint(surveyDrawer, Side.TOP, 0));
SideConstraintLayoutDelegate hintLabelDelegate = new SideConstraintLayoutDelegate();
hintLabelDelegate.putExtraConstraint(
Side.LEFT, new SideConstraint(taskListDrawer.pinButton(), Side.RIGHT, 0));
hintLabelDelegate.putExtraConstraint(
Side.RIGHT, new SideConstraint(settingsDrawer, Side.LEFT, 0));
hintLabelDelegate.putExtraConstraint(
Side.BOTTOM, new SideConstraint(surveyDrawer, Side.TOP, 0));
layeredPane.add(taskListDrawer.pinButton(), spinnerDelegate);
layeredPane.setLayer(taskListDrawer.pinButton(), JLayeredPane.getLayer(settingsDrawer));
layeredPane.add(hintLabel, hintLabelDelegate);
layeredPane.setLayer(hintLabel, JLayeredPane.getLayer(settingsDrawer));
SideConstraintLayoutDelegate canvasDelegate = new SideConstraintLayoutDelegate();
canvasDelegate.putExtraConstraint(Side.TOP, new SideConstraint(taskListDrawer, Side.BOTTOM, 0));
canvasDelegate.putExtraConstraint(Side.LEFT, new SideConstraint(miniSurveyDrawer, Side.RIGHT, 0));
canvasDelegate.putExtraConstraint(Side.RIGHT, new SideConstraint(settingsDrawer, Side.LEFT, 0));
canvasDelegate.putExtraConstraint(Side.BOTTOM, new SideConstraint(hintLabel, Side.TOP, 0));
layeredPane.add(canvas, canvasDelegate);
surveyDrawer.table().setTransferHandler(new SurveyTableTransferHandler());
surveyDrawer.table().addPropertyChangeListener("model", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
AnnotatingRowSorter<TableModel, Integer> sorter = (AnnotatingRowSorter<TableModel, Integer>) miniSurveyDrawer
.table().getRowSorter();
SurveyTableModel newModel = (SurveyTableModel) evt.getNewValue();
miniSurveyDrawer.table().setRowSorter(null);
miniSurveyDrawer.table().setModel(newModel);
sorter.setModel(newModel);
miniSurveyDrawer.table().setRowSorter(sorter);
}
});
surveyDrawer.table().addSurveyTableListener(new SurveyTableListener() {
@Override
public void surveyNotesClicked(String link, int viewRow) {
openSurveyNotes(link);
}
});
// new javax.swing.Timer(1000, e ->
// System.out.println(layeredPane.getBounds())).start();
surveyDrawer.setBinder(QObjectAttributeBinder.bind(ProjectModel.surveyDrawer, projectModelBinder));
settingsDrawer.setBinder(QObjectAttributeBinder.bind(ProjectModel.settingsDrawer, projectModelBinder));
taskListDrawer.setBinder(QObjectAttributeBinder.bind(ProjectModel.taskListDrawer, projectModelBinder));
new BinderWrapper<Color>() {
@Override
protected void onValueChanged(Color bgColor) {
if (bgColor != null) {
BreakoutMainView.this.bgColor.set(bgColor.getRed() / 255f, bgColor.getGreen() / 255f,
bgColor.getBlue() / 255f, 1f);
autoDrawable.display();
}
}
}.bind(QObjectAttributeBinder.bind(ProjectModel.backgroundColor, projectModelBinder));
new BinderWrapper<Integer>() {
@Override
protected void onValueChanged(Integer newValue) {
if (newValue != null) {
float sensitivity = newValue / 20f;
orbiter.setSensitivity(sensitivity);
navigator.setSensitivity(sensitivity);
}
}
}.bind(QObjectAttributeBinder.bind(RootModel.mouseSensitivity, rootModelBinder));
new BinderWrapper<Integer>() {
@Override
protected void onValueChanged(Integer newValue) {
if (newValue != null) {
float sensitivity = newValue / 5f;
navigator.setWheelFactor(sensitivity);
}
}
}.bind(QObjectAttributeBinder.bind(RootModel.mouseWheelSensitivity, rootModelBinder));
new BinderWrapper<Float>() {
@Override
protected void onValueChanged(final Float newValue) {
if (model3d != null && newValue != null) {
model3d.setAmbientLight(newValue);
autoDrawable.display();
}
}
}.bind(QObjectAttributeBinder.bind(ProjectModel.ambientLight, projectModelBinder));
new BinderWrapper<LinearAxisConversion>() {
@Override
protected void onValueChanged(LinearAxisConversion range) {
if (model3d != null && range != null) {
final float nearDist = (float) range.invert(0.0);
final float farDist = (float) range
.invert(settingsDrawer.getDistColorationAxis().getViewSpan());
final Survey3dModel model3d = BreakoutMainView.this.model3d;
model3d.setNearDist(nearDist);
model3d.setFarDist(farDist);
autoDrawable.display();
}
}
}.bind(QObjectAttributeBinder.bind(ProjectModel.distRange, projectModelBinder));
new BinderWrapper<LinearAxisConversion>() {
@Override
protected void onValueChanged(LinearAxisConversion range) {
if (model3d != null && range != null) {
final float loParam = (float) range.invert(0.0);
final float hiParam = (float) range.invert(settingsDrawer.getParamColorationAxis()
.getViewSpan());
final Survey3dModel model3d = BreakoutMainView.this.model3d;
model3d.setLoParam(loParam);
model3d.setHiParam(hiParam);
autoDrawable.display();
}
}
}.bind(paramRangeBinder);
new BinderWrapper<float[]>() {
@Override
protected void onValueChanged(float[] depthAxis) {
if (depthAxis == null) {
return;
}
final float[] finalDepthAxis = Arrays.copyOf(depthAxis, depthAxis.length);
if (model3d != null && depthAxis != null && depthAxis.length == 3) {
final Survey3dModel model3d = BreakoutMainView.this.model3d;
model3d.setDepthAxis(finalDepthAxis);
autoDrawable.display();
}
}
}.bind(QObjectAttributeBinder.bind(ProjectModel.depthAxis, projectModelBinder));
new BinderWrapper<ColorParam>() {
@Override
protected void onValueChanged(final ColorParam colorParam) {
if (colorParam != null && model3d != null) {
final Survey3dModel model3d = BreakoutMainView.this.model3d;
Task task = new Task() {
@Override
protected void execute() throws Exception {
Subtask rootSubtask = new Subtask(this);
rootSubtask.setTotal(1);
rootSubtask.setIndeterminate(false);
Subtask subtask = rootSubtask.beginSubtask(1);
subtask.setIndeterminate(false);
subtask.setStatus("Recoloring");
model3d.setColorParam(colorParam, subtask);
rootSubtask.setCompleted(1);
subtask.end();
autoDrawable.display();
}
};
task.setTotal(1000);
rebuildTaskService.submit(task);
}
}
}.bind(QObjectAttributeBinder.bind(ProjectModel.colorParam, projectModelBinder));
settingsDrawer.getProjectFileMenuButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Component source = (Component) e.getSource();
Localizer localizer = i18n.forClass(BreakoutMainView.class);
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setLightWeightPopupEnabled(false);
popupMenu.add(new JMenuItem(newProjectAction));
popupMenu.add(new JMenuItem(openProjectAction));
popupMenu.add(new JMenuItem(openSurveyAction));
popupMenu.add(new JSeparator());
popupMenu.add(new JMenuItem(editSurveyScanPathsAction));
popupMenu.add(new JSeparator());
JMenu importMenu = new JMenu();
localizer.setText(importMenu, "importMenu.text");
importMenu.add(new JMenuItem(importProjectArchiveAction));
popupMenu.add(importMenu);
JMenu exportMenu = new JMenu();
localizer.setText(exportMenu, "exportMenu.text");
exportMenu.add(new JMenuItem(exportProjectArchiveAction));
exportMenu.add(new JMenuItem(exportImageAction));
popupMenu.add(exportMenu);
QArrayList<Path> recentProjectFiles = getRootModel().get(RootModel.recentProjectFiles);
if (recentProjectFiles != null && !recentProjectFiles.isEmpty()) {
popupMenu.add(new JSeparator());
for (Path file : recentProjectFiles) {
popupMenu.add(new JMenuItem(new OpenRecentProjectAction(BreakoutMainView.this, file)));
}
}
popupMenu.show(source, source.getWidth(), source.getHeight());
}
});
settingsDrawer.getFitViewToSelectedButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fitViewToSelected();
}
});
settingsDrawer.getFitViewToEverythingButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fitViewToEverything();
}
});
settingsDrawer.getFitParamColorationAxisButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (model3d == null) {
return;
}
final Survey3dModel model3d = BreakoutMainView.this.model3d;
rebuildTaskService.submit(new Task() {
@Override
protected void execute() throws Exception {
setTotal(1000);
Subtask rootSubtask = new Subtask(this);
rootSubtask.setTotal(1);
Subtask calcSubtask = rootSubtask.beginSubtask(1);
float[] range = model3d.calcAutofitParamRange(getDefaultShotsForOperations(), calcSubtask);
rootSubtask.setCompleted(1);
calcSubtask.end();
if (range != null) {
ColorParam colorParam = getProjectModel().get(ProjectModel.colorParam);
if (!colorParam.isLoBright()) {
float swap = range[0];
range[0] = range[1];
range[1] = swap;
}
LinearAxisConversion conversion = new LinearAxisConversion(range[0], 0.0, range[1],
settingsDrawer.getParamColorationAxis().getViewSpan());
paramRangeBinder.set(conversion);
}
}
});
}
});
settingsDrawer.getFlipParamColorationAxisButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlotAxis axis = settingsDrawer.getParamColorationAxis();
LinearAxisConversion conversion = axis.getAxisConversion();
double start = conversion.invert(0.0);
double end = conversion.invert(axis.getViewSpan());
LinearAxisConversion newConversion = new LinearAxisConversion(end, 0.0, start, axis.getViewSpan());
paramRangeBinder.set(newConversion);
}
});
settingsDrawer.getRecalcColorByDistanceButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (model3d == null) {
return;
}
final Survey3dModel model3d = BreakoutMainView.this.model3d;
rebuildTaskService.submit(new Task() {
@Override
protected void execute() throws Exception {
setTotal(1000);
Subtask rootSubtask = new Subtask(this);
rootSubtask.setTotal(1);
Subtask calcSubtask = rootSubtask.beginSubtask(1);
model3d.calcDistFromShots(getDefaultShotsForOperations(), calcSubtask);
autoDrawable.display();
rootSubtask.setCompleted(1);
calcSubtask.end();
}
});
}
});
settingsDrawer.getResetViewButton().addActionListener(e -> {
renderer.getViewSettings().setViewXform(newMat4f());
autoDrawable.display();
});
settingsDrawer.getOrbitToPlanButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (model3d == null) {
return;
}
float[] center = new float[3];
orbiter.getCenter(center);
if (Vecmath.hasNaNsOrInfinites(center)) {
model3d.getCenter(center);
}
float[] v = newMat4f();
renderer.getViewSettings().getViewXform(v);
removeUnprotectedCameraAnimations();
cameraAnimationQueue.add(new SpringViewOrbitAnimation(autoDrawable, renderer.getViewSettings(),
center, 0f, (float) -Math.PI * .5f, .1f, .05f, 30));
cameraAnimationQueue.add(new AnimationViewSaver());
}
});
ViewButtonsPanel viewButtonsPanel = settingsDrawer.getViewButtonsPanel();
for (CameraView view : CameraView.values()) {
JToggleButton button = viewButtonsPanel.getButton(view);
if (button != null) {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setCameraView(view);
}
});
}
}
settingsDrawer.getInferDepthAxisTiltButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (model3d == null) {
return;
}
List<float[]> vectors = new ArrayList<>();
for (Shot3d shot3d : getDefaultShotsForOperations()) {
SurveyTableModel tableModel = surveyDrawer.table().getModel();
Shot shot = tableModel.shotAtRow(tableModel.rowOfShot(shot3d.getNumber()));
float[] vector = new float[3];
Vecmath.sub3(shot.to.position, shot.from.position, vector);
if (!Vecmath.hasNaNsOrInfinites(vector)) {
vectors.add(vector);
}
}
float[] normal = Fitting3d.planeNormalLeastSquares2f(vectors.stream());
Vecmath.normalize3(normal);
if (normal[1] > 0) {
Vecmath.negate3(normal);
}
getProjectModel().set(ProjectModel.depthAxis, normal);
}
});
settingsDrawer.getResetDepthAxisTiltButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getProjectModel().set(ProjectModel.depthAxis, new float[] { 0f, -1f, 0f });
}
});
settingsDrawer.getCameraToDepthAxisTiltButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
float[] axis = new float[3];
Vecmath.negate3(renderer.getViewState().inverseViewXform(), 8, axis, 0);
getProjectModel().set(ProjectModel.depthAxis, axis);
}
});
((JTextField) surveyDrawer.filterField().textComponent).addActionListener(new FitToFilteredHandler(
surveyDrawer.table()));
((JTextField) miniSurveyDrawer.filterField().textComponent)
.addActionListener(new FitToFilteredHandler(miniSurveyDrawer.table()));
Binder<Boolean> showDataInSurveyTableBinder = new QObjectAttributeBinder<>(RootModel.showDataInSurveyTable)
.bind(rootModelBinder);
new ButtonSelectedBinder(surveyDrawer.showDataButton).bind(showDataInSurveyTableBinder);
new BinderWrapper<Boolean>() {
@Override
protected void onValueChanged(Boolean showData) {
if (showData == null) {
showData = false;
}
surveyDrawer.table().setShowData(showData);
}
}.bind(showDataInSurveyTableBinder);
new BinderWrapper<Integer>() {
@Override
protected void onValueChanged(Integer desiredNumSamples) {
if (desiredNumSamples != null) {
renderer.setDesiredNumSamples(desiredNumSamples);
autoDrawable.display();
}
}
}.bind(QObjectAttributeBinder.bind(RootModel.desiredNumSamples, rootModelBinder));
autoDrawable.invoke(false, drawable -> {
GL2ES2 gl = (GL2ES2) drawable.getGL();
int[] temp = new int[1];
((GL3) gl).glGetIntegerv(GL.GL_MAX_SAMPLES, temp, 0);
SwingUtilities.invokeLater(() -> settingsDrawer.setMaxNumSamples(temp[0]));
return true;
});
File rootFile;
String rootFilePath = System.getProperty("rootFile");
if (rootFilePath == null) {
File rootDir = new File(".breakout");
rootFile = new File(rootDir, "settings.yaml");
if (!rootFile.exists()) {
rootDir.mkdir();
try {
ClassLoader cl = getClass().getClassLoader();
for (String file : Arrays.asList("demo.bop", "demo-survey.txt", "settings.yaml")) {
Path path = new File(rootDir, file).toPath();
URL resource = cl.getResource("demo/" + file);
InputStream in = resource.openStream();
Files.copy(in, path);
}
} catch (Exception ex) {
try {
FileUtils.deleteDirectory(rootDir);
} catch (IOException e1) {
e1.printStackTrace();
}
ex.printStackTrace();
}
}
} else {
rootFile = new File(rootFilePath);
}
rootDirectory = rootFile.toPath().getParent();
rootPersister = new TaskServiceFilePersister<QObject<RootModel>>(ioTaskService, "Saving settings...",
QObjectBimappers.defaultBimapper(RootModel.defaultMapper), rootFile);
QObject<RootModel> rootModel = null;
try {
rootModel = rootPersister.load();
} catch (Exception ex) {
}
if (rootModel == null) {
rootModel = RootModel.instance.newObject();
}
if (rootModel.get(RootModel.currentProjectFile) == null) {
rootModel.set(RootModel.currentProjectFile, Paths.get("defaultProject.bop"));
rootModel.set(RootModel.desiredNumSamples, 2);
}
setRootModel(rootModel);
Path projectFile = rootDirectory.resolve(rootModel.get(RootModel.currentProjectFile))
.normalize();
openProject(projectFile);
try (FileInputStream updateIn = new FileInputStream("update.properties")) {
Properties updateProps = new Properties();
updateProps.load(updateIn);
updateIn.close();
UpdateStatusPanelController updateStatusPanelController = new UpdateStatusPanelController(
settingsDrawer.getUpdateStatusPanel(),
settingsDrawer.getLoadedVersion(),
new URL(updateProps.get("latestVersionInfoUrl").toString()),
new File(updateProps.get("updateDir").toString()));
updateStatusPanelController.checkForUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
public void autoProfileMode() {
Set<Shot3d> shots = getDefaultShotsForOperations();
List<float[]> forFitting = new ArrayList<>();
SurveyTableModel tableModel = surveyDrawer.table().getModel();
for (Shot3d shot : shots) {
Shot origShot = tableModel.shotAtRow(tableModel.rowOfShot(shot.getNumber()));
forFitting
.add(new float[] { (float) origShot.from.position[0], (float) origShot.from.position[2] });
forFitting.add(new float[] { (float) origShot.to.position[0], (float) origShot.to.position[2] });
}
float[] fit = Fitting.linearLeastSquares2f(forFitting);
if (Vecmath.hasNaNsOrInfinites(fit)) {
return;
}
double azimuth = Math.atan2(1, -fit[0]);
float[] right = new float[] { (float) Math.sin(azimuth), 0, (float) -Math.cos(azimuth) };
float[] forward = new float[] { (float) Math.sin(azimuth - Math.PI * 0.5), 0,
(float) -Math.cos(azimuth - Math.PI * 0.5) };
if (Vecmath.dot3(renderer.getViewState().inverseViewXform(), 8, forward, 0) > 0) {
Vecmath.negate3(right);
Vecmath.negate3(forward);
}
changeView(forward, right, true, shots);
}
private void changeView(float[] forward, float[] right, boolean ortho, Set<Shot3d> shotsToFit) {
if (Vecmath.hasNaNsOrInfinites(forward) || Vecmath.hasNaNsOrInfinites(right)) {
throw new IllegalArgumentException("forward and right must not contain NaN or infinite values");
}
mouseLooper.removeMouseAdapter(mouseAdapterChain);
float[] up = new float[3];
Vecmath.cross(right, forward, up);
Projection newProjCalculator;
float[] vi = renderer.getViewState().inverseViewXform();
float[] endLocation = { vi[12], vi[13], vi[14] };
Animation finisher;
if (ortho) {
AutoClipOrthoProjection orthoCalculator = new AutoClipOrthoProjection();
newProjCalculator = orthoCalculator;
orbiter.getCenter(orthoCalculator.center);
if (model3d != null) {
orthoCalculator.radius = Rectmath.radius3(model3d.getTree().getRoot().mbr());
float[] orthoBounds = model3d.getOrthoBounds(shotsToFit, right, up, forward);
Rectmath.scaleFromCenter3(orthoBounds, 1 / 0.9f, 1 / 0.9f, 1f, orthoBounds);
float[] endOrthoLocation = new float[3];
Rectmath.center(orthoBounds, endOrthoLocation);
Vecmath.combine(endLocation, endOrthoLocation, right, up, forward);
float dist = Vecmath.distance3(vi, 12, endLocation, 0);
endOrthoLocation[2] -= dist;
Vecmath.combine(endLocation, endOrthoLocation, right, up, forward);
Rectmath.center(orthoBounds, endOrthoLocation);
endOrthoLocation[2] = orthoBounds[2];
Vecmath.combine(orthoCalculator.nearClipPoint, endOrthoLocation, right, up, forward);
endOrthoLocation[2] = orthoBounds[5];
Vecmath.combine(orthoCalculator.farClipPoint, endOrthoLocation, right, up, forward);
orthoCalculator.hSpan = orthoBounds[3] - orthoBounds[0];
orthoCalculator.vSpan = orthoBounds[4] - orthoBounds[1];
}
finisher = l -> {
orthoCalculator.useNearClipPoint = orthoCalculator.useFarClipPoint = true;
renderer.getViewSettings().setProjection(orthoCalculator);
saveProjection();
installOrthoMouseAdapters();
autoDrawable.display();
return 0;
};
} else {
newProjCalculator = perspCalculator;
if (model3d != null) {
FittingFrustum frustum = new FittingFrustum();
float[] projXform = newMat4f();
perspCalculator.calculate(renderer.getViewState(), projXform);
PickXform pickXform = new PickXform();
pickXform.calculate(projXform, renderer.getViewState().viewXform());
frustum.init(pickXform, 0.9f);
for (Shot3d shot : shotsToFit) {
for (float[] coord : shot.coordIterable(endLocation)) {
frustum.addPoint(coord);
}
}
frustum.calculateOrigin(endLocation);
}
finisher = l -> {
renderer.getViewSettings().setProjection(perspCalculator);
saveProjection();
installPerspectiveMouseAdapters();
autoDrawable.display();
return 0;
};
}
GeneralViewXformOrbitAnimation viewAnimation = new GeneralViewXformOrbitAnimation(autoDrawable,
renderer.getViewSettings(), 1750, 30);
float[] viewXform = newMat4f();
viewAnimation.setUpWithEndLocation(renderer.getViewState().viewXform(), endLocation, forward, right);
Projection currentProjCalculator = renderer.getViewSettings().getProjection();
InterpolationProjection calc = new InterpolationProjection(renderer.getViewSettings().getProjection(),
newProjCalculator, 0f);
FloatUnaryOperator viewReparam = f -> 1 - (1 - f) * (1 - f);
FloatUnaryOperator projReparam;
if (currentProjCalculator instanceof AutoClipOrthoProjection) {
AutoClipOrthoProjection currentOrthoCalc = (AutoClipOrthoProjection) currentProjCalculator;
currentOrthoCalc.useNearClipPoint = currentOrthoCalc.useFarClipPoint = false;
if (ortho) {
projReparam = viewReparam;
} else {
float b = 10f;
float a = 1 / b;
float ra = 1 / a / a;
float rb = 1 / b / b;
projReparam = f -> {
float ff = b + f * (a - b);
float rf = 1 / ff / ff;
return viewReparam.applyAsFloat((rf - rb) / (ra - rb));
};
}
} else {
if (ortho) {
float b = 10f;
float a = 1 / b;
float ra = 1 / a / a;
float rb = 1 / b / b;
projReparam = f -> {
float ff = a + viewReparam.applyAsFloat(f) * (b - a);
float rf = 1 / ff / ff;
return (rf - ra) / (rb - ra);
};
} else {
projReparam = viewReparam;
}
}
removeUnprotectedCameraAnimations();
cameraAnimationQueue.add(new ProjXformAnimation(autoDrawable, renderer.getViewSettings(), 1750, false,
f -> {
calc.f = projReparam.applyAsFloat(f);
return calc;
}).also(new ViewXformAnimation(autoDrawable, renderer.getViewSettings(), 1750, true, f -> {
viewAnimation.calcViewXform(viewReparam.applyAsFloat(f), viewXform);
return viewXform;
})));
finisher = finisher.also(new AnimationViewSaver());
protectedAnimations.put(finisher, null);
cameraAnimationQueue.add(finisher);
}
protected void changeView(Set<Shot3d> shotsToFit) {
float[] forward = new float[3];
float[] right = new float[3];
float[] vi = renderer.getViewState().inverseViewXform();
Vecmath.negate3(vi, 8, forward, 0);
Vecmath.getColumn3(vi, 0, right);
changeView(forward, right, getProjectModel().get(ProjectModel.cameraView) != CameraView.PERSPECTIVE,
shotsToFit);
}
public void eastFacingProfileMode() {
changeView(new float[] { 1, 0, 0 }, new float[] { 0, 0, 1 }, true, getDefaultShotsForOperations());
}
public void exportProjectArchive(File newProjectFile) {
ioTaskService.submit(new ExportProjectArchiveTask(newProjectFile));
}
protected void fitViewToEverything() {
if (model3d == null) {
return;
}
changeView(CollectionUtils.toHashSet(getShotsFromTable().map(shot -> model3d.getShot(shot.number))));
}
protected void fitViewToSelected() {
if (model3d == null) {
return;
}
changeView(CollectionUtils.toHashSet(getSelectedShotsFromTable()
.map(shot -> model3d.getShot(shot.number))));
}
protected void flyToFiltered(final AnnotatingJTable table) {
if (model3d == null) {
return;
}
removeUnprotectedCameraAnimations();
if (getProjectModel().get(ProjectModel.cameraView) == CameraView.PERSPECTIVE) {
float[] center = new float[3];
orbiter.getCenter(center);
if (Vecmath.hasNaNsOrInfinites(center)) {
model3d.getCenter(center);
}
cameraAnimationQueue.add(new SpringViewOrbitAnimation(autoDrawable, renderer.getViewSettings(),
center,
0f, (float) -Math.PI / 4, .1f, .05f, 30));
cameraAnimationQueue.add(new AnimationViewSaver());
}
cameraAnimationQueue.add(new Animation() {
@Override
public long animate(long animTime) {
table.getModelSelectionModel().clearSelection();
table.selectAll();
fitViewToSelected();
if (getProjectModel().get(ProjectModel.cameraView) != CameraView.PERSPECTIVE) {
return 0;
}
rebuildTaskService.submit(task -> SwingUtilities.invokeLater(() -> {
float[] center = new float[3];
orbiter.getCenter(center);
if (Vecmath.hasNaNsOrInfinites(center)) {
model3d.getCenter(center);
}
cameraAnimationQueue.add(new RandomViewOrbitAnimation(autoDrawable, renderer.getViewSettings(),
center, 0.0005f, (float) -Math.PI / 4, (float) -Math.PI / 9, 30, 60000));
}));
return 0;
}
});
}
public Path getAbsoluteProjectFilePath(Path relativeProjectFilePath) {
return rootDirectory.toAbsolutePath().resolve(relativeProjectFilePath);
}
public GLAutoDrawable getAutoDrawable() {
return autoDrawable;
}
public Component getCanvas() {
return canvas;
}
protected Set<Shot3d> getDefaultShotsForOperations() {
if (model3d == null) {
return Collections.emptySet();
}
Set<Shot3d> result = new HashSet<Shot3d>();
getSelectedShotsFromTable().forEach(shot -> result.add(model3d.getShot(shot.number)));
if (result.size() < 2) {
result.clear();
PlanarHull3f hull = new PlanarHull3f();
renderer.getViewState().pickXform().exportViewVolume(hull, canvas.getWidth(), canvas.getHeight());
model3d.getShotsIn(hull, result);
if (result.isEmpty()) {
result.addAll(model3d.getShots());
}
}
return result;
}
public I18n getI18n() {
return i18n;
}
public JPanel getMainPanel() {
return mainPanel;
}
public NewProjectAction getNewProjectAction() {
return newProjectAction;
}
public OpenProjectAction getOpenProjectAction() {
return openProjectAction;
}
public QObject<ProjectModel> getProjectModel() {
return projectModelBinder.get();
}
public Binder<QObject<ProjectModel>> getProjectModelBinder() {
return projectModelBinder;
}
public Path getRootDirectory() {
return rootDirectory;
}
public File getRootFile() {
return rootFile;
}
public QObject<RootModel> getRootModel() {
return rootModelBinder.get();
}
public Binder<QObject<RootModel>> getRootModelBinder() {
return rootModelBinder;
}
public JoglScene getScene() {
return scene;
}
protected Stream<Shot> getSelectedShotsFromTable() {
SurveyTableModel model = surveyDrawer.table().getModel();
ListSelectionModel selModel = surveyDrawer.table().getModelSelectionModel();
return IntStream.range(0, model.getRowCount()).filter(i -> selModel.isSelectedIndex(i))
.mapToObj(i -> model.shotAtRow(i)).filter(o -> o != null);
}
protected Stream<Shot> getShotsFromTable() {
SurveyTableModel model = surveyDrawer.table().getModel();
return IntStream.range(0, model.getRowCount()).mapToObj(i -> model.shotAtRow(i))
.filter(o -> o != null);
}
public TaskListDrawer getTaskListDrawer() {
return taskListDrawer;
}
public JoglViewSettings getViewSettings() {
return renderer.getViewSettings();
}
public void importProjectArchive(File newProjectFile) {
ioTaskService.submit(new ImportProjectArchiveTask(newProjectFile));
}
private void installOrthoMouseAdapters() {
if (mouseAdapterChain != null) {
mouseLooper.removeMouseAdapter(mouseAdapterChain);
}
mouseAdapterChain = new MouseAdapterChain();
mouseAdapterChain.addMouseAdapter(orthoNavigator);
mouseAdapterChain.addMouseAdapter(pickHandler);
mouseAdapterChain.addMouseAdapter(autoshowController);
mouseAdapterChain.addMouseAdapter(otherMouseHandler);
mouseLooper.addMouseAdapter(mouseAdapterChain);
}
private void installPerspectiveMouseAdapters() {
if (mouseAdapterChain != null) {
mouseLooper.removeMouseAdapter(mouseAdapterChain);
}
mouseAdapterChain = new MouseAdapterChain();
mouseAdapterChain.addMouseAdapter(navigator);
mouseAdapterChain.addMouseAdapter(orbiter);
mouseAdapterChain.addMouseAdapter(pickHandler);
mouseAdapterChain.addMouseAdapter(autoshowController);
mouseAdapterChain.addMouseAdapter(otherMouseHandler);
mouseLooper.addMouseAdapter(mouseAdapterChain);
};
public void northFacingProfileMode() {
changeView(new float[] { 0, 0, -1 }, new float[] { 1, 0, 0 }, true, getDefaultShotsForOperations());
}
/**
* Opens the given project file.
*
* @param newProjectFile
* the path to the project file to open; must be absolute or
* relative to the working directory.
*/
public void openProject(Path newProjectFile) {
ioTaskService.submit(new OpenProjectTask(newProjectFile));
}
public void openSurveyFile(Path newSurveyFile) {
ioTaskService.submit(new OpenSurveyTask(newSurveyFile));
}
private void openSurveyNotes(File file) {
if (file == null || !file.exists()) {
JXHyperlink searchDirsLink = new JXHyperlink(editSurveyScanPathsAction);
searchDirsLink.setText("search directories");
JPanel message = new JPanel(new FlowLayout());
message.add(new JLabel("Couldn't find the file '" + file + "' in any of your "));
message.add(searchDirsLink);
message.add(new JLabel("(note: Breakout only searches " + SCANNED_NOTES_SEARCH_DEPTH +
" levels deep)."));
JOptionPane.showMessageDialog(mainPanel, message, "Can't find file", JOptionPane.ERROR_MESSAGE);
return;
}
try {
Desktop.getDesktop().open(file);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(mainPanel, "Failed to open file '" + file + "': " + e,
"Error", JOptionPane.ERROR_MESSAGE);
}
}
private void openSurveyNotes(String link) {
URI uri = null;
try {
uri = new URL(link).toURI();
Desktop.getDesktop().browse(uri);
return;
} catch (MalformedURLException e) {
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(mainPanel, "Failed to open URL " + uri + ": " + e,
"Error", JOptionPane.ERROR_MESSAGE);
}
try {
File file = new File(link);
if (!file.isAbsolute()) {
QArrayList<File> dirs = getProjectModel().get(ProjectModel.surveyScanPaths);
if (dirs == null || dirs.isEmpty()) {
int option = JOptionPane.showConfirmDialog(mainPanel,
"There are no directories configured to search for the file: " + link
+ ". Would you like to configure them now?",
"Can't find file",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (option == JOptionPane.YES_OPTION) {
editSurveyScanPathsAction.actionPerformed(
new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
dirs = getProjectModel().get(ProjectModel.surveyScanPaths);
}
}
if (dirs != null) {
final QArrayList<File> finalDirs = dirs;
ioTaskService.submit(new SelfReportingTask(mainPanel) {
@Override
public boolean isCancelable() {
return true;
}
@Override
protected void duringDialog() throws Exception {
setStatus("Searching for file: " + link + "...");
setIndeterminate(true);
showDialogLater();
for (File dir : finalDirs) {
Optional<Path> foundFile = Files
.find(dir.toPath(), SCANNED_NOTES_SEARCH_DEPTH,
(Path path, BasicFileAttributes attrs) -> {
return path.toString().endsWith(link);
} , FileVisitOption.FOLLOW_LINKS).findFirst();
if (foundFile.isPresent() && !isCanceled() && !isCanceling()) {
SwingUtilities.invokeLater(() -> openSurveyNotes(foundFile.get().toFile()));
return;
}
}
if (!isCanceled() && !isCanceling()) {
SwingUtilities.invokeLater(() -> openSurveyNotes(file));
}
}
});
return;
}
openSurveyNotes(file);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
public void perspectiveMode() {
float[] forward = new float[3];
float[] right = new float[3];
Vecmath.negate3(renderer.getViewState().inverseViewXform(), 8, forward, 0);
Vecmath.getColumn3(renderer.getViewState().inverseViewXform(), 0, right);
changeView(forward, right, false, getDefaultShotsForOperations());
}
private Shot3dPickResult pick(Survey3dModel model3d, MouseEvent e, Shot3dPickContext spc) {
PlanarHull3f hull = new PlanarHull3f();
float[] origin = new float[3];
float[] direction = new float[3];
renderer
.getViewState()
.pickXform()
.xform(e.getX(), e.getComponent().getHeight() - e.getY(), e.getComponent().getWidth(),
e.getComponent().getHeight(), origin, direction);
renderer.getViewState().pickXform().exportViewVolume(hull, e, 10);
if (model3d != null) {
List<PickResult<Shot3d>> pickResults = new ArrayList<PickResult<Shot3d>>();
// model3d.pickShots( origin , direction , ( float ) Math.PI / 64 ,
// spc , pickResults );
model3d.pickShots(hull, spc, pickResults);
PickResult<Shot3d> best = null;
for (PickResult<Shot3d> result : pickResults) {
if (best == null || result.lateralDistance * best.distance < best.lateralDistance * result.distance
|| result.lateralDistance == 0 && best.lateralDistance == 0
&& result.distance < best.distance) {
best = result;
}
}
return (Shot3dPickResult) best;
}
return null;
}
// class SurveyFilterFactory implements RowFilterFactory<String, TableModel,
// Integer>
// {
// @Override
// public RowFilter<TableModel, Integer> createFilter( String input )
// {
// switch( getProjectModel( ).get( ProjectModel.filterType ) )
// {
// case ALPHA_DESIGNATION:
// return new SurveyDesignationFilter( input );
// case REGEXP:
// return new SurveyRegexFilter( input );
// case SURVEYORS:
// return new SurveyorFilter( input );
// case DESCRIPTION:
// return new DescriptionFilter( input );
// default:
// return null;
// }
// }
// }
public void planMode() {
changeView(new float[] { 0, -1, 0 }, new float[] { 1, 0, 0 }, true, getDefaultShotsForOperations());
}
protected void removeUnprotectedCameraAnimations() {
cameraAnimationQueue.removeAll(anim -> !protectedAnimations.containsKey(anim));
}
private void replaceNulls(QObject<ProjectModel> projectModel, Path projectFile) {
if (projectModel.get(ProjectModel.cameraView) == null) {
projectModel.set(ProjectModel.cameraView, CameraView.PERSPECTIVE);
}
if (projectModel.get(ProjectModel.backgroundColor) == null) {
projectModel.set(ProjectModel.backgroundColor, Color.black);
}
if (projectModel.get(ProjectModel.distRange) == null) {
projectModel.set(ProjectModel.distRange, new LinearAxisConversion(0, 0, 20000, 200));
}
if (projectModel.get(ProjectModel.viewXform) == null) {
projectModel.set(ProjectModel.viewXform, Vecmath.newMat4f());
}
if (projectModel.get(ProjectModel.colorParam) == null) {
projectModel.set(ProjectModel.colorParam, ColorParam.DEPTH);
}
if (projectModel.get(ProjectModel.paramRanges) == null) {
projectModel
.set(ProjectModel.paramRanges, QLinkedHashMap.<ColorParam, LinearAxisConversion> newInstance());
}
QMap<ColorParam, LinearAxisConversion, ?> paramRanges = projectModel.get(ProjectModel.paramRanges);
for (ColorParam colorParam : ColorParam.values()) {
if (!paramRanges.containsKey(colorParam)) {
paramRanges.put(colorParam, new LinearAxisConversion());
}
}
if (projectModel.get(ProjectModel.highlightRange) == null) {
projectModel.set(ProjectModel.highlightRange, new LinearAxisConversion(0, 0, 1000, 200));
}
if (projectModel.get(ProjectModel.surveyDrawer) == null) {
projectModel.set(ProjectModel.surveyDrawer, DrawerModel.instance.newObject());
}
if (projectModel.get(ProjectModel.settingsDrawer) == null) {
projectModel.set(ProjectModel.settingsDrawer, DrawerModel.instance.newObject());
}
if (projectModel.get(ProjectModel.miniSurveyDrawer) == null) {
projectModel.set(ProjectModel.miniSurveyDrawer, DrawerModel.instance.newObject());
}
if (projectModel.get(ProjectModel.taskListDrawer) == null) {
projectModel.set(ProjectModel.taskListDrawer, DrawerModel.instance.newObject());
}
if (projectModel.get(ProjectModel.surveyFile) == null) {
Path surveyFile = projectFile.relativize(
NewProjectAction.pickDefaultSurveyFile(projectFile.toFile()).toPath());
projectModel.set(ProjectModel.surveyFile, surveyFile);
}
}
private void saveProjection() {
getProjectModel().set(ProjectModel.projCalculator, renderer.getViewSettings().getProjection());
}
private void saveViewXform() {
float[] viewXform = Vecmath.newMat4f();
renderer.getViewSettings().getViewXform(viewXform);
getProjectModel().set(ProjectModel.viewXform, viewXform);
}
public void setCameraView(CameraView view) {
if (view != currentView) {
currentView = view;
switch (view) {
case PERSPECTIVE:
perspectiveMode();
break;
case PLAN:
planMode();
break;
case NORTH_FACING_PROFILE:
northFacingProfileMode();
break;
case SOUTH_FACING_PROFILE:
southFacingProfileMode();
break;
case EAST_FACING_PROFILE:
eastFacingProfileMode();
break;
case WEST_FACING_PROFILE:
westFacingProfileMode();
break;
case AUTO_PROFILE:
autoProfileMode();
break;
}
}
}
public void setNewProjectAction(NewProjectAction newProjectAction) {
this.newProjectAction = newProjectAction;
}
public void setOpenProjectAction(OpenProjectAction openProjectAction) {
this.openProjectAction = openProjectAction;
}
public void setRootModel(QObject<RootModel> rootModel) {
QObject<RootModel> currentModel = getRootModel();
if (currentModel != rootModel) {
if (currentModel != null) {
currentModel.changeSupport().removePropertyChangeListener(rootPersister);
}
rootModelBinder.set(rootModel);
if (rootModel != null) {
rootModel.changeSupport().addPropertyChangeListener(rootPersister);
}
}
}
public void southFacingProfileMode() {
changeView(new float[] { 0, 0, 1 }, new float[] { -1, 0, 0 }, true, getDefaultShotsForOperations());
}
public void westFacingProfileMode() {
changeView(new float[] { -1, 0, 0 }, new float[] { 0, 0, -1 }, true, getDefaultShotsForOperations());
}
}