package org.archstudio.bna.things.utility; import java.nio.FloatBuffer; import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL2ES3; import org.archstudio.bna.IBNAView; import org.archstudio.bna.ICoordinate; import org.archstudio.bna.ICoordinateMapper; import org.archstudio.bna.IThing; import org.archstudio.bna.IThingPeer; import org.archstudio.bna.facets.IHasBoundingBox; import org.archstudio.bna.facets.peers.IHasLocalBounds; import org.archstudio.bna.facets.peers.IHasShadowPeer; import org.archstudio.bna.things.AbstractThingPeer; import org.archstudio.bna.ui.IUIResources; import org.archstudio.bna.ui.jogl.IJOGLResources; import org.archstudio.bna.ui.jogl.utils.GL2ES2Program; import org.archstudio.bna.ui.jogl.utils.GL2ES2Shader; import org.archstudio.bna.ui.swt.ISWTResources; import org.archstudio.sysutils.SystemUtils; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.FBObject.RenderAttachment; import com.jogamp.opengl.FBObject.TextureAttachment; public class ShadowThingPeer<T extends ShadowThing> extends AbstractThingPeer<T> implements IHasLocalBounds { public ShadowThingPeer(T thing, IBNAView view, ICoordinateMapper cm) { super(thing, view, cm); } int shadowSize; int shadowOffset; double shadowAlpha; private GL2ES2Shader blurVP; private GL2ES2Shader blurFP; private GL2ES2Program blurP; private void updateShadowData() { shadowSize = Math.min(SystemUtils.round(6 * cm.getLocalScale()), 5); shadowOffset = Math.min(SystemUtils.round(6 * cm.getLocalScale()), shadowSize); shadowAlpha = 0.4; } @Override public void dispose() { if (blurP != null) { blurP.dispose(); blurP = null; } if (blurVP != null) { blurVP.dispose(); blurVP = null; } if (blurFP != null) { blurFP.dispose(); blurFP = null; } super.dispose(); } @Override public Rectangle getLocalBounds() { Rectangle lbb = null; updateShadowData(); for (IThing t : model.getAllThings()) { IThingPeer<?> tp = view.getThingPeer(t); if (tp instanceof IHasShadowPeer && t instanceof IHasBoundingBox) { Rectangle tLBB = cm.worldToLocal(((IHasBoundingBox) t).getBoundingBox()); tLBB.width += shadowOffset + shadowSize; tLBB.height += shadowOffset + shadowSize; if (lbb == null) { lbb = tLBB; } else { lbb = lbb.union(tLBB); } } } return lbb; } @Override public boolean draw(Rectangle localBounds, IUIResources r) { // only draw for the top level things if (view.getParentView() != null) { return false; } if (!r.isDisplayShadows()) { return false; } updateShadowData(); return true; } @Override public void draw(GL2ES2 gl, Rectangle localBounds, IJOGLResources r) { // load blur shader if (blurVP == null) { blurVP = GL2ES2Shader.create(gl, GL2ES2.GL_VERTEX_SHADER, // ShadowThingPeer.class.getResource("glsl/blur.vp")); } if (blurFP == null) { blurFP = GL2ES2Shader.create(gl, GL2ES2.GL_FRAGMENT_SHADER, // ShadowThingPeer.class.getResource("glsl/blur.fp")); } if (blurP == null) { blurP = GL2ES2Program.create(gl, blurVP, blurFP); blurP.bindAttribute("attribute_position", 1); blurP.bindAttribute("attribute_texture_position", 1); blurP.link(); } // note current FBO binding to restore later on int[] currentFrameBufferBindings = new int[3]; gl.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, currentFrameBufferBindings, 0); if (gl.hasFullFBOSupport()) { currentFrameBufferBindings[1] = gl.getDefaultDrawFramebuffer(); currentFrameBufferBindings[2] = gl.getDefaultReadFramebuffer(); } FBObject fbObject = null; RenderAttachment renderAttachment = null; TextureAttachment texture0Attachment = null; TextureAttachment texture1Attachment = null; try { // create vertices FloatBuffer vertices = Buffers.newDirectFloatBuffer(8); vertices.put(localBounds.x); vertices.put(localBounds.y); vertices.put(localBounds.x); vertices.put(localBounds.y + localBounds.height); vertices.put(localBounds.x + localBounds.width); vertices.put(localBounds.y + localBounds.height); vertices.put(localBounds.x + localBounds.width); vertices.put(localBounds.y); // create framebuffer fbObject = new FBObject(); fbObject.reset(gl, localBounds.width, localBounds.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 shadows r.pushMatrix(shadowOffset, shadowOffset, 0); try { Rectangle newLocalBounds = new Rectangle(localBounds.x - shadowOffset, localBounds.y - shadowOffset, localBounds.width, localBounds.height); for (IThing t : model.getAllThings()) { IThingPeer<?> tp = view.getThingPeer(t); if (tp instanceof IHasShadowPeer) { IHasShadowPeer<?> stp = (IHasShadowPeer<?>) tp; if (stp.drawShadow(newLocalBounds, r)) { stp.drawShadow(gl, newLocalBounds, r); } } } } finally { r.popMatrix(); } // disable blending so that we simply blur the existing texture to the new one r.pushBlendFunction(); gl.glDisable(GL.GL_BLEND); try { // ... apply blur blurP.use(); try { gl.glUniformMatrix4fv(blurP.getUniform("uniform_projection"), 1, false, r.getMatrix() .glGetMatrixf()); gl.glUniform1i(blurP.getUniform("uniform_texture"), 0); gl.glActiveTexture(GL.GL_TEXTURE0); gl.glBindTexture(GL.GL_TEXTURE_2D, texture0Attachment.getName()); gl.glUniform2f(blurP.getUniform("uniform_resolution"), localBounds.width, localBounds.height); gl.glUniform1i(blurP.getUniform("uniform_size"), shadowSize); gl.glUniform2f(blurP.getUniform("uniform_direction"), 1, 0); gl.glUniform1f(blurP.getUniform("uniform_alpha"), 1); vertices.rewind(); blurP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", 4, vertices, GL.GL_STATIC_DRAW, 2, false); gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0, 4); } finally { blurP.done(); } gl.glFlush(); } finally { // restore blending r.popBlendFunction(); } // draw a blur of texture 1 on the main buffer ... gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, currentFrameBufferBindings[0]); if (gl.hasFullFBOSupport()) { gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, currentFrameBufferBindings[1]); gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, currentFrameBufferBindings[2]); } // ... apply blur blurP.use(); try { gl.glUniformMatrix4fv(blurP.getUniform("uniform_projection"), 1, false, r.getMatrix().glGetMatrixf()); gl.glUniform1i(blurP.getUniform("uniform_texture"), 0); gl.glActiveTexture(GL.GL_TEXTURE0); gl.glBindTexture(GL.GL_TEXTURE_2D, texture0Attachment.getName()); gl.glUniform2f(blurP.getUniform("uniform_resolution"), localBounds.width, localBounds.height); gl.glUniform1i(blurP.getUniform("uniform_size"), shadowSize); gl.glUniform2f(blurP.getUniform("uniform_direction"), 0, 1); gl.glUniform1f(blurP.getUniform("uniform_alpha"), (float) shadowAlpha); vertices.rewind(); blurP.bindBufferData(GL.GL_ARRAY_BUFFER, "attribute_position", 4, vertices, GL.GL_STATIC_DRAW, 2, false); gl.glDrawArrays(GL.GL_TRIANGLE_FAN, 0, 4); } finally { blurP.done(); } } finally { if (texture1Attachment != null) { texture1Attachment.free(gl); texture1Attachment = null; } if (texture0Attachment != null) { texture0Attachment.free(gl); texture0Attachment = null; } if (renderAttachment != null) { renderAttachment.free(gl); renderAttachment = null; } if (fbObject != null) { fbObject.unbind(gl); fbObject.destroy(gl); fbObject = null; } gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, currentFrameBufferBindings[0]); if (gl.hasFullFBOSupport()) { gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, currentFrameBufferBindings[1]); gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, currentFrameBufferBindings[2]); } } } @Override public void draw(GC gc, Rectangle localBounds, ISWTResources r) { int shadowSize = this.shadowSize + this.shadowOffset; int offsetStep = Math.max(1, (shadowSize + 2) / 5); double cumulativeAlpha = 0; for (int offset = shadowSize; offset > 0; offset -= offsetStep) { double alpha = (float) Math.exp((float) -offset * offset / shadowSize / shadowSize) * shadowAlpha; alpha = SystemUtils.bound(0, alpha * 2 - cumulativeAlpha, 1); cumulativeAlpha += alpha; int shadowOffset = shadowSize; int translateOffset = shadowSize - offset; r.pushMatrix(translateOffset, translateOffset, 0); r.pushAlpha(alpha); try { Rectangle newLocalBounds = new Rectangle(localBounds.x + shadowOffset, localBounds.y + shadowOffset, localBounds.width, localBounds.height); for (IThing t : model.getAllThings()) { IThingPeer<?> tp = view.getThingPeer(t); if (tp instanceof IHasShadowPeer) { IHasShadowPeer<?> stp = (IHasShadowPeer<?>) tp; if (stp.drawShadow(newLocalBounds, r)) { stp.drawShadow(gc, newLocalBounds, r); } } } } finally { r.popAlpha(); r.popMatrix(); } } } @Override public boolean isInThing(ICoordinate location) { return false; } }