/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.render3d;
import com.jogamp.opengl.FPSCounter;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLJPanel;
import com.jogamp.opengl.util.FPSAnimator;
import org.geotoolkit.gui.swing.util.BufferLayout;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.List;
import java.util.EventListener;
import java.util.logging.Level;
import javax.swing.*;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import org.geotoolkit.display3d.Map3D;
import org.geotoolkit.gui.swing.render3d.control.JAllControlDecoration;
import org.geotoolkit.gui.swing.render3d.control.JZoomControlDecoration;
import org.geotoolkit.gui.swing.render3d.control.MouseController;
import org.geotoolkit.display3d.scene.ContextContainer3D;
import org.geotoolkit.display3d.scene.Terrain;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
/**
*
* @author Johann Sorel (Geomatys)
* @author Thomas Rouby (Geomatys)
*/
public class JMap3D extends JPanel {
private static final int FPS = 35; // Animator's target frames per second
//OpenGL objects
private GLJPanel glCanvas;
private final Map3D map3d = new Map3D();
private FPSAnimator animator;
private final List<JComponent> userDecorations = new ArrayList<>();
private final JLayeredPane mapDecorationPane = new JLayeredPane();
private final JLayeredPane userDecorationPane = new JLayeredPane();
private final JLayeredPane mainDecorationPane = new JLayeredPane();
// Constructor to create profile, caps, drawable, animator, and initialize Frame
public JMap3D() {
setLayout(new BorderLayout());
setPreferredSize(new Dimension(150,150));
setOpaque(false);
setBackground(Color.WHITE);
mapDecorationPane.setOpaque(false);
userDecorationPane.setOpaque(false);
mainDecorationPane.setOpaque(false);
mapDecorationPane.setFocusable(false);
userDecorationPane.setFocusable(false);
mainDecorationPane.setFocusable(false);
mapDecorationPane.setLayout(new BufferLayout());
userDecorationPane.setLayout(new GridBagLayout());
mainDecorationPane.setLayout(new BufferLayout());
final JPanel userDecorationWest = new JPanel();
userDecorationWest.setLayout(new BorderLayout());
userDecorationWest.setOpaque(false);
userDecorationWest.setFocusable(false);
final JPanel userDecorationNorth = new JPanel();
userDecorationNorth.setLayout(new BorderLayout());
userDecorationNorth.setOpaque(false);
userDecorationNorth.setFocusable(false);
mainDecorationPane.add(userDecorationWest, Integer.valueOf(1));
userDecorationWest.add(BorderLayout.WEST, userDecorationNorth);
userDecorationNorth.add(BorderLayout.NORTH, userDecorationPane);
mainDecorationPane.add(mapDecorationPane, Integer.valueOf(0));
this.add(BorderLayout.CENTER, mainDecorationPane);
//listen to frame change, we must rebuild the GL context
//The graphic device has changed for opengl, it's an unstable state.
addAncestorListener(new AncestorListener() {
private Window lastWindow = null;
@Override
public void ancestorAdded(AncestorEvent event) {
final Window window = SwingUtilities.getWindowAncestor(JMap3D.this);
if(lastWindow==null){
lastWindow = window;
}else if(lastWindow != window){
lastWindow = window;
buildGLPanel();
}
}
@Override
public void ancestorRemoved(AncestorEvent event) {
}
@Override
public void ancestorMoved(AncestorEvent event) {
}
});
buildGLPanel();
//attach navigation controls
final MouseController mouseControl = new MouseController(getMap3D().getCamera());
addEventListener(mouseControl);
addDecoration(new JAllControlDecoration(getMap3D().getCamera()));
addDecoration(new JZoomControlDecoration(getMap3D()));
}
/**
* Build the GLJPanel.
* Dispose any previous one.
*/
private synchronized void buildGLPanel(){
Terrain terrain = null;
//dispose previous panel
if(glCanvas != null){
mapDecorationPane.removeAll();
mapDecorationPane.revalidate();
mapDecorationPane.repaint();
animator.stop();
glCanvas.removeGLEventListener(map3d);
terrain = ((ContextContainer3D)map3d.getContainer()).getTerrain();
map3d.dispose();
final GLContext context = glCanvas.getContext();
if(context != null){
context.release();
context.destroy();
}
glCanvas.destroy();
}
// Get the default OpenGL profile that best reflect your running
// platform.
final GLProfile glp = GLProfile.getDefault();
// Specifies a set of OpenGL capabilities, based on your profile.
final GLCapabilities caps = new GLCapabilities(glp);
caps.setDoubleBuffered(true);
// Allocate a GLDrawable, based on your OpenGL capabilities.
glCanvas = new GLJPanel(caps);
glCanvas.setIgnoreRepaint(true);
//canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
glCanvas.addGLEventListener(map3d);
// Create a animator that drives canvas' display() at 60 fps.
animator = new FPSAnimator(glCanvas, FPS){
// @Override
// protected void display() {
// super.display();
// if(map3d.getMonitor().stopRequested()){
// System.err.println("Stop display Map3D");
// this.stop();
// }
// }
};
animator.setUpdateFPSFrames(FPSCounter.DEFAULT_FRAMES_PER_INTERVAL, null);
animator.start(); //active animator
animator.pause(); //wait for wake up
mapDecorationPane.add(glCanvas, Integer.valueOf(0));
mapDecorationPane.revalidate();
if(terrain != null){
final Envelope env = terrain.getEnvelope();
// final int numMosaic = terrain.getMaxTreeDepth();
try {
terrain = ((ContextContainer3D)map3d.getContainer()).createTerrain(env, 10);
terrain.getUpdater().forceUpdate();
} catch (TransformException | FactoryException ex) {
Map3D.LOGGER.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
/**
* add a Decoration between the map and the information top decoration
* @param deco : MapDecoration to add
*/
public void addDecoration(final JComponent deco) {
if (deco != null && !userDecorations.contains(deco)) {
userDecorations.add(deco);
final GridBagConstraints cst = new GridBagConstraints();
cst.gridx = 0;
cst.gridy = GridBagConstraints.RELATIVE;
cst.gridwidth = deco.getWidth();
cst.gridheight = deco.getHeight();
cst.anchor = GridBagConstraints.NORTHWEST;
userDecorationPane.add(deco, cst);
userDecorationPane.setSize(userDecorationPane.getWidth() + deco.getWidth(), userDecorationPane.getHeight() + deco.getHeight());
userDecorationPane.revalidate();
userDecorationPane.repaint();
}
}
/**
* insert a MapDecoration at a specific index
* @param index : index where to insert the decoration
* @param deco : MapDecoration to add
*/
public void addDecoration(final int index, final JComponent deco) {
if (deco != null && !userDecorations.contains(deco)) {
userDecorations.add(index, deco);
final GridBagConstraints cst = new GridBagConstraints();
cst.gridx = 0;
cst.gridy = GridBagConstraints.RELATIVE;
cst.gridwidth = deco.getWidth();
cst.gridheight = deco.getHeight();
cst.anchor = GridBagConstraints.NORTHWEST;
userDecorationPane.add(deco, cst);
userDecorationPane.setSize(userDecorationPane.getWidth() + deco.getWidth(), userDecorationPane.getHeight() + deco.getHeight());
userDecorationPane.revalidate();
userDecorationPane.repaint();
}
}
/**
* get the index of a MapDecoration
* @param deco : MapDecoration to find
* @return index of the MapDecoration
* @throw ClassCastException or NullPointerException
*/
public int getDecorationIndex(final JComponent deco) {
return userDecorations.indexOf(deco);
}
/**
* remove a MapDecoration
* @param deco : MapDecoration to remove
*/
public void removeDecoration(final JComponent deco) {
if (deco != null && userDecorations.contains(deco)) {
userDecorations.remove(deco);
userDecorationPane.remove(deco);
userDecorationPane.setSize(userDecorationPane.getWidth()-deco.getWidth(),userDecorationPane.getHeight()-deco.getHeight());
userDecorationPane.revalidate();
userDecorationPane.repaint();
}
}
/**
* Add an event listener to the map pane
*
* possible event listener are : MouseListener, MouseWheelListener, MouseMotionListener and KeyListener
*
* @param event en EventListener to add
*/
public void addEventListener(EventListener event){
if (event instanceof MouseListener) {
mapDecorationPane.addMouseListener((MouseListener)event);
}
if (event instanceof MouseWheelListener){
mapDecorationPane.addMouseWheelListener((MouseWheelListener)event);
}
if (event instanceof MouseMotionListener){
mapDecorationPane.addMouseMotionListener((MouseMotionListener)event);
}
if (event instanceof KeyListener){
mainDecorationPane.addKeyListener((KeyListener)event);
}
}
public Map3D getMap3D(){
return this.map3d;
}
/**
* Start the GL animator.
*/
public void startGLRendering(){
// if (!this.animator.isAnimating()){
this.animator.resume();
// }
}
/**
* Stop GL animator.
*/
public void stopGLRendering(){
if (this.animator.isAnimating()) {
this.animator.pause();
}
}
}