/*
* This file is part of Goko.
*
* Goko is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Goko 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 Goko. If not, see <http://www.gnu.org/licenses/>.
*/
package org.goko.tools.viewer.jogl.service;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.media.opengl.DebugGL3;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL3;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.vecmath.Color3f;
import javax.vecmath.Color4f;
import javax.vecmath.Point3f;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.goko.core.common.exception.GkException;
import org.goko.core.common.exception.GkFunctionalException;
import org.goko.core.common.utils.CacheById;
import org.goko.core.common.utils.SequentialIdGenerator;
import org.goko.core.log.GkLog;
import org.goko.core.math.BoundingTuple6b;
import org.goko.core.viewer.renderer.IViewer3DRenderer;
import org.goko.tools.viewer.jogl.GokoJoglCanvas;
import org.goko.tools.viewer.jogl.camera.AbstractCamera;
import org.goko.tools.viewer.jogl.camera.PerspectiveCamera;
import org.goko.tools.viewer.jogl.camera.orthographic.FrontCamera;
import org.goko.tools.viewer.jogl.camera.orthographic.LeftCamera;
import org.goko.tools.viewer.jogl.camera.orthographic.TopCamera;
import org.goko.tools.viewer.jogl.preferences.JoglViewerPreference;
import org.goko.tools.viewer.jogl.service.overlay.CameraNameOverlay;
import org.goko.tools.viewer.jogl.service.utils.CoreJoglRendererAlphaComparator;
import org.goko.tools.viewer.jogl.shaders.EnumGokoShaderProgram;
import org.goko.tools.viewer.jogl.shaders.ShaderLoader;
import org.goko.tools.viewer.jogl.utils.light.Light;
import org.goko.tools.viewer.jogl.utils.overlay.IOverlayRenderer;
import org.goko.tools.viewer.jogl.utils.render.JoglRendererWrapper;
import com.jogamp.opengl.util.PMVMatrix;
import com.jogamp.opengl.util.awt.Overlay;
public abstract class JoglSceneManager implements GLEventListener, IPropertyChangeListener{
/** LOG */
private static final GkLog LOG = GkLog.getLogger(JoglSceneManager.class);
/** Flag to enable/disable the render*/
private boolean enabled = true;
private Overlay overlay;
private int x;
private int y;
private int width;
private int height;
/** Current camera */
private AbstractCamera camera;
/** The list of supported camera */
private List<AbstractCamera> supportedCamera;
/** Display canvas */
private GokoJoglCanvas canvas;
/** Rendering proxy */
private JoglRendererProxy proxy;
/** The list of renderer */
private List<ICoreJoglRenderer> renderers;
/** The list of renderer to remove */
private List<ICoreJoglRenderer> renderersToRemove;
/** The list of overlay renderer */
private CacheById<IOverlayRenderer> overlayRenderers;
private GLCapabilities canvasCapabilities;
private Map<Integer, Boolean> layerVisibility;
private Light light0;
private Light light1;
private Color3f backgroundColor;
private boolean updateBackgroundColor;
public JoglSceneManager() {
getRenderers();
initLayers();
this.renderersToRemove = new ArrayList<ICoreJoglRenderer>();
this.overlayRenderers = new CacheById<IOverlayRenderer>(new SequentialIdGenerator());
JoglViewerPreference.getInstance().addPropertyChangeListener(this);
}
private void initLayers() {
this.layerVisibility = new HashMap<Integer, Boolean>();
this.layerVisibility.put(Layer.LAYER_GRIDS, true);
this.layerVisibility.put(Layer.LAYER_BOUNDS, true);
this.layerVisibility.put(Layer.LAYER_DEFAULT, true);
}
public GokoJoglCanvas createCanvas(Composite parent) throws GkException {
if(canvas != null){
return canvas;
}
GLProfile profile = GLProfile.getMaxFixedFunc(true);//getDefault();
canvasCapabilities = new GLCapabilities(profile);
canvasCapabilities.setSampleBuffers(true);
canvasCapabilities.setNumSamples(JoglViewerPreference.getInstance().getMultisampling());
canvasCapabilities.setHardwareAccelerated(true);
canvasCapabilities.setDoubleBuffered(true);
canvas = new GokoJoglCanvas(parent, SWT.NO_BACKGROUND, canvasCapabilities);
canvas.addGLEventListener(this);
proxy = new JoglRendererProxy(null);
addCamera(new PerspectiveCamera(canvas));
addCamera(new TopCamera(canvas));
addCamera(new LeftCamera(canvas));
addCamera(new FrontCamera(canvas));
String defaultCamera = JoglViewerPreference.getInstance().getDefaultCamera();
if(isSupportedCamera(defaultCamera)){
setActiveCamera(defaultCamera);
}else{
setActiveCamera(PerspectiveCamera.ID);
JoglViewerPreference.getInstance().setDefaultCamera(PerspectiveCamera.ID);
}
addOverlayRenderer(new CameraNameOverlay(this));
onCanvasCreated(canvas);
return canvas;
}
protected abstract void onCanvasCreated(GokoJoglCanvas canvas) throws GkException;
/**
* Initialization of the lights
*/
protected void initLights(){
light0 = new Light(new Point3f(1000,1000,1000), new Color4f(0.5f,0.5f,0.45f,1), new Color4f(0.25f,0.2f,0.2f,1));
light1 = new Light(new Point3f(-500,-1000,-600), new Color4f(0.3f,0.3f,0.31f,1), new Color4f(0.1f,0.1f,0.15f,1));
}
/** (inheritDoc)
* @see javax.media.opengl.GLEventListener#display(javax.media.opengl.GLAutoDrawable)
*/
@Override
public void display(GLAutoDrawable gLAutoDrawable) {
GL3 gl = new DebugGL3( gLAutoDrawable.getGL().getGL3());
if(updateBackgroundColor){
gl.glClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0f); // reset background (clear) color
updateBackgroundColor = false;
}
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
if(!isEnabled()){
return;
}
if(camera == null){
return;
}
updateCamera(gLAutoDrawable, gl);
PMVMatrix cameraMatrix = camera.getPmvMatrix();
ShaderLoader.getInstance().updateProjectionMatrix(gl, cameraMatrix);
ShaderLoader.getInstance().updateLightData(gl, light0, light1);
proxy.setGl(gl);
try {
displayRenderers(gl);
} catch (GkException e) {
LOG.error(e);
}
gl.glUseProgram(0);
drawOverlay();
}
/**
* Display the registered renderers
* @param gl the GL2 to draw on
* @throws GkException GkException
*/
private void displayRenderers(GL3 gl) throws GkException{
synchronized (renderers) {
for (ICoreJoglRenderer renderer : getRenderers()) {
if(renderer.shouldDestroy()){
renderersToRemove.add(renderer);
}else{
if(isLayerVisible(renderer.getLayerId())){
renderer.render(gl, camera.getPmvMatrix());
}
}
}
if(CollectionUtils.isNotEmpty(renderersToRemove)){
for (ICoreJoglRenderer renderer : renderersToRemove) {
renderer.performDestroy(gl);
LOG.info("Destroying renderer "+renderer.getCode()+" ["+renderer.toString()+"]");
renderers.remove(renderer);
}
renderersToRemove.clear();
}
}
}
public boolean isLayerVisible(int layerId) {
if(layerVisibility.containsKey(layerId)){
return layerVisibility.get(layerId);
}
return true;
}
public void addRenderer(IViewer3DRenderer renderer) throws GkException {
addRenderer(new JoglRendererWrapper(renderer));
}
public void addRenderer(ICoreJoglRenderer renderer) throws GkException {
renderers.add(renderer);
LOG.info("Adding renderer "+renderer.getCode()+" ["+renderer.toString()+"]");
synchronized (renderers) {
// Make sure that renderer using alpha get rendered last
Collections.sort(getRenderers(), new CoreJoglRendererAlphaComparator());
}
}
public void removeRenderer(ICoreJoglRenderer renderer) throws GkException {
synchronized (renderers) {
getRenderers().remove(renderer);
}
}
public void removeRenderer(IViewer3DRenderer renderer) throws GkException {
synchronized (renderers) {
getRenderers().remove(renderer);
}
}
/**
* Removes the given JOGL Renderer
* @param renderer the renderer to remove
* @throws GkException GkException
*/
protected void removeRenderer(AbstractCoreJoglRenderer renderer) throws GkException {
getRenderers().remove(renderer);
}
/**
* @return the renderers
*/
protected List<ICoreJoglRenderer> getRenderers() {
if(renderers == null){
renderers = Collections.synchronizedList(new ArrayList<ICoreJoglRenderer>());
}
return renderers;
}
public void setRendererEnabled(String idRenderer, boolean enabled) throws GkException{
getJoglRenderer(idRenderer).setEnabled(enabled);
}
/** (inheritDoc)
* @see javax.media.opengl.GLEventListener#dispose(javax.media.opengl.GLAutoDrawable)
*/
@Override
public void dispose(GLAutoDrawable gLAutoDrawable) {
}
/** (inheritDoc)
* @see javax.media.opengl.GLEventListener#init(javax.media.opengl.GLAutoDrawable)
*/
@Override
public void init(GLAutoDrawable gLAutoDrawable) {
GL3 gl = gLAutoDrawable.getGL().getGL3(); // get the OpenGL graphics context
//gl.glClearColor(.19f, .19f, .23f, 1.0f); // set background (clear) color
backgroundColor = JoglViewerPreference.getInstance().getBackgroundColor();
gl.glClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0f);
gl.glClearDepth(1.0f); // set clear depth value to farthest
gl.getMaxRenderbufferSamples();
// Enable blending
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
// Enable ZBuffer
gl.glEnable(GL.GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
gl.glDepthFunc(GL.GL_LEQUAL);
// Perspective correction
gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST); // best perspective correction
// Line smooth
gl.glEnable(GL.GL_LINE_SMOOTH);
gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_DONT_CARE);
int shaderProgram = ShaderLoader.loadShader(new DebugGL3(gl.getGL3()), EnumGokoShaderProgram.LINE_SHADER);
gl.glBindAttribLocation(shaderProgram, 0, "vertexPosition_modelspace");
gl.glUseProgram(shaderProgram);
initLights();
overlay = new Overlay(gLAutoDrawable);
overlay.createGraphics();
}
/** (inheritDoc)
* @see javax.media.opengl.GLEventListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int)
*/
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.setWidth(width);
this.height = height;
if(camera != null){
camera.reshape(drawable, x, y, width, height);
}
}
public List<AbstractCamera> getSupportedCamera() throws GkException {
if(supportedCamera == null){
this.supportedCamera = new ArrayList<AbstractCamera>();
}
return supportedCamera;
}
public boolean isSupportedCamera(String cameraId) throws GkException{
for (AbstractCamera tmpCamera : getSupportedCamera()) {
if(StringUtils.equals(cameraId, tmpCamera.getId())){
return true;
}
}
return false;
}
public void setActiveCamera(String idCamera) throws GkException {
for (AbstractCamera tmpCamera : getSupportedCamera()) {
if(StringUtils.equals(idCamera, tmpCamera.getId())){
if(camera != null){
camera.setActivated(false);
}
camera = tmpCamera;
camera.updateViewport(x, y, getWidth(), height);
camera.setActivated(true);
return;
}
}
}
/**
* Update the camera informations
* @param gLAutoDrawable the drawable
* @param gl the GL2
*/
private void updateCamera(GLAutoDrawable gLAutoDrawable, GL3 gl){
if(!camera.isInitialized()){
camera.reshape(gLAutoDrawable, x, y, getWidth(), height);
camera.setInitialized(true);
}
camera.updateViewport(x, y, getWidth(), height);
camera.updatePosition();
}
private void drawOverlay() {
overlay.beginRendering();
Graphics2D g2d = overlay.createGraphics();
try{
drawOverlayRenderer(g2d);
}catch(GkException e){
LOG.error(e);
}
overlay.markDirty(0, 0, getWidth(), height);
overlay.drawAll();
g2d.dispose();
overlay.endRendering();
}
/**
* Registers the given overlay renderer
* @param overlayRenderer the renderer to register
* @throws GkException GkException
*/
public void addOverlayRenderer(IOverlayRenderer overlayRenderer) throws GkException{
overlayRenderers.add(overlayRenderer);
}
/**
* Draws the registered overlays
* @param g2d the target graphic
* @throws GkException GkException
*/
protected void drawOverlayRenderer(Graphics2D g2d) throws GkException{
List<IOverlayRenderer> lstRenderers = overlayRenderers.get();
Rectangle bounds = new Rectangle(x, y, width, height);
for (IOverlayRenderer overlayRenderer : lstRenderers) {
if(overlayRenderer.isOverlayEnabled()){
overlayRenderer.drawOverlayData(g2d, bounds);
}
}
}
public ICoreJoglRenderer getJoglRenderer(String idRenderer) throws GkException {
if(CollectionUtils.isNotEmpty(renderers)){
for (ICoreJoglRenderer renderer : renderers) {
if(StringUtils.equals(renderer.getCode(), idRenderer)){
return renderer;
}
}
}
throw new GkFunctionalException("Renderer '"+idRenderer+"' does not exist.");
}
/** (inheritDoc)
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
// Property change listener
if(canvasCapabilities != null){
canvasCapabilities.setNumSamples(JoglViewerPreference.getInstance().getMultisampling());
}
setBackgroundColor(JoglViewerPreference.getInstance().getBackgroundColor());
}
public void addCamera(AbstractCamera camera) throws GkException{
getSupportedCamera().add(camera);
}
public AbstractCamera getActiveCamera() throws GkException {
return camera;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* @return the camera
*/
protected AbstractCamera getCamera() {
return camera;
}
/**
* @param camera the camera to set
*/
protected void setCamera(AbstractCamera camera) {
this.camera = camera;
}
/**
* @return the canvas
*/
protected GokoJoglCanvas getCanvas() {
return canvas;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public void setLayerVisible(int layerId, boolean visible){
this.layerVisibility.put(layerId, visible);
}
public void zoomToFit() throws GkException {
BoundingTuple6b contentBounds = getContentBounds();
if(contentBounds != null){
getCamera().zoomToFit(contentBounds);
}
}
public BoundingTuple6b getContentBounds(){
BoundingTuple6b result = null;
if(CollectionUtils.isNotEmpty(renderers)){
for (ICoreJoglRenderer joglRenderer : renderers) {
BoundingTuple6b bound = joglRenderer.getBounds();
if(bound != null){
if(result == null){
result = new BoundingTuple6b(bound.getMin(), bound.getMax());
}else{
result.add(bound);
}
}
}
}
return result;
}
/**
* @return the backgroundColor
*/
public Color3f getBackgroundColor() {
return backgroundColor;
}
/**
* @param backgroundColor the backgroundColor to set
*/
public void setBackgroundColor(Color3f backgroundColor) {
this.backgroundColor = backgroundColor;
this.updateBackgroundColor = true;
}
}