package org.archstudio.bna.ui.jogl; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.List; import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.fixedfunc.GLMatrixFunc; import org.archstudio.bna.IBNAModel; import org.archstudio.bna.IBNAView; import org.archstudio.bna.IThing; import org.archstudio.bna.IThingPeer; import org.archstudio.bna.constants.Antialias; import org.archstudio.bna.facets.IHasHidden; import org.archstudio.bna.ui.IUIResources; import org.archstudio.bna.ui.jogl.utils.GL2ES2Program; import org.archstudio.bna.ui.jogl.utils.GL2ES2Shader; import org.archstudio.bna.ui.utils.AbstractUIResources; import org.archstudio.bna.utils.BNAUtils; import org.archstudio.swtutils.constants.LineStyle; import org.archstudio.sysutils.ExpandableFloatBuffer; import org.archstudio.sysutils.Matrix; import org.archstudio.sysutils.SystemUtils; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import com.google.common.collect.Lists; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.FBObject.RenderAttachment; import com.jogamp.opengl.FBObject.TextureAttachment; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureCoords; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.awt.AWTTextureData; import com.jogamp.opengl.util.texture.awt.AWTTextureIO; public class JOGLResources extends AbstractUIResources implements IJOGLResources { private final GL2ES2 gl; private Rectangle localBounds = new Rectangle(0, 0, 0, 0); private final PMVMatrix matrix = new PMVMatrix(); private final List<float[]> projectionMatrixStack = Lists.newArrayList(); private final List<float[]> modelViewMatrixStack = Lists.newArrayList(); private final List<float[]> textureMatrixStack = Lists.newArrayList(); PMVMatrix textMatrix = new PMVMatrix(); private GL2ES2Shader fill2dVP; private GL2ES2Shader fill2dFP; private GL2ES2Program fill2dP; private GL2ES2Shader fill3dVP; private GL2ES2Shader fill3dFP; private GL2ES2Program fill3dP; private GL2ES2Shader line2dVP; private GL2ES2Shader line2dFP; private GL2ES2Program line2dP; private GL2ES2Shader line3dVP; private GL2ES2Shader line3dFP; private GL2ES2Program line3dP; private GL2ES2Shader textVP; private GL2ES2Shader textFP; private GL2ES2Program textP; private BufferedImage bufferedImage; private TextureData textureData; private Texture texture; public JOGLResources(GL2ES2 gl) { this.gl = gl; fill2dVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, JOGLResources.class.getResource("glsl/fill2d.vp")); fill2dFP = GL2ES2Shader .create(gl, GL2ES2.GL_FRAGMENT_SHADER, JOGLResources.class.getResource("glsl/fill2d.fp")); fill2dP = GL2ES2Program.create(gl, fill2dVP, fill2dFP); fill2dP.bindAttribute("attribute_position", 1); fill2dP.bindAttribute("attribute_color", 1); fill2dP.link(); fill3dVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, JOGLResources.class.getResource("glsl/fill3d.vp")); fill3dFP = GL2ES2Shader .create(gl, GL2ES2.GL_FRAGMENT_SHADER, JOGLResources.class.getResource("glsl/fill3d.fp")); fill3dP = GL2ES2Program.create(gl, fill3dVP, fill3dFP); fill3dP.bindAttribute("attribute_position", 1); fill3dP.bindAttribute("attribute_color", 1); fill3dP.link(); line2dVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, JOGLResources.class.getResource("glsl/line2d.vp")); line2dFP = GL2ES2Shader .create(gl, GL2ES2.GL_FRAGMENT_SHADER, JOGLResources.class.getResource("glsl/line2d.fp")); line2dP = GL2ES2Program.create(gl, line2dVP, line2dFP); line2dP.bindAttribute("attribute_position", 1); line2dP.bindAttribute("attribute_stipple_offset", 1); line2dP.link(); line3dVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, JOGLResources.class.getResource("glsl/line3d.vp")); line3dFP = GL2ES2Shader .create(gl, GL2ES2.GL_FRAGMENT_SHADER, JOGLResources.class.getResource("glsl/line3d.fp")); line3dP = GL2ES2Program.create(gl, line3dVP, line3dFP); line3dP.bindAttribute("attribute_position", 1); line3dP.bindAttribute("attribute_color", 1); line3dP.bindAttribute("attribute_stipple_offset", 1); line3dP.link(); textVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, JOGLResources.class.getResource("glsl/text.vp")); textFP = GL2ES2Shader.create(gl, GL2ES2.GL_FRAGMENT_SHADER, JOGLResources.class.getResource("glsl/text.fp")); textP = GL2ES2Program.create(gl, textVP, textFP); textP.bindAttribute("attribute_position", 1); textP.bindAttribute("attribute_texture_position", 1); textP.link(); } @Override public void dispose() { SystemUtils.dispose(fill2dP, fill2dVP, fill2dFP); fill2dP = null; fill2dVP = fill2dFP = null; SystemUtils.dispose(fill3dP, fill3dVP, fill3dFP); fill3dP = null; fill3dVP = fill3dFP = null; SystemUtils.dispose(line2dP, line2dVP, line2dFP); line2dP = null; line2dVP = line2dFP = null; SystemUtils.dispose(line3dP, line3dVP, line3dFP); line3dP = null; line3dVP = line3dFP = null; SystemUtils.dispose(textP, textVP, textFP); textP = null; textVP = textFP = null; bufferedImage = null; if (textureData != null) { textureData.destroy(); textureData = null; } if (texture != null) { texture.destroy(gl); texture = null; } super.dispose(); } public void setLocalBounds(Rectangle localBounds) { this.localBounds = localBounds; } Shape lastShape = null; int lastPoints; float lastMinY, lastMaxY; ExpandableFloatBuffer lastXYFloatBuffer = new ExpandableFloatBuffer(); ExpandableFloatBuffer lastOffsetFloatBuffer = new ExpandableFloatBuffer(); private void updateLastShape(Shape localShape) { if (lastShape != localShape) { lastShape = localShape; // determine min y, max y Rectangle2D bounds = localShape.getBounds2D(); lastMinY = (float) bounds.getMinY(); lastMaxY = (float) bounds.getMaxY(); // get xy positions, resize colors and offsets lastXYFloatBuffer.rewind(); BNAUtils.toXYFloatBuffer(localShape, lastXYFloatBuffer); lastPoints = lastXYFloatBuffer.position() / 2; // update offsets float[] xy = new float[2]; float lastX = 0, lastY = 0; float offset = 0; lastXYFloatBuffer.rewind(); lastOffsetFloatBuffer.rewind(); for (int i = 0; i < lastPoints; i++) { lastXYFloatBuffer.get(xy, 0, 2); if (i > 0) { offset += (float) Point2D.distance(xy[0], xy[1], lastX, lastY); } lastOffsetFloatBuffer.put(offset); lastX = xy[0]; lastY = xy[1]; } } } @Override public void setAntialiasGraphics(boolean antialiasGraphics) { if (antialiasGraphics) { gl.glEnable(GL.GL_MULTISAMPLE); gl.glEnable(GL.GL_LINE_SMOOTH); gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST); } else { gl.glDisable(GL.GL_MULTISAMPLE); gl.glDisable(GL.GL_LINE_SMOOTH); } super.setAntialiasGraphics(antialiasGraphics); } public void renderInit() { gl.glDisable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); } public void renderReshape(Rectangle localBounds) { matrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); matrix.glLoadIdentity(); matrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); matrix.glLoadIdentity(); matrix.glOrthof(localBounds.x, localBounds.x + localBounds.width, localBounds.y + localBounds.height, localBounds.y, -10000, 10000); textMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); textMatrix.glLoadIdentity(); textMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); textMatrix.glLoadIdentity(); textMatrix.glOrthof(localBounds.x, localBounds.x + localBounds.width, localBounds.y + localBounds.height, localBounds.y, -10000, 10000); gl.glDepthRange(-1000, 1000); } @Override public void renderTopLevelThings(IBNAView view, Rectangle localBounds) { gl.glClearColor(1, 1, 1, 0); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT); renderThings(view, localBounds); } @Override public void renderThings(IBNAView view, Rectangle localBounds) { IBNAModel model = view.getBNAWorld().getBNAModel(); for (IThing thingToRender : model.getAllThings()) { if (thingToRender.has(IHasHidden.HIDDEN_KEY, true)) { continue; } try { IThingPeer<?> peer = view.getThingPeer(thingToRender); if (peer.draw(localBounds, this)) { peer.draw(gl, localBounds, this); } } catch (Exception e) { e.printStackTrace(); } } } @Override public void drawText(Font font, String text, double x, double y, RGB color, double alpha) { if (color == null || text.length() == 0 || alpha == 0) { return; } // determine what text area needs to be rendered Dimension textSize = getTextSize(font, text); if (textSize.width <= 0 || textSize.height <= 0) { // the empty string will have a width of 0 return; } // get a transform to the local screen coordinates AffineTransform transform = getLocalTransform(); // determine the actual area we need to draw IUIResources.FontMetrics metrics = getFontMetrics(font); Path2D.Double textShape = new Path2D.Double(new Rectangle2D.Double(x, y, textSize.width, textSize.height)); textShape.transform(transform); Rectangle textBounds = BNAUtils.toRectangle(textShape.getBounds2D()); textBounds.intersect(localBounds); if (textBounds.width <= 0 || textBounds.height <= 0) { return; } // create a buffered image to back the text if (bufferedImage == null || bufferedImage.getWidth() < textBounds.width || bufferedImage.getHeight() < textBounds.height) { bufferedImage = new BufferedImage(textBounds.width + 2, textBounds.height + 2, BufferedImage.TYPE_INT_RGB); } // create a texture to display the text if (texture == null || texture.getImageWidth() != bufferedImage.getWidth() || texture.getImageHeight() != bufferedImage.getHeight()) { if (texture != null) { textureData.destroy(); texture.destroy(gl); } textureData = new AWTTextureData(gl.getGLProfile(), 0, 0, false, bufferedImage); texture = AWTTextureIO.newTexture(gl.getGLProfile(), bufferedImage, false); texture.setTexParameteri(gl, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); texture.setTexParameteri(gl, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); } // draw text on the buffered image Graphics2D g2d = bufferedImage.createGraphics(); Point2D xy = transform.transform(new Point2D.Double(x, y), null); try { g2d.setColor(Color.BLACK); g2d.fillRect(0, 0, textBounds.width, textBounds.height); g2d.setColor(Color.WHITE); if (isAntialiasText()) { g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); } else { g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); } g2d.setFont(font); g2d.translate(xy.getX() - textBounds.x, xy.getY() - textBounds.y); g2d.rotate(-getLocalRotation()); g2d.drawString(text, 0, metrics.getLeading() + metrics.getAscent()); } finally { g2d.dispose(); } // update the text area of the texture texture.updateSubImage(gl, textureData, 0, 0, 0, 0, 0, textBounds.width + 1, textBounds.height + 1); // draw the texture TextureCoords coords = texture.getSubImageTexCoords(0, bufferedImage.getHeight() - textBounds.height, textBounds.width, bufferedImage.getHeight()); FloatBuffer texture_coords = Buffers.newDirectFloatBuffer(new float[] {// // coords.right(), coords.top(),// coords.left(), coords.top(),// coords.left(), coords.bottom(),// coords.right(), coords.bottom() }); FloatBuffer vertices = Buffers.newDirectFloatBuffer(new float[] {// // (float) textBounds.x + textBounds.width, textBounds.y,// textBounds.x, textBounds.y,// textBounds.x, (float) textBounds.y + textBounds.height,// (float) textBounds.x + textBounds.width, (float) textBounds.y + textBounds.height }); pushBlendFunction(); gl.glBlendFuncSeparate(GL2ES2.GL_CONSTANT_COLOR, GL.GL_ONE_MINUS_SRC_COLOR, GL2ES2.GL_CONSTANT_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glBlendEquationSeparate(GL.GL_FUNC_ADD, GL.GL_FUNC_ADD); gl.glBlendColor(color.red / 255f, color.green / 255f, color.blue / 255f, (float) alpha); texture.enable(gl); try { textP.use(); try { gl.glUniformMatrix4fv(textP.getUniform("uniform_projection"), 1, false, textMatrix.glGetMatrixf()); gl.glUniform1i(textP.getUniform("uniform_texture"), 0); gl.glActiveTexture(GL.GL_TEXTURE0 + 0); texture.bind(gl); vertices.rewind(); textP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", 4, vertices, GL.GL_STATIC_DRAW, 2, false); texture_coords.rewind(); textP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_texture_position", 4, texture_coords, GL.GL_STATIC_DRAW, 2, false); gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0, 4); } finally { textP.done(); } } finally { popBlendFunction(); texture.disable(gl); } } @Override public void drawShape(Point2D localShape, RGB color, double alpha) { if (color == null || alpha == 0) { return; } int stipple = 0xFFFFFFFF; FloatBuffer lastXYFloatBuffer = Buffers.newDirectFloatBuffer(2); lastXYFloatBuffer.put((float) localShape.getX()); lastXYFloatBuffer.put((float) localShape.getY()); FloatBuffer lastOffsetFloatBuffer = Buffers.newDirectFloatBuffer(1); lastOffsetFloatBuffer.put(0f); int lastPoints = 1; line2dP.use(); try { matrix.glTranslatef(0.5f, 0.5f, 0); gl.glUniformMatrix4fv(line2dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); gl.glUniform4f(line2dP.getUniform("uniform_rgba"), color.red / 255f, color.green / 255f, color.blue / 255f, (float) (getGlobalAlpha() * alpha)); gl.glUniform1i(line2dP.getUniform("uniform_stipple"), stipple); lastXYFloatBuffer.rewind(); line2dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", lastPoints, lastXYFloatBuffer, GL.GL_STATIC_DRAW, 2, false); lastOffsetFloatBuffer.rewind(); line2dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_stipple_offset", lastPoints, lastOffsetFloatBuffer, GL.GL_STATIC_DRAW, 1, false); gl.glDrawArrays(GL.GL_LINE_STRIP, 0, lastPoints); matrix.glTranslatef(-0.5f, -0.5f, 0); } finally { line2dP.done(); } } @Override public void drawShape(Shape localShape, RGB color, int width, LineStyle style, double alpha) { drawShape(localShape, color, width, style.toBitPattern(), alpha); } @Override public void drawShape(Shape localShape, RGB color, int width, int stipple, double alpha) { if (color == null || stipple == 0 || alpha == 0) { return; } updateLastShape(localShape); pushBlendFunction(); gl.glBlendFuncSeparate(GL2ES2.GL_CONSTANT_COLOR, GL.GL_ONE_MINUS_SRC_COLOR, GL2ES2.GL_CONSTANT_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glBlendEquationSeparate(GL.GL_FUNC_ADD, GL.GL_FUNC_ADD); gl.glBlendColor(color.red / 255f, color.green / 255f, color.blue / 255f, getGlobalAlpha() * (float) alpha); gl.glLineWidth(width + 2); line2dP.use(); try { matrix.glTranslatef(0.5f, 0.5f, 0); gl.glUniformMatrix4fv(line2dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); gl.glUniform2fv(line2dP.getUniform("uniform_rgb_offsets"), 1, Antialias.HRGB.getSubpixelRGBXYDeltas(), 0); gl.glUniform4f(line2dP.getUniform("uniform_screen_dimension"), localBounds.width, localBounds.height, 1, 1); gl.glUniform1i(line2dP.getUniform("uniform_stipple"), stipple); gl.glUniform1f(line2dP.getUniform("uniform_width"), width); lastXYFloatBuffer.rewind(); line2dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", lastPoints, lastXYFloatBuffer.getBackingBuffer(), GL.GL_STATIC_DRAW, 2, false); lastOffsetFloatBuffer.rewind(); line2dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_stipple_offset", lastPoints, lastOffsetFloatBuffer.getBackingBuffer(), GL.GL_STATIC_DRAW, 1, false); gl.glDrawArrays(GL.GL_LINE_STRIP, 0, lastPoints); matrix.glTranslatef(-0.5f, -0.5f, 0); } finally { popBlendFunction(); line2dP.done(); } } @Override public void glowShape(Shape localShape, RGB color, int width, double alpha) { if (color == null || width == 0 || alpha == 0) { return; } updateLastShape(localShape); float[] xy = new float[lastPoints * 2]; lastXYFloatBuffer.rewind(); lastXYFloatBuffer.get(xy); boolean closed = BNAUtils.realEq(xy[0], xy[xy.length - 2]) && BNAUtils.realEq(xy[1], xy[xy.length - 1]); renderGlowSide(xy, false, color, width, alpha, closed); renderGlowSide(xy, true, color, width, alpha, closed); } protected void renderGlowSide(float[] xy, boolean reverse, RGB color, int width, double alpha, boolean closed) { int numberOfPoints = xy.length / 2 * 4 + 2; FloatBuffer vertices = Buffers.newDirectFloatBuffer(2 * numberOfPoints); FloatBuffer colors = Buffers.newDirectFloatBuffer(4 * numberOfPoints); float[] rgbArgb0 = new float[] { // // color.red / 255f, color.green / 255f, color.blue / 255f, (float) alpha, // color.red / 255f, color.green / 255f, color.blue / 255f, 0 }; int closesI = 0; float closesXe = 0; float closesYe = 0; boolean first = true; numberOfPoints = 0; for (int i = 2; i < xy.length; i += 2) { int i0 = !reverse ? i - 2 : xy.length - i; int i1 = !reverse ? i - 0 : xy.length - i - 2; double angle = -Math.atan2(xy[i1 + 1] - xy[i0 + 1], xy[i1 + 0] - xy[i0 + 0]); float sinAngle = (float) Math.sin(angle); float cosAngle = (float) Math.cos(angle); float p0Xe = xy[i0 + 0] + width * sinAngle / 2; float p0Ye = xy[i0 + 1] + width * cosAngle / 2; float p1Xe = xy[i1 + 0] + width * sinAngle / 2; float p1Ye = xy[i1 + 1] + width * cosAngle / 2; numberOfPoints++; vertices.put(xy, i0, 2); numberOfPoints++; vertices.put(p0Xe); vertices.put(p0Ye); colors.put(rgbArgb0); if (first) { first = false; closesI = i0; closesXe = p0Xe; closesYe = p0Ye; } numberOfPoints++; vertices.put(xy, i1, 2); numberOfPoints++; vertices.put(p1Xe); vertices.put(p1Ye); colors.put(rgbArgb0); } if (closed) { numberOfPoints++; vertices.put(xy, closesI, 2); numberOfPoints++; vertices.put(closesXe); vertices.put(closesYe); colors.put(rgbArgb0); } fill3dP.use(); try { gl.glUniformMatrix4fv(fill3dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); vertices.rewind(); fill3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", numberOfPoints, vertices, GL.GL_STATIC_DRAW, 2, false); colors.rewind(); fill3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_color", numberOfPoints, colors, GL.GL_STATIC_DRAW, 4, false); gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, numberOfPoints); } finally { fill3dP.done(); } } @Override public void selectShape(Shape localShape, int offset) { drawShape(localShape, new RGB(0, 0, 0), 1, 0xFFFFFFFF, 1); drawShape(localShape, new RGB(255, 255, 255), 1, 0xF0F0F0F0 >> offset * 2 % 8, 1); } @Override public void fillShape(Shape localShape, RGB color1, RGB color2, double alpha) { if (color1 == null || alpha == 0) { return; } color2 = color2 != null && isDecorativeGraphics() ? color2 : color1; updateLastShape(localShape); fill2dP.use(); try { gl.glUniformMatrix4fv(fill2dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); gl.glUniform1f(fill2dP.getUniform("uniform_y1"), lastMinY); gl.glUniform4f(fill2dP.getUniform("uniform_y1_rgba"), color1.red / 255f, color1.green / 255f, color1.blue / 255f, (float) (getGlobalAlpha() * alpha)); gl.glUniform1f(fill2dP.getUniform("uniform_y2"), lastMaxY); gl.glUniform4f(fill2dP.getUniform("uniform_y2_rgba"), color2.red / 255f, color2.green / 255f, color2.blue / 255f, (float) (getGlobalAlpha() * alpha)); lastXYFloatBuffer.rewind(); fill2dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", lastPoints, lastXYFloatBuffer.getBackingBuffer(), GL.GL_STATIC_DRAW, 2, false); gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0, lastPoints); } finally { fill2dP.done(); } } @Override public void fillShape(int mode, FloatBuffer xyzVertices, FloatBuffer rgbaColors, int numberOfPoints) { fill3dP.use(); try { gl.glUniformMatrix4fv(fill3dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); fill3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", numberOfPoints, xyzVertices, GL.GL_STATIC_DRAW, 3, false); fill3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_color", numberOfPoints, rgbaColors, GL.GL_STATIC_DRAW, 4, false); gl.glDrawArrays(mode, 0, numberOfPoints); } finally { fill3dP.done(); } } @Override public void drawShape(int mode, FloatBuffer xyzVertices, FloatBuffer rgbaColors, int numberOfPoints, int stipple) { // get projection matrix float[] matrixValues = new float[16]; matrix.glGetFloatv(GLMatrixFunc.GL_PROJECTION, matrixValues, 0); Matrix projection = Matrix.newColumnMajorMatrix(4, matrixValues); // update offsets float[] xyzw = new float[4]; xyzw[3] = 1; float lastX = 0, lastY = 0; float offset = 0; xyzVertices.mark(); FloatBuffer offsets = Buffers.newDirectFloatBuffer(numberOfPoints); for (int i = 0; i < numberOfPoints; i++) { xyzVertices.get(xyzw, 0, 3); Matrix p1 = projection.product(Matrix.newRowMajorMatrix(1, xyzw)); float nextX = localBounds.width / 2f * (float) p1.get(0, 0); float nextY = localBounds.height / 2f * (float) p1.get(0, 1); if (i > 0) { offset += (float) Point2D.distance(lastX, lastY, nextX, nextY); } if (mode == GL.GL_LINES && i % 2 == 0) { offset = 0; } offsets.put(offset); lastX = nextX; lastY = nextY; } line3dP.use(); try { gl.glUniformMatrix4fv(line3dP.getUniform("uniform_projection"), 1, false, matrix.glGetMatrixf()); gl.glUniform1i(line3dP.getUniform("uniform_stipple"), stipple); xyzVertices.reset(); line3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", numberOfPoints, xyzVertices, GL.GL_STATIC_DRAW, 3, false); line3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_color", numberOfPoints, rgbaColors, GL.GL_STATIC_DRAW, 4, false); offsets.rewind(); line3dP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_stipple_offset", numberOfPoints, offsets, GL.GL_STATIC_DRAW, 1, false); gl.glDrawArrays(mode, 0, numberOfPoints); } finally { line3dP.done(); } } @Override public void pushMatrix(double x, double y, double angle) { int oldMatrixMode = matrix.glGetMatrixMode(); pushMatrix(GLMatrixFunc.GL_MODELVIEW); pushMatrix(GLMatrixFunc.GL_PROJECTION); pushMatrix(GL.GL_TEXTURE); matrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); matrix.glTranslatef((float) x, (float) y, 0); matrix.glRotatef((float) (angle / Math.PI * 180), 0, 0, 1); matrix.glMatrixMode(oldMatrixMode); } @Override public void popMatrix() { int oldMatrixMode = matrix.glGetMatrixMode(); popMatrix(GLMatrixFunc.GL_MODELVIEW); popMatrix(GLMatrixFunc.GL_PROJECTION); popMatrix(GL.GL_TEXTURE); matrix.glMatrixMode(oldMatrixMode); } public BufferedImage renderToImage(final IBNAView view, final Rectangle lbb) { FBObject fbObject = null; RenderAttachment renderAttachment = null; TextureAttachment texture0Attachment = null; try { // create framebuffer fbObject = new FBObject(); fbObject.reset(gl, lbb.width, lbb.height); fbObject.bind(gl); // create and bind renderbuffers fbObject.attachRenderbuffer(gl, GL.GL_DEPTH_COMPONENT16); renderAttachment = fbObject.getDepthAttachment(); renderAttachment.initialize(gl); // create and bind texture 0 texture0Attachment = fbObject.attachTexture2D(gl, 0, true); texture0Attachment.initialize(gl); // clear the background gl.glClearColor(0, 0, 0, 0); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT); // ... draw things pushMatrix(-lbb.x, -lbb.y, 0); try { renderReshape(new Rectangle(0, 0, lbb.width, lbb.height)); renderThings(view, new Rectangle(0, 0, lbb.width, lbb.height)); } finally { popMatrix(); } // read texture contents ByteBuffer byteBuffer = ByteBuffer.allocate(lbb.width * lbb.height * 4); gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1); gl.glReadPixels(0, 0, lbb.width, lbb.height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, byteBuffer); // create the image BufferedImage image = new BufferedImage(lbb.width, lbb.height, BufferedImage.TYPE_4BYTE_ABGR); int bufferIndex = 0; for (int y = lbb.height - 1; y >= 0; y--) { for (int x = 0; x < lbb.width; x++) { int r = byteBuffer.get(bufferIndex++); int g = byteBuffer.get(bufferIndex++); int b = byteBuffer.get(bufferIndex++); int a = byteBuffer.get(bufferIndex++); int i = (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | b & 0xFF; image.setRGB(x, y, i); } } return image; } finally { if (texture0Attachment != null) { texture0Attachment.free(gl); } if (renderAttachment != null) { renderAttachment.free(gl); } if (fbObject != null) { fbObject.unbind(gl); fbObject.destroy(gl); } } } @Override public PMVMatrix getMatrix() { return matrix; } private AffineTransform getLocalTransform() { float[] values = new float[16]; matrix.glGetFloatv(GLMatrixFunc.GL_PROJECTION, values, 0); AffineTransform transform = new AffineTransform(values[0], values[1], values[4], values[5], values[12], values[13]); transform.preConcatenate(AffineTransform.getScaleInstance(localBounds.width / 2, -localBounds.height / 2)); transform.preConcatenate(AffineTransform.getTranslateInstance(localBounds.width / 2, localBounds.height / 2)); return transform; } private double getLocalRotation() { AffineTransform transform = getLocalTransform(); double[] matrix = new double[6]; transform.getMatrix(matrix); double theta = Math.atan(matrix[2] / matrix[0]); if (matrix[0] < 0) { theta += Math.PI; } return theta; } @Override public void pushMatrix(int matrixName) { List<float[]> stack; switch (matrixName) { case GLMatrixFunc.GL_MODELVIEW: stack = modelViewMatrixStack; break; case GLMatrixFunc.GL_PROJECTION: stack = projectionMatrixStack; break; case GL.GL_TEXTURE: stack = textureMatrixStack; break; default: throw new IllegalArgumentException("Unrecognized matrix name: " + matrixName); } float[] values = new float[16]; matrix.glMatrixMode(matrixName); matrix.glGetFloatv(matrixName, values, 0); stack.add(values); } @Override public void popMatrix(int matrixName) { List<float[]> stack; switch (matrixName) { case GLMatrixFunc.GL_MODELVIEW: stack = modelViewMatrixStack; break; case GLMatrixFunc.GL_PROJECTION: stack = projectionMatrixStack; break; case GL.GL_TEXTURE: stack = textureMatrixStack; break; default: throw new IllegalArgumentException("Unrecognized matrix name: " + matrixName); } float[] values = stack.remove(stack.size() - 1); matrix.glMatrixMode(matrixName); matrix.glLoadMatrixf(values, 0); } private final List<List<Object>> blendStack = Lists.newArrayList(); @Override public void pushBlendFunction() { boolean isEnabled = gl.glIsEnabled(GL.GL_BLEND); int[] settings = new int[6]; float[] color = new float[4]; gl.glGetIntegerv(GL.GL_BLEND_SRC_RGB, settings, 0); gl.glGetIntegerv(GL.GL_BLEND_DST_RGB, settings, 1); gl.glGetIntegerv(GL.GL_BLEND_SRC_ALPHA, settings, 2); gl.glGetIntegerv(GL.GL_BLEND_DST_ALPHA, settings, 3); gl.glGetIntegerv(GL.GL_BLEND_EQUATION_RGB, settings, 4); gl.glGetIntegerv(GL.GL_BLEND_EQUATION_ALPHA, settings, 5); gl.glGetFloatv(GL2ES2.GL_BLEND_COLOR, color, 0); blendStack.add(Lists.<Object> newArrayList(isEnabled, settings, color)); } @Override public void popBlendFunction() { List<Object> values = blendStack.remove(blendStack.size() - 1); boolean isEnabled = (Boolean) values.get(0); int[] settings = (int[]) values.get(1); float[] color = (float[]) values.get(2); if (isEnabled) { gl.glEnable(GL.GL_BLEND); } else { gl.glDisable(GL.GL_BLEND); } gl.glBlendFuncSeparate(settings[0], settings[1], settings[2], settings[3]); gl.glBlendEquationSeparate(settings[4], settings[5]); gl.glBlendColor(color[0], color[1], color[2], color[3]); } }