How to use the projection/camera technique in c#
Asked Answered
T

1

13

I drew the following grid:

Grid (Top-View)

The above grid is drawn using the following two methods, one to calculate the grid and the other to calculate the centers for each cell:

//makes grid in picture box
private void drawGrid(int numOfCells, int cellSize, Graphics gr)
{
    Pen p = new Pen(Color.SteelBlue);

    for (int i = 0; i < Math.Sqrt(numOfCells) + 1; i++)
    {
        // Vertical
        gr.DrawLine(p, i * cellSize + 300, 200, i * cellSize + 300, 700);
        // Horizontal
        gr.DrawLine(p, 300, i * cellSize+200, 800, i * cellSize+200);
    }

    this.topology.SendToBack();
}


//draw the center point for each cell of the grid 
private void drawCenters(Graphics gr)
{
    for (int j = 0; j < rows; j++)
    {
        for (int i = 0; i < columns; i++)
        {
            gr.FillRectangle(Brushes.IndianRed, cellsCenters[0, i], cellsCenters[1, j], 3, 3);
        }
    }
}

My question is how to make this grid appear as in the following picture and how to place the nodes at different cells (random deployment) in such grid.

Expected result

I need the grid to be drawn in a 3D view in which I have z as well as x and y!

Thoracotomy answered 18/7, 2017 at 6:30 Comment(3)
Asuming you are going for a orthographic projection: Simply choose a vector for Z (dX, dY) and add Z*that vector that to whatever you are drawing.Versify
It's really simple: Imagine you have a 3D point (x3,y3,z3) that you want to project onto a plane (convert it to a 2D point (x2,y2)), in this case your screen. If you look at it from the top (as you do right now) you ignore Z and use (x2,y2) = (x3,y3). If you want to look at it from the side (almost like in your second picture) you would ignore x and use (x2,y2) = (y3,z3). you want something in between: (x2,y2) so you need to choose a certain factor to multiply. For example: (x2,y2) = (y3+x3*0.2,z3+x3*0.1)Versify
If you can provide a minimal reproducible example of your code, I might be able to modify itVersify
V
15

Note: Many of the constructs already exist in one form or another, here I'm showing you how to do it from scratch. enter image description here Same control, same data, different perspective

Since your data is 3-dimensional, you need store your positional data 3-dimensional so you don't have to calculate everything by hand every time you change the perspective:

[TypeConverter(typeof(ExpandableObjectConverter))]
public struct Point3D
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }

    public Point3D(int x, int y, int z)
    {
        X = x;
        Y = y;
        Z = z;
    }
}

To project these 3D-points to screen coordinates (2D) you need some kind of projection/camera system. Usually you would do this with Matrix/Vector math, but for this example the following orthographic approach will suffice.

The perspective handles the transformation from 3D to 2D. Depending on the parameters your graphic will skew/roatate/translate etc.

[TypeConverter(typeof(ExpandableObjectConverter))]
public class Perspective
{
    public float X_shift { get; set; } = 0.0f;
    public float Y_shift { get; set; } = 0.0f;

    public float X_x { get; set; } = 1.0f;
    public float X_y { get; set; } = 0.0f;
    public float X_z { get; set; } = 0.0f;

    public float Y_x { get; set; } = 0.0f;
    public float Y_y { get; set; } = 1.0f;
    public float Y_z { get; set; } = 0.0f;

    public PointF Project(Point3D p)
    {
        return new PointF(X_shift + X_x * p.X + X_y * p.Y + X_z * p.Z, Y_shift + Y_x * p.X + Y_y * p.Y + Y_z * p.Z);
    }
}

All you need to do now, is draw everything as usual, but with 3D-coordinates translated to 2D. The following control draws a grid (at depth 400) and two sensors.

using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public class PerspectiveGrid : Control
    {
        private Perspective _perspective;

        public Perspective Perspective
        {
            get { return _perspective; }
            set
            {
                _perspective = value; 
                Invalidate();
            }
        }

        public PerspectiveGrid()
        {
            Perspective = new Perspective
            {
                X_shift = 100,
                Y_shift = 10,

                X_x = -0.2f,
                X_y = 1.0f,
                X_z = 0.0f,

                Y_x = 0.2f,
                Y_y = 0.0f,
                Y_z = 1.0f,
            };
        }

        /// <summary>
        /// Paints a Grid at Z = 400 and two Sensors
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            DrawGrid(10,40,400,e.Graphics);

            DrawSensor(new Point3D(80, 120, 400), new Point3D(80, 120, 200), e.Graphics);
            DrawSensor(new Point3D(240, 240, 400), new Point3D(240, 240, 120), e.Graphics);
        }

        /// <summary>
        /// Draws a sensor at the specified position(s)
        /// </summary>
        private void DrawSensor(Point3D from, Point3D to, Graphics gr)
        {
            DrawLine(gr, Pens.Black, from, to);
            DrawSphere(gr, Pens.Black, Brushes.Orange, to, 6);
        }

        /// <summary>
        /// Draws a sphere as a Circle at the specified position
        /// </summary>
        private void DrawSphere(Graphics gr, Pen outline, Brush fill, Point3D center, float radius)
        {
            PointF center2D = Project(center);
            gr.FillEllipse(fill, center2D.X - radius, center2D.Y - radius, radius * 2, radius * 2);
            gr.DrawEllipse(outline, center2D.X - radius, center2D.Y - radius, radius * 2, radius * 2);
        }

        /// <summary>
        /// Draws the grid at the specified depth
        /// </summary>
        private void DrawGrid(int numOfCells, int cellSize, int depth, Graphics gr)
        {
            Pen p = Pens.SteelBlue;

            for (int i = 0; i <= numOfCells; i++)
            {
                // Vertical
                DrawLine(gr, p, new Point3D(i * cellSize, 0 , depth), new Point3D(i * cellSize, numOfCells * cellSize, depth));
                // Horizontal
                DrawLine(gr, p, new Point3D(0, i * cellSize, depth), new Point3D(numOfCells * cellSize, i * cellSize, depth));
            }
        }

        /// <summary>
        /// Draws a line from one 3DPoint to another
        /// </summary>
        private void DrawLine(Graphics graphics, Pen pen, Point3D p1, Point3D p2)
        {
            PointF pointFrom = Project(p1);
            PointF pointTo = Project(p2);

            graphics.DrawLine(pen, pointFrom, pointTo);
        }

        /// <summary>
        /// Projects a Point3D to a PointF
        /// </summary>
        private PointF Project(Point3D p)
        {
            return Perspective.Project(p);
        }
    }
}

A couple of links that might help you build on these concepts:

Orthographic projection
Quaternion
Math Library with Matrix and Vector support

Versify answered 20/7, 2017 at 11:18 Comment(12)
@Thoracotomy Can't really help much without knowing your code (works in my example) but this error occurs when you try to access a method via the class name instead of the instance. In my example Perspective is the name of the class and the property so it works - instead of AUV_Topology.Perspective you need to use whatever name you gave it. Perspective needs instance values to work with, so making it static wouldn't make sense. Alternatively you can of course create a static instance in one of your classes but I recommend you just add an instance of Perspective to your Control instead.Versify
@Thoracotomy Adjust the X_shift and Y_shift parameters of your Projection to match the previously hard-coded offsets, or adjust the coordinates of the grid in code.Versify
If your sensors float beside the grid, change their coordinates. If the grid is not in the right spot, adjust the DrawGrid method (add an offset to the drawn positions).Versify
The Perspective is the logic that turns a 3D coordinate into a 2D coordinate. X/Y_shift is a constant offset that will move every single point by that amount without distorting the image. X_x is a factor that signifies how much the 3D X coordinate will influence the 2D X coordinate, X_y how much the 3D y coordinate influences the 2D X coordinate, etc. X_x to Y_z determine the Point of view. When you are looking directly from the front/back/left/right/top or bottom, one of the _x,_y,_z will be 1 all others 0.Versify
The Perspective in your latest screenshot is looking good, but you need to make sure the grid coordinates have the same origin as your sensors (as mentioned before). Have you tried adding a fixed offset to the x/y parameters of the Point3D constructors in the DrawGrid method?Versify
Have you read any of my comments? The sensor positions and the grid origins don't match up. Tinkering with the perspective won't change that. Re-read my last 3 comments and adjust what I mentioned there.Versify
I don't even understand your questions anymore ... Read the code, understand the code, adjust the code. I'm sure you'll figure it out.Versify
As explained the projection is orthographic (since the grid in your sketch is too). If you want objects that are further away from the screen to be smaller, you need to create a different projection. Details about the differences hereVersify
I've got what I want almost - Thank U - : dropbox.com/s/kn3iofltryyqgtv/plane.png?dl=0Thoracotomy
I read them ... why don't you just use the Project method to project the centers? You don't have to calculate the projection yourself. Just put the 3D coordinates in there and use the 2D output. It's that simple. Also you might want to adjust the order in which you draw (Grid, then centers, then sensors back to front) to avoid unwanted overlap.Versify
Then this is a great opportunity to rise to the challenge. I'm sure if you read and understand the code to figure out the final details yourself, then this experience will be a lot more valuable for you than if someone (me) just gives you a finished solution. StackOverflow is not a code-writing service. It's meant to help you improve your coding and help others that face the same problems. I will not write your code for you - I've given you the basis for a working solution, several links to articles on this and related topics and 11(!) answers to follow-up questions - this should suffice.Versify
Yes, it is very simple ... I drew it : [ dropbox.com/s/lqnlmnseyhrkknk/with_centers.png?dl=0 ]. Many thanks, It is very nice and strong answer (y).Thoracotomy

© 2022 - 2024 — McMap. All rights reserved.