diff --git a/src/sj/Game.d b/src/sj/Game.d index 903c212..0969dee 100644 --- a/src/sj/Game.d +++ b/src/sj/Game.d @@ -31,7 +31,7 @@ class Game : AbstractGame { lobby_ = new LobbyWorld(programs_); title_ = new TitleScene(lobby_, programs_); - select_ = new SelectScene(lobby_, programs_, songs_); + select_ = new SelectScene(lobby_, programs_, fonts_, songs_); title_.SetupSceneDependency(select_); select_.SetupSceneDependency(title_); diff --git a/src/sj/SelectScene.d b/src/sj/SelectScene.d index d942303..07d9617 100644 --- a/src/sj/SelectScene.d +++ b/src/sj/SelectScene.d @@ -1,18 +1,22 @@ /// License: MIT module sj.SelectScene; -import std.math, +import std.conv, + std.math, std.variant; import derelict.sfml2.audio; import gl4d; -import sj.KeyInput, +import sj.FontSet, + sj.KeyInput, sj.LobbyWorld, sj.ProgramSet, sj.SceneInterface, sj.Song, + sj.Text, + sj.TextProgram, sj.TitleScene, sj.util.Animation, sj.util.Easing, @@ -21,12 +25,14 @@ import sj.KeyInput, /// class SelectScene : SceneInterface { public: - /// - this(LobbyWorld lobby, ProgramSet program, Song[] songs) { + this(LobbyWorld lobby, ProgramSet program, FontSet fonts, Song[] songs) { lobby_ = lobby; songs_ = songs.dup; + text_ = new Text(program.Get!TextProgram); + fonts_ = fonts; + sound_ = sfSound_create(); soundres_.Load(); @@ -34,6 +40,8 @@ class SelectScene : SceneInterface { status_ = first_state_; } ~this() { + text_.destroy(); + sfSound_destroy(sound_); soundres_.Unload(); } @@ -61,6 +69,7 @@ class SelectScene : SceneInterface { } override void Draw() { lobby_.Draw(); + text_.Draw(lobby_.Projection, lobby_.view.Create()); } private: @@ -82,6 +91,9 @@ class SelectScene : SceneInterface { LobbyWorld lobby_; + Text text_; + FontSet fonts_; + Song[] songs_; sfSound* sound_; @@ -195,6 +207,15 @@ private class SongAppearState : AbstractSceneState { sfSound_setBuffer(owner.sound_, owner.soundres_.spotlight); sfSound_play(owner.sound_); + + // TODO: + const song = owner.songs_[song_index_]; + with (owner.text_) { + matrix.scale = vec3(0.2, 0.05, 0.05); + matrix.translation = vec3(0, -0.3, 0); + LoadGlyphs(vec2i(256, 64), + song.name.to!dstring, vec2i(32, 0), owner.fonts_.gothic); + } } override UpdateResult Update(KeyInput input) { const ratio = anime_.Update(); diff --git a/src/sj/Song.d b/src/sj/Song.d index a096934..359748c 100644 --- a/src/sj/Song.d +++ b/src/sj/Song.d @@ -84,6 +84,10 @@ class Song { assert(false); // TODO: } + /// + @property string name() const { + return name_; + } /// @property ref const(PreviewConfig) preview() const { return preview_; diff --git a/src/sj/Text.d b/src/sj/Text.d new file mode 100644 index 0000000..074b754 --- /dev/null +++ b/src/sj/Text.d @@ -0,0 +1,113 @@ +/// License: MIT +module sj.Text; + +import std.exception; + +import gl4d, ft4d; + +import sj.TextProgram; + +/// +class Text { + public: + /// + this(TextProgram program) { + program_ = program; + + texture_ = Texture2D.Create(); + vao_ = VertexArray.Create(); + vertices_ = ArrayBuffer.Create(); + + program_.SetupVertexArray(vao_, vertices_); + + vertices_.Bind(); + ArrayBufferAllocator allocator; + with (allocator) { + const v = [ + -1f, 1f, 0f, 1f, 0f, + -1f, -1f, 0f, 1f, 1f, + 1f, -1f, 0f, 0f, 1f, + 1f, 1f, 0f, 0f, 0f, + ]; + data = v.ptr; + size = v.length * v[0].sizeof; + usage = GL_STATIC_DRAW; + Allocate(vertices_); + } + } + + /// + void LoadGlyphs(vec2i texsz, dstring text, vec2i charsz, FaceRef face) + in (texsz.x > 0 && texsz.y > 0) { + auto pixels = new ubyte[texsz.x * texsz.y]; + + GlyphLoader gloader; + gloader.pxWidth = charsz.x; + gloader.pxHeight = charsz.y; + gloader.flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + + int x; + foreach (c; text) { + with (gloader) { + character = c; + Load(face).enforce; + } + + const bitmap = face.EnforceGlyphBitmap(); + const srcsz = vec2i(bitmap.width, bitmap.rows); + CopyRawPixels(bitmap.buffer, srcsz, pixels.ptr, texsz, vec2i(x, 0)); + + x += srcsz.x; + } + + texture_.Bind(); + Texture2DAllocator allocator; + with (allocator) { + level = 0; + internalFormat = GL_RGBA8; + data = pixels.ptr; + size = texsz; + format = GL_RED; + type = GL_UNSIGNED_BYTE; + Allocate(texture_); + } + glyph_loaded_ = true; + } + + /// + void Draw(mat4 proj, mat4 view) { + if (!glyph_loaded_) return; + + program_.Use(proj, view, matrix.Create(), texture_, color); + + vao_.Bind(); + gl.DrawArrays(GL_TRIANGLE_FAN, 0, 4); + } + + /// + ModelMatrixFactory!4 matrix; + + /// + vec4 color = vec4(0, 0, 0, 1); + + private: + static void CopyRawPixels(in ubyte* src, vec2i srcsz, ubyte* dst, vec2i dstsz, vec2i offset) { + auto srcy = 0, dsty = offset.y; + for (; srcy < srcsz.y && dsty < dstsz.y; ++srcy, ++dsty) { + auto srcx = 0, dstx = offset.x; + for (; srcx < srcsz.x && dstx < dstsz.x; ++srcx, ++dstx) { + dst[dstx + dsty * dstsz.x] = src[srcx + srcy * srcsz.x]; + } + } + } + + TextProgram program_; + + Texture2DRef texture_; + + VertexArrayRef vao_; + + ArrayBufferRef vertices_; + + bool glyph_loaded_ = false; +} diff --git a/src/sj/TextProgram.d b/src/sj/TextProgram.d index 270c2c0..37b1416 100644 --- a/src/sj/TextProgram.d +++ b/src/sj/TextProgram.d @@ -38,13 +38,15 @@ class TextProgram { /// enum FragmentShaderSrc = ShaderHeader ~ q{ layout(location = 3) uniform sampler2D tex; + layout(location = 4) uniform vec4 color; in vec2 uv_; out vec4 pixel_; void main() { - pixel_ = texture(tex, uv_); + pixel_ = color; + pixel_.a *= texture(tex, uv_).r; } }; @@ -87,7 +89,7 @@ class TextProgram { } /// - void Use(mat4 proj, mat4 view, mat4 model, ref Texture2DRef tex) { + void Use(mat4 proj, mat4 view, mat4 model, ref Texture2DRef tex, vec4 color) { tex.BindToUnit(GL_TEXTURE0); sampler_.Bind(0); @@ -96,6 +98,7 @@ class TextProgram { program_.uniform!1 = view; program_.uniform!2 = model; program_.uniform!3 = 0; + program_.uniform!4 = color; } private: