package com.momega.spacesimulator.renderer;
import com.momega.spacesimulator.builder.ModelBuilder;
import com.momega.spacesimulator.context.Application;
import com.momega.spacesimulator.context.ModelHolder;
import com.momega.spacesimulator.model.*;
import com.momega.spacesimulator.opengl.DefaultWindow;
import com.momega.spacesimulator.opengl.GLUtils;
import com.momega.spacesimulator.service.ModelBuilderFactory;
import com.momega.spacesimulator.service.ModelSerializer;
import com.momega.spacesimulator.service.ModelService;
import com.momega.spacesimulator.service.UserPointService;
import com.momega.spacesimulator.swing.PositionProvidersModel;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.List;
/**
* The renderer model
* Created by martin on 6/20/14.
*/
public class RendererModel {
public static final String MODEL_FILE = "modelFile";
public static final String WARP_FACTOR = "warpFactor";
public static final String FPS = "fps";
public static final String MODEL_READY = "modelReady";
private static final Logger logger = LoggerFactory.getLogger(RendererModel.class);
public final static int MIN_TARGET_SIZE = 9;
public static final double FOVY = 45.0;
private static RendererModel instance = new RendererModel();
private final Map<PositionProvider, ViewCoordinates> viewData = new HashMap<>();
private final java.util.List<ModelChangeListener> modelChangeListeners = new ArrayList<>();
private PositionProvidersModel movingObjectsModel;
private final UserPointService userPointService;
private final ModelService modelService;
private boolean spacecraftVisible;
private boolean celestialVisible;
private boolean pointsVisible;
private File modelFile;
private final JFileChooser fileChooser;
private UserOrbitalPoint selectedUserOrbitalPoint;
private PropertyChangeSupport propertyChangeSupport;
private double warpFactor = 1;
private Spacecraft newSpacecraft = null;
private Spacecraft deleteSpacecraft = null;
private boolean reloadRenderersRequired = false;
private boolean takeScreenshotRequired = false;
private Point newUserPointPosition = null;
private Point dragUserPointPosition = null;
private File saveFileRequested = null;
private File loadFileRequested = null;
private boolean quitRequested = false;
private boolean closeRequested = false;
private boolean modelReady = false;
private final ModelSerializer modelSerializer;
private float fps = 0.0f;
private ModelBuilder modelBuilderRequested;
private RendererModel() {
super();
spacecraftVisible = true;
celestialVisible = true;
pointsVisible = true;
userPointService = Application.getInstance().getService(UserPointService.class);
modelService = Application.getInstance().getService(ModelService.class);
movingObjectsModel = new PositionProvidersModel(selectPositionProviders());
if (ModelHolder.getModel()!=null) {
movingObjectsModel.setSelectedItem(ModelHolder.getModel().getCamera().getTargetObject());
}
fileChooser = new JFileChooser();
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("Space Simulator Data (.json)", "json"));
fileChooser.setFileFilter(fileChooser.getChoosableFileFilters()[1]);
modelSerializer = Application.getInstance().getService(ModelSerializer.class);
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
public static RendererModel getInstance() {
return instance;
}
public void addModelChangeListener(ModelChangeListener listener) {
modelChangeListeners.add(listener);
}
public void removeModelChangeListener(ModelChangeListener listener) {
modelChangeListeners.remove(listener);
}
public void updateViewData(GLAutoDrawable drawable) {
Model model = ModelHolder.getModel();
Camera camera = model.getCamera();
Timestamp time = model.getTime();
for(PositionProvider positionProvider : modelService.findAllPositionProviders(model, time)) {
addViewCoordinates(drawable, positionProvider, camera);
}
}
protected void addViewCoordinates(GLAutoDrawable drawable, PositionProvider positionProvider, Camera camera) {
if (positionProvider == null) {
return;
}
if (positionProvider.getPosition() == null) {
return;
}
ViewCoordinates viewCoordinates = new ViewCoordinates();
GL2 gl = drawable.getGL().getGL2();
ScreenCoordinates screenCoordinates = GLUtils.getProjectionCoordinates(gl, positionProvider.getPosition(), camera);
viewCoordinates.setVisible(screenCoordinates != null);
viewCoordinates.setScreenCoordinates(screenCoordinates);
double radiusAngle;
if (positionProvider instanceof RotatingObject) {
RotatingObject ro = (RotatingObject) positionProvider;
Vector3d distance = positionProvider.getPosition().subtract(camera.getPosition());
radiusAngle = Math.toDegrees(Math.atan2(ro.getRadius(), distance.length()));
double radius = (int)((radiusAngle/ FOVY) * drawable.getSurfaceHeight());
viewCoordinates.setRadius(radius);
} else {
viewCoordinates.setRadius(MIN_TARGET_SIZE);
}
if (positionProvider instanceof AbstractOrbitalPoint) {
AbstractOrbitalPoint point = (AbstractOrbitalPoint) positionProvider;
viewCoordinates.setVisible(viewCoordinates.isVisible() && point.isVisible());
}
viewCoordinates.setObject(positionProvider);
addViewCoordinates(viewCoordinates);
}
public void modelChanged() {
Model model = ModelHolder.getModel();
ModelChangeEvent event = new ModelChangeEvent(model);
fireModelEvent(event);
}
public void fireModelEvent(ModelChangeEvent event) {
for(ModelChangeListener listener : modelChangeListeners) {
listener.modelChanged(event);
}
}
/**
* Adds the view coordinates the renderer model for the given dynamic point
* @param viewCoordinates the view coordinates
*/
public void addViewCoordinates(ViewCoordinates viewCoordinates) {
viewData.put(viewCoordinates.getObject(), viewCoordinates);
}
public ViewCoordinates findViewCoordinates(PositionProvider positionProvider) {
return viewData.get(positionProvider);
}
public List<PositionProvider> getAll() {
return new ArrayList<>(viewData.keySet());
}
public boolean isVisibleOnScreen(PositionProvider positionProvider) {
if (positionProvider == null) {
return false;
}
ViewCoordinates viewCoordinates = findViewCoordinates(positionProvider);
return (viewCoordinates != null && viewCoordinates.isVisible());
}
public List<ViewCoordinates> findViewCoordinates(java.awt.Point point) {
double x = point.getX();
double y = point.getY();
List<ViewCoordinates> result = new ArrayList<>();
for (Map.Entry<PositionProvider, ViewCoordinates> entry : viewData.entrySet()) {
ViewCoordinates viewCoordinates = entry.getValue();
if (viewCoordinates.isVisible()) {
if ((Math.abs(x - (int) viewCoordinates.getPoint().getX()) < MIN_TARGET_SIZE) && (Math.abs(y - (int) viewCoordinates.getPoint().getY()) < MIN_TARGET_SIZE)) {
result.add(viewCoordinates);
}
}
}
return result;
}
public PositionProvider findByName(String name) {
if (name == null) {
return null;
}
for (Map.Entry<PositionProvider, ViewCoordinates> entry : viewData.entrySet()) {
ViewCoordinates viewCoordinates = entry.getValue();
if (name.equals(viewCoordinates.getObject().getName())) {
return viewCoordinates.getObject();
}
}
return null;
}
public String[] findVisibleObjects() {
List<String> list = new ArrayList<>();
for (Map.Entry<PositionProvider, ViewCoordinates> entry : viewData.entrySet()) {
ViewCoordinates viewCoordinates = entry.getValue();
if (viewCoordinates.isVisible()) {
list.add(viewCoordinates.getObject().getName());
}
}
Collections.sort(list);
return list.toArray(new String[list.size()]);
}
public void selectItem(ViewCoordinates viewCoordinates) {
Model model = ModelHolder.getModel();
Camera camera = model.getCamera();
camera.setTargetObject(viewCoordinates.getObject());
if (viewCoordinates.getObject() instanceof RotatingObject) {
RotatingObject ro = (RotatingObject) viewCoordinates.getObject();
if (camera.getDistance() < ro.getRadius()) {
camera.setDistance(ro.getRadius() * 10);
}
}
setSelectedItem(viewCoordinates.getObject());
logger.info("selected dynamical point changed to {}", viewCoordinates.getObject().getName());
}
public void setSelectedItem(PositionProvider positionProvider) {
movingObjectsModel.setSelectedItem(positionProvider);
}
public void replaceMovingObjectsModel() {
movingObjectsModel.setSelectedItem(null);
movingObjectsModel.replaceElements(selectPositionProviders());
if (isModelReady()) {
movingObjectsModel.setSelectedItem(ModelHolder.getModel().getCamera().getTargetObject());
}
}
public PositionProvider getSelectedItem() {
return (PositionProvider) movingObjectsModel.getSelectedItem();
}
public void clearViewCoordinates() {
viewData.clear();
}
public boolean isCelestialVisible() {
return celestialVisible;
}
public boolean isPointsVisible() {
return pointsVisible;
}
public boolean isSpacecraftVisible() {
return spacecraftVisible;
}
public void setPointsVisible(boolean pointsVisible) {
this.pointsVisible = pointsVisible;
}
public void setSpacecraftVisible(boolean spacecraftVisible) {
this.spacecraftVisible = spacecraftVisible;
}
public void setCelestialVisible(boolean celestialVisible) {
this.celestialVisible = celestialVisible;
}
public PositionProvidersModel getMovingObjectsModel() {
return movingObjectsModel;
}
public List<PositionProvider> selectPositionProviders() {
List<PositionProvider> result = new ArrayList<>();
Model model = ModelHolder.getModel();
if (model==null) {
return result;
}
Timestamp time = model.getTime();
List<PositionProvider> list = modelService.findAllPositionProviders(model, time);
for(PositionProvider positionProvider : list) {
if (isPointsVisible() && positionProvider instanceof AbstractOrbitalPoint) {
AbstractOrbitalPoint orbitalPoint = (AbstractOrbitalPoint) positionProvider;
if (orbitalPoint.isVisible()) {
result.add(positionProvider);
}
}
if (isSpacecraftVisible() && positionProvider instanceof Spacecraft) {
result.add(positionProvider);
}
if (isCelestialVisible() && positionProvider instanceof CelestialBody) {
result.add(positionProvider);
}
}
result = modelService.sortNamedObjects(result);
return result;
}
public void createUserPoint(GLAutoDrawable drawable, Point point) {
GL2 gl = drawable.getGL().getGL2();
Map<Integer, ScreenCoordinates> screenCoordinatesMap = GLUtils.getStencilPosition(gl, point, RendererModel.MIN_TARGET_SIZE);
if (screenCoordinatesMap.size()==1) { // only one object is selected
Map.Entry<Integer, ScreenCoordinates> entry = screenCoordinatesMap.entrySet().iterator().next();
ScreenCoordinates screenCoordinates = screenCoordinatesMap.values().iterator().next();
Vector3d modelCoordinates = GLUtils.getModelCoordinates(gl, screenCoordinates);
logger.info("model coordinates = {}", modelCoordinates.asArray());
Model model = ModelHolder.getModel();
MovingObject movingObject = modelService.findMovingObjectByIndex(model, entry.getKey().intValue());
UserPointService userPointService = Application.getInstance().getService(UserPointService.class);
userPointService.createUserOrbitalPoint(movingObject, modelCoordinates, ModelHolder.getModel().getTime());
}
}
public void dragUserPoint(GLAutoDrawable drawable, Point draggedPoint) {
GL2 gl = drawable.getGL().getGL2();
UserOrbitalPoint userOrbitalPoint = getSelectedUserOrbitalPoint();
Map<Integer, ScreenCoordinates> screenCoordinatesMap = GLUtils.getStencilPosition(gl, draggedPoint, RendererModel.MIN_TARGET_SIZE);
if (screenCoordinatesMap.size()==1) { // only one object is selected
ScreenCoordinates screenCoordinates = screenCoordinatesMap.values().iterator().next();
Vector3d modelCoordinates = GLUtils.getModelCoordinates(gl, screenCoordinates);
logger.info("dragged model coordinates = {}", modelCoordinates.asArray());
userPointService.updateUserOrbitalPoint(userOrbitalPoint, modelCoordinates, ModelHolder.getModel().getTime());
}
}
public void setSelectedUserOrbitalPoint(UserOrbitalPoint selectedUserOrbitalPoint) {
this.selectedUserOrbitalPoint = selectedUserOrbitalPoint;
}
public UserOrbitalPoint getSelectedUserOrbitalPoint() {
return selectedUserOrbitalPoint;
}
public File getModelFile() {
return modelFile;
}
public void setModelFile(File modelFile) {
File oldFile = this.modelFile;
this.modelFile = modelFile;
firePropertyChange(MODEL_FILE, oldFile, this.modelFile);
}
public JFileChooser getFileChooser() {
return fileChooser;
}
public void addPropertyChangeListener(String propertyName, final PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, final PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
protected void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) {
propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
public void initPropertyChange(final String propertyName, final Object newValue) {
firePropertyChange(propertyName, null, newValue);
}
public List<Integer> getAvailableIndexes() {
List<Integer> result = new ArrayList<>();
for(int i=1; i<=9; i++) {
result.add(Integer.valueOf(i));
}
for(MovingObject mo : ModelHolder.getModel().getMovingObjects()) {
result.remove(mo.getIndex());
}
return result;
}
public void setWarpFactor(double warpFactor) {
double oldValue = this.warpFactor;
this.warpFactor = warpFactor;
firePropertyChange(WARP_FACTOR, oldValue, warpFactor);
}
public double getWarpFactor() {
return warpFactor;
}
public void setNewSpacecraft(Spacecraft newSpacecraft) {
this.newSpacecraft = newSpacecraft;
}
public Spacecraft getNewSpacecraft() {
return newSpacecraft;
}
public void setDeleteSpacecraft(Spacecraft deleteSpacecraft) {
this.deleteSpacecraft = deleteSpacecraft;
}
public Spacecraft getDeleteSpacecraft() {
return deleteSpacecraft;
}
public boolean isReloadRenderersRequired() {
return reloadRenderersRequired;
}
public void setReloadRenderersRequired(boolean reloadRenderersRequired) {
this.reloadRenderersRequired = reloadRenderersRequired;
}
public boolean isTakeScreenshotRequired() {
return takeScreenshotRequired;
}
public void setTakeScreenshotRequired(boolean takeScreenshotRequired) {
this.takeScreenshotRequired = takeScreenshotRequired;
}
public void setNewUserPointPosition(Point newUserPointPosition) {
this.newUserPointPosition = newUserPointPosition;
}
public Point getNewUserPointPosition() {
return newUserPointPosition;
}
public void setSaveFileRequested(File saveFileRequested) {
this.saveFileRequested = saveFileRequested;
}
public File getSaveFileRequested() {
return saveFileRequested;
}
public File getLoadFileRequested() {
return loadFileRequested;
}
public void setLoadFileRequested(File loadFileRequested) {
this.loadFileRequested = loadFileRequested;
}
public void setDragUserPointPosition(Point dragUserPointPosition) {
this.dragUserPointPosition = dragUserPointPosition;
}
public Point getDragUserPointPosition() {
return dragUserPointPosition;
}
public void setQuitRequested(boolean quitRequested) {
this.quitRequested = quitRequested;
}
public boolean isQuitRequested() {
return quitRequested;
}
public void doSave(boolean saveAs) {
File file = null;
if (!saveAs) {
file = getModelFile();
}
if (file == null) {
file = selectSaveFile();
}
if (file != null) {
setSaveFileRequested(file);
}
}
public void saveFile(File file) {
logger.info("file = {}", file);
FileWriter fileWriter = null;
Assert.notNull(file);
try {
fileWriter = new FileWriter(file);
modelSerializer.save(ModelHolder.getModel(), fileWriter);
fileWriter.flush();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
StatusBarEvent event = new StatusBarEvent(ModelHolder.getModel(), "Model successfully saved.");
fireModelEvent(event);
}
});
setModelFile(file);
} catch (final IOException ioe) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(null,
ioe.getMessage(),
"Save Error",
JOptionPane.ERROR_MESSAGE);
}
});
} finally {
IOUtils.closeQuietly(fileWriter);
}
}
public Model loadFile(File file) {
FileReader fileReader = null;
Model model = null;
try {
fileReader = new FileReader(file);
model = modelSerializer.load(fileReader);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
StatusBarEvent event = new StatusBarEvent(ModelHolder.getModel(), "Model successfully loaded.");
fireModelEvent(event);
}
});
setModelFile(file);
} catch (final IOException ioe) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(null,
ioe.getMessage(),
"Load Error",
JOptionPane.ERROR_MESSAGE);
}
});
} finally {
IOUtils.closeQuietly(fileReader);
}
return model;
}
public File selectSaveFile() {
JFileChooser fileChooser = getFileChooser();
fileChooser.setDialogTitle("Save Dialog...");
if (fileChooser.showSaveDialog(null)==JFileChooser.APPROVE_OPTION) {
return fileChooser.getSelectedFile();
}
return null;
}
public float getFps() {
return this.fps;
}
public void setFps(float fps) {
float old = this.fps;
this.fps = fps;
firePropertyChange(FPS, old, this.fps);
}
public void setModelReady(boolean modelReady) {
boolean old = this.modelReady;
this.modelReady = modelReady;
firePropertyChange(MODEL_READY, old, this.modelReady);
}
public boolean isModelReady() {
return modelReady;
}
public void setCloseRequested(boolean closeRequested) {
this.closeRequested = closeRequested;
}
public boolean isCloseRequested() {
return closeRequested;
}
public void setModelBuilderRequested(ModelBuilder modelBuilderRequested) {
this.modelBuilderRequested = modelBuilderRequested;
}
public ModelBuilder getModelBuilderRequested() {
return modelBuilderRequested;
}
public void createFromBuilder(ModelBuilder modelBuilder) {
ModelHolder.setModel(Application.getInstance().getService(ModelBuilderFactory.class).init(modelBuilder));
setModelReady(true);
fireModelEvent(new StatusBarEvent(ModelHolder.getModel(), "Model created from builder '" + modelBuilder.getName() + "'"));
}
public void createFromBuilder() {
createFromBuilder(getModelBuilderRequested());
}
public void removeSpacecraft(Spacecraft spacecraft) {
modelService.removeMovingObject(ModelHolder.getModel(), spacecraft);
}
public void runDelayedActions(GLAutoDrawable drawable, ModelRenderer renderer, DefaultWindow window) {
if (getNewSpacecraft()!=null) {
Spacecraft spacecraft = getNewSpacecraft();
replaceMovingObjectsModel();
GL2 gl = drawable.getGL().getGL2();
MovingObjectCompositeRenderer movingObjectCompositeRenderer = new MovingObjectCompositeRenderer(spacecraft);
movingObjectCompositeRenderer.init(gl);
renderer.addRenderer(movingObjectCompositeRenderer);
setNewSpacecraft(null);
}
if (getDeleteSpacecraft()!=null) {
Spacecraft spacecraft = getDeleteSpacecraft();
removeSpacecraft(spacecraft);
GL2 gl = drawable.getGL().getGL2();
MovingObjectCompositeRenderer movingObjectCompositeRenderer = renderer.deleteMovingObject(spacecraft);
movingObjectCompositeRenderer.dispose(gl);
renderer.removeRenderer(movingObjectCompositeRenderer);
fireModelEvent(new StatusBarEvent(ModelHolder.getModel(), "Spacecraft '" + spacecraft.getName() + "' removed"));
setDeleteSpacecraft(null);
}
if (isReloadRenderersRequired()) {
GL2 gl = drawable.getGL().getGL2();
renderer.reload(gl);
setReloadRenderersRequired(false);
}
if (isTakeScreenshotRequired()) {
logger.info("take screenshot now");
File dir = new File(System.getProperty("user.home"));
File file = GLUtils.saveFrameAsPng(drawable, dir);
setTakeScreenshotRequired(false);
fireModelEvent(new StatusBarEvent(ModelHolder.getModel(), "Screenshot taken as file:" + file.getAbsolutePath()));
}
if (getNewUserPointPosition()!=null) {
Point position = getNewUserPointPosition();
createUserPoint(drawable, position);
setNewUserPointPosition(null);
}
if (getSaveFileRequested()!=null) {
File file = getSaveFileRequested();
saveFile(file);
setSaveFileRequested(null);
}
if (getLoadFileRequested()!=null) {
File file = getLoadFileRequested();
Model model = loadFile(file);
ModelHolder.setModel(model);
replaceMovingObjectsModel();
GL2 gl = drawable.getGL().getGL2();
renderer.dispose(gl);
renderer.clearAllRenderers();
renderer.createRenderers();
renderer.init(gl);
setModelReady(true);
setLoadFileRequested(null);
}
if (getDragUserPointPosition()!=null) {
Point position = getDragUserPointPosition();
dragUserPoint(drawable, position);
setDragUserPointPosition(null);
}
if (isQuitRequested()) {
window.stopAnimator();
setQuitRequested(false);
}
if (isCloseRequested()) {
GL2 gl = drawable.getGL().getGL2();
renderer.dispose(gl);
renderer.clearAllRenderers();
ModelHolder.setModel(null);
setModelFile(null);
setModelReady(false);
replaceMovingObjectsModel();
setCloseRequested(false);
}
if (getModelBuilderRequested() != null) {
GL2 gl = drawable.getGL().getGL2();
renderer.dispose(gl);
renderer.clearAllRenderers();
createFromBuilder();
renderer.createRenderers();
renderer.init(gl);
replaceMovingObjectsModel();
setModelBuilderRequested(null);
}
setFps(drawable.getAnimator().getLastFPS());
}
}