3

(I am aware using windows forms for this kind of project is stupid and inefficient but it is a requirement for the college course I'm doing so I'm stuck using this)

in windows forms you can draw an image with 3 vertices specified for bottom left, top left and top right vertices, which allows you to skew the image in a slanted or stretched way, but it doesn't allow for the 4th vertex to be specified, so it ends up looking like this 3d cube but side textures have edges of same length, so it does not conform to the shape correctly

I can use a texture brush and a matrix transformation to make it fill the cube side properly but the issue there is that the texture is still the same size for all the "z distance", so it tiles instead of conforming to the "3d" shape as seen here 3d cube side texture fills the polygon correctly and is "squished" but is not "squeezed" so does not give the 3d effect

so I'm just wondering if there's any way to "correctly" transform the texture or image so that it fits the polygon shape correctly and to give a "3d" effect without an extremely ridiculous, tedious and performance taxing method like modifying a texture instance directly before rendering on runtime

i have tried to use graphics.drawImage({image file}, {3 vertex points to conform to}); to draw the image to roughly fill the gap, and I have also converted the faces from quads to triangles to placed them that way but the texture ends up stretched and rotated incorrectly with that method, unless there is a way I can transform the drawimage so that rendering with triangles doesn't squish the texture being rendered.

I have tried to use a texture brush and graphics.fillpolygon({texture brush}, {all 4 vertex points of face}); with a matrix transformation applied to it although it is slightly more taxing than draw image it does fill the polygon completely but it also doesn't get the desired "pinched" effect on the image texture, windows forms matrix transformations have scale x, rotation x, rotation y, scale y, translate x and translate y for reference

Edit: "true 3d" is kind of unreasonable to achieve in winforms without external libraries or hosting directX/WPF in forms, so i've settled for isometric 3d for now based on the answer by dr.null,

isometric 3D rendering with custom vertex locations

i am calculating the positions for the vertices with the following isometric projection equation

    public PointF CalculateIsometricProjection(double[] Position)
{
    return new PointF(Globals.width / 2 + (float)(((Globals.CameraX + X + Position[0]) * 0.5) + ((Globals.CameraZ + Z + Position[1]) * -0.5)),
    Globals.height / 2 + (float)(((Globals.CameraX + X + Position[0]) * 0.25f) + ((Globals.CameraZ + Z + Position[1]) * 0.25f) - (((Globals.CameraY + Position[2]) + (Y / 3) * 2))));
}

where X, Y and Z are vertex coordinates relative to the model, Position is a double array containing X Z and Y positions relative to the world and globals.height/globals.width is the height and width of the form window and globals.camera is the camera transform

Y is height in this instance, as long as each face is a rectangle or parallelogram of some kind the 3d models can be rendered isometrically with textures, otherwise they will need flat colours to be filled as the textures can't transform to non parallelogram shapes

3
  • 3
    Why winforms? Without using external libraries the WPF has build-in support for rendering 3D (some tutorial). If you must use winforms, then how about hosting WPF controls in winforms? Commented Oct 13 at 10:09
  • 1
    Windows forms is a requirement to get high-full marks in my college course for computer science, the project is a game made in windows forms, we can use libraries and external frameworks attached to windows forms as long as they "don't develop it for you", like a physics or collision framework would not be allowed, but attaching a directX renderer would be allowed, you can use other frameworks but you'll have a significantly harder time getting high marks and using game engines and game frameworks gets you a fail, but I'll check out the WPF controls in forms, thank you for the suggestion! Commented Oct 13 at 12:24
  • 1
    See also 'Perspective' transformation with GDI+ in C#. No accepted answer, but some interesting comments. Commented Oct 13 at 15:57

1 Answer 1

5

Your Prof. may need to check your college-level math and geometry skills. Whether you can accomplish the task without using helper libraries or frameworks designed primarily to make such a task extremely easy without even knowing or understanding the math behind it. Therefore, requirement to use an abstract tech like WinForms makes sense in this context.

Without further ado,

If you review the overloads of the Graphics.DrawImage method, especially the overloads that take Point or 'PointF' arrays, you'll read in the param description:

destPoints
Array of three Point structures that define a parallelogram.

srcRect
Rectangle structure that specifies the portion of the image object to draw.

and the Remarks section clarifies:

The destPoints parameter specifies three points of a parallelogram. The three Point structures represent the upper-left, upper-right, and lower-left corners of the parallelogram. The fourth point is extrapolated from the first three to form a parallelogram.

The srcRect parameter specifies a rectangular portion of the image object to draw. This portion is scaled and sheared to fit inside the parallelogram specified by the destPoints parameter.

Accordingly, you can draw a cube by defining the parallelograms of the front, top, and left faces or sides. This needs a shear angle used to calculate the points of each parallelogram, and as your posted images suggest, the top and left sides of the cube can have different depths. So, convex hull is also considered.

Example:

public Bitmap DrawImageCube(
    Bitmap src,
    int depth,
    float angle,
    Color backColor,
    Bitmap topImage,
    Bitmap sideImage,
    bool stretchTopImage,
    bool stretchSideImage)
{
    if (src == null)
        throw new ArgumentNullException(nameof(src));

    float rad = angle * (float)Math.PI / 180;
    float radCos = (float)Math.Cos(rad);
    float radSin = (float)Math.Sin(rad);
    float leftHeight = src.Height;
    float topWidth = src.Width;
    float totalWidth = depth * radCos + topWidth * radCos;
    float totalHeight = leftHeight + topWidth * radSin + depth * radSin;
    Bitmap res = new Bitmap(
        (int)totalWidth, 
        (int)totalHeight, 
        PixelFormat.Format32bppArgb);
    PointF leftTop = new PointF(
        depth * radCos,
        (depth + topWidth) * radSin);
    PointF leftBottom = new PointF(
        depth * radCos,
        leftHeight + ((depth + topWidth) * radSin));
    PointF rightTop = new PointF(
        (topWidth + depth) * radCos,
        depth * radSin);
    PointF topUpperRight = new PointF(
        rightTop.X - (depth * radCos),
        rightTop.Y - (depth * radSin));
    PointF topUpperLeft = new PointF(
        leftTop.X - (depth * radCos),
        leftTop.Y - (depth * radSin));
    PointF sideLowerLeft = new PointF(
        leftBottom.X - (depth * radCos),
        leftBottom.Y - (depth * radSin));
    PointF sideUpperLeft = new PointF(
        leftTop.X - (depth * radCos),
        leftTop.Y - (depth * radSin));

    using (Graphics g = Graphics.FromImage(res))
    {
        g.Clear(backColor);
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.DrawImage(src, new[] { leftTop, rightTop, leftBottom });

        if (topImage != null)
        {
            var srcRec = stretchTopImage
                ? new RectangleF(
                    PointF.Empty,
                    new SizeF(topImage.Width, topImage.Height))
                : new RectangleF(
                    PointF.Empty,
                    new SizeF(
                        Math.Min(topImage.Width, topWidth),
                        Math.Min(topImage.Height, depth)));
            g.DrawImage(topImage, new[]
            {
                topUpperLeft, topUpperRight, leftTop
            }, srcRec, GraphicsUnit.Pixel);
        }
        else
        {
            // for example...
            using (var br = new LinearGradientBrush(
                rightTop, topUpperLeft,
                Color.FromArgb(135, 40, 5),
                Color.FromArgb(255, 190, 10)))
                g.FillPolygon(br, new[]
                {
                    topUpperLeft,
                    topUpperRight,
                    rightTop,
                    leftTop
                });
        }
        if (sideImage != null)
        {
            var srcRec = stretchSideImage
                ? new RectangleF(
                    PointF.Empty,
                    new SizeF(sideImage.Width, sideImage.Height))
                : new RectangleF(
                    PointF.Empty,
                    new SizeF(
                        Math.Min(sideImage.Width, depth),
                        Math.Min(sideImage.Height, leftHeight)));
            g.DrawImage(sideImage, new[]
            {
                sideUpperLeft, leftTop, sideLowerLeft
            }, srcRec, GraphicsUnit.Pixel);
        }
        else
        {
            using (var br = new LinearGradientBrush(
                sideUpperLeft, leftBottom,
                Color.FromArgb(255, 190, 10),
                Color.FromArgb(135, 40, 5)))
                g.FillPolygon(br, new[]
                {
                    sideUpperLeft,
                    leftTop,
                    leftBottom,
                    sideLowerLeft
                });
        }
    }

    return res;
}

Cube

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the explanation and example! it seems that I'll have to stick to isometric 3d rather than attempt "true 3d" because of the limitations in the TBrush matrix transformations and the parallelogram based image skewing

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.