mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-16 18:55:03 -04:00
Reworked font rendering system to be a little more generic.
Added font renderer i've been tinkering with that generates character textures on the fly from an actual font, in the hopes of supporting arbitrary unicode one day. It's terribly broken, still, though.
This commit is contained in:
parent
cbfe1dd25a
commit
cfd8b5db16
@ -1,2 +1,2 @@
|
||||
☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
|
||||
5 9
|
||||
10 18
|
@ -3,7 +3,7 @@ package li.cil.oc.client.gui
|
||||
import java.util
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.Settings
|
||||
import li.cil.oc.client.renderer.MonospaceFontRenderer
|
||||
import li.cil.oc.client.renderer.TextBufferRenderCache
|
||||
import li.cil.oc.client.renderer.gui.BufferRenderer
|
||||
import li.cil.oc.client.{PacketSender => ClientPacketSender, Textures}
|
||||
import li.cil.oc.common.container
|
||||
@ -250,8 +250,8 @@ class Robot(playerInventory: InventoryPlayer, val robot: tileentity.Robot) exten
|
||||
}
|
||||
|
||||
override protected def changeSize(w: Double, h: Double, recompile: Boolean) = {
|
||||
val bw = w * MonospaceFontRenderer.fontWidth
|
||||
val bh = h * MonospaceFontRenderer.fontHeight
|
||||
val bw = w * TextBufferRenderCache.renderer.charRenderWidth
|
||||
val bh = h * TextBufferRenderCache.renderer.charRenderHeight
|
||||
val scaleX = math.min(bufferWidth / (bw + bufferMargin * 2.0), 1)
|
||||
val scaleY = math.min(bufferHeight / (bh + bufferMargin * 2.0), 1)
|
||||
math.min(scaleX, scaleY)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package li.cil.oc.client.gui
|
||||
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.client.renderer.MonospaceFontRenderer
|
||||
import li.cil.oc.client.renderer.TextBufferRenderCache
|
||||
import li.cil.oc.client.renderer.gui.BufferRenderer
|
||||
import li.cil.oc.util.RenderState
|
||||
import org.lwjgl.input.Mouse
|
||||
@ -27,8 +27,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha
|
||||
if (Mouse.hasWheel && Mouse.getEventDWheel != 0) {
|
||||
val mouseX = Mouse.getEventX * width / mc.displayWidth
|
||||
val mouseY = height - Mouse.getEventY * height / mc.displayHeight - 1
|
||||
val bx = (mouseX - x - bufferMargin) / MonospaceFontRenderer.fontWidth + 1
|
||||
val by = (mouseY - y - bufferMargin) / MonospaceFontRenderer.fontHeight + 1
|
||||
val bx = (mouseX - x - bufferMargin) / TextBufferRenderCache.renderer.charRenderWidth + 1
|
||||
val by = (mouseY - y - bufferMargin) / TextBufferRenderCache.renderer.charRenderHeight + 1
|
||||
val bw = buffer.getWidth
|
||||
val bh = buffer.getHeight
|
||||
if (bx > 0 && by > 0 && bx <= bw && by <= bh) {
|
||||
@ -60,8 +60,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha
|
||||
super.mouseMovedOrUp(mouseX, mouseY, button)
|
||||
if (button >= 0) {
|
||||
if (didDrag) {
|
||||
val bx = ((mouseX - x - bufferMargin) / scale / MonospaceFontRenderer.fontWidth).toInt + 1
|
||||
val by = ((mouseY - y - bufferMargin) / scale / MonospaceFontRenderer.fontHeight).toInt + 1
|
||||
val bx = ((mouseX - x - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderWidth).toInt + 1
|
||||
val by = ((mouseY - y - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderHeight).toInt + 1
|
||||
val bw = buffer.getWidth
|
||||
val bh = buffer.getHeight
|
||||
if (bx > 0 && by > 0 && bx <= bw && by <= bh) {
|
||||
@ -78,8 +78,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha
|
||||
}
|
||||
|
||||
private def clickOrDrag(mouseX: Int, mouseY: Int, button: Int) {
|
||||
val bx = ((mouseX - x - bufferMargin) / scale / MonospaceFontRenderer.fontWidth).toInt + 1
|
||||
val by = ((mouseY - y - bufferMargin) / scale / MonospaceFontRenderer.fontHeight).toInt + 1
|
||||
val bx = ((mouseX - x - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderWidth).toInt + 1
|
||||
val by = ((mouseY - y - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderHeight).toInt + 1
|
||||
val bw = buffer.getWidth
|
||||
val bh = buffer.getHeight
|
||||
if (bx > 0 && by > 0 && bx <= bw && by <= bh) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package li.cil.oc.client.gui
|
||||
|
||||
import li.cil.oc.client.{Textures, KeyBindings}
|
||||
import li.cil.oc.client.renderer.MonospaceFontRenderer
|
||||
import li.cil.oc.client.renderer.gui.BufferRenderer
|
||||
import li.cil.oc.util.RenderState
|
||||
import li.cil.oc.util.mods.NEI
|
||||
@ -35,7 +34,6 @@ trait TextBuffer extends GuiScreen {
|
||||
|
||||
override def initGui() = {
|
||||
super.initGui()
|
||||
MonospaceFontRenderer.init(Minecraft.getMinecraft.renderEngine)
|
||||
BufferRenderer.init(Minecraft.getMinecraft.renderEngine)
|
||||
Keyboard.enableRepeatEvents(true)
|
||||
}
|
||||
|
@ -1,156 +0,0 @@
|
||||
package li.cil.oc.client.renderer
|
||||
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.client.Textures
|
||||
import li.cil.oc.util.{RenderState, PackedColor}
|
||||
import li.cil.oc.{OpenComputers, Settings}
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.texture.TextureManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import org.lwjgl.opengl.GL11
|
||||
import scala.io.Source
|
||||
|
||||
// IMPORTANT: we must not use the tessellator here. Doing so can cause
|
||||
// crashes on certain graphics cards with certain drivers (reported for
|
||||
// ATI/AMD and Intel chip sets). These crashes have been reported to
|
||||
// happen I have no idea why, and can only guess that it's related to
|
||||
// using the VBO/ARB the tessellator uses inside a display list (since
|
||||
// this stuff is eventually only rendered via display lists).
|
||||
|
||||
object MonospaceFontRenderer {
|
||||
val (chars, fontWidth, fontHeight) = try {
|
||||
val lines = Source.fromInputStream(Minecraft.getMinecraft.getResourceManager.getResource(new ResourceLocation(Settings.resourceDomain, "textures/font/chars.txt")).getInputStream)("UTF-8").getLines()
|
||||
val chars = lines.next()
|
||||
val (w, h) = if (lines.hasNext) {
|
||||
val size = lines.next().split(" ", 2)
|
||||
(size(0).toInt, size(1).toInt)
|
||||
} else (5, 9)
|
||||
(chars, w, h)
|
||||
}
|
||||
catch {
|
||||
case t: Throwable =>
|
||||
OpenComputers.log.log(Level.WARNING, "Failed reading font metadata, using defaults.", t)
|
||||
( """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■""", 5, 9)
|
||||
}
|
||||
|
||||
private var instance: Option[Renderer] = None
|
||||
|
||||
def init(textureManager: TextureManager) = this.synchronized(
|
||||
instance = instance.orElse(Some(new Renderer(textureManager))))
|
||||
|
||||
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = this.synchronized(instance match {
|
||||
case None => OpenComputers.log.warning("Trying to render string with uninitialized MonospaceFontRenderer.")
|
||||
case Some(renderer) => renderer.drawString(x, y, value, color, format)
|
||||
})
|
||||
|
||||
private class Renderer(private val textureManager: TextureManager) {
|
||||
private val (charWidth, charHeight) = (MonospaceFontRenderer.fontWidth * 2, MonospaceFontRenderer.fontHeight * 2)
|
||||
private val cols = 256 / charWidth
|
||||
private val uStep = charWidth / 256.0
|
||||
private val uSize = uStep
|
||||
private val vStep = (charHeight + 1) / 256.0
|
||||
private val vSize = charHeight / 256.0
|
||||
private val s = Settings.get.fontCharScale
|
||||
private val dw = charWidth * s - charWidth
|
||||
private val dh = charHeight * s - charHeight
|
||||
|
||||
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = {
|
||||
if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.")
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawString: entering (aka: wasntme)")
|
||||
|
||||
if (Settings.get.textAntiAlias)
|
||||
textureManager.bindTexture(Textures.fontAntiAliased)
|
||||
else
|
||||
textureManager.bindTexture(Textures.fontAliased)
|
||||
GL11.glPushMatrix()
|
||||
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS)
|
||||
|
||||
GL11.glTranslatef(x, y, 0)
|
||||
GL11.glScalef(0.5f, 0.5f, 1)
|
||||
GL11.glDepthMask(false)
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D)
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawString: configure state")
|
||||
|
||||
GL11.glBegin(GL11.GL_QUADS)
|
||||
// Background first. We try to merge adjacent backgrounds of the same
|
||||
// color to reduce the number of quads we have to draw.
|
||||
var cbg = 0x000000
|
||||
var offset = 0
|
||||
var width = 0
|
||||
for (col <- color.map(PackedColor.unpackBackground(_, format))) {
|
||||
if (col != cbg) {
|
||||
draw(cbg, offset, width)
|
||||
cbg = col
|
||||
offset += width
|
||||
width = 0
|
||||
}
|
||||
width = width + 1
|
||||
}
|
||||
draw(cbg, offset, width)
|
||||
GL11.glEnd()
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawString: background")
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D)
|
||||
|
||||
if (Settings.get.textLinearFiltering) {
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR)
|
||||
}
|
||||
|
||||
// Foreground second. We only have to flush when the color changes, so
|
||||
// unless every char has a different color this should be quite efficient.
|
||||
GL11.glBegin(GL11.GL_QUADS)
|
||||
var cfg = -1
|
||||
var posX = 0.0
|
||||
for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, format)))) {
|
||||
val index = 1 + (chars.indexOf(ch) match {
|
||||
case -1 => chars.indexOf('?')
|
||||
case i => i
|
||||
})
|
||||
if (col != cfg) {
|
||||
// Color changed.
|
||||
cfg = col
|
||||
GL11.glColor3ub(
|
||||
((cfg & 0xFF0000) >> 16).toByte,
|
||||
((cfg & 0x00FF00) >> 8).toByte,
|
||||
((cfg & 0x0000FF) >> 0).toByte)
|
||||
}
|
||||
if (ch != ' ') {
|
||||
// Don't render whitespace.
|
||||
val x = (index - 1) % cols
|
||||
val y = (index - 1) / cols
|
||||
val u = x * uStep
|
||||
val v = y * vStep
|
||||
GL11.glTexCoord2d(u, v + vSize)
|
||||
GL11.glVertex3d(posX - dw, charHeight * s, 0)
|
||||
GL11.glTexCoord2d(u + uSize, v + vSize)
|
||||
GL11.glVertex3d(posX + charWidth * s, charHeight * s, 0)
|
||||
GL11.glTexCoord2d(u + uSize, v)
|
||||
GL11.glVertex3d(posX + charWidth * s, -dh, 0)
|
||||
GL11.glTexCoord2d(u, v)
|
||||
GL11.glVertex3d(posX - dw, -dh, 0)
|
||||
}
|
||||
posX += charWidth
|
||||
}
|
||||
GL11.glEnd()
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawString: foreground")
|
||||
|
||||
GL11.glPopAttrib()
|
||||
GL11.glPopMatrix()
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawString: leaving")
|
||||
}
|
||||
|
||||
private def draw(color: Int, offset: Int, width: Int) = if (color != 0 && width > 0) {
|
||||
GL11.glColor3ub(((color >> 16) & 0xFF).toByte, ((color >> 8) & 0xFF).toByte, (color & 0xFF).toByte)
|
||||
GL11.glVertex3d(charWidth * offset, charHeight, 0)
|
||||
GL11.glVertex3d(charWidth * (offset + width), charHeight, 0)
|
||||
GL11.glVertex3d(charWidth * (offset + width), 0, 0)
|
||||
GL11.glVertex3d(charWidth * offset, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,18 +1,23 @@
|
||||
package li.cil.oc.client.renderer
|
||||
|
||||
import com.google.common.cache.{RemovalListener, RemovalNotification, CacheBuilder}
|
||||
import cpw.mods.fml.common.{ITickHandler, TickType}
|
||||
import java.util
|
||||
import java.util.concurrent.{Callable, TimeUnit}
|
||||
|
||||
import com.google.common.cache.{CacheBuilder, RemovalListener, RemovalNotification}
|
||||
import cpw.mods.fml.common.{ITickHandler, TickType}
|
||||
import li.cil.oc.client.renderer.font.StaticFontRenderer
|
||||
import li.cil.oc.common.component.TextBuffer
|
||||
import li.cil.oc.util.RenderState
|
||||
import net.minecraft.client.renderer.GLAllocation
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import org.lwjgl.opengl.GL11
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEntity, Int] with ITickHandler {
|
||||
val cache = com.google.common.cache.CacheBuilder.newBuilder().
|
||||
val renderer =
|
||||
new StaticFontRenderer()
|
||||
// new DynamicFontRenderer("Terminal")
|
||||
|
||||
private val cache = com.google.common.cache.CacheBuilder.newBuilder().
|
||||
expireAfterAccess(2, TimeUnit.SECONDS).
|
||||
removalListener(this).
|
||||
asInstanceOf[CacheBuilder[TextBuffer, Int]].
|
||||
@ -26,7 +31,6 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def render(buffer: TextBuffer) {
|
||||
MonospaceFontRenderer.init(Minecraft.getMinecraft.getTextureManager)
|
||||
currentBuffer = buffer
|
||||
compileOrDraw(cache.get(currentBuffer, this))
|
||||
}
|
||||
@ -35,6 +39,10 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti
|
||||
if (currentBuffer.proxy.dirty) {
|
||||
RenderState.checkError(getClass.getName + ".compileOrDraw: entering (aka: wasntme)")
|
||||
|
||||
for (line <- currentBuffer.data.buffer) {
|
||||
renderer.generateChars(line)
|
||||
}
|
||||
|
||||
val doCompile = !RenderState.compilingDisplayList
|
||||
if (doCompile) {
|
||||
currentBuffer.proxy.dirty = false
|
||||
@ -43,9 +51,7 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti
|
||||
RenderState.checkError(getClass.getName + ".compileOrDraw: glNewList")
|
||||
}
|
||||
|
||||
for (((line, color), i) <- currentBuffer.data.buffer.zip(currentBuffer.data.color).zipWithIndex) {
|
||||
MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, currentBuffer.data.format)
|
||||
}
|
||||
renderer.drawBuffer(currentBuffer.data)
|
||||
|
||||
RenderState.checkError(getClass.getName + ".compileOrDraw: drawString")
|
||||
|
||||
|
@ -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 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()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package li.cil.oc.client.renderer.font
|
||||
|
||||
import java.awt.Font
|
||||
import java.io.InputStream
|
||||
|
||||
import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture
|
||||
import li.cil.oc.util.RenderState
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.opengl._
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* Font renderer that dynamically generates lookup textures by rendering a font
|
||||
* to it. It's pretty broken right now, and font rendering looks crappy as hell.
|
||||
*/
|
||||
class DynamicFontRenderer(val font: Font) extends TextureFontRenderer {
|
||||
def this(name: String) = this(new Font(name, Font.PLAIN, 11))
|
||||
|
||||
def this(stream: InputStream) = this(Font.createFont(Font.TRUETYPE_FONT, stream).deriveFont(11f))
|
||||
|
||||
private val charRenderer = new DynamicCharRenderer(font)
|
||||
|
||||
private val textures = mutable.ArrayBuffer(new DynamicFontRenderer.CharTexture(this))
|
||||
|
||||
private val charMap = mutable.Map.empty[Char, DynamicFontRenderer.CharIcon]
|
||||
|
||||
private val fbo = GL30.glGenFramebuffers()
|
||||
|
||||
private val rbo = GL30.glGenRenderbuffers()
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo)
|
||||
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rbo)
|
||||
|
||||
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_RGBA8, charWidth, charHeight)
|
||||
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RENDERBUFFER, rbo)
|
||||
|
||||
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, 0)
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
|
||||
|
||||
var activeTexture: CharTexture = textures(0)
|
||||
|
||||
generateChars(basicChars.toCharArray)
|
||||
|
||||
RenderState.checkError(getClass.getName + ".<init>: glGenFramebuffers")
|
||||
|
||||
override protected def charWidth = charRenderer.charWidth.toInt
|
||||
|
||||
override protected def charHeight = charRenderer.charHeight.toInt
|
||||
|
||||
override protected def textureCount = textures.length
|
||||
|
||||
override protected def bindTexture(index: Int) {
|
||||
activeTexture = textures(index)
|
||||
activeTexture.bind()
|
||||
RenderState.checkError(getClass.getName + ".bindTexture")
|
||||
}
|
||||
|
||||
override protected def generateChar(char: Char) {
|
||||
charMap.getOrElseUpdate(char, createCharIcon(char))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private def createCharIcon(char: Char): DynamicFontRenderer.CharIcon = {
|
||||
if (!font.canDisplay(char)) {
|
||||
if (char == '?') null
|
||||
else charMap.getOrElseUpdate('?', createCharIcon('?'))
|
||||
}
|
||||
else {
|
||||
if (textures.last.isFull) {
|
||||
textures += new DynamicFontRenderer.CharTexture(this)
|
||||
textures.last.bind()
|
||||
}
|
||||
textures.last.add(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DynamicFontRenderer {
|
||||
private val size = 256
|
||||
|
||||
class CharTexture(val owner: DynamicFontRenderer) {
|
||||
private val id = GL11.glGenTextures()
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id)
|
||||
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.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(size * size * 4))
|
||||
|
||||
RenderState.checkError(getClass.getName + ".<init>: create texture")
|
||||
|
||||
// Some padding to avoid bleeding.
|
||||
private val cellWidth = owner.charWidth + 2
|
||||
private val cellHeight = owner.charHeight + 2
|
||||
private val cols = size / cellWidth
|
||||
private val rows = size / cellHeight
|
||||
private val uStep = cellWidth / size.toFloat
|
||||
private val vStep = cellHeight / size.toFloat
|
||||
private val pad = 1f / size
|
||||
private val capacity = cols * rows
|
||||
|
||||
private var chars = 0
|
||||
|
||||
def bind() {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id)
|
||||
}
|
||||
|
||||
def isFull = chars >= capacity
|
||||
|
||||
def add(char: Char) = {
|
||||
val x = chars % cols
|
||||
val y = chars / cols
|
||||
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST)
|
||||
GL11.glDepthMask(false)
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, owner.fbo)
|
||||
GL11.glClearColor(0, 0, 0, 0)
|
||||
GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0)
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)
|
||||
|
||||
GL11.glViewport(0, 0, owner.charWidth, owner.charHeight)
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION)
|
||||
GL11.glPushMatrix()
|
||||
GL11.glLoadIdentity()
|
||||
|
||||
GL11.glOrtho(0, owner.charWidth, owner.charHeight, 0, 0, 1)
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW)
|
||||
GL11.glPushMatrix()
|
||||
GL11.glLoadIdentity()
|
||||
GL11.glTranslatef(0, 0, -0.5f)
|
||||
|
||||
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)
|
||||
|
||||
GL11.glMatrixMode(GL11.GL_PROJECTION)
|
||||
GL11.glPopMatrix()
|
||||
GL11.glMatrixMode(GL11.GL_MODELVIEW)
|
||||
GL11.glPopMatrix()
|
||||
|
||||
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
|
||||
|
||||
chars += 1
|
||||
|
||||
new CharIcon(this, 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)
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package li.cil.oc.client.renderer.font
|
||||
|
||||
import scala.io.Source
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import li.cil.oc.{OpenComputers, Settings}
|
||||
import java.util.logging.Level
|
||||
import li.cil.oc.client.Textures
|
||||
import org.lwjgl.opengl.GL11
|
||||
|
||||
/**
|
||||
* Font renderer using a user specified texture file, meaning the list of
|
||||
* supported characters is fixed. But at least this one works.
|
||||
*/
|
||||
class StaticFontRenderer extends TextureFontRenderer {
|
||||
protected val (chars, charWidth, charHeight) = try {
|
||||
val lines = Source.fromInputStream(Minecraft.getMinecraft.getResourceManager.getResource(new ResourceLocation(Settings.resourceDomain, "textures/font/chars.txt")).getInputStream)("UTF-8").getLines()
|
||||
val chars = lines.next()
|
||||
val (w, h) = if (lines.hasNext) {
|
||||
val size = lines.next().split(" ", 2)
|
||||
(size(0).toInt, size(1).toInt)
|
||||
} else (10, 18)
|
||||
(chars, w, h)
|
||||
}
|
||||
catch {
|
||||
case t: Throwable =>
|
||||
OpenComputers.log.log(Level.WARNING, "Failed reading font metadata, using defaults.", t)
|
||||
(basicChars, 10, 18)
|
||||
}
|
||||
|
||||
private val cols = 256 / charWidth
|
||||
private val uStep = charWidth / 256.0
|
||||
private val uSize = uStep
|
||||
private val vStep = (charHeight + 1) / 256.0
|
||||
private val vSize = charHeight / 256.0
|
||||
private val s = Settings.get.fontCharScale
|
||||
private val dw = charWidth * s - charWidth
|
||||
private val dh = charHeight * s - charHeight
|
||||
|
||||
override protected def textureCount = 1
|
||||
|
||||
override protected def bindTexture(index: Int) {
|
||||
if (Settings.get.textAntiAlias) {
|
||||
Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.fontAntiAliased)
|
||||
}
|
||||
else {
|
||||
Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.fontAliased)
|
||||
}
|
||||
}
|
||||
|
||||
override protected def drawChar(tx: Float, ty: Float, char: Char) {
|
||||
val index = 1 + (chars.indexOf(char) match {
|
||||
case -1 => chars.indexOf('?')
|
||||
case i => i
|
||||
})
|
||||
val x = (index - 1) % cols
|
||||
val y = (index - 1) / cols
|
||||
val u = x * uStep
|
||||
val v = y * vStep
|
||||
GL11.glTexCoord2d(u, v + vSize)
|
||||
GL11.glVertex3d(tx - dw, ty + charHeight * s, 0)
|
||||
GL11.glTexCoord2d(u + uSize, v + vSize)
|
||||
GL11.glVertex3d(tx + charWidth * s, ty + charHeight * s, 0)
|
||||
GL11.glTexCoord2d(u + uSize, v)
|
||||
GL11.glVertex3d(tx + charWidth * s, ty - dh, 0)
|
||||
GL11.glTexCoord2d(u, v)
|
||||
GL11.glVertex3d(tx - dw, ty - dh, 0)
|
||||
}
|
||||
|
||||
override protected def generateChar(char: Char) {}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package li.cil.oc.client.renderer.font
|
||||
|
||||
import li.cil.oc.Settings
|
||||
import li.cil.oc.util.{TextBuffer, RenderState, PackedColor}
|
||||
import org.lwjgl.opengl.GL11
|
||||
|
||||
/**
|
||||
* Base class for texture based font rendering.
|
||||
*
|
||||
* Provides common logic for the static one (using an existing texture) and the
|
||||
* dynamic one (generating textures on the fly from a font).
|
||||
*/
|
||||
abstract class TextureFontRenderer {
|
||||
protected final val basicChars = """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■"""
|
||||
|
||||
def charRenderWidth = charWidth / 2
|
||||
|
||||
def charRenderHeight = charHeight / 2
|
||||
|
||||
/**
|
||||
* If drawString() is called inside display lists this should be called
|
||||
* beforehand, outside the display list, to ensure no characters have to
|
||||
* be generated inside the draw call.
|
||||
*/
|
||||
def generateChars(chars: Array[Char]) {
|
||||
for (char <- chars) {
|
||||
generateChar(char)
|
||||
}
|
||||
}
|
||||
|
||||
def drawBuffer(buffer: TextBuffer) {
|
||||
val format = buffer.format
|
||||
|
||||
GL11.glPushMatrix()
|
||||
GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS)
|
||||
|
||||
GL11.glScalef(0.5f, 0.5f, 1)
|
||||
|
||||
GL11.glDepthMask(false)
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D)
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawBuffer: configure state")
|
||||
|
||||
// Background first. We try to merge adjacent backgrounds of the same
|
||||
// color to reduce the number of quads we have to draw.
|
||||
GL11.glBegin(GL11.GL_QUADS)
|
||||
for (y <- 0 until buffer.height) {
|
||||
val color = buffer.color(y)
|
||||
var cbg = 0x000000
|
||||
var x = 0
|
||||
var width = 0
|
||||
for (col <- color.map(PackedColor.unpackBackground(_, format))) {
|
||||
if (col != cbg) {
|
||||
drawQuad(cbg, x, y, width)
|
||||
cbg = col
|
||||
x += width
|
||||
width = 0
|
||||
}
|
||||
width = width + 1
|
||||
}
|
||||
drawQuad(cbg, x, y, width)
|
||||
}
|
||||
GL11.glEnd()
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawBuffer: background")
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D)
|
||||
|
||||
if (Settings.get.textLinearFiltering) {
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR)
|
||||
}
|
||||
|
||||
// Foreground second. We only have to flush when the color changes, so
|
||||
// unless every char has a different color this should be quite efficient.
|
||||
for (y <- 0 until buffer.height) {
|
||||
val line = buffer.buffer(y)
|
||||
val color = buffer.color(y)
|
||||
val ty = y * charHeight
|
||||
for (i <- 0 until textureCount) {
|
||||
bindTexture(i)
|
||||
GL11.glBegin(GL11.GL_QUADS)
|
||||
var cfg = -1
|
||||
var tx = 0f
|
||||
for (n <- 0 until line.length) {
|
||||
val ch = line(n)
|
||||
val col = PackedColor.unpackForeground(color(n), format)
|
||||
// Check if color changed.
|
||||
if (col != cfg) {
|
||||
cfg = col
|
||||
GL11.glColor3ub(
|
||||
((cfg & 0xFF0000) >> 16).toByte,
|
||||
((cfg & 0x00FF00) >> 8).toByte,
|
||||
((cfg & 0x0000FF) >> 0).toByte)
|
||||
}
|
||||
// Don't render whitespace.
|
||||
if (ch != ' ') {
|
||||
drawChar(tx, ty, ch)
|
||||
}
|
||||
tx += charWidth
|
||||
}
|
||||
GL11.glEnd()
|
||||
}
|
||||
}
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawBuffer: foreground")
|
||||
|
||||
GL11.glPopAttrib()
|
||||
GL11.glPopMatrix()
|
||||
|
||||
RenderState.checkError(getClass.getName + ".drawBuffer: leaving")
|
||||
}
|
||||
|
||||
protected def charWidth: Int
|
||||
|
||||
protected def charHeight: Int
|
||||
|
||||
protected def textureCount: Int
|
||||
|
||||
protected def bindTexture(index: Int)
|
||||
|
||||
protected def generateChar(char: Char)
|
||||
|
||||
protected def drawChar(tx: Float, ty: Float, char: Char)
|
||||
|
||||
private def drawQuad(color: Int, x: Int, y: Int, width: Int) = if (color != 0 && width > 0) {
|
||||
val x0 = x * charWidth
|
||||
val x1 = (x + width) * charWidth
|
||||
val y0 = y * charHeight
|
||||
val y1 = (y + 1) * charHeight
|
||||
GL11.glColor3ub(((color >> 16) & 0xFF).toByte, ((color >> 8) & 0xFF).toByte, (color & 0xFF).toByte)
|
||||
GL11.glVertex3d(x0, y1, 0)
|
||||
GL11.glVertex3d(x1, y1, 0)
|
||||
GL11.glVertex3d(x1, y0, 0)
|
||||
GL11.glVertex3d(x0, y0, 0)
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ import li.cil.oc.common.tileentity.Screen
|
||||
import li.cil.oc.util.RenderState
|
||||
import li.cil.oc.util.mods.BuildCraft
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer
|
||||
import net.minecraft.client.renderer.Tessellator
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import net.minecraftforge.common.ForgeDirection
|
||||
import org.lwjgl.opengl.GL11
|
||||
|
@ -7,7 +7,7 @@ import li.cil.oc.api.component.TextBuffer.ColorDepth
|
||||
import li.cil.oc.api.driver.Container
|
||||
import li.cil.oc.api.network._
|
||||
import li.cil.oc.client.{PacketSender => ClientPacketSender, ComponentTracker => ClientComponentTracker}
|
||||
import li.cil.oc.client.renderer.{MonospaceFontRenderer, TextBufferRenderCache}
|
||||
import li.cil.oc.client.renderer.TextBufferRenderCache
|
||||
import li.cil.oc.common.tileentity
|
||||
import li.cil.oc.server.{PacketSender => ServerPacketSender, ComponentTracker => ServerComponentTracker}
|
||||
import li.cil.oc.server.component.Keyboard
|
||||
@ -293,10 +293,10 @@ class TextBuffer(val owner: Container) extends ManagedComponent with api.compone
|
||||
override def renderText() = relativeLitArea != 0 && proxy.render()
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
override def renderWidth = MonospaceFontRenderer.fontWidth * data.width
|
||||
override def renderWidth = TextBufferRenderCache.renderer.charRenderWidth * data.width
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
override def renderHeight = MonospaceFontRenderer.fontHeight * data.height
|
||||
override def renderHeight = TextBufferRenderCache.renderer.charRenderHeight * data.height
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
override def setRenderingEnabled(enabled: Boolean) = isRendering = enabled
|
||||
|
Loading…
x
Reference in New Issue
Block a user