package com.momega.spacesimulator.opengl; import com.jogamp.opengl.util.GLReadBufferUtil; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.TextureIO; import com.momega.spacesimulator.model.*; import com.momega.spacesimulator.renderer.ScreenCoordinates; import com.momega.spacesimulator.renderer.ViewCoordinates; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLProfile; import javax.media.opengl.glu.GLU; import javax.media.opengl.glu.GLUquadric; import java.awt.*; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import static javax.media.opengl.GL.*; /** * The class contains static handful methods to draw objects such as {#link drawCircle} or {#link drawEllipse} * Created by martin on 4/21/14. */ public class GLUtils { private static final Logger logger = LoggerFactory.getLogger(GLUtils.class); public static final double DEFAULT_THRESHOLD = 1E-3; /** * Draws the circle * @param gl Open GL * @param cx x-center of circle * @param cy y-center of circle * @param r radius of the circle * @param num_segments number of the segments */ public static void drawCircle(GL2 gl, double cx, double cy, double r, int num_segments) { double theta = (float) (2 * Math.PI / num_segments); double c = (float) Math.cos(theta); //calculates the sine and cosine double s = (float) Math.sin(theta); double t; double x = r;//we start at angle = 0 double y = 0; gl.glBegin(GL_LINE_LOOP); for(int ii = 0; ii < num_segments; ii++) { gl.glVertex2d(x + cx, y + cy);//output vertex //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } gl.glEnd(); } /** * Draw set of the points * @param gl the GL context * @param points the collection of the points */ public static <T extends PositionProvider> void drawPoints(GL2 gl, int size, double[] color, List<T> points) { gl.glPointSize(size); gl.glColor3dv(color, 0); gl.glBegin(GL_POINTS); for(T point : points) { gl.glVertex3dv(point.getPosition().asArray(), 0); } gl.glEnd(); } public static void drawSphere(GL2 gl2, double radius, boolean texture) { GLU glu = new GLU(); GLUquadric quadric = glu.gluNewQuadric(); glu.gluQuadricTexture(quadric, texture); if (texture == false) { glu.gluQuadricDrawStyle(quadric, GLU.GLU_FILL); } glu.gluQuadricNormals(quadric, GLU.GLU_FLAT); glu.gluQuadricOrientation(quadric, GLU.GLU_OUTSIDE); glu.gluSphere(quadric, radius, 64, 64); glu.gluDeleteQuadric(quadric); } /** * Draw the strip line * @param gl the GL context * @param points the collection of the points */ public static <T extends PositionProvider> void drawMultiLine(GL2 gl, int width, double[] color, List<T> points) { gl.glLineWidth(width); gl.glColor3dv(color, 0); gl.glBegin(GL_LINE_STRIP); for (T point : points) { gl.glVertex3dv(point.getPosition().asArray(), 0); } gl.glEnd(); } /** * Draw the strip line * @param gl the GL context * @param points the collection of the points */ public static void drawMultiLine(GL2 gl, double width, double[] color, Vector3d[] points) { gl.glLineWidth((float) width); gl.glColor3dv(color, 0); gl.glBegin(GL_LINE_STRIP); for(Vector3d point : points) { gl.glVertex3dv(point.asArray(), 0); } gl.glEnd(); } /** * Draws the beams * @param gl Open GL * @param cx x-center of circle * @param cy y-center of circle * @param r radius of the circle * @param num_beams number of the beams */ public static void drawBeams(GL2 gl, double cx, double cy, double r, int num_beams) { double theta = (float) (2 * Math.PI / num_beams); double c = (float) Math.cos(theta); //calculates the sine and cosine double s = (float) Math.sin(theta); double t; double x = r;//we start at angle = 0 double y = 0; gl.glBegin(GL_LINES); for(int ii = 0; ii < num_beams; ii++) { gl.glVertex2d(cx, cy); gl.glVertex2d(x + cx, y + cy);//output vertex //apply the rotation matrix t = x; x = c * x - s * y; y = s * t + c * y; } gl.glEnd(); } public static void drawBeansAndCircles(GL2 gl, double r, int num_beams, int num_circles, double[] color) { gl.glColor3dv(color, 0); double circleDistance = r / num_circles; for (int i = 1; i <= num_circles; i++) { GLUtils.drawCircle(gl, 0, 0, circleDistance * i, 360); } GLUtils.drawBeams(gl, 0, 0, r, num_beams); } public static void drawBezier(GL2 gl, Vector3d start, Vector3d startCtrlPoint, Vector3d endCtrlPoint, Vector3d end, double[] color) { DoubleBuffer ctrlPointBuffer = DoubleBuffer.allocate(12); ctrlPointBuffer.put(start.asArray()); ctrlPointBuffer.put(startCtrlPoint.asArray()); ctrlPointBuffer.put(endCtrlPoint.asArray()); ctrlPointBuffer.put(end.asArray()); ctrlPointBuffer.rewind(); gl.glMap1d(GL2.GL_MAP1_VERTEX_3, 0.0d, 1.0d, 3, 4, ctrlPointBuffer); gl.glColor3dv(color, 0); gl.glEnable(GL2.GL_MAP1_VERTEX_3); gl.glBegin(GL2.GL_LINE_STRIP); for (int i = 0; i <= 600; i++) { gl.glEvalCoord1d(i / 600.0d); } gl.glEnd(); gl.glDisable(GL2.GL_MAP1_VERTEX_3); } public static void drawAxis(GL2 gl, double lineWidth, double radius) { gl.glLineWidth((float) lineWidth); gl.glBegin(GL2.GL_LINES); gl.glVertex3d(0, 0, radius); gl.glVertex3d(0, 0, -radius); gl.glVertex3d(radius, 0, 0); gl.glVertex3d(0, 0, 0); gl.glVertex3d(0, 0, 0); gl.glVertex3d(0, -radius, 0); gl.glEnd(); } /** * Draw the ellipse. The center of the ellipse is at coordinates [0,0] * @param gl the OpenGL context * @param a the semi-major axis * @param b the semi-minor axis * @param num_segments the number of the segments */ public static void drawEllipse(GL2 gl, double a, double b, int num_segments, double[] color) { gl.glColor3dv(color, 0); gl.glBegin(GL_LINE_LOOP); double DEG2RAD = 2.0 * Math.PI / num_segments; for (int i=0; i<=num_segments ; i++) { double degInRad = DEG2RAD * i; gl.glVertex2d(Math.cos(degInRad) * a, Math.sin(degInRad) * b); } gl.glEnd(); } /** * Draw the ellipse partially. The center of the ellipse is at coordinates [0,0] * @param gl the OpenGL context * @param a the semi-major axis * @param b the semi-minor axis * @param startAngle * @param stopAngle end angle * @param color * @param num_segments the number of the segments */ public static void drawEllipse(GL2 gl, double a, double b, double startAngle, double stopAngle, int num_segments, double[] color) { gl.glColor3dv(color, 0); gl.glBegin(GL_LINE_STRIP); double DEG2RAD = 2.0 * Math.PI / num_segments; int startIndex = (int) (startAngle / DEG2RAD); int stopIndex = (int) (stopAngle / DEG2RAD); for (int i= startIndex; i<=stopIndex; i++) { double degInRad = DEG2RAD * i; gl.glVertex2d(Math.cos(degInRad) * a, Math.sin(degInRad) * b); } gl.glEnd(); } public static void drawHyperbolaPartial(GL2 gl, double a, double b, double startAngle, double stopAngle, int num_segments, double[] color) { gl.glColor3dv(color, 0); gl.glBegin(GL_LINE_STRIP); double DEG2RAD = 2 * Math.PI / num_segments; int startIndex = (int) (startAngle / DEG2RAD); int stopIndex = (int) (stopAngle / DEG2RAD); for (int i= startIndex; i<=stopIndex; i++) { double degInRad = DEG2RAD * i; gl.glVertex2d(Math.cosh(degInRad) * a, Math.sinh(degInRad) * b); } gl.glEnd(); } public static void drawHyperbola(GL2 gl, double a, double b, int num_segments) { gl.glBegin(GL_LINE_STRIP); double DEG2RAD = 2.0 * Math.PI / num_segments; for (int i= -num_segments/2 + 1; i<=num_segments/2-1 ; i++) { double degInRad = DEG2RAD * i; gl.glVertex2d(Math.cosh(degInRad) * a, Math.sinh(degInRad) * b); } gl.glEnd(); } /** * Draws the ring and setup the point of the textures * @param gl the open GL context * @param minorRadius inner radius * @param majorRadius outer radius * @param num_segments number of the segments * @param num_slices number of the disk slices */ public static void drawRing(GL2 gl, double minorRadius, double majorRadius, int num_segments, int num_slices) { gl.glColor3dv(new double[] {1, 1, 1}, 0); gl.glBegin(GL2.GL_QUAD_STRIP); double DEG2RAD = 2.0 * Math.PI / num_segments; for (int j=0; j<num_slices; j++){ double rmin = minorRadius + j * ((majorRadius - minorRadius) / (double)num_slices); double rmax = minorRadius + (j+1) * ((majorRadius - minorRadius) / (double)num_slices); double jmin = (double)j / (double) num_slices; double jmax = (double)(j+1) / (double) num_slices; for (int i=0; i<=num_segments ; i++) { double degInRad = DEG2RAD * i; gl.glTexCoord2d(1 - jmin, 0); gl.glVertex2d(Math.cos(degInRad) * rmin, Math.sin(degInRad) * rmin); gl.glTexCoord2d(1 - jmax, 0); gl.glVertex2d(Math.cos(degInRad) * rmax, Math.sin(degInRad) * rmax); degInRad = DEG2RAD * (i + 1); gl.glTexCoord2d(1 - jmin, 1); gl.glVertex2d(Math.cos(degInRad) * rmin, Math.sin(degInRad) * rmin); gl.glTexCoord2d(1- jmax, 1); gl.glVertex2d(Math.cos(degInRad) * rmax, Math.sin(degInRad) * rmax); } } gl.glEnd(); } public static Point getPosition(MouseEvent e) { final GLAutoDrawable canvas = (GLAutoDrawable) e.getSource(); return getPosition(canvas, e); } public static Point getPosition(GLDrawable drawable, MouseEvent e) { int x = e.getX(); int y = e.getY(); if (drawable.isGLOriented()) { y = drawable.getSurfaceHeight() - y; } return new Point(x, y); } /** * Finds the stenciled point around given coordinates * @param gl the opengl context * @param center the center of the search in projection coordinates * @param size the size of the square to be sought. This has to be odd number * @return result the map were keys are index of the stencil and value is the best point found */ public static Map<Integer, ScreenCoordinates> getStencilPosition(GL2 gl, Point center, int size) { gl.glReadBuffer(GL2.GL_FRONT); // for future usage //ByteBuffer buffer = ByteBuffer.allocate(4); //gl.glReadPixels(position.x, position.y, 1, 1, GL2.GL_RGBA, GL2.GL_UNSIGNED_BYTE, buffer); logger.info("position x,y = {},{}", center.x, center.y); int half = size / 2; IntBuffer stencilBuffer = IntBuffer.allocate(size * size); gl.glReadPixels(center.x - half, center.y - half, size, size, GL2.GL_STENCIL_INDEX, GL2.GL_UNSIGNED_INT, stencilBuffer); Map<Integer, ScreenCoordinates> result = new HashMap<>(); for(int i=0; i<size; i++) { for(int j=0; j<size; j++) { int realI = stencilFunction(i); int realJ = stencilFunction(j); int index = (realJ + size/2) * size + (realI + size/2); int stencil = stencilBuffer.get(index); if (stencil>0) { if (!result.containsKey(stencil)) { Point point = new Point(center.x + realI, center.y + realJ); double depth = GLUtils.getDepth(gl, point); result.put(stencil, new ScreenCoordinates(point, depth)); } } } } return result; } private static int stencilFunction(int i) { return ( (i+1)/2 * (i%2==0 ? 1 : -1)); } /** * Gets the depth on the given coordinates * @param gl the opengl context * @param point the projection coordinates * @return the the depth [0..1] */ public static double getDepth(GL2 gl, Point point) { FloatBuffer depthBuffer = FloatBuffer.allocate(1); gl.glReadPixels(point.x, point.y, 1, 1, GL2.GL_DEPTH_COMPONENT, GL2.GL_FLOAT, depthBuffer); return depthBuffer.get(0); } /** * Gets the depth on the given coordinates * @param gl the opengl context * @param point the projection coordinates * @return the the depth [0..1] */ public static double getDepth(GL2 gl, Point point, double threshold) { return (getDepth(gl, point) + threshold); } public static boolean checkDepth(GL2 gl, ViewCoordinates viewCoordinates) { return viewCoordinates.getScreenCoordinates().getDepth() <= getDepth(gl, viewCoordinates.getPoint(), DEFAULT_THRESHOLD); } /** * Gets the projection coordinates of the position in model-view based on the camera * @param gl open gl context * @param position any position in 3D * @param camera the camera * @return array of the coordinates, 0th coordinate is x, 1st coordinate is y, Result can be null if the point is behind * the camera */ public static ScreenCoordinates getProjectionCoordinates(GL2 gl, Vector3d position, Camera camera) { Vector3d viewVector = camera.getOppositeOrientation().getN(); Vector3d diffVector = position.subtract(camera.getPosition()); if (viewVector.dot(diffVector) < 0) { // object is in front of the camera double modelView[] = new double[16]; double projection[] = new double[16]; int viewport[] = new int[4]; gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, modelView, 0); gl.glGetDoublev(GL2.GL_PROJECTION_MATRIX, projection, 0 ); gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0 ); double[] my2DPoint = new double[4]; GLU glu = new GLU(); glu.gluProject(position.getX(), position.getY(), position.getZ(), modelView, 0, projection, 0, viewport, 0, my2DPoint, 0); return new ScreenCoordinates(my2DPoint); } return null; } public static Vector3d getModelCoordinates(GL2 gl, ScreenCoordinates screenCoordinates) { double modelView[] = new double[16]; double projection[] = new double[16]; int viewport[] = new int[4]; double wcoord[] = new double[4]; gl.glGetDoublev(GL2.GL_MODELVIEW_MATRIX, modelView, 0); gl.glGetDoublev(GL2.GL_PROJECTION_MATRIX, projection, 0 ); gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0 ); GLU glu = new GLU(); glu.gluUnProject(screenCoordinates.getPoint().getX(), screenCoordinates.getPoint().getY(), screenCoordinates.getDepth(), modelView, 0, projection, 0, viewport, 0, wcoord, 0); return new Vector3d(wcoord[0], wcoord[1], wcoord[2]); } public static Texture loadTexture(GL2 gl, Class<?> clazz, String fileName) { Texture result = loadTexture(gl, clazz, fileName, TextureIO.JPG, true); result.setTexParameteri(gl, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); result.setTexParameteri(gl, GL_TEXTURE_MAG_FILTER, GL_LINEAR); return result; } public static Texture loadTexture(GL2 gl, Class<?> clazz, String fileName, String fileSuffix, boolean mipmap) { InputStream stream = clazz.getResourceAsStream(fileName); return loadTexture(gl, stream, fileSuffix, mipmap); } public static Texture loadTexture(GL2 gl, InputStream stream, String fileSuffix, boolean mipmap) { try { TextureData data = TextureIO.newTextureData(GLProfile.getDefault(), stream, mipmap, fileSuffix); Texture result = TextureIO.newTexture(data); return result; } catch (IOException e) { throw new IllegalArgumentException("loading textures failed", e); } finally { IOUtils.closeQuietly(stream); } } public static void translate(GL2 gl, Vector3d position) { gl.glTranslated(position.getX(), position.getY(), position.getZ()); } /** * Rotates by spherical coordinates * @param gl the the GL context * @param sphericalCoordinates the spherical coordinates */ public static void rotate(GL2 gl, SphericalCoordinates sphericalCoordinates) { gl.glRotated(Math.toDegrees(sphericalCoordinates.getPhi()), 0, 0, 1); gl.glRotated(Math.toDegrees(sphericalCoordinates.getTheta()), 0, 1, 0); } /** * Rotate open gl context based on the following 3 axis (Z,X,Z) * @param gl * @param angles */ public static void rotateZXZ(GL2 gl, double[] angles) { // the order is important gl.glRotated(Math.toDegrees(angles[0]), 0, 0, 1); gl.glRotated(Math.toDegrees(angles[1]), 1, 0, 0); gl.glRotated(Math.toDegrees(angles[2]), 0, 0, 1); } public static void rotate(GL2 gl, KeplerianElements keplerianElements) { // the order is important rotateZXZ(gl, keplerianElements.getKeplerianOrbit().getAngles()); } public static void drawPoint(GL2 gl, int size, double[] color, PositionProvider positionProvider) { drawPoint(gl, size, color, positionProvider.getPosition()); } public static void drawPoint(GL2 gl, int size, double[] color, Vector3d position) { gl.glPointSize(size); gl.glColor3dv(color, 0); gl.glBegin(GL2.GL_POINTS); gl.glVertex3dv(position.asArray(), 0); gl.glEnd(); } /** * This method has to be called at the end * of {@link AbstractGLRenderer#display(javax.media.opengl.GLAutoDrawable)} * @param drawable the drawable * @param directory the directory where the output file will be stores * @return the output file name */ public static File saveFrameAsPng( GLAutoDrawable drawable, File directory ) { File outputFile = new File( directory, String.valueOf(System.nanoTime()) + ".png" ); // Do not overwrite existing image file. if( outputFile.exists() ) { return null; } logger.info("screenshot taken to {}", outputFile.getName()); GL2 gl = drawable.getGL().getGL2(); final GLReadBufferUtil screenshot = new GLReadBufferUtil(false, false); if(screenshot.readPixels(gl, false)) { screenshot.write(outputFile); } return outputFile; } }