diff --git a/Lab4/Algorithm.cs b/Lab4/Algorithm.cs new file mode 100644 index 0000000..67730fd --- /dev/null +++ b/Lab4/Algorithm.cs @@ -0,0 +1,282 @@ +using System.Diagnostics; + +namespace Lab4 +{ + public class Algorithm + { + /* + Faile places_data.xlsx pateikta informacija apie lankytinas vietas (1 lentelė). Tikslas: kaip galima pigesnio maršruto sudarymas kai, + + * priimama, kad kelionės tarp vietų kaina lygi kvadratinei šakniai iš kelionės atstumo; + * kelionės pradžios ir pabaigos vieta sutampa (su grįžimu atgal); + * ta pati vieta negali būti aplankyta daugiau nei vieną; + * Reikia aplankyti bet kurias 150 vietų iš pateikto sąrašo. + */ + + public int pathLength = 150; + public int batchSize = 40; + public float mutationPickUniqueChance = 0.005f; + public float mutationSwapChance = 0.01f; + public float mutationPickLocalChance = 0.005f; + public float localAreaRadius = 70000.000f; + public int seed; + public int iteration = 0; + public SortedList> paths; + public List> localPlaces; + + List places; + Random rand; + + public Algorithm(List places, int seed) + { + this.places = places; + this.seed = seed; + rand = new Random(seed); + paths = GenerateInitialPaths(rand, places, pathLength, batchSize); + localPlaces = ComputeLocalNodes(places, localAreaRadius); + } + + public Algorithm(List places) : this(places, (int)DateTime.Now.Ticks) { } + + public void Reset() + { + rand = new Random(seed); + paths = GenerateInitialPaths(rand, places, pathLength, batchSize); + localPlaces = ComputeLocalNodes(places, localAreaRadius); + iteration = 0; + } + + public class Place + { + public string name; + public float x; + public float y; + public Place(string name, float x, float y) + { + this.name = name; + this.x = x; + this.y = y; + } + } + + public static double GetDistance(float x0, float y0, float x1, float y1) + { + float dx = x0 - x1; + float dy = y0 - y1; + return Math.Sqrt(dx * dx + dy * dy); + } + + public static double GetDistance(Place A, Place B) + { + return GetDistance(A.x, A.y, B.x, B.y); + } + + public static double GetTravalCost(Place A, Place B) + { + return Math.Sqrt(GetDistance(A, B)); + } + + public static List ReadPlacesFromTSV(string filename) + { + List cities = new List(); + foreach (var line in File.ReadLines(filename)) + { + string[] parts = line.Split("\t"); + + string name = parts[0]; + float x = float.Parse(parts[2]); + float y = float.Parse(parts[3]); + cities.Add(new Place(name, x, y)); + } + return cities; + } + + // Warning 'PickUnqiueNode' could get stuck in an infinite loop, if there are not enough places + public static int PickUnqiueNode(Random rand, List path, int placesCount) + { + while (true) + { + int num = rand.Next(0, placesCount); + if (!path.Contains(num)) + { + return num; + } + } + } + + public static List> ComputeLocalNodes(List places, float localRadius) + { + var locals = new List>(places.Count); + for (int i = 0; i < places.Count; i++) + { + var nodeLocals = new List(); + for (int j = 0; j < places.Count; j++) + { + if (i == j) continue; + + if (GetDistance(places[i], places[j]) < localRadius) + { + nodeLocals.Add(j); + } + } + locals.Add(nodeLocals); + } + return locals; + } + + public static int PickUniqueLocalNode(Random rand, List path, List localPlaces) + { + if (localPlaces.Count == 0) return -1; + + int retryCount = 5; + for (int i = 0; i < retryCount; i++) + { + int idx = rand.Next(0, localPlaces.Count); + int place = localPlaces[idx]; + if (!path.Contains(place)) + { + return place; + } + } + return -1; + } + + public static List GenerateRandomPath(Random rand, int size, int placesCount) + { + List path = new List(size); + for (int i = 0; i < size; i++) + { + path.Add(PickUnqiueNode(rand, path, placesCount)); + } + return path; + } + + public static double GetPathCost(List path, List places) + { + double cost = 0; + for (int i = 0; i < path.Count - 1; i++) + { + int from = path[i]; + int to = path[i + 1]; + cost += GetTravalCost(places[from], places[to]); + } + cost += GetTravalCost(places[path.First()], places[path.Last()]); + return cost; + } + + public static List SplicePaths(Random rand, List A, List B) + { + Debug.Assert(A.Count == B.Count); + + List spliced = new List(A.Count); + for (int i = 0; i < A.Count - 1; i++) + { + spliced[i] = rand.Next(0, 2) == 0 ? A[i] : B[i]; + } + return spliced; + } + + public static SortedList> GenerateInitialPaths(Random rand, List places, int pathLength, int pathCount) + { + var paths = new SortedList>(); + for (int i = 0; i < pathCount; i++) + { + var path = GenerateRandomPath(rand, pathLength, places.Count); + var cost = GetPathCost(path, places); + paths.Add(cost, path); + } + return paths; + } + + + public void SetLocalRadius(float radius) + { + localAreaRadius = radius; + localPlaces = ComputeLocalNodes(places, localAreaRadius); + } + + public void MutatePath(List path) + { + for (int i = 0; i < path.Count; i++) + { + if (rand.NextSingle() < mutationPickUniqueChance) + { + path[i] = PickUnqiueNode(rand, path, places.Count); + } + if (rand.NextSingle() < mutationSwapChance) + { + int idx1 = rand.Next(0, path.Count); + int idx2 = rand.Next(0, path.Count); + + (path[idx2], path[idx1]) = (path[idx1], path[idx2]); + } + if (rand.NextSingle() < mutationPickLocalChance) + { + int localNode = PickUniqueLocalNode(rand, path, localPlaces[i]); + if (localNode != -1) + { + path[i] = localNode; + } + } + } + } + + public void IterateSolution() + { + double bestScore = paths.First().Key; + double worstScore = paths.Last().Key; + + var removedPaths = new List(); + var parentPool = new List>(); + foreach (var entry in paths) + { + double probability = (entry.Key - bestScore) / (worstScore - bestScore); + if (rand.NextSingle() >= probability) + { + parentPool.Add(entry.Value); + } + else + { + removedPaths.Add(entry.Key); + } + } + + foreach (var key in removedPaths) + { + paths.Remove(key); + } + + while (batchSize > paths.Count) + { + var parentIdx = rand.Next(0, parentPool.Count); + var parent = parentPool[parentIdx]; + var child = new List(parent); + MutatePath(child); + var cost = GetPathCost(child, places); + if (!paths.ContainsKey(cost)) + { + paths.Add(cost, child); + } + } + + iteration++; + } + + public void IterateSolutionFor(float seconds) + { + var stopwatch = new Stopwatch(); + stopwatch.Start(); + while (stopwatch.ElapsedMilliseconds < seconds * 1000) + { + IterateSolution(); + } + stopwatch.Stop(); + } + + public Tuple> GetBestPath() + { + var entry = paths.First(); + return Tuple.Create(entry.Key, entry.Value); + } + } +} diff --git a/Lab4/Ataskaita.docx b/Lab4/Ataskaita.docx new file mode 100644 index 0000000..e6779de Binary files /dev/null and b/Lab4/Ataskaita.docx differ diff --git a/Lab4/EditorWindow.cs b/Lab4/EditorWindow.cs new file mode 100644 index 0000000..d599f68 --- /dev/null +++ b/Lab4/EditorWindow.cs @@ -0,0 +1,94 @@ +using System.Numerics; +using ImGuiNET; +using Lab4; +using Raylib_cs; + +namespace Lab4 +{ + class EditorWindow + { + private bool showDemoWindow = false; + public ImFontPtr font1; + + MainWindow win; + + public EditorWindow(MainWindow win) + { + this.win = win; + } + + public void Update(float dt) + { + ImGui.PushFont(font1); + + if (showDemoWindow) + { + // Normally user code doesn't need/want to call this because positions are saved in .ini file anyway. + // Here we just want to make the demo initial state a bit more friendly! + ImGui.SetNextWindowPos(new Vector2(650, 20), ImGuiCond.FirstUseEver); + ImGui.ShowDemoWindow(ref showDemoWindow); + } + + if (ImGui.Begin("Editor", ImGuiWindowFlags.None)) + { + var algo = win.algo; + + var mouse = win.GetMousePosition(); + if (win.isIterationRunning) + { + if (ImGui.Button(win.iterationPaused ? "Continue" : "Pause")) + { + win.iterationPaused = !win.iterationPaused; + } + if (ImGui.Button("Stop")) + { + win.Stop(); + } + } else + { + if (ImGui.Button("Start")) + { + win.Start(); + } + } + ImGui.Text($"Mouse: {mouse.X:f3} {mouse.Y:f3}"); + ImGui.Text($"Iteration: {algo.iteration}"); + ImGui.Text($"Runtime: {win.interationTime:f3}s"); + + ImGuiInputTextFlags inputFlags = 0; + if (win.isIterationRunning) + { + uint bg = ImGui.GetColorU32(ImGuiCol.FrameBg); + ImGui.PushStyleColor(ImGuiCol.FrameBg, 0x44000000 | (bg & 0x00FFFFFF)); + ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)); + inputFlags = ImGuiInputTextFlags.ReadOnly; + } + + ImGui.InputInt("Seed", ref algo.seed, 1, 1, ImGuiInputTextFlags.CharsDecimal | inputFlags); + ImGui.InputInt("Batch size", ref algo.batchSize, 1, 1, ImGuiInputTextFlags.CharsDecimal | inputFlags); + + if (win.isIterationRunning) + { + ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + } + + var localRadius = algo.localAreaRadius; + if (ImGui.InputFloat("Local area radius", ref localRadius)) + { + Console.WriteLine(localRadius); + win.algo.SetLocalRadius(localRadius); + } + ImGui.SliderFloat("Mutation swap chance", ref algo.mutationSwapChance, 0, 0.3f); + ImGui.SliderFloat("Mutation repick chance", ref algo.mutationPickUniqueChance, 0, 0.3f); + ImGui.SliderFloat("Mutation repick local chance", ref algo.mutationPickLocalChance, 0, 0.3f); + + ImGui.Text($"Best path score: {win.bestScore}"); + + ImGui.End(); + } + + ImGui.PopFont(); + } + } +} \ No newline at end of file diff --git a/Lab4/ImguiController.cs b/Lab4/ImguiController.cs new file mode 100644 index 0000000..28e8513 --- /dev/null +++ b/Lab4/ImguiController.cs @@ -0,0 +1,323 @@ +using ImGuiNET; +using Raylib_cs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace Lab4 +{ + public class ImguiController : IDisposable + { + IntPtr context; + Texture2D fontTexture; + Vector2 scaleFactor = Vector2.One; + + public ImguiController() + { + context = ImGui.CreateContext(); + ImGui.SetCurrentContext(context); + } + + public void Dispose() + { + ImGui.DestroyContext(context); + Raylib.UnloadTexture(fontTexture); + } + + /// + /// Creates a texture and loads the font data from ImGui. + /// + public void Load(int width, int height) + { + ImGuiIOPtr io = ImGui.GetIO(); + io.Fonts.AddFontDefault(); + + Resize(width, height); + LoadFontTexture(); + SetupInput(); + ImGui.NewFrame(); + } + + unsafe void LoadFontTexture() + { + ImGuiIOPtr io = ImGui.GetIO(); + + // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. + // If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height); + + // Upload texture to graphics system + Image image = new Image + { + data = pixels, + width = width, + height = height, + mipmaps = 1, + format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, + }; + fontTexture = Raylib.LoadTextureFromImage(image); + + // Store texture id in imgui font + io.Fonts.SetTexID(new IntPtr(fontTexture.id)); + + // Clears font data on the CPU side + io.Fonts.ClearTexData(); + } + + void SetupInput() + { + ImGuiIOPtr io = ImGui.GetIO(); + + // Setup config flags + io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; + + // Setup back-end capabilities flags + io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors; + io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos; + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.KEY_TAB; + io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.KEY_LEFT; + io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.KEY_RIGHT; + io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.KEY_UP; + io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.KEY_DOWN; + io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.KEY_PAGE_UP; + io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.KEY_PAGE_DOWN; + io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.KEY_HOME; + io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.KEY_END; + io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.KEY_INSERT; + io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.KEY_DELETE; + io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.KEY_BACKSPACE; + io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.KEY_SPACE; + io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.KEY_ENTER; + io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.KEY_ESCAPE; + io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.KEY_A; + io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.KEY_C; + io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.KEY_V; + io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.KEY_X; + io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.KEY_Y; + io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.KEY_Z; + } + + /// + /// Update imgui internals(input, frameData) + /// + /// + public void Update(float dt) + { + ImGuiIOPtr io = ImGui.GetIO(); + + io.DisplayFramebufferScale = Vector2.One; + io.DeltaTime = dt; + + UpdateKeyboard(); + UpdateMouse(); + + if (Raylib.IsWindowResized()) + { + Resize(Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); + } + + ImGui.NewFrame(); + } + + /// + /// Resize imgui display + /// + public void Resize(int width, int height) + { + ImGuiIOPtr io = ImGui.GetIO(); + io.DisplaySize = new Vector2(width, height) / scaleFactor; + } + + void UpdateKeyboard() + { + ImGuiIOPtr io = ImGui.GetIO(); + + // Modifiers are not reliable across systems + io.KeyCtrl = io.KeysDown[(int)KeyboardKey.KEY_LEFT_CONTROL] || io.KeysDown[(int)KeyboardKey.KEY_RIGHT_CONTROL]; + io.KeyShift = io.KeysDown[(int)KeyboardKey.KEY_LEFT_SHIFT] || io.KeysDown[(int)KeyboardKey.KEY_RIGHT_SHIFT]; + io.KeyAlt = io.KeysDown[(int)KeyboardKey.KEY_LEFT_ALT] || io.KeysDown[(int)KeyboardKey.KEY_RIGHT_ALT]; + io.KeySuper = io.KeysDown[(int)KeyboardKey.KEY_LEFT_SUPER] || io.KeysDown[(int)KeyboardKey.KEY_RIGHT_SUPER]; + + // Key states + for (int i = (int)KeyboardKey.KEY_SPACE; i < (int)KeyboardKey.KEY_KB_MENU + 1; i++) + { + io.KeysDown[i] = Raylib.IsKeyDown((KeyboardKey)i); + } + + // Key input + int keyPressed = Raylib.GetCharPressed(); + if (keyPressed != 0) + { + io.AddInputCharacter((uint)keyPressed); + } + } + + void UpdateMouse() + { + ImGuiIOPtr io = ImGui.GetIO(); + + // Store button states + for (int i = 0; i < io.MouseDown.Count; i++) + { + io.MouseDown[i] = Raylib.IsMouseButtonDown((MouseButton)i); + } + + // Mouse scroll + io.MouseWheel += Raylib.GetMouseWheelMove(); + + // Mouse position + Vector2 mousePosition = io.MousePos; + bool focused = Raylib.IsWindowFocused(); + + if (focused) + { + if (io.WantSetMousePos) + { + Raylib.SetMousePosition((int)mousePosition.X, (int)mousePosition.Y); + } + else + { + io.MousePos = Raylib.GetMousePosition(); + } + } + + // Mouse cursor state + if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0 || Raylib.IsCursorHidden()) + { + ImGuiMouseCursor cursor = ImGui.GetMouseCursor(); + if (cursor == ImGuiMouseCursor.None || io.MouseDrawCursor) + { + Raylib.HideCursor(); + } + else + { + Raylib.ShowCursor(); + } + } + } + + /// + /// Gets the geometry as set up by ImGui and sends it to the graphics device + /// + public void Draw() + { + ImGui.Render(); + RenderCommandLists(ImGui.GetDrawData()); + } + + // Returns a Color struct from hexadecimal value + Color GetColor(uint hexValue) + { + Color color; + + color.r = (byte)(hexValue & 0xFF); + color.g = (byte)((hexValue >> 8) & 0xFF); + color.b = (byte)((hexValue >> 16) & 0xFF); + color.a = (byte)((hexValue >> 24) & 0xFF); + + return color; + } + + void DrawTriangleVertex(ImDrawVertPtr idxVert) + { + Color c = GetColor(idxVert.col); + Rlgl.rlColor4ub(c.r, c.g, c.b, c.a); + Rlgl.rlTexCoord2f(idxVert.uv.X, idxVert.uv.Y); + Rlgl.rlVertex2f(idxVert.pos.X, idxVert.pos.Y); + } + + // Draw the imgui triangle data + void DrawTriangles(uint count, int idxOffset, int vtxOffset, ImVector idxBuffer, ImPtrVector idxVert, IntPtr textureId) + { + ushort index = 0; + ImDrawVertPtr vertex; + + if (Rlgl.rlCheckRenderBatchLimit((int)count * 3)) + { + Rlgl.rlDrawRenderBatchActive(); + } + + Rlgl.rlBegin(DrawMode.TRIANGLES); + Rlgl.rlSetTexture((uint)textureId); + + for (int i = 0; i <= (count - 3); i += 3) + { + index = idxBuffer[idxOffset + i]; + vertex = idxVert[vtxOffset + index]; + DrawTriangleVertex(vertex); + + index = idxBuffer[idxOffset + i + 1]; + vertex = idxVert[vtxOffset + index]; + DrawTriangleVertex(vertex); + + index = idxBuffer[idxOffset + i + 2]; + vertex = idxVert[vtxOffset + index]; + DrawTriangleVertex(vertex); + } + Rlgl.rlEnd(); + } + + void RenderCommandLists(ImDrawDataPtr data) + { + // Scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fbWidth = (int)(data.DisplaySize.X * data.FramebufferScale.X); + int fbHeight = (int)(data.DisplaySize.Y * data.FramebufferScale.Y); + + // Avoid rendering if display is minimized or if the command list is empty + if (fbWidth <= 0 || fbHeight <= 0 || data.CmdListsCount == 0) + { + return; + } + + Rlgl.rlDrawRenderBatchActive(); + Rlgl.rlDisableBackfaceCulling(); + Rlgl.rlEnableScissorTest(); + + data.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); + + for (int n = 0; n < data.CmdListsCount; n++) + { + int idxOffset = 0; + ImDrawListPtr cmdList = data.CmdListsRange[n]; + + // Vertex buffer and index buffer generated by DearImGui + ImPtrVector vtxBuffer = cmdList.VtxBuffer; + ImVector idxBuffer = cmdList.IdxBuffer; + + for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++) + { + ImDrawCmdPtr pcmd = cmdList.CmdBuffer[cmdi]; + + // Scissor rect + Vector2 pos = data.DisplayPos; + int rectX = (int)((pcmd.ClipRect.X - pos.X) * data.FramebufferScale.X); + int rectY = (int)((pcmd.ClipRect.Y - pos.Y) * data.FramebufferScale.Y); + int rectW = (int)((pcmd.ClipRect.Z - rectX) * data.FramebufferScale.Y); + int rectH = (int)((pcmd.ClipRect.W - rectY) * data.FramebufferScale.Y); + Rlgl.rlScissor(rectX, Raylib.GetScreenHeight() - (rectY + rectH), rectW, rectH); + + if (pcmd.UserCallback != IntPtr.Zero) + { + // pcmd.UserCallback(cmdList, pcmd); + idxOffset += (int)pcmd.ElemCount; + } + else + { + DrawTriangles(pcmd.ElemCount, idxOffset, (int)pcmd.VtxOffset, idxBuffer, vtxBuffer, pcmd.TextureId); + idxOffset += (int)pcmd.ElemCount; + Rlgl.rlDrawRenderBatchActive(); + } + } + } + + Rlgl.rlSetTexture(0); + Rlgl.rlDisableScissorTest(); + Rlgl.rlEnableBackfaceCulling(); + } + } +} diff --git a/Lab4/Lab4.csproj b/Lab4/Lab4.csproj index 6a32d2b..1455465 100644 --- a/Lab4/Lab4.csproj +++ b/Lab4/Lab4.csproj @@ -7,6 +7,7 @@ enable x64 False + true @@ -21,6 +22,8 @@ PreserveNewest + + diff --git a/Lab4/MainWindow.cs b/Lab4/MainWindow.cs new file mode 100644 index 0000000..118c705 --- /dev/null +++ b/Lab4/MainWindow.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using Raylib_cs; +using static Lab4.Algorithm; + +namespace Lab4 +{ + public class MainWindow + { + public Algorithm algo; + List places; + public int screenWidth; + public int screenHeight; + + public float interationTime = 0; + Task? iterationTask; + + public List? bestPath; + public double bestScore = 0; + public bool isIterationRunning = false; + + public bool iterationPaused = false; + + RaylibDrawContext ctx; + + ScoreHistWindow scoreHist; + + public MainWindow(int screenWidth, int screenHeight, List places) + { + this.places = places; + algo = new Algorithm(places); + scoreHist = new ScoreHistWindow(this); + + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + + { + ctx = new RaylibDrawContext(); + + const int screenMargin = 50; + + var (minX, maxX, minY, maxY) = GetCoordinateBounds(places); + var rangeX = maxX - minX; + var rangeY = maxY - minY; + float scale = Math.Min((screenWidth - screenMargin * 2) / rangeX, (screenHeight - screenMargin * 2) / rangeY); + ctx.Scale(scale, scale); + ctx.Translate(-screenMargin, -screenMargin); + ctx.Translate(minX * scale, minY * scale); + } + } + + public void Start() + { + algo.Reset(); + scoreHist.Reset(); + isIterationRunning = true; + iterationPaused = false; + interationTime = 0; + iterationTask = new Task(() => { + while (isIterationRunning) + { + if (!iterationPaused) + { + algo.IterateSolution(); + var best = algo.GetBestPath(); + bestScore = best.Item1; + bestPath = best.Item2; + } + } + }); + iterationTask.Start(); + } + + public void Stop() + { + isIterationRunning = false; + } + + public static Tuple GetCoordinateBounds(List places) + { + float minX = places[0].x; + float maxX = places[0].x; + float minY = places[0].y; + float maxY = places[0].y; + + for (int i = 1; i < places.Count; i++) + { + minX = Math.Min(minX, places[i].x); + maxX = Math.Max(maxX, places[i].x); + minY = Math.Min(minY, places[i].y); + maxY = Math.Max(maxY, places[i].y); + } + + return Tuple.Create(minX, maxX, minY, maxY); + } + + public static void DrawPath(RaylibDrawContext ctx, List path, List places) + { + Color color = Color.GREEN; + for (int i = 0; i < path.Count - 1; i++) + { + var place0 = places[path[i + 0]]; + var place1 = places[path[i + 1]]; + ctx.DrawLine(place0.x, place0.y, place1.x, place1.y, color); + } + } + + public Vector2 GetMousePosition() + { + return ctx.TransformToLocalSpace(Raylib.GetMousePosition()); + } + + public Place? GetNearestPlaceToMouse() + { + if (places.Count == 0) return null; + + var mouse = GetMousePosition(); + var mearest = places.First(); + double nearestDistance = GetDistance(mearest.x, mearest.y, mouse.X, mouse.Y); + foreach (var place in places) + { + var distance = GetDistance(place.x, place.y, mouse.X, mouse.Y); + if (distance < nearestDistance) + { + nearestDistance = distance; + mearest = place; + } + } + return mearest; + } + + public unsafe void Run() + { + Raylib.SetTraceLogCallback(&Logging.LogConsole); + Raylib.SetConfigFlags(ConfigFlags.FLAG_VSYNC_HINT | ConfigFlags.FLAG_WINDOW_RESIZABLE); + Raylib.InitWindow(this.screenWidth, this.screenHeight, "IP Užduotis"); + Raylib.SetTargetFPS(60); + + Raylib.InitAudioDevice(); + + ImguiController controller = new ImguiController(); + EditorWindow editor = new EditorWindow(this); + + controller.Load(screenWidth, screenHeight); + while (!Raylib.WindowShouldClose()) + { + // Update + float dt = Raylib.GetFrameTime(); + controller.Update(dt); + editor.Update(dt); + scoreHist.Update(dt); + if (isIterationRunning && !iterationPaused) + { + interationTime += dt; + } + + + // Draw + Raylib.BeginDrawing(); + Raylib.ClearBackground(Color.RAYWHITE); + + foreach (var place in places) + { + ctx.DrawCircle(place.x, place.y, 2000, Color.RED); + } + + if (bestPath != null) + { + DrawPath(ctx, bestPath, places); + } + + /* + var nearest = GetNearestPlaceToMouse(); + if (nearest != null) + { + var mouse = GetMousePosition(); + ctx.DrawLine(nearest.x, nearest.y, mouse.X, mouse.Y, Color.BLUE); + ctx.DrawCircleLines(nearest.x, nearest.y, algo.localAreaRadius, Color.BLUE); + } + */ + + controller.Draw(); + + Raylib.EndDrawing(); + } + + // De-Initialization + controller.Dispose(); + Raylib.CloseAudioDevice(); + Raylib.CloseWindow(); + Stop(); + } + } +} diff --git a/Lab4/Program.cs b/Lab4/Program.cs index e3d7136..1303779 100644 --- a/Lab4/Program.cs +++ b/Lab4/Program.cs @@ -1,197 +1,27 @@ -using System.Diagnostics; -using System.IO; +using Microsoft.VisualBasic; +using Raylib_cs; +using System.Diagnostics; namespace Lab4 { - class Place - { - public string name; - public float x; - public float y; - public Place(string name, float x, float y) - { - this.name = name; - this.x = x; - this.y = y; - } - } - internal class Program { - public static double GetDistance(Place A, Place B) + static unsafe void Main(string[] args) { - float dx = A.x - B.x; - float dy = A.y - B.y; - return Math.Sqrt(dx * dx + dy * dy); - } + var places = Algorithm.ReadPlacesFromTSV("places.tsv"); - public static double GetTravalCost(Place A, Place B) - { - return Math.Sqrt(GetDistance(A, B)); - } + var win = new MainWindow(1280, 720, places); + win.Run(); - public static List ReadPlacesFromTSV(string filename) - { - List cities = new List(); - foreach (var line in File.ReadLines(filename)) - { - string[] parts = line.Split("\t"); - - string name = parts[0]; - float x = float.Parse(parts[2]); - float y = float.Parse(parts[3]); - cities.Add(new Place(name, x, y)); - } - return cities; - } - - // Warning 'PickUnqiueNode' could get stuck in an infinite loop, if there are not enough places - public static int PickUnqiueNode(Random rand, List path, int placesCount) - { - while (true) - { - int num = rand.Next(0, placesCount); - if (!path.Contains(num)) - { - return num; - } - } - } - - public static List GenerateRandomPath(Random rand, int size, int placesCount) - { - List path = new List(size); - for (int i = 0; i < size; i++) - { - path.Add(PickUnqiueNode(rand, path, placesCount)); - } - return path; - } - - public static double GetPathCost(List path, List places) - { - double cost = 0; - for (int i = 0; i < path.Count-1; i++) - { - int from = path[i]; - int to = path[i+1]; - cost += GetTravalCost(places[from], places[to]); - } - cost += GetTravalCost(places[path.First()], places[path.Last()]); - return cost; - } - - public static List SplicePaths(Random rand, List A, List B) - { - Debug.Assert(A.Count == B.Count); - - List spliced = new List(A.Count); - for (int i = 0; i < A.Count-1; i++) - { - spliced[i] = rand.Next(0, 2) == 0 ? A[i] : B[i]; - } - return spliced; - } - - public static void MutatePath(Random rand, List path, float mutationPickUniqueChance, float mutationSwapChance, int placesCount) - { - for (int i = 0; i < path.Count; i++) - { - if (rand.NextSingle() < mutationPickUniqueChance) { - path[i] = PickUnqiueNode(rand, path, placesCount); - } - if (rand.NextSingle() < mutationSwapChance) { - int idx1 = rand.Next(0, path.Count); - int idx2 = rand.Next(0, path.Count); - - (path[idx2], path[idx1]) = (path[idx1], path[idx2]); - } - } - } - - static void Main(string[] args) - { - /* - Faile places_data.xlsx pateikta informacija apie lankytinas vietas (1 lentelė). Tikslas: kaip galima pigesnio maršruto sudarymas kai, - - * priimama, kad kelionės tarp vietų kaina lygi kvadratinei šakniai iš kelionės atstumo; - * kelionės pradžios ir pabaigos vieta sutampa (su grįžimu atgal); - * ta pati vieta negali būti aplankyta daugiau nei vieną; - * Reikia aplankyti bet kurias 150 vietų iš pateikto sąrašo. - */ - - int pathLength = 150; - int batchSize = 50; - int timeLimitMs = 60 * 1000; - float mutationPickUniqueChance = 0.05f; - float mutationSwapChance = 0.05f; - - int seed = (int)DateTime.Now.Ticks; - Random rand = new Random(seed); - - var places = ReadPlacesFromTSV("places.tsv"); - - var bestPaths = new SortedList>(); - for (int i = 0; i < batchSize; i++) - { - var path = GenerateRandomPath(rand, pathLength, places.Count); - var cost = GetPathCost(path, places); - bestPaths.Add(cost, path); - } - - var stopwatch = new Stopwatch(); - int iteration = 0; - stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < timeLimitMs) - { - double bestScore = bestPaths.First().Key; - double worstScore = bestPaths.Last().Key; - - if (iteration % 200 == 0) - { - //Console.WriteLine("At {0}, best '{1}', worst '{2}'", iteration, bestScore, worstScore); - } - - var removedPaths = new List(); - var parentPool = new List>(); - foreach (var entry in bestPaths) - { - double probability = (entry.Key - bestScore) / (worstScore - bestScore); - if (rand.NextSingle() >= probability) { - parentPool.Add(entry.Value); - } else { - removedPaths.Add(entry.Key); - } - } - - foreach (var key in removedPaths) - { - bestPaths.Remove(key); - } - - while (batchSize > bestPaths.Count) - { - var parentIdx = rand.Next(0, parentPool.Count); - var parent = parentPool[parentIdx]; - var child = new List(parent); - MutatePath(rand, child, mutationPickUniqueChance, mutationSwapChance, places.Count); - var cost = GetPathCost(child, places); - if (!bestPaths.ContainsKey(cost)) { - bestPaths.Add(cost, child); - } - } - - iteration++; - } - stopwatch.Stop(); - - Console.WriteLine("Best after {0} iterations is {1}", iteration, bestPaths.First().Key); - Console.WriteLine("Time taken: {0}ms", stopwatch.ElapsedMilliseconds); - Console.WriteLine("Seed: {0}", seed); + //var algo = new Algorithm(places); + //algo.IterateSolutionFor(1); + //Console.WriteLine("Best after {0} iterations is {1}", algo.iteration, algo.GetBestPath().Item1); + //Console.WriteLine("Seed: {0}", algo.seed); // Best after 484012 iterations is 30826.444176719036 // Time taken: 60000ms // Seed: -1477536140 } } + } \ No newline at end of file diff --git a/Lab4/RaylibDrawContext.cs b/Lab4/RaylibDrawContext.cs new file mode 100644 index 0000000..17d8c4a --- /dev/null +++ b/Lab4/RaylibDrawContext.cs @@ -0,0 +1,98 @@ +using Raylib_cs; +using System.Numerics; +using System.Security.Cryptography; +using System.Xml; + +namespace Lab4 +{ + public class RaylibDrawContext + { + class DrawFrame + { + public float ox = 0; + public float oy = 0; + public float sx = 1; + public float sy = 1; + public DrawFrame(float ox, float oy, float sx, float sy) + { + this.ox = ox; + this.oy = oy; + this.sx = sx; + this.sy = sy; + } + } + + public float ox = 0; + public float oy = 0; + public float sx = 1; + public float sy = 1; + + Stack stack; + + public RaylibDrawContext() + { + stack = new Stack(); + } + + public void Translate(float offsetX, float offsetY) + { + ox += offsetX / sx; + oy += offsetY / sy; + } + + public void Scale(float scaleX, float scaleY) + { + sx *= scaleX; + sy *= scaleY; + } + + public void Push() + { + stack.Push(new DrawFrame(ox, oy, sx, sy)); + } + + public void Pop() + { + var frame = stack.Pop(); + ox = frame.ox; + oy = frame.oy; + sx = frame.sx; + sy = frame.sy; + } + + public Vector2 TransformToLocalSpace(Vector2 pos) + { + return new Vector2( + (pos.X / sx) + ox, + (pos.Y / sy) + oy + ); + } + + public void DrawCircle(float centerX, float centerY, float radius, Color color) + { + int x = (int)((centerX - ox) * sx); + int y = (int)((centerY - oy) * sy); + float rh = radius * sx; + float rv = radius * sy; + Raylib.DrawEllipse(x, y, rh, rv, color); + } + + public void DrawCircleLines(float centerX, float centerY, float radius, Color color) + { + int x = (int)((centerX - ox) * sx); + int y = (int)((centerY - oy) * sy); + float rh = radius * sx; + float rv = radius * sy; + Raylib.DrawEllipseLines(x, y, rh, rv, color); + } + + public void DrawLine(float startPosX, float startPosY, float endPosX, float endPosY, Color color) + { + int x0 = (int)((startPosX - ox) * sx); + int y0 = (int)((startPosY - oy) * sy); + int x1 = (int)((endPosX - ox) * sx); + int y1 = (int)((endPosY - oy) * sy); + Raylib.DrawLine(x0, y0, x1, y1, color); + } + } +} diff --git a/Lab4/ScoreHistWindow.cs b/Lab4/ScoreHistWindow.cs new file mode 100644 index 0000000..3bd74df --- /dev/null +++ b/Lab4/ScoreHistWindow.cs @@ -0,0 +1,59 @@ +using ImGuiNET; + +namespace Lab4 +{ + internal class ScoreHistWindow + { + public ImFontPtr font1; + MainWindow win; + + float[] dataPoints; + + float timer = 0; + float lastSampleTime = -1; + float sampleInterval = 0.1f; + + public ScoreHistWindow(MainWindow win) + { + this.win = win; + dataPoints = new float[100]; + } + + public void Reset() + { + dataPoints = new float[100]; + timer = 0; + lastSampleTime = -1; + } + + public unsafe void Update(float dt) + { + if (win.isIterationRunning && !win.iterationPaused) + { + timer += dt; + if (timer - lastSampleTime > sampleInterval) + { + for (int i = 0; i < dataPoints.Length-1; i++) + { + dataPoints[i] = dataPoints[i + 1]; + } + + dataPoints[dataPoints.Length - 1] = (float)win.bestScore; + + lastSampleTime = timer; + } + } + + ImGui.PushFont(font1); + + if (ImGui.Begin("Score histogram", ImGuiWindowFlags.None)) + { + ImGui.PlotLines("", ref dataPoints[0], dataPoints.Length, 0, null, 10_000f, 50_000f, new System.Numerics.Vector2(0, 180.0f)); + ImGui.Spacing(); + ImGui.End(); + } + + ImGui.PopFont(); + } + } +}