From e33d67c48488cafbbdc26a963d8eaf7285b70add Mon Sep 17 00:00:00 2001 From: ikpil Date: Thu, 13 Aug 2026 23:59:54 +0900 Subject: [PATCH 1/2] [upstream] Update UI (erincatto/box2d#1061) Port upstream commit 6e96697064e1ec09265c64fe953e5e056098c215 to C#. Revised the samples UI. imgui now has an embedded vector font. This saves users from having to run the samples app from the correct working directory, yet still works well with display scale. shaders are now embedded as a header file by a CMake process now using imgui for viewport text, simplifying the drawing code --- data/font.fs | 16 - data/font.vs | 21 - .../Box2D.NET.Samples.csproj | 7 +- src/Box2D.NET.Samples/EmbeddedShaders.cs | 35 + src/Box2D.NET.Samples/Graphics/Backgrounds.cs | 3 +- src/Box2D.NET.Samples/Graphics/Cameras.cs | 23 - src/Box2D.NET.Samples/Graphics/Circles.cs | 5 +- src/Box2D.NET.Samples/Graphics/Draw.cs | 3 +- src/Box2D.NET.Samples/Graphics/DrawText.cs | 35 + src/Box2D.NET.Samples/Graphics/Draws.cs | 19 +- src/Box2D.NET.Samples/Graphics/Font.cs | 38 - src/Box2D.NET.Samples/Graphics/Fonts.cs | 59 - src/Box2D.NET.Samples/Graphics/FontsV1.cs | 237 --- src/Box2D.NET.Samples/Graphics/Lines.cs | 5 +- src/Box2D.NET.Samples/Graphics/Points.cs | 5 +- .../Graphics/SolidCapsules.cs | 5 +- .../Graphics/SolidCircles.cs | 5 +- .../Graphics/SolidPolygons.cs | 5 +- src/Box2D.NET.Samples/Primitives/FontText.cs | 9 - .../Primitives/FontVertex.cs | 19 - src/Box2D.NET.Samples/SampleApp.cs | 249 +++- src/Box2D.NET.Samples/SampleContext.cs | 55 +- .../Samples/Benchmarks/BenchmarkBarrel.cs | 13 +- .../Samples/Benchmarks/BenchmarkCast.cs | 20 +- .../Benchmarks/BenchmarkCreateDestroy.cs | 8 +- .../Benchmarks/BenchmarkManyTumblers.cs | 14 +- .../Samples/Benchmarks/BenchmarkSensor.cs | 8 +- .../Benchmarks/BenchmarkShapeDistance.cs | 16 +- .../Samples/Benchmarks/BenchmarkSmash.cs | 11 +- .../Samples/Bodies/BadBody.cs | 8 +- .../Samples/Bodies/BodyType.cs | 12 +- src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs | 6 +- src/Box2D.NET.Samples/Samples/Bodies/Sleep.cs | 14 +- .../Samples/Bodies/WakeTouching.cs | 9 +- .../Samples/Bodies/Weeble.cs | 14 +- .../Samples/Characters/Mover.cs | 18 +- .../Samples/Collisions/CastWorld.cs | 29 +- .../Samples/Collisions/DynamicTree.cs | 24 +- .../Samples/Collisions/Manifold.cs | 13 +- .../Samples/Collisions/OverlapWorld.cs | 19 +- .../Samples/Collisions/RayCast.cs | 20 +- .../Samples/Collisions/ShapeCast.cs | 28 +- .../Samples/Collisions/ShapeDistance.cs | 30 +- .../Samples/Collisions/SmoothManifold.cs | 18 +- .../Samples/Collisions/TimeOfImpact.cs | 4 +- .../Samples/Continuous/BounceHouse.cs | 19 +- .../Samples/Continuous/ChainDrop.cs | 19 +- .../Samples/Continuous/GhostBumps.cs | 15 +- .../Samples/Continuous/PixelImperfect.cs | 6 +- .../Continuous/RestitutionThreshold.cs | 6 +- .../Samples/Continuous/SkinnyBox.cs | 13 +- .../Samples/Determinisms/FallingHinges.cs | 6 +- .../Samples/Events/BodyMove.cs | 17 +- .../Samples/Events/ContactEvent.cs | 21 +- .../Samples/Events/Platform.cs | 23 +- .../Samples/Events/ProjectileEvent.cs | 4 +- .../Samples/Events/SensorBookend.cs | 11 +- .../Samples/Events/SensorFunnel.cs | 14 +- .../Samples/Events/SensorHits.cs | 9 +- .../Samples/Events/SensorTypes.cs | 2 +- .../Samples/Geometries/ConvexHull.cs | 8 +- .../Samples/Issues/Crash01.cs | 10 +- .../Samples/Issues/DisableCrash.cs | 12 +- .../Samples/Joints/BallAndChain.cs | 19 +- .../Samples/Joints/BreakableJoint.cs | 19 +- .../Samples/Joints/Bridge.cs | 15 +- .../Samples/Joints/Cantilever.cs | 28 +- .../Samples/Joints/DistanceJoint.cs | 15 +- src/Box2D.NET.Samples/Samples/Joints/Door.cs | 25 +- .../Samples/Joints/Driving.cs | 19 +- .../Samples/Joints/GearLift.cs | 16 +- .../Samples/Joints/JointSeparation.cs | 34 +- .../Samples/Joints/MotionLocks.cs | 13 +- .../Samples/Joints/MotorJoint.cs | 20 +- .../Samples/Joints/PrismaticJoint.cs | 19 +- .../Samples/Joints/Ragdoll.cs | 19 +- .../Samples/Joints/RevoluteJoint.cs | 19 +- .../Samples/Joints/ScaleRagdoll.cs | 17 +- .../Samples/Joints/ScissorLift.cs | 18 +- .../Samples/Joints/TopDownFriction.cs | 11 +- .../Samples/Joints/WheelJoint.cs | 19 +- .../Samples/Robustness/Cart.cs | 15 +- .../Samples/Robustness/OverlapRecovery.cs | 15 +- src/Box2D.NET.Samples/Samples/Sample.cs | 1298 +++++++++++------ .../Samples/Shapes/ChainLink.cs | 6 +- .../Samples/Shapes/ChainSegmentShape.cs | 14 +- .../Samples/Shapes/ChainShape.cs | 20 +- .../Samples/Shapes/CompoundShapes.cs | 13 +- .../Samples/Shapes/CustomFilter.cs | 6 +- .../Samples/Shapes/Explosion.cs | 17 +- .../Samples/Shapes/ModifyGeometry.cs | 17 +- .../Samples/Shapes/Restitution.cs | 20 +- .../Samples/Shapes/ShapeFilter.cs | 13 +- .../Samples/Shapes/TangentSpeed.cs | 16 +- src/Box2D.NET.Samples/Samples/Shapes/Wind.cs | 11 +- .../Samples/Stackings/CircleImpulse.cs | 13 +- .../Samples/Stackings/Cliff.cs | 13 +- .../Samples/Stackings/VerticalStack.cs | 15 +- .../Samples/Worlds/LargeWorld.cs | 19 +- src/Box2D.NET.Samples/Settings.cs | 64 +- src/Box2D.NET.Samples/Shader.cs | 67 +- 101 files changed, 1517 insertions(+), 1929 deletions(-) delete mode 100644 data/font.fs delete mode 100644 data/font.vs create mode 100644 src/Box2D.NET.Samples/EmbeddedShaders.cs create mode 100644 src/Box2D.NET.Samples/Graphics/DrawText.cs delete mode 100644 src/Box2D.NET.Samples/Graphics/Font.cs delete mode 100644 src/Box2D.NET.Samples/Graphics/Fonts.cs delete mode 100644 src/Box2D.NET.Samples/Graphics/FontsV1.cs delete mode 100644 src/Box2D.NET.Samples/Primitives/FontText.cs delete mode 100644 src/Box2D.NET.Samples/Primitives/FontVertex.cs 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..08aeefaa 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,38 @@ private void CreateUI(string glslVersion) // } // - var fontPath = Path.Combine("data", "droid_sans.ttf"); - if (!File.Exists(fontPath)) + // 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")!) { - Logger.Information("ERROR: the Box2D samples working directory must be the top level Box2D directory (same as README.md)"); - //exit(EXIT_FAILURE); - return; + _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, () => + { + ImGuiIOPtr io = ImGui.GetIO(); + 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.Clear(); + io.Fonts.AddFontFromMemoryTTF(_fontDataHandle.AddrOfPinnedObject(), _fontData.Length, regularSize, fontConfig); + ImGuiNative.ImFontConfig_destroy(fontConfig.NativePtr); + } + }); - ImGui.GetFontSize(); - ImGui.GetStyle().ScaleAllSizes(_context.uiScale); + ApplyUIStyle(); - 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 +581,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 +675,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 +722,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/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..f646f61b 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 @@ -138,11 +138,11 @@ public override void Draw() { base.Draw(); - DrawTextLine($"total: create = {m_createTime} ms, destroy = {m_destroyTime} ms"); + DrawScreenTextLine($"total: create = {m_createTime} ms, destroy = {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 = {createPerBody} us, destroy = {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..f4feadd8 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 @@ -97,17 +97,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 +139,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 = {m_minMilliseconds}, ave us = {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..369d1346 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 @@ -76,7 +76,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 = ({vp.X:g}, {vp.Y:g})"); } -} \ 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..1f33dfae 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 @@ -297,15 +297,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 +335,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 +381,7 @@ public override void Draw() { base.Draw(); - DrawTextLine($"hit = {output.hit}, iterations = {output.iterations}, fraction = {output.fraction}, distance = {_distanceOutput.distance}"); + DrawScreenTextLine($"hit = {output.hit}, iterations = {output.iterations}, fraction = {output.fraction}, distance = {_distanceOutput.distance}"); DrawShape(m_typeA, b2Transform_identity, m_radiusA, B2HexColor.b2_colorCyan); DrawShape(m_typeB, m_transform, m_radiusB, B2HexColor.b2_colorLightGreen); @@ -425,9 +419,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..b0f41325 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,11 +436,11 @@ 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) @@ -459,7 +453,7 @@ public override void Draw() } 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..693dc9d5 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($"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..c9572fba 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,7 @@ using static Box2D.NET.Samples.Graphics.Draws; using static Box2D.NET.Samples.Graphics.Cameras; using static Box2D.NET.B2Constants; +using static Box2D.NET.B2Cores; namespace Box2D.NET.Samples.Samples; @@ -30,6 +32,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 +79,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 +93,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 +119,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 +201,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, 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,34 +609,6 @@ 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) - { - 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) @@ -603,7 +681,16 @@ private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 s } 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 +798,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 +859,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 +913,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 +951,486 @@ public static void SelectSample(SampleContext context, int selection, bool resta context.restart = false; } + // 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) + { + if (string.IsNullOrEmpty(needle)) + { + return 0; + } + + int score = 0; + int hi = 0; + int previousMatchHi = -2; + + for (int ni = 0; ni < needle.Length; ++ni) + { + 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; + } + + return score; + } + + 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) + { + 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; + } + + // 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; + } + + private static void AddHelpRow(string key, string description) + { + ImGui.TableNextRow(); + ImGui.TableSetColumnIndex(0); + ImGui.TextUnformatted(key); + ImGui.TableSetColumnIndex(1); + ImGui.TextUnformatted(description); + } + + private static void TextLinkOpenURL(string label, string url) + { + // ImGui.NET 1.90 does not expose TextLinkOpenURL, so preserve the same click behavior here. + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.26f, 0.59f, 0.98f, 1.0f)); + ImGui.TextUnformatted(label); + ImGui.PopStyleColor(); + if (ImGui.IsItemClicked()) + { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } + } + public static void UpdateSampleUI(SampleContext context) { int maxWorkers = B2_MAX_WORKERS; - B2WorldId worldId = context.sample.m_worldId; float fontSize = ImGui.GetFontSize(); - float menuWidth = 13.0f * fontSize; - if (context.showUI) - { - ImGui.SetNextWindowPos(new Vector2(context.camera.width - menuWidth - 0.5f * fontSize, 0.5f * fontSize)); - ImGui.SetNextWindowSize(new Vector2(menuWidth, context.camera.height - fontSize)); + float menuWidth = 14.0f * fontSize; - ImGui.Begin("Tools", ref context.showUI, ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse); + if (context.showUI == false) + { + return; + } - if (ImGui.BeginTabBar("ControlTabs", ImGuiTabBarFlags.None)) + if (ImGui.BeginMainMenuBar()) + { + if (ImGui.BeginMenu("Sim")) { - if (ImGui.BeginTabItem("Controls")) + 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")) { - 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"); + b2World_DumpMemoryStats(context.sample.m_worldId); + } + ImGui.Separator(); + if (ImGui.MenuItem("Quit", "Esc")) + { + unsafe + { + context.glfw.SetWindowShouldClose(context.window, true); + } + } + ImGui.EndMenu(); + } - if (ImGui.SliderInt("Workers", ref context.workerCount, 1, maxWorkers)) + 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.workerCount = b2ClampInt(context.workerCount, 1, maxWorkers); - SelectSample(context, context.sampleIndex, true); + context.debugDraw.drawAnchorA = true; + } + if (ImGui.MenuItem("Anchor B", null, context.debugDraw.drawAnchorA == false)) + { + context.debugDraw.drawAnchorA = false; } + 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(); + } - ImGui.Separator(); - - ImGui.Checkbox("Sleep", ref context.enableSleep); - ImGui.Checkbox("Warm Starting", ref context.enableWarmStarting); - ImGui.Checkbox("Continuous", ref context.enableContinuous); - - ImGui.PushItemWidth(100.0f); - float recyclingCentimeters = 100.0f * context.recycleDistance; - if (ImGui.SliderFloat("Recycle", ref recyclingCentimeters, 0.0f, 10.0f, "%.1f cm")) + if (ImGui.BeginMenu("Samples")) + { + int i = 0; + while (i < SampleFactory.Shared.SampleCount) + { + string category = SampleFactory.Shared.GetCategory(i); + if (ImGui.BeginMenu(category)) { - context.recycleDistance = 0.01f * recyclingCentimeters; - b2World_SetContactRecycleDistance(worldId, context.recycleDistance); + 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(); } - ImGui.PopItemWidth(); + else + { + while (i < SampleFactory.Shared.SampleCount && category == SampleFactory.Shared.GetCategory(i)) + { + ++i; + } + } + } + ImGui.EndMenu(); + } - ImGui.Separator(); + if (ImGui.BeginMenu("Help")) + { + ImGui.MenuItem("Controls", null, ref s_showHelp); + ImGui.MenuItem("About", null, ref s_showAbout); + ImGui.EndMenu(); + } - 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); + ImGui.EndMainMenuBar(); - ImGui.Separator(); + { + 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); + } - ImGui.Checkbox("Contact Points", ref context.debugDraw.drawContacts); + 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.RadioButton("Anchor A", context.debugDraw.drawAnchorA == true)) + if (ImGui.Begin("Controls", ref s_showHelp, + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.SeparatorText("Keyboard"); + if (ImGui.BeginTable("keys", 2, ImGuiTableFlags.SizingFixedFit)) { - context.debugDraw.drawAnchorA = true; + 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(); } - ImGui.SameLine(); - if (ImGui.RadioButton("Anchor B", context.debugDraw.drawAnchorA == false)) + + ImGui.SeparatorText("Mouse"); + if (ImGui.BeginTable("mouse", 2, ImGuiTableFlags.SizingFixedFit)) { - context.debugDraw.drawAnchorA = false; + AddHelpRow("Left drag", "Move bodies (mouse joint)"); + AddHelpRow("Right drag", "Pan camera"); + AddHelpRow("Scroll wheel", "Zoom"); + ImGui.EndTable(); } - 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.End(); + } - ImGui.Separator(); + 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); - ImGui.PushItemWidth(80.0f); - ImGui.InputFloat("Joint Scale", ref context.debugDraw.jointScale); - ImGui.InputFloat("Force Scale", ref context.debugDraw.forceScale); - ImGui.PopItemWidth(); + 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/erincatto/box2d", "https://github.com/erincatto/box2d"); + } + ImGui.End(); + } + } - Vector2 button_sz = new Vector2(-1, 0); - if (ImGui.Button("Pause (P)", button_sz)) - { - context.pause = !context.pause; - } + // 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; + } - if (ImGui.Button("Single Step (O)", button_sz)) - { - context.singleStep = !context.singleStep; - } + 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("Dump Mem Stats", 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)) { - b2World_DumpMemoryStats(context.sample.m_worldId); + s_pickerHighlight = (s_pickerHighlight + 1) % s_filteredCount; } - - if (ImGui.Button("Reset Profile", button_sz)) + if (ImGui.IsKeyPressed(ImGuiKey.UpArrow, true)) { - context.sample.ResetProfile(); + s_pickerHighlight = (s_pickerHighlight + s_filteredCount - 1) % s_filteredCount; } + } + bool commit = ImGui.IsKeyPressed(ImGuiKey.Enter, false) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter, false); - if (ImGui.Button("Restart (R)", 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)) { - SelectSample(context, context.sampleIndex, true); + s_pickerHighlight = row; + commit = true; } - - if (ImGui.Button("Quit", button_sz)) + if (selected && (s_forcePickerScroll || s_pickerHighlight != s_previousPickerHighlight)) { - unsafe - { - context.glfw.SetWindowShouldClose(context.window, true); - } + 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..4dacd5ef 100644 --- a/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs +++ b/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs @@ -104,14 +104,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 +118,7 @@ public override void UpdateGui() Spawn(); } - ImGui.End(); + } public override void Step() @@ -151,14 +146,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 = {m_mass}, gravity = {(m_useGravity ? 10.0f : 0.0f)}, restitution = {(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 = {e.speed}, hit momentum = {m_mass * e.speed}, final impulse = {e.impulse}, total impulse = {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 +} From 22973811da0954933d0652df79bdee8b05094115 Mon Sep 17 00:00:00 2001 From: ikpil Date: Fri, 14 Aug 2026 00:36:02 +0900 Subject: [PATCH 2/2] [upstream] Fix Update UI port fidelity (erincatto/box2d#1061) Align the C# port with erincatto/box2d commit 6e96697064e1ec09265c64fe953e5e056098c215. Match the default font path, profile plot time scale, diagnostic text formatting, cache and joint output, and link interaction. Keep the About link adapted for ikpil/Box2D.NET and add coverage for printf-style general float formatting. --- src/Box2D.NET.Samples/SampleApp.cs | 35 +++++---- src/Box2D.NET.Samples/SampleText.cs | 30 +++++++ .../Benchmarks/BenchmarkCreateDestroy.cs | 5 +- .../Benchmarks/BenchmarkShapeDistance.cs | 3 +- src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs | 3 +- .../Samples/Collisions/ShapeCast.cs | 3 +- .../Samples/Collisions/ShapeDistance.cs | 6 +- .../Samples/Joints/MotorJoint.cs | 2 +- src/Box2D.NET.Samples/Samples/Sample.cs | 78 +++++++++++++------ .../Samples/Stackings/CircleImpulse.cs | 5 +- test/Box2D.NET.Test/Box2D.NET.Test.csproj | 1 + test/Box2D.NET.Test/SampleTextTests.cs | 36 +++++++++ 12 files changed, 159 insertions(+), 48 deletions(-) create mode 100644 src/Box2D.NET.Samples/SampleText.cs create mode 100644 test/Box2D.NET.Test/SampleTextTests.cs diff --git a/src/Box2D.NET.Samples/SampleApp.cs b/src/Box2D.NET.Samples/SampleApp.cs index 08aeefaa..b14a2178 100644 --- a/src/Box2D.NET.Samples/SampleApp.cs +++ b/src/Box2D.NET.Samples/SampleApp.cs @@ -541,9 +541,10 @@ private void CreateUI(string glslVersion) // } // - // 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")!) + if (_context.uiScale != 1.0f) { + // 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); @@ -553,23 +554,29 @@ private void CreateUI(string glslVersion) // link - https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist _imgui = new ImGuiController(_context.gl, _window, _input, () => { + ApplyUIStyle(); + ImGuiIOPtr io = ImGui.GetIO(); - 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; - fontConfig.FontDataOwnedByAtlas = false; - - float regularSize = MathF.Floor(13.0f * _context.uiScale); - io.Fonts.Clear(); - io.Fonts.AddFontFromMemoryTTF(_fontDataHandle.AddrOfPinnedObject(), _fontData.Length, regularSize, fontConfig); - ImGuiNative.ImFontConfig_destroy(fontConfig.NativePtr); + 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); + } } }); - ApplyUIStyle(); - if (_context.uiScale != 1.0f) { ImGui.GetStyle().ScaleAllSizes(_context.uiScale); 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/BenchmarkCreateDestroy.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs index f646f61b..c22400bb 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkCreateDestroy.cs @@ -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(); - DrawScreenTextLine($"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; - DrawScreenTextLine($"body: create = {createPerBody} us, destroy = {destroyPerBody} us"); + DrawScreenTextLine($"body: create = {FormatFloat(createPerBody)} us, destroy = {FormatFloat(destroyPerBody)} us"); } } diff --git a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs index f4feadd8..bd297931 100644 --- a/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs +++ b/src/Box2D.NET.Samples/Samples/Benchmarks/BenchmarkShapeDistance.cs @@ -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; @@ -139,7 +140,7 @@ public override void Draw() if (m_context.pause == false || m_context.singleStep == true) { DrawTextLine($"count = {m_count}"); - DrawScreenTextLine($"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/Bodies/Pivot.cs b/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs index 369d1346..533331a6 100644 --- a/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs +++ b/src/Box2D.NET.Samples/Samples/Bodies/Pivot.cs @@ -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); - DrawScreenTextLine($"pivot velocity = ({vp.X:g}, {vp.Y:g})"); + DrawScreenTextLine($"pivot velocity = ({FormatFloat(vp.X)}, {FormatFloat(vp.Y)})"); } } diff --git a/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs b/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs index 1f33dfae..00234fa7 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/ShapeCast.cs @@ -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; @@ -381,7 +382,7 @@ public override void Draw() { base.Draw(); - DrawScreenTextLine($"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); diff --git a/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs b/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs index b0f41325..55b4d0f0 100644 --- a/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs +++ b/src/Box2D.NET.Samples/Samples/Collisions/ShapeDistance.cs @@ -445,15 +445,15 @@ public override void Draw() 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) { - 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]}"); + 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]}}}"); } } } diff --git a/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs b/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs index 693dc9d5..eb93eaa0 100644 --- a/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs +++ b/src/Box2D.NET.Samples/Samples/Joints/MotorJoint.cs @@ -188,7 +188,7 @@ public override void Draw() B2Vec2 force = b2Joint_GetConstraintForce(m_jointId); float torque = b2Joint_GetConstraintTorque(m_jointId); - DrawScreenTextLine($"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/Sample.cs b/src/Box2D.NET.Samples/Samples/Sample.cs index c9572fba..bb9ec5a3 100644 --- a/src/Box2D.NET.Samples/Samples/Sample.cs +++ b/src/Box2D.NET.Samples/Samples/Sample.cs @@ -24,6 +24,7 @@ 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; @@ -506,7 +507,7 @@ void AddSegment(float t, Vector4 color) } // This project does not depend on ImPlot, so draw the same profile data with ImGui's draw list. - DrawProfilePlot("Profile", count, maxValue, ImGui.GetContentRegionAvail()); + DrawProfilePlot("Profile", count, maxValue, m_profileCapacity / 60.0f, ImGui.GetContentRegionAvail()); ImGui.EndTabItem(); } @@ -609,15 +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 DrawProfilePlot(string label, int count, float maxValue, Vector2 size) + private void DrawProfilePlot(string label, int count, float maxValue, float maxTime, 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) @@ -630,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) { @@ -661,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); @@ -679,6 +700,7 @@ private void DrawProfileSeries(ImDrawListPtr drawList, Vector2 origin, Vector2 s previous = current; } } + public void ResetText() { m_hudLineCount = 0; @@ -1055,10 +1077,20 @@ private static void AddHelpRow(string key, string description) private static void TextLinkOpenURL(string label, string url) { - // ImGui.NET 1.90 does not expose TextLinkOpenURL, so preserve the same click behavior here. - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0.26f, 0.59f, 0.98f, 1.0f)); + // 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); + } + if (ImGui.IsItemClicked()) { Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); @@ -1280,7 +1312,7 @@ public static void UpdateSampleUI(SampleContext context) ImGui.Text($"Box2D {version.major}.{version.minor}.{version.revision}"); ImGui.Spacing(); TextLinkOpenURL("box2d.org", "https://box2d.org/"); - TextLinkOpenURL("github.com/erincatto/box2d", "https://github.com/erincatto/box2d"); + TextLinkOpenURL("github.com/ikpil/Box2D.NET", "https://github.com/ikpil/Box2D.NET"); } ImGui.End(); } diff --git a/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs b/src/Box2D.NET.Samples/Samples/Stackings/CircleImpulse.cs index 4dacd5ef..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; @@ -146,14 +147,14 @@ public override void Step() m_events.Add(e); } - DrawScreenTextLine($"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]; - DrawScreenTextLine($"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/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; + } + } +}