XMAMan.GraphicEngine2D 0.0.1

dotnet add package XMAMan.GraphicEngine2D --version 0.0.1
                    
NuGet\Install-Package XMAMan.GraphicEngine2D -Version 0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XMAMan.GraphicEngine2D" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XMAMan.GraphicEngine2D" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="XMAMan.GraphicEngine2D" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XMAMan.GraphicEngine2D --version 0.0.1
                    
#r "nuget: XMAMan.GraphicEngine2D, 0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package XMAMan.GraphicEngine2D@0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XMAMan.GraphicEngine2D&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=XMAMan.GraphicEngine2D&version=0.0.1
                    
Install as a Cake Tool

How to use the GraphicEngine2D


There is support for WinForm and WPF. Outputmethods are 2D only.

2D-Output via WinForm


Create a new WinForm-application in VisualStudio and add the XMAMan.GraphicEngine2D-NuGet. After this go the the toolbox and add the GraphicPanel2D-Panel to your MainWindow-Panel. If you want to show a movement, then add also a System.windows.Forms.Timer-object to your Form1-panel.

public partial class Form1 : Form
{
    private string DataDirectory = @"..\..\..\..\Data\";

    private Vector2D mousePosition = new Vector2D(0, 0);
    private Vector2D ballPosition = new Vector2D(100, 100);
    private Vector2D ballSpeed = new Vector2D(100, 10);
    private int ballRadius = 10;

    public Form1()
    {
        InitializeComponent();

        this.panel.Mode = GraphicPanels.Mode2D.OpenGL_Version_3_0;

        this.panel.MouseClick += new MouseEventHandler(GraphicPanel2D_MouseClick);


        this.timer1.Start();
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        MoveBall(this.timer1.Interval / 1000.0f);
        Draw();
    }

    private void Draw()
    {
        this.panel.ClearScreen(Color.AliceBlue);

        this.panel.DrawRectangle(new Pen(Color.Black, 2), 0, 0, this.panel.Width, this.panel.Height);

        this.panel.DrawFillCircle(Color.Green, this.ballPosition, ballRadius);
        this.panel.DrawCircle(Pens.Red, this.mousePosition, 10);

        this.panel.DrawFillRectangle(DataDirectory + "Face_04.png", 10, 10, 300, 200, true, Color.White);

        this.panel.DrawString(10, 10, Color.Black, 15, "Left mouse click for placing red circle");
        this.panel.DrawString(10, 30, Color.Black, 15, "Press 1,2,3 or 4 for switching mode. Current mode: " + this.panel.Mode);


        this.panel.FlipBuffer();
    }

    private void MoveBall(float time)
    {
       this.ballPosition += this.ballSpeed * time;
       
       if (this.ballPosition.X - ballRadius < 0 || this.ballPosition.X + ballRadius > this.panel.Width)
       {
           this.ballSpeed.X *= -1;
       }

       if (this.ballPosition.Y - ballRadius < 0 || this.ballPosition.Y + ballRadius > this.panel.Height)
       {
         this.ballSpeed.Y *= -1;
       }
    }

    private void GraphicPanel2D_MouseClick(object sender, MouseEventArgs e)
    {
        this.mousePosition = new Vector2D(e.X, e.Y);
    }

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (keyData == Keys.D1) this.panel.Mode = GraphicPanels.Mode2D.OpenGL_Version_1_0;
        if (keyData == Keys.D2) this.panel.Mode = GraphicPanels.Mode2D.OpenGL_Version_3_0;
        if (keyData == Keys.D3) this.panel.Mode = GraphicPanels.Mode2D.Direct3D_11;
        if (keyData == Keys.D4) this.panel.Mode = GraphicPanels.Mode2D.CPU;

        return base.ProcessCmdKey(ref msg, keyData);
    }
}

This will produce this image with a moving circle: Rasterizer2D

2D-Output via WPF


Create a WPF-Project (by example with usage of .NET 8.0)

You need this in your csproj-File because GraphicPanel2D is a winForm-control.

 <UseWindowsForms>true</UseWindowsForms>

Add this to your xaml-File to place the GraphicPanel

<Border BorderThickness="5" BorderBrush="DarkBlue" x:Name="graphicControlBorder"/>

For you code-behind you need this

 var panel = new GraphicPanel2D() { Width = 100, Height = 100, Mode = Mode3D.OpenGL_Version_3_0 }; 
 this.graphicControlBorder.Child = new GraphicControl(panel);

 this.DataContext = new ViewModel(panel);

Now you can use the GraphicPanel2D-Object in your ViewModel to send the drawing-commands to the view.

class ViewModel
{
    private GraphicPanel2D panel;

    private System.Windows.Threading.DispatcherTimer timer;
    private string DataDirectory = @"..\..\..\..\..\Data\";

    private List<Vertex2D[]> voronioPolygons = null;
    private List<Point> voronoiCellPoints = null;
    private TextureData marioTexture = null;
    private int spriteNr = 0;

    public ViewModel(GraphicPanel2D panel)
    {
        this.panel = panel;

        this.panel.Mode = Mode2D.OpenGL_Version_3_0;
        this.panel.MouseClick += GraphicPanel2D_MouseClick;
        this.panel.SizeChanged += GraphicPanel2D_SizeChanged;

        this.timer = new System.Windows.Threading.DispatcherTimer();
        this.timer.Interval = new TimeSpan(0, 0, 0, 0, 30);//30 ms
        this.timer.Tick += Timer_Tick;
        this.timer.Start();

        CreateVoronoiPolygons();
    }

    private void GraphicPanel2D_MouseClick(object sender, MouseEventArgs e)
    {
        this.voronoiCellPoints = GraphicPanel2D.GetRandomPointList(10, this.panel.Width, this.panel.Height, new Random(0));
        this.voronioPolygons = GraphicPanel2D.GetVoronoiPolygons(this.panel.Size, this.voronoiCellPoints);
    }

    private void GraphicPanel2D_SizeChanged(object sender, EventArgs e)
    {
        Draw(this.panel, DataDirectory, this.spriteNr, this.voronioPolygons, this.voronoiCellPoints, this.marioTexture, false, Matrix4x4.Ident());
    }

    private void Timer_Tick(object? sender, EventArgs e)
    {
        spriteNr++;
        Draw(this.panel, DataDirectory, this.spriteNr, this.voronioPolygons, this.voronoiCellPoints, this.marioTexture, false, Matrix4x4.Ident());
    }

    //First, it creates a new texture bitmap by drawing into a framebuffer and then taking the data from it.This bitmap is then
    //decomposed into polygons using Voronoi, and these polygons are then drawn using the polygon drawing function.
    private void CreateVoronoiPolygons()
    {
        float yAngle = 10;

        this.marioTexture = CreateMarioTexture(this.panel, DataDirectory + "nes_super_mario_bros.png", yAngle);
        this.voronoiCellPoints = GraphicPanel2D.GetRandomPointList(10, marioTexture.Image.Width, marioTexture.Image.Height, new Random(0));
        this.voronioPolygons = GraphicPanel2D.GetVoronoiPolygons(marioTexture.Image.Size, this.voronoiCellPoints);

        this.voronioPolygons = this.voronioPolygons.Select(x => TransformPolygon(x, new Vector2D(340, 30))).ToList(); //Move to position (340, 30)
    }


    public class TextureData
    {
        public Bitmap Image;
        public string TextureName;
    }

    //Creates a Mario texture in the currently selected 2D mode's texture memory in the GraphicsPanel2D.
    //Return value: Name of the created texture.If the texture already exists, it will be updated.
    public static TextureData CreateMarioTexture(GraphicPanel2D graphic, string texturFile, float yAngle)
    {
        int width = 80;
        int height = 80;
        int frameBufferId = graphic.CreateFramebuffer(width, height, true, false);
        graphic.EnableRenderToFramebuffer(frameBufferId);
        graphic.ClearScreen(Color.Transparent);
        //Take only the section where Mario is visible from the large image and paint it into the framebuffer
        graphic.DrawImage(texturFile, width / 2, height / 2, width, height, 243, 202, 283 - 243, 240 - 202, true, Color.FromArgb(255, 255, 255), 0, yAngle);
        graphic.FlipBuffer();
        int colorTextureId = graphic.GetColorTextureIdFromFramebuffer(frameBufferId);
        Bitmap marioTexture = graphic.GetTextureData(colorTextureId);
        graphic.DisableRenderToFramebuffer();
        graphic.CreateOrUpdateNamedBitmapTexture("MarioBitmap", marioTexture);

        //marioTexture.Save("MarioTexture.bmp");
        //Color c = marioTexture.GetPixel(0, 0);

        return new TextureData()
        {
            TextureName = "MarioBitmap",
            Image = marioTexture
        };
    }

    public static Vertex2D[] TransformPolygon(Vertex2D[] polygon, Vector2D position)
    {
        return polygon.Select(x => new Vertex2D(x.Position + position, x.Textcoord)).ToArray();
    }

    public void Draw(GraphicPanel2D graphic, string dataDirectory, int spriteNr, List<Vertex2D[]> voronioPolygons, List<Point> voronoiCellPoints, TextureData marioTex, bool showScreenAlpha, Matrix4x4 transform)
    {
         /*graphic.ClearScreen(Color.White);
         foreach (var polygon in this.voronioPolygons)
         {
             graphic.DrawFillPolygon(dataDirectory + "thumb_COLOURBOX5847554.jpg", false, Color.FromArgb(255, 255, 255), polygon.ToList());
             graphic.DrawPolygon(new Pen(Color.Black, 2), polygon.Select(x => x.Position).ToList());
         }
         foreach (var point in this.voronoiCellPoints)
         {
             graphic.DrawFillCircle(Color.Red, new Vector2D(point.X, point.Y), 2);
         }

         graphic.FlipBuffer();
         return;*/

        string text = graphic.Mode.ToString();
        graphic.ClearScreen(dataDirectory + "thumb_COLOURBOX5847554.jpg");
        graphic.MultTransformationMatrix(transform);
        Size size = graphic.GetStringSize(20, text);

        graphic.EnableScissorTesting(300, 20, 60, 25);
        graphic.DrawImage(dataDirectory + "nes_super_mario_bros.png", 300, 20, 60, 50, 243, 202, 283 - 243, 240 - 202, true, Color.FromArgb(255, 255, 255));
        graphic.DisableScissorTesting();
        graphic.DrawImage(dataDirectory + "nes_super_mario_bros.png", 300, 100, 60, 50, 243, 202, 283 - 243, 240 - 202, true, Color.FromArgb(255, 255, 255), 0, 180);

        graphic.DrawImage("MarioBitmap", 420, 50, 40, 40, 0, 0, 80, 80, true, Color.FromArgb(0, 255, 0));

        //Check that the alpha value of the Mario texture is correct
        graphic.CreateOrUpdateNamedBitmapTexture("MarioBitmapAlpha", BitmapHelp.GetAlphaChannel(marioTex.Image));
        graphic.DrawFillRectangle("MarioBitmapAlpha", 420, 90, 40, 40, false, Color.FromArgb(255, 255, 255));

        foreach (var polygon in voronioPolygons)
        {
            graphic.DrawFillPolygon("MarioBitmap", false, Color.FromArgb(255, 255, 255), polygon.ToList());
            graphic.DrawPolygon(new Pen(Color.Black, 2), polygon.Select(x => x.Position).ToList());
        }
        foreach (var point in voronoiCellPoints)
        {
            graphic.DrawFillCircle(Color.Red, new Vector2D(point.X + 340, point.Y + 30), 2);
        }

        //graphic.DrawRectangle(new Pen(Color.Black, 3), 30, 30, size.Width, size.Height);
        graphic.DrawFillRectangle(dataDirectory + "Mario.png", 10, 50, 40, 40, true, Color.FromArgb(spriteNr % 255, 255, 255, 255));
        graphic.DrawString(30, 30, Color.Black, 10, text);
        graphic.DrawLine(new Pen(Color.Black, 5), new Vector2D(0, 0), new Vector2D(graphic.Width, graphic.Height));
        graphic.DrawPixel(new Vector2D(30, 30), Color.Green, 5);
        graphic.DrawFillPolygon(dataDirectory + "Decal.bmp", new List<Vector2D>() { new Vector2D(100, 100), new Vector2D(110, 110), new Vector2D(120, 70), new Vector2D(100, 50), new Vector2D(70, 90) }, false, Color.FromArgb(255, 255, 255));
        graphic.DrawFillPolygon(dataDirectory + "Decal.bmp", new List<Vector2D>() { new Vector2D(100 + 100, 100), new Vector2D(110 + 100, 110), new Vector2D(120 + 100, 70), new Vector2D(100 + 100, 50), new Vector2D(70 + 100, 90) }, false, Color.FromArgb(spriteNr % 255, 255, 255, 255));
        graphic.DrawFillPolygon(Color.Red, new List<Vector2D>() { new Vector2D(100, 100 + 70), new Vector2D(110, 110 + 70), new Vector2D(120, 70 + 70), new Vector2D(100, 50 + 70), new Vector2D(70, 90 + 70) });
        graphic.DrawFillPolygon(Color.Green, new List<Vector2D>() { new Vector2D(100 + 100, 100 + 70), new Vector2D(110 + 100, 110 + 70), new Vector2D(120 + 100, 70 + 70), new Vector2D(100 + 100, 50 + 70), new Vector2D(70 + 100, 90 + 70) });
        graphic.DrawPolygon(new Pen(Color.BlueViolet, 3), new List<Vector2D>() { new Vector2D(100, 100), new Vector2D(110, 110), new Vector2D(120, 70), new Vector2D(100, 50), new Vector2D(70, 90) });
        graphic.DrawCircle(new Pen(Color.BurlyWood, 3), new Vector2D(40, 200), 35);
        graphic.DrawFillCircle(Color.BurlyWood, new Vector2D(40, 250), 25);
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 200, 200, 30, 20, true, Color.FromArgb(255, 255, 255));
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 240, 240, 40, 30, true, Color.FromArgb(255, 255, 255), 30);
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 280, 280, 40, 30, true, Color.FromArgb(255, 255, 255), 30, 50);
        graphic.DrawFillRectangle(Color.FromArgb(200, 255, 0, 0), 200 + 70, 200, 30, 20);
        graphic.DrawFillRectangle(Color.Green, 240 + 70, 240, 40, 30, 30);
        graphic.DrawFillRectangle(Color.Blue, 280 + 70, 280, 40, 30, 30, 50);

        //Here I'm testing the case of drawing a triangle with P0.X==P1.X
        //Observation: OpenGL always seems to shift a line one pixel to the left. If I specify (3,3), it draws at (2,3)
        //For a triangle, the top left corner is correct and the bottom right corner is shifted one pixel to the top left
        graphic.DrawPolygon(new Pen(Color.Red, 1), new List<Vector2D>() { new Vector2D(140, 200), new Vector2D(162, 200), new Vector2D(162, 211), new Vector2D(140, 211) });
        graphic.DrawFillPolygon(Color.Green, new List<Vector2D>() { new Vector2D(140, 200), new Vector2D(150, 200), new Vector2D(150, 210), new Vector2D(140, 210) });
        graphic.DrawFillPolygon(Color.Blue, new List<Vector2D>() { new Vector2D(151, 200), new Vector2D(161, 200), new Vector2D(161, 210), new Vector2D(151, 210) });

        //Vertical lines with different widths
        for (int i = 0; i < 5; i++)
        {
            graphic.DrawLine(new Pen(Color.Red, 1 + i), new Vector2D(130 + i * 20, 240), new Vector2D(130 + i * 20, 270));
            graphic.DrawPixel(new Vector2D(130 + i * 20, 235), Color.Green, i + 1);
            graphic.DrawPixel(new Vector2D(130 + i * 20, 235), Color.Red, 1);

            for (int j = 0; j <= i; j++)
            {
                //One pixel is moved up by one
                graphic.DrawPixel(new Vector2D(130 + i * 20 + j - (i + 1) / 2, 240 + j + 0.5f), Color.Yellow, 1);
            }
        }

        //Horizontal lines of different widths
        for (int i = 0; i < 5; i++)
        {
            graphic.DrawLine(new Pen(Color.Red, 1 + i), new Vector2D(140, 300 + i * 10), new Vector2D(170, 300 + i * 10));
            graphic.DrawPixel(new Vector2D(130, 300 + i * 10), Color.Green, i + 1);
            graphic.DrawPixel(new Vector2D(130, 300 + i * 10), Color.Red, 1);

            for (int j = 0; j <= i; j++)
            {
                //One pixel is moved up by one
                graphic.DrawPixel(new Vector2D(140 + j, 300 + i * 10 + j - (i + 0.5f) / 2), Color.Yellow, 1);
            }
        }


        //int spriteNr = 0;
        graphic.DrawSprite(dataDirectory + "fire1.png", 11, 11, spriteNr % 11, spriteNr / 11, 20, 180, 40, 40, 0.01f, true, Color.FromArgb(spriteNr % 255, 255, 255, 255));

        //graphic.DrawLine(new Pen(Color.Blue, 5), new Vector2D(40, 200), new Vector2D(40, 250));
        graphic.FlipBuffer();

        if (showScreenAlpha)
        {
            //Prüfe ab, dass der Alpha-Wert der ScreenShoot-Funktion stimmt
            Bitmap screenAlpha = BitmapHelp.GetAlphaChannel(graphic.GetScreenShoot());
            graphic.CreateOrUpdateNamedBitmapTexture("ScreenAlpha", screenAlpha);
            graphic.DrawFillRectangle("ScreenAlpha", 420, 130, 40, 40, false, Color.FromArgb(255, 255, 255));
        }

        //Check that the sprite display shows the correct sub-image (expected numbers from 1 to 6)
        int spriteCounter = 0;
        for (int x = 0; x < 3; x++)
            for (int y = 0; y < 2; y++)
            {
                graphic.DrawSprite(dataDirectory + "Numbers.png", 3, 2, x, y, 325 + x * 20, 177 + y * 20, 20, 20, 0.01f, true, Color.White);
                spriteCounter++;
            }

        //Since no DepthTesting is activated, the line is drawn over the turtle
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 410, 210, 30, 20, true, Color.FromArgb(255, 255, 255));
        graphic.DrawLine(new Pen(Color.Blue, 2), new Vector2D(404, 220), new Vector2D(450, 220));

        //DepthTesting is activated
        graphic.EnableDepthTesting();

        //The Z-value of the turtle is smaller than the Z-value of the line. So the line is in front
        graphic.ZValue2D = 1 - 5; //Rear
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 410, 230, 30, 20, true, Color.FromArgb(255, 255, 255));
        graphic.ZValue2D = 2 - 5; //Front
        graphic.DrawLine(new Pen(Color.Blue, 2), new Vector2D(404, 240), new Vector2D(450, 240));

        //The Z-value of the turtle is greater than the Z-value of the line. Therefore, the turtle is in front
        graphic.ZValue2D = 2 - 5; //Front
        graphic.DrawFillRectangle(dataDirectory + "Tortoise.png", 410, 250, 30, 20, true, Color.FromArgb(255, 255, 255));
        graphic.ZValue2D = 1 - 5; //Rear
        graphic.DrawLine(new Pen(Color.Blue, 2), new Vector2D(404, 260), new Vector2D(450, 260));

        graphic.DisableDepthTesting();
    }
}

This will produce this image with a moving fire-sprite: wpf2d_example2

Product Compatible and additional computed target framework versions.
.NET Framework net is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on XMAMan.GraphicEngine2D:

Package Downloads
XMAMan.PhysicEngine.GrxExtension

extends the physic engine with graphic output Used sourcecode is https://github.com/XMAMan/RigidBodyPhysics/commit/f0b97c091f324cf91dce92a5b227eced5e6e5717

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1 151 5/7/2025

First Version