From e6154c6ab758b647867d21acc98a899adeaf9b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 12 Jul 2014 03:06:51 +0200 Subject: [PATCH] Some preparatory changes for #41. --- .../renderer/TextBufferRenderCache.scala | 2 +- .../renderer/font/DataCharRenderer.scala | 43 ++++++ .../renderer/font/DynamicCharRenderer.scala | 124 +---------------- .../renderer/font/DynamicFontRenderer.scala | 39 +++--- .../renderer/font/FontCharRenderer.scala | 125 ++++++++++++++++++ .../oc/client/renderer/font/FontParser.scala | 11 ++ src/main/scala/li/cil/oc/util/FontUtil.scala | 5 + 7 files changed, 212 insertions(+), 137 deletions(-) create mode 100644 src/main/scala/li/cil/oc/client/renderer/font/DataCharRenderer.scala create mode 100644 src/main/scala/li/cil/oc/client/renderer/font/FontCharRenderer.scala create mode 100644 src/main/scala/li/cil/oc/client/renderer/font/FontParser.scala create mode 100644 src/main/scala/li/cil/oc/util/FontUtil.scala diff --git a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala index ddc31e2ee..4fc4419fe 100644 --- a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala +++ b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala @@ -101,7 +101,7 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti def getLabel = "OpenComputers.TextBufferRenderer" - def ticks() = util.EnumSet.of(TickType.CLIENT) + def ticks = util.EnumSet.of(TickType.CLIENT) def tickStart(tickType: util.EnumSet[TickType], tickData: AnyRef*) = cache.cleanUp() diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DataCharRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DataCharRenderer.scala new file mode 100644 index 000000000..052b6852f --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/DataCharRenderer.scala @@ -0,0 +1,43 @@ +package li.cil.oc.client.renderer.font + +import li.cil.oc.util.FontUtil +import org.lwjgl.opengl.GL11 + +class DataCharRenderer extends DynamicCharRenderer { + val parser: FontParser = ??? + + override def charWidth: Double = parser.getGlyphWidth + + override def charHeight: Double = parser.getGlyphHeight + + override def drawChar(charCode: Int) { + val w = parser.getGlyphWidth * FontUtil.wcwidth(charCode) + val h = parser.getGlyphHeight + + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + GL11.glColor4f(1, 1, 1, 1) + GL11.glEnable(GL11.GL_BLEND) + + val texture = GL11.glGenTextures() + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture) + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, w, h, 0, GL11.GL_RGBA, GL11.GL_BYTE, parser.getGlyph(charCode)) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) + + GL11.glBegin(GL11.GL_QUADS) + GL11.glTexCoord2f(0, 1) + GL11.glVertex2f(0, h) + GL11.glTexCoord2f(1, 1) + GL11.glVertex2f(w, h) + GL11.glTexCoord2f(1, 0) + GL11.glVertex2f(w, 0) + GL11.glTexCoord2f(0, 0) + GL11.glVertex2f(0, 0) + GL11.glEnd() + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0) + GL11.glDeleteTextures(texture) + + GL11.glPopAttrib() + } +} diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala index 7ad53f7ba..56a0d50fb 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala @@ -1,125 +1,9 @@ package li.cil.oc.client.renderer.font -import java.awt.Font -import java.awt.font.FontRenderContext -import java.awt.geom.{PathIterator, Point2D} +trait DynamicCharRenderer { + def charWidth: Double -import org.lwjgl.opengl.GL11 -import org.lwjgl.util.glu.{GLU, GLUtessellatorCallbackAdapter} + def charHeight: Double -class DynamicCharRenderer(val font: Font) { - private val context = new FontRenderContext(font.getTransform, true, true) - private val callback = new DynamicCharRenderer.Callback() - private val maxCharBounds = font.getMaxCharBounds(context) - - def charWidth = maxCharBounds.getWidth.toFloat - - def charHeight = maxCharBounds.getHeight.toFloat - - def drawChar(char: Char) { - GL11.glPushMatrix() - GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) - GL11.glTranslated(-maxCharBounds.getX, -maxCharBounds.getY, 0) - GL11.glDisable(GL11.GL_TEXTURE_2D) - GL11.glColor4f(1, 1, 1, 1) - - val vector = font.createGlyphVector(context, Array(char)) - - val tess = GLU.gluNewTess() - tess.gluTessCallback(GLU.GLU_TESS_BEGIN, callback) - tess.gluTessCallback(GLU.GLU_TESS_END, callback) - tess.gluTessCallback(GLU.GLU_TESS_VERTEX, callback) - tess.gluTessCallback(GLU.GLU_TESS_EDGE_FLAG, callback) - tess.gluTessNormal(0, 0, -1) - - for (i <- 0 until vector.getNumGlyphs) { - val outline = vector.getGlyphOutline(i).getPathIterator(null) - tess.gluTessBeginPolygon(null) - val current = new Point2D.Double(0, 0) - val coords = new Array[Double](6) - while (!outline.isDone) { - if (outline.getWindingRule == PathIterator.WIND_EVEN_ODD) - tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD) - else - tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO) - outline.currentSegment(coords) match { - case PathIterator.SEG_MOVETO => - tess.gluTessBeginContour() - current.setLocation(coords(0), coords(1)) - case PathIterator.SEG_LINETO => - val buffer = Array[Float](coords(0).toFloat, coords(1).toFloat) - tess.gluTessVertex(coords, 0, buffer) - current.setLocation(coords(0), coords(1)) - case PathIterator.SEG_QUADTO => - // From SEG_QUADTO: - // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 - // with 0 <= t <= 1 - // B(n,m) = mth coefficient of nth degree Bernstein polynomial - // = C(n,m) * t^(m) * (1 - t)^(n-m) - // C(n,m) = Combinations of n things, taken m at a time - // = n! / (m! * (n-m)!) - // So: - // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 - // = C(2,0) * t^(0) * (1 - t)^(2-0) * CP + - // C(2,1) * t^(1) * (1 - t)^(2-1) * P1 + - // C(2,2) * t^(2) * (1 - t)^(2-2) * P2 - // = 2! / (0! * (2-0)!) * (1 - t)^2 * CP + - // 2! / (1! * (2-1)!) * t * (1 - t) * P1 + - // 2! / (2! * (2-2)!) * t^2 * P2 - // = (1 - t)^2 * CP + - // 2 * t * (1 - t) * P1 + - // t^2 * P2 - // = (1 - 2*t + t^2) * CP + - // 2 * (t - t^2) * P1 + - // t^2 * P2 - val interpolated = new Array[Double](3) - def p(t: Double) = { - val tSquared = t * t - val fc = 1 - 2 * t + tSquared - val f1 = 2 * (t - tSquared) - val f2 = tSquared - interpolated(0) = fc * current.x + f1 * coords(0) + f2 * coords(2) - interpolated(1) = fc * current.y + f1 * coords(1) + f2 * coords(3) - val buffer = Array[Float](interpolated(0).toFloat, interpolated(1).toFloat) - tess.gluTessVertex(interpolated, 0, buffer) - } - for (t <- 0.0 until 1.0 by 0.25) { - p(t) - } - current.setLocation(coords(2), coords(3)) - case PathIterator.SEG_CUBICTO => - // Not supported. - case PathIterator.SEG_CLOSE => - tess.gluTessEndContour() - case _ => // Wuh? - } - outline.next() - } - tess.gluTessEndPolygon() - } - - tess.gluDeleteTess() - - GL11.glPopAttrib() - GL11.glPopMatrix() - } + def drawChar(charCode: Int) } - -object DynamicCharRenderer { - - private class Callback extends GLUtessellatorCallbackAdapter { - override def begin(mode: Int) = GL11.glBegin(mode) - - override def end() = GL11.glEnd() - - override def vertex(coords: scala.Any) { - val point = coords.asInstanceOf[Array[Float]] - GL11.glVertex2f(point(0), point(1)) - } - - override def edgeFlag(boundaryEdge: Boolean) { - GL11.glEdgeFlag(boundaryEdge) - } - } - -} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala index 3f31f606b..d81d9b391 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala @@ -4,7 +4,7 @@ import java.awt.Font import java.io.InputStream import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture -import li.cil.oc.util.RenderState +import li.cil.oc.util.{FontUtil, RenderState} import org.lwjgl.BufferUtils import org.lwjgl.opengl._ @@ -19,7 +19,7 @@ class DynamicFontRenderer(val font: Font) extends TextureFontRenderer { def this(stream: InputStream, size: Int) = this(Font.createFont(Font.TRUETYPE_FONT, stream).deriveFont(size)) - private val charRenderer = new DynamicCharRenderer(font) + private val charRenderer: DynamicCharRenderer = new FontCharRenderer(font) private val textures = mutable.ArrayBuffer(new DynamicFontRenderer.CharTexture(this)) @@ -63,14 +63,7 @@ class DynamicFontRenderer(val font: Font) extends TextureFontRenderer { override protected def drawChar(tx: Float, ty: Float, char: Char) { val icon = charMap(char) if (icon.texture == activeTexture) { - GL11.glTexCoord2f(icon.u1, icon.v2) - GL11.glVertex2f(tx, ty + charHeight) - GL11.glTexCoord2f(icon.u2, icon.v2) - GL11.glVertex2f(tx + charWidth, ty + charHeight) - GL11.glTexCoord2f(icon.u2, icon.v1) - GL11.glVertex2f(tx + charWidth, ty) - GL11.glTexCoord2f(icon.u1, icon.v1) - GL11.glVertex2f(tx, ty) + icon.draw(tx, ty) } } @@ -120,8 +113,11 @@ object DynamicFontRenderer { def isFull = chars >= capacity def add(char: Char) = { + // TODO force to next row if wide char and won't fit into row. val x = chars % cols val y = chars / cols + val w = owner.charWidth * FontUtil.wcwidth(char) + val h = owner.charHeight GL11.glDisable(GL11.GL_DEPTH_TEST) GL11.glDepthMask(false) @@ -131,13 +127,13 @@ object DynamicFontRenderer { GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0) GL11.glClear(GL11.GL_COLOR_BUFFER_BIT) - GL11.glViewport(0, 0, owner.charWidth, owner.charHeight) + GL11.glViewport(0, 0, w, h) GL11.glMatrixMode(GL11.GL_PROJECTION) GL11.glPushMatrix() GL11.glLoadIdentity() - GL11.glOrtho(0, owner.charWidth, owner.charHeight, 0, 0, 1) + GL11.glOrtho(0, w, h, 0, 0, 1) GL11.glMatrixMode(GL11.GL_MODELVIEW) GL11.glPushMatrix() @@ -146,7 +142,7 @@ object DynamicFontRenderer { owner.charRenderer.drawChar(char) - GL43.glCopyImageSubData(owner.rbo, GL30.GL_RENDERBUFFER, 0, 0, 0, 0, id, GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, 0, owner.charWidth, owner.charHeight, 1) + GL43.glCopyImageSubData(owner.rbo, GL30.GL_RENDERBUFFER, 0, 0, 0, 0, id, GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, 0, w, h, 1) GL11.glMatrixMode(GL11.GL_PROJECTION) GL11.glPopMatrix() @@ -155,12 +151,23 @@ object DynamicFontRenderer { GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0) - chars += 1 + chars += FontUtil.wcwidth(char) - new CharIcon(this, pad + x * uStep, pad + y * vStep, (x + 1) * uStep - 2 * pad, (y + 1) * vStep - 2 * pad) + new CharIcon(this, w, h, pad + x * uStep, pad + y * vStep, (x + 1) * uStep - 2 * pad, (y + 1) * vStep - 2 * pad) } } - class CharIcon(val texture: CharTexture, val u1: Float, val v1: Float, val u2: Float, val v2: Float) + class CharIcon(val texture: CharTexture, val w: Float, val h: Float, val u1: Float, val v1: Float, val u2: Float, val v2: Float) { + def draw(tx: Float, ty: Float) { + GL11.glTexCoord2f(u1, v1) + GL11.glVertex2f(tx, ty + h) + GL11.glTexCoord2f(u2, v1) + GL11.glVertex2f(tx + w, ty + h) + GL11.glTexCoord2f(u2, v2) + GL11.glVertex2f(tx + w, ty) + GL11.glTexCoord2f(u1, v2) + GL11.glVertex2f(tx, ty) + } + } } \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/font/FontCharRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/FontCharRenderer.scala new file mode 100644 index 000000000..a56bc6869 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/FontCharRenderer.scala @@ -0,0 +1,125 @@ +package li.cil.oc.client.renderer.font + +import java.awt.Font +import java.awt.font.FontRenderContext +import java.awt.geom.{PathIterator, Point2D} + +import org.lwjgl.opengl.GL11 +import org.lwjgl.util.glu.{GLU, GLUtessellatorCallbackAdapter} + +class FontCharRenderer(val font: Font) extends DynamicCharRenderer { + private val context = new FontRenderContext(font.getTransform, true, true) + private val callback = new FontCharRenderer.Callback() + private val maxCharBounds = font.getMaxCharBounds(context) + + def charWidth = maxCharBounds.getWidth + + def charHeight = maxCharBounds.getHeight + + def drawChar(charCode: Int) { + GL11.glPushMatrix() + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + GL11.glTranslated(-maxCharBounds.getX, -maxCharBounds.getY, 0) + GL11.glDisable(GL11.GL_TEXTURE_2D) + GL11.glColor4f(1, 1, 1, 1) + + val vector = font.createGlyphVector(context, Array(charCode.toChar)) + + val tess = GLU.gluNewTess() + tess.gluTessCallback(GLU.GLU_TESS_BEGIN, callback) + tess.gluTessCallback(GLU.GLU_TESS_END, callback) + tess.gluTessCallback(GLU.GLU_TESS_VERTEX, callback) + tess.gluTessCallback(GLU.GLU_TESS_EDGE_FLAG, callback) + tess.gluTessNormal(0, 0, -1) + + for (i <- 0 until vector.getNumGlyphs) { + val outline = vector.getGlyphOutline(i).getPathIterator(null) + tess.gluTessBeginPolygon(null) + val current = new Point2D.Double(0, 0) + val coords = new Array[Double](6) + while (!outline.isDone) { + if (outline.getWindingRule == PathIterator.WIND_EVEN_ODD) + tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD) + else + tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO) + outline.currentSegment(coords) match { + case PathIterator.SEG_MOVETO => + tess.gluTessBeginContour() + current.setLocation(coords(0), coords(1)) + case PathIterator.SEG_LINETO => + val buffer = Array[Float](coords(0).toFloat, coords(1).toFloat) + tess.gluTessVertex(coords, 0, buffer) + current.setLocation(coords(0), coords(1)) + case PathIterator.SEG_QUADTO => + // From SEG_QUADTO: + // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 + // with 0 <= t <= 1 + // B(n,m) = mth coefficient of nth degree Bernstein polynomial + // = C(n,m) * t^(m) * (1 - t)^(n-m) + // C(n,m) = Combinations of n things, taken m at a time + // = n! / (m! * (n-m)!) + // So: + // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 + // = C(2,0) * t^(0) * (1 - t)^(2-0) * CP + + // C(2,1) * t^(1) * (1 - t)^(2-1) * P1 + + // C(2,2) * t^(2) * (1 - t)^(2-2) * P2 + // = 2! / (0! * (2-0)!) * (1 - t)^2 * CP + + // 2! / (1! * (2-1)!) * t * (1 - t) * P1 + + // 2! / (2! * (2-2)!) * t^2 * P2 + // = (1 - t)^2 * CP + + // 2 * t * (1 - t) * P1 + + // t^2 * P2 + // = (1 - 2*t + t^2) * CP + + // 2 * (t - t^2) * P1 + + // t^2 * P2 + val interpolated = new Array[Double](3) + def p(t: Double) = { + val tSquared = t * t + val fc = 1 - 2 * t + tSquared + val f1 = 2 * (t - tSquared) + val f2 = tSquared + interpolated(0) = fc * current.x + f1 * coords(0) + f2 * coords(2) + interpolated(1) = fc * current.y + f1 * coords(1) + f2 * coords(3) + val buffer = Array[Float](interpolated(0).toFloat, interpolated(1).toFloat) + tess.gluTessVertex(interpolated, 0, buffer) + } + for (t <- 0.0 until 1.0 by 0.25) { + p(t) + } + current.setLocation(coords(2), coords(3)) + case PathIterator.SEG_CUBICTO => + // Not supported. + case PathIterator.SEG_CLOSE => + tess.gluTessEndContour() + case _ => // Wuh? + } + outline.next() + } + tess.gluTessEndPolygon() + } + + tess.gluDeleteTess() + + GL11.glPopAttrib() + GL11.glPopMatrix() + } +} + +object FontCharRenderer { + + private class Callback extends GLUtessellatorCallbackAdapter { + override def begin(mode: Int) = GL11.glBegin(mode) + + override def end() = GL11.glEnd() + + override def vertex(coords: scala.Any) { + val point = coords.asInstanceOf[Array[Float]] + GL11.glVertex2f(point(0), point(1)) + } + + override def edgeFlag(boundaryEdge: Boolean) { + GL11.glEdgeFlag(boundaryEdge) + } + } + +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/font/FontParser.scala b/src/main/scala/li/cil/oc/client/renderer/font/FontParser.scala new file mode 100644 index 000000000..74c672537 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/FontParser.scala @@ -0,0 +1,11 @@ +package li.cil.oc.client.renderer.font + +import java.nio.ByteBuffer + +trait FontParser { + def getGlyph(charCode: Int): ByteBuffer + + def getGlyphWidth: Int + + def getGlyphHeight: Int +} diff --git a/src/main/scala/li/cil/oc/util/FontUtil.scala b/src/main/scala/li/cil/oc/util/FontUtil.scala new file mode 100644 index 000000000..7e621d043 --- /dev/null +++ b/src/main/scala/li/cil/oc/util/FontUtil.scala @@ -0,0 +1,5 @@ +package li.cil.oc.util + +object FontUtil { + def wcwidth(charCode: Int) = 1 // TODO +}