New example: text/draw_3d

This commit is contained in:
Per Hultqvist 2024-11-10 14:02:00 +01:00
parent 553871567e
commit a1df2e388e
2 changed files with 841 additions and 0 deletions

View file

@ -0,0 +1,822 @@
/*******************************************************************************************
*
* raylib [text] example - Draw 3d
*
* NOTE: Draw a 2D text in 3D space, each letter is drawn in a quad (or 2 quads if back face is set)
* where the texture coordinates of each quad map to the texture coordinates of the glyphs
* inside the font texture.
*
* A more efficient approach, i believe, would be to render the text in a render texture and
* map that texture to a plane and render that, or maybe a shader but my method allows more
* flexibility...for example to change position of each letter individually to make some think
* like a wavy text effect.
*
* Special thanks to:
* @Nighten for the DrawTextStyle() code https://github.com/NightenDushi/Raylib_DrawTextStyle
* Chris Camacho (codifies - http://bedroomcoders.co.uk/) for the alpha discard shader
*
* Example originally created with raylib 3.5, last time updated with raylib 4.0
*
* Example contributed by Vlad Adrian (@demizdor) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2024 Vlad Adrian (@demizdor)
*
********************************************************************************************/
package main
import (
"fmt"
"math"
"path/filepath"
"unicode/utf8"
"unsafe"
rl "github.com/gen2brain/raylib-go/raylib"
)
// Globals
const (
letterBoundarySize = 0.25
textMaxLayers = 32
maxTextLength = 64
screenWidth = 800
screenHeight = 450
)
var letterBoundaryColor = rl.Violet
var showLetterBoundary, showTextBoundary = false, false
// Data Types definition
// WaveTextConfig is a configuration structure for waving the text
type WaveTextConfig struct {
waveRange, waveSpeed, waveOffset rl.Vector3
}
// Program main entry point
func main() {
// Initialization
rl.SetConfigFlags(rl.FlagMsaa4xHint | rl.FlagVsyncHint)
rl.InitWindow(screenWidth, screenHeight, "raylib [text] example - draw 2D text in 3D")
spin := true // Spin the camera?
multicolor := false // Multicolor mode
// Define the camera to look into our 3d world
camera := rl.Camera3D{
Position: rl.Vector3{
X: -10.0,
Y: 15.0,
Z: -10.0,
}, // Camera position
Target: rl.Vector3{}, // Camera looking at point
Up: rl.Vector3{Y: 1.0}, // Camera up vector (rotation towards target)
Fovy: 45.0, // Camera field-of-view Y
Projection: rl.CameraPerspective, // Camera projection type
}
cameraMode := rl.CameraOrbital
cubePosition := rl.Vector3{Y: 1.0}
cubeSize := rl.Vector3{
X: 2.0,
Y: 2.0,
Z: 2.0,
}
// Use the default font
font := rl.GetFontDefault()
var fontSize, fontSpacing, lineSpacing float32 = 8.0, 0.5, -1
// Set the text (using markdown!)
text := "Hello ~~World~~ in 3D!"
tbox := rl.Vector3{}
var layers, quads int32 = 1, 0
var layerDistance float32 = 0.01
wcfg := WaveTextConfig{
waveSpeed: rl.Vector3{
X: 3,
Y: 3,
Z: 0.5,
},
waveOffset: rl.Vector3{
X: 0.35,
Y: 0.35,
Z: 0.35,
},
waveRange: rl.Vector3{
X: 0.45,
Y: 0.45,
Z: 0.45,
},
}
var time float32
// Set up a light and dark color
light, dark := rl.Maroon, rl.Red
// Load the alpha discard shader
alphaDiscard := rl.LoadShader("", "resources/shaders/glsl330/alpha_discard.fs")
// Array filled with multiple random colors (when multicolor mode is set)
var multi [textMaxLayers]rl.Color
rl.DisableCursor() // Limit cursor to relative movement inside the window
rl.SetTargetFPS(60) // Set our game to run at 60 frames-per-second
// Main game loop
for !rl.WindowShouldClose() { // Detect window close button or ESC key
// Update
rl.UpdateCamera(&camera, cameraMode)
// Handle font files dropped
if rl.IsFileDropped() {
droppedFiles := rl.LoadDroppedFiles()
// NOTE: We only support first ttf file dropped
rl.UnloadFont(font)
if filepath.Ext(droppedFiles[0]) == ".ttf" {
font = rl.LoadFontEx(droppedFiles[0], int32(fontSize), nil, 0)
} else if filepath.Ext(droppedFiles[0]) == ".fnt" {
font = rl.LoadFont(droppedFiles[0])
fontSize = float32(font.BaseSize)
}
rl.UnloadDroppedFiles() // Unload file paths from memory
}
// Handle Events
if rl.IsKeyPressed(rl.KeyF1) {
showLetterBoundary = !showLetterBoundary
}
if rl.IsKeyPressed(rl.KeyF2) {
showTextBoundary = !showTextBoundary
}
if rl.IsKeyPressed(rl.KeyF3) {
// Handle camera change
spin = !spin
// we need to reset the camera when changing modes
camera = rl.Camera3D{
Target: rl.Vector3{}, // Camera looking at point
Up: rl.Vector3{Y: 1.0}, // Camera up vector (rotation towards target)
Fovy: 45.0, // Camera field-of-view Y
Projection: rl.CameraPerspective, // Camera projection type
}
if spin {
camera.Position = rl.Vector3{
X: -10.0,
Y: 15.0,
Z: -10.0,
} // Camera position
cameraMode = rl.CameraOrbital
} else {
camera.Position = rl.Vector3{
X: 10.0,
Y: 10.0,
Z: -10.0,
} // Camera position
cameraMode = rl.CameraFree
}
}
// Handle clicking the cube
if rl.IsMouseButtonPressed(rl.MouseButtonLeft) {
// TODO : Missing function, see issue https://github.com/gen2brain/raylib-go/issues/457
//ray := rl.GetScreenToWorldRay(rl.GetMousePosition(), camera)
ray := rl.GetMouseRay(rl.GetMousePosition(), camera)
// Check collision between ray and box
v1 := rl.Vector3{
X: cubePosition.X - cubeSize.X/2,
Y: cubePosition.Y - cubeSize.Y/2,
Z: cubePosition.Z - cubeSize.Z/2,
}
v2 := rl.Vector3{
X: cubePosition.X + cubeSize.X/2,
Y: cubePosition.Y + cubeSize.Y/2,
Z: cubePosition.Z + cubeSize.Z/2,
}
collision := rl.GetRayCollisionBox(ray, rl.BoundingBox{
Min: v1,
Max: v2,
})
if collision.Hit {
// Generate new random colors
light = generateRandomColor(0.5, 0.78)
dark = generateRandomColor(0.4, 0.58)
}
}
// Handle text layers changes
if rl.IsKeyPressed(rl.KeyHome) && layers > 1 {
layers--
} else if rl.IsKeyPressed(rl.KeyEnd) && layers < textMaxLayers {
layers++
}
// Handle text changes
if rl.IsKeyPressed(rl.KeyLeft) {
fontSize -= 0.5
} else if rl.IsKeyPressed(rl.KeyRight) {
fontSize += 0.5
} else if rl.IsKeyPressed(rl.KeyUp) {
fontSpacing -= 0.1
} else if rl.IsKeyPressed(rl.KeyDown) {
fontSpacing += 0.1
} else if rl.IsKeyPressed(rl.KeyPageUp) {
lineSpacing -= 0.5
} else if rl.IsKeyPressed(rl.KeyPageDown) {
lineSpacing += 0.5
} else if rl.IsKeyDown(rl.KeyInsert) {
layerDistance -= 0.001
} else if rl.IsKeyDown(rl.KeyDelete) {
layerDistance += 0.001
} else if rl.IsKeyPressed(rl.KeyTab) {
multicolor = !multicolor // Enable /disable multicolor mode
if multicolor {
// Fill color array with random colors
for i := 0; i < textMaxLayers; i++ {
multi[i] = generateRandomColor(0.5, 0.8)
multi[i].A = uint8(rl.GetRandomValue(0, 255))
}
}
}
// Handle text input
ch := rl.GetCharPressed()
if rl.IsKeyPressed(rl.KeyBackspace) {
// Remove last char
text = text[:len(text)-1]
} else if rl.IsKeyPressed(rl.KeyEnter) {
// handle newline
text += "\n"
} else if ch != 0 && len(text) < maxTextLength {
// append only printable chars
text += string(ch)
}
// Measure 3D text so we can center it
tbox = measureTextWave3D(font, text, fontSize, fontSpacing, lineSpacing)
quads = 0 // Reset quad counter
time += rl.GetFrameTime() // Update timer needed by `drawTextWave3D()`
// Draw
rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite)
rl.BeginMode3D(camera)
rl.DrawCubeV(cubePosition, cubeSize, dark)
rl.DrawCubeWires(cubePosition, 2.1, 2.1, 2.1, light)
rl.DrawGrid(10, 2.0)
// Use a shader to handle the depth buffer issue with transparent textures
// NOTE: more info at https://bedroomcoders.co.uk/raylib-billboards-advanced-use/
rl.BeginShaderMode(alphaDiscard)
// Draw the 3D text above the red cube
rl.PushMatrix()
rl.Rotatef(90.0, 1.0, 0.0, 0.0)
rl.Rotatef(90.0, 0.0, 0.0, -1.0)
for i := int32(0); i < layers; i++ {
clr := light
if multicolor {
clr = multi[i]
}
vec := rl.Vector3{
X: -tbox.X / 2.0,
Y: layerDistance * float32(i),
Z: -4.5,
}
drawTextWave3D(font, text, vec, fontSize, fontSpacing, lineSpacing, true, &wcfg, time, clr)
}
// Draw the text boundary if set
if showTextBoundary {
rl.DrawCubeWiresV(rl.Vector3{Z: -4.5 + tbox.Z/2}, tbox, dark)
}
rl.PopMatrix()
// Don't draw the letter boundaries for the 3D text below
slb := showLetterBoundary
showLetterBoundary = false
// Draw 3D options (use default font)
rl.PushMatrix()
rl.Rotatef(180.0, 0.0, 1.0, 0.0)
opt := fmt.Sprintf("< SIZE: %2.1f >", fontSize)
quads += int32(len(opt))
m := measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos := rl.Vector3{
X: -m.X / 2.0,
Y: 0.01,
Z: 2.0,
}
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.Blue)
pos.Z += 0.5 + m.Z
opt = fmt.Sprintf("< SPACING: %2.1f >", fontSpacing)
quads += int32(len(opt))
m = measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.Blue)
pos.Z += 0.5 + m.Z
opt = fmt.Sprintf("< LINE: %2.1f >", lineSpacing)
quads += int32(len(opt))
m = measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.Blue)
pos.Z += 1.0 + m.Z
lbox := "OFF"
if slb {
lbox = "ON"
}
opt = fmt.Sprintf("< LBOX: %3s >", lbox)
quads += int32(len(opt))
m = measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.Red)
pos.Z += 0.5 + m.Z
tb := "OFF"
if showTextBoundary {
tb = "ON"
}
opt = fmt.Sprintf("< TBOX: %3s >", tb)
quads += int32(len(opt))
m = measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.Red)
pos.Z += 0.5 + m.Z
opt = fmt.Sprintf("< LAYER DISTANCE: %.3f >", layerDistance)
quads += int32(len(opt))
m = measureText3D(rl.GetFontDefault(), opt, 8.0, 1.0, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 8.0, 1.0, 0.0, false, rl.DarkPurple)
rl.PopMatrix()
// Draw 3D info text (use default font)
opt = "All the text displayed here is in 3D"
quads += 36
m = measureText3D(rl.GetFontDefault(), opt, 10.0, 0.5, 0.0)
pos = rl.Vector3{
X: -m.X / 2.0,
Y: 0.01,
Z: 2.0,
}
drawText3D(rl.GetFontDefault(), opt, pos, 10.0, 0.5, 0.0, false, rl.DarkBlue)
pos.Z += 1.5 + m.Z
opt = "press [Left]/[Right] to change the font size"
quads += 44
m = measureText3D(rl.GetFontDefault(), opt, 6.0, 0.5, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 6.0, 0.5, 0.0, false, rl.DarkBlue)
pos.Z += 0.5 + m.Z
opt = "press [Up]/[Down] to change the font spacing"
quads += 44
m = measureText3D(rl.GetFontDefault(), opt, 6.0, 0.5, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 6.0, 0.5, 0.0, false, rl.DarkBlue)
pos.Z += 0.5 + m.Z
opt = "press [PgUp]/[PgDown] to change the line spacing"
quads += 48
m = measureText3D(rl.GetFontDefault(), opt, 6.0, 0.5, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 6.0, 0.5, 0.0, false, rl.DarkBlue)
pos.Z += 0.5 + m.Z
opt = "press [F1] to toggle the letter boundary"
quads += 39
m = measureText3D(rl.GetFontDefault(), opt, 6.0, 0.5, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 6.0, 0.5, 0.0, false, rl.DarkBlue)
pos.Z += 0.5 + m.Z
opt = "press [F2] to toggle the text boundary"
quads += 37
m = measureText3D(rl.GetFontDefault(), opt, 6.0, 0.5, 0.0)
pos.X = -m.X / 2.0
drawText3D(rl.GetFontDefault(), opt, pos, 6.0, 0.5, 0.0, false, rl.DarkBlue)
showLetterBoundary = slb
rl.EndShaderMode()
rl.EndMode3D()
// Draw 2D info text & stats
msg := `Drag & drop a font file to change the font!
Type something, see what happens!
Press [F3] to toggle the camera`
rl.DrawText(msg, 10, 35, 10, rl.Black)
cam := "FREE"
if spin {
cam = "ORBITAL"
}
quads += int32(len(text)) * 2 * layers
tmp := fmt.Sprintf("%2d layer(s) | %s camera | %4d quads (%4d verts)", layers, cam, quads, quads*4)
width := rl.MeasureText(tmp, 10)
rl.DrawText(tmp, screenWidth-20-width, 10, 10, rl.DarkGreen)
tmp = "[Home]/[End] to add/remove 3D text layers"
width = rl.MeasureText(tmp, 10)
rl.DrawText(tmp, screenWidth-20-width, 25, 10, rl.DarkGray)
tmp = "[Insert]/[Delete] to increase/decrease distance between layers"
width = rl.MeasureText(tmp, 10)
rl.DrawText(tmp, screenWidth-20-width, 40, 10, rl.DarkGray)
tmp = "click the [CUBE] for a random color"
width = rl.MeasureText(tmp, 10)
rl.DrawText(tmp, screenWidth-20-width, 55, 10, rl.DarkGray)
tmp = "[Tab] to toggle multicolor mode"
width = rl.MeasureText(tmp, 10)
rl.DrawText(tmp, screenWidth-20-width, 70, 10, rl.DarkGray)
rl.DrawFPS(10, 10)
rl.EndDrawing()
}
// De-Initialization
rl.UnloadFont(font)
rl.CloseWindow() // Close window and OpenGL context
}
// Module Functions Definitions
// drawTextCodepoint3D draws codepoint at specified position in 3D space
func drawTextCodepoint3D(font rl.Font, codepoint rune, position rl.Vector3, fontSize float32, backface bool,
tint rl.Color) {
// Character index position in sprite font
// NOTE: In case a codepoint is not available in the font, index returned points to '?'
index := rl.GetGlyphIndex(font, codepoint)
scale := fontSize / float32(font.BaseSize)
// Character destination rectangle on screen
// NOTE: We consider charsPadding on drawing
glyphs := unsafe.Slice(font.Chars, font.CharsCount)
position.X += float32(glyphs[index].OffsetX-font.CharsPadding) / float32(font.BaseSize) * scale
position.Z += float32(glyphs[index].OffsetY-font.CharsPadding) / float32(font.BaseSize) * scale
// Character source rectangle from font texture atlas
// NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
recs := unsafe.Slice(font.Recs, font.CharsCount)
srcRec := rl.Rectangle{
X: recs[index].X - float32(font.CharsPadding),
Y: recs[index].Y - float32(font.CharsPadding),
Width: recs[index].Width + 2.0*float32(font.CharsPadding),
Height: recs[index].Height + 2.0*float32(font.CharsPadding),
}
width := (recs[index].Width + 2.0*float32(font.CharsPadding)) / float32(font.BaseSize) * scale
height := (recs[index].Height + 2.0*float32(font.CharsPadding)) / float32(font.BaseSize) * scale
if font.Texture.ID > 0 {
var x, y, z float32
// normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
tx := srcRec.X / float32(font.Texture.Width)
ty := srcRec.Y / float32(font.Texture.Height)
tw := (srcRec.X + srcRec.Width) / float32(font.Texture.Width)
th := (srcRec.Y + srcRec.Height) / float32(font.Texture.Height)
if showLetterBoundary {
pos := rl.Vector3{
X: position.X + width/2,
Y: position.Y,
Z: position.Z + height/2,
}
size := rl.Vector3{
X: width,
Y: letterBoundarySize,
Z: height,
}
rl.DrawCubeWiresV(pos, size, letterBoundaryColor)
}
var limit int32 = 4
if backface {
limit = 8
}
rl.CheckRenderBatchLimit(limit)
rl.SetTexture(font.Texture.ID)
rl.PushMatrix()
rl.Translatef(position.X, position.Y, position.Z)
rl.Begin(rl.Quads)
rl.Color4ub(tint.R, tint.G, tint.B, tint.A)
// Front Face
rl.Normal3f(0.0, 1.0, 0.0) // Normal Pointing Up
rl.TexCoord2f(tx, ty)
rl.Vertex3f(x, y, z) // Top Left Of The Texture and Quad
rl.TexCoord2f(tx, th)
rl.Vertex3f(x, y, z+height) // Bottom Left Of The Texture and Quad
rl.TexCoord2f(tw, th)
rl.Vertex3f(x+width, y, z+height) // Bottom Right Of The Texture and Quad
rl.TexCoord2f(tw, ty)
rl.Vertex3f(x+width, y, z) // Top Right Of The Texture and Quad
if backface {
// Back Face
rl.Normal3f(0.0, -1.0, 0.0) // Normal Pointing Down
rl.TexCoord2f(tx, ty)
rl.Vertex3f(x, y, z) // Top Right Of The Texture and Quad
rl.TexCoord2f(tw, ty)
rl.Vertex3f(x+width, y, z) // Top Left Of The Texture and Quad
rl.TexCoord2f(tw, th)
rl.Vertex3f(x+width, y, z+height) // Bottom Left Of The Texture and Quad
rl.TexCoord2f(tx, th)
rl.Vertex3f(x, y, z+height) // Bottom Right Of The Texture and Quad
}
rl.End()
rl.PopMatrix()
rl.SetTexture(0)
}
}
// drawText3D draws a 2D text in 3D space
func drawText3D(font rl.Font, text string, position rl.Vector3, fontSize, fontSpacing,
lineSpacing float32, backface bool, tint rl.Color) {
length := int32(len(text)) // Total length in bytes of the text,
// scanned by codepoints in loop
// TextOffsetY : Offset between lines (on line break '\n')
// TextOffsetX : Offset X to next character to draw
var textOffsetY, textOffsetX float32
scale := fontSize / float32(font.BaseSize)
for i := int32(0); i < length; {
// Get next codepoint from byte string and glyph index in font
codepoint, codepointByteCount := getCodepoint(text, i)
index := rl.GetGlyphIndex(font, codepoint)
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all the bad bytes using the '?' symbol moving one byte
if codepoint == 0x3f {
codepointByteCount = 1
}
if codepoint == 0 || codepoint == '\n' {
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += scale + lineSpacing/float32(font.BaseSize)*scale
textOffsetX = 0.0
} else {
if (codepoint != ' ') && (codepoint != '\t') {
vec := rl.Vector3{
X: position.X + textOffsetX,
Y: position.Y,
Z: position.Z + textOffsetY,
}
drawTextCodepoint3D(font, codepoint, vec, fontSize, backface, tint)
}
textOffsetX += getTextWidth(font, index, fontSpacing, scale)
}
i += codepointByteCount // Move text bytes counter to next codepoint
}
}
// measureText3D measures a text in 3D. For some reason `MeasureTextEx()`
// just doesn't seem to work, so I had to use this instead.
func measureText3D(font rl.Font, text string, fontSize, fontSpacing, lineSpacing float32) rl.Vector3 {
length := int32(len(text))
var tempLen, lenCounter int32 // Used to count longer text line num chars
var (
tempTextWidth float32 // Used to count longer text line width
scale = fontSize / float32(font.BaseSize)
textHeight = scale
textWidth float32
)
var (
letter rune // Current character
index int32 // Index position in sprite font
)
for i := int32(0); i < length; i++ {
lenCounter++
var next int32
letter, next = getCodepoint(text, i)
index = rl.GetGlyphIndex(font, letter)
// NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all the bad bytes using the '?' symbol so to not skip any we set next = 1
if letter == 0x3f {
next = 1
}
i += next - 1
if letter != 0 && letter != '\n' {
textWidth += getTextWidth(font, index, fontSpacing, scale)
} else {
if tempTextWidth < textWidth {
tempTextWidth = textWidth
}
lenCounter = 0
textWidth = 0.0
textHeight += scale + lineSpacing/float32(font.BaseSize)*scale
}
if tempLen < lenCounter {
tempLen = lenCounter
}
}
if tempTextWidth < textWidth {
tempTextWidth = textWidth
}
// Adds chars spacing to measure
vec := rl.Vector3{
X: tempTextWidth + float32(tempLen-1)*fontSpacing/float32(font.BaseSize)*scale,
Y: 0.25,
Z: textHeight,
}
return vec
}
// drawTextWave3D draws a 2D text in 3D space and wave the parts that start with `~~` and end with `~~`.
// This is a modified version of the original code by @Nighten found here https://github.com/NightenDushi/Raylib_DrawTextStyle
func drawTextWave3D(font rl.Font, text string, position rl.Vector3, fontSize, fontSpacing,
lineSpacing float32, backface bool, config *WaveTextConfig, time float32, tint rl.Color) {
length := int32(len(text)) // Total length in bytes of the text, scanned by codepoints in loop
// TextOffsetY : Offset between lines (on line break '\n')
// TextOffsetX : Offset X to next character to draw
var textOffsetY, textOffsetX float32
var wave bool
scale := fontSize / float32(font.BaseSize)
for i, k := int32(0), int32(0); i < length; k++ {
// Get next codepoint from byte string and glyph index in font
codepoint, codepointByteCount := getCodepoint(text, i)
index := rl.GetGlyphIndex(font, codepoint)
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all the bad bytes using the '?' symbol moving one byte
if codepoint == 0x3f {
codepointByteCount = 1
}
if codepoint == 0 || codepoint == '\n' {
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += scale + lineSpacing/float32(font.BaseSize)*scale
textOffsetX = 0.0
k = 0
} else if codepoint == '~' {
var r rune
r, codepointByteCount = getCodepoint(text, i+1)
if r == '~' {
codepointByteCount += 1
wave = !wave
}
} else {
if (codepoint != ' ') && (codepoint != '\t') {
pos := position
if wave { // Apply the wave effect
kk := float32(k)
pos.X += sin(time*config.waveSpeed.X-kk*config.waveOffset.X) * config.waveRange.X
pos.Y += sin(time*config.waveSpeed.Y-kk*config.waveOffset.Y) * config.waveRange.Y
pos.Z += sin(time*config.waveSpeed.Z-kk*config.waveOffset.Z) * config.waveRange.Z
}
vec := rl.Vector3{
X: pos.X + textOffsetX,
Y: pos.Y,
Z: pos.Z + textOffsetY,
}
drawTextCodepoint3D(font, codepoint, vec, fontSize, backface, tint)
}
textOffsetX += getTextWidth(font, index, fontSpacing, scale)
}
i += codepointByteCount // Move text bytes counter to next codepoint
}
}
// measureTextWave3D measures a text in 3D ignoring the `~~` chars.
func measureTextWave3D(font rl.Font, text string, fontSize, fontSpacing, lineSpacing float32) rl.Vector3 {
length := int32(len(text))
var tempLen, lenCounter int32 // Used to count longer text line num chars
var (
tempTextWidth float32 = 0.0 // Used to count longer text line width
scale = fontSize / float32(font.BaseSize)
textHeight = scale
textWidth float32 = 0.0
)
var letter, index int32 // Current character and Index position in sprite font
for i := int32(0); i < length; i++ {
lenCounter++
var next int32
letter, next = getCodepoint(text, i)
index = rl.GetGlyphIndex(font, letter)
// NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all the bad bytes using the '?' symbol so to not skip any we set next = 1
if letter == 0x3f {
next = 1
}
i += next - 1
if letter != 0 && letter != '\n' {
r, _ := getCodepoint(text, i+1)
if letter == '~' && r == '~' {
i++
} else {
textWidth += getTextWidth(font, index, fontSpacing, scale)
}
} else {
if tempTextWidth < textWidth {
tempTextWidth = textWidth
}
lenCounter = 0
textWidth = 0.0
textHeight += scale + lineSpacing/float32(font.BaseSize)*scale
}
if tempLen < lenCounter {
tempLen = lenCounter
}
}
if tempTextWidth < textWidth {
tempTextWidth = textWidth
}
vec := rl.Vector3{
X: tempTextWidth + float32(tempLen-1)*fontSpacing/float32(font.BaseSize)*scale, // Adds chars spacing to measure
Y: 0.25,
Z: textHeight,
}
return vec
}
// generateRandomColor generates a nice color with a random hue
func generateRandomColor(s, v float32) rl.Color {
const Phi = float64(0.618033988749895) // Golden ratio conjugate
h := float64(rl.GetRandomValue(0, 360))
h = math.Mod(h+h*Phi, 360.0)
return rl.ColorFromHSV(float32(h), s, v)
}
// getCodepoint returns the rune starting at index, plus the length of that rune in bytes
func getCodepoint(s string, index int32) (rune, int32) {
if index == int32(len(s)) {
return 0, 0
}
r := []rune(s[index:])
return r[0], int32(utf8.RuneLen(r[0]))
}
// sin is just a convenience function to avoid a bunch of type conversions
func sin(value float32) float32 {
return float32(math.Sin(float64(value)))
}
func getTextWidth(font rl.Font, index int32, spacing, scale float32) float32 {
glyphs := unsafe.Slice(font.Chars, font.CharsCount)
if glyphs[index].AdvanceX == 0 {
recs := unsafe.Slice(font.Recs, font.CharsCount)
return (recs[index].Width + spacing) / float32(font.BaseSize) * scale
} else {
return (float32(glyphs[index].AdvanceX) + spacing) / float32(font.BaseSize) * scale
}
}

View file

@ -0,0 +1,19 @@
#version 330
// Input vertex attributes (from vertex shader)
in vec2 fragTexCoord;
in vec4 fragColor;
// Input uniform values
uniform sampler2D texture0;
uniform vec4 colDiffuse;
// Output fragment color
out vec4 finalColor;
void main()
{
vec4 texelColor = texture(texture0, fragTexCoord);
if (texelColor.a == 0.0) discard;
finalColor = texelColor * fragColor * colDiffuse;
}