diff --git a/data/font.fs b/data/font.fs deleted file mode 100644 index dd84d034..00000000 --- a/data/font.fs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-License-Identifier: MIT - -#version 330 core - -in vec4 color; -in vec2 uv; - -uniform sampler2D FontAtlas; - -out vec4 fragColor; - -void main() -{ - fragColor = vec4(color.rgb, color.a * texture(FontAtlas, uv).r); -} diff --git a/data/font.vs b/data/font.vs deleted file mode 100644 index 5d4955d7..00000000 --- a/data/font.vs +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-License-Identifier: MIT - -#version 330 core - -layout (location = 0) in vec2 aPosition; -layout (location = 1) in vec2 aUV; -layout (location = 2) in vec4 aColor; - -out vec4 color; -out vec2 uv; - -uniform mat4 ProjectionMatrix; - -void main() -{ - gl_Position = ProjectionMatrix * vec4(aPosition, 0.0, 1.0); - - color = aColor; - uv = aUV; -} diff --git a/src/Box2D.NET.Samples/Box2D.NET.Samples.csproj b/src/Box2D.NET.Samples/Box2D.NET.Samples.csproj index 76597051..c3af722b 100644 --- a/src/Box2D.NET.Samples/Box2D.NET.Samples.csproj +++ b/src/Box2D.NET.Samples/Box2D.NET.Samples.csproj @@ -11,6 +11,11 @@ + + + + + @@ -42,4 +47,4 @@ - \ No newline at end of file + diff --git a/src/Box2D.NET.Samples/EmbeddedShaders.cs b/src/Box2D.NET.Samples/EmbeddedShaders.cs new file mode 100644 index 00000000..fd2a2ee5 --- /dev/null +++ b/src/Box2D.NET.Samples/EmbeddedShaders.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2026 Erin Catto +// SPDX-FileCopyrightText: 2026 Ikpil Choi(ikpil@naver.com) +// SPDX-License-Identifier: MIT + +using System.IO; +using System.Reflection; + +namespace Box2D.NET.Samples; + +// Embedded from data/*.vs and *.fs. Do not edit the shader strings here. +public static class EmbeddedShaders +{ + public static readonly string k_background_vs = Load("background.vs"); + public static readonly string k_background_fs = Load("background.fs"); + public static readonly string k_circle_vs = Load("circle.vs"); + public static readonly string k_circle_fs = Load("circle.fs"); + public static readonly string k_line_vs = Load("line.vs"); + public static readonly string k_line_fs = Load("line.fs"); + public static readonly string k_point_vs = Load("point.vs"); + public static readonly string k_point_fs = Load("point.fs"); + public static readonly string k_solid_capsule_vs = Load("solid_capsule.vs"); + public static readonly string k_solid_capsule_fs = Load("solid_capsule.fs"); + public static readonly string k_solid_circle_vs = Load("solid_circle.vs"); + public static readonly string k_solid_circle_fs = Load("solid_circle.fs"); + public static readonly string k_solid_polygon_vs = Load("solid_polygon.vs"); + public static readonly string k_solid_polygon_fs = Load("solid_polygon.fs"); + + private static string Load(string name) + { + Assembly assembly = typeof(EmbeddedShaders).Assembly; + using Stream stream = assembly.GetManifestResourceStream($"Box2D.NET.Samples.Shaders.{name}")!; + using StreamReader reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/src/Box2D.NET.Samples/Graphics/Backgrounds.cs b/src/Box2D.NET.Samples/Graphics/Backgrounds.cs index d4d56580..90b5892d 100644 --- a/src/Box2D.NET.Samples/Graphics/Backgrounds.cs +++ b/src/Box2D.NET.Samples/Graphics/Backgrounds.cs @@ -5,6 +5,7 @@ using System; using Silk.NET.GLFW; using Silk.NET.OpenGL; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -14,7 +15,7 @@ public static Background CreateBackground(GL gl) { Background background = new Background(); - background.programId = gl.CreateProgramFromFiles("data/background.vs", "data/background.fs"); + background.programId = gl.CreateProgramFromStrings(k_background_vs, k_background_fs); background.timeUniform = gl.GetUniformLocation(background.programId, "time"); background.resolutionUniform = gl.GetUniformLocation(background.programId, "resolution"); background.baseColorUniform = gl.GetUniformLocation(background.programId, "baseColor"); diff --git a/src/Box2D.NET.Samples/Graphics/Cameras.cs b/src/Box2D.NET.Samples/Graphics/Cameras.cs index b4f261d2..0e1bb84a 100644 --- a/src/Box2D.NET.Samples/Graphics/Cameras.cs +++ b/src/Box2D.NET.Samples/Graphics/Cameras.cs @@ -94,29 +94,6 @@ public static void BuildProjectionMatrix(Camera camera, Span m, float zBi m[15] = 1.0f; } - public static void MakeOrthographicMatrix(Span m, float left, float right, float bottom, float top, float near, float far) - { - m[0] = 2.0f / (right - left); - m[1] = 0.0f; - m[2] = 0.0f; - m[3] = 0.0f; - - m[4] = 0.0f; - m[5] = 2.0f / (top - bottom); - m[6] = 0.0f; - m[7] = 0.0f; - - m[8] = 0.0f; - m[9] = 0.0f; - m[10] = -2.0f / (far - near); - m[11] = 0.0f; - - m[12] = -(right + left) / (right - left); - m[13] = -(top + bottom) / (top - bottom); - m[14] = -(far + near) / (far - near); - m[15] = 1.0f; - } - public static B2AABB GetViewBounds(Camera camera) { if (camera.height == 0.0f || camera.width == 0.0f) diff --git a/src/Box2D.NET.Samples/Graphics/Circles.cs b/src/Box2D.NET.Samples/Graphics/Circles.cs index 4193dda6..82b0e1f6 100644 --- a/src/Box2D.NET.Samples/Graphics/Circles.cs +++ b/src/Box2D.NET.Samples/Graphics/Circles.cs @@ -9,6 +9,7 @@ using Box2D.NET.Samples.Primitives; using static Box2D.NET.B2MathFunction; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -19,7 +20,7 @@ public static class Circles public static CircleRender CreateCircles(GL gl) { CircleRender render = new CircleRender(); - render.programId = gl.CreateProgramFromFiles("data/circle.vs", "data/circle.fs"); + render.programId = gl.CreateProgramFromStrings(k_circle_vs, k_circle_fs); render.projectionUniform = gl.GetUniformLocation(render.programId, "projectionMatrix"); render.pixelScaleUniform = gl.GetUniformLocation(render.programId, "pixelScale"); uint vertexAttribute = 0; @@ -144,4 +145,4 @@ public static void FlushCircles(GL gl, ref CircleRender render, Camera camera) render.circles.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/Draw.cs b/src/Box2D.NET.Samples/Graphics/Draw.cs index 09a54891..0f166209 100644 --- a/src/Box2D.NET.Samples/Graphics/Draw.cs +++ b/src/Box2D.NET.Samples/Graphics/Draw.cs @@ -20,6 +20,5 @@ public class Draw public SolidCircleRender circles; public SolidCapsuleRender capsules; public SolidPolygonRender polygons; - public Font font; -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/DrawText.cs b/src/Box2D.NET.Samples/Graphics/DrawText.cs new file mode 100644 index 00000000..c6c5a7c0 --- /dev/null +++ b/src/Box2D.NET.Samples/Graphics/DrawText.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Erin Catto +// SPDX-FileCopyrightText: 2026 Ikpil Choi(ikpil@naver.com) +// SPDX-License-Identifier: MIT + +using System.Numerics; +using ImGuiNET; +using static Box2D.NET.Samples.Graphics.Cameras; + +namespace Box2D.NET.Samples.Graphics; + +public static partial class Draws +{ + public static void DrawScreenString(Draw draw, float x, float y, B2HexColor color, string message) + { + message = message.Length > 255 ? message[..255] : message; + uint hex = (uint)color; + + // b2HexColor packs as 0xRRGGBB; matches MakeRGBA8's byte order. Force alpha to opaque. + uint col = ImGui.ColorConvertFloat4ToU32(new Vector4(((hex >> 16) & 0xFF) / 255.0f, ((hex >> 8) & 0xFF) / 255.0f, + (hex & 0xFF) / 255.0f, 1.0f)); + + // Old STB path treated y as the text baseline; ImGui::AddText treats y as the top of the + // glyph. Shift up by the font ascent so existing callers work. + float ascent = ImGui.GetFont().Ascent; + Vector2 viewportPosition = ImGui.GetMainViewport().Pos; + ImGui.GetBackgroundDrawList().AddText(viewportPosition + new Vector2(x, y - ascent), col, message); + } + + public static void DrawWorldString(Draw draw, Camera camera, B2Vec2 p, B2HexColor color, string message) + { + message = message.Length > 255 ? message[..255] : message; + B2Vec2 ps = ConvertWorldToScreen(camera, p); + DrawScreenString(draw, ps.X, ps.Y, color, message); + } +} diff --git a/src/Box2D.NET.Samples/Graphics/Draws.cs b/src/Box2D.NET.Samples/Graphics/Draws.cs index 80c8b149..531a7a21 100644 --- a/src/Box2D.NET.Samples/Graphics/Draws.cs +++ b/src/Box2D.NET.Samples/Graphics/Draws.cs @@ -4,7 +4,6 @@ using System; using static Box2D.NET.B2MathFunction; -using static Box2D.NET.Samples.Graphics.Cameras; using static Box2D.NET.Samples.Graphics.Backgrounds; using static Box2D.NET.Samples.Graphics.Points; using static Box2D.NET.Samples.Graphics.Circles; @@ -12,11 +11,10 @@ using static Box2D.NET.Samples.Graphics.SolidCapsules; using static Box2D.NET.Samples.Graphics.SolidCircles; using static Box2D.NET.Samples.Graphics.SolidPolygons; -using static Box2D.NET.Samples.Graphics.Fonts; namespace Box2D.NET.Samples.Graphics; -public static class Draws +public static partial class Draws { public static Draw CreateDraw(SampleContext context) { @@ -30,8 +28,6 @@ public static Draw CreateDraw(SampleContext context) draw.circles = CreateSolidCircles(context.gl); draw.capsules = CreateSolidCapsule(context.gl); draw.polygons = CreateSolidPolygons(context.gl); - draw.font = CreateFont(context.gl, "data/droid_sans.ttf", 18.0f); - return draw; } @@ -44,7 +40,6 @@ public static void DestroyDraw(Draw draw) DestroySolidCircles(draw.gl, ref draw.circles); DestroyCapsules(draw.gl, ref draw.capsules); DestroyPolygons(draw.gl, ref draw.polygons); - DestroyFont(draw.gl, ref draw.font); } public static void DrawPolygon(Draw draw, ReadOnlySpan vertices, int vertexCount, B2HexColor color) @@ -87,17 +82,6 @@ public static void DrawBounds(Draw draw, in B2AABB aabb, B2HexColor c) AddLine(ref draw.lines, p4, p1, c); } - public static void DrawScreenString(Draw draw, float x, float y, B2HexColor color, string message) - { - AddText(ref draw.font, x, y, color, message); - } - - public static void DrawWorldString(Draw draw, Camera camera, B2Vec2 p, B2HexColor color, string message) - { - B2Vec2 ps = ConvertWorldToScreen(camera, p); - AddText(ref draw.font, ps.X, ps.Y, color, message); - } - public static void DrawCircle(Draw draw, B2Vec2 center, float radius, B2HexColor color) { AddCircle(ref draw.hollowCircles, center, radius, color); @@ -135,7 +119,6 @@ public static void FlushDraw(Draw draw, Camera camera) FlushCircles(draw.gl, ref draw.hollowCircles, camera); FlushLines(draw.gl, ref draw.lines, camera); FlushPoints(draw.gl, ref draw.points, camera); - FlushText(draw.gl, ref draw.font, camera); draw.gl.CheckOpenGL(); } } diff --git a/src/Box2D.NET.Samples/Graphics/Font.cs b/src/Box2D.NET.Samples/Graphics/Font.cs deleted file mode 100644 index 92f353c9..00000000 --- a/src/Box2D.NET.Samples/Graphics/Font.cs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) -// SPDX-License-Identifier: MIT - -using System.Collections.Generic; -using Box2D.NET.Samples.Primitives; - -namespace Box2D.NET.Samples.Graphics; - -public struct FontV1 -{ - public float fontSize; - public B2Array vertices; - //public stbtt_bakedchar* characters; - public byte[] characters; - public uint[] textureId; - public uint[] vaoId; - public uint[] vboId; - public uint programId; - - public FontV1() - { - textureId = new uint[1]; - vaoId = new uint[1]; - vboId = new uint[1]; - } -} - -public struct Font -{ - public float fontSize; - public List texts; - - public Font() - { - texts = new List(); - } -} diff --git a/src/Box2D.NET.Samples/Graphics/Fonts.cs b/src/Box2D.NET.Samples/Graphics/Fonts.cs deleted file mode 100644 index a72aa85a..00000000 --- a/src/Box2D.NET.Samples/Graphics/Fonts.cs +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) -// SPDX-License-Identifier: MIT - -using System.Numerics; -using Silk.NET.OpenGL; -using Box2D.NET.Samples.Primitives; -using ImGuiNET; - -namespace Box2D.NET.Samples.Graphics; - -public static class Fonts -{ - public static Font CreateFont(GL gl, string trueTypeFile, float fontSize) - { - Font font = new Font(); - return font; - } - - public static void DestroyFont(GL gl, ref Font font) - { - font.texts.Clear(); - } - - public static void AddText(ref Font font, float x, float y, B2HexColor color, string text) - { - var fontText = new FontText(); - fontText.x = x; - fontText.y = y; - fontText.color = color; - fontText.text = text; - - font.texts.Add(fontText); - } - - public static void FlushText(GL gl, ref Font font, Camera camera) - { - if (0 >= font.texts.Count) - return; - - ImGui.Begin("Overlay", - ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.AlwaysAutoResize | - ImGuiWindowFlags.NoScrollbar); - - foreach (var text in font.texts) - { - int hex = (int)text.color; - float r = ((hex >> 16) & 0xFF) / 255.0f; - float g = ((hex >> 8) & 0xFF) / 255.0f; - float b = (hex & 0xFF) / 255.0f; - - ImGui.SetCursorPos(new Vector2(text.x, text.y)); - ImGui.TextColored(new Vector4(r, g, b, 1.0f), text.text); - } - - ImGui.End(); - font.texts.Clear(); - } -} \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Graphics/FontsV1.cs b/src/Box2D.NET.Samples/Graphics/FontsV1.cs deleted file mode 100644 index 4f8a3464..00000000 --- a/src/Box2D.NET.Samples/Graphics/FontsV1.cs +++ /dev/null @@ -1,237 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) -// SPDX-License-Identifier: MIT - -using System; -using System.IO; -using System.Runtime.InteropServices; -using Silk.NET.OpenGL; -using Box2D.NET.Samples.Helpers; -using Box2D.NET.Samples.Primitives; -using static Box2D.NET.Samples.Graphics.Cameras; -using static Box2D.NET.B2Arrays; -using static Box2D.NET.B2Diagnostics; - -namespace Box2D.NET.Samples.Graphics; - -public static class FontsV1 -{ - public const int FONT_FIRST_CHARACTER = 32; - public const int FONT_CHARACTER_COUNT = 96; - public const int FONT_ATLAS_WIDTH = 512; - public const int FONT_ATLAS_HEIGHT = 512; - - // The number of vertices the vbo can hold. Must be a multiple of 6. - public const int FONT_BATCH_SIZE = (6 * 10000); - - public struct stbtt_bakedchar - { - public ushort x0, y0, x1, y1; // coordinates of bbox in bitmap - public float xoff, yoff, xadvance; - } - - public struct stbtt_aligned_quad - { - public float x0; - public float y0; - public float s0; - public float t0; // top-left - public float x1; - public float y1; - public float s1; - public float t1; // bottom-right - } - - - public static FontV1 CreateFont(GL gl, string trueTypeFile, float fontSize) - { - FontV1 font = new FontV1(); - - if (!File.Exists(trueTypeFile)) - { - B2_ASSERT(false); - return font; - } - - - nint unmanagedPtr = 0; - try - { - //FontDescription description = FontDescription.LoadDescription(trueTypeFile); - font.vertices = b2Array_Create(FONT_BATCH_SIZE); - font.fontSize = fontSize; - font.characters = new byte[FONT_CHARACTER_COUNT * B2SizeOf.Size]; - - int fileBufferCapacity = 1 << 20; - byte[] fileBuffer = File.ReadAllBytes(trueTypeFile); - - int pw = FONT_ATLAS_WIDTH; - int ph = FONT_ATLAS_HEIGHT; - byte[] tempBitmap = new byte[pw * ph * B2SizeOf.Size]; - // stbtt_BakeFontBitmap(fileBuffer, 0, font.fontSize, tempBitmap, pw, ph, FONT_FIRST_CHARACTER, FONT_CHARACTER_COUNT, - // font.characters); - - unmanagedPtr = Marshal.AllocHGlobal(tempBitmap.Length); - Marshal.Copy(tempBitmap, 0, unmanagedPtr, tempBitmap.Length); - - gl.GenTextures(1, font.textureId); - gl.BindTexture(GLEnum.Texture2D, font.textureId[0]); - //gl.TexImage2D(GLEnum.Texture2D, 0, InternalFormat.R8, (uint)pw, (uint)ph, 0, PixelFormat.Red, PixelType.UnsignedByte, unmanagedPtr); - gl.TexParameter(GLEnum.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Linear); - - // for debugging - // stbi_write_png( "build/fontAtlas.png", pw, ph, 1, tempBitmap, pw ); - } - finally - { - Marshal.FreeHGlobal(unmanagedPtr); - } - - font.programId = gl.CreateProgramFromFiles("data/font.vs", "data/font.fs"); - if (font.programId == 0) - { - return font; - } - - // Setting up the VAO and VBO - gl.GenBuffers(1, font.vboId); - gl.BindBuffer(GLEnum.ArrayBuffer, font.vboId[0]); - gl.BufferData(GLEnum.ArrayBuffer, FONT_BATCH_SIZE * (uint)B2SizeOf.Size, null, GLEnum.DynamicDraw); - - gl.GenVertexArrays(1, font.vaoId); - gl.BindVertexArray(font.vaoId[0]); - - // position attribute - gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, (uint)B2SizeOf.Size, IntPtr.Zero); // position - gl.EnableVertexAttribArray(0); - - // uv attribute - gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, (uint)B2SizeOf.Size, IntPtr.Zero + 8); // uv - gl.EnableVertexAttribArray(1); - - // color attribute will be expanded to floats using normalization - gl.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, (uint)B2SizeOf.Size, IntPtr.Zero + 16); // color - gl.EnableVertexAttribArray(2); - - gl.BindVertexArray(0); - - gl.CheckOpenGL(); - - return font; - } - - public static void DestroyFont(GL gl, ref FontV1 font) - { - if (font.programId != 0) - { - gl.DeleteProgram(font.programId); - } - - gl.DeleteBuffers(1, font.vboId); - gl.DeleteVertexArrays(1, font.vaoId); - - if (font.textureId[0] != 0) - { - gl.DeleteTextures(1, font.textureId); - } - - font.characters = null; - - b2Array_Destroy(ref font.vertices); - } - - public static void AddText(ref FontV1 font, float x, float y, B2HexColor color, string text) - { - if (text == null) - { - return; - } - - B2Vec2 position = new B2Vec2(x, y); - RGBA8 c = RGBA8.MakeRGBA8(color, 1.0f); - int pw = FONT_ATLAS_WIDTH; - int ph = FONT_ATLAS_HEIGHT; - - for (int i = 0; i < text.Length; ++i) - { - int index = (int)text[i] - FONT_FIRST_CHARACTER; - - if (0 <= index && index < FONT_CHARACTER_COUNT) - { - // 1=opengl - stbtt_aligned_quad q = new stbtt_aligned_quad(); - //stbtt_GetBakedQuad(font.characters, pw, ph, index, &position.x, &position.y, &q, 1); - - FontVertex v1 = new FontVertex(new B2Vec2(q.x0, q.y0), new B2Vec2(q.s0, q.t0), c); - FontVertex v2 = new FontVertex(new B2Vec2(q.x1, q.y0), new B2Vec2(q.s1, q.t0), c); - FontVertex v3 = new FontVertex(new B2Vec2(q.x1, q.y1), new B2Vec2(q.s1, q.t1), c); - FontVertex v4 = new FontVertex(new B2Vec2(q.x0, q.y1), new B2Vec2(q.s0, q.t1), c); - - b2Array_Push(ref font.vertices, v1); - b2Array_Push(ref font.vertices, v3); - b2Array_Push(ref font.vertices, v2); - b2Array_Push(ref font.vertices, v1); - b2Array_Push(ref font.vertices, v4); - b2Array_Push(ref font.vertices, v3); - } - - i += 1; - } - } - - public static void FlushText(GL gl, ref FontV1 font, Camera camera) - { - var tempProjectionMatrix = new B2FixedArray16(); - Span projectionMatrix = tempProjectionMatrix.AsSpan(); - MakeOrthographicMatrix(projectionMatrix, 0.0f, camera.width, camera.height, 0.0f, -1.0f, 1.0f); - - gl.UseProgram(font.programId); - - gl.Enable(GLEnum.Blend); - gl.BlendFunc(GLEnum.SrcAlpha, GLEnum.OneMinusSrcAlpha); - - int slot = 0; - gl.ActiveTexture(GLEnum.Texture0 + slot); - gl.BindTexture(GLEnum.Texture2D, font.textureId[0]); - - gl.BindVertexArray(font.vaoId[0]); - gl.BindBuffer(GLEnum.ArrayBuffer, font.vboId[0]); - - int textureUniform = gl.GetUniformLocation(font.programId, "FontAtlas"); - gl.Uniform1(textureUniform, slot); - - int matrixUniform = gl.GetUniformLocation(font.programId, "ProjectionMatrix"); - gl.UniformMatrix4(matrixUniform, 1, false, projectionMatrix); - - int totalVertexCount = font.vertices.count; - int drawCallCount = (totalVertexCount / FONT_BATCH_SIZE) + 1; - - for (int i = 0; i < drawCallCount; i++) - { - Span data = font.vertices.data.AsSpan(i * FONT_BATCH_SIZE); - - int vertexCount; - if (i == drawCallCount - 1) - { - vertexCount = totalVertexCount % FONT_BATCH_SIZE; - } - else - { - vertexCount = FONT_BATCH_SIZE; - } - - gl.BufferSubData(GLEnum.ArrayBuffer, 0, data); - gl.DrawArrays(GLEnum.Triangles, 0, (uint)vertexCount); - } - - gl.BindBuffer(GLEnum.ArrayBuffer, 0); - gl.BindVertexArray(0); - gl.BindTexture(GLEnum.Texture2D, 0); - - gl.Disable(GLEnum.Blend); - - gl.CheckOpenGL(); - - font.vertices.count = 0; - } -} \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Graphics/Lines.cs b/src/Box2D.NET.Samples/Graphics/Lines.cs index 4ceedf8f..0e51f537 100644 --- a/src/Box2D.NET.Samples/Graphics/Lines.cs +++ b/src/Box2D.NET.Samples/Graphics/Lines.cs @@ -10,6 +10,7 @@ using static Box2D.NET.B2MathFunction; using static Box2D.NET.B2Diagnostics; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -23,7 +24,7 @@ public static class Lines public static LineRender CreateLineRender(GL gl) { var render = new LineRender(); - render.m_programId = gl.CreateProgramFromFiles("data/line.vs", "data/line.fs"); + render.m_programId = gl.CreateProgramFromStrings(k_line_vs, k_line_fs); render.m_projectionUniform = gl.GetUniformLocation(render.m_programId, "projectionMatrix"); uint vertexAttribute = 0; uint colorAttribute = 1; @@ -126,4 +127,4 @@ public static void FlushLines(GL gl, ref LineRender render, Camera camera) render.m_points.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/Points.cs b/src/Box2D.NET.Samples/Graphics/Points.cs index 2012c730..ee46f937 100644 --- a/src/Box2D.NET.Samples/Graphics/Points.cs +++ b/src/Box2D.NET.Samples/Graphics/Points.cs @@ -9,6 +9,7 @@ using Box2D.NET.Samples.Primitives; using static Box2D.NET.B2MathFunction; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -19,7 +20,7 @@ public static class Points public static PointRender CreatePointDrawData(GL gl) { PointRender render = new PointRender(); - render.programId = gl.CreateProgramFromFiles("data/point.vs", "data/point.fs"); + render.programId = gl.CreateProgramFromStrings(k_point_vs, k_point_fs); render.projectionUniform = gl.GetUniformLocation(render.programId, "projectionMatrix"); uint vertexAttribute = 0; uint sizeAttribute = 1; @@ -120,4 +121,4 @@ public static void FlushPoints(GL gl, ref PointRender render, Camera camera) render.points.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/SolidCapsules.cs b/src/Box2D.NET.Samples/Graphics/SolidCapsules.cs index b56f5a25..8511fbf0 100644 --- a/src/Box2D.NET.Samples/Graphics/SolidCapsules.cs +++ b/src/Box2D.NET.Samples/Graphics/SolidCapsules.cs @@ -10,6 +10,7 @@ using Serilog; using static Box2D.NET.B2MathFunction; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -23,7 +24,7 @@ public static class SolidCapsules public static SolidCapsuleRender CreateSolidCapsule(GL gl) { var render = new SolidCapsuleRender(); - render.programId = gl.CreateProgramFromFiles("data/solid_capsule.vs", "data/solid_capsule.fs"); + render.programId = gl.CreateProgramFromStrings(k_solid_capsule_vs, k_solid_capsule_fs); render.projectionUniform = gl.GetUniformLocation(render.programId, "projectionMatrix"); render.pixelScaleUniform = gl.GetUniformLocation(render.programId, "pixelScale"); @@ -169,4 +170,4 @@ public static void FlushCapsules(GL gl, ref SolidCapsuleRender render, Camera ca render.capsules.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/SolidCircles.cs b/src/Box2D.NET.Samples/Graphics/SolidCircles.cs index 562c06a4..42beea57 100644 --- a/src/Box2D.NET.Samples/Graphics/SolidCircles.cs +++ b/src/Box2D.NET.Samples/Graphics/SolidCircles.cs @@ -9,6 +9,7 @@ using Box2D.NET.Samples.Primitives; using static Box2D.NET.B2MathFunction; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -22,7 +23,7 @@ public static class SolidCircles public static SolidCircleRender CreateSolidCircles(GL gl) { var render = new SolidCircleRender(); - render.programId = gl.CreateProgramFromFiles("data/solid_circle.vs", "data/solid_circle.fs"); + render.programId = gl.CreateProgramFromStrings(k_solid_circle_vs, k_solid_circle_fs); render.projectionUniform = gl.GetUniformLocation(render.programId, "projectionMatrix"); render.pixelScaleUniform = gl.GetUniformLocation(render.programId, "pixelScale"); @@ -149,4 +150,4 @@ public static void FlushSolidCircles(GL gl, ref SolidCircleRender render, Camera render.circles.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Graphics/SolidPolygons.cs b/src/Box2D.NET.Samples/Graphics/SolidPolygons.cs index bdfbc899..99f0f9bc 100644 --- a/src/Box2D.NET.Samples/Graphics/SolidPolygons.cs +++ b/src/Box2D.NET.Samples/Graphics/SolidPolygons.cs @@ -9,6 +9,7 @@ using Box2D.NET.Samples.Primitives; using static Box2D.NET.B2MathFunction; using static Box2D.NET.Samples.Graphics.Cameras; +using static Box2D.NET.Samples.EmbeddedShaders; namespace Box2D.NET.Samples.Graphics; @@ -20,7 +21,7 @@ public static class SolidPolygons public static SolidPolygonRender CreateSolidPolygons(GL gl) { var render = new SolidPolygonRender(); - render.programId = gl.CreateProgramFromFiles("data/solid_polygon.vs", "data/solid_polygon.fs"); + render.programId = gl.CreateProgramFromStrings(k_solid_polygon_vs, k_solid_polygon_fs); render.projectionUniform = gl.GetUniformLocation(render.programId, "projectionMatrix"); render.pixelScaleUniform = gl.GetUniformLocation(render.programId, "pixelScale"); @@ -177,4 +178,4 @@ public static void FlushPolygons(GL gl, ref SolidPolygonRender render, Camera ca render.polygons.Clear(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Primitives/FontText.cs b/src/Box2D.NET.Samples/Primitives/FontText.cs deleted file mode 100644 index 18e663ec..00000000 --- a/src/Box2D.NET.Samples/Primitives/FontText.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Box2D.NET.Samples.Primitives; - -public struct FontText -{ - public float x; - public float y; - public B2HexColor color; - public string text; -} \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Primitives/FontVertex.cs b/src/Box2D.NET.Samples/Primitives/FontVertex.cs deleted file mode 100644 index 8954f3ec..00000000 --- a/src/Box2D.NET.Samples/Primitives/FontVertex.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto -// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) -// SPDX-License-Identifier: MIT - -namespace Box2D.NET.Samples.Primitives; - -public struct FontVertex -{ - public B2Vec2 position; - public B2Vec2 uv; - public RGBA8 color; - - public FontVertex(B2Vec2 position, B2Vec2 uv, RGBA8 color) - { - this.position = position; - this.uv = uv; - this.color = color; - } -} diff --git a/src/Box2D.NET.Samples/SampleApp.cs b/src/Box2D.NET.Samples/SampleApp.cs index d6eae905..b14a2178 100644 --- a/src/Box2D.NET.Samples/SampleApp.cs +++ b/src/Box2D.NET.Samples/SampleApp.cs @@ -47,6 +47,9 @@ public class SampleApp private B2Vec2 s_clickPointWS = b2Vec2_zero; private float s_framebufferScale = 1.0f; private float _frameTime = 0.0f; + private double _frameStartTime = 0.0; + private byte[] _fontData; + private GCHandle _fontDataHandle; public SampleApp() { @@ -241,13 +244,12 @@ private void OnWindowLoad() _context.glfw.SetScrollCallback(_context.window, ScrollCallback); } + // todo put this in _context.settings + CreateUI(glslVersion); _context.draw = CreateDraw(_context); _context.sampleIndex = b2ClampInt(_context.sampleIndex, 0, SampleFactory.Shared.SampleCount - 1); - // todo put this in _context.settings - CreateUI(glslVersion); - _context.gl.ClearColor(0.2f, 0.2f, 0.2f, 1.0f); } @@ -271,7 +273,7 @@ private void OnWindowUpdate(double dt) return; } - double time1 = _context.glfw.GetTime(); + _frameStartTime = _context.glfw.GetTime(); if (GlfwHelpers.GetKey(_context, Keys.Z) == InputAction.Press) { @@ -297,30 +299,6 @@ private void OnWindowUpdate(double dt) _context.glfw.GetCursorPos(_context.window, out cursorPosX, out cursorPosY); } - // For the Tracy profiler - //FrameMark; - - if (_context.sample == null) - { - // delayed creation because imgui doesn't create fonts until NewFrame() is called - _context.sample = SampleFactory.Shared.Create(_context.sampleIndex, _context); - } - - _context.sample.Step(); - - _context.glfw.PollEvents(); - - // Limit frame rate to 60Hz - double time2 = _context.glfw.GetTime(); - double targetTime = time1 + 1.0 / 60.0; - while (time2 < targetTime) - { - b2Yield(); - time2 = _context.glfw.GetTime(); - } - - _frameTime = (float)(time2 - time1); - // ImGui_ImplGlfw_CursorPosCallback(_ctx.g_mainWindow, cursorPosX / s_windowScale, cursorPosY / s_windowScale); // ImGui_ImplOpenGL3_NewFrame(); // ImGui_ImplGlfw_NewFrame(); @@ -333,6 +311,30 @@ private void OnWindowUpdate(double dt) io.DeltaTime = (float)dt; _imgui.Update((float)dt); } + + if (_context.sample == null) + { + // delayed creation because imgui doesn't create fonts until NewFrame() is called + _context.sample = SampleFactory.Shared.Create(_context.sampleIndex, _context); + } + + _context.sample.ResetText(); + + if (_context.showUI) + { + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorGoldenRod, SampleFactory.Shared.GetName(_context.sampleIndex)); + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorLightGray, SampleFactory.Shared.GetCategory(_context.sampleIndex)); + _context.sample.DrawTextLine(""); + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorSeaGreen, $"{1000.0f * _frameTime:0.0} ms"); + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorSeaGreen, $"step {_context.sample.m_stepCount}"); + _context.sample.DrawTextLine(""); + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorSeaGreen, + $"cam ({_context.camera.center.X:0.0}, {_context.camera.center.Y:0.0})"); + _context.sample.DrawColoredTextLine(B2HexColor.b2_colorSeaGreen, $"zoom {_context.camera.zoom:0.00}"); + } + + _context.sample.Step(); + _context.sample.Draw(); } private void OnWindowRenderSafe(double dt) @@ -351,22 +353,15 @@ private void OnWindowRender(double dt) { _context.gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - ImGui.SetNextWindowPos(new Vector2(0.0f, 0.0f)); - ImGui.SetNextWindowSize(new Vector2(_context.camera.width, _context.camera.height)); - ImGui.SetNextWindowBgAlpha(0.0f); - ImGui.Begin("Overlay", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar); - ImGui.End(); - - _context.sample.ResetText(); - - var title = SampleFactory.Shared.GetTitle(_context.sampleIndex); - _context.sample.DrawColoredTextLine(B2HexColor.b2_colorYellow, title); - - string buffer = $"{1000.0f * _frameTime:0.0} ms - step {_context.sample.m_stepCount} - " + - $"camera ({_context.camera.center.X:G}, {_context.camera.center.Y:G}, {_context.camera.zoom:G})"; - DrawScreenString(_context.draw, 5.0f, _context.camera.height - 18.0f, B2HexColor.b2_colorSeaGreen, buffer); + if (_context.showUI == false) + { + float fontSize = ImGui.GetFontSize(); + DrawScreenString(_context.draw, 5.0f, 1.5f * fontSize, B2HexColor.b2_colorYellow, + $"{SampleFactory.Shared.GetCategory(_context.sampleIndex)} : {SampleFactory.Shared.GetName(_context.sampleIndex)}"); + DrawScreenString(_context.draw, 5.0f, _context.camera.height - 0.5f * fontSize, B2HexColor.b2_colorSeaGreen, + $"{1000.0f * _frameTime:0.0} ms step {_context.sample.m_stepCount}"); + } - _context.sample.Draw(); FlushDraw(_context.draw, _context.camera); UpdateSampleUI(_context); @@ -380,6 +375,22 @@ private void OnWindowRender(double dt) { _context.glfw.SwapBuffers(_context.window); } + + // For the Tracy profiler + //FrameMark; + + // Silk.NET.Windowing polls events for the run loop. + + // Limit frame rate to 60Hz + double time2 = _context.glfw.GetTime(); + double targetTime = _frameStartTime + 1.0 / 60.0; + while (time2 < targetTime) + { + b2Yield(); + time2 = _context.glfw.GetTime(); + } + + _frameTime = (float)(time2 - _frameStartTime); } @@ -426,6 +437,90 @@ private void glfwErrorCallback(ErrorCode error, string description) Logger.Information($"GLFW error occurred. Code: {error}. Description: {description}"); } + private static void ApplyUIStyle() + { + ImGuiStylePtr style = ImGui.GetStyle(); + + // Metrics: containers round at 4px, controls at 3px - one deliberate + // system instead of the stock mix. Padding gives rows room to breathe. + style.WindowPadding = new Vector2(10.0f, 10.0f); + style.FramePadding = new Vector2(8.0f, 4.0f); + style.CellPadding = new Vector2(6.0f, 4.0f); + style.ItemSpacing = new Vector2(8.0f, 7.0f); + style.ItemInnerSpacing = new Vector2(7.0f, 4.0f); + style.IndentSpacing = 18.0f; + style.ScrollbarSize = 12.0f; + style.GrabMinSize = 10.0f; + + style.WindowBorderSize = 1.0f; + style.FrameBorderSize = 0.0f; + style.PopupBorderSize = 1.0f; + style.TabBorderSize = 0.0f; + style.SeparatorTextBorderSize = 1.0f; + + style.WindowRounding = 4.0f; + style.ChildRounding = 4.0f; + style.PopupRounding = 4.0f; + style.FrameRounding = 3.0f; + style.GrabRounding = 3.0f; + style.ScrollbarRounding = 3.0f; + style.TabRounding = 3.0f; + + style.WindowTitleAlign = new Vector2(0.0f, 0.5f); + + // Palette: neutral charcoal surfaces, one steel-blue accent at three + // brightnesses. Replaces stock ImGui's saturated cornflower blue. + Vector4 accent = new Vector4(0.28f, 0.48f, 0.66f, 1.00f); + Vector4 accentHi = new Vector4(0.38f, 0.60f, 0.80f, 1.00f); + Vector4 accentLo = new Vector4(0.22f, 0.36f, 0.50f, 1.00f); + + style.Colors[(int)ImGuiCol.Text] = new Vector4(0.90f, 0.91f, 0.93f, 1.00f); + style.Colors[(int)ImGuiCol.TextDisabled] = new Vector4(0.49f, 0.51f, 0.55f, 1.00f); + style.Colors[(int)ImGuiCol.WindowBg] = new Vector4(0.110f, 0.115f, 0.125f, 0.97f); + style.Colors[(int)ImGuiCol.ChildBg] = new Vector4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[(int)ImGuiCol.PopupBg] = new Vector4(0.100f, 0.105f, 0.115f, 0.98f); + style.Colors[(int)ImGuiCol.Border] = new Vector4(0.00f, 0.00f, 0.00f, 0.45f); + style.Colors[(int)ImGuiCol.BorderShadow] = new Vector4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[(int)ImGuiCol.FrameBg] = new Vector4(0.18f, 0.19f, 0.21f, 1.00f); + style.Colors[(int)ImGuiCol.FrameBgHovered] = new Vector4(0.24f, 0.26f, 0.29f, 1.00f); + style.Colors[(int)ImGuiCol.FrameBgActive] = new Vector4(0.29f, 0.32f, 0.36f, 1.00f); + style.Colors[(int)ImGuiCol.TitleBg] = new Vector4(0.090f, 0.095f, 0.105f, 1.00f); + style.Colors[(int)ImGuiCol.TitleBgActive] = new Vector4(0.14f, 0.16f, 0.19f, 1.00f); + style.Colors[(int)ImGuiCol.TitleBgCollapsed] = new Vector4(0.090f, 0.095f, 0.105f, 0.75f); + style.Colors[(int)ImGuiCol.MenuBarBg] = new Vector4(0.13f, 0.14f, 0.16f, 1.00f); + style.Colors[(int)ImGuiCol.ScrollbarBg] = new Vector4(0.06f, 0.06f, 0.07f, 0.55f); + style.Colors[(int)ImGuiCol.ScrollbarGrab] = new Vector4(0.28f, 0.30f, 0.33f, 1.00f); + style.Colors[(int)ImGuiCol.ScrollbarGrabHovered] = new Vector4(0.36f, 0.39f, 0.43f, 1.00f); + style.Colors[(int)ImGuiCol.ScrollbarGrabActive] = accent; + style.Colors[(int)ImGuiCol.CheckMark] = accentHi; + style.Colors[(int)ImGuiCol.SliderGrab] = accent; + style.Colors[(int)ImGuiCol.SliderGrabActive] = accentHi; + style.Colors[(int)ImGuiCol.Button] = new Vector4(0.22f, 0.24f, 0.27f, 1.00f); + style.Colors[(int)ImGuiCol.ButtonHovered] = accentLo; + style.Colors[(int)ImGuiCol.ButtonActive] = accent; + style.Colors[(int)ImGuiCol.Header] = new Vector4(0.19f, 0.21f, 0.24f, 1.00f); + style.Colors[(int)ImGuiCol.HeaderHovered] = accentLo; + style.Colors[(int)ImGuiCol.HeaderActive] = accent; + style.Colors[(int)ImGuiCol.Separator] = new Vector4(1.00f, 1.00f, 1.00f, 0.09f); + style.Colors[(int)ImGuiCol.SeparatorHovered] = accentLo; + style.Colors[(int)ImGuiCol.SeparatorActive] = accent; + style.Colors[(int)ImGuiCol.ResizeGrip] = new Vector4(1.00f, 1.00f, 1.00f, 0.06f); + style.Colors[(int)ImGuiCol.ResizeGripHovered] = accentLo; + style.Colors[(int)ImGuiCol.ResizeGripActive] = accent; + style.Colors[(int)ImGuiCol.Tab] = new Vector4(0.15f, 0.16f, 0.18f, 1.00f); + style.Colors[(int)ImGuiCol.TabHovered] = accentLo; + style.Colors[(int)ImGuiCol.TabActive] = accent; + style.Colors[(int)ImGuiCol.TabUnfocused] = new Vector4(0.12f, 0.13f, 0.14f, 1.00f); + style.Colors[(int)ImGuiCol.TabUnfocusedActive] = accentLo; + style.Colors[(int)ImGuiCol.TextSelectedBg] = new Vector4(accent.X, accent.Y, accent.Z, 0.40f); + style.Colors[(int)ImGuiCol.DragDropTarget] = accentHi; + style.Colors[(int)ImGuiCol.NavHighlight] = accentHi; + style.Colors[(int)ImGuiCol.PlotLines] = new Vector4(0.70f, 0.72f, 0.75f, 1.00f); + style.Colors[(int)ImGuiCol.PlotLinesHovered] = accentHi; + style.Colors[(int)ImGuiCol.PlotHistogram] = accent; + style.Colors[(int)ImGuiCol.PlotHistogramHovered] = accentHi; + } + private void CreateUI(string glslVersion) { //IMGUI_CHECKVERSION(); @@ -446,39 +541,45 @@ private void CreateUI(string glslVersion) // } // - var fontPath = Path.Combine("data", "droid_sans.ttf"); - if (!File.Exists(fontPath)) + if (_context.uiScale != 1.0f) { - Logger.Information("ERROR: the Box2D samples working directory must be the top level Box2D directory (same as README.md)"); - //exit(EXIT_FAILURE); - return; + // ImGui.NET 1.90 does not expose AddFontDefaultVector, so use the existing font as an embedded resource. + using Stream stream = typeof(SampleApp).Assembly.GetManifestResourceStream("Box2D.NET.Samples.Fonts.droid_sans.ttf")!; + _fontData = new byte[stream.Length]; + stream.ReadExactly(_fontData); + _fontDataHandle = GCHandle.Alloc(_fontData, GCHandleType.Pinned); } // for windows : Microsoft Visual C++ Redistributable Package // link - https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist - var imGuiFontConfig = new ImGuiFontConfig(fontPath, 15, null); - _imgui = new ImGuiController(_context.gl, _window, _input, imGuiFontConfig); + _imgui = new ImGuiController(_context.gl, _window, _input, () => + { + ApplyUIStyle(); - ImGui.GetFontSize(); - ImGui.GetStyle().ScaleAllSizes(_context.uiScale); + ImGuiIOPtr io = ImGui.GetIO(); + if (_context.uiScale == 1.0f) + { + io.Fonts.AddFontDefault(); + } + else + { + unsafe + { + ImFontConfigPtr fontConfig = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()); + // This brightens the font, improving readability when it is small. + fontConfig.RasterizerMultiply = _context.uiScale * s_framebufferScale; + fontConfig.FontDataOwnedByAtlas = false; + + float regularSize = MathF.Floor(13.0f * _context.uiScale); + io.Fonts.AddFontFromMemoryTTF(_fontDataHandle.AddrOfPinnedObject(), _fontData.Length, regularSize, fontConfig); + ImGuiNative.ImFontConfig_destroy(fontConfig.NativePtr); + } + } + }); - unsafe + if (_context.uiScale != 1.0f) { - // ImFontConfigPtr fontConfig = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()); - // This brightens the font, improving readability when it is small. - // fontConfig.RasterizerMultiply = _context.uiScale * s_framebufferScale; - // - // float regularSize = MathF.Floor(13.0f * _context.uiScale); - // float mediumSize = MathF.Floor(40.0f * _context.uiScale); - // float largeSize = MathF.Floor(64.0f * _context.uiScale); - // - // var io = ImGui.GetIO(); - //_context.regularFont = io.Fonts.AddFontFromFileTTF(fontPath, regularSize); - //_context.regularFont = io.Fonts.AddFontFromFileTTF(fontPath, regularSize, fontConfig); - // _context.mediumFont = io.Fonts.AddFontFromFileTTF(fontPath, mediumSize, fontConfig); - // _context.largeFont = io.Fonts.AddFontFromFileTTF(fontPath, largeSize, fontConfig); - - //io.FontDefault = _context.regularFont; + ImGui.GetStyle().ScaleAllSizes(_context.uiScale); } } @@ -487,6 +588,11 @@ public void DestroyUI() var tmp = _imgui; _imgui = null; tmp.Dispose(); + if (_fontDataHandle.IsAllocated) + { + _fontDataHandle.Free(); + } + _fontData = null; } private unsafe void KeyCallback(WindowHandle* window, Keys key, int scancode, InputAction action, KeyModifiers mods) @@ -576,7 +682,15 @@ private unsafe void KeyCallback(WindowHandle* window, Keys key, int scancode, In break; case Keys.O: - _context.singleStep = true; + if (mods == KeyModifiers.Control) + { + _context.showUI = true; + _context.openSamplePicker = true; + } + else + { + _context.singleStep = true; + } break; case Keys.P: @@ -615,6 +729,10 @@ private unsafe void KeyCallback(WindowHandle* window, Keys key, int scancode, In _context.showUI = !_context.showUI; break; + case Keys.M: + _context.showDiagnostics = !_context.showDiagnostics; + break; + default: if (_context.sample != null) { diff --git a/src/Box2D.NET.Samples/SampleContext.cs b/src/Box2D.NET.Samples/SampleContext.cs index 4798fe17..dfd32457 100644 --- a/src/Box2D.NET.Samples/SampleContext.cs +++ b/src/Box2D.NET.Samples/SampleContext.cs @@ -7,12 +7,12 @@ using System.Runtime.CompilerServices; using Box2D.NET.Samples.Graphics; using Box2D.NET.Samples.Samples; -using ImGuiNET; using Silk.NET.GLFW; using Silk.NET.OpenGL; using static Box2D.NET.B2Types; using static Box2D.NET.B2Constants; using static Box2D.NET.Samples.Graphics.Backgrounds; +using static Box2D.NET.Samples.Graphics.Cameras; using static Box2D.NET.Samples.Graphics.Draws; namespace Box2D.NET.Samples; @@ -29,9 +29,6 @@ public class SampleContext public Sample sample = null; public B2Capacity capacity; public B2DebugDraw debugDraw; - public ImFontPtr regularFont; - public ImFontPtr mediumFont; - public ImFontPtr largeFont; public float uiScale = 1.0f; public float hertz = 60.0f; @@ -41,13 +38,16 @@ public class SampleContext public bool restart = false; public bool pause = false; public bool singleStep = false; - public bool drawCounters = false; - public bool drawProfile = false; public bool enableWarmStarting = true; public bool enableContinuous = true; public bool enableSleep = true; public bool showUI = true; - public bool frameTime = false; + + // Diagnostics drawer visibility. D toggles. + public bool showDiagnostics = false; + + // Set by Ctrl+O; consumed by UpdateSampleUI to open the fuzzy sample picker. + public bool openSamplePicker = false; // These are persisted public int sampleIndex = 0; @@ -61,7 +61,7 @@ private SampleContext(string signature, Glfw glfw) { Signature = signature; this.glfw = glfw; - camera = new Camera(); + camera = GetDefaultCamera(); draw = new Draw(); showUI = true; @@ -83,9 +83,6 @@ private SampleContext(string signature, Glfw glfw) debugDraw.context = this; - mediumFont = default; - largeFont = default; - regularFont = default; } public void Load() @@ -94,45 +91,11 @@ public void Load() var settings = Settings.Load(); - // - camera.width = settings.windowWidth; - camera.height = settings.windowHeight; - - // - uiScale = settings.uiScale; - hertz = settings.hertz; - subStepCount = settings.subStepCount; - workerCount = settings.workerCount; - // sampleIndex = settings.sampleIndex; debugDraw.drawShapes = settings.drawShapes; - debugDraw.drawChainNormals = settings.drawChainNormals; debugDraw.drawJoints = settings.drawJoints; - - // - debugDraw.drawShapes = settings.drawShapes; - debugDraw.drawChainNormals = settings.drawChainNormals; - debugDraw.drawJoints = settings.drawJoints; - debugDraw.drawJointExtras = settings.drawJointExtras; - debugDraw.drawBounds = settings.drawBounds; - debugDraw.drawMass = settings.drawMass; - debugDraw.drawGraphColors = settings.drawGraphColors; - debugDraw.drawContactNormals = settings.drawContactNormals; - debugDraw.drawContactForces = settings.drawContactForces; - debugDraw.drawContactFeatures = settings.drawContactFeatures; - debugDraw.drawFrictionForces = settings.drawFrictionForces; - debugDraw.drawIslands = settings.drawIslands; - drawCounters = settings.drawCounters; - drawProfile = settings.drawProfile; - frameTime = settings.frameTime; - enableWarmStarting = settings.enableWarmStarting; - enableContinuous = settings.enableContinuous; - enableSleep = settings.enableSleep; - - // - debugDraw.jointScale = settings.jointScale; - debugDraw.forceScale = settings.forceScale; + showDiagnostics = settings.showDiagnostics; } diff --git a/src/Box2D.NET.Samples/SampleText.cs b/src/Box2D.NET.Samples/SampleText.cs new file mode 100644 index 00000000..0a360d41 --- /dev/null +++ b/src/Box2D.NET.Samples/SampleText.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 Ikpil Choi(ikpil@naver.com) +// SPDX-License-Identifier: MIT + +using System.Globalization; + +namespace Box2D.NET.Samples; + +public static class SampleText +{ + public static string FormatFloat(float value) + { + if (float.IsNaN(value)) + { + return "nan"; + } + + if (float.IsPositiveInfinity(value)) + { + return "inf"; + } + + if (float.IsNegativeInfinity(value)) + { + return "-inf"; + } + + // C printf("%g") defaults to six significant digits and is not affected by the current .NET culture. + return value.ToString("g6", CultureInfo.InvariantCulture); + } +} diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs index c3b2ff71..864f761e 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkBarrel.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -302,16 +302,9 @@ void CreateScene() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 6.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(15.0f * fontSize, height)); - - ImGui.Begin("Benchmark: Barrel", ImGuiWindowFlags.NoResize); bool changed = false; string[] shapeTypes = ["Circle", "Capsule", "Mix", "Compound", "Human"]; @@ -333,6 +326,6 @@ public override void UpdateGui() CreateScene(); } - ImGui.End(); + } } diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCast.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCast.cs index 90824d54..b6f094e5 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCast.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCast.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -321,18 +321,11 @@ public override void Step() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 17.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(13.0f * fontSize, height)); - ImGui.Begin("Cast", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - - ImGui.PushItemWidth(7.5f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; @@ -378,6 +371,8 @@ public override void UpdateGui() changed = true; } + ImGui.PopItemWidth(); + if (ImGui.Checkbox("top down", ref m_topDown)) { changed = true; @@ -388,9 +383,6 @@ public override void UpdateGui() m_drawIndex = (m_drawIndex + 1) % m_origins.Count; } - ImGui.PopItemWidth(); - ImGui.End(); - if (changed) { BuildScene(); @@ -404,7 +396,7 @@ public override void Draw() DrawTextLine($"build time ms = {m_buildTime:g}"); - DrawTextLine($"hit count = {hitCount}, node visits = {nodeVisits}, leaf visits = {leafVisits}"); + DrawScreenTextLine($"hit count = {hitCount}, node visits = {nodeVisits}, leaf visits = {leafVisits}"); DrawTextLine($"total ms = {ms:F3}"); diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs index 37158e72..c22400bb 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -10,6 +10,7 @@ using static Box2D.NET.B2Worlds; using static Box2D.NET.B2Timers; using static Box2D.NET.B2Diagnostics; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples.Benchmarks; @@ -138,11 +139,11 @@ public override void Draw() { base.Draw(); - DrawTextLine($"total: create = {m_createTime} ms, destroy = {m_destroyTime} ms"); + DrawScreenTextLine($"total: create = {FormatFloat(m_createTime)} ms, destroy = {FormatFloat(m_destroyTime)} ms"); float createPerBody = 1000.0f * m_createTime / m_iterations / m_bodyCount; float destroyPerBody = 1000.0f * m_destroyTime / m_iterations / m_bodyCount; - DrawTextLine($"body: create = {createPerBody} us, destroy = {destroyPerBody} us"); + DrawScreenTextLine($"body: create = {FormatFloat(createPerBody)} us, destroy = {FormatFloat(destroyPerBody)} us"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkManyTumblers.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkManyTumblers.cs index e79e3efc..9cf6433d 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkManyTumblers.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkManyTumblers.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -138,16 +138,10 @@ void CreateScene() m_bodyIndex = 0; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 8.5f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(15.5f * fontSize, height)); - ImGui.Begin("Benchmark: Many Tumblers", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(8.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; changed = changed || ImGui.SliderInt("Row Count", ref m_rowCount, 1, 32); @@ -168,7 +162,7 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs index d34dce2e..5e019c2c 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSensor.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -240,7 +240,7 @@ public override void Draw() { base.Draw(); - DrawTextLine($"max begin touch events = {m_maxBeginCount}"); - DrawTextLine($"max end touch events = {m_maxEndCount}"); + DrawScreenTextLine($"max begin touch events = {m_maxBeginCount}"); + DrawScreenTextLine($"max end touch events = {m_maxEndCount}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs index d24b56c3..bd297931 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -11,6 +11,7 @@ using static Box2D.NET.B2Distances; using static Box2D.NET.B2Timers; using static Box2D.NET.Samples.Graphics.Draws; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples.Benchmarks; @@ -97,17 +98,11 @@ public override void Dispose() m_outputs = null; } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 5.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(17.0f * fontSize, height)); - ImGui.Begin("Benchmark: Shape Distance", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderInt("draw index", ref m_drawIndex, 0, m_count - 1); - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -145,7 +140,7 @@ public override void Draw() if (m_context.pause == false || m_context.singleStep == true) { DrawTextLine($"count = {m_count}"); - DrawTextLine($"min ms = {m_minMilliseconds}, ave us = {1000.0f * m_minMilliseconds / (float)m_count}"); + DrawScreenTextLine($"min ms = {FormatFloat(m_minMilliseconds)}, ave us = {FormatFloat(1000.0f * m_minMilliseconds / (float)m_count)}"); DrawTextLine($"average iterations = {totalIterations / (float)m_count}"); } diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSmash.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSmash.cs index 9d1979cf..d7fc6ef5 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSmash.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkSmash.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -54,14 +54,9 @@ private void CreateScene() } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float height = 110.0f; - ImGui.SetNextWindowPos(new Vector2(10.0f, m_camera.height - height - 50.0f), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(220.0f, height)); - ImGui.Begin("Benchmark: Smash", ImGuiWindowFlags.NoResize); bool changed = false; if (ImGui.SliderInt("rows", ref m_rowCount, 1, MaxRowCount, "%d")) @@ -84,6 +79,6 @@ public override void UpdateGui() CreateScene(); } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Bodies/BadBody.cs b/src/Box2D.NET.Samples/Samples/Bodies/BadBody.cs index b7ce49a1..ddd08ac8 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/BadBody.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/BadBody.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -86,9 +86,9 @@ public override void Draw() { base.Draw(); - DrawTextLine("A bad body is a dynamic body with no mass and behaves like a kinematic body."); + DrawScreenTextLine("A bad body is a dynamic body with no mass and behaves like a kinematic body."); - DrawTextLine("Bad bodies are considered invalid and a user bug. Behavior is not guaranteed."); + DrawScreenTextLine("Bad bodies are considered invalid and a user bug. Behavior is not guaranteed."); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Bodies/BodyType.cs b/src/Box2D.NET.Samples/Samples/Bodies/BodyType.cs index 09351df1..a23475ba 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/BodyType.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/BodyType.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -220,15 +220,9 @@ public BodyType(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 11.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(9.0f * fontSize, height)); - ImGui.Begin("Body Type", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.RadioButton("Static", m_type == B2BodyType.b2_staticBody)) { @@ -281,7 +275,7 @@ public override void UpdateGui() } } - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs b/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs index 2037e686..533331a6 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -8,6 +8,7 @@ using static Box2D.NET.B2MathFunction; using static Box2D.NET.B2Bodies; using static Box2D.NET.B2Shapes; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples.Bodies; @@ -76,7 +77,7 @@ public override void Draw() B2Vec2 r = b2Body_GetWorldVector(m_bodyId, new B2Vec2(0.0f, -m_lever)); B2Vec2 vp = v + b2CrossSV(omega, r); - DrawTextLine($"pivot velocity = ({vp.X:g}, {vp.Y:g})"); + DrawScreenTextLine($"pivot velocity = ({FormatFloat(vp.X)}, {FormatFloat(vp.Y)})"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Bodies/Sleep.cs b/src/Box2D.NET.Samples/Samples/Bodies/Sleep.cs index 531fc8e0..a259c261 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/Sleep.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/Sleep.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -171,17 +171,11 @@ void ToggleInvoker() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 160.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - ImGui.Begin("Sleep", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(120.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.Text("Pendulum Tuning"); @@ -217,7 +211,7 @@ public override void UpdateGui() } } - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Bodies/WakeTouching.cs b/src/Box2D.NET.Samples/Samples/Bodies/WakeTouching.cs index aa512ce2..7f0170d1 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/WakeTouching.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/WakeTouching.cs @@ -64,20 +64,15 @@ public WakeTouching(SampleContext context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 5.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(10.0f * fontSize, height)); - ImGui.Begin("Wake Touching", ImGuiWindowFlags.NoResize); if (ImGui.Button("Wake Touching")) { b2Body_WakeTouching(m_groundId); } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Bodies/Weeble.cs b/src/Box2D.NET.Samples/Samples/Bodies/Weeble.cs index 01728325..2116f325 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/Weeble.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/Weeble.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Erin Catto +// SPDX-FileCopyrightText: 2022 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -91,15 +91,9 @@ static float RestitutionCallback(float restitutionA, ulong materialA, float rest } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 120.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(200.0f, height)); - ImGui.Begin("Weeble", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.Button("Teleport")) { b2Body_SetTransform(m_weebleId, new B2Vec2(0.0f, 5.0f), b2MakeRot(0.95f * B2_PI)); @@ -115,12 +109,12 @@ public override void UpdateGui() b2World_Explode(m_worldId, def); } - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("Magnitude", ref m_explosionMagnitude, -100.0f, 100.0f, "%.1f"); ImGui.PopItemWidth(); - ImGui.End(); + } public override void Draw() diff --git a/src/Box2D.NET.Samples/Samples/Characters/Mover.cs b/src/Box2D.NET.Samples/Samples/Characters/Mover.cs index 157fed95..39f0e56c 100644 --- a/src/Box2D.NET.Samples/Samples/Characters/Mover.cs +++ b/src/Box2D.NET.Samples/Samples/Characters/Mover.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Erin Catto +// SPDX-FileCopyrightText: 2022 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -459,16 +459,12 @@ void SolveMove(float timeStep, float throttle) m_velocity = b2ClipVector(m_velocity, m_planes, m_planeCount); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 350.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 25.0f), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(340.0f, height)); + ImGui.TextUnformatted("Mover"); + ImGui.Spacing(); - ImGui.Begin("Mover", 0); - - ImGui.PushItemWidth(240.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("Jump Speed", ref m_jumpSpeed, 0.0f, 40.0f, "%.0f"); ImGui.SliderFloat("Min Speed", ref m_minSpeed, 0.0f, 1.0f, "%.2f"); @@ -485,7 +481,7 @@ public override void UpdateGui() ImGui.Separator(); - ImGui.Text("Pogo Shape"); + ImGui.TextUnformatted("Pogo Shape"); ImGui.RadioButton("Point", ref m_pogoShape, (int)PogoShape.PogoPoint); ImGui.SameLine(); ImGui.RadioButton("Circle", ref m_pogoShape, (int)PogoShape.PogoCircle); @@ -493,8 +489,6 @@ public override void UpdateGui() ImGui.RadioButton("Segment", ref m_pogoShape, (int)PogoShape.PogoSegment); ImGui.Checkbox("Lock Camera", ref m_lockCamera); - - ImGui.End(); } static bool PlaneResultFcn(B2ShapeId shapeId, ref B2PlaneResult planeResult, object context) diff --git a/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs b/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs index 96e4a347..3d243cf1 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/CastWorld.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -297,21 +297,15 @@ public override void MouseMove(B2Vec2 p) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 320.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(200.0f, height)); - - ImGui.Begin("Ray-cast World", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); ImGui.Checkbox("Simple", ref m_simple); if (m_simple == false) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] castTypes = ["Ray", "Circle", "Capsule", "Polygon"]; int castType = (int)m_castType; if (ImGui.Combo("Type", ref castType, castTypes, castTypes.Length)) @@ -330,6 +324,7 @@ public override void UpdateGui() { m_mode = mode; } + ImGui.PopItemWidth(); } if (ImGui.Button("Polygon")) @@ -373,8 +368,6 @@ public override void UpdateGui() { DestroyBody(); } - - ImGui.End(); } public override void Step() @@ -513,31 +506,31 @@ public override void Draw() { base.Draw(); - DrawTextLine("Click left mouse button and drag to modify ray cast"); - DrawTextLine("Shape 7 is intentionally ignored by the ray"); + DrawScreenTextLine("Click left mouse button and drag to modify ray cast"); + DrawScreenTextLine("Shape 7 is intentionally ignored by the ray"); if (m_simple) { - DrawTextLine("Simple closest point ray cast"); + DrawScreenTextLine("Simple closest point ray cast"); } else { switch ((Mode)m_mode) { case Mode.e_any: - DrawTextLine("Cast mode: any - check for obstruction - unsorted"); + DrawScreenTextLine("Cast mode: any - check for obstruction - unsorted"); break; case Mode.e_closest: - DrawTextLine("Cast mode: closest - find closest shape along the cast"); + DrawScreenTextLine("Cast mode: closest - find closest shape along the cast"); break; case Mode.e_multiple: - DrawTextLine("Cast mode: multiple - gather up to 3 shapes - unsorted"); + DrawScreenTextLine("Cast mode: multiple - gather up to 3 shapes - unsorted"); break; case Mode.e_sorted: - DrawTextLine("Cast mode: sorted - gather up to 3 shapes sorted by closeness"); + DrawScreenTextLine("Cast mode: sorted - gather up to 3 shapes sorted by closeness"); break; default: diff --git a/src/Box2D.NET.Samples/Samples/Collisions/DynamicTree.cs b/src/Box2D.NET.Samples/Samples/Collisions/DynamicTree.cs index ca7598b6..14dc28ef 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/DynamicTree.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/DynamicTree.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -188,18 +188,11 @@ void BuildTree() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 320.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(200.0f, height)); - ImGui.Begin("Dynamic Tree", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; if (ImGui.SliderInt("rows", ref m_rowCount, 0, 1000, "%d")) @@ -235,6 +228,8 @@ public override void UpdateGui() { } + ImGui.PopItemWidth(); + if (ImGui.RadioButton("Incremental", m_updateType == (int)UpdateType.Update_Incremental)) { m_updateType = (int)UpdateType.Update_Incremental; @@ -258,9 +253,6 @@ public override void UpdateGui() ImGui.Text("mouse button 1: ray cast"); ImGui.Text("mouse button 1 + shift: query"); - ImGui.PopItemWidth(); - ImGui.End(); - if (changed) { BuildTree(); @@ -449,7 +441,7 @@ public override void Draw() DrawPoint(m_draw, m_startPoint, 5.0f, B2HexColor.b2_colorGreen); DrawPoint(m_draw, m_endPoint, 5.0f, B2HexColor.b2_colorRed); - DrawTextLine($"node visits = {result.nodeVisits}, leaf visits = {result.leafVisits}"); + DrawScreenTextLine($"node visits = {result.nodeVisits}, leaf visits = {result.leafVisits}"); } switch ((UpdateType)m_updateType) @@ -481,6 +473,6 @@ public override void Draw() float areaRatio = b2DynamicTree_GetAreaRatio(m_tree); int hmin = (int)(MathF.Ceiling(MathF.Log((float)m_proxyCount) / MathF.Log(2.0f) - 1.0f)); - DrawTextLine($"proxies = {m_proxyCount}, height = {height}, hmin = {hmin}, area ratio = {areaRatio:F1}"); + DrawScreenTextLine($"proxies = {m_proxyCount}, height = {height}, hmin = {hmin}, area ratio = {areaRatio:F1}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/Manifold.cs b/src/Box2D.NET.Samples/Samples/Collisions/Manifold.cs index 63bfde49..d790d7f2 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/Manifold.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/Manifold.cs @@ -86,18 +86,11 @@ public Manifold(SampleContext context) : base(context) m_wedge = b2ComputeHull(points, 3); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 24.0f * fontSize; - ImGui.SetNextWindowPos( new Vector2( 0.5f * fontSize, m_camera.height - height - 2.0f * fontSize ), ImGuiCond.Once ); - ImGui.SetNextWindowSize( new Vector2( 20.0f * fontSize, height ) ); - ImGui.Begin( "Manifold", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize ); - - ImGui.PushItemWidth( 14.0f * fontSize ); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("x offset", ref m_transform.p.X, -2.0f, 2.0f, "%.2f"); ImGui.SliderFloat("y offset", ref m_transform.p.Y, -2.0f, 2.0f, "%.2f"); @@ -131,7 +124,7 @@ public override void UpdateGui() ImGui.Text("mouse button 1: drag"); ImGui.Text("mouse button 1 + shift: rotate"); - ImGui.End(); + } public override void MouseDown(B2Vec2 p, MouseButton button, KeyModifiers mods) diff --git a/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs b/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs index 92978fc3..fb7d6e45 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/OverlapWorld.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -266,16 +266,9 @@ public override void MouseMove(B2Vec2 p) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 330.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(140.0f, height)); - - ImGui.Begin("Overlap World", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.Button("Polygon 1")) Create(0); @@ -330,7 +323,7 @@ public override void UpdateGui() ImGui.RadioButton("Capsule##Overlap", ref m_shapeType, e_capsuleShape); ImGui.RadioButton("Box##Overlap", ref m_shapeType, e_boxShape); - ImGui.End(); + } public override void Step() @@ -392,9 +385,9 @@ public override void Draw() { base.Draw(); - DrawTextLine("left mouse button: drag query shape"); + DrawScreenTextLine("left mouse button: drag query shape"); - DrawTextLine("left mouse button + shift: rotate query shape"); + DrawScreenTextLine("left mouse button + shift: rotate query shape"); if (B2_IS_NON_NULL(m_bodyIds[m_ignoreIndex])) @@ -404,4 +397,4 @@ public override void Draw() DrawWorldString(m_draw, m_camera, p, B2HexColor.b2_colorWhite, "skip"); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/RayCast.cs b/src/Box2D.NET.Samples/Samples/Collisions/RayCast.cs index 6e0d85e5..5527b5cb 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/RayCast.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/RayCast.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -80,18 +80,11 @@ public RayCast(SampleContext context) : base(context) m_showFraction = false; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 230.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(200.0f, height)); - ImGui.Begin("Ray-cast", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("x offset", ref m_transform.p.X, -2.0f, 2.0f, "%.2f"); ImGui.SliderFloat("y offset", ref m_transform.p.Y, -2.0f, 2.0f, "%.2f"); @@ -105,6 +98,8 @@ public override void UpdateGui() //{ // } + ImGui.PopItemWidth(); + ImGui.Checkbox("show fraction", ref m_showFraction); if (ImGui.Button("Reset")) @@ -119,9 +114,6 @@ public override void UpdateGui() ImGui.Text("mouse btn 1 + shft: translate"); ImGui.Text("mouse btn 1 + ctrl: rotate"); - ImGui.PopItemWidth(); - - ImGui.End(); } public override void MouseDown(B2Vec2 p, MouseButton button, KeyModifiers mods) @@ -340,4 +332,4 @@ public override void Step() DrawRay(ref output); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs b/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs index 0f5eb93e..00234fa7 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -11,6 +11,7 @@ using static Box2D.NET.B2Geometries; using static Box2D.NET.B2Diagnostics; using static Box2D.NET.Samples.Graphics.Draws; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples.Collisions; @@ -297,15 +298,9 @@ public override void MouseMove(B2Vec2 p) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 300.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Shape Distance", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = { "point", "segment", "triangle", "box" }; int shapeType = (int)m_typeA; if (ImGui.Combo("shape A", ref shapeType, shapeTypes, shapeTypes.Length)) @@ -341,12 +336,12 @@ public override void UpdateGui() m_transform.q = b2MakeRot(m_angle); } + ImGui.PopItemWidth(); + ImGui.Separator(); ImGui.Checkbox("show indices", ref m_showIndices); ImGui.Checkbox("encroach", ref m_encroach); - - ImGui.End(); } public override void Step() @@ -387,7 +382,7 @@ public override void Draw() { base.Draw(); - DrawTextLine($"hit = {output.hit}, iterations = {output.iterations}, fraction = {output.fraction}, distance = {_distanceOutput.distance}"); + DrawScreenTextLine($"hit = {(output.hit ? "true" : "false")}, iterations = {output.iterations}, fraction = {FormatFloat(output.fraction)}, distance = {FormatFloat(_distanceOutput.distance)}"); DrawShape(m_typeA, b2Transform_identity, m_radiusA, B2HexColor.b2_colorCyan); DrawShape(m_typeB, m_transform, m_radiusB, B2HexColor.b2_colorLightGreen); @@ -425,9 +420,9 @@ public override void Draw() } } - DrawTextLine("mouse button 1: drag"); - DrawTextLine("mouse button 1 + shift: rotate"); - DrawTextLine("mouse button 1 + control: sweep"); - DrawTextLine($"distance = {_distanceOutput.distance:F2}, iterations = {output.iterations}"); + DrawScreenTextLine("mouse button 1: drag"); + DrawScreenTextLine("mouse button 1 + shift: rotate"); + DrawScreenTextLine("mouse button 1 + control: sweep"); + DrawScreenTextLine($"distance = {_distanceOutput.distance:F2}, iterations = {output.iterations}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs b/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs index c80e0df8..55b4d0f0 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -212,17 +212,9 @@ void DrawShape(ShapeType type, in B2Transform transform, float radius, B2HexColo } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 21.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(19.0f * fontSize, height)); - - ImGui.Begin("Shape Distance", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = ["point", "segment", "triangle", "box"]; int shapeType = (int)m_typeA; if (ImGui.Combo("shape A", ref shapeType, shapeTypes, shapeTypes.Length)) @@ -258,6 +250,8 @@ public override void UpdateGui() m_transform.q = b2MakeRot(m_angle); } + ImGui.PopItemWidth(); + ImGui.Separator(); ImGui.Checkbox("show indices", ref m_showIndices); @@ -272,11 +266,11 @@ public override void UpdateGui() if (m_drawSimplex && m_simplexCount > 0) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderInt("index", ref m_simplexIndex, 0, m_simplexCount - 1); m_simplexIndex = b2ClampInt(m_simplexIndex, 0, m_simplexCount - 1); + ImGui.PopItemWidth(); } - - ImGui.End(); } public override void MouseDown(B2Vec2 p, MouseButton button, KeyModifiers mods) @@ -442,24 +436,24 @@ public override void Draw() } } - DrawTextLine("mouse button 1: drag"); + DrawScreenTextLine("mouse button 1: drag"); - DrawTextLine("mouse button 1 + shift: rotate"); + DrawScreenTextLine("mouse button 1 + shift: rotate"); - DrawTextLine($"distance = {_outputDistance:F2}, iterations = {_outputIterations}"); + DrawScreenTextLine($"distance = {_outputDistance:F2}, iterations = {_outputIterations}"); if (m_cache.count == 1) { - DrawTextLine($"cache = {m_cache.indexA[0]}, {m_cache.indexB[0]}"); + DrawTextLine($"cache = {{{m_cache.indexA[0]}}}, {{{m_cache.indexB[0]}}}"); } else if (m_cache.count == 2) { - DrawTextLine($"cache = {m_cache.indexA[0]}, {m_cache.indexA[1]}, {m_cache.indexB[0]}, {m_cache.indexB[1]}"); + DrawTextLine($"cache = {{{m_cache.indexA[0]}, {m_cache.indexA[1]}}}, {{{m_cache.indexB[0]}, {m_cache.indexB[1]}}}"); } else if (m_cache.count == 3) { - DrawTextLine($"cache = {m_cache.indexA[0]}, {m_cache.indexA[1]}, {m_cache.indexA[2]}, {m_cache.indexB[0]}, {m_cache.indexB[1]}, {m_cache.indexB[2]}"); + DrawScreenTextLine($"cache = {{{m_cache.indexA[0]}, {m_cache.indexA[1]}, {m_cache.indexA[2]}}}, {{{m_cache.indexB[0]}, {m_cache.indexB[1]}, {m_cache.indexB[2]}}}"); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/SmoothManifold.cs b/src/Box2D.NET.Samples/Samples/Collisions/SmoothManifold.cs index acf90c38..482d6c5b 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/SmoothManifold.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/SmoothManifold.cs @@ -133,17 +133,10 @@ public SmoothManifold(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 290.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(180.0f, height)); - - ImGui.Begin("Smooth Manifold", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); { string[] shapeTypes = { "Circle", "Box" }; @@ -161,6 +154,9 @@ public override void UpdateGui() } ImGui.SliderFloat("Round", ref m_round, 0.0f, 0.4f, "%.1f"); + + ImGui.PopItemWidth(); + ImGui.Checkbox("Show Ids", ref m_showIds); ImGui.Checkbox("Show Separation", ref m_showSeparation); ImGui.Checkbox("Show Anchors", ref m_showAnchors); @@ -176,8 +172,6 @@ public override void UpdateGui() ImGui.Text("mouse button 1: drag"); ImGui.Text("mouse button 1 + shift: rotate"); - ImGui.PopItemWidth(); - ImGui.End(); } public override void MouseDown(B2Vec2 p, MouseButton button, KeyModifiers mods) @@ -305,4 +299,4 @@ public override void Draw() } } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Collisions/TimeOfImpact.cs b/src/Box2D.NET.Samples/Samples/Collisions/TimeOfImpact.cs index 8d9f0bd5..6eeb8042 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/TimeOfImpact.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/TimeOfImpact.cs @@ -76,8 +76,6 @@ public override void Draw() DrawTextLine($"toi = {_output.fraction:g}"); - // DrawTextLine("max toi iters = %d, max root iters = %d", b2_toiMaxIters, b2_toiMaxRootIters); - B2Vec2[] vertices = new B2Vec2[B2_MAX_POLYGON_VERTICES]; // Draw A @@ -144,4 +142,4 @@ public override void Draw() } #endif } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Continuous/BounceHouse.cs b/src/Box2D.NET.Samples/Samples/Continuous/BounceHouse.cs index b7451c20..87caffa2 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/BounceHouse.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/BounceHouse.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -125,17 +125,9 @@ void Launch() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Bounce House", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = { "Circle", "Capsule", "Box" }; int shapeType = (int)m_shapeType; if (ImGui.Combo("Shape", ref shapeType, shapeTypes, shapeTypes.Length)) @@ -143,13 +135,14 @@ public override void UpdateGui() m_shapeType = (ShapeType)shapeType; Launch(); } + ImGui.PopItemWidth(); if (ImGui.Checkbox("hit events", ref m_enableHitEvents)) { b2Body_EnableHitEvents(m_bodyId, m_enableHitEvents); } - ImGui.End(); + } public override void Step() @@ -196,4 +189,4 @@ public override void Draw() } } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Continuous/ChainDrop.cs b/src/Box2D.NET.Samples/Samples/Continuous/ChainDrop.cs index 3730d27e..2535acee 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/ChainDrop.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/ChainDrop.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -85,25 +85,18 @@ void Launch() //m_shapeId = b2CreatePolygonShape( m_bodyId, &shapeDef, &box ); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 140.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Chain Drop", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("Speed", ref m_speed, -100.0f, 0.0f, "%.0f"); ImGui.SliderFloat("Y Offset", ref m_yOffset, -1.0f, 1.0f, "%.1f"); + ImGui.PopItemWidth(); if (ImGui.Button("Launch")) { Launch(); } - ImGui.End(); + } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Continuous/GhostBumps.cs b/src/Box2D.NET.Samples/Samples/Continuous/GhostBumps.cs index 96e7afba..31511ecf 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/GhostBumps.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/GhostBumps.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -267,17 +267,10 @@ void Launch() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 140.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(180.0f, height)); - - ImGui.Begin("Ghost Bumps", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.Checkbox("Chain", ref m_useChain)) { @@ -320,6 +313,6 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Continuous/PixelImperfect.cs b/src/Box2D.NET.Samples/Samples/Continuous/PixelImperfect.cs index f5a2c56a..4370301f 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/PixelImperfect.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/PixelImperfect.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -76,6 +76,6 @@ public override void Draw() B2Vec2 p = b2Body_GetPosition(m_ballId); B2Vec2 v = b2Body_GetLinearVelocity(m_ballId); - DrawTextLine($"p.x = {p.X:F9}, v.y = {v.Y:F9}"); + DrawScreenTextLine($"p.x = {p.X:F9}, v.y = {v.Y:F9}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Continuous/RestitutionThreshold.cs b/src/Box2D.NET.Samples/Samples/Continuous/RestitutionThreshold.cs index 5155eb16..f2f5dd84 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/RestitutionThreshold.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/RestitutionThreshold.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -81,6 +81,6 @@ public override void Draw() B2Vec2 p = b2Body_GetPosition(m_ballId); B2Vec2 v = b2Body_GetLinearVelocity(m_ballId); - DrawTextLine($"p.x = {p.X:F9}, v.y = {v.Y:F9}"); + DrawScreenTextLine($"p.x = {p.X:F9}, v.y = {v.Y:F9}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Continuous/SkinnyBox.cs b/src/Box2D.NET.Samples/Samples/Continuous/SkinnyBox.cs index 1df29174..374c0def 100644 --- a/src/Box2D.NET.Samples/Samples/Continuous/SkinnyBox.cs +++ b/src/Box2D.NET.Samples/Samples/Continuous/SkinnyBox.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -111,16 +111,9 @@ void Launch() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 110.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(140.0f, height)); - - ImGui.Begin("Skinny Box", ImGuiWindowFlags.NoResize); ImGui.Checkbox("Capsule", ref m_capsule); @@ -131,7 +124,7 @@ public override void UpdateGui() ImGui.Checkbox("Auto Test", ref m_autoTest); - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Determinisms/FallingHinges.cs b/src/Box2D.NET.Samples/Samples/Determinisms/FallingHinges.cs index 464c9fca..5192ef01 100644 --- a/src/Box2D.NET.Samples/Samples/Determinisms/FallingHinges.cs +++ b/src/Box2D.NET.Samples/Samples/Determinisms/FallingHinges.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -60,7 +60,7 @@ public override void Draw() if (m_done) { - DrawTextLine($"sleep step = {m_data.sleepStep}, hash = 0x{m_data.hash:X8}"); + DrawScreenTextLine($"sleep step = {m_data.sleepStep}, hash = 0x{m_data.hash:X8}"); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs b/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs index 8d053bc4..29036e15 100644 --- a/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs +++ b/src/Box2D.NET.Samples/Samples/Events/BodyMove.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -177,16 +177,9 @@ public override void Step() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Body Move", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.Button("Explode")) { @@ -198,9 +191,9 @@ public override void UpdateGui() b2World_Explode(m_worldId, def); } + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("Magnitude", ref m_explosionMagnitude, -20.0f, 20.0f, "%.1f"); - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Draw() @@ -211,4 +204,4 @@ public override void Draw() DrawTextLine($"sleep count: {m_sleepCount}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs b/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs index 9ab8abe3..218a255c 100644 --- a/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs +++ b/src/Box2D.NET.Samples/Samples/Events/ContactEvent.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -144,20 +144,11 @@ void SpawnDebris() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 60.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Contact Event", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("force", ref m_force, 100.0f, 500.0f, "%.1f"); - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -435,6 +426,6 @@ public override void Draw() { base.Draw(); - DrawTextLine("move using WASD"); + DrawScreenTextLine("move using WASD"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Events/Platform.cs b/src/Box2D.NET.Samples/Samples/Events/Platform.cs index c772f269..988db39f 100644 --- a/src/Box2D.NET.Samples/Samples/Events/Platform.cs +++ b/src/Box2D.NET.Samples/Samples/Events/Platform.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -154,21 +154,12 @@ public bool PreSolve(B2ShapeId shapeIdA, B2ShapeId shapeIdB, B2Vec2 point, B2Vec return false; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("One-Sided Platform", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("force", ref m_force, 0.0f, 50.0f, "%.1f"); ImGui.SliderFloat("impulse", ref m_impulse, 0.0f, 50.0f, "%.1f"); - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -256,13 +247,13 @@ public override void Draw() { Span contactData = stackalloc B2ContactData[1]; int contactCount = b2Body_GetContactData(m_movingPlatformId, contactData, contactData.Length); - DrawTextLine($"Platform contact count = {contactCount}, point count = {contactData[0].manifold.pointCount}"); + DrawScreenTextLine($"Platform contact count = {contactCount}, point count = {contactData[0].manifold.pointCount}"); } - DrawTextLine("Movement: A/D/Space"); + DrawScreenTextLine("Movement: A/D/Space"); DrawTextLine($"Can jump = {m_canJump}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Events/ProjectileEvent.cs b/src/Box2D.NET.Samples/Samples/Events/ProjectileEvent.cs index 83d363ae..030ade4b 100644 --- a/src/Box2D.NET.Samples/Samples/Events/ProjectileEvent.cs +++ b/src/Box2D.NET.Samples/Samples/Events/ProjectileEvent.cs @@ -176,7 +176,7 @@ public override void Draw() { base.Draw(); - DrawTextLine("Use Ctrl + Left Mouse to drag and shoot a projectile"); + DrawScreenTextLine("Use Ctrl + Left Mouse to drag and shoot a projectile"); if (m_dragging) { @@ -185,4 +185,4 @@ public override void Draw() DrawPoint(m_draw, m_point2, 5.0f, B2HexColor.b2_colorRed); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Events/SensorBookend.cs b/src/Box2D.NET.Samples/Samples/Events/SensorBookend.cs index ced6844e..c3816d27 100644 --- a/src/Box2D.NET.Samples/Samples/Events/SensorBookend.cs +++ b/src/Box2D.NET.Samples/Samples/Events/SensorBookend.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -120,14 +120,9 @@ void CreateVisitor() m_visitorShapeId = b2CreateCircleShape(m_visitorBodyId, shapeDef, circle); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 19.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(12.0f * fontSize, height)); - ImGui.Begin("Sensor Bookend", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (B2_IS_NULL(m_visitorBodyId)) { @@ -247,7 +242,7 @@ public override void UpdateGui() } } - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs b/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs index 0fbf3dfe..abccd2ec 100644 --- a/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs +++ b/src/Box2D.NET.Samples/Samples/Events/SensorFunnel.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -261,17 +261,9 @@ void Clear() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 90.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), - ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(140.0f, height)); - - ImGui.Begin("Sensor Event", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.RadioButton("donut", m_type == (int)e_donut)) { @@ -285,7 +277,7 @@ public override void UpdateGui() m_type = (int)e_human; } - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Events/SensorHits.cs b/src/Box2D.NET.Samples/Samples/Events/SensorHits.cs index ca227220..90877243 100644 --- a/src/Box2D.NET.Samples/Samples/Events/SensorHits.cs +++ b/src/Box2D.NET.Samples/Samples/Events/SensorHits.cs @@ -170,14 +170,9 @@ void Launch() m_shapeId = b2CreateCircleShape(m_bodyId, shapeDef, circle); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 120.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(120.0f, height)); - ImGui.Begin("Sensor Hit", ImGuiWindowFlags.NoResize); ImGui.Checkbox("Bullet", ref m_isBullet); @@ -186,7 +181,7 @@ public override void UpdateGui() Launch(); } - ImGui.End(); + } void CollectTransforms(B2ShapeId sensorShapeId) diff --git a/src/Box2D.NET.Samples/Samples/Events/SensorTypes.cs b/src/Box2D.NET.Samples/Samples/Events/SensorTypes.cs index 67b4e7e2..b2553214 100644 --- a/src/Box2D.NET.Samples/Samples/Events/SensorTypes.cs +++ b/src/Box2D.NET.Samples/Samples/Events/SensorTypes.cs @@ -180,7 +180,7 @@ void PrintOverlaps(B2ShapeId sensorShapeId, string prefix) start += entryByteCount; } - DrawTextLine(builder.ToString()); + DrawScreenTextLine(builder.ToString()); } private static int AppendUtf8(StringBuilder builder, string text, int maxByteCount) diff --git a/src/Box2D.NET.Samples/Samples/Geometries/ConvexHull.cs b/src/Box2D.NET.Samples/Samples/Geometries/ConvexHull.cs index 6e4980fe..a2611882 100644 --- a/src/Box2D.NET.Samples/Samples/Geometries/ConvexHull.cs +++ b/src/Box2D.NET.Samples/Samples/Geometries/ConvexHull.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -191,7 +191,7 @@ public override void Draw() { base.Draw(); - DrawTextLine("Options: generate(g), auto(a), bulk(b)"); + DrawScreenTextLine("Options: generate(g), auto(a), bulk(b)"); if (m_valid == false) @@ -201,7 +201,7 @@ public override void Draw() } else { - DrawTextLine($"generation = {m_generation}, count = {m_hull.count}"); + DrawScreenTextLine($"generation = {m_generation}, count = {m_hull.count}"); } @@ -229,4 +229,4 @@ public override void Draw() DrawPoint(m_draw, m_hull.points[i], 6.0f, B2HexColor.b2_colorGreen); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Issues/Crash01.cs b/src/Box2D.NET.Samples/Samples/Issues/Crash01.cs index d22d663c..f114285e 100644 --- a/src/Box2D.NET.Samples/Samples/Issues/Crash01.cs +++ b/src/Box2D.NET.Samples/Samples/Issues/Crash01.cs @@ -106,13 +106,9 @@ public Crash01(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 11.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(9.0f * fontSize, height)); - ImGui.Begin("Crash 01", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); + if (ImGui.RadioButton("Static", m_type == B2BodyType.b2_staticBody)) { @@ -145,6 +141,6 @@ public override void UpdateGui() } } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Issues/DisableCrash.cs b/src/Box2D.NET.Samples/Samples/Issues/DisableCrash.cs index 14d67343..dc8c2501 100644 --- a/src/Box2D.NET.Samples/Samples/Issues/DisableCrash.cs +++ b/src/Box2D.NET.Samples/Samples/Issues/DisableCrash.cs @@ -73,15 +73,9 @@ public DisableCrash(SampleContext context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 11.0f * fontSize; - float winX = 0.5f * fontSize; - float winY = m_camera.height - height - 2.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(winX, winY), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(9.0f * fontSize, height)); - ImGui.Begin("Disable Crash", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); + if (ImGui.Checkbox("Enable", ref m_isEnabled)) { @@ -95,6 +89,6 @@ public override void UpdateGui() } } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Joints/BallAndChain.cs b/src/Box2D.NET.Samples/Samples/Joints/BallAndChain.cs index ceae195f..37f556b0 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/BallAndChain.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/BallAndChain.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -107,18 +107,11 @@ public BallAndChain(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 60.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Ball and Chain", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool updateFriction = ImGui.SliderFloat("Joint Friction", ref m_frictionTorque, 0.0f, 1000.0f, "%2.f"); + ImGui.PopItemWidth(); if (updateFriction) { for (int i = 0; i <= m_count; ++i) @@ -126,7 +119,5 @@ public override void UpdateGui() b2RevoluteJoint_SetMaxMotorTorque(m_jointIds[i], m_frictionTorque); } } - - ImGui.End(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/BreakableJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/BreakableJoint.cs index 96415460..eed01365 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/BreakableJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/BreakableJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -200,17 +200,9 @@ public BreakableJoint(SampleContext context) : base(context) m_breakForce = 1000.0f; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Breakable Joint", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("break force", ref m_breakForce, 0.0f, 10000.0f, "%.1f"); B2Vec2 gravity = b2World_GetGravity(m_worldId); @@ -218,8 +210,7 @@ public override void UpdateGui() { b2World_SetGravity(m_worldId, gravity); } - - ImGui.End(); + ImGui.PopItemWidth(); } @@ -262,4 +253,4 @@ public override void Draw() } } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/Bridge.cs b/src/Box2D.NET.Samples/Samples/Joints/Bridge.cs index eb8cf985..7ab21d29 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Bridge.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Bridge.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -138,18 +138,11 @@ public Bridge(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 180.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(320.0f, height)); - ImGui.Begin("Bridge", ImGuiWindowFlags.NoResize); - - ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.6f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool updateFriction = ImGui.SliderFloat("Joint Friction", ref m_frictionTorque, 0.0f, 10000.0f, "%2.f"); if (updateFriction) { @@ -193,6 +186,6 @@ public override void UpdateGui() ImGui.PopItemWidth(); - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Joints/Cantilever.cs b/src/Box2D.NET.Samples/Samples/Joints/Cantilever.cs index 9ec9e3c1..f7bb3fac 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Cantilever.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Cantilever.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -100,17 +100,10 @@ public Cantilever(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 14.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(19.0f * fontSize, height)); - - ImGui.Begin("Cantilever", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(8.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Linear Hertz", ref m_linearHertz, 0.0f, 20.0f, "%.0f")) { @@ -144,24 +137,23 @@ public override void UpdateGui() } } - if (ImGui.Checkbox("Collide Connected", ref m_collideConnected)) + if (ImGui.SliderFloat("Gravity Scale", ref m_gravityScale, -1.0f, 1.0f, "%.1f")) { for (int i = 0; i < e_count; ++i) { - b2Joint_SetCollideConnected(m_jointIds[i], m_collideConnected); + b2Body_SetGravityScale(m_bodyIds[i], m_gravityScale); } } - if (ImGui.SliderFloat("Gravity Scale", ref m_gravityScale, -1.0f, 1.0f, "%.1f")) + ImGui.PopItemWidth(); + + if (ImGui.Checkbox("Collide Connected", ref m_collideConnected)) { for (int i = 0; i < e_count; ++i) { - b2Body_SetGravityScale(m_bodyIds[i], m_gravityScale); + b2Joint_SetCollideConnected(m_jointIds[i], m_collideConnected); } } - - ImGui.PopItemWidth(); - ImGui.End(); } @@ -172,4 +164,4 @@ public override void Draw() B2Vec2 tipPosition = b2Body_GetPosition(m_tipId); DrawTextLine($"tip-y = {tipPosition.Y:F2}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/DistanceJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/DistanceJoint.cs index 5bba75ab..0a870b2b 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/DistanceJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/DistanceJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -129,17 +129,10 @@ void CreateScene(int newCount) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 20.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(18.0f * fontSize, height)); - - ImGui.Begin("Distance Joint", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(10.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Length", ref m_length, 0.1f, 4.0f, "%3.1f")) { @@ -236,6 +229,6 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Joints/Door.cs b/src/Box2D.NET.Samples/Samples/Joints/Door.cs index 13ce2b87..1b246d49 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Door.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Door.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -91,14 +91,9 @@ public Door(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 220.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - ImGui.Begin("Door", ImGuiWindowFlags.NoResize); if (ImGui.Button("impulse")) { @@ -107,12 +102,9 @@ public override void UpdateGui() m_translationError = 0.0f; } - ImGui.SliderFloat("magnitude", ref m_impulse, 1000.0f, 100000.0f, "%.0f"); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); - if (ImGui.Checkbox("limit", ref m_enableLimit)) - { - b2RevoluteJoint_EnableLimit(m_jointId, m_enableLimit); - } + ImGui.SliderFloat("magnitude", ref m_impulse, 1000.0f, 100000.0f, "%.0f"); if (ImGui.SliderFloat("hertz", ref m_jointHertz, 15.0f, 480.0f, "%.0f")) { @@ -124,7 +116,12 @@ public override void UpdateGui() b2Joint_SetConstraintTuning(m_jointId, m_jointHertz, m_jointDampingRatio); } - ImGui.End(); + ImGui.PopItemWidth(); + + if (ImGui.Checkbox("limit", ref m_enableLimit)) + { + b2RevoluteJoint_EnableLimit(m_jointId, m_enableLimit); + } } public override void Draw() @@ -139,4 +136,4 @@ public override void Draw() DrawTextLine($"translation error = {m_translationError}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/Driving.cs b/src/Box2D.NET.Samples/Samples/Joints/Driving.cs index 6aa07904..a83cc801 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Driving.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Driving.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -211,18 +211,11 @@ public Driving(SampleContext context) : base(context) m_car.Spawn(m_worldId, new B2Vec2(0.0f, 0.0f), 1.0f, m_hertz, m_dampingRatio, m_torque, B2UserData.Empty); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 10.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(16.0f * fontSize, height)); - ImGui.Begin("Driving", ImGuiWindowFlags.NoResize); - - ImGui.PushItemWidth(8.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Spring Hertz", ref m_hertz, 0.0f, 20.0f, "%.0f")) { m_car.SetHertz(m_hertz); @@ -245,7 +238,7 @@ public override void UpdateGui() ImGui.PopItemWidth(); - ImGui.End(); + } public override void Step() @@ -275,7 +268,7 @@ public override void Draw() { base.Draw(); - DrawTextLine("Keys: left = a, brake = s, right = d"); + DrawScreenTextLine("Keys: left = a, brake = s, right = d"); B2Vec2 linearVelocity = b2Body_GetLinearVelocity(m_car.m_chassisId); @@ -286,4 +279,4 @@ public override void Draw() B2Vec2 carPosition = b2Body_GetPosition(m_car.m_chassisId); m_camera.center.X = carPosition.X; } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/GearLift.cs b/src/Box2D.NET.Samples/Samples/Joints/GearLift.cs index 410330ea..5bdbe5a7 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/GearLift.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/GearLift.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -294,14 +294,9 @@ public GearLift(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 120.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 25.0f), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - ImGui.Begin("Gear Lift", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Motor", ref m_enableMotor)) { @@ -309,6 +304,8 @@ public override void UpdateGui() b2Joint_WakeBodies(m_driverId); } + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); + if (ImGui.SliderFloat("Max Torque", ref m_motorTorque, 0.0f, 100.0f, "%.0f")) { b2RevoluteJoint_SetMaxMotorTorque(m_driverId, m_motorTorque); @@ -320,8 +317,7 @@ public override void UpdateGui() b2RevoluteJoint_SetMotorSpeed(m_driverId, m_motorSpeed); b2Joint_WakeBodies(m_driverId); } - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -342,4 +338,4 @@ public override void Step() base.Step(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/JointSeparation.cs b/src/Box2D.NET.Samples/Samples/Joints/JointSeparation.cs index 1ac5795d..05254203 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/JointSeparation.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/JointSeparation.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -176,30 +176,15 @@ public JointSeparation(SampleContext context) : base(context) m_jointDampingRatio = 2.0f; } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 14.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(20.0f * fontSize, height)); - - ImGui.Begin("Joint Separation", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); B2Vec2 gravity = b2World_GetGravity(m_worldId); if (ImGui.SliderFloat("gravity", ref gravity.Y, -500.0f, 500.0f, "%.0f")) { b2World_SetGravity(m_worldId, gravity); } - if (ImGui.Button("impulse")) - { - for (int i = 0; i < e_count; ++i) - { - B2Vec2 p = b2Body_GetWorldPoint(m_bodyIds[i], new B2Vec2(1.0f, 1.0f)); - b2Body_ApplyLinearImpulse(m_bodyIds[i], new B2Vec2(m_impulse, -m_impulse), p, true); - } - } - ImGui.SliderFloat("magnitude", ref m_impulse, 0.0f, 1000.0f, "%.0f"); if (ImGui.SliderFloat("hertz", ref m_jointHertz, 15.0f, 120.0f, "%.0f")) @@ -218,7 +203,16 @@ public override void UpdateGui() } } - ImGui.End(); + ImGui.PopItemWidth(); + + if (ImGui.Button("impulse")) + { + for (int i = 0; i < e_count; ++i) + { + B2Vec2 p = b2Body_GetWorldPoint(m_bodyIds[i], new B2Vec2(1.0f, 1.0f)); + b2Body_ApplyLinearImpulse(m_bodyIds[i], new B2Vec2(m_impulse, -m_impulse), p, true); + } + } } public override void Draw() @@ -238,4 +232,4 @@ public override void Draw() DrawWorldString(m_draw, m_camera, localFrame.p, B2HexColor.b2_colorWhite, $"{linear:F2} m, {180.0f * angular / B2_PI:F1} deg"); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/MotionLocks.cs b/src/Box2D.NET.Samples/Samples/Joints/MotionLocks.cs index 664daf59..cb53e20f 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/MotionLocks.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/MotionLocks.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -198,16 +198,9 @@ public MotionLocks(SampleContext context) : base(context) ++index; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 8.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(14.0f * fontSize, height)); - - ImGui.Begin("Motion Locks", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Lock Linear X", ref m_motionLocks.linearX)) { @@ -236,7 +229,7 @@ public override void UpdateGui() } } - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs index 442167c6..eb93eaa0 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -123,17 +123,9 @@ public MotorJoint(SampleContext context) : base(context) m_time = 0.0f; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 180.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Motor Joint", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Speed", ref m_speed, -5.0f, 5.0f, "%.0f")) { } @@ -148,12 +140,12 @@ public override void UpdateGui() b2MotorJoint_SetMaxSpringTorque(m_jointId, m_maxTorque); } + ImGui.PopItemWidth(); + if (ImGui.Button("Apply Impulse")) { b2Body_ApplyLinearImpulseToCenter(m_bodyId, new B2Vec2(100.0f, 0.0f), true); } - - ImGui.End(); } @@ -196,7 +188,7 @@ public override void Draw() B2Vec2 force = b2Joint_GetConstraintForce(m_jointId); float torque = b2Joint_GetConstraintTorque(m_jointId); - DrawTextLine($"force = {force.X:3,F0}, {force.Y:3,F0}, torque = {torque:3,F0}"); + DrawScreenTextLine(FormattableString.Invariant($"force = {{{force.X,3:F0}, {force.Y,3:F0}}}, torque = {torque,3:F0}")); DrawTransform(m_draw, _transform, 1.0f); } } diff --git a/src/Box2D.NET.Samples/Samples/Joints/PrismaticJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/PrismaticJoint.cs index 1dd586b7..6958d2da 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/PrismaticJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/PrismaticJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -91,16 +91,9 @@ public PrismaticJoint(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 240.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Prismatic Joint", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Limit", ref m_enableLimit)) { @@ -116,6 +109,7 @@ public override void UpdateGui() if (m_enableMotor) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Max Force", ref m_motorForce, 0.0f, 200.0f, "%.0f")) { b2PrismaticJoint_SetMaxMotorForce(m_jointId, m_motorForce); @@ -127,6 +121,7 @@ public override void UpdateGui() b2PrismaticJoint_SetMotorSpeed(m_jointId, m_motorSpeed); b2Joint_WakeBodies(m_jointId); } + ImGui.PopItemWidth(); } if (ImGui.Checkbox("Spring", ref m_enableSpring)) @@ -137,6 +132,7 @@ public override void UpdateGui() if (m_enableSpring) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Hertz", ref m_hertz, 0.0f, 10.0f, "%.1f")) { b2PrismaticJoint_SetSpringHertz(m_jointId, m_hertz); @@ -154,9 +150,8 @@ public override void UpdateGui() b2PrismaticJoint_SetTargetTranslation(m_jointId, m_translation); b2Joint_WakeBodies(m_jointId); } + ImGui.PopItemWidth(); } - - ImGui.End(); } public override void Draw() @@ -174,4 +169,4 @@ public override void Draw() float speed = b2PrismaticJoint_GetSpeed(m_jointId); DrawTextLine($"Speed = {speed:4,F8}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs b/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs index 36f71be4..ccd479b6 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/Ragdoll.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -61,17 +61,10 @@ void Spawn() //Human_ApplyRandomAngularImpulse(ref m_human, 10.0f); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 10.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(14.0f * fontSize, height)); - - ImGui.Begin("Ragdoll", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(8.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Friction", ref m_jointFrictionTorque, 0.0f, 1.0f, "%3.2f")) { @@ -88,13 +81,13 @@ public override void UpdateGui() Human_SetJointDampingRatio(ref m_human, m_jointDampingRatio); } + ImGui.PopItemWidth(); + if (ImGui.Button("Respawn")) { DestroyHuman(ref m_human); Spawn(); } - ImGui.PopItemWidth(); - ImGui.End(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/RevoluteJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/RevoluteJoint.cs index 9f2a4988..17a059c4 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/RevoluteJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/RevoluteJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -143,16 +143,9 @@ public RevoluteJoint(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 8.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(8.0f * fontSize, height)); - - ImGui.Begin("Revolute Joint", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Limit", ref m_enableLimit)) { @@ -168,6 +161,7 @@ public override void UpdateGui() if (m_enableMotor) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Max Torque", ref m_motorTorque, 0.0f, 5000.0f, "%.0f")) { b2RevoluteJoint_SetMaxMotorTorque(m_jointId1, m_motorTorque); @@ -179,6 +173,7 @@ public override void UpdateGui() b2RevoluteJoint_SetMotorSpeed(m_jointId1, m_motorSpeed); b2Joint_WakeBodies(m_jointId1); } + ImGui.PopItemWidth(); } if (ImGui.Checkbox("Spring", ref m_enableSpring)) @@ -189,6 +184,7 @@ public override void UpdateGui() if (m_enableSpring) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Hertz", ref m_hertz, 0.0f, 30.0f, "%.1f")) { b2RevoluteJoint_SetSpringHertz(m_jointId1, m_hertz); @@ -207,9 +203,8 @@ public override void UpdateGui() b2RevoluteJoint_SetTargetAngle(m_jointId1, B2_PI * m_targetDegrees / 180.0f); b2Joint_WakeBodies(m_jointId1); } + ImGui.PopItemWidth(); } - - ImGui.End(); } public override void Draw() @@ -226,4 +221,4 @@ public override void Draw() float torque2 = b2RevoluteJoint_GetMotorTorque(m_jointId2); DrawTextLine($"Motor Torque 2 = {torque2:F1}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs b/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs index de718a38..7f52f5bb 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/ScaleRagdoll.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -56,18 +56,13 @@ void Spawn() float jointHertz = 1.0f; float jointDampingRatio = 0.5f; CreateHuman(ref m_human, m_worldId, new B2Vec2(0.0f, 5.0f), m_scale, jointFrictionTorque, jointHertz, jointDampingRatio, 1, B2UserData.Empty, false); - Human_ApplyRandomAngularImpulse(ref m_human, 10.0f); + Human_ApplyRandomAngularImpulse(ref m_human, 0.1f); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 4.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(20.0f * fontSize, height)); - ImGui.Begin("Scale Ragdoll", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(15.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Scale", ref m_scale, 0.1f, 10.0f, "%3.2f", ImGuiSliderFlags.AlwaysClamp)) { @@ -75,6 +70,6 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs b/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs index 928648a4..9ca95117 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/ScissorLift.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -204,16 +204,9 @@ public ScissorLift(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 140.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Scissor Lift", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Motor", ref m_enableMotor)) { @@ -221,6 +214,8 @@ public override void UpdateGui() b2Joint_WakeBodies(m_liftJointId); } + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); + if (ImGui.SliderFloat("Max Force", ref m_motorForce, 0.0f, 3000.0f, "%.0f")) { b2DistanceJoint_SetMaxMotorForce(m_liftJointId, m_motorForce); @@ -232,7 +227,6 @@ public override void UpdateGui() b2DistanceJoint_SetMotorSpeed(m_liftJointId, m_motorSpeed); b2Joint_WakeBodies(m_liftJointId); } - - ImGui.End(); + ImGui.PopItemWidth(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Joints/TopDownFriction.cs b/src/Box2D.NET.Samples/Samples/Joints/TopDownFriction.cs index 69a7002a..c319750c 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/TopDownFriction.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/TopDownFriction.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -109,14 +109,9 @@ private TopDownFriction(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 180.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - ImGui.Begin("Top Down Friction", ImGuiWindowFlags.NoResize); if (ImGui.Button("Explode")) { @@ -130,6 +125,6 @@ public override void UpdateGui() DrawCircle(m_draw, def.position, 10.0f, B2HexColor.b2_colorWhite); } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Joints/WheelJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/WheelJoint.cs index d160e57e..4dc6e519 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/WheelJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/WheelJoint.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -85,16 +85,9 @@ public WheelJoint(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 15.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(18.0f, height)); - - ImGui.Begin("Wheel Joint", ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("Limit", ref m_enableLimit)) { @@ -108,6 +101,7 @@ public override void UpdateGui() if (m_enableMotor) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Torque", ref m_motorTorque, 0.0f, 20.0f, "%.0f")) { b2WheelJoint_SetMaxMotorTorque(m_jointId, m_motorTorque); @@ -117,6 +111,7 @@ public override void UpdateGui() { b2WheelJoint_SetMotorSpeed(m_jointId, m_motorSpeed); } + ImGui.PopItemWidth(); } if (ImGui.Checkbox("Spring", ref m_enableSpring)) @@ -126,6 +121,7 @@ public override void UpdateGui() if (m_enableSpring) { + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Hertz", ref m_hertz, 0.0f, 10.0f, "%.1f")) { b2WheelJoint_SetSpringHertz(m_jointId, m_hertz); @@ -135,9 +131,8 @@ public override void UpdateGui() { b2WheelJoint_SetSpringDampingRatio(m_jointId, m_dampingRatio); } + ImGui.PopItemWidth(); } - - ImGui.End(); } public override void Draw() @@ -147,4 +142,4 @@ public override void Draw() float torque = b2WheelJoint_GetMotorTorque(m_jointId); DrawTextLine($"Motor Torque = {torque,4:F1}"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Robustness/Cart.cs b/src/Box2D.NET.Samples/Samples/Robustness/Cart.cs index 0b5e9e06..05433688 100644 --- a/src/Box2D.NET.Samples/Samples/Robustness/Cart.cs +++ b/src/Box2D.NET.Samples/Samples/Robustness/Cart.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -141,17 +141,10 @@ void CreateScene() b2Joint_SetConstraintTuning(m_jointId2, m_constraintHertz, m_constraintDampingRatio); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 240.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(320.0f, height)); - - ImGui.Begin("Cart", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(200.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; ImGui.Text("Contact"); @@ -184,6 +177,6 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Robustness/OverlapRecovery.cs b/src/Box2D.NET.Samples/Samples/Robustness/OverlapRecovery.cs index 56aa0e05..7af4c2a7 100644 --- a/src/Box2D.NET.Samples/Samples/Robustness/OverlapRecovery.cs +++ b/src/Box2D.NET.Samples/Samples/Robustness/OverlapRecovery.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -101,17 +101,10 @@ void CreateScene() B2_ASSERT(bodyIndex == m_bodyCount); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 210.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(220.0f, height)); - - ImGui.Begin("Overlap Recovery", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(100.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; changed = changed || ImGui.SliderFloat("Extent", ref m_extent, 0.1f, 1.0f, "%.1f"); @@ -128,6 +121,6 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Sample.cs b/src/Box2D.NET.Samples/Samples/Sample.cs index c14e2f3a..bb9ec5a3 100644 --- a/src/Box2D.NET.Samples/Samples/Sample.cs +++ b/src/Box2D.NET.Samples/Samples/Sample.cs @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2023 Erin Catto +// SPDX-FileCopyrightText: 2023 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT using System; +using System.Diagnostics; using System.Numerics; using Box2D.NET.Samples.Graphics; using Box2D.NET.Samples.Helpers; @@ -22,6 +23,8 @@ using static Box2D.NET.Samples.Graphics.Draws; using static Box2D.NET.Samples.Graphics.Cameras; using static Box2D.NET.B2Constants; +using static Box2D.NET.B2Cores; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples; @@ -30,6 +33,34 @@ public class Sample : IDisposable public const int m_maxTasks = 512; public const int m_maxThreads = 64; public const int m_profileCapacity = 512; + public const int m_maxHudLines = 64; + private const int MAX_SAMPLES = 256; + + public struct HudLine + { + public B2HexColor color; + public string text; + } + + private readonly struct ProfileRowDef + { + public readonly string name; + public readonly int indent; + public readonly Vector4 color; + + public ProfileRowDef(string name, int indent, Vector4 color) + { + this.name = name; + this.indent = indent; + this.color = color; + } + } + + private struct ScoredSample + { + public int index; + public int score; + } #if DEBUG public const bool m_isDebug = true; @@ -49,8 +80,10 @@ public class Sample : IDisposable private B2Vec2 m_mousePoint; protected float m_mouseForceScale; public int m_stepCount; - private int m_textLine; - private int m_textIncrement; + + public readonly HudLine[] m_hudLines; + public int m_hudLineCount; + private float m_screenTextY; // private readonly B2Profile[] m_profiles; @@ -61,6 +94,17 @@ public class Sample : IDisposable // private static bool s_showProfilePlots; private static readonly bool[] s_profileRowOpen = new bool[22]; + private static bool s_showHelp; + private static bool s_showAbout; + private static string s_pickerQuery = string.Empty; + private static string s_previousPickerQuery = string.Empty; + private static int s_pickerHighlight; + private static int s_previousPickerHighlight; + private static readonly int[] s_filteredSamples = new int[MAX_SAMPLES]; + private static readonly ScoredSample[] s_scoredSamples = new ScoredSample[MAX_SAMPLES]; + private static int s_filteredCount; + private static bool s_pickerJustOpened; + private static bool s_forcePickerScroll; // private bool m_didStep; @@ -76,12 +120,13 @@ public Sample(SampleContext context) m_worldId = b2_nullWorldId; - m_textIncrement = 26; - m_textLine = m_textIncrement; m_mouseJointId = b2_nullJointId; m_stepCount = 0; m_didStep = false; + m_hudLines = new HudLine[m_maxHudLines]; + m_hudLineCount = 0; + m_screenTextY = 0.0f; m_mouseBodyId = b2_nullBodyId; m_mousePoint = new B2Vec2(); @@ -157,344 +202,406 @@ public virtual void UpdateGui() { float fontSize = ImGui.GetFontSize(); - if (m_context.drawProfile) + if (m_context.showDiagnostics == false) { - ImGui.SetNextWindowPos(new Vector2(fontSize, 8.0f * fontSize), ImGuiCond.FirstUseEver); - ImGui.Begin("Profile (ms)", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize); + return; + } - int count = (int)(m_profileWriteIndex - m_profileReadIndex); + float menuWidth = 14.0f * fontSize; + float drawerHeight = 16.0f * fontSize; + float drawerWidth = m_camera.width - menuWidth - 1.5f * fontSize; - // Unroll ring buffer into per-field histories. - const int kRowCount = 22; - float[][] histories = new float[kRowCount][]; - float[] totals = new float[kRowCount]; - for (int i = 0; i < kRowCount; ++i) - { - histories[i] = new float[m_profileCapacity]; - } + ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - drawerHeight - 0.5f * fontSize)); + ImGui.SetNextWindowSize(new Vector2(drawerWidth, drawerHeight)); - for (int i = 0; i < count; ++i) - { - int idx = (int)((m_profileReadIndex + (ulong)i) & (m_profileCapacity - 1)); - ref readonly B2Profile p = ref m_profiles[idx]; - histories[0][i] = p.step; - histories[1][i] = p.pairs; - histories[2][i] = p.collide; - histories[3][i] = p.solve; - histories[4][i] = p.solverSetup; - histories[5][i] = p.constraints; - histories[6][i] = p.prepareConstraints; - histories[7][i] = p.integrateVelocities; - histories[8][i] = p.warmStart; - histories[9][i] = p.solveImpulses; - histories[10][i] = p.integratePositions; - histories[11][i] = p.relaxImpulses; - histories[12][i] = p.applyRestitution; - histories[13][i] = p.storeImpulses; - histories[14][i] = p.splitIslands; - histories[15][i] = p.transforms; - histories[16][i] = p.jointEvents; - histories[17][i] = p.hitEvents; - histories[18][i] = p.refit; - histories[19][i] = p.sleepIslands; - histories[20][i] = p.bullets; - histories[21][i] = p.sensors; - - totals[0] += p.step; - totals[1] += p.pairs; - totals[2] += p.collide; - totals[3] += p.solve; - totals[4] += p.solverSetup; - totals[5] += p.constraints; - totals[6] += p.prepareConstraints; - totals[7] += p.integrateVelocities; - totals[8] += p.warmStart; - totals[9] += p.solveImpulses; - totals[10] += p.integratePositions; - totals[11] += p.relaxImpulses; - totals[12] += p.applyRestitution; - totals[13] += p.storeImpulses; - totals[14] += p.splitIslands; - totals[15] += p.transforms; - totals[16] += p.jointEvents; - totals[17] += p.hitEvents; - totals[18] += p.refit; - totals[19] += p.sleepIslands; - totals[20] += p.bullets; - totals[21] += p.sensors; - } + ImGui.Begin("Diagnostics", + ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | + ImGuiWindowFlags.NoTitleBar); - ref readonly B2Profile cur = ref m_profiles[m_currentProfileIndex]; - float[] now = - [ - cur.step, cur.pairs, cur.collide, cur.solve, cur.solverSetup, - cur.constraints, cur.prepareConstraints, cur.integrateVelocities, cur.warmStart, - cur.solveImpulses, cur.integratePositions, cur.relaxImpulses, cur.applyRestitution, - cur.storeImpulses, cur.splitIslands, cur.transforms, cur.jointEvents, - cur.hitEvents, cur.refit, cur.sleepIslands, cur.bullets, cur.sensors - ]; - - // Rolling average - float[] avg = new float[kRowCount]; - if (count > 0) + if (ImGui.BeginTabBar("DiagnosticsTabs", ImGuiTabBarFlags.None)) + { + if (ImGui.BeginTabItem("Profile")) { - float scale = 1.0f / count; + int count = (int)(m_profileWriteIndex - m_profileReadIndex); + + const int kRowCount = 22; + float[][] histories = new float[kRowCount][]; + float[] totals = new float[kRowCount]; for (int i = 0; i < kRowCount; ++i) { - avg[i] = scale * totals[i]; + histories[i] = new float[m_profileCapacity]; } - } - string[] names = - [ - "step", "pairs", "collide", "solve", "setup", "constraints", "prepare", - "velocities", "warm start", "bias", "positions", "relax", - "restitution", "store", "split islands", "transforms", "joint events", - "hit events", "refit BVH", "sleep", "bullets", "sensors" - ]; - int[] indents = [0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]; - // Derive parent/child links from the indent levels so we can collapse subtrees. - int[] parents = new int[kRowCount]; - bool[] hasChildren = new bool[kRowCount]; - int[] stack = new int[8]; - int stackSize = 0; - for (int i = 0; i < kRowCount; ++i) - { - while (stackSize > 0 && indents[stack[stackSize - 1]] >= indents[i]) + for (int i = 0; i < count; ++i) { - stackSize -= 1; + int idx = (int)((m_profileReadIndex + (ulong)i) & (m_profileCapacity - 1)); + ref readonly B2Profile p = ref m_profiles[idx]; + histories[0][i] = p.step; + histories[1][i] = p.pairs; + histories[2][i] = p.collide; + histories[3][i] = p.solve; + histories[4][i] = p.solverSetup; + histories[5][i] = p.constraints; + histories[6][i] = p.prepareConstraints; + histories[7][i] = p.integrateVelocities; + histories[8][i] = p.warmStart; + histories[9][i] = p.solveImpulses; + histories[10][i] = p.integratePositions; + histories[11][i] = p.relaxImpulses; + histories[12][i] = p.applyRestitution; + histories[13][i] = p.storeImpulses; + histories[14][i] = p.splitIslands; + histories[15][i] = p.transforms; + histories[16][i] = p.jointEvents; + histories[17][i] = p.hitEvents; + histories[18][i] = p.refit; + histories[19][i] = p.sleepIslands; + histories[20][i] = p.bullets; + histories[21][i] = p.sensors; + for (int j = 0; j < kRowCount; ++j) + { + totals[j] += histories[j][i]; + } } - parents[i] = stackSize > 0 ? stack[stackSize - 1] : -1; - stack[stackSize] = i; - stackSize += 1; - - if (parents[i] >= 0) + // "now" smoothed over the last few frames so bars don't jitter visibly. + const int kNowWindow = 10; + float[] now = new float[kRowCount]; { - hasChildren[parents[i]] = true; + int n = count < kNowWindow ? count : kNowWindow; + if (n > 0) + { + float inv = 1.0f / n; + for (int r = 0; r < kRowCount; ++r) + { + float sum = 0.0f; + for (int i = count - n; i < count; ++i) + { + sum += histories[r][i]; + } + now[r] = sum * inv; + } + } } - } - - // Match Frame Time chart's first three colors so rows read with the line plot. - Vector4 colorStep = new Vector4(102.0f / 255.0f, 153.0f / 255.0f, 1.0f, 1.0f); - Vector4 colorCollide = new Vector4(1.0f, 140.0f / 255.0f, 51.0f / 255.0f, 1.0f); - Vector4 colorSolve = new Vector4(102.0f / 255.0f, 204.0f / 255.0f, 102.0f / 255.0f, 1.0f); - Vector4 colorDefault = new Vector4(220.0f / 255.0f, 220.0f / 255.0f, 220.0f / 255.0f, 1.0f); - - Vector4[] colors = - [ - colorStep, colorDefault, colorCollide, colorSolve, colorDefault, colorDefault, - colorDefault, colorDefault, colorDefault, colorDefault, colorDefault, colorDefault, - colorDefault, colorDefault, colorDefault, colorDefault, colorDefault, colorDefault, - colorDefault, colorDefault, colorDefault, colorDefault - ]; - if (ImGui.Button("Reset")) - { - ResetProfile(); - } - ImGui.SameLine(); - ImGui.Checkbox("Show plots", ref s_showProfilePlots); - - ImGuiTableFlags tableFlags = ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit; - int columnCount = s_showProfilePlots ? 6 : 5; - if (ImGui.BeginTable("profile", columnCount, tableFlags)) - { - ImGui.TableSetupColumn("section", ImGuiTableColumnFlags.WidthFixed, 8.0f * fontSize); - ImGui.TableSetupColumn("now", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); - ImGui.TableSetupColumn("avg", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); - ImGui.TableSetupColumn("max", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); - ImGui.TableSetupColumn("% step", ImGuiTableColumnFlags.WidthFixed, 8.0f * fontSize); - if (s_showProfilePlots) + float[] avg = new float[kRowCount]; + if (count > 0) { - ImGui.TableSetupColumn("history", ImGuiTableColumnFlags.WidthFixed, 16.0f * fontSize); + float scale = 1.0f / count; + for (int i = 0; i < kRowCount; ++i) + { + avg[i] = scale * totals[i]; + } } - ImGui.TableHeadersRow(); - - float rowHeight = 1.5f * fontSize; - - // Bars are drawn relative to the step row so the proportions are visually consistent. - float stepNow = b2MaxFloat(cur.step, 0.001f); - for (int row = 0; row < kRowCount; ++row) + float[] rowMax = new float[kRowCount]; + for (int r = 0; r < kRowCount; ++r) { - bool visible = true; - for (int parent = parents[row]; parent >= 0; parent = parents[parent]) + for (int i = 0; i < count; ++i) { - if (s_profileRowOpen[parent] == false) + if (histories[r][i] > rowMax[r]) { - visible = false; - break; + rowMax[r] = histories[r][i]; } } + } - if (visible == false) + Vector4 colorStep = new Vector4(102.0f / 255.0f, 153.0f / 255.0f, 1.0f, 1.0f); + Vector4 colorPairs = new Vector4(220.0f / 255.0f, 220.0f / 255.0f, 220.0f / 255.0f, 1.0f); + Vector4 colorCollide = new Vector4(1.0f, 140.0f / 255.0f, 51.0f / 255.0f, 1.0f); + Vector4 colorSolve = new Vector4(102.0f / 255.0f, 204.0f / 255.0f, 102.0f / 255.0f, 1.0f); + Vector4 colorSensors = new Vector4(200.0f / 255.0f, 120.0f / 255.0f, 220.0f / 255.0f, 1.0f); + Vector4 colorOther = new Vector4(90.0f / 255.0f, 90.0f / 255.0f, 90.0f / 255.0f, 1.0f); + Vector4 colorDefault = new Vector4(220.0f / 255.0f, 220.0f / 255.0f, 220.0f / 255.0f, 1.0f); + + ProfileRowDef[] rows = + [ + new("step", 0, colorStep), new("pairs", 0, colorPairs), new("collide", 0, colorCollide), + new("solve", 0, colorSolve), new("setup", 1, colorDefault), new("constraints", 1, colorDefault), + new("prepare", 2, colorDefault), new("velocities", 2, colorDefault), new("warm start", 2, colorDefault), + new("bias", 2, colorDefault), new("positions", 2, colorDefault), new("relax", 2, colorDefault), + new("restitution", 2, colorDefault), new("store", 2, colorDefault), new("split islands", 2, colorDefault), + new("transforms", 1, colorDefault), new("joint events", 1, colorDefault), new("hit events", 1, colorDefault), + new("refit BVH", 1, colorDefault), new("sleep", 1, colorDefault), new("bullets", 1, colorDefault), + new("sensors", 0, colorSensors), + ]; + + int[] parents = new int[kRowCount]; + bool[] hasChildren = new bool[kRowCount]; + { + int[] stack = new int[8]; + int stackSize = 0; + for (int i = 0; i < kRowCount; ++i) { - continue; + while (stackSize > 0 && rows[stack[stackSize - 1]].indent >= rows[i].indent) + { + --stackSize; + } + parents[i] = stackSize > 0 ? stack[stackSize - 1] : -1; + stack[stackSize++] = i; + if (parents[i] >= 0) + { + hasChildren[parents[i]] = true; + } } + } - float[] history = histories[row]; + float stepNow = b2MaxFloat(now[0], 0.001f); - // Rolling max from live history, replacing the old session-sticky max. - float rollingMax = 0.0f; - for (int i = 0; i < count; ++i) - { - rollingMax = b2MaxFloat(rollingMax, history[i]); - } + if (ImGui.Button("Reset")) + { + ResetProfile(); + } + ImGui.SameLine(); + ImGui.Checkbox("Show plots", ref s_showProfilePlots); + ImGui.SameLine(); + ImGui.Text($" step {now[0]:F2} ms"); - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - if (indents[row] > 0) - { - ImGui.Indent(indents[row] * fontSize); - } - if (hasChildren[row]) + // Flame strip: step subdivided by top-level children. + { + float pairsT = now[1]; + float collideT = now[2]; + float solveT = now[3]; + float sensorsT = now[21]; + float otherT = b2MaxFloat(stepNow - pairsT - collideT - solveT - sensorsT, 0.0f); + + float availWidth = ImGui.GetContentRegionAvail().X; + float barHeight = 1.5f * fontSize; + ImDrawListPtr drawList = ImGui.GetWindowDrawList(); + Vector2 cursor = ImGui.GetCursorScreenPos(); + float x = cursor.X; + + void AddSegment(float t, Vector4 color) { - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick | - ImGuiTreeNodeFlags.NoTreePushOnOpen; - ImGui.PushStyleColor(ImGuiCol.Text, colors[row]); - s_profileRowOpen[row] = ImGui.TreeNodeEx(names[row], flags); - ImGui.PopStyleColor(); + float width = availWidth * (t / stepNow); + if (width > 0.0f) + { + drawList.AddRectFilled(new Vector2(x, cursor.Y), new Vector2(x + width, cursor.Y + barHeight), ImGui.ColorConvertFloat4ToU32(color)); + x += width; + } } - else + + AddSegment(pairsT, colorPairs); + AddSegment(collideT, colorCollide); + AddSegment(solveT, colorSolve); + AddSegment(sensorsT, colorSensors); + AddSegment(otherT, colorOther); + + ImGui.Dummy(new Vector2(availWidth, barHeight)); + } + + ImGuiTableFlags tableFlags = ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.RowBg | + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollY; + + int columnCount = s_showProfilePlots ? 6 : 5; + Vector2 tableSize = ImGui.GetContentRegionAvail(); + if (ImGui.BeginTable("profile", columnCount, tableFlags, tableSize)) + { + ImGui.TableSetupColumn("section", ImGuiTableColumnFlags.WidthFixed, 8.0f * fontSize); + ImGui.TableSetupColumn("now", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); + ImGui.TableSetupColumn("avg", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); + ImGui.TableSetupColumn("max", ImGuiTableColumnFlags.WidthFixed, 3.0f * fontSize); + ImGui.TableSetupColumn("% step", ImGuiTableColumnFlags.WidthFixed, 8.0f * fontSize); + if (s_showProfilePlots) { - float leafIndent = ImGui.GetTreeNodeToLabelSpacing(); - ImGui.Indent(leafIndent); - ImGui.PushStyleColor(ImGuiCol.Text, colors[row]); - ImGui.TextUnformatted(names[row]); - ImGui.PopStyleColor(); - ImGui.Unindent(leafIndent); + ImGui.TableSetupColumn("history", ImGuiTableColumnFlags.WidthFixed, 16.0f * fontSize); } - if (indents[row] > 0) + ImGui.TableHeadersRow(); + + float rowHeight = 1.5f * fontSize; + + for (int r = 0; r < kRowCount; ++r) { - ImGui.Unindent(indents[row] * fontSize); - } + bool visible = true; + for (int p = parents[r]; p >= 0; p = parents[p]) + { + if (s_profileRowOpen[p] == false) + { + visible = false; + break; + } + } + if (visible == false) + { + continue; + } - ImGui.TableNextColumn(); - ImGui.Text($"{now[row],6:F2}"); - ImGui.TableNextColumn(); - ImGui.Text($"{avg[row],6:F2}"); - ImGui.TableNextColumn(); - ImGui.Text($"{rollingMax,6:F2}"); + // Hide leaf rows that are entirely zero; parents stay so structure reads. + if (hasChildren[r] == false && now[r] == 0.0f && avg[r] == 0.0f && rowMax[r] == 0.0f) + { + continue; + } - ImGui.TableNextColumn(); - float frac = b2ClampFloat(now[row] / stepNow, 0.0f, 1.0f); - ImGui.PushStyleColor(ImGuiCol.PlotHistogram, colors[row]); - ImGui.ProgressBar(frac, new Vector2(-float.Epsilon, 0.0f), ""); - ImGui.PopStyleColor(); + ProfileRowDef row = rows[r]; + float[] history = histories[r]; + + ImGui.TableNextRow(); - if (s_showProfilePlots) - { ImGui.TableNextColumn(); - if (count > 1) + if (row.indent > 0) + { + ImGui.Indent(row.indent * fontSize); + } + if (hasChildren[r]) { - ImGui.PushStyleColor(ImGuiCol.PlotLines, colors[row]); - ImGui.PlotLines($"##h{row}", ref history[0], count, 0, null, 0.0f, rollingMax * 1.05f + 0.001f, new Vector2(-float.Epsilon, rowHeight)); + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick | + ImGuiTreeNodeFlags.NoTreePushOnOpen; + ImGui.PushStyleColor(ImGuiCol.Text, row.color); + s_profileRowOpen[r] = ImGui.TreeNodeEx(row.name, flags); ImGui.PopStyleColor(); } + else + { + float leafIndent = ImGui.GetTreeNodeToLabelSpacing(); + ImGui.Indent(leafIndent); + ImGui.PushStyleColor(ImGuiCol.Text, row.color); + ImGui.TextUnformatted(row.name); + ImGui.PopStyleColor(); + ImGui.Unindent(leafIndent); + } + if (row.indent > 0) + { + ImGui.Unindent(row.indent * fontSize); + } + + ImGui.TableNextColumn(); + ImGui.Text($"{now[r],6:F2}"); + ImGui.TableNextColumn(); + ImGui.Text($"{avg[r],6:F2}"); + ImGui.TableNextColumn(); + ImGui.Text($"{rowMax[r],6:F2}"); + + ImGui.TableNextColumn(); + float fraction = b2ClampFloat(now[r] / stepNow, 0.0f, 1.0f); + ImGui.PushStyleColor(ImGuiCol.PlotHistogram, row.color); + ImGui.ProgressBar(fraction, new Vector2(-float.Epsilon, 0.0f), ""); + ImGui.PopStyleColor(); + + if (s_showProfilePlots) + { + ImGui.TableNextColumn(); + if (count > 1) + { + ImGui.PushStyleColor(ImGuiCol.PlotLines, row.color); + ImGui.PlotLines($"##h{r}", ref history[0], count, 0, null, 0.0f, rowMax[r] * 1.05f + 0.001f, + new Vector2(-float.Epsilon, rowHeight)); + ImGui.PopStyleColor(); + } + } } + ImGui.EndTable(); } - ImGui.EndTable(); + ImGui.EndTabItem(); } - ImGui.End(); - } - - if (m_context.drawCounters) - { - B2Counters s = b2World_GetCounters(m_worldId); - int colorCount = s.colorCounts.Length; - int overflowIndex = colorCount - 1; - - // Bars are scaled to the largest non-overflow color so the distribution shape reads clearly; - // overflow gets its own bar against the same scale, with a red tint to flag coupling problems. - int totalCount = 0; - int maxCount = 1; - for (int i = 0; i < colorCount; ++i) + if (ImGui.BeginTabItem("Frame Time")) { - totalCount += s.colorCounts[i]; - if (i != overflowIndex && s.colorCounts[i] > maxCount) + float maxValue = 0.0f; + int count = (int)(m_profileWriteIndex - m_profileReadIndex); + for (int i = 0; i < count; ++i) { - maxCount = s.colorCounts[i]; + int index = (int)((m_profileReadIndex + (ulong)i) & (m_profileCapacity - 1)); + m_frameTimes[i] = i / 60.0f; + maxValue = b2MaxFloat(m_profiles[index].step, maxValue); } - } - ImGui.SetNextWindowPos(new Vector2(fontSize, 8.0f * fontSize), ImGuiCond.FirstUseEver); - ImGui.Begin("Counters", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize); - ImGui.Text($"bodies/shapes/contacts/joints = {s.bodyCount}/{s.shapeCount}/{s.contactCount}/{s.jointCount}"); + // This project does not depend on ImPlot, so draw the same profile data with ImGui's draw list. + DrawProfilePlot("Profile", count, maxValue, m_profileCapacity / 60.0f, ImGui.GetContentRegionAvail()); - float recycledFraction = s.awakeContactCount > 0 - ? b2ClampFloat((float)s.recycledContactCount / s.awakeContactCount, 0.0f, 1.0f) - : 0.0f; - ImGui.TextUnformatted("recycled contacts"); - ImGui.SameLine(); - ImGui.ProgressBar(recycledFraction, new Vector2(-float.Epsilon, 0.0f), $"{s.recycledContactCount} / {s.awakeContactCount}"); + ImGui.EndTabItem(); + } - ImGui.Text($"islands/tasks = {s.islandCount}/{s.taskCount}"); - ImGui.Text($"tree height static/movable = {s.staticTreeHeight}/{s.treeHeight}"); - ImGui.Text($"stack allocator size = {s.stackUsed / 1024} K"); - ImGui.Text($"total allocation = {s.byteCount / 1024} K"); + if (ImGui.BeginTabItem("Counters")) + { + B2Counters counters = b2World_GetCounters(m_worldId); + B2Capacity capacity = b2World_GetMaxCapacity(m_worldId); + int colorCount = counters.colorCounts.Length; + int overflowIndex = colorCount - 1; - ImGui.Separator(); - B2Capacity capacity = b2World_GetMaxCapacity(m_worldId); - ImGui.TextUnformatted("max capacities"); - ImGui.BulletText($"static shapes/bodies = {capacity.staticShapeCount}/{capacity.staticBodyCount}"); - ImGui.BulletText($"dynamic shapes/bodies = {capacity.dynamicShapeCount}/{capacity.dynamicBodyCount}"); - ImGui.BulletText($"contacts = {capacity.contactCount}"); + if (ImGui.BeginTable("counters_layout", 2, ImGuiTableFlags.SizingFixedFit)) + { + ImGui.TableSetupColumn("left", ImGuiTableColumnFlags.WidthFixed, 22.0f * fontSize); + ImGui.TableSetupColumn("right", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextRow(); + ImGui.TableNextColumn(); - ImGui.Separator(); - ImGui.Text($"{totalCount} constraints across {colorCount} colors"); + ImGui.Text($"bodies {counters.bodyCount} / {capacity.staticBodyCount + capacity.dynamicBodyCount}"); + ImGui.Text($"shapes {counters.shapeCount} / {capacity.staticShapeCount + capacity.dynamicShapeCount}"); + ImGui.Text($"contacts {counters.contactCount} / {capacity.contactCount}"); + ImGui.Text($"joints {counters.jointCount}"); + ImGui.Text($"islands/tasks {counters.islandCount} / {counters.taskCount}"); + ImGui.Text($"tree height static/movable {counters.staticTreeHeight} / {counters.treeHeight}"); + ImGui.Text($"alloc {counters.byteCount / 1024} K stack {counters.stackUsed / 1024} K"); - ImGuiTableFlags tableFlags = ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit; - if (ImGui.BeginTable("graphColors", 3, tableFlags)) - { - ImGui.TableSetupColumn("color", ImGuiTableColumnFlags.WidthFixed, 3.5f * fontSize); - ImGui.TableSetupColumn("count", ImGuiTableColumnFlags.WidthFixed, 5.0f * fontSize); - ImGui.TableSetupColumn("share", ImGuiTableColumnFlags.WidthFixed, 16.0f * fontSize); - ImGui.TableHeadersRow(); + { + float fraction = counters.awakeContactCount > 0 + ? b2ClampFloat((float)counters.recycledContactCount / counters.awakeContactCount, 0.0f, 1.0f) + : 0.0f; + ImGui.TextUnformatted("recycled"); + ImGui.SameLine(); + ImGui.ProgressBar(fraction, new Vector2(-float.Epsilon, 0.0f), + $"{counters.recycledContactCount} / {counters.awakeContactCount}"); + } - float invMax = 1.0f / maxCount; - for (int i = 0; i < colorCount; ++i) - { - int count = s.colorCounts[i]; - bool isOverflow = i == overflowIndex; + ImGui.TableNextColumn(); - // Skip empty slots, but always show overflow -- a non-zero overflow row is the signal we care about. - if (count == 0 && isOverflow == false) + int totalCount = 0; + int normalCount = 0; + for (int i = 0; i < colorCount; ++i) { - continue; + totalCount += counters.colorCounts[i]; + if (i != overflowIndex) + { + normalCount += counters.colorCounts[i]; + } } + int overflowCount = counters.colorCounts[overflowIndex]; - Vector4 color = isOverflow - ? new Vector4(220.0f / 255.0f, 60.0f / 255.0f, 60.0f / 255.0f, 1.0f) - : HexToColor(b2GetGraphColor(i)); + ImGui.Text($"{totalCount} constraints across {colorCount - 1} colors"); - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TextUnformatted(isOverflow ? "over" : i.ToString()); - ImGui.PopStyleColor(); + float availableWidth = ImGui.GetContentRegionAvail().X; + float barHeight = 2.0f * fontSize; + ImDrawListPtr drawList = ImGui.GetWindowDrawList(); - ImGui.TableNextColumn(); - ImGui.Text(count.ToString()); + Vector2 cursor = ImGui.GetCursorScreenPos(); + drawList.AddRectFilled(cursor, new Vector2(cursor.X + availableWidth, cursor.Y + barHeight), + ImGui.ColorConvertFloat4ToU32(new Vector4(40.0f / 255.0f, 40.0f / 255.0f, 40.0f / 255.0f, 1.0f))); + if (normalCount > 0) + { + float x = cursor.X; + float invTotal = 1.0f / normalCount; + for (int i = 0; i < overflowIndex; ++i) + { + int itemCount = counters.colorCounts[i]; + if (itemCount == 0) + { + continue; + } + float segmentWidth = availableWidth * itemCount * invTotal; + uint color = ImGui.ColorConvertFloat4ToU32(HexToColor(b2GetGraphColor(i))); + drawList.AddRectFilled(new Vector2(x, cursor.Y), new Vector2(x + segmentWidth, cursor.Y + barHeight), color); + x += segmentWidth; + } + } + ImGui.Dummy(new Vector2(availableWidth, barHeight)); - ImGui.TableNextColumn(); - ImGui.PushStyleColor(ImGuiCol.PlotHistogram, color); - ImGui.ProgressBar(b2ClampFloat(count * invMax, 0.0f, 1.0f), new Vector2(-float.Epsilon, 0.0f), ""); + ImGui.Spacing(); + float overflowFraction = totalCount > 0 ? (float)overflowCount / totalCount : 0.0f; + ImGui.PushStyleColor(ImGuiCol.PlotHistogram, new Vector4(220.0f / 255.0f, 60.0f / 255.0f, 60.0f / 255.0f, 1.0f)); + ImGui.ProgressBar(overflowFraction, new Vector2(-float.Epsilon, 0.0f), $"overflow {overflowCount}"); ImGui.PopStyleColor(); + + ImGui.EndTable(); } - ImGui.EndTable(); + ImGui.EndTabItem(); } - ImGui.End(); + ImGui.EndTabBar(); } - if (m_context.frameTime) - { - UpdateFrameTimeGui(fontSize); - } + ImGui.End(); } private static Vector4 HexToColor(B2HexColor color) @@ -503,43 +610,8 @@ private static Vector4 HexToColor(B2HexColor color) return new Vector4(((hex >> 16) & 0xFF) / 255.0f, ((hex >> 8) & 0xFF) / 255.0f, (hex & 0xFF) / 255.0f, 1.0f); } - private void UpdateFrameTimeGui(float fontSize) + private void DrawProfilePlot(string label, int count, float maxValue, float maxTime, Vector2 size) { - float frameTimeHeight = 30.0f * fontSize; - float frameTimeWidth = 50.0f * fontSize; - - ImGui.SetNextWindowPos(new Vector2(3.0f * fontSize, 3.0f * fontSize), ImGuiCond.FirstUseEver); - ImGui.SetNextWindowSize(new Vector2(frameTimeWidth, frameTimeHeight), ImGuiCond.FirstUseEver); - - ImGui.Begin("Frame Time", ref m_context.frameTime, ImGuiWindowFlags.NoCollapse); - ImGui.PushItemWidth(ImGui.GetWindowWidth() - 2.0f * fontSize); - - int count = (int)(m_profileWriteIndex - m_profileReadIndex); - float maxValue = 0.0f; - for (int i = 0; i < count; ++i) - { - int index = (int)((m_profileReadIndex + (ulong)i) & (m_profileCapacity - 1)); - m_frameTimes[i] = i / 60.0f; - maxValue = b2MaxFloat(maxValue, m_profiles[index].step); - } - - // This is the pixel size, not the range. - Vector2 plotSize = new Vector2(-1.0f, 22.0f * fontSize); - DrawProfilePlot("Profile", count, maxValue, plotSize); - - ImGui.PopItemWidth(); - ImGui.End(); - } - - private void DrawProfilePlot(string label, int count, float maxValue, Vector2 size) - { - if (count == 0) - { - ImGui.TextUnformatted("No frame data"); - return; - } - - maxValue = b2MaxFloat(maxValue, 0.001f); Vector2 canvasPos = ImGui.GetCursorScreenPos(); Vector2 canvasSize = size; if (canvasSize.X < 0.0f) @@ -552,29 +624,57 @@ private void DrawProfilePlot(string label, int count, float maxValue, Vector2 si ImGui.InvisibleButton(label, canvasSize); - Vector2 min = canvasPos; - Vector2 max = canvasPos + canvasSize; + Vector2 min = canvasPos + new Vector2(42.0f, 8.0f); + Vector2 max = canvasPos + canvasSize - new Vector2(12.0f, 28.0f); + Vector2 plotSize = max - min; ImDrawListPtr drawList = ImGui.GetWindowDrawList(); uint borderColor = ImGui.ColorConvertFloat4ToU32(new Vector4(0.35f, 0.35f, 0.35f, 1.0f)); uint gridColor = ImGui.ColorConvertFloat4ToU32(new Vector4(0.20f, 0.20f, 0.20f, 1.0f)); uint textColor = ImGui.ColorConvertFloat4ToU32(new Vector4(0.85f, 0.85f, 0.85f, 1.0f)); + Vector4 stepColor = new Vector4(0.20f, 0.70f, 1.00f, 1.0f); + Vector4 collideColor = new Vector4(0.95f, 0.65f, 0.20f, 1.0f); + Vector4 solveColor = new Vector4(0.30f, 0.85f, 0.45f, 1.0f); drawList.AddRect(min, max, borderColor); for (int i = 1; i < 4; ++i) { - float y = min.Y + canvasSize.Y * i / 4.0f; + float y = min.Y + plotSize.Y * i / 4.0f; drawList.AddLine(new Vector2(min.X, y), new Vector2(max.X, y), gridColor); + + float x = min.X + plotSize.X * i / 4.0f; + drawList.AddLine(new Vector2(x, min.Y), new Vector2(x, max.Y), gridColor); } - DrawProfileSeries(drawList, min, canvasSize, count, maxValue, p => p.step, new Vector4(0.20f, 0.70f, 1.00f, 1.0f)); - DrawProfileSeries(drawList, min, canvasSize, count, maxValue, p => p.collide, new Vector4(0.95f, 0.65f, 0.20f, 1.0f)); - DrawProfileSeries(drawList, min, canvasSize, count, maxValue, p => p.solve, new Vector4(0.30f, 0.85f, 0.45f, 1.0f)); + float plotMaxValue = maxValue > 0.0f ? maxValue : 1.0f; + DrawProfileSeries(drawList, min, plotSize, count, plotMaxValue, maxTime, p => p.step, stepColor); + DrawProfileSeries(drawList, min, plotSize, count, plotMaxValue, maxTime, p => p.collide, collideColor); + DrawProfileSeries(drawList, min, plotSize, count, plotMaxValue, maxTime, p => p.solve, solveColor); + + drawList.AddText(new Vector2(canvasPos.X + 4.0f, min.Y), textColor, "ms"); + drawList.AddText(new Vector2(canvasPos.X + 4.0f, min.Y + ImGui.GetFontSize()), textColor, FormatFloat(maxValue)); + drawList.AddText(new Vector2(min.X, max.Y + 4.0f), textColor, "0"); + drawList.AddText(new Vector2(max.X - 36.0f, max.Y + 4.0f), textColor, FormatFloat(maxTime)); + drawList.AddText(new Vector2(max.X + 2.0f, max.Y + 4.0f), textColor, "t"); + + Vector2 legend = min + new Vector2(8.0f, 6.0f); + legend = DrawProfileLegendItem(drawList, legend, "step", stepColor, textColor); + legend = DrawProfileLegendItem(drawList, legend, "collide", collideColor, textColor); + DrawProfileLegendItem(drawList, legend, "solve", solveColor, textColor); + } - drawList.AddText(min + new Vector2(8.0f, 6.0f), textColor, $"step / collide / solve max {maxValue:F2} ms"); - drawList.AddText(new Vector2(min.X + 8.0f, max.Y - 22.0f), textColor, $"0s .. {m_frameTimes[count - 1]:F1}s"); + private static Vector2 DrawProfileLegendItem(ImDrawListPtr drawList, Vector2 position, string name, Vector4 color, uint textColor) + { + uint lineColor = ImGui.ColorConvertFloat4ToU32(color); + float centerY = position.Y + 0.5f * ImGui.GetFontSize(); + drawList.AddLine(new Vector2(position.X, centerY), new Vector2(position.X + 14.0f, centerY), lineColor, 2.0f); + position.X += 18.0f; + drawList.AddText(position, textColor, name); + position.X += ImGui.CalcTextSize(name).X + 12.0f; + return position; } - private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 size, int count, float maxValue, Func selector, Vector4 color) + private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 size, int count, float maxValue, float maxTime, + Func selector, Vector4 color) { if (count < 2) { @@ -583,13 +683,12 @@ private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 s uint lineColor = ImGui.ColorConvertFloat4ToU32(color); Vector2 previous = default; - float invCount = 1.0f / (count - 1); for (int i = 0; i < count; ++i) { int index = (int)((m_profileReadIndex + (ulong)i) & (m_profileCapacity - 1)); float value = selector(m_profiles[index]); - float x = origin.X + size.X * i * invCount; + float x = origin.X + size.X * m_frameTimes[i] / maxTime; float y = origin.Y + size.Y * (1.0f - b2ClampFloat(value / maxValue, 0.0f, 1.0f)); Vector2 current = new Vector2(x, y); @@ -601,9 +700,19 @@ private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 s previous = current; } } + public void ResetText() { - m_textLine = m_textIncrement; + m_hudLineCount = 0; + float fontSize = ImGui.GetFontSize(); + if (m_context.showUI) + { + m_screenTextY = ImGui.GetFrameHeight() + 1.5f * fontSize; + } + else + { + m_screenTextY = 3.0f * fontSize; + } } public bool QueryCallback(B2ShapeId shapeId, object context) @@ -711,25 +820,44 @@ public virtual void MouseMove(B2Vec2 p) public void DrawColoredTextLine(B2HexColor color, string text) { - if (m_context.showUI == false) + if (m_context.showUI == false || m_hudLineCount >= m_maxHudLines) { return; } - DrawScreenString(m_draw, 5, m_textLine, color, text); - m_textLine += m_textIncrement; + m_hudLines[m_hudLineCount].color = color; + m_hudLines[m_hudLineCount].text = TruncateText(text); + m_hudLineCount += 1; } public void DrawTextLine(string text) { - if (m_context.showUI == false) + if (m_context.showUI == false || m_hudLineCount >= m_maxHudLines) { return; } - DrawScreenString(m_draw, 5, m_textLine, B2HexColor.b2_colorWhite, text); - m_textLine += m_textIncrement; + m_hudLines[m_hudLineCount].color = B2HexColor.b2_colorWhite; + m_hudLines[m_hudLineCount].text = TruncateText(text); + m_hudLineCount += 1; + } + + public void DrawScreenTextLine(string text) + { + DrawScreenString(m_draw, 5.0f, m_screenTextY, B2HexColor.b2_colorWhite, TruncateText(text)); + m_screenTextY += 1.5f * ImGui.GetFontSize(); + } + + public void DrawColoredScreenTextLine(B2HexColor color, string text) + { + DrawScreenString(m_draw, 5.0f, m_screenTextY, color, TruncateText(text)); + m_screenTextY += 1.5f * ImGui.GetFontSize(); + } + + private static string TruncateText(string text) + { + return text.Length > 255 ? text[..255] : text; } public void ResetProfile() @@ -753,6 +881,12 @@ public virtual void Step() { timeStep = 0.0f; } + + if (m_context.showUI) + { + DrawTextLine("****PAUSED****"); + DrawTextLine(""); + } } if (B2_IS_NON_NULL(m_mouseJointId) && b2Joint_IsValid(m_mouseJointId) == false) @@ -801,20 +935,16 @@ public virtual void Step() public virtual void Draw() { - if (m_context.pause) - { - if (m_context.showUI) - { - DrawTextLine("****PAUSED****"); - } - } - m_context.debugDraw.drawingBounds = GetViewBounds(m_context.camera); b2World_Draw(m_worldId, m_context.debugDraw); } + public virtual void BuildSamplePanel() + { + } + public void ShiftOrigin(B2Vec2 newOrigin) { // m_world.ShiftOrigin(newOrigin); @@ -843,184 +973,496 @@ public static void SelectSample(SampleContext context, int selection, bool resta context.restart = false; } - public static void UpdateSampleUI(SampleContext context) + // Case-insensitive subsequence match. Returns >=0 score on match, -1 on no match. + // Empty needle returns 0 so an empty query lets all samples through with a neutral score. + private static int FuzzyScore(string needle, string haystack) { - int maxWorkers = B2_MAX_WORKERS; - B2WorldId worldId = context.sample.m_worldId; + if (string.IsNullOrEmpty(needle)) + { + return 0; + } - float fontSize = ImGui.GetFontSize(); - float menuWidth = 13.0f * fontSize; - if (context.showUI) + int score = 0; + int hi = 0; + int previousMatchHi = -2; + + for (int ni = 0; ni < needle.Length; ++ni) { - ImGui.SetNextWindowPos(new Vector2(context.camera.width - menuWidth - 0.5f * fontSize, 0.5f * fontSize)); - ImGui.SetNextWindowSize(new Vector2(menuWidth, context.camera.height - fontSize)); + char nc = char.ToLowerInvariant(needle[ni]); + while (hi < haystack.Length && char.ToLowerInvariant(haystack[hi]) != nc) + { + ++hi; + } + if (hi == haystack.Length) + { + return -1; + } + + int bonus = 1; + if (hi == 0) + { + bonus += 10; // prefix match + } + else if (char.IsLetterOrDigit(haystack[hi - 1]) == false) + { + bonus += 5; // word-start (after _, space, etc.) + } + if (hi == previousMatchHi + 1) + { + bonus += 3; // contiguous run + } + + score += bonus; + previousMatchHi = hi; + ++hi; + } - ImGui.Begin("Tools", ref context.showUI, ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse); + return score; + } - if (ImGui.BeginTabBar("ControlTabs", ImGuiTabBarFlags.None)) + private static void RebuildSampleFilter(string query) + { + int sampleCount = Math.Min(SampleFactory.Shared.SampleCount, MAX_SAMPLES); + int count = 0; + for (int i = 0; i < sampleCount; ++i) + { + int nameScore = FuzzyScore(query, SampleFactory.Shared.GetName(i)); + int categoryScore = FuzzyScore(query, SampleFactory.Shared.GetCategory(i)); + int best = -1; + if (nameScore >= 0) { - if (ImGui.BeginTabItem("Controls")) - { - ImGui.PushItemWidth(100.0f); - ImGui.SliderInt("Sub-steps", ref context.subStepCount, 1, 32); - ImGui.SliderFloat("Hertz", ref context.hertz, 5.0f, 240.0f, "%.0f hz"); + best = nameScore * 2; // name matches outweigh category-only matches + } + if (categoryScore >= 0 && categoryScore > best) + { + best = categoryScore; + } + if (best < 0) + { + continue; + } + s_scoredSamples[count].index = i; + s_scoredSamples[count].score = best; + ++count; + } - if (ImGui.SliderInt("Workers", ref context.workerCount, 1, maxWorkers)) - { - context.workerCount = b2ClampInt(context.workerCount, 1, maxWorkers); - SelectSample(context, context.sampleIndex, true); - } - ImGui.PopItemWidth(); + // Stable insertion sort by score desc; equal scores keep registry order + // (which main.cpp sorts by category then name). + for (int i = 1; i < count; ++i) + { + ScoredSample temporary = s_scoredSamples[i]; + int j = i - 1; + while (j >= 0 && s_scoredSamples[j].score < temporary.score) + { + s_scoredSamples[j + 1] = s_scoredSamples[j]; + --j; + } + s_scoredSamples[j + 1] = temporary; + } + for (int i = 0; i < count; ++i) + { + s_filteredSamples[i] = s_scoredSamples[i].index; + } + s_filteredCount = count; + } - ImGui.Separator(); + private static void AddHelpRow(string key, string description) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.TextUnformatted(key); + ImGui.TableSetColumnIndex(1); + ImGui.TextUnformatted(description); + } - ImGui.Checkbox("Sleep", ref context.enableSleep); - ImGui.Checkbox("Warm Starting", ref context.enableWarmStarting); - ImGui.Checkbox("Continuous", ref context.enableContinuous); + private static void TextLinkOpenURL(string label, string url) + { + // ImGui.NET 1.90 does not expose TextLinkOpenURL, so preserve the same appearance and click behavior here. + Vector4 color = new Vector4(0.26f, 0.59f, 0.98f, 1.0f); + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TextUnformatted(label); + ImGui.PopStyleColor(); + if (ImGui.IsItemHovered()) + { + Vector2 min = ImGui.GetItemRectMin(); + Vector2 max = ImGui.GetItemRectMax(); + uint underlineColor = ImGui.ColorConvertFloat4ToU32(color); + ImGui.GetWindowDrawList().AddLine(new Vector2(min.X, max.Y), max, underlineColor); + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + } - ImGui.PushItemWidth(100.0f); - float recyclingCentimeters = 100.0f * context.recycleDistance; - if (ImGui.SliderFloat("Recycle", ref recyclingCentimeters, 0.0f, 10.0f, "%.1f cm")) - { - context.recycleDistance = 0.01f * recyclingCentimeters; - b2World_SetContactRecycleDistance(worldId, context.recycleDistance); - } - ImGui.PopItemWidth(); + if (ImGui.IsItemClicked()) + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + } - ImGui.Separator(); + public static void UpdateSampleUI(SampleContext context) + { + int maxWorkers = B2_MAX_WORKERS; - ImGui.Checkbox("Shapes", ref context.debugDraw.drawShapes); - ImGui.Checkbox("Chain Normals", ref context.debugDraw.drawChainNormals); - ImGui.Checkbox("Joints", ref context.debugDraw.drawJoints); - ImGui.Checkbox("Joint Extras", ref context.debugDraw.drawJointExtras); - ImGui.Checkbox("Bounds", ref context.debugDraw.drawBounds); - ImGui.Checkbox("Mass", ref context.debugDraw.drawMass); - ImGui.Checkbox("Body Names", ref context.debugDraw.drawBodyNames); - ImGui.Checkbox("Graph Colors", ref context.debugDraw.drawGraphColors); - ImGui.Checkbox("Islands", ref context.debugDraw.drawIslands); - ImGui.Checkbox("Counters", ref context.drawCounters); - ImGui.Checkbox("Profile", ref context.drawProfile); - ImGui.Checkbox("Frame Time", ref context.frameTime); + float fontSize = ImGui.GetFontSize(); + float menuWidth = 14.0f * fontSize; - ImGui.Separator(); + if (context.showUI == false) + { + return; + } - ImGui.Checkbox("Contact Points", ref context.debugDraw.drawContacts); + if (ImGui.BeginMainMenuBar()) + { + if (ImGui.BeginMenu("Sim")) + { + ImGui.MenuItem("Pause", "P", ref context.pause); + if (ImGui.MenuItem("Single Step", "O")) + { + context.singleStep = true; + } + if (ImGui.MenuItem("Restart", "R")) + { + SelectSample(context, context.sampleIndex, true); + } + ImGui.Separator(); + if (ImGui.MenuItem("Previous Sample", "[")) + { + int selection = context.sampleIndex - 1; + if (selection < 0) + { + selection = SampleFactory.Shared.SampleCount - 1; + } + SelectSample(context, selection, false); + } + if (ImGui.MenuItem("Next Sample", "]")) + { + int selection = context.sampleIndex + 1; + if (selection == SampleFactory.Shared.SampleCount) + { + selection = 0; + } + SelectSample(context, selection, false); + } + ImGui.Separator(); + if (ImGui.MenuItem("Reset Profile")) + { + context.sample.ResetProfile(); + } + if (ImGui.MenuItem("Dump Mem Stats")) + { + b2World_DumpMemoryStats(context.sample.m_worldId); + } + ImGui.Separator(); + if (ImGui.MenuItem("Quit", "Esc")) + { + unsafe + { + context.glfw.SetWindowShouldClose(context.window, true); + } + } + ImGui.EndMenu(); + } - if (ImGui.RadioButton("Anchor A", context.debugDraw.drawAnchorA == true)) + if (ImGui.BeginMenu("View")) + { + if (ImGui.MenuItem("Hide UI", "Tab")) + { + context.showUI = false; + } + if (ImGui.MenuItem("Reset Camera", "Home")) + { + ResetView(context.camera); + } + ImGui.Separator(); + ImGui.MenuItem("Shapes", null, ref context.debugDraw.drawShapes); + ImGui.MenuItem("Chain Normals", null, ref context.debugDraw.drawChainNormals); + ImGui.MenuItem("Joints", null, ref context.debugDraw.drawJoints); + ImGui.MenuItem("Joint Extras", null, ref context.debugDraw.drawJointExtras); + ImGui.MenuItem("Bounds", null, ref context.debugDraw.drawBounds); + ImGui.MenuItem("Mass", null, ref context.debugDraw.drawMass); + ImGui.MenuItem("Body Names", null, ref context.debugDraw.drawBodyNames); + ImGui.MenuItem("Graph Colors", null, ref context.debugDraw.drawGraphColors); + ImGui.MenuItem("Islands", null, ref context.debugDraw.drawIslands); + ImGui.Separator(); + ImGui.MenuItem("Contact Points", null, ref context.debugDraw.drawContacts); + ImGui.MenuItem("Contact Normals", null, ref context.debugDraw.drawContactNormals); + ImGui.MenuItem("Contact Features", null, ref context.debugDraw.drawContactFeatures); + ImGui.MenuItem("Contact Forces", null, ref context.debugDraw.drawContactForces); + ImGui.MenuItem("Friction Forces", null, ref context.debugDraw.drawFrictionForces); + if (ImGui.BeginMenu("Anchor")) + { + if (ImGui.MenuItem("Anchor A", null, context.debugDraw.drawAnchorA)) { context.debugDraw.drawAnchorA = true; } - ImGui.SameLine(); - if (ImGui.RadioButton("Anchor B", context.debugDraw.drawAnchorA == false)) + if (ImGui.MenuItem("Anchor B", null, context.debugDraw.drawAnchorA == false)) { context.debugDraw.drawAnchorA = false; } - ImGui.Checkbox("Contact Normals", ref context.debugDraw.drawContactNormals); - ImGui.Checkbox("Contact Features", ref context.debugDraw.drawContactFeatures); - ImGui.Checkbox("Contact Forces", ref context.debugDraw.drawContactForces); - ImGui.Checkbox("Friction Forces", ref context.debugDraw.drawFrictionForces); - - ImGui.Separator(); - - ImGui.PushItemWidth(80.0f); - ImGui.InputFloat("Joint Scale", ref context.debugDraw.jointScale); - ImGui.InputFloat("Force Scale", ref context.debugDraw.forceScale); + ImGui.EndMenu(); + } + ImGui.Separator(); + ImGui.MenuItem("Diagnostics", "M", ref context.showDiagnostics); + ImGui.Separator(); + if (ImGui.BeginMenu("Scale")) + { + ImGui.PushItemWidth(6.0f * fontSize); + ImGui.InputFloat("Joint", ref context.debugDraw.jointScale); + ImGui.InputFloat("Force", ref context.debugDraw.forceScale); ImGui.PopItemWidth(); + ImGui.EndMenu(); + } + ImGui.EndMenu(); + } - Vector2 button_sz = new Vector2(-1, 0); - if (ImGui.Button("Pause (P)", button_sz)) + if (ImGui.BeginMenu("Samples")) + { + int i = 0; + while (i < SampleFactory.Shared.SampleCount) + { + string category = SampleFactory.Shared.GetCategory(i); + if (ImGui.BeginMenu(category)) { - context.pause = !context.pause; + while (i < SampleFactory.Shared.SampleCount && category == SampleFactory.Shared.GetCategory(i)) + { + bool selected = i == context.sampleIndex; + if (ImGui.MenuItem(SampleFactory.Shared.GetName(i), null, selected)) + { + SelectSample(context, i, false); + } + ++i; + } + ImGui.EndMenu(); } - - if (ImGui.Button("Single Step (O)", button_sz)) + else { - context.singleStep = !context.singleStep; + while (i < SampleFactory.Shared.SampleCount && category == SampleFactory.Shared.GetCategory(i)) + { + ++i; + } } + } + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Help")) + { + ImGui.MenuItem("Controls", null, ref s_showHelp); + ImGui.MenuItem("About", null, ref s_showAbout); + ImGui.EndMenu(); + } + + ImGui.EndMainMenuBar(); - if (ImGui.Button("Dump Mem Stats", button_sz)) + { + float menuBarBottom = ImGui.GetFrameHeight(); + uint borderColor = ImGui.GetColorU32(ImGuiCol.Border); + Vector2 displaySize = ImGui.GetIO().DisplaySize; + ImGui.GetForegroundDrawList().AddLine(new Vector2(0.0f, menuBarBottom), + new Vector2(displaySize.X, menuBarBottom), borderColor, 1.0f); + } + + if (s_showHelp) + { + ImGui.SetNextWindowPos(new Vector2(context.camera.width * 0.5f, context.camera.height * 0.5f), + ImGuiCond.Appearing, new Vector2(0.5f, 0.5f)); + ImGui.SetNextWindowSize(new Vector2(24.0f * fontSize, 0.0f), ImGuiCond.Appearing); + + if (ImGui.Begin("Controls", ref s_showHelp, + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.SeparatorText("Keyboard"); + if (ImGui.BeginTable("keys", 2, ImGuiTableFlags.SizingFixedFit)) { - b2World_DumpMemoryStats(context.sample.m_worldId); + AddHelpRow("Tab", "Show / hide UI"); + AddHelpRow("M", "Show / hide diagnostics"); + AddHelpRow("P", "Pause / resume"); + AddHelpRow("O", "Single step"); + AddHelpRow("R", "Restart sample"); + AddHelpRow("[ ]", "Previous / next sample"); + AddHelpRow("Ctrl+O", "Open sample picker"); + AddHelpRow("Arrows", "Pan camera"); + AddHelpRow("Ctrl+Arrows", "Shift origin"); + AddHelpRow("Z X", "Zoom out / in"); + AddHelpRow("Home", "Reset camera"); + AddHelpRow("Esc", "Quit"); + ImGui.EndTable(); } - if (ImGui.Button("Reset Profile", button_sz)) + ImGui.SeparatorText("Mouse"); + if (ImGui.BeginTable("mouse", 2, ImGuiTableFlags.SizingFixedFit)) { - context.sample.ResetProfile(); + AddHelpRow("Left drag", "Move bodies (mouse joint)"); + AddHelpRow("Right drag", "Pan camera"); + AddHelpRow("Scroll wheel", "Zoom"); + ImGui.EndTable(); } + } + ImGui.End(); + } + + if (s_showAbout) + { + ImGui.SetNextWindowPos(new Vector2(context.camera.width * 0.5f, context.camera.height * 0.5f), + ImGuiCond.Appearing, new Vector2(0.5f, 0.5f)); + ImGui.SetNextWindowSize(new Vector2(22.0f * fontSize, 0.0f), ImGuiCond.Appearing); + + if (ImGui.Begin("About", ref s_showAbout, + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize)) + { + B2Version version = b2GetVersion(); + ImGui.Text($"Box2D {version.major}.{version.minor}.{version.revision}"); + ImGui.Spacing(); + TextLinkOpenURL("box2d.org", "https://box2d.org/"); + TextLinkOpenURL("github.com/ikpil/Box2D.NET", "https://github.com/ikpil/Box2D.NET"); + } + ImGui.End(); + } + } + + // Fuzzy sample picker (Ctrl+O). Opens a transient popup; type to filter by + // name or category, Up/Down to navigate, Enter to select, Esc / click-outside to dismiss. + { + if (context.openSamplePicker) + { + ImGui.OpenPopup("##sample_picker"); + context.openSamplePicker = false; + s_pickerQuery = string.Empty; + s_previousPickerQuery = string.Empty; + s_pickerHighlight = 0; + s_previousPickerHighlight = 0; + RebuildSampleFilter(s_pickerQuery); + s_pickerJustOpened = true; + s_forcePickerScroll = true; + } + + ImGui.SetNextWindowPos(new Vector2(context.camera.width * 0.5f, context.camera.height * 0.35f), + ImGuiCond.Appearing, new Vector2(0.5f, 0.5f)); + ImGui.SetNextWindowSize(new Vector2(32.0f * fontSize, 0.0f), ImGuiCond.Appearing); + + if (ImGui.BeginPopup("##sample_picker", + ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoSavedSettings)) + { + if (s_pickerJustOpened) + { + ImGui.SetKeyboardFocusHere(); + s_pickerJustOpened = false; + } - if (ImGui.Button("Restart (R)", button_sz)) + ImGui.PushItemWidth(-1.0f); + ImGui.InputTextWithHint("##q", "Search by name or category...", ref s_pickerQuery, 64); + ImGui.PopItemWidth(); + + if (s_pickerQuery != s_previousPickerQuery) + { + RebuildSampleFilter(s_pickerQuery); + s_previousPickerQuery = s_pickerQuery; + s_pickerHighlight = 0; + s_forcePickerScroll = true; + } + + if (s_filteredCount > 0) + { + if (ImGui.IsKeyPressed(ImGuiKey.DownArrow, true)) { - SelectSample(context, context.sampleIndex, true); + s_pickerHighlight = (s_pickerHighlight + 1) % s_filteredCount; } + if (ImGui.IsKeyPressed(ImGuiKey.UpArrow, true)) + { + s_pickerHighlight = (s_pickerHighlight + s_filteredCount - 1) % s_filteredCount; + } + } + bool commit = ImGui.IsKeyPressed(ImGuiKey.Enter, false) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter, false); - if (ImGui.Button("Quit", button_sz)) + ImGui.BeginChild("##results", new Vector2(0.0f, 14.0f * fontSize), ImGuiChildFlags.Border); + for (int row = 0; row < s_filteredCount; ++row) + { + int i = s_filteredSamples[row]; + string label = $"{SampleFactory.Shared.GetCategory(i)} > {SampleFactory.Shared.GetName(i)}"; + bool selected = row == s_pickerHighlight; + if (ImGui.Selectable(label, selected)) { - unsafe - { - context.glfw.SetWindowShouldClose(context.window, true); - } + s_pickerHighlight = row; + commit = true; + } + if (selected && (s_forcePickerScroll || s_pickerHighlight != s_previousPickerHighlight)) + { + ImGui.SetScrollHereY(); } + } + ImGui.EndChild(); + s_previousPickerHighlight = s_pickerHighlight; + s_forcePickerScroll = false; - ImGui.EndTabItem(); + if (commit && s_filteredCount > 0) + { + SelectSample(context, s_filteredSamples[s_pickerHighlight], false); + ImGui.CloseCurrentPopup(); } - ImGuiTreeNodeFlags leafNodeFlags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick; - leafNodeFlags |= ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen; + ImGui.EndPopup(); + } + } - ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick; + float menuBarHeight = ImGui.GetFrameHeight(); - if (ImGui.BeginTabItem("Samples")) - { - int categoryIndex = 0; - string category = SampleFactory.Shared.GetCategory(categoryIndex); - int i = 0; - while (i < SampleFactory.Shared.SampleCount) - { - bool categorySelected = category == SampleFactory.Shared.GetCategory(context.sampleIndex); - ImGuiTreeNodeFlags nodeSelectionFlags = categorySelected ? ImGuiTreeNodeFlags.Selected : 0; - bool nodeOpen = ImGui.TreeNodeEx(category, nodeFlags | nodeSelectionFlags); + ImGui.SetNextWindowPos(new Vector2(context.camera.width - menuWidth - 0.5f * fontSize, + menuBarHeight + 0.5f * fontSize)); + ImGui.SetNextWindowSize(new Vector2(menuWidth, context.camera.height - menuBarHeight - fontSize)); - if (nodeOpen) - { - while (i < SampleFactory.Shared.SampleCount && category == SampleFactory.Shared.GetCategory(i)) - { - ImGuiTreeNodeFlags selectionFlags = 0; - if (context.sampleIndex == i) - { - selectionFlags = ImGuiTreeNodeFlags.Selected; - } - - ImGui.TreeNodeEx(SampleFactory.Shared.GetName(i), leafNodeFlags | selectionFlags); - if (ImGui.IsItemClicked()) - { - SelectSample(context, i, false); - } - ++i; - } - ImGui.TreePop(); - } - else - { - while (i < SampleFactory.Shared.SampleCount && category == SampleFactory.Shared.GetCategory(i)) - { - ++i; - } - } + ImGui.Begin("Info", + ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | + ImGuiWindowFlags.NoTitleBar); - if (i < SampleFactory.Shared.SampleCount) - { - category = SampleFactory.Shared.GetCategory(i); - categoryIndex = i; - } - } - ImGui.EndTabItem(); - } - ImGui.EndTabBar(); + for (int i = 0; i < context.sample.m_hudLineCount; ++i) + { + HudLine line = context.sample.m_hudLines[i]; + if (string.IsNullOrEmpty(line.text)) + { + ImGui.Separator(); + continue; } + ImGui.PushStyleColor(ImGuiCol.Text, HexToColor(line.color)); + ImGui.TextUnformatted(line.text); + ImGui.PopStyleColor(); + } + + if (context.sample.m_hudLineCount > 0) + { + ImGui.Separator(); + } - ImGui.End(); + context.sample.BuildSamplePanel(); + + ImGui.Separator(); + + if (ImGui.CollapsingHeader("Solver", ImGuiTreeNodeFlags.DefaultOpen)) + { + ImGui.PushItemWidth(6.0f * fontSize); + ImGui.SliderInt("Sub-steps", ref context.subStepCount, 1, 32); + ImGui.SliderFloat("Hertz", ref context.hertz, 5.0f, 240.0f, "%.0f hz"); - context.sample.UpdateGui(); + if (ImGui.SliderInt("Workers", ref context.workerCount, 1, maxWorkers)) + { + context.workerCount = b2ClampInt(context.workerCount, 1, maxWorkers); + SelectSample(context, context.sampleIndex, true); + } + + float recyclingCentimeters = 100.0f * context.recycleDistance; + if (ImGui.SliderFloat("Recycle", ref recyclingCentimeters, 0.0f, 10.0f, "%.1f cm")) + { + context.recycleDistance = 0.01f * recyclingCentimeters; + b2World_SetContactRecycleDistance(context.sample.m_worldId, context.recycleDistance); + } + ImGui.PopItemWidth(); + + ImGui.Checkbox("Sleep", ref context.enableSleep); + ImGui.Checkbox("Warm Starting", ref context.enableWarmStarting); + ImGui.Checkbox("Continuous", ref context.enableContinuous); } + + ImGui.End(); + + context.sample.UpdateGui(); } + + } diff --git a/src/Box2D.NET.Samples/Samples/Shapes/ChainLink.cs b/src/Box2D.NET.Samples/Samples/Shapes/ChainLink.cs index c49e8ce2..559ef6b2 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/ChainLink.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/ChainLink.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -95,7 +95,7 @@ public override void Draw() { base.Draw(); - DrawTextLine("This shows how to link together two chain shapes"); + DrawScreenTextLine("This shows how to link together two chain shapes"); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/ChainSegmentShape.cs b/src/Box2D.NET.Samples/Samples/Shapes/ChainSegmentShape.cs index 25cad6ba..0eadbb1e 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/ChainSegmentShape.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/ChainSegmentShape.cs @@ -162,15 +162,9 @@ public void Mutate() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 130.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Chain Segment Shape", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = { "Circle", "Capsule", "Box" }; int shapeType = (int)m_shapeType; if (ImGui.Combo("Shape", ref shapeType, shapeTypes, shapeTypes.Length)) @@ -179,6 +173,8 @@ public override void UpdateGui() Launch(); } + ImGui.PopItemWidth(); + if (ImGui.Button("Launch")) { Launch(); @@ -188,8 +184,6 @@ public override void UpdateGui() { Mutate(); } - - ImGui.End(); } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Shapes/ChainShape.cs b/src/Box2D.NET.Samples/Samples/Shapes/ChainShape.cs index eca24bcf..3ebb102e 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/ChainShape.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/ChainShape.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -185,17 +185,9 @@ public override void Draw() // DrawTextLine($"toi calls, hits = {b2_toiCalls}, {b2_toiHitCount}"); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 155.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Chain Shape", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = { "Circle", "Capsule", "Box" }; int shapeType = (int)m_shapeType; if (ImGui.Combo("Shape", ref shapeType, shapeTypes, shapeTypes.Length)) @@ -215,11 +207,11 @@ public override void UpdateGui() b2Shape_SetSurfaceMaterial(m_shapeId, m_material); } + ImGui.PopItemWidth(); + if (ImGui.Button("Launch")) { Launch(); } - - ImGui.End(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/CompoundShapes.cs b/src/Box2D.NET.Samples/Samples/Shapes/CompoundShapes.cs index cd2a3da7..c0b1b951 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/CompoundShapes.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/CompoundShapes.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -194,16 +194,9 @@ void Spawn() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(180.0f, height)); - - ImGui.Begin("Compound Shapes", ImGuiWindowFlags.NoResize); if (ImGui.Button("Intrude")) { @@ -212,7 +205,7 @@ public override void UpdateGui() ImGui.Checkbox("Body AABBs", ref m_drawBodyAABBs); - ImGui.End(); + } public override void Draw() diff --git a/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs b/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs index eb3d6d67..a4a485b0 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/CustomFilter.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -100,7 +100,7 @@ public override void Draw() { base.Draw(); - DrawTextLine("Custom filter disables collision between odd and even shapes"); + DrawScreenTextLine("Custom filter disables collision between odd and even shapes"); for (int i = 0; i < e_count; ++i) @@ -109,4 +109,4 @@ public override void Draw() DrawWorldString(m_draw, m_camera, new B2Vec2(p.X, p.Y), B2HexColor.b2_colorWhite, $"{i}"); } } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/Explosion.cs b/src/Box2D.NET.Samples/Samples/Shapes/Explosion.cs index 10121ac8..ef2c6afd 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/Explosion.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/Explosion.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -80,16 +80,9 @@ public Explosion(SampleContext context) : base(context) m_impulse = 10.0f; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 160.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Explosion", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.Button("Explode")) { @@ -101,11 +94,11 @@ public override void UpdateGui() b2World_Explode(m_worldId, def); } + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("radius", ref m_radius, 0.0f, 20.0f, "%.1f"); ImGui.SliderFloat("falloff", ref m_falloff, 0.0f, 20.0f, "%.1f"); ImGui.SliderFloat("impulse", ref m_impulse, -20.0f, 20.0f, "%.1f"); - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -136,4 +129,4 @@ public override void Draw() DrawCircle(m_draw, b2Vec2_zero, m_radius + m_falloff, B2HexColor.b2_colorBox2DBlue); DrawCircle(m_draw, b2Vec2_zero, m_radius, B2HexColor.b2_colorBox2DYellow); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/ModifyGeometry.cs b/src/Box2D.NET.Samples/Samples/Shapes/ModifyGeometry.cs index 4a6d9c74..87472e9c 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/ModifyGeometry.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/ModifyGeometry.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -108,16 +108,9 @@ void UpdateShape() b2Body_ApplyMassFromShapes(bodyId); } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 230.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(200.0f, height)); - - ImGui.Begin("Modify Geometry", ImGuiWindowFlags.NoResize); if (ImGui.RadioButton("Circle", m_shapeType == B2ShapeType.b2_circleShape)) { @@ -143,10 +136,12 @@ public override void UpdateGui() UpdateShape(); } + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Scale", ref m_scale, 0.1f, 10.0f, "%.2f")) { UpdateShape(); } + ImGui.PopItemWidth(); B2BodyId bodyId = b2Shape_GetBody(m_shapeId); B2BodyType bodyType = b2Body_GetType(bodyId); @@ -165,7 +160,5 @@ public override void UpdateGui() { b2Body_SetType(bodyId, B2BodyType.b2_dynamicBody); } - - ImGui.End(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/Restitution.cs b/src/Box2D.NET.Samples/Samples/Shapes/Restitution.cs index dd7294ec..853cca59 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/Restitution.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/Restitution.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -104,17 +104,9 @@ void CreateBodies() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 100.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Restitution", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; string[] shapeTypes = ["Circle", "Box"]; @@ -122,13 +114,13 @@ public override void UpdateGui() changed = changed || ImGui.Combo("Shape", ref shapeType, shapeTypes, shapeTypes.Length); m_shapeType = (ShapeType)shapeType; + ImGui.PopItemWidth(); + changed = changed || ImGui.Button("Reset"); if (changed) { CreateBodies(); } - - ImGui.End(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/ShapeFilter.cs b/src/Box2D.NET.Samples/Samples/Shapes/ShapeFilter.cs index 5525f99a..e788f101 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/ShapeFilter.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/ShapeFilter.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -88,16 +88,9 @@ public ShapeFilter(SampleContext context) : base(context) } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 240.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(240.0f, height)); - - ImGui.Begin("Shape Filter", ImGuiWindowFlags.NoResize); ImGui.Text("Player 1 Collides With"); { @@ -205,7 +198,7 @@ public override void UpdateGui() } } - ImGui.End(); + } public override void Draw() diff --git a/src/Box2D.NET.Samples/Samples/Shapes/TangentSpeed.cs b/src/Box2D.NET.Samples/Samples/Shapes/TangentSpeed.cs index ed99329e..8db394b8 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/TangentSpeed.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/TangentSpeed.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -114,15 +114,10 @@ void Reset() m_bodyIds.Clear(); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 80.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(260.0f, height)); - ImGui.Begin("Ball Parameters", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(140.0f); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); if (ImGui.SliderFloat("Friction", ref m_friction, 0.0f, 2.0f, "%.2f")) { @@ -133,8 +128,7 @@ public override void UpdateGui() { Reset(); } - - ImGui.End(); + ImGui.PopItemWidth(); } public override void Step() @@ -147,4 +141,4 @@ public override void Step() base.Step(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Samples/Shapes/Wind.cs b/src/Box2D.NET.Samples/Samples/Shapes/Wind.cs index 5b71095a..dd485261 100644 --- a/src/Box2D.NET.Samples/Samples/Shapes/Wind.cs +++ b/src/Box2D.NET.Samples/Samples/Shapes/Wind.cs @@ -128,15 +128,10 @@ private void CreateScene() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 15.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(24.0f * fontSize, height)); - ImGui.Begin("Wind", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(18.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); string[] shapeTypes = { "Circle", "Capsule", "Box" }; int shapeType = (int)m_shapeType; @@ -157,7 +152,7 @@ public override void UpdateGui() } ImGui.PopItemWidth(); - ImGui.End(); + } public override void Step() diff --git a/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs b/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs index d7261876..04afd85c 100644 --- a/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs +++ b/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs @@ -13,6 +13,7 @@ using static Box2D.NET.B2Worlds; using static Box2D.NET.B2Contacts; using static Box2D.NET.Samples.Graphics.Draws; +using static Box2D.NET.Samples.SampleText; namespace Box2D.NET.Samples.Samples.Stackings; @@ -104,14 +105,9 @@ void Spawn() b2Body_SetMassData(m_bodyId, massData); } - public override void UpdateGui() + public override void BuildSamplePanel() { - float fontSize = ImGui.GetFontSize(); - float height = 6.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(10.0f * fontSize, height)); - ImGui.Begin("Circle Impulse", ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize); if (ImGui.Checkbox("gravity", ref m_useGravity)) { @@ -123,7 +119,7 @@ public override void UpdateGui() Spawn(); } - ImGui.End(); + } public override void Step() @@ -151,14 +147,14 @@ public override void Step() m_events.Add(e); } - DrawTextLine($"mass = {m_mass}, gravity = {(m_useGravity ? 10.0f : 0.0f)}, restitution = {(m_useRestitution ? m_restitution : 0.0f)}"); + DrawScreenTextLine($"mass = {FormatFloat(m_mass)}, gravity = {FormatFloat(m_useGravity ? 10.0f : 0.0f)}, restitution = {FormatFloat(m_useRestitution ? m_restitution : 0.0f)}"); int eventCount = m_events.Count; var eventsSpan = CollectionsMarshal.AsSpan(m_events); for (int i = 0; i < eventCount; ++i) { ref readonly Event e = ref eventsSpan[i]; - DrawTextLine($"hit speed = {e.speed}, hit momentum = {m_mass * e.speed}, final impulse = {e.impulse}, total impulse = {e.totalImpulse}"); + DrawScreenTextLine($"hit speed = {FormatFloat(e.speed)}, hit momentum = {FormatFloat(m_mass * e.speed)}, final impulse = {FormatFloat(e.impulse)}, total impulse = {FormatFloat(e.totalImpulse)}"); } } } diff --git a/src/Box2D.NET.Samples/Samples/Stackings/Cliff.cs b/src/Box2D.NET.Samples/Samples/Stackings/Cliff.cs index d80f482a..c6432e3b 100644 --- a/src/Box2D.NET.Samples/Samples/Stackings/Cliff.cs +++ b/src/Box2D.NET.Samples/Samples/Stackings/Cliff.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -142,16 +142,9 @@ void CreateBodies() } } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 60.0f; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(160.0f, height)); - - ImGui.Begin("Cliff", ImGuiWindowFlags.NoResize); if (ImGui.Button("Flip")) { @@ -159,6 +152,6 @@ public override void UpdateGui() CreateBodies(); } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Stackings/VerticalStack.cs b/src/Box2D.NET.Samples/Samples/Stackings/VerticalStack.cs index 0d8744ca..e80d7b24 100644 --- a/src/Box2D.NET.Samples/Samples/Stackings/VerticalStack.cs +++ b/src/Box2D.NET.Samples/Samples/Stackings/VerticalStack.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -242,18 +242,11 @@ public override void Keyboard(Keys key) } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - float fontSize = ImGui.GetFontSize(); - float height = 16.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(20.0f * fontSize, height)); - - ImGui.Begin("Vertical Stack", ImGuiWindowFlags.NoResize); - ImGui.PushItemWidth(13.0f * fontSize); + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); bool changed = false; string[] shapeTypes = ["Circle", "Box"]; @@ -292,6 +285,6 @@ public override void UpdateGui() CreateStacks(); } - ImGui.End(); + } } \ No newline at end of file diff --git a/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs b/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs index 3b3cf668..fc3f2178 100644 --- a/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs +++ b/src/Box2D.NET.Samples/Samples/Worlds/LargeWorld.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Erin Catto +// SPDX-FileCopyrightText: 2025 Erin Catto // SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com) // SPDX-License-Identifier: MIT @@ -167,18 +167,11 @@ public LargeWorld(SampleContext context) : base(context) m_followCar = false; } - public override void UpdateGui() + public override void BuildSamplePanel() { - base.UpdateGui(); - - float fontSize = ImGui.GetFontSize(); - float height = 13.0f * fontSize; - ImGui.SetNextWindowPos(new Vector2(0.5f * fontSize, m_camera.height - height - 2.0f * fontSize), ImGuiCond.Once); - ImGui.SetNextWindowSize(new Vector2(18.0f * fontSize, height)); - - ImGui.Begin("Large World", ImGuiWindowFlags.NoResize); - + ImGui.PushItemWidth(6.0f * ImGui.GetFontSize()); ImGui.SliderFloat("speed", ref m_speed, -400.0f, 400.0f, "%.0f"); + ImGui.PopItemWidth(); if (ImGui.Button("stop")) { m_speed = 0.0f; @@ -188,7 +181,7 @@ public override void UpdateGui() ImGui.Checkbox("follow car", ref m_followCar); ImGui.Text($"world size = {m_gridSize * m_gridCount / 1000.0f} kilometers"); - ImGui.End(); + } public override void Step() @@ -251,4 +244,4 @@ public override void Step() base.Step(); } -} \ No newline at end of file +} diff --git a/src/Box2D.NET.Samples/Settings.cs b/src/Box2D.NET.Samples/Settings.cs index b98fbc03..3d172a7e 100644 --- a/src/Box2D.NET.Samples/Settings.cs +++ b/src/Box2D.NET.Samples/Settings.cs @@ -14,41 +14,10 @@ public class Settings public const int MAX_TOKENS = 32; public const string fileName = "settings.ini"; - public int windowWidth = 1920; - public int windowHeight = 1080; - - // - public float uiScale = 1.0f; - public float hertz = 60.0f; - public float jointScale = 1.0f; - public float forceScale = 1.0f; - public int subStepCount = 4; - public int workerCount = 1; - - // - public bool singleStep = false; - public bool drawJointExtras = false; - public bool drawBounds = false; - public bool drawMass = false; - public bool drawBodyNames = false; - public bool drawContactNormals = false; - public bool drawContactFeatures = false; - public bool drawContactForces = false; - public bool drawFrictionForces = false; - public bool drawIslands = false; - public bool drawGraphColors = false; - public bool drawCounters = false; - public bool drawProfile = false; - public bool frameTime = false; - public bool enableWarmStarting = true; - public bool enableContinuous = true; - public bool enableSleep = true; - - // public int sampleIndex = 0; public bool drawShapes = true; - public bool drawChainNormals = false; public bool drawJoints = true; + public bool showDiagnostics = false; public static void Save(SampleContext context) { @@ -73,39 +42,10 @@ public static Settings Load() public static Settings CopyFrom(SampleContext context) { var setting = new Settings(); - setting.windowWidth = (int)context.camera.width; - setting.windowHeight = (int)context.camera.height; - setting.sampleIndex = context.sampleIndex; - // - setting.uiScale = context.uiScale; - setting.hertz = context.hertz; - setting.subStepCount = context.subStepCount; - setting.workerCount = context.workerCount; - - // - setting.drawCounters = context.drawCounters; - setting.drawProfile = context.drawProfile; - setting.frameTime = context.frameTime; - setting.enableWarmStarting = context.enableWarmStarting; - setting.enableContinuous = context.enableContinuous; - setting.enableSleep = context.enableSleep; - setting.singleStep = context.singleStep; - - // setting.drawShapes = context.debugDraw.drawShapes; - setting.drawChainNormals = context.debugDraw.drawChainNormals; setting.drawJoints = context.debugDraw.drawJoints; - setting.drawJointExtras = context.debugDraw.drawJointExtras; - setting.drawBounds = context.debugDraw.drawBounds; - setting.drawMass = context.debugDraw.drawMass; - setting.drawBodyNames = context.debugDraw.drawBodyNames; - setting.drawContactNormals = context.debugDraw.drawContactNormals; - setting.drawContactForces = context.debugDraw.drawContactForces; - setting.drawContactFeatures = context.debugDraw.drawContactFeatures; - setting.drawFrictionForces = context.debugDraw.drawFrictionForces; - setting.drawIslands = context.debugDraw.drawIslands; - setting.drawGraphColors = context.debugDraw.drawGraphColors; + setting.showDiagnostics = context.showDiagnostics; return setting; } diff --git a/src/Box2D.NET.Samples/Shader.cs b/src/Box2D.NET.Samples/Shader.cs index c144f309..6f2f9f61 100644 --- a/src/Box2D.NET.Samples/Shader.cs +++ b/src/Box2D.NET.Samples/Shader.cs @@ -3,8 +3,6 @@ // SPDX-License-Identifier: MIT using System; -using System.IO; -using System.Text; using Serilog; using Silk.NET.OpenGL; using static Box2D.NET.B2Diagnostics; @@ -131,67 +129,4 @@ public static uint CreateProgramFromStrings(this GL gl, string vertexString, str return program; } - public static uint sCreateShaderFromFile(this GL gl, string filename, GLEnum type) - { - if (!File.Exists(filename)) - { - Logger.Information($"Error opening {filename}"); - return 0; - } - - byte[] bytes = File.ReadAllBytes(filename); - var source = Encoding.UTF8.GetString(bytes); - - - uint shader = gl.CreateShader(type); - - gl.ShaderSource(shader, source); - gl.CompileShader(shader); - - Span success = stackalloc int[1]; - gl.GetShader(shader, GLEnum.CompileStatus, success); - - if (success[0] == 0) - { - Logger.Information($"Error compiling shader of type {type}!"); - gl.PrintLogGL(shader); - } - - return shader; - } - - public static uint CreateProgramFromFiles(this GL gl, string vertexPath, string fragmentPath) - { - uint vertex = gl.sCreateShaderFromFile(vertexPath, GLEnum.VertexShader); - if (vertex == 0) - { - return 0; - } - - uint fragment = gl.sCreateShaderFromFile(fragmentPath, GLEnum.FragmentShader); - if (fragment == 0) - { - return 0; - } - - uint program = gl.CreateProgram(); - gl.AttachShader(program, vertex); - gl.AttachShader(program, fragment); - - gl.LinkProgram(program); - - Span success = stackalloc int[1]; - gl.GetProgram(program, GLEnum.LinkStatus, success); - if (success[0] == 0) - { - Logger.Information("glLinkProgram:"); - gl.PrintLogGL(program); - return 0; - } - - gl.DeleteShader(vertex); - gl.DeleteShader(fragment); - - return program; - } -} \ No newline at end of file +} diff --git a/test/Box2D.NET.Test/Box2D.NET.Test.csproj b/test/Box2D.NET.Test/Box2D.NET.Test.csproj index a85885b7..ee9f72b8 100644 --- a/test/Box2D.NET.Test/Box2D.NET.Test.csproj +++ b/test/Box2D.NET.Test/Box2D.NET.Test.csproj @@ -31,6 +31,7 @@ + diff --git a/test/Box2D.NET.Test/SampleTextTests.cs b/test/Box2D.NET.Test/SampleTextTests.cs new file mode 100644 index 00000000..329aedb3 --- /dev/null +++ b/test/Box2D.NET.Test/SampleTextTests.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2026 Ikpil Choi(ikpil@naver.com) +// SPDX-License-Identifier: MIT + +using System.Globalization; +using Box2D.NET.Samples; +using NUnit.Framework; + +namespace Box2D.NET.Test; + +public class SampleTextTests +{ + [TestCase(1.2345678f, "1.23457")] + [TestCase(0.0000001f, "1e-07")] + [TestCase(10000000.0f, "1e+07")] + [TestCase(float.PositiveInfinity, "inf")] + [TestCase(float.NegativeInfinity, "-inf")] + public void FormatFloatMatchesPrintfGeneralFormat(float value, string expected) + { + Assert.That(SampleText.FormatFloat(value), Is.EqualTo(expected)); + } + + [Test] + public void FormatFloatDoesNotUseCurrentCulture() + { + CultureInfo previousCulture = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("de-DE"); + Assert.That(SampleText.FormatFloat(1.25f), Is.EqualTo("1.25")); + } + finally + { + CultureInfo.CurrentCulture = previousCulture; + } + } +}