├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .travis.yml ├── .gitignore ├── gradle.properties ├── settings.gradle ├── src └── main │ └── kotlin │ └── org │ └── anglur │ └── joglext │ ├── jogl2d │ ├── font │ │ └── BitmapFont.kt │ ├── impl │ │ ├── gl2 │ │ │ ├── GL2TesselatorVisitor.kt │ │ │ ├── LineDrawingVisitor.kt │ │ │ ├── FillSimpleConvexPolygonVisitor.kt │ │ │ ├── GL2ImageDrawer.kt │ │ │ ├── GL2ShapeDrawer.kt │ │ │ ├── GL2Transformhelper.kt │ │ │ ├── GL2ColorHelper.kt │ │ │ ├── GL2StringDrawer.kt │ │ │ └── FastLineVisitor.kt │ │ ├── GLGraphicsDevice.kt │ │ ├── GLGraphicsConfiguration.kt │ │ ├── AbstractMatrixHelper.kt │ │ ├── AbstractTextDrawer.kt │ │ ├── AbstractColorHelper.kt │ │ ├── SimplePathVisitor.kt │ │ ├── AbstractTesselatorVisitor.kt │ │ ├── AbstractShapeHelper.kt │ │ ├── SimpleOrTesselatingVisitor.kt │ │ └── AbstractImageHelper.kt │ ├── GLG2DTransformHelper.kt │ ├── GLG2DColorHelper.kt │ ├── GLG2DTextHelper.kt │ ├── shape │ │ ├── ShapeIterator.kt │ │ └── impl │ │ │ ├── LineIterator.kt │ │ │ ├── RectIterator.kt │ │ │ ├── EllipseIterator.kt │ │ │ ├── RoundRectIterator.kt │ │ │ └── ArcIterator.kt │ ├── GLG2DShapeHelper.kt │ ├── GLAwareRepaintManager.java │ ├── GLG2DUtils.kt │ ├── GLG2DImageHelper.kt │ ├── GLG2DRenderingHints.kt │ ├── G2DDrawingHelper.kt │ ├── GLG2DSimpleEventListener.java │ ├── VertexBuffer.kt │ ├── PathVisitor.kt │ ├── GLG2DCanvas.java │ └── GLGraphics2D.kt │ └── cacheable │ ├── CachedIntArray.kt │ ├── CachedFloatArray.kt │ └── CachedList.kt ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonatino/JOGL2D/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: java 3 | jdk: 4 | - oraclejdk8 5 | - oraclejdk9 6 | - oraclejdk11 7 | notifications: 8 | email: false 9 | before_install: 10 | - chmod +x gradlew -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea 8 | .gradle 9 | build 10 | 11 | ## File-based project format: 12 | *.iws 13 | *.7z 14 | 15 | ## Plugin-specific files: 16 | 17 | # IntelliJ 18 | /out/ 19 | 20 | # mpeltonen/sbt-idea plugin 21 | .idea_modules/ 22 | 23 | # JIRA plugin 24 | atlassian-ide-plugin.xml 25 | 26 | # Crashlytics plugin (for Android Studio and IntelliJ) 27 | com_crashlytics_export_strings.xml 28 | crashlytics.properties 29 | crashlytics-build.properties 30 | fabric.properties 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Jonathan Beaudoin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | kotlin.incremental=true 18 | org.gradle.daemon=true -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | rootProject.name = 'JOGLExt' 18 | 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016 Jonathan Beaudoin 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | distributionBase=GRADLE_USER_HOME 18 | distributionPath=wrapper/dists 19 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip 20 | zipStoreBase=GRADLE_USER_HOME 21 | zipStorePath=wrapper/dists 22 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/font/BitmapFont.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.font 18 | 19 | class BitmapFontRec(var name: String, 20 | var num_chars: Int, 21 | var first: Int, 22 | var ch: Array) 23 | 24 | 25 | class BitmapCharRec(var width: Int, 26 | var height: Int, 27 | var xorig: Float, 28 | var yorig: Float, 29 | var advance: Float, 30 | var bitmap: ByteArray?) -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2TesselatorVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import org.anglur.joglext.jogl2d.impl.AbstractTesselatorVisitor 23 | 24 | class GL2TesselatorVisitor : AbstractTesselatorVisitor() { 25 | 26 | private lateinit var gl: GL2 27 | 28 | override fun setGLContext(context: GL) { 29 | gl = context.gL2 30 | } 31 | 32 | override fun endTess() { 33 | vBuffer.drawBuffer(gl, drawMode) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DTransformHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.geom.AffineTransform 20 | 21 | interface GLG2DTransformHelper : G2DDrawingHelper { 22 | 23 | var transform: AffineTransform 24 | 25 | fun translate(x: Int, y: Int) 26 | 27 | fun translate(tx: Double, ty: Double) 28 | 29 | fun rotate(theta: Double) 30 | 31 | fun rotate(theta: Double, x: Double, y: Double) 32 | 33 | fun scale(sx: Double, sy: Double) 34 | 35 | fun shear(shx: Double, shy: Double) 36 | 37 | fun transform(Tx: AffineTransform) 38 | 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DColorHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.Color 20 | import java.awt.Composite 21 | import java.awt.Paint 22 | 23 | interface GLG2DColorHelper : G2DDrawingHelper { 24 | 25 | var composite: Composite 26 | 27 | var paint: Paint 28 | 29 | var color: Color 30 | 31 | var background: Color 32 | 33 | fun setColorNoRespectComposite(c: Color) 34 | 35 | fun setColorRespectComposite(c: Color) 36 | 37 | fun setPaintMode() 38 | 39 | fun setXORMode(c: Color) 40 | 41 | fun copyArea(x: Int, y: Int, width: Int, height: Int, dx: Int, dy: Int) 42 | 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/cacheable/CachedIntArray.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.cacheable 18 | 19 | import java.util.* 20 | 21 | object CachedIntArray { 22 | 23 | /** 24 | * The resource map cache, mapping size in bytes to memory. 25 | */ 26 | private val map = ThreadLocal.withInitial { HashMap(32) } 27 | 28 | /** 29 | * Returns a zeroed-out FloatArray of the specified size in bytes. 30 | * 31 | * @param size The desired amount of bytes of the FloatArray. 32 | */ 33 | private fun cached(size: Int) = map.get().getOrPut(size, { IntArray(size) })!! 34 | 35 | operator fun invoke(size: Int) = cached(size) 36 | 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/cacheable/CachedFloatArray.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.cacheable 18 | 19 | import java.util.* 20 | 21 | object CachedFloatArray { 22 | 23 | /** 24 | * The resource map cache, mapping size in bytes to memory. 25 | */ 26 | private val map = ThreadLocal.withInitial { HashMap(32) } 27 | 28 | /** 29 | * Returns a zeroed-out FloatArray of the specified size in bytes. 30 | * 31 | * @param size The desired amount of bytes of the FloatArray. 32 | */ 33 | private fun cached(size: Int) = map.get().getOrPut(size, { FloatArray(size) })!! 34 | 35 | operator fun invoke(size: Int) = cached(size) 36 | 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DTextHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.Font 20 | import java.awt.FontMetrics 21 | import java.awt.font.FontRenderContext 22 | import java.text.AttributedCharacterIterator 23 | 24 | interface GLG2DTextHelper : G2DDrawingHelper { 25 | 26 | var font: Font 27 | 28 | fun getFontMetrics(font: Font): FontMetrics 29 | 30 | val fontRenderContext: FontRenderContext 31 | 32 | fun drawString(iterator: AttributedCharacterIterator, x: Int, y: Int) 33 | 34 | fun drawString(iterator: AttributedCharacterIterator, x: Float, y: Float) 35 | 36 | fun drawString(string: String, x: Float, y: Float) 37 | 38 | fun drawString(string: String, x: Int, y: Int) 39 | 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/ShapeIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape 18 | 19 | import org.anglur.joglext.jogl2d.shape.impl.* 20 | import java.awt.Shape 21 | import java.awt.geom.* 22 | 23 | /** 24 | * Created by Jonathan on 9/30/2016. 25 | */ 26 | object ShapeIterator { 27 | 28 | fun get(shape: Shape): PathIterator { 29 | when (shape) { 30 | is Ellipse2D -> return EllipseIterator(shape) 31 | is RoundRectangle2D -> return RoundRectIterator(shape) 32 | is Arc2D -> return ArcIterator(shape) 33 | is Rectangle2D -> return RectIterator(shape) 34 | is Line2D -> return LineIterator(shape) 35 | } 36 | throw RuntimeException("Unknown shape! ${shape.javaClass.simpleName}") 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/GLGraphicsDevice.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import java.awt.GraphicsConfiguration 20 | import java.awt.GraphicsDevice 21 | 22 | /** 23 | * Fulfills the contract of a `GraphicsDevice`. 24 | */ 25 | class GLGraphicsDevice(private val config: GLGraphicsConfiguration) : GraphicsDevice() { 26 | 27 | override fun getType(): Int { 28 | if (config.target.chosenGLCapabilities.isOnscreen) { 29 | return GraphicsDevice.TYPE_RASTER_SCREEN 30 | } else { 31 | return GraphicsDevice.TYPE_IMAGE_BUFFER 32 | } 33 | } 34 | 35 | override fun getIDstring(): String { 36 | return "glg2d" 37 | } 38 | 39 | override fun getConfigurations(): Array { 40 | return arrayOf(defaultConfiguration) 41 | } 42 | 43 | override fun getDefaultConfiguration(): GraphicsConfiguration { 44 | return config 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DShapeHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | 20 | import java.awt.Shape 21 | import java.awt.Stroke 22 | 23 | interface GLG2DShapeHelper : G2DDrawingHelper { 24 | 25 | var stroke: Stroke 26 | 27 | fun drawRoundRect(x: Int, y: Int, width: Int, height: Int, arcWidth: Int, arcHeight: Int, fill: Boolean) 28 | 29 | fun drawRect(x: Int, y: Int, width: Int, height: Int, fill: Boolean) 30 | 31 | fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int) 32 | 33 | fun drawOval(x: Int, y: Int, width: Int, height: Int, fill: Boolean) 34 | 35 | fun drawArc(x: Int, y: Int, width: Int, height: Int, startAngle: Int, arcAngle: Int, fill: Boolean) 36 | 37 | fun drawPolyline(xPoints: IntArray, yPoints: IntArray, nPoints: Int) 38 | 39 | fun drawPolygon(xPoints: IntArray, yPoints: IntArray, nPoints: Int, fill: Boolean) 40 | 41 | fun draw(shape: Shape) 42 | 43 | fun fill(shape: Shape) 44 | 45 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLAwareRepaintManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d; 18 | 19 | import com.jogamp.opengl.GLAutoDrawable; 20 | 21 | import javax.swing.*; 22 | import java.awt.*; 23 | 24 | public class GLAwareRepaintManager extends RepaintManager 25 | { 26 | 27 | public static RepaintManager INSTANCE = new GLAwareRepaintManager(); 28 | 29 | @Override 30 | public void addDirtyRegion(JComponent c, int x, int y, int w, int h) 31 | { 32 | GLG2DCanvas canvas = getGLParent(c); 33 | if (canvas == null || c instanceof GLAutoDrawable) 34 | { 35 | super.addDirtyRegion(c, x, y, w, h); 36 | } 37 | else 38 | { 39 | canvas.repaint(); 40 | } 41 | } 42 | 43 | protected GLG2DCanvas getGLParent(JComponent component) 44 | { 45 | Container c = component.getParent(); 46 | while (true) 47 | { 48 | if (c == null) 49 | { 50 | return null; 51 | } 52 | else if (c instanceof GLG2DCanvas) 53 | { 54 | return (GLG2DCanvas) c; 55 | } 56 | else 57 | { 58 | c = c.getParent(); 59 | } 60 | } 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import com.jogamp.opengl.GL 20 | import com.jogamp.opengl.GL2ES1 21 | import org.anglur.joglext.cacheable.CachedIntArray 22 | import java.awt.Color 23 | 24 | object GLG2DUtils { 25 | 26 | fun setColor(gl: GL2ES1, c: Color, preMultiplyAlpha: Float) { 27 | val rgb = c.rgb 28 | gl.glColor4ub((rgb shr 16 and 0xFF).toByte(), (rgb shr 8 and 0xFF).toByte(), (rgb and 0xFF).toByte(), ((rgb shr 24 and 0xFF) * preMultiplyAlpha).toByte()) 29 | } 30 | 31 | fun getGLColor(c: Color) = c.getComponents(null) 32 | 33 | private val viewportDimensions = CachedIntArray(4) 34 | 35 | fun getViewportHeight(gl: GL): Int { 36 | gl.glGetIntegerv(GL.GL_VIEWPORT, viewportDimensions, 0) 37 | val canvasHeight = viewportDimensions[3] 38 | return canvasHeight 39 | } 40 | 41 | fun ensureIsGLBuffer(gl: GL, bufferId: Int) = 42 | if (gl.glIsBuffer(bufferId)) bufferId else genBufferId(gl) 43 | 44 | private val ids = CachedIntArray(1) 45 | 46 | fun genBufferId(gl: GL): Int { 47 | gl.glGenBuffers(1, ids, 0) 48 | return ids[0] 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DImageHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.Color 20 | import java.awt.Image 21 | import java.awt.geom.AffineTransform 22 | import java.awt.image.BufferedImage 23 | import java.awt.image.BufferedImageOp 24 | import java.awt.image.ImageObserver 25 | import java.awt.image.RenderedImage 26 | import java.awt.image.renderable.RenderableImage 27 | 28 | interface GLG2DImageHelper : G2DDrawingHelper { 29 | 30 | fun drawImage(img: Image, x: Int, y: Int, bgcolor: Color, observer: ImageObserver): Boolean 31 | 32 | fun drawImage(img: Image, xform: AffineTransform, observer: ImageObserver): Boolean 33 | 34 | fun drawImage(img: Image, x: Int, y: Int, width: Int, height: Int, bgcolor: Color, observer: ImageObserver): Boolean 35 | 36 | fun drawImage(img: Image, dx1: Int, dy1: Int, dx2: Int, dy2: Int, sx1: Int, sy1: Int, sx2: Int, sy2: Int, bgcolor: Color, observer: ImageObserver): Boolean 37 | 38 | fun drawImage(img: BufferedImage, op: BufferedImageOp, x: Int, y: Int) 39 | 40 | fun drawImage(img: RenderedImage, xform: AffineTransform) 41 | 42 | fun drawImage(img: RenderableImage, xform: AffineTransform) 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/LineDrawingVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import com.jogamp.opengl.fixedfunc.GLMatrixFunc 23 | import org.anglur.joglext.jogl2d.impl.BasicStrokeLineVisitor 24 | import java.awt.BasicStroke 25 | 26 | 27 | /** 28 | * Draws a line, as outlined by a [BasicStroke]. The current 29 | * implementation supports everything except dashes. This class draws a series 30 | * of quads for each line segment, joins corners and endpoints as appropriate. 31 | */ 32 | class LineDrawingVisitor : BasicStrokeLineVisitor() { 33 | 34 | private lateinit var gl: GL2 35 | 36 | override fun setGLContext(context: GL) { 37 | gl = context.gL2 38 | } 39 | 40 | override fun beginPoly(windingRule: Int) { 41 | /* 42 | * pen hangs down and to the right. See java.awt.Graphics 43 | */ 44 | gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW) 45 | gl.glPushMatrix() 46 | gl.glTranslatef(0.5f, 0.5f, 0f) 47 | 48 | super.beginPoly(windingRule) 49 | } 50 | 51 | override fun endPoly() { 52 | super.endPoly() 53 | 54 | gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW) 55 | gl.glPopMatrix() 56 | } 57 | 58 | override fun drawBuffer() = 59 | vBuffer.drawBuffer(gl, GL.GL_TRIANGLE_STRIP) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/FillSimpleConvexPolygonVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import org.anglur.joglext.jogl2d.VertexBuffer 23 | import org.anglur.joglext.jogl2d.impl.SimplePathVisitor 24 | import java.awt.BasicStroke 25 | 26 | /** 27 | * Fills a simple convex polygon. This class does not test to determine if the 28 | * polygon is actually simple and convex. 29 | */ 30 | class FillSimpleConvexPolygonVisitor : SimplePathVisitor() { 31 | 32 | private lateinit var gl: GL2 33 | 34 | private var vBuffer = VertexBuffer.sharedBuffer 35 | 36 | override fun setGLContext(context: GL) { 37 | gl = context.gL2 38 | } 39 | 40 | override fun setStroke(stroke: BasicStroke) { 41 | // nop 42 | } 43 | 44 | override fun beginPoly(windingRule: Int) { 45 | vBuffer.clear() 46 | 47 | /* 48 | * We don't care what the winding rule is, we disable face culling. 49 | */ 50 | gl.glDisable(GL.GL_CULL_FACE) 51 | } 52 | 53 | override fun closeLine() { 54 | vBuffer.drawBuffer(gl, GL2.GL_POLYGON) 55 | } 56 | 57 | override fun endPoly() { 58 | } 59 | 60 | override fun lineTo(vertex: FloatArray) { 61 | vBuffer.addVertex(vertex, 0, 1) 62 | } 63 | 64 | override fun moveTo(vertex: FloatArray) { 65 | vBuffer.clear() 66 | vBuffer.addVertex(vertex, 0, 1) 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DRenderingHints.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.RenderingHints.Key 20 | 21 | /** 22 | * Rendering hints for the GLG2D library that customize the behavior. 23 | */ 24 | object GLG2DRenderingHints { 25 | 26 | private var keyId = 384739478 27 | 28 | /** 29 | * Never clear the texture cache. 30 | */ 31 | val VALUE_CLEAR_TEXTURES_CACHE_NEVER = Any() 32 | 33 | /** 34 | * Clear the texture cache before each paint. This allows images to be cached 35 | * within a paint cycle. 36 | */ 37 | val VALUE_CLEAR_TEXTURES_CACHE_EACH_PAINT = Any() 38 | 39 | /** 40 | * Use the default texture cache policy. 41 | */ 42 | val VALUE_CLEAR_TEXTURES_CACHE_DEFAULT = VALUE_CLEAR_TEXTURES_CACHE_NEVER 43 | 44 | /** 45 | * Specifies when to clear the texture cache. Each image to be painted must be 46 | * turned into a texture and then the texture is re-used whenever that image 47 | * is seen. Values can be one of 48 | * 49 | * 50 | * 51 | * * [.VALUE_CLEAR_TEXTURES_CACHE_DEFAULT] 52 | * * [.VALUE_CLEAR_TEXTURES_CACHE_NEVER] 53 | * * [.VALUE_CLEAR_TEXTURES_CACHE_EACH_PAINT] 54 | * * any integer for the maximum size of the cache 55 | * 56 | */ 57 | val KEY_CLEAR_TEXTURES_CACHE: Key = object : Key(keyId++) { 58 | override fun isCompatibleValue(`val`: Any): Boolean { 59 | return `val` === VALUE_CLEAR_TEXTURES_CACHE_DEFAULT || 60 | `val` === VALUE_CLEAR_TEXTURES_CACHE_EACH_PAINT || 61 | `val` === VALUE_CLEAR_TEXTURES_CACHE_NEVER || 62 | `val` is Int 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/GLGraphicsConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import com.jogamp.opengl.GLDrawable 20 | import java.awt.GraphicsConfiguration 21 | import java.awt.Rectangle 22 | import java.awt.Transparency 23 | import java.awt.geom.AffineTransform 24 | import java.awt.image.BufferedImage 25 | import java.awt.image.ColorModel 26 | import java.awt.image.DirectColorModel 27 | 28 | /** 29 | * Fulfills the contract of a `GraphicsConfiguration`. 30 | * 31 | * 32 | * 33 | * 34 | * Implementation note: this object is intended primarily to allow callers to 35 | * create compatible images. The transforms and bounds should be thought out 36 | * before being used. 37 | * 38 | */ 39 | object GLGraphicsConfiguration : GraphicsConfiguration() { 40 | 41 | lateinit var target: GLDrawable 42 | 43 | fun setDrawable(target: GLDrawable) = apply { this.target = target } 44 | 45 | private val device: GLGraphicsDevice 46 | 47 | init { 48 | device = GLGraphicsDevice(this) 49 | } 50 | 51 | override fun getDevice() = device 52 | 53 | override fun createCompatibleImage(width: Int, height: Int) = 54 | BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) 55 | 56 | /* 57 | * Any reasonable {@code ColorModel} can be transformed into a texture we can 58 | * render in OpenGL. I'm not worried about creating an exactly correct one 59 | * right now. 60 | */ 61 | override fun getColorModel() = ColorModel.getRGBdefault() 62 | 63 | override fun getColorModel(transparency: Int): ColorModel? { 64 | when (transparency) { 65 | Transparency.OPAQUE, Transparency.TRANSLUCENT -> return colorModel 66 | Transparency.BITMASK -> return DirectColorModel(25, 0xff0000, 0xff00, 0xff, 0x1000000) 67 | else -> return null 68 | } 69 | } 70 | 71 | override fun getDefaultTransform() = AffineTransform() 72 | 73 | override fun getNormalizingTransform() = AffineTransform() 74 | 75 | override fun getBounds() = Rectangle(target.surfaceWidth, target.surfaceHeight) 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2ImageDrawer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | import com.jogamp.opengl.GL 20 | import com.jogamp.opengl.GL2 21 | import com.jogamp.opengl.GL2ES1 22 | import com.jogamp.opengl.util.texture.Texture 23 | import org.anglur.joglext.jogl2d.GLGraphics2D 24 | import org.anglur.joglext.jogl2d.impl.AbstractImageHelper 25 | import java.awt.Color 26 | import java.awt.geom.AffineTransform 27 | 28 | class GL2ImageDrawer : AbstractImageHelper() { 29 | 30 | private lateinit var gl: GL2 31 | 32 | private var savedTransform: AffineTransform? = null 33 | 34 | override fun setG2D(g2d: GLGraphics2D) { 35 | super.setG2D(g2d) 36 | gl = g2d.glContext.gl.gL2 37 | } 38 | 39 | override fun begin(texture: Texture, xform: AffineTransform?, bgcolor: Color) { 40 | gl.glTexEnvi(GL2ES1.GL_TEXTURE_ENV, GL2ES1.GL_TEXTURE_ENV_MODE, GL2ES1.GL_MODULATE) 41 | gl.glTexParameterf(GL2ES1.GL_TEXTURE_ENV, GL2ES1.GL_TEXTURE_ENV_MODE, GL.GL_BLEND.toFloat()) 42 | 43 | g2d.composite = g2d.composite 44 | 45 | texture.enable(gl) 46 | texture.bind(gl) 47 | 48 | savedTransform = null 49 | if (xform != null && !xform.isIdentity) { 50 | savedTransform = g2d.transform 51 | g2d.transform(xform) 52 | } 53 | 54 | g2d.colorHelper.setColorRespectComposite(bgcolor) 55 | } 56 | 57 | override fun end(texture: Texture) { 58 | if (savedTransform != null) { 59 | g2d.transform = savedTransform!! 60 | } 61 | 62 | texture.disable(gl) 63 | g2d.colorHelper.setColorRespectComposite(g2d.color) 64 | } 65 | 66 | override fun applyTexture(texture: Texture, dx1: Int, dy1: Int, dx2: Int, dy2: Int, sx1: Float, sy1: Float, sx2: Float, sy2: Float) { 67 | gl.glBegin(GL2.GL_QUADS) 68 | 69 | // SW 70 | gl.glTexCoord2f(sx1, sy2) 71 | gl.glVertex2i(dx1, dy2) 72 | // SE 73 | gl.glTexCoord2f(sx2, sy2) 74 | gl.glVertex2i(dx2, dy2) 75 | // NE 76 | gl.glTexCoord2f(sx2, sy1) 77 | gl.glVertex2i(dx2, dy1) 78 | // NW 79 | gl.glTexCoord2f(sx1, sy1) 80 | gl.glVertex2i(dx1, dy1) 81 | 82 | gl.glEnd() 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2ShapeDrawer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import org.anglur.joglext.jogl2d.GLGraphics2D 23 | import org.anglur.joglext.jogl2d.impl.AbstractShapeHelper 24 | import org.anglur.joglext.jogl2d.impl.SimpleOrTesselatingVisitor 25 | import java.awt.BasicStroke 26 | import java.awt.RenderingHints 27 | import java.awt.RenderingHints.Key 28 | import java.awt.Shape 29 | 30 | class GL2ShapeDrawer : AbstractShapeHelper() { 31 | 32 | private lateinit var gl: GL2 33 | 34 | private var simpleFillVisitor: FillSimpleConvexPolygonVisitor 35 | private var complexFillVisitor: SimpleOrTesselatingVisitor 36 | private var simpleStrokeVisitor: LineDrawingVisitor 37 | private var fastLineVisitor: FastLineVisitor 38 | 39 | init { 40 | simpleFillVisitor = FillSimpleConvexPolygonVisitor() 41 | complexFillVisitor = SimpleOrTesselatingVisitor(simpleFillVisitor, GL2TesselatorVisitor()) 42 | simpleStrokeVisitor = LineDrawingVisitor() 43 | fastLineVisitor = FastLineVisitor() 44 | } 45 | 46 | override fun setG2D(g2d: GLGraphics2D) { 47 | super.setG2D(g2d) 48 | val gl = g2d.glContext.gl 49 | simpleFillVisitor.setGLContext(gl) 50 | complexFillVisitor.setGLContext(gl) 51 | simpleStrokeVisitor.setGLContext(gl) 52 | fastLineVisitor.setGLContext(gl) 53 | 54 | this.gl = gl.gL2 55 | } 56 | 57 | override fun setHint(key: Key, value: Any?) { 58 | super.setHint(key, value) 59 | 60 | if (key === RenderingHints.KEY_ANTIALIASING) { 61 | if (value === RenderingHints.VALUE_ANTIALIAS_ON) { 62 | gl.glEnable(GL.GL_MULTISAMPLE) 63 | } else { 64 | gl.glDisable(GL.GL_MULTISAMPLE) 65 | } 66 | } 67 | } 68 | 69 | override fun draw(shape: Shape) { 70 | val stroke = stroke 71 | if (stroke is BasicStroke) { 72 | if (fastLineVisitor.isValid(stroke)) { 73 | fastLineVisitor.setStroke(stroke) 74 | traceShape(shape, fastLineVisitor) 75 | return 76 | } else if (stroke.dashArray == null) { 77 | simpleStrokeVisitor.setStroke(stroke) 78 | traceShape(shape, simpleStrokeVisitor) 79 | return 80 | } 81 | } 82 | 83 | // can fall through for various reasons 84 | fill(stroke.createStrokedShape(shape)) 85 | } 86 | 87 | override fun fill(shape: Shape, forceSimple: Boolean) 88 | = traceShape(shape, if (forceSimple) simpleFillVisitor else complexFillVisitor) 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/G2DDrawingHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import java.awt.RenderingHints 20 | 21 | /** 22 | * Assists in the drawing of a particular aspect of the Graphics2D object. This 23 | * allows the drawing to be segregated into certain aspects, such as image, text 24 | * or shape drawing. 25 | */ 26 | interface G2DDrawingHelper { 27 | 28 | /** 29 | * Sets the current `GLGraphics2D` parent. The current `GL` and 30 | * `GLContext` objects can be accessed from this. This should clear all 31 | * internal stacks in the helper object because previous painting iterations 32 | * may not have called dispose() for each time they called create(). 33 | 34 | * @param g2d The parent context for subsequent drawing operations. 35 | */ 36 | fun setG2D(g2d: GLGraphics2D) 37 | 38 | /** 39 | * Sets the new `GLGraphics2D` context in a stack. This is called when 40 | * `Graphics2D.create()` is called and each helper is given notice to 41 | * push any necessary information onto the stack. This is used in conjunction 42 | * with [.pop]. 43 | 44 | * @param newG2d The new context, top of the stack. 45 | */ 46 | fun push(newG2d: GLGraphics2D) 47 | 48 | /** 49 | * Sets the new `GLGraphics2D` context in a stack after a pop. This is 50 | * called when `Graphics2D.dispose()` is called and each helper is given 51 | * notice to pop any necessary information off the stack. This is used in 52 | * conjunction with [.push]. 53 | 54 | * @param parentG2d The new context, top of the stack - which is actually the parent 55 | * * of what was popped. 56 | */ 57 | fun pop(parentG2d: GLGraphics2D) 58 | 59 | /** 60 | * Sets a new rendering hint. The state of all rendering hints is kept by the 61 | * `GLGraphics2D` object, but all new state changes are propagated to 62 | * all listeners. 63 | 64 | * @param key The rendering hint key. 65 | * * 66 | * @param value The new hint value. 67 | */ 68 | fun setHint(key: RenderingHints.Key, value: Any?) 69 | 70 | /** 71 | * Clears all hints back to their default states. 72 | */ 73 | fun resetHints() 74 | 75 | /** 76 | * Disposes the helper object. This is not called during the dispose operation 77 | * of the `Graphics2D` object. This should dispose all GL resources when 78 | * all drawing is finished and no more calls will be executing on this OpenGL 79 | * context and these resources. 80 | */ 81 | fun dispose() 82 | } 83 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2Transformhelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | import com.jogamp.opengl.GL 20 | import com.jogamp.opengl.GL2 21 | import com.jogamp.opengl.fixedfunc.GLMatrixFunc 22 | import org.anglur.joglext.cacheable.CachedFloatArray 23 | import org.anglur.joglext.cacheable.CachedIntArray 24 | import org.anglur.joglext.jogl2d.GLGraphics2D 25 | import org.anglur.joglext.jogl2d.impl.AbstractMatrixHelper 26 | 27 | import java.awt.geom.AffineTransform 28 | 29 | class GL2Transformhelper : AbstractMatrixHelper() { 30 | private lateinit var gl: GL2 31 | 32 | private val matrixBuf = CachedFloatArray(16) 33 | 34 | override fun setG2D(g2d: GLGraphics2D) { 35 | super.setG2D(g2d) 36 | gl = g2d.glContext.gl.gL2 37 | 38 | setupGLView() 39 | flushTransformToOpenGL() 40 | } 41 | 42 | val viewportDimensions = CachedIntArray(4) 43 | 44 | private fun setupGLView() { 45 | gl.glGetIntegerv(GL.GL_VIEWPORT, viewportDimensions, 0) 46 | val width = viewportDimensions[2] 47 | val height = viewportDimensions[3] 48 | 49 | // setup projection 50 | gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION) 51 | gl.glLoadIdentity() 52 | gl.glOrtho(0.0, width.toDouble(), 0.0, height.toDouble(), -1.0, 1.0) 53 | 54 | // the MODELVIEW matrix will get adjusted later 55 | 56 | gl.glMatrixMode(GL.GL_TEXTURE) 57 | gl.glLoadIdentity() 58 | } 59 | 60 | /** 61 | * Sends the `AffineTransform` that's on top of the stack to the video 62 | * card. 63 | */ 64 | override fun flushTransformToOpenGL() { 65 | val matrix = getGLMatrix(stack.peek()) 66 | 67 | gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW) 68 | gl.glLoadMatrixf(matrix, 0) 69 | } 70 | 71 | /** 72 | * Gets the GL matrix for the `AffineTransform` with the change of 73 | * coordinates inlined. Since Java2D uses the upper-left as 0,0 and OpenGL 74 | * uses the lower-left as 0,0, we have to pre-multiply the matrix before 75 | * loading it onto the video card. 76 | */ 77 | private fun getGLMatrix(transform: AffineTransform): FloatArray { 78 | matrixBuf[0] = transform.scaleX.toFloat() 79 | matrixBuf[1] = -transform.shearY.toFloat() 80 | matrixBuf[4] = transform.shearX.toFloat() 81 | matrixBuf[5] = -transform.scaleY.toFloat() 82 | matrixBuf[10] = 1f 83 | matrixBuf[12] = transform.translateX.toFloat() 84 | matrixBuf[13] = g2d.canvasHeight - transform.translateY.toFloat() 85 | matrixBuf[15] = 1f 86 | 87 | return matrixBuf 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2ColorHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | import com.jogamp.opengl.GL2 20 | import com.jogamp.opengl.GL2GL3 21 | import org.anglur.joglext.jogl2d.GLGraphics2D 22 | import org.anglur.joglext.jogl2d.impl.AbstractColorHelper 23 | import java.awt.* 24 | 25 | class GL2ColorHelper : AbstractColorHelper() { 26 | 27 | private lateinit var gl: GL2 28 | 29 | override fun setG2D(g2d: GLGraphics2D) { 30 | super.setG2D(g2d) 31 | gl = g2d.glContext.gl.gL2 32 | } 33 | 34 | // This will probably be easier to handle with a fragment shader 35 | // in the shader pipeline, not sure how to handle it in the fixed- 36 | // function pipeline. 37 | override var paint: Paint 38 | get() = color 39 | set(paint) = when (paint) { 40 | is Color -> color = paint 41 | is GradientPaint -> { 42 | color = paint.color1 43 | TODO("setPaint(Paint) with GradientPaint") 44 | } 45 | is MultipleGradientPaint -> { 46 | color = paint.colors[0] 47 | TODO("setPaint(Paint) with MultipleGradientPaint") 48 | } 49 | else -> TODO("setPaint(Paint) with " + paint.javaClass.simpleName) 50 | } 51 | 52 | override fun setColorNoRespectComposite(c: Color) = setColor(gl, c, 1f) 53 | 54 | /** 55 | * Sets the current color with a call to glColor4*. But it respects the 56 | * AlphaComposite if any. If the AlphaComposite wants to pre-multiply an 57 | * alpha, pre-multiply it. 58 | */ 59 | override fun setColorRespectComposite(c: Color) { 60 | var alpha = 1f 61 | val composite = composite 62 | if (composite is AlphaComposite) { 63 | alpha = composite.alpha 64 | } 65 | 66 | setColor(gl, c, alpha) 67 | } 68 | 69 | private fun setColor(gl: GL2, c: Color, preMultiplyAlpha: Float) { 70 | val rgb = c.rgb 71 | gl.glColor4ub((rgb shr 16 and 0xFF).toByte(), (rgb shr 8 and 0xFF).toByte(), (rgb and 0xFF).toByte(), ((rgb shr 24 and 0xFF) * preMultiplyAlpha).toByte()) 72 | } 73 | 74 | override fun setPaintMode() = TODO("setPaintMode()") 75 | 76 | override fun setXORMode(c: Color) = TODO("setXORMode(Color)") 77 | 78 | override fun copyArea(x: Int, y: Int, width: Int, height: Int, dx: Int, dy: Int) { 79 | // glRasterPos* is transformed, but CopyPixels is not 80 | val x2 = x + dx 81 | val y2 = y + dy + height 82 | gl.glRasterPos2i(x2, y2) 83 | 84 | val x1 = x 85 | val y1 = g2d.canvasHeight - (y + height) 86 | gl.glCopyPixels(x1, y1, width, height, GL2GL3.GL_COLOR) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractMatrixHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import org.anglur.joglext.jogl2d.GLG2DTransformHelper 20 | import org.anglur.joglext.jogl2d.GLGraphics2D 21 | import java.awt.RenderingHints.Key 22 | import java.awt.geom.AffineTransform 23 | import java.util.* 24 | 25 | abstract class AbstractMatrixHelper : GLG2DTransformHelper { 26 | 27 | protected lateinit var g2d: GLGraphics2D 28 | 29 | protected var stack: Deque = ArrayDeque() 30 | 31 | private val EMPTY = AffineTransform() 32 | 33 | override fun setG2D(g2d: GLGraphics2D) { 34 | this.g2d = g2d 35 | 36 | stack.clear() 37 | stack.push(EMPTY) 38 | } 39 | 40 | override fun push(newG2d: GLGraphics2D) { 41 | stack.push(transform) 42 | } 43 | 44 | override fun pop(parentG2d: GLGraphics2D) { 45 | stack.pop() 46 | flushTransformToOpenGL() 47 | } 48 | 49 | override fun setHint(key: Key, value: Any?) { 50 | // nop 51 | } 52 | 53 | override fun resetHints() { 54 | // nop 55 | } 56 | 57 | override fun dispose() { 58 | // nop 59 | } 60 | 61 | override fun translate(x: Int, y: Int) { 62 | translate(x.toDouble(), y.toDouble()) 63 | flushTransformToOpenGL() 64 | } 65 | 66 | override fun translate(tx: Double, ty: Double) { 67 | transform0.translate(tx, ty) 68 | flushTransformToOpenGL() 69 | } 70 | 71 | override fun rotate(theta: Double) { 72 | transform0.rotate(theta) 73 | flushTransformToOpenGL() 74 | } 75 | 76 | override fun rotate(theta: Double, x: Double, y: Double) { 77 | transform0.rotate(theta, x, y) 78 | flushTransformToOpenGL() 79 | } 80 | 81 | override fun scale(sx: Double, sy: Double) { 82 | transform0.scale(sx, sy) 83 | flushTransformToOpenGL() 84 | } 85 | 86 | override fun shear(shx: Double, shy: Double) { 87 | transform0.shear(shx, shy) 88 | flushTransformToOpenGL() 89 | } 90 | 91 | override fun transform(Tx: AffineTransform) { 92 | transform0.concatenate(Tx) 93 | flushTransformToOpenGL() 94 | } 95 | 96 | override var transform: AffineTransform 97 | get() = transform0.clone() as AffineTransform 98 | set(transform) { 99 | transform0.setTransform(transform) 100 | flushTransformToOpenGL() 101 | } 102 | 103 | /** 104 | * Returns the `AffineTransform` at the top of the stack, *not* a 105 | * copy. 106 | */ 107 | private val transform0: AffineTransform 108 | get() = stack.peek() 109 | 110 | /** 111 | * Sends the `AffineTransform` that's on top of the stack to the video 112 | * card. 113 | */ 114 | protected abstract fun flushTransformToOpenGL() 115 | 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JOGL2D 2 | _Zero-overhead 2D rendering library for JOGL_ 3 | 4 | [![Build Status](https://travis-ci.org/Jonatino/JOGL2D.svg?branch=master)](https://travis-ci.org/Jonatino/JOGL2D) 5 | ![license](https://img.shields.io/github/license/Jonatino/JOGL2D.svg) 6 | 7 | This library is licensed under Apache License 2.0. 8 | 9 | 10 | JOGL2D is an open source Kotlin library that provides easy 2D graphics rendering capabilities to JOGL without adding any overhead whatsoever (memory/CPU). 11 | JOGL2D is a lightweight, resource friendly, stripped down version of brandonborkholder's glg2d. 12 | 13 | # How Can I Use JOGL2D? 14 | Simply add JOGL2D to your JOGL application using your favourite dependancy management systems. 15 | 16 | ### Gradle 17 | ```groovy 18 | compile 'org.anglur:joglext:1.0.3' 19 | ``` 20 | 21 | ### Maven 22 | ```xml 23 | 24 | org.anglur 25 | joglext 26 | 1.0.3 27 | 28 | ``` 29 | 30 | --- 31 | 32 | 33 | Once added, it is very easy to implement. Start off by making a `GLEventListener` 34 | ```kotlin 35 | object CharlatanoOverlay : GLEventListener { 36 | 37 | private val WINDOW_WIDTH = 500 38 | private val WINDOW_HEIGHT = 500 39 | private val FPS = 60 40 | 41 | val window = GLWindow.create(GLCapabilities(null)) 42 | 43 | init { 44 | GLProfile.initSingleton() 45 | } 46 | 47 | fun open(width: Int = WINDOW_WIDTH, height: Int = WINDOW_HEIGHT, x: Int = 100, y: Int = 1000) { 48 | val animator = FPSAnimator(window, FPS, true) 49 | 50 | window.addWindowListener(object : WindowAdapter() { 51 | override fun windowDestroyNotify(e: WindowEvent) { 52 | thread { 53 | if (animator.isStarted) 54 | animator.stop() 55 | exitProcess(0) 56 | }.start() 57 | } 58 | }) 59 | 60 | window.addGLEventListener(this) 61 | window.setSize(width, height) 62 | window.setPosition(x, y) 63 | window.title = "Hello world" 64 | window.isVisible = true 65 | animator.start() 66 | } 67 | 68 | val g = GLGraphics2D() //Create GL2D wrapper 69 | 70 | override fun display(gLDrawable: GLAutoDrawable) { 71 | val gl2 = gLDrawable.gl.gL2 72 | 73 | gl2.glClear(GL.GL_COLOR_BUFFER_BIT) 74 | 75 | g.prePaint(gLDrawable.context) //Updated wrapper to latest glContext 76 | 77 | g.color = Color.RED 78 | g.drawRect(0, 0, 200, 200) 79 | g.color = Color.YELLOW 80 | g.drawLine(0, 0, 100, 100) 81 | g.color = Color.CYAN 82 | g.drawString("OpenGL 2D Made Easy! :D", 100, 100) 83 | } 84 | 85 | override fun init(glDrawable: GLAutoDrawable) { 86 | } 87 | 88 | override fun reshape(gLDrawable: GLAutoDrawable, x: Int, y: Int, width: Int, height: Int) { 89 | val gl = gLDrawable.gl.gL2 90 | gl.glViewport(0, 0, width, height) 91 | } 92 | 93 | override fun dispose(gLDrawable: GLAutoDrawable) { 94 | g.glDispose() 95 | } 96 | } 97 | ``` 98 | 99 | # Screenshots 100 | 101 | ![Alt text](https://dl.dropboxusercontent.com/s/08vy1wnjhdoqivn/java_2016-10-01_17-45-28.png "Gui Demo") 102 | 103 | Huge credits again to @brandonborkholder for open sourcing his G2D library here: https://github.com/brandonborkholder/glg2d 104 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/cacheable/CachedList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.cacheable 18 | 19 | @Suppress("UNCHECKED_CAST") 20 | open class CachedList(private val minIndex: Int, private val capacity: Int) : Iterable { 21 | 22 | private var arr = arrayOfNulls(capacity) 23 | 24 | private var size = 0 25 | private var highest: Int = 0 26 | private var dirty = false 27 | 28 | constructor(capacity: Int) : this(0, capacity) 29 | 30 | operator fun get(index: Int) = arr[index] as E 31 | 32 | operator fun set(index: Int, element: @UnsafeVariance E?): E { 33 | val previous = arr[index] 34 | if (previous == element) return previous as E 35 | 36 | arr[index] = element 37 | if (previous == null && element != null) { 38 | size++ 39 | if (highest < index) { 40 | highest = index 41 | } 42 | } else if (previous != null && element == null) { 43 | size-- 44 | if (highest == index) { 45 | highest-- 46 | } 47 | } 48 | dirty = true 49 | return previous as E 50 | } 51 | 52 | fun add(element: @UnsafeVariance E): Int { 53 | val index = nextIndex() 54 | set(index, element) 55 | return index 56 | } 57 | 58 | fun remove(element: @UnsafeVariance E) { 59 | for (i in minIndex..highest) { 60 | if (element!! == arr[i]) { 61 | set(i, null) 62 | return 63 | } 64 | } 65 | } 66 | 67 | operator fun contains(element: @UnsafeVariance E): Boolean { 68 | for (e in iterator()) { 69 | if (element!! == e) { 70 | return true 71 | } 72 | } 73 | 74 | return false 75 | } 76 | 77 | inline fun forEach(action: (E) -> Unit) { 78 | for (e in iterator()) { 79 | if (e != null) 80 | action(e) 81 | } 82 | } 83 | 84 | open fun clear() { 85 | for (i in minIndex until arr.size) 86 | arr[i] = null 87 | size = 0 88 | dirty = true 89 | } 90 | 91 | fun size() = size 92 | 93 | fun isDirty() = dirty 94 | 95 | fun clean() = apply { dirty = false } 96 | 97 | private fun nextIndex(): Int { 98 | for (i in minIndex until arr.size) { 99 | if (null == arr[i]) { 100 | return i 101 | } 102 | } 103 | throw IllegalStateException("Out of indices!") 104 | } 105 | 106 | override operator fun iterator(): Iterator { 107 | iterator.pointer = minIndex 108 | return iterator 109 | } 110 | 111 | private val iterator = IndexerIterator() 112 | 113 | private inner class IndexerIterator : Iterator { 114 | 115 | var pointer: Int = 0 116 | 117 | override fun hasNext() = size > 0 && pointer <= highest 118 | 119 | override fun next(): E { 120 | val o = arr[pointer++] 121 | if (o == null && hasNext()) { 122 | return next() 123 | } 124 | return o as E 125 | } 126 | 127 | fun remove() = set(pointer, null) 128 | 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractTextDrawer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | 20 | import org.anglur.joglext.jogl2d.GLG2DTextHelper 21 | import org.anglur.joglext.jogl2d.GLGraphics2D 22 | import java.awt.Font 23 | import java.awt.FontMetrics 24 | import java.awt.RenderingHints 25 | import java.awt.RenderingHints.Key 26 | import java.awt.font.FontRenderContext 27 | import java.lang.Math.ceil 28 | import java.util.* 29 | 30 | abstract class AbstractTextDrawer : GLG2DTextHelper { 31 | 32 | protected lateinit var g2d: GLGraphics2D 33 | 34 | private var stack: Deque = ArrayDeque() 35 | 36 | private val EMPTY = FontState() 37 | 38 | override fun setG2D(g2d: GLGraphics2D) { 39 | this.g2d = g2d 40 | 41 | stack.clear() 42 | stack.push(EMPTY.reset()) 43 | } 44 | 45 | override fun push(newG2d: GLGraphics2D) { 46 | stack.push(stack.peek().clone()) 47 | } 48 | 49 | override fun pop(parentG2d: GLGraphics2D) { 50 | stack.pop() 51 | } 52 | 53 | override fun setHint(key: Key, value: Any?) { 54 | if (key === RenderingHints.KEY_TEXT_ANTIALIASING) { 55 | stack.peek().antiAlias = value === RenderingHints.VALUE_TEXT_ANTIALIAS_ON 56 | } 57 | } 58 | 59 | override fun resetHints() { 60 | setHint(RenderingHints.KEY_TEXT_ANTIALIASING, null) 61 | } 62 | 63 | override var font: Font 64 | get() = stack.peek().font!! 65 | set(font) { 66 | stack.peek().font = font 67 | } 68 | 69 | override fun getFontMetrics(font: Font): FontMetrics { 70 | return GLFontMetrics(font, fontRenderContext) 71 | } 72 | 73 | override val fontRenderContext: FontRenderContext 74 | get() = FontRenderContext(g2d.transform, stack.peek().antiAlias, false) 75 | 76 | /** 77 | * The default implementation is good enough for now. 78 | */ 79 | class GLFontMetrics(font: Font, private var frc: FontRenderContext) : FontMetrics(font) { 80 | 81 | override fun getFontRenderContext(): FontRenderContext { 82 | return frc 83 | } 84 | 85 | override fun charsWidth(data: CharArray, off: Int, len: Int): Int { 86 | if (len <= 0) { 87 | return 0 88 | } 89 | 90 | val bounds = font.getStringBounds(data, off, len, frc) 91 | return ceil(bounds.width).toInt() 92 | } 93 | 94 | companion object { 95 | private val serialVersionUID = 3676850359220061793L 96 | } 97 | } 98 | 99 | private class FontState : Cloneable { 100 | var font: Font? = null 101 | var antiAlias: Boolean = false 102 | 103 | public override fun clone(): FontState { 104 | try { 105 | return super.clone() as FontState 106 | } catch (e: CloneNotSupportedException) { 107 | throw AssertionError(e) 108 | } 109 | 110 | } 111 | 112 | fun reset() = apply { 113 | font = null 114 | antiAlias = false 115 | } 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DSimpleEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d; 18 | 19 | import com.jogamp.opengl.GLAutoDrawable; 20 | import com.jogamp.opengl.GLEventListener; 21 | 22 | import javax.swing.*; 23 | 24 | /** 25 | * Wraps a {@code JComponent} and paints it using a {@code GLGraphics2D}. This 26 | * object will paint the entire component fully for each frame. 27 | */ 28 | public class GLG2DSimpleEventListener implements GLEventListener 29 | { 30 | /** 31 | * The cached object. 32 | */ 33 | protected GLGraphics2D g2d; 34 | 35 | /** 36 | * The component to paint. 37 | */ 38 | protected JComponent comp; 39 | 40 | public GLG2DSimpleEventListener(JComponent component) 41 | { 42 | if (component == null) 43 | { 44 | throw new NullPointerException("component is null"); 45 | } 46 | 47 | this.comp = component; 48 | } 49 | 50 | @Override 51 | public void display(GLAutoDrawable drawable) 52 | { 53 | prePaint(drawable); 54 | paintGL(g2d); 55 | postPaint(drawable); 56 | } 57 | 58 | /** 59 | * Called before any painting is done. This should setup the matrices and ask 60 | * the {@code GLGraphics2D} object to setup any client state. 61 | */ 62 | protected void prePaint(GLAutoDrawable drawable) 63 | { 64 | setupViewport(drawable); 65 | g2d.prePaint(drawable.getContext()); 66 | 67 | // clip to only the component we're painting 68 | g2d.translate(comp.getX(), comp.getY()); 69 | g2d.clipRect(0, 0, comp.getWidth(), comp.getHeight()); 70 | } 71 | 72 | /** 73 | * Defines the viewport to paint into. 74 | */ 75 | protected void setupViewport(GLAutoDrawable drawable) 76 | { 77 | drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); 78 | } 79 | 80 | /** 81 | * Called after all Java2D painting is complete. 82 | */ 83 | protected void postPaint(GLAutoDrawable drawable) 84 | { 85 | g2d.postPaint(); 86 | } 87 | 88 | /** 89 | * Paints using the {@code GLGraphics2D} object. This could be forwarded to 90 | * any code that expects to draw using the Java2D framework. 91 | *

92 | * Currently is paints the component provided, turning off double-buffering in 93 | * the {@code RepaintManager} to force drawing directly to the 94 | * {@code Graphics2D} object. 95 | *

96 | */ 97 | protected void paintGL(GLGraphics2D g2d) 98 | { 99 | boolean wasDoubleBuffered = comp.isDoubleBuffered(); 100 | comp.setDoubleBuffered(false); 101 | 102 | comp.paint(g2d); 103 | 104 | comp.setDoubleBuffered(wasDoubleBuffered); 105 | } 106 | 107 | @Override 108 | public void init(GLAutoDrawable drawable) 109 | { 110 | g2d = createGraphics2D(drawable); 111 | } 112 | 113 | @Override 114 | public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) 115 | { 116 | } 117 | 118 | /** 119 | * Creates the {@code Graphics2D} object that forwards Java2D calls to OpenGL 120 | * calls. 121 | */ 122 | protected GLGraphics2D createGraphics2D(GLAutoDrawable drawable) 123 | { 124 | return new GLGraphics2D(); 125 | } 126 | 127 | @Override 128 | public void dispose(GLAutoDrawable arg0) 129 | { 130 | if (g2d != null) 131 | { 132 | g2d.glDispose(); 133 | g2d = null; 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractColorHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import com.jogamp.opengl.GL 20 | import org.anglur.joglext.jogl2d.GLG2DColorHelper 21 | import org.anglur.joglext.jogl2d.GLGraphics2D 22 | import java.awt.AlphaComposite 23 | import java.awt.Color 24 | import java.awt.Composite 25 | import java.awt.Paint 26 | import java.awt.RenderingHints.Key 27 | import java.util.* 28 | 29 | abstract class AbstractColorHelper : GLG2DColorHelper { 30 | 31 | protected lateinit var g2d: GLGraphics2D 32 | 33 | private var stack: Deque = ArrayDeque() 34 | 35 | private val EMPTY = ColorState() 36 | override fun setG2D(g2d: GLGraphics2D) { 37 | this.g2d = g2d 38 | 39 | stack.clear() 40 | stack.push(EMPTY.reset()) 41 | } 42 | 43 | override fun push(newG2d: GLGraphics2D) = stack.push(stack.peek().clone()) 44 | 45 | override fun pop(parentG2d: GLGraphics2D) { 46 | stack.pop() 47 | 48 | // set all the states 49 | composite = composite 50 | color = color 51 | background = background 52 | } 53 | 54 | override fun setHint(key: Key, value: Any?) { 55 | // nop 56 | } 57 | 58 | override fun resetHints() { 59 | // nop 60 | } 61 | 62 | override fun dispose() { 63 | } 64 | 65 | /* 66 | * Since the destination _always_ covers the entire canvas (i.e. there are 67 | * always color components for every pixel), some of these composites can 68 | * be collapsed into each other. They matter when Java2D is drawing into 69 | * an image and the destination may not take up the entire canvas. 70 | */// need to pre-multiply the alpha 71 | override var composite: Composite 72 | get() = stack.peek().composite!! 73 | set(comp) { 74 | val gl = g2d.glContext.gl 75 | gl.glEnable(GL.GL_BLEND) 76 | if (comp is AlphaComposite) { 77 | when (comp.rule) { 78 | AlphaComposite.SRC, AlphaComposite.SRC_IN -> gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ZERO) 79 | 80 | AlphaComposite.SRC_OVER, AlphaComposite.SRC_ATOP -> gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) 81 | 82 | AlphaComposite.SRC_OUT, AlphaComposite.CLEAR -> gl.glBlendFunc(GL.GL_ZERO, GL.GL_ZERO) 83 | 84 | AlphaComposite.DST, AlphaComposite.DST_OVER -> gl.glBlendFunc(GL.GL_ZERO, GL.GL_ONE) 85 | 86 | AlphaComposite.DST_IN, AlphaComposite.DST_ATOP -> gl.glBlendFunc(GL.GL_ZERO, GL.GL_SRC_ALPHA) 87 | 88 | AlphaComposite.DST_OUT, AlphaComposite.XOR -> gl.glBlendFunc(GL.GL_ZERO, GL.GL_ONE_MINUS_SRC_ALPHA) 89 | } 90 | 91 | stack.peek().composite = comp 92 | color = color 93 | } else { 94 | TODO(if ("setComposite(Composite) with " + comp == null) "null Composite" else comp.javaClass.simpleName) 95 | } 96 | } 97 | 98 | override var color: Color 99 | get() = stack.peek().color 100 | set(c) { 101 | stack.peek().color = c 102 | setColorRespectComposite(c) 103 | } 104 | 105 | override var background: Color 106 | get() = stack.peek().background 107 | set(color) { 108 | stack.peek().background = color 109 | } 110 | 111 | override var paint: Paint 112 | get() = stack.peek().paint!! 113 | set(paint) { 114 | stack.peek().paint = paint 115 | } 116 | 117 | private class ColorState : Cloneable { 118 | var composite = AlphaComposite.SrcOver 119 | var color = Color.WHITE 120 | var paint: Paint? = null 121 | var background = Color.WHITE 122 | 123 | public override fun clone(): ColorState { 124 | try { 125 | return super.clone() as ColorState 126 | } catch (e: CloneNotSupportedException) { 127 | throw AssertionError(e) 128 | } 129 | 130 | } 131 | 132 | fun reset() = apply { 133 | composite = AlphaComposite.SrcOver 134 | color = Color.WHITE 135 | paint = null 136 | background = Color.WHITE 137 | } 138 | 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/impl/LineIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape.impl 18 | 19 | import java.awt.geom.AffineTransform 20 | import java.awt.geom.Line2D 21 | import java.awt.geom.PathIterator 22 | import java.util.* 23 | 24 | 25 | /** 26 | * Created by Jonathan on 9/30/2016. 27 | */ 28 | object LineIterator : PathIterator { 29 | 30 | private lateinit var line: Line2D 31 | private var affine: AffineTransform? = null 32 | 33 | private var index: Int = 0 34 | 35 | fun set(line: Line2D, affine: AffineTransform?) = apply { 36 | this.index = 0 37 | this.line = line 38 | this.affine = affine 39 | } 40 | 41 | /** 42 | * Return the winding rule for determining the insideness of the 43 | * path. 44 | * @see #WIND_EVEN_ODD 45 | * @see #WIND_NON_ZERO 46 | */ 47 | override fun getWindingRule() = PathIterator.WIND_NON_ZERO 48 | 49 | /** 50 | * Tests if there are more points to read. 51 | * @return true if there are more points to read 52 | */ 53 | override fun isDone() = index > 1 54 | 55 | /** 56 | * Moves the iterator to the next segment of the path forwards 57 | * along the primary direction of traversal as long as there are 58 | * more points in that direction. 59 | */ 60 | override fun next() { 61 | index++ 62 | } 63 | 64 | /** 65 | * Returns the coordinates and type of the current path segment in 66 | * the iteration. 67 | * The return value is the path segment type: 68 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 69 | * A float array of length 6 must be passed in and may be used to 70 | * store the coordinates of the point(s). 71 | * Each point is stored as a pair of float x,y coordinates. 72 | * SEG_MOVETO and SEG_LINETO types will return one point, 73 | * SEG_QUADTO will return two points, 74 | * SEG_CUBICTO will return 3 points 75 | * and SEG_CLOSE will not return any points. 76 | * @see #SEG_MOVETO 77 | * @see #SEG_LINETO 78 | * @see #SEG_QUADTO 79 | * @see #SEG_CUBICTO 80 | * @see #SEG_CLOSE 81 | */ 82 | override fun currentSegment(coords: FloatArray): Int { 83 | if (isDone) { 84 | throw NoSuchElementException("line iterator out of bounds") 85 | } 86 | val type: Int 87 | if (index === 0) { 88 | coords[0] = line.x1.toFloat() 89 | coords[1] = line.y1.toFloat() 90 | type = PathIterator.SEG_MOVETO 91 | } else { 92 | coords[0] = line.x2.toFloat() 93 | coords[1] = line.y2.toFloat() 94 | type = PathIterator.SEG_LINETO 95 | } 96 | affine?.transform(coords, 0, coords, 0, 1) 97 | 98 | return type 99 | } 100 | 101 | /** 102 | * Returns the coordinates and type of the current path segment in 103 | * the iteration. 104 | * The return value is the path segment type: 105 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 106 | * A double array of length 6 must be passed in and may be used to 107 | * store the coordinates of the point(s). 108 | * Each point is stored as a pair of double x,y coordinates. 109 | * SEG_MOVETO and SEG_LINETO types will return one point, 110 | * SEG_QUADTO will return two points, 111 | * SEG_CUBICTO will return 3 points 112 | * and SEG_CLOSE will not return any points. 113 | * @see #SEG_MOVETO 114 | * @see #SEG_LINETO 115 | * @see #SEG_QUADTO 116 | * @see #SEG_CUBICTO 117 | * @see #SEG_CLOSE 118 | */ 119 | override fun currentSegment(coords: DoubleArray): Int { 120 | if (isDone) { 121 | throw NoSuchElementException("line iterator out of bounds") 122 | } 123 | val type: Int 124 | if (index === 0) { 125 | coords[0] = line.x1 126 | coords[1] = line.y1 127 | type = PathIterator.SEG_MOVETO 128 | } else { 129 | coords[0] = line.x2 130 | coords[1] = line.y2 131 | type = PathIterator.SEG_LINETO 132 | } 133 | affine?.transform(coords, 0, coords, 0, 1) 134 | 135 | return type 136 | } 137 | 138 | 139 | operator fun invoke(line: Line2D, affine: AffineTransform? = null) = set(line, affine) 140 | 141 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/SimplePathVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import org.anglur.joglext.cacheable.CachedFloatArray 20 | import org.anglur.joglext.jogl2d.PathVisitor 21 | 22 | /** 23 | * This is a fast Bzier curve implementation. I can't use OpenGL's 24 | * built-in evaluators because subclasses need to do something with the points, 25 | * not just pass them directly to glVertex2f. This algorithm uses forward 26 | * differencing. Most of this is taken from [http://www.niksula.hut.fi/~hkankaan/Homepages/bezierfast.html](http://www.niksula.hut.fi/~hkankaan/Homepages/bezierfast.html). I derived 27 | * the implementation for the quadratic on my own, but it's simple. 28 | */ 29 | abstract class SimplePathVisitor : PathVisitor { 30 | 31 | /** 32 | * Gets the number of steps to take in a quadratic or cubic curve spline. 33 | */ 34 | /** 35 | * Sets the number of steps to take in a quadratic or cubic curve spline. 36 | */ 37 | var numCurveSteps = CURVE_STEPS 38 | 39 | override fun quadTo(previousVertex: FloatArray, control: FloatArray) { 40 | val p = CachedFloatArray(2) 41 | 42 | var xd: Float 43 | val xdd: Float 44 | val xdd_per_2: Float 45 | var yd: Float 46 | val ydd: Float 47 | val ydd_per_2: Float 48 | val t = 1f / numCurveSteps 49 | val tt = t * t 50 | 51 | // x 52 | p[0] = previousVertex[0] 53 | xd = 2f * (control[0] - previousVertex[0]) * t 54 | xdd_per_2 = 1f * (previousVertex[0] - 2 * control[0] + control[2]) * tt 55 | xdd = xdd_per_2 + xdd_per_2 56 | 57 | // y 58 | p[1] = previousVertex[1] 59 | yd = 2f * (control[1] - previousVertex[1]) * t 60 | ydd_per_2 = 1f * (previousVertex[1] - 2 * control[1] + control[3]) * tt 61 | ydd = ydd_per_2 + ydd_per_2 62 | 63 | for (loop in 0..numCurveSteps - 1) { 64 | lineTo(p) 65 | 66 | p[0] = p[0] + xd + xdd_per_2 67 | xd += xdd 68 | 69 | p[1] = p[1] + yd + ydd_per_2 70 | yd += ydd 71 | } 72 | 73 | // use exactly the last point 74 | p[0] = control[2] 75 | p[1] = control[3] 76 | lineTo(p) 77 | } 78 | 79 | override fun cubicTo(previousVertex: FloatArray, control: FloatArray) { 80 | val p = CachedFloatArray(2) 81 | 82 | var xd: Float 83 | var xdd: Float 84 | val xddd: Float 85 | var xdd_per_2: Float 86 | val xddd_per_2: Float 87 | val xddd_per_6: Float 88 | var yd: Float 89 | var ydd: Float 90 | val yddd: Float 91 | var ydd_per_2: Float 92 | val yddd_per_2: Float 93 | val yddd_per_6: Float 94 | val t = 1f / numCurveSteps 95 | val tt = t * t 96 | 97 | // x 98 | p[0] = previousVertex[0] 99 | xd = 3f * (control[0] - previousVertex[0]) * t 100 | xdd_per_2 = 3f * (previousVertex[0] - 2 * control[0] + control[2]) * tt 101 | xddd_per_2 = 3f * (3 * (control[0] - control[2]) + control[4] - previousVertex[0]) * tt * t 102 | 103 | xddd = xddd_per_2 + xddd_per_2 104 | xdd = xdd_per_2 + xdd_per_2 105 | xddd_per_6 = xddd_per_2 / 3 106 | 107 | // y 108 | p[1] = previousVertex[1] 109 | yd = 3f * (control[1] - previousVertex[1]) * t 110 | ydd_per_2 = 3f * (previousVertex[1] - 2 * control[1] + control[3]) * tt 111 | yddd_per_2 = 3f * (3 * (control[1] - control[3]) + control[5] - previousVertex[1]) * tt * t 112 | 113 | yddd = yddd_per_2 + yddd_per_2 114 | ydd = ydd_per_2 + ydd_per_2 115 | yddd_per_6 = yddd_per_2 / 3 116 | 117 | for (loop in 0..numCurveSteps - 1) { 118 | lineTo(p) 119 | 120 | p[0] = p[0] + xd + xdd_per_2 + xddd_per_6 121 | xd += xdd + xddd_per_2 122 | xdd += xddd 123 | xdd_per_2 += xddd_per_2 124 | 125 | p[1] = p[1] + yd + ydd_per_2 + yddd_per_6 126 | yd += ydd + yddd_per_2 127 | ydd += yddd 128 | ydd_per_2 += yddd_per_2 129 | } 130 | 131 | // use exactly the last point 132 | p[0] = control[4] 133 | p[1] = control[5] 134 | lineTo(p) 135 | } 136 | 137 | companion object { 138 | /** 139 | * Just a guess. Would be nice to make a better guess based on what's visually 140 | * acceptable. 141 | */ 142 | val CURVE_STEPS = 30 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/VertexBuffer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import com.jogamp.common.nio.Buffers 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import com.jogamp.opengl.fixedfunc.GLPointerFunc 23 | 24 | import java.nio.FloatBuffer 25 | 26 | /** 27 | * Wraps a simple `FloatBuffer` and makes it easier to push 2-D vertices 28 | * into the buffer and then draw them using any mode desired. The default 29 | * constructor uses a global buffer since drawing in OpenGL is not 30 | * multi-threaded. 31 | */ 32 | class VertexBuffer { 33 | 34 | var buffer: FloatBuffer 35 | private set 36 | 37 | private var deviceBufferId: Int = 0 38 | 39 | /** 40 | * Creates a private buffer. This can be used without fear of clobbering the 41 | * global buffer. This should only be used if you have a need to create two 42 | * parallel shape at the same time. 43 | 44 | * @param capacity The size of the buffer in number of vertices 45 | */ 46 | constructor(capacity: Int) { 47 | buffer = (Buffers.newDirectFloatBuffer(capacity * 2)) 48 | } 49 | 50 | /** 51 | * Adds multiple vertices to the buffer. 52 | 53 | * @param array The array containing vertices in the form (x,y),(x,y) 54 | * * 55 | * @param offset The starting index 56 | * * 57 | * @param numVertices The number of vertices, pairs of floats 58 | */ 59 | fun addVertex(array: FloatArray, offset: Int, numVertices: Int) { 60 | val numFloats = numVertices * 2 61 | ensureCapacity(numFloats) 62 | buffer.put(array, offset, numFloats) 63 | } 64 | 65 | /** 66 | * Adds a vertex to the buffer. 67 | 68 | * @param x The x coordinate 69 | * * 70 | * @param y The y coordinate 71 | */ 72 | fun addVertex(x: Float, y: Float) { 73 | ensureCapacity(2) 74 | buffer.put(x) 75 | buffer.put(y) 76 | } 77 | 78 | /** 79 | * Adds multiple vertices to the buffer. 80 | 81 | * @param vertices The buffer of new vertices to add. 82 | */ 83 | fun addVertices(vertices: FloatBuffer) { 84 | val size = vertices.limit() - vertices.position() 85 | ensureCapacity(size) 86 | 87 | buffer.put(vertices) 88 | } 89 | 90 | private fun ensureCapacity(numNewFloats: Int) { 91 | if (buffer.capacity() <= buffer.position() + numNewFloats) { 92 | val larger = Buffers.newDirectFloatBuffer(Math.max(buffer.position() * 2, buffer.position() + numNewFloats)) 93 | deviceBufferId = -deviceBufferId 94 | val position = buffer.position() 95 | buffer.rewind() 96 | larger.put(buffer) 97 | buffer = larger 98 | buffer.position(position) 99 | } 100 | } 101 | 102 | /** 103 | * Discard all existing points. This method is not necessary unless the points 104 | * already added are not needed anymore and the buffer will be reused. 105 | */ 106 | fun clear() { 107 | buffer.clear() 108 | } 109 | 110 | /** 111 | * Draws the vertices and rewinds the buffer to be ready to draw next time. 112 | 113 | * @param gl The graphics context to use to draw 114 | * * 115 | * @param mode The mode, e.g. `GL#GL_LINE_STRIP` 116 | */ 117 | fun drawBuffer(gl: GL2, mode: Int) { 118 | if (buffer.position() == 0) { 119 | return 120 | } 121 | 122 | val count = buffer.position() 123 | buffer.rewind() 124 | 125 | gl.glVertexPointer(2, GL.GL_FLOAT, 0, buffer) 126 | 127 | gl.glEnableClientState(GLPointerFunc.GL_VERTEX_ARRAY) 128 | gl.glDrawArrays(mode, 0, count / 2) 129 | gl.glDisableClientState(GLPointerFunc.GL_VERTEX_ARRAY) 130 | 131 | buffer.position(count) 132 | } 133 | 134 | companion object { 135 | private var shared = VertexBuffer(1024) 136 | 137 | /** 138 | * Creates a buffer that uses the shared global buffer. This is faster than 139 | * allocating multiple float buffers. Since OpenGL is single-threaded, we can 140 | * assume this won't be accessed outside the OpenGL thread and typically one 141 | * object is drawn completely before another one. If this is not true, one of 142 | * the objects being drawn simultaneously must use a private buffer. See 143 | * `#VertexBuffer(int)`. 144 | */ 145 | val sharedBuffer: VertexBuffer 146 | get() { 147 | shared.clear() 148 | return shared 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractTesselatorVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | 20 | import com.jogamp.opengl.GLException 21 | import com.jogamp.opengl.glu.GLU 22 | import com.jogamp.opengl.glu.GLUtessellatorCallback 23 | import com.jogamp.opengl.glu.GLUtessellatorCallbackAdapter 24 | import org.anglur.joglext.cacheable.CachedFloatArray 25 | import org.anglur.joglext.jogl2d.VertexBuffer 26 | import java.awt.BasicStroke 27 | import java.awt.geom.PathIterator 28 | 29 | /** 30 | * Fills a shape by tesselating it with the GLU library. This is a slower 31 | * implementation and `FillNonintersectingPolygonVisitor` should be used 32 | * when possible. 33 | */ 34 | abstract class AbstractTesselatorVisitor : SimplePathVisitor() { 35 | 36 | private val tesselator by lazy { GLU.gluNewTess() } 37 | 38 | private var callback: GLUtessellatorCallback 39 | 40 | /** 41 | * Last command was a move to. This is where drawing starts. 42 | */ 43 | private var drawStart = CachedFloatArray(2) 44 | private var drawing = false 45 | 46 | protected var drawMode: Int = 0 47 | protected var vBuffer = VertexBuffer(1024) 48 | 49 | init { 50 | callback = TessellatorCallback() 51 | } 52 | 53 | override fun setStroke(stroke: BasicStroke) { 54 | // nop 55 | } 56 | 57 | override fun beginPoly(windingRule: Int) { 58 | configureTesselator(windingRule) 59 | 60 | GLU.gluTessBeginPolygon(tesselator, null) 61 | } 62 | 63 | private fun configureTesselator(windingRule: Int) { 64 | when (windingRule) { 65 | PathIterator.WIND_EVEN_ODD -> GLU.gluTessProperty(tesselator, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD.toDouble()) 66 | 67 | PathIterator.WIND_NON_ZERO -> GLU.gluTessProperty(tesselator, GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO.toDouble()) 68 | } 69 | 70 | GLU.gluTessCallback(tesselator, GLU.GLU_TESS_VERTEX, callback) 71 | GLU.gluTessCallback(tesselator, GLU.GLU_TESS_BEGIN, callback) 72 | GLU.gluTessCallback(tesselator, GLU.GLU_TESS_END, callback) 73 | GLU.gluTessCallback(tesselator, GLU.GLU_TESS_ERROR, callback) 74 | GLU.gluTessCallback(tesselator, GLU.GLU_TESS_COMBINE, callback) 75 | GLU.gluTessNormal(tesselator, 0.0, 0.0, -1.0) 76 | 77 | } 78 | 79 | override fun moveTo(vertex: FloatArray) { 80 | endIfRequired() 81 | drawStart[0] = vertex[0] 82 | drawStart[1] = vertex[1] 83 | } 84 | 85 | override fun lineTo(vertex: FloatArray) { 86 | startIfRequired() 87 | addVertex(vertex) 88 | } 89 | 90 | private fun addVertex(vertex: FloatArray) { 91 | val v = DoubleArray(3) 92 | v[0] = vertex[0].toDouble() 93 | v[1] = vertex[1].toDouble() 94 | GLU.gluTessVertex(tesselator, v, 0, v) 95 | } 96 | 97 | override fun closeLine() { 98 | endIfRequired() 99 | } 100 | 101 | override fun endPoly() { 102 | // shape may just end on the starting point without calling closeLine 103 | endIfRequired() 104 | 105 | GLU.gluTessEndPolygon(tesselator) 106 | GLU.gluDeleteTess(tesselator) 107 | } 108 | 109 | private fun startIfRequired() { 110 | if (!drawing) { 111 | GLU.gluTessBeginContour(tesselator) 112 | addVertex(drawStart) 113 | drawing = true 114 | } 115 | } 116 | 117 | private fun endIfRequired() { 118 | if (drawing) { 119 | GLU.gluTessEndContour(tesselator) 120 | drawing = false 121 | } 122 | } 123 | 124 | private fun beginTess(type: Int) { 125 | drawMode = type 126 | vBuffer.clear() 127 | } 128 | 129 | private fun addTessVertex(vertex: DoubleArray) { 130 | vBuffer.addVertex(vertex[0].toFloat(), vertex[1].toFloat()) 131 | } 132 | 133 | protected abstract fun endTess() 134 | 135 | private inner class TessellatorCallback : GLUtessellatorCallbackAdapter() { 136 | override fun begin(type: Int) { 137 | beginTess(type) 138 | } 139 | 140 | override fun end() { 141 | endTess() 142 | } 143 | 144 | override fun vertex(vertexData: Any?) { 145 | assert(vertexData is DoubleArray) { "Invalid assumption" } 146 | addTessVertex(vertexData as DoubleArray) 147 | } 148 | 149 | override fun combine(coords: DoubleArray, data: Array?, weight: FloatArray?, outData: Array) { 150 | outData[0] = coords 151 | } 152 | 153 | override fun error(errnum: Int) { 154 | throw GLException("Tesselation Error: " + GLU().gluErrorString(errnum)) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/PathVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import com.jogamp.opengl.GL 20 | import java.awt.BasicStroke 21 | 22 | /** 23 | * Receives the calls from a [java.awt.geom.PathIterator] and draws the 24 | * shape as it is visited. The form of this interface and the documentation 25 | * closely mirror that class. 26 | * 27 | * 28 | * Note: The implementation should assume that the array being passed into each 29 | * of these calls is being modified when the call returns, the vertex array is 30 | * recycled and any storage of the points should guard against external 31 | * mutation. 32 | * 33 | 34 | * @see java.awt.geom.PathIterator 35 | */ 36 | interface PathVisitor { 37 | 38 | /** 39 | * Sets the GL context to be used for the next drawing session. 40 | 41 | * @param context The GL context 42 | */ 43 | fun setGLContext(context: GL) 44 | 45 | /** 46 | * Sets the stroke to be used when drawing a path. It's not needed for 47 | * visitors that fill. 48 | */ 49 | fun setStroke(stroke: BasicStroke) 50 | 51 | /** 52 | * Specifies the starting location for a new subpath. 53 | 54 | * @param vertex An array where the first two values are the x,y coordinates of the 55 | * * start of the subpath. 56 | */ 57 | fun moveTo(vertex: FloatArray) 58 | 59 | /** 60 | * Specifies the end point of a line to be drawn from the most recently 61 | * specified point. 62 | 63 | * @param vertex An array where the first two values are the x,y coordinates of the 64 | * * next point in the subpath. 65 | */ 66 | fun lineTo(vertex: FloatArray) 67 | 68 | /** 69 | * Specifies a quadratic parametric curve is to be drawn. The curve is 70 | * interpolated by solving the parametric control equation in the range 71 | * `(t=[0..1])` using the previous point (CP), the first control 72 | * point (P1), and the final interpolated control point (P2). The parametric 73 | * control equation for this curve is: 74 | * 75 | * 76 | * 77 | * P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 78 | * 0 <= t <= 1 79 | 80 | * B(n,m) = mth coefficient of nth degree Bernstein polynomial 81 | * = C(n,m) * t^(m) * (1 - t)^(n-m) 82 | * C(n,m) = Combinations of n things, taken m at a time 83 | * = n! / (m! * (n-m)!) 84 | * 85 | 86 | * @param previousVertex The first control point. The same as the most recent specified 87 | * * vertex. 88 | * * 89 | * @param control The control point and the second vertex, in order. 90 | */ 91 | fun quadTo(previousVertex: FloatArray, control: FloatArray) 92 | 93 | /** 94 | * Specifies a cubic parametric curve to be drawn. The curve is interpolated 95 | * by solving the parametric control equation in the range 96 | * `(t=[0..1])` using the previous point (CP), the first control 97 | * point (P1), the second control point (P2), and the final interpolated 98 | * control point (P3). The parametric control equation for this curve is: 99 | * 100 | * 101 | * 102 | * P(t) = B(3,0)*CP + B(3,1)*P1 + B(3,2)*P2 + B(3,3)*P3 103 | * 0 <= t <= 1 104 | 105 | * B(n,m) = mth coefficient of nth degree Bernstein polynomial 106 | * = C(n,m) * t^(m) * (1 - t)^(n-m) 107 | * C(n,m) = Combinations of n things, taken m at a time 108 | * = n! / (m! * (n-m)!) 109 | * 110 | * 111 | * 112 | * This form of curve is commonly known as a Bzier curve. 113 | 114 | * @param previousVertex The first control point. The same as the most recent specified 115 | * * vertex. 116 | * * 117 | * @param control The two control points and the second vertex, in order. 118 | */ 119 | fun cubicTo(previousVertex: FloatArray, control: FloatArray) 120 | 121 | /** 122 | * Specifies that the preceding subpath should be closed by appending a line 123 | * segment back to the point corresponding to the most recent call to 124 | * `#moveTo(float[])`. 125 | */ 126 | fun closeLine() 127 | 128 | /** 129 | * Starts the polygon or polyline. 130 | 131 | * @param windingRule The winding rule for the polygon. 132 | */ 133 | fun beginPoly(windingRule: Int) 134 | 135 | /** 136 | * Signifies that the polygon or polyline has ended. All cleanup should occur 137 | * and there will be no more calls for this shape. 138 | */ 139 | fun endPoly() 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/impl/RectIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape.impl 18 | 19 | import java.awt.geom.AffineTransform 20 | import java.awt.geom.PathIterator 21 | import java.awt.geom.Rectangle2D 22 | import java.util.* 23 | 24 | /** 25 | * Created by Jonathan on 9/30/2016. 26 | */ 27 | object RectIterator : PathIterator { 28 | 29 | private lateinit var r: Rectangle2D 30 | private var affine: AffineTransform? = null 31 | 32 | private var x: Double = 0.toDouble() 33 | private var y: Double = 0.toDouble() 34 | private var w: Double = 0.toDouble() 35 | private var h: Double = 0.toDouble() 36 | private var index: Int = 0 37 | 38 | fun set(r: Rectangle2D, affine: AffineTransform?) = apply { 39 | this.index = 0 40 | this.r = r 41 | this.x = r.x 42 | this.y = r.y 43 | this.w = r.width 44 | this.h = r.height 45 | this.affine = affine 46 | if (w < 0 || h < 0) { 47 | index = 6 48 | } 49 | } 50 | 51 | /** 52 | * Return the winding rule for determining the insideness of the 53 | * path. 54 | * @see #WIND_EVEN_ODD 55 | * @see #WIND_NON_ZERO 56 | */ 57 | override fun getWindingRule() = PathIterator.WIND_NON_ZERO 58 | 59 | /** 60 | * Tests if there are more points to read. 61 | * @return true if there are more points to read 62 | */ 63 | override fun isDone() = index > 5 64 | 65 | /** 66 | * Moves the iterator to the next segment of the path forwards 67 | * along the primary direction of traversal as long as there are 68 | * more points in that direction. 69 | */ 70 | override fun next() { 71 | index++ 72 | } 73 | 74 | /** 75 | * Returns the coordinates and type of the current path segment in 76 | * the iteration. 77 | * The return value is the path segment type: 78 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 79 | * A float array of length 6 must be passed in and may be used to 80 | * store the coordinates of the point(s). 81 | * Each point is stored as a pair of float x,y coordinates. 82 | * SEG_MOVETO and SEG_LINETO types will return one point, 83 | * SEG_QUADTO will return two points, 84 | * SEG_CUBICTO will return 3 points 85 | * and SEG_CLOSE will not return any points. 86 | * @see #SEG_MOVETO 87 | * @see #SEG_LINETO 88 | * @see #SEG_QUADTO 89 | * @see #SEG_CUBICTO 90 | * @see #SEG_CLOSE 91 | */ 92 | override fun currentSegment(coords: FloatArray): Int { 93 | if (isDone) { 94 | throw NoSuchElementException("rect iterator out of bounds") 95 | } 96 | if (index == 5) { 97 | return PathIterator.SEG_CLOSE 98 | } 99 | coords[0] = x.toFloat() 100 | coords[1] = y.toFloat() 101 | if (index == 1 || index == 2) { 102 | coords[0] += w.toFloat() 103 | } 104 | if (index == 2 || index == 3) { 105 | coords[1] += h.toFloat() 106 | } 107 | affine?.transform(coords, 0, coords, 0, 1) 108 | 109 | return (if (index == 0) PathIterator.SEG_MOVETO else PathIterator.SEG_LINETO) 110 | } 111 | 112 | /** 113 | * Returns the coordinates and type of the current path segment in 114 | * the iteration. 115 | * The return value is the path segment type: 116 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 117 | * A double array of length 6 must be passed in and may be used to 118 | * store the coordinates of the point(s). 119 | * Each point is stored as a pair of double x,y coordinates. 120 | * SEG_MOVETO and SEG_LINETO types will return one point, 121 | * SEG_QUADTO will return two points, 122 | * SEG_CUBICTO will return 3 points 123 | * and SEG_CLOSE will not return any points. 124 | * @see #SEG_MOVETO 125 | * @see #SEG_LINETO 126 | * @see #SEG_QUADTO 127 | * @see #SEG_CUBICTO 128 | * @see #SEG_CLOSE 129 | */ 130 | override fun currentSegment(coords: DoubleArray): Int { 131 | if (isDone) { 132 | throw NoSuchElementException("rect iterator out of bounds") 133 | } 134 | if (index == 5) { 135 | return PathIterator.SEG_CLOSE 136 | } 137 | coords[0] = x 138 | coords[1] = y 139 | if (index == 1 || index == 2) { 140 | coords[0] += w 141 | } 142 | if (index == 2 || index == 3) { 143 | coords[1] += h 144 | } 145 | affine?.transform(coords, 0, coords, 0, 1) 146 | 147 | return (if (index == 0) PathIterator.SEG_MOVETO else PathIterator.SEG_LINETO) 148 | } 149 | 150 | 151 | operator fun invoke(rect: Rectangle2D, affine: AffineTransform? = null) = set(rect, affine) 152 | 153 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/GL2StringDrawer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.* 21 | import com.jogamp.opengl.glu.gl2.GLUgl2 22 | import com.jogamp.opengl.util.gl2.GLUT.* 23 | import org.anglur.joglext.cacheable.CachedIntArray 24 | import org.anglur.joglext.jogl2d.font.* 25 | import org.anglur.joglext.jogl2d.impl.AbstractTextDrawer 26 | import java.text.AttributedCharacterIterator 27 | 28 | class GL2StringDrawer : AbstractTextDrawer() { 29 | 30 | override fun dispose() { 31 | } 32 | 33 | override fun drawString(iterator: AttributedCharacterIterator, x: Float, y: Float) = 34 | drawString(iterator, x.toInt(), y.toInt()) 35 | 36 | override fun drawString(iterator: AttributedCharacterIterator, x: Int, y: Int) { 37 | val builder = StringBuilder(iterator.endIndex - iterator.beginIndex) 38 | while (iterator.next() != AttributedCharacterIterator.DONE) { 39 | builder.append(iterator.current()) 40 | } 41 | 42 | drawString(builder.toString(), x, y) 43 | } 44 | 45 | override fun drawString(string: String, x: Float, y: Float) = 46 | drawString(string, x.toInt(), y.toInt()) 47 | 48 | override fun drawString(string: String, x: Int, y: Int) { 49 | g2d.glContext.gl.gL2.glRasterPos2d(x.toDouble(), y.toDouble()) 50 | glutBitmapString(BITMAP_TIMES_ROMAN_10, string) 51 | } 52 | 53 | private val swapbytes = CachedIntArray(1) 54 | private val lsbfirst = CachedIntArray(1) 55 | private val rowlength = CachedIntArray(1) 56 | private val skiprows = CachedIntArray(1) 57 | private val skippixels = CachedIntArray(1) 58 | private val alignment = CachedIntArray(1) 59 | 60 | fun glutBitmapString(font: Int, string: String) { 61 | val gl = GLUgl2.getCurrentGL2() 62 | 63 | beginBitmap(gl) 64 | val len = string.length 65 | for (i in 0..len - 1) { 66 | bitmapCharacterImpl(gl, font, string[i]) 67 | } 68 | endBitmap(gl) 69 | } 70 | 71 | private fun beginBitmap(gl: GL2) { 72 | gl.glGetIntegerv(GL2GL3.GL_UNPACK_SWAP_BYTES, swapbytes, 0) 73 | gl.glGetIntegerv(GL2GL3.GL_UNPACK_LSB_FIRST, lsbfirst, 0) 74 | gl.glGetIntegerv(GL2ES2.GL_UNPACK_ROW_LENGTH, rowlength, 0) 75 | gl.glGetIntegerv(GL2ES2.GL_UNPACK_SKIP_ROWS, skiprows, 0) 76 | gl.glGetIntegerv(GL2ES2.GL_UNPACK_SKIP_PIXELS, skippixels, 0) 77 | gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, alignment, 0) 78 | gl.glPixelStorei(GL2GL3.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE) 79 | gl.glPixelStorei(GL2GL3.GL_UNPACK_LSB_FIRST, GL.GL_FALSE) 80 | gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, 0) 81 | gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_ROWS, 0) 82 | gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_PIXELS, 0) 83 | gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1) 84 | } 85 | 86 | private fun endBitmap(gl: GL2) { 87 | /* Restore saved modes. */ 88 | gl.glPixelStorei(GL2GL3.GL_UNPACK_SWAP_BYTES, swapbytes[0]) 89 | gl.glPixelStorei(GL2GL3.GL_UNPACK_LSB_FIRST, lsbfirst[0]) 90 | gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, rowlength[0]) 91 | gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_ROWS, skiprows[0]) 92 | gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_PIXELS, skippixels[0]) 93 | gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, alignment[0]) 94 | } 95 | 96 | private fun bitmapCharacterImpl(gl: GL2, font: Int, cin: Char) { 97 | val fontinfo = getBitmapFont(font) 98 | val c = cin.toInt() and 0xFFFF 99 | if (c < fontinfo.first || c >= fontinfo.first + fontinfo.num_chars) 100 | return 101 | val ch = fontinfo.ch[c - fontinfo.first] 102 | if (ch != null) { 103 | gl.glBitmap(ch.width, ch.height, ch.xorig, ch.yorig, 104 | ch.advance, 0f, ch.bitmap, 0) 105 | } 106 | } 107 | 108 | private val bitmapFonts = arrayOfNulls(9) 109 | 110 | private fun getBitmapFont(font: Int): BitmapFontRec { 111 | var rec: BitmapFontRec? = bitmapFonts[font] 112 | if (rec == null) { 113 | when (font) { 114 | BITMAP_9_BY_15 -> rec = GLUTBitmap9x15.glutBitmap9By15 115 | BITMAP_8_BY_13 -> rec = GLUTBitmap8x13.glutBitmap8By13 116 | BITMAP_TIMES_ROMAN_10 -> rec = GLUTBitmapTimesRoman10.glutBitmapTimesRoman10 117 | BITMAP_TIMES_ROMAN_24 -> rec = GLUTBitmapTimesRoman24.glutBitmapTimesRoman24 118 | BITMAP_HELVETICA_10 -> rec = GLUTBitmapHelvetica10.glutBitmapHelvetica10 119 | BITMAP_HELVETICA_12 -> rec = GLUTBitmapHelvetica12.glutBitmapHelvetica12 120 | BITMAP_HELVETICA_18 -> rec = GLUTBitmapHelvetica18.glutBitmapHelvetica18 121 | else -> throw GLException("Unknown bitmap font number " + font) 122 | } 123 | bitmapFonts[font] = rec 124 | } 125 | return rec 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/gl2/FastLineVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl.gl2 18 | 19 | 20 | import com.jogamp.opengl.GL 21 | import com.jogamp.opengl.GL2 22 | import org.anglur.joglext.cacheable.CachedFloatArray 23 | import org.anglur.joglext.jogl2d.VertexBuffer 24 | import org.anglur.joglext.jogl2d.impl.SimplePathVisitor 25 | import java.awt.BasicStroke 26 | 27 | /** 28 | * Draws a line using the native GL implementation of a line. This is only 29 | * appropriate if the width of the line is less than a certain number of pixels 30 | * (not coordinate units) so that the user cannot see that the join and 31 | * endpoints are different. See [.isValid] for a set of 32 | * useful criteria. 33 | */ 34 | class FastLineVisitor : SimplePathVisitor() { 35 | 36 | private var testMatrix = CachedFloatArray(16) 37 | 38 | private var buffer = VertexBuffer.sharedBuffer 39 | 40 | private lateinit var gl: GL2 41 | 42 | private lateinit var stroke2: BasicStroke 43 | 44 | private var glLineWidth: Float = 0.toFloat() 45 | 46 | override fun setGLContext(context: GL) { 47 | gl = context.gL2 48 | } 49 | 50 | override fun setStroke(stroke: BasicStroke) { 51 | gl.glLineWidth(glLineWidth) 52 | gl.glPointSize(glLineWidth) 53 | 54 | /* 55 | * Not perfect copy of the BasicStroke implementation, but it does get 56 | * decently close. The pattern is pretty much the same. I think it's pretty 57 | * much impossible to do with out a fragment shader and only the fixed 58 | * function pipeline. 59 | */ 60 | val dash = stroke.dashArray 61 | if (dash != null) { 62 | var totalLength = 0f 63 | for (f in dash) { 64 | totalLength += f 65 | } 66 | 67 | var lengthSoFar = 0f 68 | var prevIndex = 0 69 | var mask = 0 70 | for (i in dash.indices) { 71 | lengthSoFar += dash[i] 72 | 73 | val nextIndex = (lengthSoFar / totalLength * 16).toInt() 74 | for (j in prevIndex..nextIndex - 1) { 75 | mask = mask or (i.inv() and 1 shl j) 76 | } 77 | 78 | prevIndex = nextIndex 79 | } 80 | 81 | /* 82 | * XXX Should actually use the stroke phase, but not sure how yet. 83 | */ 84 | 85 | gl.glEnable(GL2.GL_LINE_STIPPLE) 86 | val factor = totalLength.toInt() 87 | gl.glLineStipple(factor shr 4, mask.toShort()) 88 | } else { 89 | gl.glDisable(GL2.GL_LINE_STIPPLE) 90 | } 91 | 92 | this.stroke2 = stroke 93 | } 94 | 95 | /** 96 | * Returns `true` if this class can reasonably render the line. This 97 | * takes into account whether or not the transform will blow the line width 98 | * out of scale and it obvious that we aren't drawing correct corners and line 99 | * endings. 100 | * 101 | * 102 | * 103 | * 104 | * Note: This must be called before [.setStroke]. If this 105 | * returns `false` then this renderer should not be used. 106 | * 107 | */ 108 | fun isValid(stroke: BasicStroke): Boolean { 109 | // if the dash length is odd, I don't know how to handle that yet 110 | val dash = stroke.dashArray 111 | if (dash != null && dash.size and 1 == 1) { 112 | return false 113 | } 114 | 115 | gl.glGetFloatv(GL2.GL_MODELVIEW_MATRIX, testMatrix, 0) 116 | 117 | val scaleX = Math.abs(testMatrix[0]) 118 | val scaleY = Math.abs(testMatrix[5]) 119 | 120 | // scales are different, we can't get a good line width 121 | if (Math.abs(scaleX - scaleY) > 1e-6) { 122 | return false 123 | } 124 | 125 | val strokeWidth = stroke.lineWidth 126 | 127 | // gl line width is in pixels, convert to pixel width 128 | glLineWidth = strokeWidth * scaleX 129 | 130 | // we'll only try if it's a thin line 131 | return glLineWidth <= 2 132 | } 133 | 134 | override fun moveTo(vertex: FloatArray) { 135 | drawLine(false) 136 | drawLine(false) 137 | buffer.addVertex(vertex, 0, 1) 138 | } 139 | 140 | override fun lineTo(vertex: FloatArray) { 141 | buffer.addVertex(vertex, 0, 1) 142 | } 143 | 144 | override fun closeLine() { 145 | drawLine(true) 146 | } 147 | 148 | private fun drawLine(close: Boolean) { 149 | val buf = buffer.buffer 150 | val p = buf.position() 151 | buffer.drawBuffer(gl, if (close) GL2.GL_LINE_LOOP else GL2.GL_LINE_STRIP) 152 | 153 | /* 154 | * We'll ignore butt endcaps, but we'll pretend like we're drawing round, 155 | * bevel or miter corners as well as round or square corners by just putting 156 | * a point there. Since our line should be very thin, pixel-wise, it 157 | * shouldn't be noticeable. 158 | */ 159 | if (stroke2.dashArray == null) { 160 | buf.position(p) 161 | buffer.drawBuffer(gl, GL2.GL_POINTS) 162 | } 163 | 164 | buffer.clear() 165 | } 166 | 167 | override fun beginPoly(windingRule: Int) { 168 | buffer.clear() 169 | 170 | /* 171 | * pen hangs down and to the right. See java.awt.Graphics 172 | */ 173 | gl.glMatrixMode(GL2.GL_MODELVIEW) 174 | gl.glPushMatrix() 175 | gl.glTranslatef(0.5f, 0.5f, 0f) 176 | 177 | gl.glPushAttrib(GL2.GL_LINE_BIT or GL2.GL_POINT_BIT) 178 | } 179 | 180 | override fun endPoly() { 181 | drawLine(false) 182 | gl.glDisable(GL2.GL_LINE_STIPPLE) 183 | gl.glPopMatrix() 184 | 185 | gl.glPopAttrib() 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/impl/EllipseIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape.impl 18 | 19 | import java.awt.geom.AffineTransform 20 | import java.awt.geom.Ellipse2D 21 | import java.awt.geom.PathIterator 22 | import java.util.* 23 | 24 | object EllipseIterator : PathIterator { 25 | 26 | private lateinit var e: Ellipse2D 27 | private var affine: AffineTransform? = null 28 | 29 | private var x: Double = 0.toDouble() 30 | private var y: Double = 0.toDouble() 31 | private var w: Double = 0.toDouble() 32 | private var h: Double = 0.toDouble() 33 | private var index: Int = 0 34 | 35 | fun set(r: Ellipse2D, affine: AffineTransform?) = apply { 36 | this.index = 0 37 | this.e = r 38 | this.x = r.x 39 | this.y = r.y 40 | this.w = r.width 41 | this.h = r.height 42 | this.affine = affine 43 | if (w < 0 || h < 0) { 44 | index = 6 45 | } 46 | } 47 | 48 | /** 49 | * Return the winding rule for determining the insideness of the 50 | * path. 51 | * @see #WIND_EVEN_ODD 52 | * @see #WIND_NON_ZERO 53 | */ 54 | override fun getWindingRule() = PathIterator.WIND_NON_ZERO 55 | 56 | /** 57 | * Tests if there are more points to read. 58 | * @return true if there are more points to read 59 | */ 60 | override fun isDone() = index > 5 61 | 62 | /** 63 | * Moves the iterator to the next segment of the path forwards 64 | * along the primary direction of traversal as long as there are 65 | * more points in that direction. 66 | */ 67 | override fun next() { 68 | index++ 69 | } 70 | 71 | /** 72 | * Returns the coordinates and type of the current path segment in 73 | * the iteration. 74 | * The return value is the path segment type: 75 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 76 | * A float array of length 6 must be passed in and may be used to 77 | * store the coordinates of the point(s).N 78 | * Each point is stored as a pair of float x,y coordinates. 79 | * SEG_MOVETO and SEG_LINETO types will return one point, 80 | * SEG_QUADTO will return two points, 81 | * SEG_CUBICTO will return 3 points 82 | * and SEG_CLOSE will not return any points. 83 | * @see #SEG_MOVETO 84 | * @see #SEG_LINETO 85 | * @see #SEG_QUADTO 86 | * @see #SEG_CUBICTO 87 | * @see #SEG_CLOSE 88 | */ 89 | override fun currentSegment(coords: FloatArray): Int { 90 | if (isDone) { 91 | throw NoSuchElementException("ellipse iterator out of bounds") 92 | } 93 | if (index == 5) { 94 | return PathIterator.SEG_CLOSE 95 | } 96 | if (index == 0) { 97 | val ctrls = ctrlpts[3] 98 | coords[0] = (x + ctrls[4] * w).toFloat() 99 | coords[1] = (y + ctrls[5] * h).toFloat() 100 | affine?.transform(coords, 0, coords, 0, 1) 101 | 102 | return PathIterator.SEG_MOVETO 103 | } 104 | val ctrls = ctrlpts[index - 1] 105 | coords[0] = (x + ctrls[0] * w).toFloat() 106 | coords[1] = (y + ctrls[1] * h).toFloat() 107 | coords[2] = (x + ctrls[2] * w).toFloat() 108 | coords[3] = (y + ctrls[3] * h).toFloat() 109 | coords[4] = (x + ctrls[4] * w).toFloat() 110 | coords[5] = (y + ctrls[5] * h).toFloat() 111 | affine?.transform(coords, 0, coords, 0, 3) 112 | 113 | return PathIterator.SEG_CUBICTO 114 | } 115 | 116 | /** 117 | * Returns the coordinates and type of the current path segment in 118 | * the iteration. 119 | * The return value is the path segment type: 120 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 121 | * A double array of length 6 must be passed in and may be used to 122 | * store the coordinates of the point(s). 123 | * Each point is stored as a pair of double x,y coordinates. 124 | * SEG_MOVETO and SEG_LINETO types will return one point, 125 | * SEG_QUADTO will return two points, 126 | * SEG_CUBICTO will return 3 points 127 | * and SEG_CLOSE will not return any points. 128 | * @see #SEG_MOVETO 129 | * @see #SEG_LINETO 130 | * @see #SEG_QUADTO 131 | * @see #SEG_CUBICTO 132 | * @see #SEG_CLOSE 133 | */ 134 | override fun currentSegment(coords: DoubleArray): Int { 135 | if (isDone) { 136 | throw NoSuchElementException("ellipse iterator out of bounds") 137 | } 138 | if (index == 5) { 139 | return PathIterator.SEG_CLOSE 140 | } 141 | if (index == 0) { 142 | val ctrls = ctrlpts[3] 143 | coords[0] = x + ctrls[4] * w 144 | coords[1] = y + ctrls[5] * h 145 | affine?.transform(coords, 0, coords, 0, 1) 146 | 147 | return PathIterator.SEG_MOVETO 148 | } 149 | val ctrls = ctrlpts[index - 1] 150 | coords[0] = x + ctrls[0] * w 151 | coords[1] = y + ctrls[1] * h 152 | coords[2] = x + ctrls[2] * w 153 | coords[3] = y + ctrls[3] * h 154 | coords[4] = x + ctrls[4] * w 155 | coords[5] = y + ctrls[5] * h 156 | affine?.transform(coords, 0, coords, 0, 3) 157 | 158 | return PathIterator.SEG_CUBICTO 159 | } 160 | 161 | operator fun invoke(rect: Ellipse2D, affine: AffineTransform? = null) = set(rect, affine) 162 | 163 | // ArcIterator.btan(Math.PI/2) 164 | val CtrlVal = 0.5522847498307933 165 | /* 166 | * ctrlpts contains the control points for a set of 4 cubic 167 | * bezier curves that approximate a circle of radius 0.5 168 | * centered at 0.5, 0.5 169 | */ 170 | private val pcv = 0.5 + CtrlVal * 0.5 171 | private val ncv = 0.5 - CtrlVal * 0.5 172 | private val ctrlpts = arrayOf(doubleArrayOf(1.0, pcv, pcv, 1.0, 0.5, 1.0), doubleArrayOf(ncv, 1.0, 0.0, pcv, 0.0, 0.5), doubleArrayOf(0.0, ncv, ncv, 0.0, 0.5, 0.0), doubleArrayOf(pcv, 0.0, 1.0, ncv, 1.0, 0.5)) 173 | 174 | } 175 | 176 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/impl/RoundRectIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape.impl 18 | 19 | import java.awt.geom.AffineTransform 20 | import java.awt.geom.PathIterator 21 | import java.awt.geom.Rectangle2D 22 | import java.awt.geom.RoundRectangle2D 23 | import java.util.* 24 | 25 | /** 26 | * Created by Jonathan on 9/30/2016. 27 | */ 28 | object RoundRectIterator : PathIterator { 29 | 30 | private lateinit var r: Rectangle2D 31 | private var affine: AffineTransform? = null 32 | 33 | private var x: Double = 0.toDouble() 34 | private var y: Double = 0.toDouble() 35 | private var w: Double = 0.toDouble() 36 | private var h: Double = 0.toDouble() 37 | private var aw: Double = 0.toDouble() 38 | private var ah: Double = 0.toDouble() 39 | private var index: Int = 0 40 | 41 | fun set(r: RoundRectangle2D, affine: AffineTransform?) = apply { 42 | this.index = 0 43 | this.x = r.x 44 | this.y = r.y 45 | this.w = r.width 46 | this.h = r.height 47 | this.aw = Math.min(w, Math.abs(r.arcWidth)) 48 | this.ah = Math.min(h, Math.abs(r.arcHeight)) 49 | this.affine = affine 50 | if (aw < 0 || ah < 0) { 51 | // Don't draw anything... 52 | index = ctrlpts.size 53 | } 54 | } 55 | 56 | /** 57 | * Return the winding rule for determining the insideness of the 58 | * path. 59 | * @see #WIND_EVEN_ODD 60 | * @see #WIND_NON_ZERO 61 | */ 62 | override fun getWindingRule() = PathIterator.WIND_NON_ZERO 63 | 64 | /** 65 | * Tests if there are more points to read. 66 | * @return true if there are more points to read 67 | */ 68 | override fun isDone() = index >= ctrlpts.size 69 | 70 | /** 71 | * Moves the iterator to the next segment of the path forwards 72 | * along the primary direction of traversal as long as there are 73 | * more points in that direction. 74 | */ 75 | override fun next() { 76 | index++ 77 | } 78 | 79 | private val angle = Math.PI / 4.0 80 | private val a = 1.0 - Math.cos(angle) 81 | private val b = Math.tan(angle) 82 | private val c = Math.sqrt(1.0 + b * b) - 1 + a 83 | private val cv = 4.0 / 3.0 * a * b / c 84 | private val acv = (1.0 - cv) / 2.0 85 | 86 | // For each array: 87 | // 4 values for each point {v0, v1, v2, v3}: 88 | // point = (x + v0 * w + v1 * arcWidth, 89 | // y + v2 * h + v3 * arcHeight); 90 | private val ctrlpts = arrayOf(doubleArrayOf(0.0, 0.0, 0.0, 0.5), doubleArrayOf(0.0, 0.0, 1.0, -0.5), doubleArrayOf(0.0, 0.0, 1.0, -acv, 0.0, acv, 1.0, 0.0, 0.0, 0.5, 1.0, 0.0), doubleArrayOf(1.0, -0.5, 1.0, 0.0), doubleArrayOf(1.0, -acv, 1.0, 0.0, 1.0, 0.0, 1.0, -acv, 1.0, 0.0, 1.0, -0.5), doubleArrayOf(1.0, 0.0, 0.0, 0.5), doubleArrayOf(1.0, 0.0, 0.0, acv, 1.0, -acv, 0.0, 0.0, 1.0, -0.5, 0.0, 0.0), doubleArrayOf(0.0, 0.5, 0.0, 0.0), doubleArrayOf(0.0, acv, 0.0, 0.0, 0.0, 0.0, 0.0, acv, 0.0, 0.0, 0.0, 0.5), doubleArrayOf()) 91 | private val types = intArrayOf(PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_LINETO, PathIterator.SEG_CUBICTO, PathIterator.SEG_CLOSE) 92 | 93 | /** 94 | * Returns the coordinates and type of the current path segment in 95 | * the iteration. 96 | * The return value is the path segment type: 97 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 98 | * A float array of length 6 must be passed in and may be used to 99 | * store the coordinates of the point(s). 100 | * Each point is stored as a pair of float x,y coordinates. 101 | * SEG_MOVETO and SEG_LINETO types will return one point, 102 | * SEG_QUADTO will return two points, 103 | * SEG_CUBICTO will return 3 points 104 | * and SEG_CLOSE will not return any points. 105 | * @see .SEG_MOVETO 106 | 107 | * @see .SEG_LINETO 108 | 109 | * @see .SEG_QUADTO 110 | 111 | * @see .SEG_CUBICTO 112 | 113 | * @see .SEG_CLOSE 114 | */ 115 | override fun currentSegment(coords: FloatArray): Int { 116 | if (isDone) { 117 | throw NoSuchElementException("roundrect iterator out of bounds") 118 | } 119 | val ctrls = ctrlpts[index] 120 | var nc = 0 121 | var i = 0 122 | while (i < ctrls.size) { 123 | coords[nc++] = (x + ctrls[i + 0] * w + ctrls[i + 1] * aw).toFloat() 124 | coords[nc++] = (y + ctrls[i + 2] * h + ctrls[i + 3] * ah).toFloat() 125 | i += 4 126 | } 127 | affine?.transform(coords, 0, coords, 0, nc / 2) 128 | 129 | return types[index] 130 | } 131 | 132 | /** 133 | * Returns the coordinates and type of the current path segment in 134 | * the iteration. 135 | * The return value is the path segment type: 136 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 137 | * A double array of length 6 must be passed in and may be used to 138 | * store the coordinates of the point(s). 139 | * Each point is stored as a pair of double x,y coordinates. 140 | * SEG_MOVETO and SEG_LINETO types will return one point, 141 | * SEG_QUADTO will return two points, 142 | * SEG_CUBICTO will return 3 points 143 | * and SEG_CLOSE will not return any points. 144 | * @see .SEG_MOVETO 145 | 146 | * @see .SEG_LINETO 147 | 148 | * @see .SEG_QUADTO 149 | 150 | * @see .SEG_CUBICTO 151 | 152 | * @see .SEG_CLOSE 153 | */ 154 | override fun currentSegment(coords: DoubleArray): Int { 155 | if (isDone) { 156 | throw NoSuchElementException("roundrect iterator out of bounds") 157 | } 158 | val ctrls = ctrlpts[index] 159 | var nc = 0 160 | var i = 0 161 | while (i < ctrls.size) { 162 | coords[nc++] = x + ctrls[i + 0] * w + ctrls[i + 1] * aw 163 | coords[nc++] = y + ctrls[i + 2] * h + ctrls[i + 3] * ah 164 | i += 4 165 | } 166 | affine?.transform(coords, 0, coords, 0, nc / 2) 167 | 168 | return types[index] 169 | } 170 | 171 | 172 | operator fun invoke(rect: RoundRectangle2D, affine: AffineTransform? = null) = set(rect, affine) 173 | 174 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractShapeHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | 20 | import org.anglur.joglext.cacheable.CachedFloatArray 21 | import org.anglur.joglext.jogl2d.GLG2DShapeHelper 22 | import org.anglur.joglext.jogl2d.GLGraphics2D 23 | import org.anglur.joglext.jogl2d.PathVisitor 24 | import org.anglur.joglext.jogl2d.shape.ShapeIterator 25 | import java.awt.BasicStroke 26 | import java.awt.RenderingHints 27 | import java.awt.RenderingHints.Key 28 | import java.awt.Shape 29 | import java.awt.Stroke 30 | import java.awt.geom.* 31 | import java.util.* 32 | 33 | abstract class AbstractShapeHelper : GLG2DShapeHelper { 34 | 35 | private var strokeStack: Deque = ArrayDeque() 36 | 37 | init { 38 | strokeStack.push(BasicStroke()) 39 | } 40 | 41 | private val EMPTY = BasicStroke() 42 | 43 | override fun setG2D(g2d: GLGraphics2D) { 44 | strokeStack.clear() 45 | strokeStack.push(EMPTY) 46 | } 47 | 48 | override fun push(newG2d: GLGraphics2D) { 49 | strokeStack.push(newG2d.stroke) 50 | } 51 | 52 | override fun pop(parentG2d: GLGraphics2D) { 53 | strokeStack.pop() 54 | } 55 | 56 | override fun setHint(key: Key, value: Any?) { 57 | // nop 58 | } 59 | 60 | override fun resetHints() { 61 | setHint(RenderingHints.KEY_ANTIALIASING, null) 62 | } 63 | 64 | override fun dispose() { 65 | // nop 66 | } 67 | 68 | override var stroke: Stroke 69 | get() = strokeStack.peek() 70 | set(stroke) { 71 | strokeStack.pop() 72 | strokeStack.push(stroke) 73 | } 74 | 75 | override fun drawRoundRect(x: Int, y: Int, width: Int, height: Int, arcWidth: Int, arcHeight: Int, fill: Boolean) { 76 | ROUND_RECT.setRoundRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), arcWidth.toFloat(), arcHeight.toFloat()) 77 | if (fill) { 78 | fill(ROUND_RECT, true) 79 | } else { 80 | draw(ROUND_RECT) 81 | } 82 | } 83 | 84 | override fun drawRect(x: Int, y: Int, width: Int, height: Int, fill: Boolean) { 85 | RECT.setRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) 86 | if (fill) { 87 | fill(RECT, true) 88 | } else { 89 | draw(RECT) 90 | } 91 | } 92 | 93 | override fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int) { 94 | LINE.setLine(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) 95 | draw(LINE) 96 | } 97 | 98 | override fun drawOval(x: Int, y: Int, width: Int, height: Int, fill: Boolean) { 99 | ELLIPSE.setFrame(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) 100 | if (fill) { 101 | fill(ELLIPSE, true) 102 | } else { 103 | draw(ELLIPSE) 104 | } 105 | } 106 | 107 | override fun drawArc(x: Int, y: Int, width: Int, height: Int, startAngle: Int, arcAngle: Int, fill: Boolean) { 108 | ARC.setArc(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble(), startAngle.toDouble(), arcAngle.toDouble(), if (fill) Arc2D.PIE else Arc2D.OPEN) 109 | if (fill) { 110 | fill(ARC, true) 111 | } else { 112 | draw(ARC) 113 | } 114 | } 115 | 116 | override fun drawPolyline(xPoints: IntArray, yPoints: IntArray, nPoints: Int) { 117 | drawPoly(xPoints, yPoints, nPoints, false, false) 118 | } 119 | 120 | override fun drawPolygon(xPoints: IntArray, yPoints: IntArray, nPoints: Int, fill: Boolean) { 121 | drawPoly(xPoints, yPoints, nPoints, fill, true) 122 | } 123 | 124 | private fun drawPoly(xPoints: IntArray, yPoints: IntArray, nPoints: Int, fill: Boolean, close: Boolean) { 125 | val path = Path2D.Float(PathIterator.WIND_NON_ZERO, nPoints) 126 | path.moveTo(xPoints[0].toFloat(), yPoints[0].toFloat()) 127 | for (i in 1..nPoints - 1) { 128 | path.lineTo(xPoints[i].toFloat(), yPoints[i].toFloat()) 129 | } 130 | 131 | if (close) { 132 | path.closePath() 133 | } 134 | 135 | if (fill) { 136 | fill(path) 137 | } else { 138 | draw(path) 139 | } 140 | } 141 | 142 | override fun fill(shape: Shape) { 143 | if (shape is Rectangle2D || 144 | shape is Ellipse2D || 145 | shape is Arc2D || 146 | shape is RoundRectangle2D) { 147 | fill(shape, true) 148 | } else { 149 | fill(shape, false) 150 | } 151 | } 152 | 153 | protected abstract fun fill(shape: Shape, isDefinitelySimpleConvex: Boolean) 154 | 155 | protected fun traceShape(shape: Shape, visitor: PathVisitor) { 156 | visitShape(shape, visitor) 157 | } 158 | 159 | companion object { 160 | /** 161 | * We know this is single-threaded, so we can use these as archetypes. 162 | */ 163 | private val ELLIPSE = Ellipse2D.Float() 164 | private val ROUND_RECT = RoundRectangle2D.Float() 165 | private val ARC = Arc2D.Float() 166 | private val RECT = Rectangle2D.Float() 167 | private val LINE = Line2D.Float() 168 | 169 | fun visitShape(shape: Shape, visitor: PathVisitor) { 170 | val iterator = ShapeIterator.get(shape) 171 | visitor.beginPoly(iterator.windingRule) 172 | 173 | val coords = CachedFloatArray(10) 174 | val previousVertex = CachedFloatArray(2) 175 | while (!iterator.isDone) { 176 | val type = iterator.currentSegment(coords) 177 | when (type) { 178 | PathIterator.SEG_MOVETO -> visitor.moveTo(coords) 179 | 180 | PathIterator.SEG_LINETO -> visitor.lineTo(coords) 181 | 182 | PathIterator.SEG_QUADTO -> visitor.quadTo(previousVertex, coords) 183 | 184 | PathIterator.SEG_CUBICTO -> visitor.cubicTo(previousVertex, coords) 185 | 186 | PathIterator.SEG_CLOSE -> visitor.closeLine() 187 | } 188 | 189 | when (type) { 190 | PathIterator.SEG_LINETO, PathIterator.SEG_MOVETO -> { 191 | previousVertex[0] = coords[0] 192 | previousVertex[1] = coords[1] 193 | } 194 | 195 | PathIterator.SEG_QUADTO -> { 196 | previousVertex[0] = coords[2] 197 | previousVertex[1] = coords[3] 198 | } 199 | 200 | PathIterator.SEG_CUBICTO -> { 201 | previousVertex[0] = coords[4] 202 | previousVertex[1] = coords[5] 203 | } 204 | } 205 | iterator.next() 206 | } 207 | 208 | visitor.endPoly() 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/shape/impl/ArcIterator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.shape.impl 18 | 19 | import java.awt.geom.AffineTransform 20 | import java.awt.geom.Arc2D 21 | import java.awt.geom.PathIterator 22 | import java.util.* 23 | 24 | object ArcIterator : PathIterator { 25 | 26 | private lateinit var a: Arc2D 27 | private var affine: AffineTransform? = null 28 | 29 | private var x: Double = 0.toDouble() 30 | private var y: Double = 0.toDouble() 31 | private var w: Double = 0.toDouble() 32 | private var h: Double = 0.toDouble() 33 | private var angStRad: Double = 0.toDouble() 34 | private var increment: Double = 0.toDouble() 35 | private var cv: Double = 0.toDouble() 36 | private var arcSegs: Int = 0 37 | private var lineSegs: Int = 0 38 | private var index: Int = 0 39 | 40 | fun set(a: Arc2D, affine: AffineTransform?) = apply { 41 | this.index = 0 42 | this.a = a 43 | this.w = a.width / 2 44 | this.h = a.height / 2 45 | this.x = a.x + w 46 | this.y = a.y + h 47 | this.angStRad = -Math.toRadians(a.angleStart) 48 | this.affine = affine 49 | val ext = -a.angleExtent 50 | if (ext >= 360.0 || ext <= -360) { 51 | arcSegs = 4 52 | this.increment = Math.PI / 2 53 | this.cv = 0.5522847498307933 54 | if (ext < 0) { 55 | increment = -increment 56 | cv = -cv 57 | } 58 | } else { 59 | arcSegs = Math.ceil(Math.abs(ext) / 90.0).toInt() 60 | this.increment = Math.toRadians(ext / arcSegs) 61 | this.cv = btan(increment) 62 | if (cv == 0.0) { 63 | arcSegs = 0 64 | } 65 | } 66 | when (a.arcType) { 67 | Arc2D.OPEN -> lineSegs = 0 68 | Arc2D.CHORD -> lineSegs = 1 69 | Arc2D.PIE -> lineSegs = 2 70 | } 71 | if (w < 0 || h < 0) { 72 | arcSegs = -1 73 | lineSegs = -1 74 | } 75 | } 76 | 77 | /** 78 | * Return the winding rule for determining the insideness of the 79 | * path. 80 | * @see #WIND_EVEN_ODD 81 | * @see #WIND_NON_ZERO 82 | */ 83 | override fun getWindingRule() = PathIterator.WIND_NON_ZERO 84 | 85 | /** 86 | * Tests if there are more points to read. 87 | * @return true if there are more points to read 88 | */ 89 | override fun isDone() = index > arcSegs + lineSegs 90 | 91 | /** 92 | * Moves the iterator to the next segment of the path forwards 93 | * along the primary direction of traversal as long as there are 94 | * more points in that direction. 95 | */ 96 | override fun next() { 97 | index++ 98 | } 99 | 100 | private fun btan(increment: Double): Double { 101 | var increment = increment 102 | increment /= 2.0 103 | return 4.0 / 3.0 * Math.sin(increment) / (1.0 + Math.cos(increment)) 104 | } 105 | 106 | /** 107 | * Returns the coordinates and type of the current path segment in 108 | * the iteration. 109 | * The return value is the path segment type: 110 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 111 | * A float array of length 6 must be passed in and may be used to 112 | * store the coordinates of the point(s). 113 | * Each point is stored as a pair of float x,y coordinates. 114 | * SEG_MOVETO and SEG_LINETO types will return one point, 115 | * SEG_QUADTO will return two points, 116 | * SEG_CUBICTO will return 3 points 117 | * and SEG_CLOSE will not return any points. 118 | * @see #SEG_MOVETO 119 | * @see #SEG_LINETO 120 | * @see #SEG_QUADTO 121 | * @see #SEG_CUBICTO 122 | * @see #SEG_CLOSE 123 | */ 124 | override fun currentSegment(coords: FloatArray): Int { 125 | if (isDone) { 126 | throw NoSuchElementException("arc iterator out of bounds") 127 | } 128 | var angle = angStRad 129 | if (index == 0) { 130 | coords[0] = (x + Math.cos(angle) * w).toFloat() 131 | coords[1] = (y + Math.sin(angle) * h).toFloat() 132 | affine?.transform(coords, 0, coords, 0, 1) 133 | 134 | return PathIterator.SEG_MOVETO 135 | } 136 | if (index > arcSegs) { 137 | if (index == arcSegs + lineSegs) { 138 | return PathIterator.SEG_CLOSE 139 | } 140 | coords[0] = x.toFloat() 141 | coords[1] = y.toFloat() 142 | affine?.transform(coords, 0, coords, 0, 1) 143 | 144 | return PathIterator.SEG_LINETO 145 | } 146 | angle += increment * (index - 1) 147 | var relx = Math.cos(angle) 148 | var rely = Math.sin(angle) 149 | coords[0] = (x + (relx - cv * rely) * w).toFloat() 150 | coords[1] = (y + (rely + cv * relx) * h).toFloat() 151 | angle += increment 152 | relx = Math.cos(angle) 153 | rely = Math.sin(angle) 154 | coords[2] = (x + (relx + cv * rely) * w).toFloat() 155 | coords[3] = (y + (rely - cv * relx) * h).toFloat() 156 | coords[4] = (x + relx * w).toFloat() 157 | coords[5] = (y + rely * h).toFloat() 158 | affine?.transform(coords, 0, coords, 0, 3) 159 | 160 | return PathIterator.SEG_CUBICTO 161 | } 162 | 163 | /** 164 | * Returns the coordinates and type of the current path segment in 165 | * the iteration. 166 | * The return value is the path segment type: 167 | * SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE. 168 | * A double array of length 6 must be passed in and may be used to 169 | * store the coordinates of the point(s). 170 | * Each point is stored as a pair of double x,y coordinates. 171 | * SEG_MOVETO and SEG_LINETO types will return one point, 172 | * SEG_QUADTO will return two points, 173 | * SEG_CUBICTO will return 3 points 174 | * and SEG_CLOSE will not return any points. 175 | * @see #SEG_MOVETO 176 | * @see #SEG_LINETO 177 | * @see #SEG_QUADTO 178 | * @see #SEG_CUBICTO 179 | * @see #SEG_CLOSE 180 | */ 181 | override fun currentSegment(coords: DoubleArray): Int { 182 | if (isDone) { 183 | throw NoSuchElementException("arc iterator out of bounds") 184 | } 185 | var angle = angStRad 186 | if (index == 0) { 187 | coords[0] = x + Math.cos(angle) * w 188 | coords[1] = y + Math.sin(angle) * h 189 | affine?.transform(coords, 0, coords, 0, 1) 190 | 191 | return PathIterator.SEG_MOVETO 192 | } 193 | if (index > arcSegs) { 194 | if (index == arcSegs + lineSegs) { 195 | return PathIterator.SEG_CLOSE 196 | } 197 | coords[0] = x 198 | coords[1] = y 199 | affine?.transform(coords, 0, coords, 0, 1) 200 | 201 | return PathIterator.SEG_LINETO 202 | } 203 | angle += increment * (index - 1) 204 | var relx = Math.cos(angle) 205 | var rely = Math.sin(angle) 206 | coords[0] = x + (relx - cv * rely) * w 207 | coords[1] = y + (rely + cv * relx) * h 208 | angle += increment 209 | relx = Math.cos(angle) 210 | rely = Math.sin(angle) 211 | coords[2] = x + (relx + cv * rely) * w 212 | coords[3] = y + (rely - cv * relx) * h 213 | coords[4] = x + relx * w 214 | coords[5] = y + rely * h 215 | affine?.transform(coords, 0, coords, 0, 3) 216 | 217 | return PathIterator.SEG_CUBICTO 218 | } 219 | 220 | operator fun invoke(rect: Arc2D, affine: AffineTransform? = null) = set(rect, affine) 221 | 222 | // ArcIterator.btan(Math.PI/2) 223 | val CtrlVal = 0.5522847498307933 224 | /* 225 | * ctrlpts contains the control points for a set of 4 cubic 226 | * bezier curves that approximate a circle of radius 0.5 227 | * centered at 0.5, 0.5 228 | */ 229 | private val pcv = 0.5 + CtrlVal * 0.5 230 | private val ncv = 0.5 - CtrlVal * 0.5 231 | private val ctrlpts = arrayOf(doubleArrayOf(1.0, pcv, pcv, 1.0, 0.5, 1.0), doubleArrayOf(ncv, 1.0, 0.0, pcv, 0.0, 0.5), doubleArrayOf(0.0, ncv, ncv, 0.0, 0.5, 0.0), doubleArrayOf(pcv, 0.0, 1.0, ncv, 1.0, 0.5)) 232 | 233 | } 234 | 235 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/SimpleOrTesselatingVisitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import com.jogamp.opengl.GL 20 | import org.anglur.joglext.cacheable.CachedFloatArray 21 | import org.anglur.joglext.jogl2d.PathVisitor 22 | import org.anglur.joglext.jogl2d.VertexBuffer 23 | import java.awt.BasicStroke 24 | import java.lang.Math.acos 25 | import java.lang.Math.sqrt 26 | 27 | /** 28 | * Tesselating is expensive. This is a simple workaround to check if we can just 29 | * draw the simple, convex polygon without tesselating. At each corner, we have 30 | * to check the sign of the z-component of the cross-product. If it's the same 31 | * all the way around, we know that every turn went the same direction. That 32 | * ensures it's convex. That's necessary, but not sufficient since we might 33 | * still have self-intersections. For that, we check that the total curvature 34 | * along the path is 2π. That ensures it's simple. 35 | * 36 | * 37 | * 38 | * 39 | * This checks every corner and if it has the same sign and total curvature is 40 | * 2π, we know the polygon is convex. Once we get to the end, we draw it. If 41 | * it's not convex, then we fall back to tesselating it. 42 | * 43 | * 44 | * 45 | * There are many places where we could fail being a simple convex polygon and 46 | * then have to fail over to the tesselator. As soon as we fail over we need to 47 | * catch the tesselator up to the current position and then use the tesselator 48 | * from then on. For that reason, this class is a little messy. 49 | * 50 | */ 51 | class SimpleOrTesselatingVisitor(private var simpleFallback: PathVisitor, private var tesselatorFallback: PathVisitor) : SimplePathVisitor() { 52 | /** 53 | * This buffer is used to store points for the simple polygon, until we find 54 | * out it's not simple. Then we push all this data to the tesselator and 55 | * ignore the buffer. 56 | */ 57 | private var vBuffer = VertexBuffer(1024) 58 | 59 | /** 60 | * This is the buffer of vertices we'll use to test the corner. 61 | */ 62 | private var previousVertices = CachedFloatArray(4) 63 | private var numberOfPreviousVertices: Int = 0 64 | 65 | /** 66 | * The total curvature along the path. Since we know we close the path, if 67 | * it's a simple, convex polygon, we'll have a total curvature of 2π. 68 | */ 69 | private var totalCurvature: Double = 0.toDouble() 70 | 71 | /** 72 | * All corners must have the same sign. 73 | */ 74 | private var sign: Int = 0 75 | 76 | /** 77 | * The flag to indicate if we currently believe this polygon to be simple and 78 | * convex. 79 | */ 80 | private var isConvexSoFar: Boolean = false 81 | 82 | /** 83 | * The flag to indicate if we are on our first segment (move-to). If we have 84 | * multiple move-to's, then we need to tesselate. 85 | */ 86 | private var firstContour: Boolean = false 87 | 88 | /** 89 | * Keep the winding rule for when we pass the information off to the 90 | * tesselator. 91 | */ 92 | private var windingRule: Int = 0 93 | 94 | override fun setGLContext(context: GL) { 95 | simpleFallback.setGLContext(context) 96 | tesselatorFallback.setGLContext(context) 97 | } 98 | 99 | override fun setStroke(stroke: BasicStroke) { 100 | // this is only used to fill, no need to consider stroke 101 | } 102 | 103 | override fun beginPoly(windingRule: Int) { 104 | isConvexSoFar = true 105 | firstContour = true 106 | sign = 0 107 | totalCurvature = 0.0 108 | 109 | this.windingRule = windingRule 110 | } 111 | 112 | override fun moveTo(vertex: FloatArray) { 113 | if (firstContour) { 114 | firstContour = false 115 | } else if (isConvexSoFar) { 116 | setUseTesselator(false) 117 | } 118 | 119 | if (isConvexSoFar) { 120 | numberOfPreviousVertices = 1 121 | previousVertices = floatArrayOf(vertex[0], vertex[1], 0f, 0f) 122 | 123 | vBuffer.clear() 124 | vBuffer.addVertex(vertex[0], vertex[1]) 125 | } else { 126 | tesselatorFallback.closeLine() 127 | tesselatorFallback.moveTo(vertex) 128 | } 129 | } 130 | 131 | override fun lineTo(vertex: FloatArray) { 132 | if (isConvexSoFar) { 133 | vBuffer.addVertex(vertex[0], vertex[1]) 134 | 135 | if (!isValidCorner(vertex)) { 136 | setUseTesselator(false) 137 | } 138 | } else { 139 | tesselatorFallback.lineTo(vertex) 140 | } 141 | } 142 | 143 | /** 144 | * Returns true if the corner is correct, using the new vertex and the buffer 145 | * of previous vertices. This always updates the buffer of previous vertices. 146 | */ 147 | private fun isValidCorner(vertex: FloatArray): Boolean { 148 | if (numberOfPreviousVertices >= 2) { 149 | val diff1 = (previousVertices[2] - previousVertices[0]).toDouble() 150 | val diff2 = (previousVertices[3] - previousVertices[1]).toDouble() 151 | val diff3 = (vertex[0] - previousVertices[0]).toDouble() 152 | val diff4 = (vertex[1] - previousVertices[1]).toDouble() 153 | 154 | val cross2 = diff1 * diff4 - diff2 * diff3 155 | 156 | /* 157 | * Check that the current sign of the cross-product is the same as the 158 | * others. 159 | */ 160 | val currentSign = sign(cross2) 161 | if (sign == 0) { 162 | sign = currentSign 163 | 164 | // allow for currentSign = 0, in which case we don't care 165 | } else if (currentSign * sign == -1) { 166 | return false 167 | } 168 | 169 | /* 170 | * Check that the total curvature along the path is less than 2π. 171 | */ 172 | val norm1sq = diff1 * diff1 + diff2 * diff2 173 | val norm2sq = diff3 * diff3 + diff4 * diff4 174 | val dot = diff1 * diff3 + diff2 * diff4 175 | val cosThetasq = dot * dot / (norm1sq * norm2sq) 176 | val theta = acos(sqrt(cosThetasq)) 177 | 178 | totalCurvature += theta 179 | if (totalCurvature > 2 * Math.PI + 1e-3) { 180 | return false 181 | } 182 | } 183 | 184 | numberOfPreviousVertices++ 185 | previousVertices[2] = previousVertices[0] 186 | previousVertices[3] = previousVertices[1] 187 | previousVertices[0] = vertex[0] 188 | previousVertices[1] = vertex[1] 189 | 190 | return true 191 | } 192 | 193 | private fun sign(value: Double): Int { 194 | if (value > 1e-8) { 195 | return 1 196 | } else if (value < -1e-8) { 197 | return -1 198 | } else { 199 | return 0 200 | } 201 | } 202 | 203 | override fun closeLine() { 204 | if (isConvexSoFar) { 205 | /* 206 | * If we're convex so far, we need to finish out all the corners to make 207 | * sure everything is kosher. 208 | */ 209 | val buf = vBuffer.buffer 210 | val vertex = CachedFloatArray(2) 211 | val position = buf.position() 212 | 213 | buf.rewind() 214 | buf.get(vertex) 215 | 216 | var good = false 217 | if (isValidCorner(vertex)) { 218 | buf.get(vertex) 219 | if (isValidCorner(vertex)) { 220 | good = true 221 | } 222 | } 223 | 224 | buf.position(position) 225 | 226 | if (!good) { 227 | setUseTesselator(true) 228 | } 229 | } else { 230 | tesselatorFallback.closeLine() 231 | } 232 | } 233 | 234 | override fun endPoly() { 235 | if (isConvexSoFar) { 236 | simpleFallback.beginPoly(windingRule) 237 | drawToVisitor(simpleFallback, true) 238 | simpleFallback.endPoly() 239 | } else { 240 | tesselatorFallback.endPoly() 241 | } 242 | } 243 | 244 | /** 245 | * Sets the state to start using the tesselator. This will catch the 246 | * tesselator up to the current position and then set `isConvexSoFar` to 247 | * false so we can start using the tesselator exclusively. 248 | * 249 | * 250 | * If `doClose` is true, then we will also close the line when we update 251 | * the tesselator. This is for when we realized it's not a simple poly after 252 | * we already finished the first path. 253 | */ 254 | private fun setUseTesselator(doClose: Boolean) { 255 | isConvexSoFar = false 256 | 257 | tesselatorFallback.beginPoly(windingRule) 258 | drawToVisitor(tesselatorFallback, doClose) 259 | } 260 | 261 | private fun drawToVisitor(visitor: PathVisitor, doClose: Boolean) { 262 | val buf = vBuffer.buffer 263 | buf.flip() 264 | 265 | val vertex = CachedFloatArray(2) 266 | 267 | if (buf.hasRemaining()) { 268 | buf.get(vertex) 269 | visitor.moveTo(vertex) 270 | } 271 | 272 | while (buf.hasRemaining()) { 273 | buf.get(vertex) 274 | visitor.lineTo(vertex) 275 | } 276 | 277 | if (doClose) { 278 | visitor.closeLine() 279 | } 280 | 281 | // put everything back the way it was 282 | vBuffer.clear() 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/impl/AbstractImageHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d.impl 18 | 19 | import com.jogamp.opengl.util.texture.Texture 20 | import com.jogamp.opengl.util.texture.awt.AWTTextureIO 21 | import org.anglur.joglext.jogl2d.GLG2DImageHelper 22 | import org.anglur.joglext.jogl2d.GLG2DRenderingHints 23 | import org.anglur.joglext.jogl2d.GLGraphics2D 24 | import java.awt.Color 25 | import java.awt.Image 26 | import java.awt.RenderingHints.Key 27 | import java.awt.geom.AffineTransform 28 | import java.awt.image.* 29 | import java.awt.image.renderable.RenderableImage 30 | import java.lang.ref.Reference 31 | import java.lang.ref.ReferenceQueue 32 | import java.lang.ref.WeakReference 33 | import java.util.* 34 | import java.util.logging.Level 35 | import java.util.logging.Logger 36 | 37 | 38 | abstract class AbstractImageHelper : GLG2DImageHelper { 39 | 40 | /** 41 | * See [GLG2DRenderingHints.KEY_CLEAR_TEXTURES_CACHE] 42 | */ 43 | private var imageCache = TextureCache() 44 | private var clearCachePolicy: Any? = null 45 | 46 | protected lateinit var g2d: GLGraphics2D 47 | 48 | protected abstract fun begin(texture: Texture, xform: AffineTransform?, bgcolor: Color) 49 | 50 | protected abstract fun applyTexture(texture: Texture, dx1: Int, dy1: Int, dx2: Int, dy2: Int, 51 | sx1: Float, sy1: Float, sx2: Float, sy2: Float) 52 | 53 | protected abstract fun end(texture: Texture) 54 | 55 | override fun setG2D(g2d: GLGraphics2D) { 56 | this.g2d = g2d 57 | 58 | if (clearCachePolicy === GLG2DRenderingHints.VALUE_CLEAR_TEXTURES_CACHE_EACH_PAINT) { 59 | imageCache.clear() 60 | } 61 | } 62 | 63 | override fun push(newG2d: GLGraphics2D) { 64 | // nop 65 | } 66 | 67 | override fun pop(parentG2d: GLGraphics2D) { 68 | // nop 69 | } 70 | 71 | override fun setHint(key: Key, value: Any?) { 72 | if (key === GLG2DRenderingHints.KEY_CLEAR_TEXTURES_CACHE) { 73 | clearCachePolicy = value 74 | } 75 | } 76 | 77 | override fun resetHints() { 78 | clearCachePolicy = GLG2DRenderingHints.VALUE_CLEAR_TEXTURES_CACHE_DEFAULT 79 | } 80 | 81 | override fun dispose() { 82 | imageCache.clear() 83 | } 84 | 85 | override fun drawImage(img: Image, x: Int, y: Int, bgcolor: Color, observer: ImageObserver): Boolean { 86 | return drawImage(img, AffineTransform.getTranslateInstance(x.toDouble(), y.toDouble()), bgcolor, observer) 87 | } 88 | 89 | override fun drawImage(img: Image, xform: AffineTransform, observer: ImageObserver): Boolean { 90 | return drawImage(img, xform, Color.WHITE, observer) 91 | } 92 | 93 | override fun drawImage(img: Image, x: Int, y: Int, width: Int, height: Int, bgcolor: Color, observer: ImageObserver): Boolean { 94 | val imgHeight = img.getHeight(null).toDouble() 95 | val imgWidth = img.getWidth(null).toDouble() 96 | 97 | if (imgHeight < 0 || imgWidth < 0) { 98 | return false 99 | } 100 | 101 | val transform = AffineTransform.getTranslateInstance(x.toDouble(), y.toDouble()) 102 | transform.scale(width / imgWidth, height / imgHeight) 103 | return drawImage(img, transform, bgcolor, observer) 104 | } 105 | 106 | override fun drawImage(img: Image, dx1: Int, dy1: Int, dx2: Int, dy2: Int, sx1: Int, sy1: Int, sx2: Int, 107 | sy2: Int, bgcolor: Color, observer: ImageObserver): Boolean { 108 | val texture = getTexture(img, observer) ?: return false 109 | 110 | val height = texture.height.toFloat() 111 | val width = texture.width.toFloat() 112 | begin(texture, null, bgcolor) 113 | applyTexture(texture, dx1, dy1, dx2, dy2, sx1 / width, sy1 / height, sx2 / width, sy2 / height) 114 | end(texture) 115 | 116 | return true 117 | } 118 | 119 | private fun drawImage(img: Image, xform: AffineTransform, color: Color, observer: ImageObserver): Boolean { 120 | val texture = getTexture(img, observer) ?: return false 121 | 122 | begin(texture, xform, color) 123 | applyTexture(texture) 124 | end(texture) 125 | 126 | return true 127 | } 128 | 129 | private fun applyTexture(texture: Texture) { 130 | val width = texture.width 131 | val height = texture.height 132 | val coords = texture.imageTexCoords 133 | 134 | applyTexture(texture, 0, 0, width, height, coords.left(), coords.top(), coords.right(), coords.bottom()) 135 | } 136 | 137 | /** 138 | * Cache the texture if possible. I have a feeling this will run into issues 139 | * later as images change. Just not sure how to handle it if they do. I 140 | * suspect I should be using the ImageConsumer class and dumping pixels to the 141 | * screen as I receive them. 142 | * 143 | * 144 | * 145 | * 146 | * If an image is a BufferedImage, turn it into a texture and cache it. If 147 | * it's not, draw it to a BufferedImage and see if all the image data is 148 | * available. If it is, cache it. If it's not, don't cache it. But if not all 149 | * the image data is available, we will draw it what we have, since we draw 150 | * anything in the image to a BufferedImage. 151 | * 152 | */ 153 | private fun getTexture(image: Image, observer: ImageObserver): Texture? { 154 | var texture: Texture? = imageCache[image] 155 | if (texture == null) { 156 | val bufferedImage: BufferedImage? 157 | if (image is BufferedImage && image.type != BufferedImage.TYPE_CUSTOM) { 158 | bufferedImage = image 159 | } else { 160 | bufferedImage = toBufferedImage(image) 161 | } 162 | 163 | if (bufferedImage != null) { 164 | texture = create(bufferedImage) 165 | addToCache(image, texture) 166 | } 167 | } 168 | 169 | return texture 170 | } 171 | 172 | private fun create(image: BufferedImage): Texture { 173 | // we'll assume the image is complete and can be rendered 174 | return AWTTextureIO.newTexture(g2d.glContext.gl.glProfile, image, false) 175 | } 176 | 177 | private fun destroy(texture: Texture) { 178 | texture.destroy(g2d.glContext.gl) 179 | } 180 | 181 | private fun addToCache(image: Image, texture: Texture) { 182 | if (clearCachePolicy is Number) { 183 | val maxSize = (clearCachePolicy as Number).toInt() 184 | if (imageCache.size > maxSize) { 185 | if (LOGGER.isLoggable(Level.FINE)) { 186 | LOGGER.fine("Clearing texture cache with size " + imageCache.size) 187 | } 188 | 189 | imageCache.clear() 190 | } 191 | } 192 | 193 | imageCache.put(image, texture) 194 | } 195 | 196 | private fun toBufferedImage(image: Image): BufferedImage? { 197 | if (image is VolatileImage) { 198 | return image.snapshot 199 | } 200 | 201 | val width = image.getWidth(null) 202 | val height = image.getHeight(null) 203 | if (width < 0 || height < 0) { 204 | return null 205 | } 206 | 207 | val bufferedImage = BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR) 208 | bufferedImage.createGraphics().drawImage(image, null, null) 209 | return bufferedImage 210 | } 211 | 212 | override fun drawImage(img: BufferedImage, op: BufferedImageOp, x: Int, y: Int) { 213 | TODO("drawImage(BufferedImage, BufferedImageOp, int, int)") 214 | } 215 | 216 | override fun drawImage(img: RenderedImage, xform: AffineTransform) { 217 | TODO("drawImage(RenderedImage, AffineTransform)") 218 | } 219 | 220 | override fun drawImage(img: RenderableImage, xform: AffineTransform) { 221 | TODO("drawImage(RenderableImage, AffineTransform)") 222 | } 223 | 224 | /** 225 | * We could use a WeakHashMap here, but we want access to the ReferenceQueue 226 | * so we can dispose the Textures when the Image is no longer referenced. 227 | */ 228 | @SuppressWarnings("serial") 229 | private inner class TextureCache : HashMap, Texture>() { 230 | private val queue = ReferenceQueue() 231 | 232 | fun expungeStaleEntries() { 233 | var ref: Reference? = queue.poll() 234 | while (ref != null) { 235 | val texture = remove(ref) 236 | if (texture != null) { 237 | destroy(texture) 238 | } 239 | 240 | ref = queue.poll() 241 | } 242 | } 243 | 244 | operator fun get(image: Image): Texture? { 245 | expungeStaleEntries() 246 | val key = WeakKey(image, null) 247 | return get(key) 248 | } 249 | 250 | fun put(image: Image, texture: Texture): Texture? { 251 | expungeStaleEntries() 252 | val key = WeakKey(image, queue) 253 | return put(key, texture) 254 | } 255 | } 256 | 257 | private class WeakKey(value: T, queue: ReferenceQueue?) : WeakReference(value, queue) { 258 | private val hash: Int 259 | 260 | init { 261 | hash = value!!.hashCode() 262 | } 263 | 264 | override fun hashCode(): Int { 265 | return hash 266 | } 267 | 268 | override fun equals(other: Any?): Boolean { 269 | if (this === other) { 270 | return true 271 | } else if (other is WeakKey<*>) { 272 | return other.hash == hash && get() === other.get() 273 | } else { 274 | return false 275 | } 276 | } 277 | } 278 | 279 | companion object { 280 | private val LOGGER = Logger.getLogger(AbstractImageHelper::class.java.name) 281 | } 282 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Jonathan Beaudoin 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLG2DCanvas.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d; 18 | 19 | import com.jogamp.opengl.*; 20 | import com.jogamp.opengl.awt.GLCanvas; 21 | import com.jogamp.opengl.util.Animator; 22 | 23 | import javax.swing.*; 24 | import java.awt.*; 25 | import java.io.Serializable; 26 | import java.util.logging.Logger; 27 | 28 | /** 29 | * This canvas redirects all paints to an OpenGL canvas. The drawable component 30 | * can be any JComponent. This is a simple implementation to allow manual 31 | * painting of a JComponent scene to OpenGL. A {@code G2DGLCanvas} is more 32 | * appropriate when rendering a complex scene using 33 | * {@link JComponent#paintComponent(Graphics)} and the {@code Graphics2D} 34 | * object. 35 | * 36 | *

37 | * GL drawing can be enabled or disabled using the {@code setGLDrawing(boolean)} 38 | * method. If GL drawing is enabled, all full paint requests are intercepted and 39 | * the drawable component is drawn to the OpenGL canvas. 40 | *

41 | * 42 | *

43 | * Override {@link #createGLComponent(GLCapabilitiesImmutable, GLContext)} to 44 | * create the OpenGL canvas. The returned canvas may be a {@code GLJPanel} or a 45 | * {@code GLCanvas}. {@link #createG2DListener(JComponent)} is used to create 46 | * the {@code GLEventListener} that will draw to the OpenGL canvas. Use 47 | * {@link #getGLDrawable()} if you want to attach an {@code Animator}. 48 | * Otherwise, paints will only happen when requested (either with 49 | * {@code repaint()} or from AWT). 50 | *

51 | */ 52 | public class GLG2DCanvas extends JComponent 53 | { 54 | private static final long serialVersionUID = -471481443599019888L; 55 | 56 | protected GLAutoDrawable canvas; 57 | protected GLCapabilitiesImmutable chosenCapabilities; 58 | 59 | protected GLEventListener g2dglListener; 60 | 61 | /** 62 | * @see #removeNotify() 63 | */ 64 | private GLAutoDrawable sideContext; 65 | 66 | private JComponent drawableComponent; 67 | 68 | private boolean drawGL; 69 | 70 | /** 71 | * Returns the default, desired OpenGL capabilities needed for this component. 72 | */ 73 | public static GLCapabilities getDefaultCapabalities() 74 | { 75 | GLCapabilities caps = new GLCapabilities(GLProfile.getGL2ES1()); 76 | caps.setRedBits(8); 77 | caps.setGreenBits(8); 78 | caps.setBlueBits(8); 79 | caps.setAlphaBits(8); 80 | caps.setDoubleBuffered(true); 81 | caps.setHardwareAccelerated(true); 82 | caps.setNumSamples(4); 83 | caps.setBackgroundOpaque(false); 84 | caps.setSampleBuffers(true); 85 | return caps; 86 | } 87 | 88 | /** 89 | * Creates a new, blank {@code G2DGLCanvas} using the default capabilities 90 | * from {@link #getDefaultCapabalities()}. 91 | */ 92 | public GLG2DCanvas() 93 | { 94 | this(getDefaultCapabalities()); 95 | } 96 | 97 | /** 98 | * Creates a new, blank {@code G2DGLCanvas} using the given OpenGL 99 | * capabilities. 100 | */ 101 | public GLG2DCanvas(GLCapabilities capabilities) 102 | { 103 | canvas = createGLComponent(capabilities, null); 104 | 105 | /* 106 | * Set both canvas and drawableComponent to be the same size, but we never 107 | * draw the drawableComponent except into the canvas. 108 | */ 109 | setLayout(new GLOverlayLayout()); 110 | add((Component) canvas); 111 | 112 | setGLDrawing(true); 113 | 114 | RepaintManager.setCurrentManager(GLAwareRepaintManager.INSTANCE); 115 | } 116 | 117 | /** 118 | * Creates a new {@code G2DGLCanvas} where {@code drawableComponent} fills the 119 | * canvas. This uses the default capabilities from 120 | * {@link #getDefaultCapabalities()}. 121 | */ 122 | public GLG2DCanvas(JComponent drawableComponent) 123 | { 124 | this(); 125 | setDrawableComponent(drawableComponent); 126 | } 127 | 128 | /** 129 | * Creates a new {@code G2DGLCanvas} where {@code drawableComponent} fills the 130 | * canvas. 131 | */ 132 | public GLG2DCanvas(GLCapabilities capabilities, JComponent drawableComponent) 133 | { 134 | this(capabilities); 135 | setDrawableComponent(drawableComponent); 136 | } 137 | 138 | /** 139 | * Returns {@code true} if the {@code drawableComonent} is drawn using OpenGL 140 | * libraries. If {@code false}, it is using normal Java2D drawing routines. 141 | */ 142 | public boolean isGLDrawing() 143 | { 144 | return drawGL; 145 | } 146 | 147 | /** 148 | * Sets the drawing path, {@code true} for OpenGL, {@code false} for normal 149 | * Java2D. 150 | * 151 | * @see #isGLDrawing() 152 | */ 153 | public void setGLDrawing(boolean drawGL) 154 | { 155 | if (this.drawGL != drawGL) 156 | { 157 | this.drawGL = drawGL; 158 | ((Component) canvas).setVisible(drawGL); 159 | setOpaque(drawGL); 160 | 161 | firePropertyChange("gldrawing", !drawGL, drawGL); 162 | 163 | repaint(); 164 | } 165 | } 166 | 167 | /** 168 | * Gets the {@code JComponent} to be drawn to the OpenGL canvas. 169 | */ 170 | public JComponent getDrawableComponent() 171 | { 172 | return drawableComponent; 173 | } 174 | 175 | /** 176 | * Sets the {@code JComponent} that will be drawn to the OpenGL canvas. 177 | */ 178 | public void setDrawableComponent(JComponent component) 179 | { 180 | if (component == drawableComponent) 181 | { 182 | return; 183 | } 184 | 185 | if (g2dglListener != null) 186 | { 187 | canvas.removeGLEventListener(g2dglListener); 188 | if (sideContext != null) 189 | { 190 | sideContext.removeGLEventListener(g2dglListener); 191 | } 192 | } 193 | 194 | if (drawableComponent != null) 195 | { 196 | remove(drawableComponent); 197 | } 198 | 199 | drawableComponent = component; 200 | if (drawableComponent != null) 201 | { 202 | verifyHierarchy(drawableComponent); 203 | 204 | g2dglListener = createG2DListener(drawableComponent); 205 | canvas.addGLEventListener(g2dglListener); 206 | if (sideContext != null) 207 | { 208 | sideContext.addGLEventListener(g2dglListener); 209 | } 210 | 211 | add(drawableComponent); 212 | } 213 | } 214 | 215 | /** 216 | * Checks the component and all children to ensure that everything is pure 217 | * Swing. We can only draw lightweights. 218 | *

219 | *

220 | * We'll also set PopupMenus to heavyweight and fix JViewport blitting. 221 | */ 222 | protected void verifyHierarchy(Component comp) 223 | { 224 | JPopupMenu.setDefaultLightWeightPopupEnabled(false); 225 | 226 | if (comp instanceof JComponent) 227 | { 228 | ((JComponent) comp).setDoubleBuffered(false); 229 | } 230 | 231 | if (!(comp instanceof JComponent)) 232 | { 233 | Logger.getLogger(GLG2DCanvas.class.getName()).warning("Drawable component and children should be pure Swing: " + 234 | comp + " does not inherit JComponent"); 235 | } 236 | 237 | if (comp instanceof JViewport) 238 | { 239 | ((JViewport) comp).setScrollMode(JViewport.SIMPLE_SCROLL_MODE); 240 | } 241 | 242 | if (comp instanceof Container) 243 | { 244 | Container cont = (Container) comp; 245 | for (int i = 0; i < cont.getComponentCount(); i++) 246 | { 247 | verifyHierarchy(cont.getComponent(i)); 248 | } 249 | } 250 | } 251 | 252 | /** 253 | * Gets the {@code GLAutoDrawable} used for drawing. By default this is a 254 | * {@link GLCanvas}, but can be changed by overriding 255 | * {@link #createGLComponent(GLCapabilitiesImmutable, GLContext)}. 256 | * 257 | *

258 | * Use the returned {@code GLAutoDrawable} as input to an {@link Animator} to 259 | * automate painting. 260 | *

261 | */ 262 | public GLAutoDrawable getGLDrawable() 263 | { 264 | return canvas; 265 | } 266 | 267 | /** 268 | * Creates a {@code Component} that is also a {@code GLAutoDrawable}. This is 269 | * where all the drawing takes place. The advantage of a {@code GLCanvas} is 270 | * that it is faster, but a {@code GLJPanel} is more portable. The component 271 | * should also be disabled so that it does not receive events that should be 272 | * sent to the {@code drawableComponent}. A {@code GLCanvas} is a heavyweight 273 | * component and on some platforms will not pass through mouse events even 274 | * though it is disabled. A {@code GLJPanel} supports this better. 275 | */ 276 | protected GLAutoDrawable createGLComponent(GLCapabilitiesImmutable capabilities, GLContext shareWith) 277 | { 278 | GLCanvas canvas = new GLCanvas(capabilities); 279 | if (shareWith != null) 280 | { 281 | canvas.setSharedContext(shareWith); 282 | } 283 | 284 | canvas.setEnabled(false); 285 | chosenCapabilities = (GLCapabilitiesImmutable) capabilities.cloneMutable(); 286 | return canvas; 287 | } 288 | 289 | /** 290 | * Creates the GLEventListener that will draw the given component to the 291 | * canvas. 292 | */ 293 | protected GLEventListener createG2DListener(JComponent drawingComponent) 294 | { 295 | return new GLG2DSimpleEventListener(drawingComponent); 296 | } 297 | 298 | /** 299 | * Calling {@link GLCanvas#removeNotify()} destroys the GLContext. We could 300 | * mess with that internally, but this is slightly easier. 301 | *

302 | * This method is particularly important for docking frameworks and moving the 303 | * panel from one window to another. This is simple for normal Swing 304 | * components, but GL contexts are destroyed when {@code removeNotify()} is 305 | * called. 306 | *

307 | *

308 | * Our workaround is to use context sharing. The pbuffer is initialized and by 309 | * drawing into it at least once, we automatically share all textures, etc. 310 | * with the new pbuffer. This pbuffer holds the data until we can initialize 311 | * our new JOGL canvas. We share the pbuffer canvas with the new JOGL canvas 312 | * and everything works nicely from then on. 313 | *

314 | *

315 | * This has the unfortunate side-effect of leaking memory. I'm not sure how to 316 | * fix this yet. 317 | *

318 | */ 319 | @Override 320 | public void removeNotify() 321 | { 322 | prepareSideContext(); 323 | 324 | remove((Component) canvas); 325 | super.removeNotify(); 326 | 327 | canvas = createGLComponent(chosenCapabilities, sideContext.getContext()); 328 | canvas.addGLEventListener(g2dglListener); 329 | add((Component) canvas, 0); 330 | } 331 | 332 | private void prepareSideContext() 333 | { 334 | if (sideContext == null) 335 | { 336 | GLDrawableFactory factory = canvas.getFactory(); 337 | sideContext = factory.createOffscreenAutoDrawable(null, chosenCapabilities, null, 1, 1); 338 | ((GLOffscreenAutoDrawable) sideContext).setSharedContext(canvas.getContext()); 339 | sideContext.addGLEventListener(g2dglListener); 340 | } 341 | 342 | Runnable work = new Runnable() 343 | { 344 | @Override 345 | public void run() 346 | { 347 | sideContext.display(); 348 | } 349 | }; 350 | 351 | if (Threading.isOpenGLThread()) 352 | { 353 | work.run(); 354 | } 355 | else 356 | { 357 | Threading.invokeOnOpenGLThread(false, work); 358 | } 359 | } 360 | 361 | @Override 362 | public void paint(Graphics g) 363 | { 364 | if (isGLDrawing() && drawableComponent != null && canvas != null) 365 | { 366 | canvas.display(); 367 | } 368 | else 369 | { 370 | super.paint(g); 371 | } 372 | } 373 | 374 | @Override 375 | protected void paintChildren(Graphics g) 376 | { 377 | /* 378 | * Don't paint the drawableComponent. If we'd use a GLJPanel instead of a 379 | * GLCanvas, we'd have to paint it here. 380 | */ 381 | if (!isGLDrawing()) 382 | { 383 | super.paintChildren(g); 384 | } 385 | } 386 | 387 | @Override 388 | protected void addImpl(Component comp, Object constraints, int index) 389 | { 390 | if (comp == canvas || comp == drawableComponent) 391 | { 392 | super.addImpl(comp, constraints, index); 393 | } 394 | else 395 | { 396 | throw new IllegalArgumentException("Do not add component to this. Add them to the object in getDrawableComponent()"); 397 | } 398 | } 399 | 400 | /** 401 | * Implements a simple layout where all the components are the same size as 402 | * the parent. 403 | */ 404 | protected static class GLOverlayLayout implements LayoutManager2, Serializable 405 | { 406 | private static final long serialVersionUID = -8248213786715565045L; 407 | 408 | @Override 409 | public Dimension preferredLayoutSize(Container parent) 410 | { 411 | if (parent.isPreferredSizeSet() || parent.getComponentCount() == 0) 412 | { 413 | return parent.getPreferredSize(); 414 | } 415 | else 416 | { 417 | int x = -1, y = -1; 418 | for (Component child : parent.getComponents()) 419 | { 420 | Dimension dim = child.getPreferredSize(); 421 | x = Math.max(dim.width, x); 422 | y = Math.max(dim.height, y); 423 | } 424 | 425 | return new Dimension(x, y); 426 | } 427 | } 428 | 429 | @Override 430 | public Dimension minimumLayoutSize(Container parent) 431 | { 432 | if (parent.getComponentCount() == 0) 433 | { 434 | return new Dimension(0, 0); 435 | } 436 | else 437 | { 438 | int x = Integer.MAX_VALUE, y = Integer.MAX_VALUE; 439 | for (Component child : parent.getComponents()) 440 | { 441 | Dimension dim = child.getMinimumSize(); 442 | x = Math.min(dim.width, x); 443 | y = Math.min(dim.height, y); 444 | } 445 | 446 | return new Dimension(x, y); 447 | } 448 | } 449 | 450 | @Override 451 | public Dimension maximumLayoutSize(Container parent) 452 | { 453 | if (parent.getComponentCount() == 0) 454 | { 455 | return new Dimension(0, 0); 456 | } 457 | else 458 | { 459 | int x = -1, y = -1; 460 | for (Component child : parent.getComponents()) 461 | { 462 | Dimension dim = child.getMaximumSize(); 463 | x = Math.max(dim.width, x); 464 | y = Math.max(dim.height, y); 465 | } 466 | 467 | return new Dimension(x, y); 468 | } 469 | } 470 | 471 | @Override 472 | public void layoutContainer(Container parent) 473 | { 474 | for (Component child : parent.getComponents()) 475 | { 476 | child.setSize(parent.getSize()); 477 | } 478 | } 479 | 480 | @Override 481 | public void addLayoutComponent(String name, Component comp) 482 | { 483 | // nop 484 | } 485 | 486 | @Override 487 | public void addLayoutComponent(Component comp, Object constraints) 488 | { 489 | // nop 490 | } 491 | 492 | @Override 493 | public void removeLayoutComponent(Component comp) 494 | { 495 | // nop 496 | } 497 | 498 | @Override 499 | public void invalidateLayout(Container target) 500 | { 501 | // nop 502 | } 503 | 504 | @Override 505 | public float getLayoutAlignmentX(Container target) 506 | { 507 | return 0.5f; 508 | } 509 | 510 | @Override 511 | public float getLayoutAlignmentY(Container target) 512 | { 513 | return 0.5f; 514 | } 515 | } 516 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/anglur/joglext/jogl2d/GLGraphics2D.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Beaudoin 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.anglur.joglext.jogl2d 18 | 19 | import com.jogamp.opengl.GL 20 | import com.jogamp.opengl.GLContext 21 | import com.jogamp.opengl.GLDrawable 22 | import org.anglur.joglext.cacheable.CachedList 23 | import org.anglur.joglext.jogl2d.impl.GLGraphicsConfiguration 24 | import org.anglur.joglext.jogl2d.impl.gl2.* 25 | import java.awt.* 26 | import java.awt.RenderingHints.Key 27 | import java.awt.font.GlyphVector 28 | import java.awt.geom.AffineTransform 29 | import java.awt.geom.NoninvertibleTransformException 30 | import java.awt.geom.Rectangle2D 31 | import java.awt.image.BufferedImage 32 | import java.awt.image.BufferedImageOp 33 | import java.awt.image.ImageObserver 34 | import java.awt.image.RenderedImage 35 | import java.awt.image.renderable.RenderableImage 36 | import java.text.AttributedCharacterIterator 37 | import java.util.logging.Level 38 | import java.util.logging.Logger 39 | 40 | /** 41 | * Implements the standard `Graphics2D` functionality, but instead draws 42 | * to an OpenGL canvas. 43 | */ 44 | class GLGraphics2D : Graphics2D(), Cloneable { 45 | /** 46 | * The parent graphics object, if we have one. This reference is used to pass 47 | * control back to the parent. 48 | */ 49 | private var parent: GLGraphics2D? = null 50 | 51 | /** 52 | * When we are painting, this is the drawable/context we're painting into. 53 | */ 54 | private lateinit var glDrawable: GLDrawable 55 | lateinit var glContext: GLContext 56 | 57 | /** 58 | * Ensures we only dispose() once. 59 | */ 60 | private var isDisposed: Boolean = false 61 | 62 | /** 63 | * Keeps the current viewport height for things like painting text. 64 | */ 65 | var canvasHeight: Int = 0 66 | private set 67 | 68 | /** 69 | * All the drawing helpers or listeners to drawing events. 70 | */ 71 | private var helpers = CachedList(5) 72 | 73 | /* 74 | * The following are specific drawing helpers used explicitly. 75 | */ 76 | 77 | val shapeHelper = addG2DDrawingHelper(GL2ShapeDrawer()) as GLG2DShapeHelper 78 | 79 | val imageHelper = addG2DDrawingHelper(GL2ImageDrawer()) as GLG2DImageHelper 80 | 81 | val stringHelper = addG2DDrawingHelper(GL2StringDrawer()) as GLG2DTextHelper 82 | 83 | val matrixHelper = addG2DDrawingHelper(GL2Transformhelper()) as GLG2DTransformHelper 84 | 85 | val colorHelper = addG2DDrawingHelper(GL2ColorHelper()) as GL2ColorHelper 86 | 87 | /** 88 | * The current clip rectangle. This implementation supports only rectangular 89 | * clip areas. This clip must be treated as immutable and replaced but never 90 | * changed. 91 | */ 92 | private var clip: Rectangle? = null 93 | 94 | private lateinit var graphicsConfig: GraphicsConfiguration 95 | 96 | /** 97 | * The set of cached hints for this graphics object. 98 | */ 99 | private var hints = RenderingHints(emptyMap()) 100 | 101 | fun addG2DDrawingHelper(helper: G2DDrawingHelper): G2DDrawingHelper { 102 | helpers.add(helper) 103 | return helper 104 | } 105 | 106 | fun removeG2DDrawingHelper(helper: G2DDrawingHelper) = helpers.remove(helper) 107 | 108 | private fun setCanvas(context: GLContext) { 109 | glDrawable = context.glDrawable 110 | glContext = context 111 | 112 | for (helper in helpers) { 113 | helper.setG2D(this) 114 | } 115 | } 116 | 117 | /** 118 | * Sets up the graphics object in preparation for drawing. Initialization such 119 | * as getting the viewport 120 | */ 121 | fun prePaint(context: GLContext) { 122 | canvasHeight = GLG2DUtils.getViewportHeight(context.gl) 123 | setCanvas(context) 124 | setDefaultState() 125 | } 126 | 127 | companion object { 128 | private val defaultFont = Font("Arial", Font.PLAIN, 12) 129 | private val defaultStroke = BasicStroke() 130 | } 131 | 132 | private fun setDefaultState() { 133 | background = Color.black 134 | color = Color.white 135 | font = defaultFont 136 | stroke = defaultStroke 137 | composite = AlphaComposite.SrcOver 138 | setClip(null) 139 | setRenderingHints(null) 140 | graphicsConfig = GLGraphicsConfiguration.setDrawable(glDrawable) 141 | } 142 | 143 | fun postPaint() { 144 | // could glFlush here, but not necessary 145 | } 146 | 147 | fun glDispose() { 148 | for (helper in helpers) { 149 | helper.dispose() 150 | } 151 | } 152 | 153 | override fun draw(s: Shape) = shapeHelper.draw(s) 154 | 155 | override fun drawString(str: String, x: Int, y: Int) = stringHelper.drawString(str, x, y) 156 | 157 | override fun drawString(str: String, x: Float, y: Float) = stringHelper.drawString(str, x, y) 158 | 159 | override fun drawString(iterator: AttributedCharacterIterator, x: Int, y: Int) = 160 | stringHelper.drawString(iterator, x, y) 161 | 162 | override fun drawString(iterator: AttributedCharacterIterator, x: Float, y: Float) = 163 | stringHelper.drawString(iterator, x, y) 164 | 165 | override fun drawGlyphVector(g: GlyphVector, x: Float, y: Float) = shapeHelper.fill(g.getOutline(x, y)) 166 | 167 | override fun fill(s: Shape) = shapeHelper.fill(s) 168 | 169 | override fun hit(rect: Rectangle, s: Shape, onStroke: Boolean): Boolean { 170 | var rect = rect 171 | var s = s 172 | if (clip != null) { 173 | rect = clip!!.intersection(rect) 174 | } 175 | 176 | if (rect.isEmpty) { 177 | return false 178 | } 179 | 180 | if (onStroke) { 181 | s = shapeHelper.stroke.createStrokedShape(s) 182 | } 183 | 184 | s = transform.createTransformedShape(s) 185 | return s.intersects(rect) 186 | } 187 | 188 | override fun getDeviceConfiguration() = graphicsConfig 189 | 190 | override fun getComposite() = colorHelper.composite 191 | 192 | override fun setComposite(comp: Composite) { 193 | colorHelper.composite = comp 194 | } 195 | 196 | override fun setPaint(paint: Paint) { 197 | colorHelper.paint = paint 198 | } 199 | 200 | override fun setRenderingHint(hintKey: Key, hintValue: Any) { 201 | if (!hintKey.isCompatibleValue(hintValue)) { 202 | throw IllegalArgumentException("$hintValue is not compatible with $hintKey") 203 | } else { 204 | for (helper in helpers) { 205 | helper.setHint(hintKey, hintValue) 206 | } 207 | } 208 | } 209 | 210 | override fun getRenderingHint(hintKey: Key) = hints[hintKey] 211 | 212 | override fun setRenderingHints(hints: Map<*, *>?) { 213 | resetRenderingHints() 214 | if (hints != null) { 215 | addRenderingHints(hints) 216 | } 217 | } 218 | 219 | private fun resetRenderingHints() { 220 | hints.clear() 221 | 222 | for (helper in helpers) { 223 | helper.resetHints() 224 | } 225 | } 226 | 227 | override fun addRenderingHints(hints: Map<*, *>) { 228 | for ((key, value) in hints) { 229 | if (key is Key) { 230 | setRenderingHint(key, value!!) 231 | } 232 | } 233 | } 234 | 235 | override fun getRenderingHints() = hints 236 | 237 | override fun translate(x: Int, y: Int) = matrixHelper.translate(x, y) 238 | 239 | override fun translate(x: Double, y: Double) = matrixHelper.translate(x, y) 240 | 241 | override fun rotate(theta: Double) = matrixHelper.rotate(theta) 242 | 243 | override fun rotate(theta: Double, x: Double, y: Double) = matrixHelper.rotate(theta, x, y) 244 | 245 | override fun scale(sx: Double, sy: Double) = matrixHelper.scale(sx, sy) 246 | 247 | override fun shear(shx: Double, shy: Double) = matrixHelper.shear(shx, shy) 248 | 249 | override fun transform(Tx: AffineTransform) = matrixHelper.transform(Tx) 250 | 251 | override fun setTransform(transform: AffineTransform) { 252 | matrixHelper.transform = transform 253 | } 254 | 255 | override fun getTransform() = matrixHelper.transform 256 | 257 | override fun getPaint() = colorHelper.paint 258 | 259 | override fun getColor() = colorHelper.color 260 | 261 | override fun setColor(c: Color) { 262 | colorHelper.color = c 263 | } 264 | 265 | override fun setBackground(color: Color) { 266 | colorHelper.background = color 267 | } 268 | 269 | override fun getBackground() = colorHelper.background 270 | 271 | override fun getStroke() = shapeHelper.stroke 272 | 273 | override fun setStroke(s: Stroke) { 274 | shapeHelper.stroke = s 275 | } 276 | 277 | override fun setPaintMode() = colorHelper.setPaintMode() 278 | 279 | override fun setXORMode(c: Color) = colorHelper.setXORMode(c) 280 | 281 | override fun getFont() = stringHelper.font 282 | 283 | override fun setFont(font: Font) { 284 | stringHelper.font = font 285 | } 286 | 287 | override fun getFontMetrics(f: Font) = stringHelper.getFontMetrics(f) 288 | 289 | override fun getFontRenderContext() = stringHelper.fontRenderContext 290 | 291 | override fun getClipBounds(): Rectangle? { 292 | if (clip == null) { 293 | return null 294 | } else { 295 | try { 296 | val pts = DoubleArray(8) 297 | pts[0] = clip!!.minX 298 | pts[1] = clip!!.minY 299 | pts[2] = clip!!.maxX 300 | pts[3] = clip!!.minY 301 | pts[4] = clip!!.maxX 302 | pts[5] = clip!!.maxY 303 | pts[6] = clip!!.minX 304 | pts[7] = clip!!.maxY 305 | transform.inverseTransform(pts, 0, pts, 0, 4) 306 | val minX = Math.min(pts[0], Math.min(pts[2], Math.min(pts[4], pts[6]))).toInt() 307 | val maxX = Math.max(pts[0], Math.max(pts[2], Math.max(pts[4], pts[6]))).toInt() 308 | val minY = Math.min(pts[1], Math.min(pts[3], Math.min(pts[5], pts[7]))).toInt() 309 | val maxY = Math.max(pts[1], Math.max(pts[3], Math.max(pts[5], pts[7]))).toInt() 310 | return Rectangle(minX, minY, maxX - minX, maxY - minY) 311 | } catch (e: NoninvertibleTransformException) { 312 | // Not sure why this would happen 313 | Logger.getLogger(GLGraphics2D::class.java.name).log(Level.WARNING, "User transform is non-invertible", e) 314 | 315 | return clip!!.bounds 316 | } 317 | 318 | } 319 | } 320 | 321 | override fun clip(s: Shape) = setClip(s.bounds, true) 322 | 323 | override fun clipRect(x: Int, y: Int, width: Int, height: Int) = 324 | setClip(Rectangle(x, y, width, height), true) 325 | 326 | override fun setClip(x: Int, y: Int, width: Int, height: Int) = 327 | setClip(Rectangle(x, y, width, height), false) 328 | 329 | override fun getClip(): Shape? = clipBounds 330 | 331 | override fun setClip(clipShape: Shape?) { 332 | if (clipShape is Rectangle2D) { 333 | setClip(clipShape as Rectangle2D?, false) 334 | } else if (clipShape == null) { 335 | setClip(null, false) 336 | } else { 337 | setClip(clipShape.bounds2D) 338 | } 339 | } 340 | 341 | private fun setClip(clipShape: Rectangle2D?, intersect: Boolean) { 342 | if (clipShape == null) { 343 | clip = null 344 | scissor(false) 345 | } else if (intersect && clip != null) { 346 | val rect = transform.createTransformedShape(clipShape).bounds 347 | clip = rect.intersection(clip!!) 348 | scissor(true) 349 | } else { 350 | clip = transform.createTransformedShape(clipShape).bounds 351 | scissor(true) 352 | } 353 | } 354 | 355 | private fun scissor(enable: Boolean) { 356 | val gl = glContext.gl 357 | if (enable) { 358 | gl.glScissor(clip!!.x, canvasHeight - clip!!.y - clip!!.height, Math.max(clip!!.width, 0), Math.max(clip!!.height, 0)) 359 | gl.glEnable(GL.GL_SCISSOR_TEST) 360 | } else { 361 | clip = null 362 | gl.glDisable(GL.GL_SCISSOR_TEST) 363 | } 364 | } 365 | 366 | override fun copyArea(x: Int, y: Int, width: Int, height: Int, dx: Int, dy: Int) = 367 | colorHelper.copyArea(x, y, width, height, dx, dy) 368 | 369 | override fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int) = 370 | shapeHelper.drawLine(x1, y1, x2, y2) 371 | 372 | override fun fillRect(x: Int, y: Int, width: Int, height: Int) = 373 | shapeHelper.drawRect(x, y, width, height, true) 374 | 375 | override fun clearRect(x: Int, y: Int, width: Int, height: Int) { 376 | val c = color 377 | colorHelper.setColorNoRespectComposite(background) 378 | fillRect(x, y, width, height) 379 | colorHelper.setColorRespectComposite(c) 380 | } 381 | 382 | override fun drawRect(x: Int, y: Int, width: Int, height: Int) = 383 | shapeHelper.drawRect(x, y, width, height, false) 384 | 385 | override fun drawRoundRect(x: Int, y: Int, width: Int, height: Int, arcWidth: Int, arcHeight: Int) = 386 | shapeHelper.drawRoundRect(x, y, width, height, arcWidth, arcHeight, false) 387 | 388 | override fun fillRoundRect(x: Int, y: Int, width: Int, height: Int, arcWidth: Int, arcHeight: Int) = 389 | shapeHelper.drawRoundRect(x, y, width, height, arcWidth, arcHeight, true) 390 | 391 | override fun drawOval(x: Int, y: Int, width: Int, height: Int) = 392 | shapeHelper.drawOval(x, y, width, height, false) 393 | 394 | override fun fillOval(x: Int, y: Int, width: Int, height: Int) = 395 | shapeHelper.drawOval(x, y, width, height, true) 396 | 397 | override fun drawArc(x: Int, y: Int, width: Int, height: Int, startAngle: Int, arcAngle: Int) = 398 | shapeHelper.drawArc(x, y, width, height, startAngle, arcAngle, false) 399 | 400 | override fun fillArc(x: Int, y: Int, width: Int, height: Int, startAngle: Int, arcAngle: Int) = 401 | shapeHelper.drawArc(x, y, width, height, startAngle, arcAngle, true) 402 | 403 | override fun drawPolyline(xPoints: IntArray, yPoints: IntArray, nPoints: Int) = 404 | shapeHelper.drawPolyline(xPoints, yPoints, nPoints) 405 | 406 | override fun drawPolygon(xPoints: IntArray, yPoints: IntArray, nPoints: Int) = 407 | shapeHelper.drawPolygon(xPoints, yPoints, nPoints, false) 408 | 409 | override fun fillPolygon(xPoints: IntArray, yPoints: IntArray, nPoints: Int) = 410 | shapeHelper.drawPolygon(xPoints, yPoints, nPoints, true) 411 | 412 | override fun drawImage(img: Image, xform: AffineTransform, obs: ImageObserver) = 413 | imageHelper.drawImage(img, xform, obs) 414 | 415 | override fun drawImage(img: BufferedImage, op: BufferedImageOp, x: Int, y: Int) = 416 | imageHelper.drawImage(img, op, x, y) 417 | 418 | override fun drawRenderedImage(img: RenderedImage, xform: AffineTransform) = 419 | imageHelper.drawImage(img, xform) 420 | 421 | override fun drawRenderableImage(img: RenderableImage, xform: AffineTransform) = 422 | imageHelper.drawImage(img, xform) 423 | 424 | override fun drawImage(img: Image, x: Int, y: Int, observer: ImageObserver) = 425 | imageHelper.drawImage(img, x, y, Color.WHITE, observer) 426 | 427 | override fun drawImage(img: Image, x: Int, y: Int, bgcolor: Color, observer: ImageObserver) = 428 | imageHelper.drawImage(img, x, y, bgcolor, observer) 429 | 430 | override fun drawImage(img: Image, x: Int, y: Int, width: Int, height: Int, observer: ImageObserver) = 431 | imageHelper.drawImage(img, x, y, width, height, Color.WHITE, observer) 432 | 433 | override fun drawImage(img: Image, x: Int, y: Int, width: Int, height: Int, bgcolor: Color, observer: ImageObserver) = 434 | imageHelper.drawImage(img, x, y, width, height, bgcolor, observer) 435 | 436 | override fun drawImage(img: Image, dx1: Int, dy1: Int, dx2: Int, dy2: Int, sx1: Int, sy1: Int, sx2: Int, sy2: Int, observer: ImageObserver) = 437 | imageHelper.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, Color.WHITE, observer) 438 | 439 | override fun drawImage(img: Image, dx1: Int, dy1: Int, dx2: Int, dy2: Int, sx1: Int, sy1: Int, sx2: Int, sy2: Int, bgcolor: Color, 440 | observer: ImageObserver) = imageHelper.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer) 441 | 442 | override fun create(): Graphics { 443 | val newG2d = clone() 444 | 445 | for (helper in helpers) { 446 | helper.push(newG2d) 447 | } 448 | 449 | return newG2d 450 | } 451 | 452 | override fun dispose() { 453 | if (!isDisposed) { 454 | isDisposed = true 455 | 456 | if (parent != null) { 457 | /*for (i in helpers.indices.reversed()) { 458 | helpers[i].pop(parent!!) 459 | }*/ 460 | 461 | parent!!.scissor(parent!!.clip != null) 462 | } 463 | } 464 | } 465 | 466 | override fun clone(): GLGraphics2D { 467 | try { 468 | val clone = super.clone() as GLGraphics2D 469 | clone.parent = this 470 | clone.hints = hints.clone() as RenderingHints 471 | return clone 472 | } catch (exception: CloneNotSupportedException) { 473 | throw AssertionError(exception) 474 | } 475 | 476 | } 477 | } 478 | --------------------------------------------------------------------------------