Mateusz Antoniak
Mateusz Antoniak

Reputation: 11

N-body simple gravity simulation in sharpDX?

i was using .net framework windowsforms gdi+ to draw my simulation on form and render it. So far, i want it to render the same, but i want render it in directX(SharpDX 2d). I know my simulation have bugs and other such a things, but for me it looks great, as simple simulation(I'm still learning about making simulations). If someone know how can i do this with my code, i appreciate it, and also i want this to be optimized!

Code:

  1. Body class:
    public class Body
    {
        public string Name { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        public double Mass { get; set; }
        public double X { get; set; }
        public double Y { get; set; }
        public double VX { get; set; }
        public double VY { get; set; }
        public double AX { get; set; }
        public double AY { get; set; }
        public int Radius { get; set; }
        public bool IsSelected { get; set; }
        public bool IsStatic { get; set; }
        public bool Visible { get; set; }
        public Brush BodyColor { get; set; } = Brushes.Red;
        public List<Point> OrbitTrail { get; set; } = new List<Point>();
        public double Elasticity { get; set; }
        public double FrictionCoefficient { get; set; }
        public double Rotation { get; set; }
        public double AngularVelocity { get; set; }
        public double AngularAcceleration { get; set; }
        public BigInteger Temperature { get; set; }
        public List<Body> AttachedObjects { get; set; } = new List<Body>();
        public string Type { get; set; } = "Ellipse";

        public Body() { }
    }
  1. Main code:
    public partial class GravitySim : Form
    {
        #region variables
        private double G = 6.67430e-11;
        private QuadTree quadTree;
        private double timeStep = 1.0;
        private int numSimulationSteps = 5000;
        private const double scaleFactor = 1e5;
        private const double MassScaleFactor = 1e22;
        private bool drawQuadTree = false;

        private bool selectionMode = true;
        private bool velocityVectors = true;

        private List<Body> currentPreset = null;


        // Declare a variable for real-time acceleration factor
        private double realTimeAcceleration = 1.0;

        private int maxOrbitPoints = 50000;


        private const double TargetFPS = 120.0;
        private const double TargetFrameTime = 1000.0 / TargetFPS;

        private List<Body> bodies;
        private Timer timer;

        private Stopwatch frameStopwatch = new Stopwatch();
        private Stopwatch renderStopwatch = new Stopwatch();
        private Stopwatch SimStopwatch = new Stopwatch();
        private int framesCount = 0;
        private double frameRate = 0.0;
        private double frames;
        #endregion
        public GravitySim()
        {
            InitializeComponent();
            InitializeSimulation();
            this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
            // Inicjalizacja KONTROLEK
            this.MouseDoubleClick += Form1_MouseDoubleClick;
        }

        #region basicFNC

        private void StartRenderTimer()
        {
            renderStopwatch.Restart();
        }

        private void StopRenderTimer()
        {
            renderStopwatch.Stop();
            double renderTime = renderStopwatch.Elapsed.TotalMilliseconds;
            label28.Text = $"Render Time: {renderTime:F2} ms";
        }

        private void InitializeSimulation()
        {
            InitializeColorComboBox();
            InitializePresets();


            List<Body> bodies = new List<Body>()
            {
                new Body { Mass = 6e5, X = 200 + 350, Y = 500, VX = 0, VY = 0, Radius = 20, IsStatic = true, BodyColor = Brushes.Red },
                new Body { Mass = 4e4, X = 450 + 300, Y = 350, VX = 0, VY = -22 / scaleFactor, Radius = 5, IsStatic = false, BodyColor = Brushes.Blue },
                new Body { Mass = 2.5e4, X = 100 + 300, Y = 400, VX = -40 / scaleFactor, VY = 18 / scaleFactor, Radius = 5, IsStatic = false, BodyColor = Brushes.Yellow },
                // Dodaj kolejne ciała według potrzeb
            };

            LoadPreset(bodies);

            quadTree = new QuadTree(new Rectangle(0, 0, ClientSize.Width - panel1.Width, ClientSize.Height), 1);
            QuadTreeInsertBodies();


            // Inicjalizacja timera
            timer = new Timer();
            timer.Interval = 1; // Czas w milisekundach między krokami symulacji
            timer.Tick += Timer_Tick;

            SimStopwatch.Start();
        }

        private void QuadTreeInsertBodies()
        {
            quadTree.Clear();
            foreach (var body in bodies)
            {
                quadTree.Insert(body);
            }
        }

        private void LoadPreset(List<Body> bodies)
        {
            this.bodies = bodies;
            this.Invalidate();
        }

        private void InitializePresets()
        {
            comboBox2.Items.Add("Example preset");
            comboBox2.Items.Add("Earth orbiting Sun - 2 bodies");

            comboBox2.SelectedIndex = 0;
        }

        private void InitializeColorComboBox()
        {
            // Pobierz właściwości Brushes za pomocą refleksji
            var brushProperties = typeof(Brushes).GetProperties();

            // Dodaj kolory do ComboBox
            foreach (var brushProperty in brushProperties)
            {
                Brush brush = (Brush)brushProperty.GetValue(null, null);

                // Sprawdź, czy kolor jest SolidColorBrush (możesz dostosować warunek do innych rodzajów pędzli, jeśli to konieczne)
                if (brush is SolidBrush)
                {
                    comboBox1.Items.Add(brushProperty.Name);
                }
            }

            // Ustaw domyślny kolor
            comboBox1.SelectedIndex = 0;
        }
        #endregion

        private Body selectedBody = null;


        private void Form1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (selectionMode)
            {
                Point mousePosition = e.Location;

                // Check if the mouse click is within the bounds of any body
                bool clickedOnBody = false;

                foreach (var body in bodies)
                {
                    if (IsMouseOnBody(mousePosition, body))
                    {
                        // Unselect the currently selected body
                        if (selectedBody != null)
                        {
                            selectedBody.IsSelected = false;
                        }

                        body.IsSelected = true;
                        selectedBody = body;
                        clickedOnBody = true;

                        // Pobierz kolor z Brush
                        Color BodyColor = (body.BodyColor as SolidBrush)?.Color ?? Color.Black;

                        // Znajdź indeks koloru ciała w comboBox1
                        int colorIndex = comboBox1.Items.IndexOf(BodyColor.Name);

                        // Jeśli indeks jest większy lub równy zeru, ustaw go jako zaznaczony
                        if (colorIndex >= 0)
                        {
                            comboBox1.SelectedIndex = colorIndex;
                            this.BodyColor.BackColor = BodyColor;
                        }

                        checkBox4.Checked = selectedBody.IsStatic;

                        if (body.Mass > Math.Pow(10, 9) || body.Mass < Math.Pow(10, -3)) // Sprawdź, czy masa jest większa niż miliard
                        {
                            label9.Text = $"Mass: {ConvertToScientificNotation(body.Mass)} kg";
                            bodyMass.Text = ConvertToScientificNotation(body.Mass) + " kg";
                        }
                        else
                        {
                            label9.Text = $"Mass: {body.Mass.ToString("F2")} kg";
                            bodyMass.Text = body.Mass.ToString("F2") + " kg";
                        }



                        break;
                    }
                }

                // Clicked outside of any body, unselect all bodies
                if (!clickedOnBody)
                {
                    if (selectedBody != null)
                    {
                        selectedBody.IsSelected = false;
                        selectedBody = null;
                    }

                    foreach (var body in bodies)
                    {
                        body.IsSelected = false;
                    }
                }

                this.Invalidate(); // Redraw to reflect the changes

                // Dodaj kod, który decyduje, czy panel2 powinien być widoczny
                if (selectedBody != null)
                {
                    // Panel powinien być widoczny, ponieważ jest wybrana jakakolwiek ciało
                    rjButton8.Location = new Point(5, 403);
                    label24.Location = new Point(75, 443);
                    comboBox2.Location = new Point(15, 460);
                    rjButton10.Location = new Point(6, 487);
                    panel2.Show();
                    rjButton9.Show();
                    rjButton13.Show();
                    label16.Show();
                }
                else
                {
                    // Panel powinien być ukryty, ponieważ nie jest wybrane żadne ciało
                    rjButton8.Location = new Point(5, 191);
                    label24.Location = new Point(76, 231);
                    comboBox2.Location = new Point(12, 248);
                    rjButton10.Location = new Point(3, 275);
                    panel2.Hide();
                    rjButton9.Hide();
                    rjButton13.Hide();
                    label16.Hide();
                }
            }
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if (timeStep >= 5000 | numSimulationSteps > 75000)
            {
                Application.Exit();
                this.Close();
                MessageBox.Show("An unexpected error occured! Too high timestep. Program has succesfully exited");
            }
            framesCount++;
            frames++;
            for (int step = 0; step < numSimulationSteps; step++)
            {
                UpdatePositions();
                CalculateGravity();

                // Check collisions and handle them
                HandleCollisions();

                // Update the QuadTree every frame
                UpdateQuadTree();


                foreach (var body in bodies)
                {
                    CheckWindowCollision(body);
                }
            }

            this.Invalidate(); // Wywołuje ponownie Paint, aby zaktualizować wyświetlanie ciał

            // Update frame rate every second
            if (frameStopwatch.ElapsedMilliseconds >= 1000)
            {
                frameRate = framesCount / (frameStopwatch.ElapsedMilliseconds / 1000.0);
                framesCount = 0;
                frameStopwatch.Restart();
            }
        }

        private void UpdateQuadTree()
        {
            // Clear the QuadTree and reinsert all bodies
            quadTree.Clear();
            foreach (var body in bodies)
            {
                quadTree.Insert(body);
            }
        }

        private void HandleCollisions()
        {
            for (int i = 0; i < bodies.Count; i++)
            {
                for (int j = i + 1; j < bodies.Count; j++)
                {
                    Body body1 = bodies[i];
                    Body body2 = bodies[j];

                    double distanceSquared = (body1.X - body2.X) * (body1.X - body2.X) + (body1.Y - body2.Y) * (body1.Y - body2.Y);
                    double sumOfRadiiSquared = (body1.Radius + body2.Radius) * (body1.Radius + body2.Radius);

                    if (distanceSquared <= sumOfRadiiSquared)
                    {
                        // Introduce a damping factor to reduce velocity after collision
                        double dampingFactor = 0; // Możesz dostosować tę wartość według potrzeb

                        body1.VX *= dampingFactor;
                        body1.VY *= dampingFactor;

                        // Usunięcie zjedzonego ciała
                        if (body1.Mass > body2.Mass)
                        {
                            // Destroy body2
                            bodies.Remove(body2);
                        }
                        else
                        {
                            // Destroy body1
                            bodies.Remove(body1);
                            // Dodałem dodatkowy współczynnik tłumienia prędkości dla ciała, które pozostało po zjedzeniu innego ciała
                            body2.VX *= dampingFactor;
                            body2.VY *= dampingFactor;
                        }
                    }
                }
            }
        }

        private bool CheckWindowCollision(Body body)
        {
            int windowWidth = this.ClientSize.Width - 251;
            int windowHeight = this.ClientSize.Height;

            bool collisionDetected = false;

            if (body.X - body.Radius < 0)
            {
                //body.VX = Math.Abs(body.VX); // Bounce off left border
                collisionDetected = true;
            }
            else if (body.X + body.Radius > windowWidth)
            {
                //body.VX = -Math.Abs(body.VX); // Bounce off right border
                collisionDetected = true;
            }

            if (body.Y - body.Radius < 0)
            {
                //body.VY = Math.Abs(body.VY); // Bounce off top border
                collisionDetected = true;
            }
            else if (body.Y + body.Radius > windowHeight)
            {
                //body.VY = -Math.Abs(body.VY); // Bounce off bottom border
                collisionDetected = true;
            }

            return collisionDetected;
        }

        private void UpdatePositions()
        {
            foreach (var body in bodies)
            {
                if (!body.IsStatic)
                {

                    body.X += body.VX * timeStep;
                    body.Y += body.VY * timeStep;
                }
            }
        }

        private void CalculateGravity()
        {
            for (int i = 0; i < bodies.Count; i++)
            {
                for (int j = i + 1; j < bodies.Count; j++)
                {
                    Body body1 = bodies[i];
                    Body body2 = bodies[j];

                    double dx = body2.X - body1.X;
                    double dy = body2.Y - body1.Y;
                    double distanceSquared = dx * dx + dy * dy;
                    double distance = Math.Sqrt(distanceSquared);

                    double forceMagnitude = (G * body1.Mass * body2.Mass) / distanceSquared;
                    double forceX = forceMagnitude * (dx / distance);
                    double forceY = forceMagnitude * (dy / distance);

                    body1.VX += forceX / body1.Mass * timeStep;
                    body1.VY += forceY / body1.Mass * timeStep;

                    body2.VX -= forceX / body2.Mass * timeStep;
                    body2.VY -= forceY / body2.Mass * timeStep;
                }
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            frameStopwatch.Start();
            label13.Text = $"Body count: {bodies.Count()}";
            label2.Text = "VX: N/A, VY: N/A";
            label7.Text = "PX: N/A, PY: N/A";
            label9.Text = "Mass: N/A kg";
            checkBox1.Checked = Settings.Default.debugMode;
            checkBox2.Checked = Settings.Default.selectionMode;
            checkBox3.Checked = Settings.Default.velocityVectors;
            rjButton8.Location = new Point(5, 191);
            label24.Location = new Point(76, 231);
            comboBox2.Location = new Point(12, 248);
            rjButton10.Location = new Point(3, 275);
        }

        private TimeSpan simulatedTime = TimeSpan.Zero;
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
            g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
            g.TextContrast = 0;

            StartRenderTimer();
            // Rysowanie ciał
            foreach (var body in bodies)
            {
                DrawBody(g, body, body.BodyColor); // Kolor można dostosować w zależności od ciała

                if (body.IsSelected)
                {
                    DrawSelectionSquare(g, body);
                }

                if (velocityVectors)
                {
                    // Rysowanie wektorów prędkości
                    DrawVelocityVectors(g, body);
                }
            }
            if (drawQuadTree)
            {
                // Rysuj Quad Tree
                DrawQuadTree(g, quadTree.Root);
            }
            StopRenderTimer();

            double totalForceX = 0.0;
            double totalForceY = 0.0;

            for (int i = 0; i < bodies.Count; i++)
            {
                for (int j = i + 1; j < bodies.Count; j++)
                {
                    Body body1 = bodies[i];
                    Body body2 = bodies[j];

                    double dx = body2.X - body1.X;
                    double dy = body2.Y - body1.Y;
                    double distanceSquared = dx * dx + dy * dy;
                    double distance = Math.Sqrt(distanceSquared);

                    double forceMagnitude = (G * body1.Mass * body2.Mass) / distanceSquared;
                    double forceX = forceMagnitude * (dx / distance);
                    double forceY = forceMagnitude * (dy / distance);

                    body1.VX += forceX / body1.Mass * timeStep;
                    body1.VY += forceY / body1.Mass * timeStep;

                    body2.VX -= forceX / body2.Mass * timeStep;
                    body2.VY -= forceY / body2.Mass * timeStep;

                    totalForceX += forceX;
                    totalForceY += forceY;
                }
            }



            if (selectedBody != null)
            {
                label2.Text = $"VX: {(selectedBody.VX * scaleFactor).ToString("F2")}, VY: {(selectedBody.VY * scaleFactor).ToString("F2")}";
                label7.Text = $"PX: {selectedBody.X.ToString("F2")}, PY: {selectedBody.Y.ToString("F2")}";
                if (selectedBody.Mass > Math.Pow(10, 9) || selectedBody.Mass < Math.Pow(10, -3)) // Sprawdź, czy masa jest większa niż miliard
                {
                    label9.Text = $"Mass: {ConvertToScientificNotation(selectedBody.Mass)} kg";
                }
                else
                {
                    label9.Text = $"Mass: {selectedBody.Mass.ToString("F2")} kg";
                }
            }
            else
            {
                // If no body is selected, reset the labels
                label2.Text = "VX: N/A, VY: N/A";
                label7.Text = "PX: N/A, PY: N/A";
                label9.Text = "Mass: N/A kg";
            }

            // Update label26.Text with the formatted time
            double adjustedElapsedMilliseconds = SimStopwatch.ElapsedMilliseconds;
            TimeSpan formattedRealTime = TimeSpan.FromMilliseconds(adjustedElapsedMilliseconds);
            string formattedTime = $"{formattedRealTime.Hours:D2}h:{formattedRealTime.Minutes:D2}m:{formattedRealTime.Seconds:D2}s";
            label26.Text = $"Real Time: {formattedTime}";

            // Update label27.Text with the formatted simulated time
            simulatedTime += TimeSpan.FromSeconds(timeStep); // Increment simulated time
                                                             // Ensure that simulatedTime is not negative
                                                             // Dodaj obliczenia dla formatowania czasu symulacji w latach, dniach, godzinach, minutach i sekundach
            int years = simulatedTime.Days / 365;
            int days = simulatedTime.Days % 365;

            string formattedSimTime = $"{years:D2}yr:{days:D3}d:{simulatedTime.Hours:D2}h:{simulatedTime.Minutes:D2}m:{simulatedTime.Seconds:D2}s";
            label27.Text = $"Simulated Time: {formattedSimTime}";

            label3.Text = $"F: {Math.Sqrt(totalForceX * totalForceX + totalForceY * totalForceY).ToString("F8")}N";
            label4.Text = $"Timestep: {timeStep.ToString("F3")}";
            label5.Text = $"numSimulationSteps: {numSimulationSteps}";
            label10.Text = $"Frame Rate: {frameRate.ToString("F2")} fps";
            label6.Text = $"Frames: {frames} frames";
            label13.Text = $"Body count: {bodies.Count()}";

            if (bodies.Count <= 1)
            {
                timer.Stop();
                MessageBox.Show("Simulation has ended! Reason: No enough bodies, F=0");
                //Dodaj w tej linijce resetowanie do pozycji początkowych
                // Wywołanie metody resetowania
                ResetBodiesToInitialPositions();
                rjButton1.Enabled = true;
                rjButton2.Enabled = false;
                timeStep = 1;
                floatTrackBar1.Value = 1;
                trackBar1.Value = 5000;
                numSimulationSteps = 5000;
                frames = 0;
                panel2.Hide();

                // Odświeżenie widoku
                this.Invalidate();

            }
        }

        private void DrawQuadTree(Graphics g, QuadTree.QuadTreeNode node)
        {
            if (node != null)
            {
                float thickness = 0.5f; // Grubość linii
                Pen pen = new Pen(Brushes.White, thickness);

                Point[] polygonPoints = new Point[]
                {
                    new Point((int)node.Bounds.Left, (int)node.Bounds.Top),
                    new Point((int)node.Bounds.Right, (int)node.Bounds.Top),
                    new Point((int)node.Bounds.Right, (int)node.Bounds.Bottom),
                    new Point((int)node.Bounds.Left, (int)node.Bounds.Bottom)
                };

                g.DrawPolygon(pen, polygonPoints);

                if (node.HasChildren)
                {
                    foreach (var child in node.Children)
                    {
                        DrawQuadTree(g, child);
                    }
                }
            }
        }

        static string ConvertToScientificNotation(double value)
        {
            return value.ToString("0.###e0");
        }

        static string ConvertFromScientificNotation(string value)
        {
            if (double.TryParse(value, out double result))
            {
                return result.ToString();
            }

            return "Invalid input";
        }

        private void DrawSelectionSquare(Graphics g, Body body)
        {
            float x = (float)(body.X - body.Radius - 3); // Adjusting for padding
            float y = (float)(body.Y - body.Radius - 3); // Adjusting for padding
            float diameter = 2 * body.Radius + 6; // Adjusting for padding
            g.DrawRectangle(new Pen(Brushes.Yellow, 1), x, y, diameter, diameter);
        }

        private bool IsMouseOnBody(Point mousePosition, Body body)
        {
            double distance = Math.Sqrt(Math.Pow(mousePosition.X - body.X, 2) + Math.Pow(mousePosition.Y - body.Y, 2));
            return distance <= body.Radius;
        }

        private void DrawBody(Graphics g, Body body, Brush brush)
        {
            float x = (float)(body.X - body.Radius);
            float y = (float)(body.Y - body.Radius);
            float diameter = 2 * body.Radius;
            g.FillEllipse(brush, x, y, diameter, diameter);
        }
    }
    .......
  1. Quad Tree Class(Soon, i will be adding this algorithm):
 public class QuadTree
    {
        public class QuadTreeNode
        {
            public Rectangle Bounds { get; }
            public List<Body> Bodies { get; } = new List<Body>();
            public QuadTreeNode[] Children { get; private set; }

            public int MaxBodiesPerNode { get; private set; } = 4;

            public bool HasChildren => Children != null;

            private bool ShouldSubdivide() => !HasChildren && Bodies.Count >= MaxBodiesPerNode;

            public QuadTreeNode(Rectangle bounds, int maxBodiesPerNode)
            {
                Bounds = bounds;
                MaxBodiesPerNode = maxBodiesPerNode;
            }

            public void Subdivide()
            {
                int width = Bounds.Width / 2;
                int height = Bounds.Height / 2;

                Children = new QuadTreeNode[4];
                Children[0] = new QuadTreeNode(new Rectangle(Bounds.Left, Bounds.Top, width, height), MaxBodiesPerNode);
                Children[1] = new QuadTreeNode(new Rectangle(Bounds.Left + width, Bounds.Top, width, height), MaxBodiesPerNode);
                Children[2] = new QuadTreeNode(new Rectangle(Bounds.Left, Bounds.Top + height, width, height), MaxBodiesPerNode);
                Children[3] = new QuadTreeNode(new Rectangle(Bounds.Left + width, Bounds.Top + height, width, height), MaxBodiesPerNode);
            }
        }

        public QuadTreeNode Root { get; private set; }

        public QuadTree(Rectangle bounds, int maxBodiesPerNode = 4)
        {
            Root = new QuadTreeNode(bounds, maxBodiesPerNode);
        }

        public void Clear()
        {
            Root = new QuadTreeNode(Root.Bounds, Root.MaxBodiesPerNode);
        }

        public void Insert(Body body)
        {
            Insert(Root, body);
        }

        private void Insert(QuadTreeNode node, Body body)
        {
            if (node.Bounds.Contains(new Point((int)body.X, (int)body.Y)))
            {
                if (!node.HasChildren && node.Bodies.Count < node.MaxBodiesPerNode)
                {
                    node.Bodies.Add(body);
                }
                else
                {
                    if (!node.HasChildren)
                    {
                        node.Subdivide();
                        DistributeBodies(node);
                    }

                    foreach (var child in node.Children)
                    {
                        Insert(child, body);
                    }
                }
            }
        }

        private void DistributeBodies(QuadTreeNode node)
        {
            foreach (var storedBody in node.Bodies)
            {
                foreach (var child in node.Children)
                {
                    Insert(child, storedBody);
                }
            }

            node.Bodies.Clear();
        }
    }

Thats all for it, i hope someone can help me with rendering my simulation using sharpDX 2D. It is my second time trying sharpDx and it is too hard for me now!

Upvotes: -1

Views: 120

Answers (1)

wd357dui
wd357dui

Reputation: 373

The drawing part of the code is not hard, since the Direct2D equivalent of the draw calls that you use in your code are mostly the same.

It's the part of setting up a Direct2D device that is hard...

  • The easy part: change your draw functions

In the function DrawQuadTree, I can see that you are drawing a rectangle, then you can just use DrawRectangle. Is there a reason why you were using DrawPolygon?

private SharpDX.Direct2D1.SolidColorBrush WhiteBrush;
// It's better to create a brush once and reuse it
// instead of creating a new one every time

private void DrawQuadTree(SharpDX.Direct2D1.RenderTarget context, QuadTree.QuadTreeNode node)
{
    if (node != null)
    {
        float thickness = 0.5f;
        RawRectangleF rect = new RawRectangleF(
            node.Bounds.Left,
            node.Bounds.Top,
            node.Bounds.Right,
            node.Bounds.Bottom);
        context.DrawRectangle(rect, WhiteBrush, thickness);

        if (WhiteBrush == null)
        {
            WhiteBrush = new SharpDX.Direct2D1.SolidColorBrush(context,
                new RawColor4(1.0f, 1.0f, 1.0f, 1.0f));
        }

        if (node.HasChildren)
        {
            foreach (var child in node.Children)
            {
                DrawQuadTree(context, child);
            }
        }
    }
}
private SharpDX.Direct2D1.SolidColorBrush YellowBrush;
// It's better to create a brush once and reuse it
// instead of creating a new one every time

private void DrawSelectionSquare(SharpDX.Direct2D1.RenderTarget context, Body body)
{
    float x = (float)(body.X - body.Radius - 3);
    float y = (float)(body.Y - body.Radius - 3);
    float diameter = 2 * body.Radius + 6;

    RectangleF convert = new RectangleF(x, y, width: diameter, height: diameter);
    // convert "X Y Width Height" to "Left Top Right Bottom"
    RawRectangleF rect = new RawRectangleF(
        convert.Left, convert.Top, convert.Right, convert.Bottom);
            
    if (YellowBrush == null)
    {
        YellowBrush = new SharpDX.Direct2D1.SolidColorBrush(context,
            new RawColor4(1.0f, 1.0f, 0.0f, 1.0f));
    }

    context.DrawRectangle(rect, YellowBrush);
}
private void DrawBody(SharpDX.Direct2D1.RenderTarget context, Body body,
    SharpDX.Direct2D1.Brush brush)
{
    float x = (float)(body.X - body.Radius);
    float y = (float)(body.Y - body.Radius);
    float diameter = 2 * body.Radius;

    SharpDX.Direct2D1.Ellipse ellipse = new SharpDX.Direct2D1.Ellipse(
        center: new RawVector2(x, y),
        radiusX: diameter,
        radiusY: diameter);

    context.FillEllipse(ellipse, brush);
}

I've noticed that you have a function DrawVelocityVectors but you didn't post its source code here. If you need to draw a line in that function, the DrawLine function is available in Direct2D.


  • The hard part: Setting up Direct2D

now there are two ways to do this, the legacy way and the modern way.

with the legacy way, you will generally need to manage fewer things. with the modern way you need to manage much more stuff like swap chain, but you will have access to ID2D1DeviceContext, which inherits from and expands upon ID2D1RenderTarget, although you didn't seem to need it yet, every draw function you use is already available in the ID2D1RenderTarget interface so far.

The legacy way:

private SharpDX.Direct2D1.Factory d2dFactory;
private SharpDX.Direct2D1.WindowRenderTarget d2dRenderTarget;
private void Form1_Load(object sender, EventArgs e)
{
    // ...
    
    IntPtr TargetHWND = Controls.Find("name of the control you will render to", true).First().Handle;

    d2dFactory = new SharpDX.Direct2D1.Factory();
    RenderTargetProperties properties = new RenderTargetProperties(
            RenderTargetType.Default,
            new PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied),
            0, 0, RenderTargetUsage.None, FeatureLevel.Level_DEFAULT);
    HwndRenderTargetProperties hwndRenderTargetProperties = new HwndRenderTargetProperties
    {
        Hwnd = TargetHWND,
        PixelSize = new SharpDX.Size2(0, 0),
#if VSYNC
        PresentOptions = PresentOptions.None, // turn on VSync
#else
        PresentOptions = PresentOptions.Immediately, // turn off VSync
#endif
    };
    d2dRenderTarget = new SharpDX.Direct2D1.WindowRenderTarget(
        d2dFactory, properties, hwndRenderTargetProperties);
    
    // Create all the brushes you need
    WhiteBrush = new SolidColorBrush(d2dRenderTarget, new RawColor4(1.0f, 1.0f, 1.0f, 1.0f));
    YellowBrush = new SolidColorBrush(d2dRenderTarget, new RawColor4(1.0f, 1.0f, 0.0f, 1.0f));
    RedBrush = new SolidColorBrush(d2dRenderTarget, new RawColor4(1.0f, 0.0f, 0.0f, 1.0f));
    BlueBrush = new SolidColorBrush(d2dRenderTarget, new RawColor4(0.0f, 0.0f, 1.0f, 1.0f));
}
private SharpDX.Direct2D1.SolidColorBrush WhiteBrush;
private SharpDX.Direct2D1.SolidColorBrush YellowBrush;
private SharpDX.Direct2D1.SolidColorBrush RedBrush;
private SharpDX.Direct2D1.SolidColorBrush BlueBrush;

You need to surround your draw operation with BeginDraw and EndDraw

private RawColor4 backgroundColor = new RawColor4(0.0f, 0.0f, 0.0f, 1.0f);
// change the background color to your liking
private void Form1_Paint(object sender, PaintEventArgs e)
{
    d2dRenderTarget.BeginDraw();
    d2dRenderTarget.Clear(backgroundColor);
    // draw calls...
    d2dRenderTarget.EndDraw();
}

Notice that it's your responsibility to close all the DirectX interfaces after you exit the application

private void Form1_Closed(object sender, EventArgs e)
{
    d2dRenderTarget.Flush(); // wait for all operations on the GPU to finish

    WhiteBrush.Dispose();
    YellowBrush.Dispose();
    RedBrush.Dispose();
    BlueBrush.Dispose();

    d2dRenderTarget.Dispose();
    d2dFactory.Dispose();
}

The modern way:

private SharpDX.DXGI.Factory dxgiFactory;
#if USE_D3D10
private SharpDX.Direct3D10.Device d3d10Device;
#else
private SharpDX.Direct3D11.Device d3d11Device;
#endif
private SharpDX.DXGI.SwapChain dxgiSwapChain;
private SharpDX.DXGI.Surface backBuffer;

private SharpDX.DXGI.Device dxgiDevice;
private SharpDX.Direct2D1.Device d2dDevice;
private SharpDX.Direct2D1.DeviceContext d2dContext;
private SharpDX.Direct2D1.Bitmap1 d2dTarget;
private void Form1_Load(object sender, EventArgs e)
{
    IntPtr TargetHWND = Controls.Find("name of the control you will render to", true).First().Handle;

    dxgiFactory = new SharpDX.DXGI.Factory1();

    SwapChainDescription swapChainDescription = new SwapChainDescription
    {
        ModeDescription = new ModeDescription(Format.B8G8R8A8_UNorm),
        SampleDescription = new SampleDescription(1, 0),
        Usage = Usage.RenderTargetOutput,
        BufferCount = 2,
        OutputHandle = TargetHWND,
        IsWindowed = true,
        SwapEffect = SwapEffect.Discard,
        Flags = SwapChainFlags.None,
    };
#if USE_D3D10
    d3d10Device = new SharpDX.Direct3D10.Device(SharpDX.Direct3D10.DriverType.Hardware,
        SharpDX.Direct3D10.DeviceCreationFlags.BgraSupport);
    dxgiDevice = d3d10Device.QueryInterface<SharpDX.DXGI.Device>();
    dxgiSwapChain = new SharpDX.DXGI.SwapChain(dxgiFactory, d3d10Device, swapChainDescription);
#else
    d3d11Device = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware,
        SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport,
        SharpDX.Direct3D.FeatureLevel.Level_11_1, SharpDX.Direct3D.FeatureLevel.Level_11_0);
    dxgiDevice = d3d11Device.QueryInterface<SharpDX.DXGI.Device>();
    dxgiSwapChain = new SharpDX.DXGI.SwapChain(dxgiFactory, d3d11Device, swapChainDescription);
#endif

    backBuffer = dxgiSwapChain.GetBackBuffer<SharpDX.DXGI.Surface>(0);

    d2dDevice = new SharpDX.Direct2D1.Device(dxgiDevice);
    d2dContext = new SharpDX.Direct2D1.DeviceContext(d2dDevice, DeviceContextOptions.None);
    d2dTarget = new Bitmap1(d2dContext, backBuffer);

    // create all the brushes you need
    WhiteBrush = new SolidColorBrush(d2dContext, new RawColor4(1.0f, 1.0f, 1.0f, 1.0f));
    YellowBrush = new SolidColorBrush(d2dContext, new RawColor4(1.0f, 1.0f, 0.0f, 1.0f));
    RedBrush = new SolidColorBrush(d2dContext, new RawColor4(1.0f, 0.0f, 0.0f, 1.0f));
    BlueBrush = new SolidColorBrush(d2dContext, new RawColor4(0.0f, 0.0f, 1.0f, 1.0f));
}
private SharpDX.Direct2D1.SolidColorBrush WhiteBrush;
private SharpDX.Direct2D1.SolidColorBrush YellowBrush;
private SharpDX.Direct2D1.SolidColorBrush RedBrush;
private SharpDX.Direct2D1.SolidColorBrush BlueBrush;

Apart from BeginDraw and EndDraw, you will additionally need to set render target and call Present on the swap chain

private RawColor4 backgroundColor = new RawColor4(0.0f, 0.0f, 0.0f, 1.0f);
// change the background color to your liking
private void Form1_Paint(object sender, PaintEventArgs e)
{
    d2dContext.BeginDraw();
    d2dContext.Target = d2dTarget;
    d2dContext.Clear(backgroundColor);
    // draw calls...
    d2dContext.Target = null;
    d2dContext.EndDraw();
#if VSYNC
    dxgiSwapChain.Present(1, PresentFlags.None);
#else
    dxgiSwapChain.Present(0, PresentFlags.None);
#endif
}

If the size of the control element you rendering to may be resized, you will need to handle it as well

private void Form1_SizeChanged(object sender, EventArgs e)
{
    Size Resolution = Controls.Find("name of the control you will render to", true).First().Size;
    d2dContext.Flush(); // wait for all operations on the GPU to finish
    d2dTarget.Dispose();
    backBuffer.Dispose();
    dxgiSwapChain.ResizeBuffers(2, Resolution.Width, Resolution.Height, Format.B8G8R8A8_UNorm, SwapChainFlags.None);
    backBuffer = dxgiSwapChain.GetBackBuffer<SharpDX.DXGI.Surface>(0);
    d2dTarget = new Bitmap1(d2dContext, backBuffer);
}

remember to close all the interfaces

private void Form1_Closed(object sender, EventArgs e)
{
    d2dContext.Flush(); // wait for all operations on the GPU to finish

    WhiteBrush.Dispose();
    YellowBrush.Dispose();
    RedBrush.Dispose();
    BlueBrush.Dispose();

    d2dTarget.Dispose();
    d2dContext.Dispose();
    d2dDevice.Dispose();
    backBuffer.Dispose();
    dxgiSwapChain.Dispose();
    dxgiDevice.Dispose();
#if USE_D3D10
    d3d10Device.Dispose();
#else
    d3d11Device.Dispose();
#endif
    dxgiFactory.Dispose();
}

Upvotes: 0

Related Questions