822 lines
24 KiB
Go
822 lines
24 KiB
Go
/*******************************************************************************************
|
|
*
|
|
* 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
|
|
}
|
|
}
|