aminal/vendor/github.com/4ydx/gltext/v4.5/text.go

539 lines
14 KiB
Go

// Copyright 2012 The go-gl Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package v45
import (
"fmt"
"github.com/4ydx/gltext"
"github.com/go-gl/gl/v4.5-core/gl"
"github.com/go-gl/mathgl/mgl32"
)
// CharacterSide shows which side of a character is
// clicked
type CharacterSide int
const (
CSLeft CharacterSide = iota
CSRight
CSUnknown
)
// Text is not designed to be accessed concurrently
type Text struct {
Font *Font
// final position on screen
finalPosition mgl32.Vec2
// text color
color mgl32.Vec3
// scaling the text
Scale float32
ScaleMin float32
ScaleMax float32
scaleMatrix mgl32.Mat4
// Fadeout reduces alpha
FadeOutBegun bool
FadeOutFrameCount float32 // number of frames since drawing began
FadeOutPerFrame float32 // smaller value takes more time
// bounding box of text
BoundingBox *BoundingBox
// general opengl values
vao uint32
vbo uint32
ebo uint32
vboData []float32
vboIndexCount int
eboData []int32
eboIndexCount int
// determines how many prefix characters are drawn on screen
RuneCount int
// no longer than this string
MaxRuneCount int
// X1, X2: the lower left and upper right points of a box that bounds the text with a center point (0,0)
// lower left
X1 gltext.Point
// upper right
X2 gltext.Point
// Screen position away from center
Position mgl32.Vec2
String string
CharSpacing []float32
}
func (t *Text) GetLength() int {
return t.eboIndexCount / 6
}
// NewText creates a new text object with scaling boundaries
// the rest state of the text when not being interacted with
// is scaleMin. most likely one wants to use 1.0.
func NewText(f *Font, scaleMin, scaleMax float32) (t *Text) {
t = &Text{}
t.Font = f
// text hover values
// "resting state" of a text object is the min scale
t.ScaleMin, t.ScaleMax = scaleMin, scaleMax
t.SetScale(1)
glfloat_size := int32(4)
// stride of the buffered data
xy_count := int32(2)
stride := xy_count + int32(2)
gl.GenVertexArrays(1, &t.vao)
gl.GenBuffers(1, &t.vbo)
gl.GenBuffers(1, &t.ebo)
// vao
gl.BindVertexArray(t.vao)
// i think this call isnt necessary here
// gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, t.Font.textureID)
// vbo
// specify the buffer for which the VertexAttribPointer calls apply
gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)
gl.EnableVertexAttribArray(t.Font.centeredPositionAttribute)
gl.VertexAttribPointer(
t.Font.centeredPositionAttribute,
2,
gl.FLOAT,
false,
glfloat_size*stride,
gl.PtrOffset(0),
)
gl.EnableVertexAttribArray(t.Font.uvAttribute)
gl.VertexAttribPointer(
t.Font.uvAttribute,
2,
gl.FLOAT,
false,
glfloat_size*stride,
gl.PtrOffset(int(glfloat_size*xy_count)),
)
// ebo
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)
// i am guessing that order is important here
gl.BindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
return t
}
// Release releases text resources.
func (t *Text) Release() {
gl.DeleteBuffers(1, &t.vbo)
gl.DeleteBuffers(1, &t.ebo)
gl.DeleteVertexArrays(1, &t.vao)
}
// SetScale returns true when a change occured
func (t *Text) SetScale(s float32) bool {
if s > t.ScaleMax || s < t.ScaleMin {
return false
}
t.Scale = s
t.scaleMatrix = mgl32.Scale3D(s, s, s)
return true
}
// AddScale returns true when a change occured
func (t *Text) AddScale(s float32) bool {
if s < 0 && t.Scale <= t.ScaleMin {
return false
}
if s > 0 && t.Scale >= t.ScaleMax {
return false
}
t.Scale += s
t.scaleMatrix = mgl32.Scale3D(t.Scale, t.Scale, t.Scale)
return true
}
func (t *Text) SetColor(color mgl32.Vec3) {
t.color = color
}
// SetString performs creates new vbo and ebo objects as well as to perform all
// binding required for displaying text to screen
func (t *Text) SetString(fs string, argv ...interface{}) {
indices := []rune(fmt.Sprintf(fs, argv...))
if t.MaxRuneCount > 0 && len(indices) > t.MaxRuneCount+1 {
indices = indices[0:t.MaxRuneCount]
}
t.String = string(indices)
// ebo, vbo data
glfloat_size := int32(4)
t.vboIndexCount = len(indices) * 4 * 2 * 2 // 4 indexes per rune (containing 2 position + 2 texture)
t.eboIndexCount = len(indices) * 6 // each rune requires 6 triangle indices for a quad
t.RuneCount = len(indices)
t.vboData = make([]float32, t.vboIndexCount, t.vboIndexCount)
t.eboData = make([]int32, t.eboIndexCount, t.eboIndexCount)
// generate the basic vbo data and bounding box
// center the vbo data around the orthographic (0,0) point
t.X1 = gltext.Point{0, 0}
t.X2 = gltext.Point{0, 0}
t.makeBufferData(indices)
t.centerTheData(t.getLowerLeft())
if gltext.IsDebug {
prefix := gltext.DebugPrefix()
fmt.Printf("%s bounding box %v %v\n", prefix, t.X1, t.X2)
fmt.Printf("%s text vbo data\n%v\n", prefix, t.vboData)
fmt.Printf("%s text ebo data\n%v\n", prefix, t.eboData)
}
if len(indices) > 0 {
// in the event that we have no data to draw dont bother here
gl.BindVertexArray(t.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)
gl.BufferData(
gl.ARRAY_BUFFER, int(glfloat_size)*t.vboIndexCount, gl.Ptr(t.vboData), gl.DYNAMIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)
gl.BufferData(
gl.ELEMENT_ARRAY_BUFFER, int(glfloat_size)*t.eboIndexCount, gl.Ptr(t.eboData), gl.DYNAMIC_DRAW)
gl.BindVertexArray(0)
// possibly not necesssary?
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
}
// SetString can be called at anytime. we want to make sure that if the user is updating the text,
// the previous position will be maintained
t.SetPosition(t.Position)
}
// The block of text is positioned around the center of the screen, which in this case must
// be considered (0,0). This is necessary for orthographic projection and scaling to work
// well together. If the text is *not* at (0,0), then scaling doesnt produce a direct zoom effect.
func (t *Text) getLowerLeft() (lowerLeft gltext.Point) {
lineWidthHalf := (t.X2.X - t.X1.X) / 2
lineHeightHalf := (t.X2.Y - t.X1.Y) / 2
lowerLeft.X = -lineWidthHalf
lowerLeft.Y = -lineHeightHalf
return
}
// SetPosition prepares variables passed to the shader as well as values
// used for bounding box calculations when clicking or hovering above text
func (t *Text) SetPosition(v mgl32.Vec2) {
// transform to orthographic coordinates ranged -1 to 1 for the shader
t.finalPosition[0] = v.X() / (t.Font.WindowWidth / 2)
t.finalPosition[1] = v.Y() / (t.Font.WindowHeight / 2)
if gltext.IsDebug {
t.BoundingBox.finalPosition[0] = v.X() / (t.Font.WindowWidth / 2)
t.BoundingBox.finalPosition[1] = v.Y() / (t.Font.WindowHeight / 2)
}
t.Position = v
}
func (t *Text) GetBoundingBox() (X1, X2 gltext.Point) {
x, y := t.Position.X(), t.Position.Y()
X1.X = t.X1.X + x
X1.Y = t.X1.Y + y
X2.X = t.X2.X + x
X2.Y = t.X2.Y + y
return
}
func (t *Text) Draw() {
if gltext.IsDebug {
t.BoundingBox.Draw()
}
if t.FadeOutBegun {
t.FadeOutFrameCount++
if t.FadeOutPerFrame*t.FadeOutFrameCount > 1 {
// prevent overflow
t.FadeOutFrameCount--
}
}
gl.UseProgram(t.Font.program)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, t.Font.textureID)
// uniforms
gl.Uniform1i(t.Font.fragmentTextureUniform, 0)
gl.Uniform1f(t.Font.fadeoutUniform, t.FadeOutPerFrame*t.FadeOutFrameCount)
gl.Uniform4fv(t.Font.colorUniform, 1, &t.color[0])
gl.Uniform2fv(t.Font.finalPositionUniform, 1, &t.finalPosition[0])
gl.UniformMatrix4fv(t.Font.orthographicMatrixUniform, 1, false, &t.Font.OrthographicMatrix[0])
gl.UniformMatrix4fv(t.Font.scaleMatrixUniform, 1, false, &t.scaleMatrix[0])
// draw
drawCount := int32(t.RuneCount * 6)
if drawCount > int32(t.eboIndexCount) {
drawCount = int32(t.eboIndexCount)
}
if drawCount <= 0 {
return
}
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.BindVertexArray(t.vao)
gl.DrawElements(gl.TRIANGLES, drawCount, gl.UNSIGNED_INT, nil)
gl.BindVertexArray(0)
gl.Disable(gl.BLEND)
}
func (t *Text) BeginFadeOut() {
if t.FadeOutBegun == false {
t.FadeOutBegun = true
t.FadeOutFrameCount = 0
}
}
func (t *Text) Show() {
t.FadeOutBegun = false
t.FadeOutFrameCount = 0
}
func (t *Text) Hide() {
t.FadeOutBegun = false
t.FadeOutFrameCount = 1.0 / t.FadeOutPerFrame
}
// centerTheData prepares the value "centered_position" found in the font shader
// as named, the function centers the text around the orthographic center of the screen
// expected to only be called within SetString
func (t *Text) centerTheData(lowerLeft gltext.Point) (err error) {
length := len(t.vboData)
for index := 0; index < length; {
// index (0,0)
t.vboData[index] += lowerLeft.X
index++
t.vboData[index] += lowerLeft.Y
index += 3 // skip texture data
// index (1,0)
t.vboData[index] += lowerLeft.X
index++
t.vboData[index] += lowerLeft.Y
index += 3
// index (1,1)
t.vboData[index] += lowerLeft.X
index++
t.vboData[index] += lowerLeft.Y
index += 3
// index (0,1)
t.vboData[index] += lowerLeft.X
index++
t.vboData[index] += lowerLeft.Y
index += 3
}
// update bounding box so that it is centered around (0,0)
t.X1.X += lowerLeft.X
t.X2.X += lowerLeft.X
t.X1.Y += lowerLeft.Y
t.X2.Y += lowerLeft.Y
// prepare objects for drawing the bounding box
if gltext.IsDebug {
t.BoundingBox, err = loadBoundingBox(t.Font, t.X1, t.X2)
}
return
}
func (t *Text) Width() float32 {
return t.X2.X - t.X1.X
}
func (t *Text) Height() float32 {
return t.X2.Y - t.X1.Y
}
// PrintCharSpacing is used for debugging
func (t *Text) PrintCharSpacing() {
fmt.Printf("\n%s:\n", t.String)
at := t.X1.X
for i, cs := range t.CharSpacing {
at = cs + at
fmt.Printf("'%c': %.2f ", t.String[i], at)
}
}
// ClickedCharacter should only be called after a bounding box hit is confirmed because
// it does not check y-axis values at all. Returns the index and side of the char clicked.
func (t *Text) ClickedCharacter(xPos, offset float64) (index int, side CharacterSide) {
// transform from screen coordinates to... window coordinates?
xPos = xPos - float64(t.Font.WindowWidth/2) - offset
// could do a binary search...
at := float64(t.X1.X)
for i, cs := range t.CharSpacing {
at = float64(cs) + at
if i == 0 && xPos <= at-float64(cs) {
return i, CSLeft
}
if i == len(t.CharSpacing)-1 && xPos > at {
return i, CSRight
}
if xPos <= at && xPos > at-float64(cs) {
if xPos-(at-float64(cs)) > float64(cs)/2 {
return i, CSRight
} else {
return i, CSLeft
}
}
}
return -1, CSUnknown
}
func (t *Text) CharPosition(index int) float64 {
at := float64(t.X1.X)
for i, cs := range t.CharSpacing {
if i == index {
break
}
at = float64(cs) + at
}
return at
}
func (t *Text) HasRune(r rune) bool {
for _, runes := range t.Font.Config.RuneRanges {
if r >= runes.Low && r <= runes.High {
return true
}
}
return false
}
// makeBufferData positions quads for drawing the text in the indices parameter using glyph dimensions
// it also generates the bounding box (which needs to later be centered around (0,0))
// expected to only be called by SetString
func (t *Text) makeBufferData(indices []rune) {
glyphs := t.Font.Config.Glyphs
vboIndex := 0
eboIndex := 0
lineX := float32(0)
eboOffset := int32(0)
t.CharSpacing = make([]float32, 0)
for i, r := range indices {
glyphIndex := t.Font.Config.RuneRanges.GetGlyphIndex(r)
if glyphIndex >= 0 {
if gltext.IsDebug {
prefix := gltext.DebugPrefix()
fmt.Printf("%s png index %3d: %s rune %+v line at %f", prefix, glyphIndex, string(r), glyphs[glyphIndex], lineX)
}
advance := float32(glyphs[glyphIndex].Advance)
// Originally the glyph Width was used, but that results in quads that overlap one another.
vw := float32(glyphs[glyphIndex].Advance)
vh := float32(glyphs[glyphIndex].Height)
// used to determine which character inside of the text was clicked
t.CharSpacing = append(t.CharSpacing, advance)
// variable width characters will produce a bounding box that is just
// a bit too long on the right-hand side unless we trim off the excess
// when processing the right-most character
trim := float32(0)
if i == len(indices)-1 {
trim = vw - advance
}
tP1, tP2 := glyphs[glyphIndex].GetTexturePositions(t.Font)
// counter-clockwise quad
// the bounding box value X2 is being expanded as characters are added
// index (0,0)
t.vboData[vboIndex] = lineX // position
vboIndex++
t.vboData[vboIndex] = 0
vboIndex++
t.vboData[vboIndex] = tP1.X // texture uv
vboIndex++
t.vboData[vboIndex] = tP2.Y
vboIndex++
// index (1,0) - expanding X2
t.vboData[vboIndex], t.X2.X = lineX+vw, lineX+vw-trim
vboIndex++
t.vboData[vboIndex] = 0
vboIndex++
t.vboData[vboIndex] = tP2.X
vboIndex++
t.vboData[vboIndex] = tP2.Y
vboIndex++
// index (1,1) - expanding X2
t.vboData[vboIndex] = lineX + vw
vboIndex++
t.vboData[vboIndex], t.X2.Y = vh, vh
vboIndex++
t.vboData[vboIndex] = tP2.X
vboIndex++
t.vboData[vboIndex] = tP1.Y
vboIndex++
// index (0,1)
t.vboData[vboIndex] = lineX
vboIndex++
t.vboData[vboIndex] = vh
vboIndex++
t.vboData[vboIndex] = tP1.X
vboIndex++
t.vboData[vboIndex] = tP1.Y
vboIndex++
// ebo data
t.eboData[eboIndex] = 0 + eboOffset
eboIndex++
t.eboData[eboIndex] = 1 + eboOffset
eboIndex++
t.eboData[eboIndex] = 2 + eboOffset
eboIndex++
t.eboData[eboIndex] = 0 + eboOffset
eboIndex++
t.eboData[eboIndex] = 2 + eboOffset
eboIndex++
t.eboData[eboIndex] = 3 + eboOffset
eboIndex++
eboOffset += 4
// shift to the right
lineX += advance
if gltext.IsDebug {
fmt.Printf("-> %f\n", lineX)
}
}
}
if gltext.IsDebug {
gltext.PrintVBO(t.vboData, t.Font.GetTextureHeight(), t.Font.GetTextureWidth())
}
return
}