How to draw the bottom half of a 3D tube from C #

I am creating a storage (Figure A) using DrawStorage (). To fill the store, I use FillWater (). I wanted to fill the storage with water according to a 0-100% level (empty fill) like image C, but the current output is generated from FillWater () like image B. How to fill the storage to look like image C? The difficulty is how to fill the storage to make it look 3D (Figure C)

Sorry if my english is not very good. I hope you can help you who is an expert, thanks.

output

    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);
        Graphics g = pe.Graphics;
        g.SmoothingMode = SmoothingMode.AntiAlias;

        this.DrawBar(g, this.ForeColor);
    }


    private void DrawBar(Graphics g, Color foreColor)
    {
        bool outLine = this._outLineColor != Color.Transparent;
        Rectangle bound = this.ClientRectangle;
        bound.Inflate(-20, -20);

        DrawStorage(g, bound, new Size(4, 10), Color.FromArgb(this.Alpha, foreColor), this.OutLineColor, outLine);

        if (this.Value > this.Minimum && this.Value <= this.Maximum)
        {
            float barValue = bound.Height * ((this.Value - this.Minimum) / (this.Maximum - this.Minimum));
            RectangleF valueBound = RectangleF.FromLTRB(bound.Left, bound.Bottom - barValue, bound.Right, bound.Bottom);

            FillWater(g, valueBound, new Size(4, 10), Color.FromArgb(this.Alpha, this.BarColor), this.OutLineColor, outLine);

            if (this._showValue && valueBound.Height > 20)
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                StringFormat format = new StringFormat();
                format.Alignment = StringAlignment.Center;
                format.LineAlignment = StringAlignment.Center;
                g.DrawString(this._value.ToString("F2"), this.Font, Brushes.Black, valueBound, format);
                format.Dispose();
            }
        } 
    }


    public static void DrawStorage(Graphics g, RectangleF front, SizeF depth, Color fillColor, Color borderColor, bool outLine)
    {
        if (front.Width <= 0 || front.Height <= 0)
            return;

        // Make Back Side Area
        RectangleF aback = front;

        // Make Depth
        aback.X += depth.Width;
        aback.Y -= depth.Height;

        // Create Top and Bottom Plane. 
        RectangleF leftPlane;
        RectangleF rightPlane;

        // Create Graphics Object
        GraphicsPath gp = new GraphicsPath();

        rightPlane = new RectangleF(front.Width, front.Y, front.X, front.Height);
        leftPlane = new RectangleF(front.X, front.Y, front.X, front.Height);

        // Brush
        SolidBrush brush = new SolidBrush(fillColor);

        // Border Pen
        Pen borderPen = new Pen(borderColor);

        /***************
         *   LEFT    *
         * ************/
        // Make GP On Bottom
        gp.AddEllipse(leftPlane);

        // Get Bottom color
        brush.Color = GetSideColor(fillColor, WallSide.Left);

        // Fill Bottom Plane
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, false);

        // Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        gp.Reset();
        gp.AddArc(rightPlane, 270, 180);
        gp.AddArc(leftPlane, 90, -180);
        gp.CloseFigure();

        /***************
         *     Body    *
         * ************/
        // Color For Body is real Fill Color.
        brush.Color = fillColor;

        // Fill Body
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, true);

        // Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        /***************
         *     RIGHT     *
         * ************/
        gp.Reset();
        gp.AddEllipse(rightPlane);

        // Get Bottom color
        brush.Color = GetSideColor(fillColor, WallSide.Back);

        // Fill Top Plane
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, true);

        //Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        // Dispose
        gp.Dispose();
        brush.Dispose();
        borderPen.Dispose();
    }


    public static void FillWater(Graphics g, RectangleF front, SizeF depth, Color fillColor, Color borderColor, bool outLine)
    {
        if (front.Width <= 0 || front.Height <= 0)
            return;

        // Make Back Side Area
        RectangleF aback = front;

        // Make Depth
        aback.X += depth.Width;
        aback.Y -= depth.Height;

        // Create Top and Bottom Plane. 
        RectangleF leftPlane;
        RectangleF rightPlane;

        // Create Graphics Object
        GraphicsPath gp = new GraphicsPath();

        rightPlane = new RectangleF(front.Width, front.Y, front.X, front.Height);
        leftPlane = new RectangleF(front.X, front.Y, front.X, front.Height);

        // Brush
        SolidBrush brush = new SolidBrush(fillColor);

        // Border Pen
        Pen borderPen = new Pen(borderColor);

        /***************
         *   LEFT    *
         * ************/
        // Make GP On Bottom
        gp.AddEllipse(leftPlane);

        // Get Bottom color
        brush.Color = GetSideColor(fillColor, WallSide.Left);

        // Fill Bottom Plane
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, false);

        // Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        gp.Reset();
        gp.AddArc(rightPlane, 270, 180);
        gp.AddArc(leftPlane, 90, -180);
        gp.CloseFigure();

        /***************
         *     Body    *
         * ************/
        // Color For Body is real Fill Color.
        brush.Color = fillColor;

        // Fill Body
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, true);

        // Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        /***************
         *     RIGHT     *
         * ************/
        gp.Reset();
        gp.AddEllipse(rightPlane);

        // Get Bottom color
        brush.Color = GetSideColor(fillColor, WallSide.Back);

        // Fill Top Plane
        g.FillPath(brush, gp);

        // Shadow of the Body
        FillCylinderShadow(g, front, gp, true);

        //Check Draw Border
        if (outLine)
            g.DrawPath(borderPen, gp);

        // Dispose
        gp.Dispose();
        brush.Dispose();
        borderPen.Dispose();
    }

      

+3


source to share


2 answers


I have had limited success with creating a 3D effect using cut and crop order. The code below usually works, but has some problems when the padding is around 0% or 100% ... I think this can be fixed.

3D cylinder



/// <summary>
/// Calculate X coordinate on an ellipse
/// </summary>
/// <param name="width">Ellipse width</param>
/// <param name="height">Ellipse height</param>
/// <param name="y">Y ranging from 0 to height</param>
/// <returns>X relative to the center of the ellipse</returns>
/// 
static float EllipseCalculateX( float width, float height, float y )
{
    if ( y < 0 || y > height )
    {
        return 0;
    }

    y = y - ( height / 2f );
    var a = width / 2f;
    var b = height / 2f;

    var x = ( a * Math.Sqrt( ( b * b ) - ( y * y ) ) ) / b;

    return (float)x;
}

protected override void OnPaint( PaintEventArgs e )
{
    var g = e.Graphics;

    var percent_full = PercentFull;


    // length, width, and depth of the storage in pixels
    //
    var storage_length = 140f;
    var storage_height = 80f;            
    var storage_depth  = 15f;


    var start = new PointF( 80, 50 );

    var cylinder = new RectangleF( start.X, start.Y, storage_length, storage_height );
    var left_cap = new RectangleF( cylinder.Left - ( storage_depth / 2f ), cylinder.Top, storage_depth, storage_height );
    var right_cap = new RectangleF( cylinder.Right - ( storage_depth / 2f ), cylinder.Top, storage_depth, storage_height );


    // relative x,y of the fill level on the "near" (front) side of the storage
    //
    var fill_near_y = storage_height * ( percent_full / 100f );
    var fill_near_x = EllipseCalculateX( storage_depth, storage_height, fill_near_y );

    // relative x,y of the fill level on the "far" (back) side of the storage
    // y is offset slightly for the 3D effect
    //
    var fill_far_y = storage_height * ( percent_full / 100f ) + ( storage_depth / 2f );
    var fill_far_x = EllipseCalculateX( storage_depth, storage_height, fill_far_y );


    // absolute x,y of the fill level on the left side (near and far)
    //
    var fill_left_far = new PointF( cylinder.Left - fill_far_x, cylinder.Bottom - fill_far_y );
    var fill_left_near = new PointF( cylinder.Left + fill_near_x, cylinder.Bottom - fill_near_y );

    // absolute x,y of the fill level on the right side (near and far)
    //
    var fill_right_far = new PointF( cylinder.Right - fill_far_x, cylinder.Bottom - fill_far_y );
    var fill_right_near = new PointF( cylinder.Right + fill_near_x, cylinder.Bottom - fill_near_y );

    // calculate the slope between the near and far levels
    //
    var slope = ( fill_left_far.Y - fill_left_near.Y ) / ( fill_left_far.X - fill_left_near.X + 0.001f );

    // build a clip path to be used in filling the left cap; its top is angled to match the 3D effect
    // the first two points in the path have to be extended outside of the cap ellipse, or the fill will look wrong above 50% fill
    //
    var left_clip = new GraphicsPath();
    left_clip.AddPolygon( new PointF[] {
        new PointF( left_cap.Left, fill_left_far.Y - ( slope * ( fill_left_far.X - left_cap.Left ) ) ), 
        new PointF( left_cap.Right, fill_left_near.Y + ( slope * ( left_cap.Right - fill_left_near.X ) ) ), 
        new PointF( left_cap.Right, left_cap.Bottom ), 
        new PointF( left_cap.Left, left_cap.Bottom ),
    } );

    // same for right cap
    // 
    var right_clip = new GraphicsPath();
    right_clip.AddPolygon( new PointF[] {
        new PointF( right_cap.Left, fill_right_far.Y - ( slope * ( fill_right_far.X - right_cap.Left ) ) ), 
        new PointF( right_cap.Right, fill_right_near.Y + ( slope * ( right_cap.Right - fill_right_near.X ) )),
        new PointF( right_cap.Right, left_cap.Bottom ), 
        new PointF( right_cap.Left, right_cap.Bottom ),
    } );

    var outline = new Pen( Color.Black, 2f ) { LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel };

    // outline the top and bottom of the storage
    //
    g.DrawLine( outline, cylinder.Left, cylinder.Top, cylinder.Right, cylinder.Top );
    g.DrawLine( outline, cylinder.Left, cylinder.Bottom, cylinder.Right, cylinder.Bottom );


    // outline the right cap
    //
    g.DrawEllipse( outline, right_cap );


    // outline and fill the right side
    //
    g.SetClip( right_clip );
    g.DrawEllipse( outline, right_cap );
    g.FillEllipse( Brushes.Orange, right_cap );
    g.ResetClip();


    // fill in the center area
    //
    g.SetClip( RectangleF.FromLTRB( cylinder.Left, fill_left_near.Y, cylinder.Right, cylinder.Bottom ) );
    g.FillRectangle( Brushes.Orange, cylinder );
    g.ResetClip();


    // fill left side
    //
    g.SetClip( left_clip );
    g.FillEllipse( Brushes.Yellow, left_cap );
    g.ResetClip();


    // outline and fill the surface
    //
    var surface = new[] { fill_left_near, fill_left_far, fill_right_far, fill_right_near, fill_left_near };
    g.DrawPolygon( outline, surface );
    g.FillPolygon( Brushes.Yellow, surface );


    // outline the left cap
    //
    g.DrawEllipse( outline, left_cap );
}

      

+1


source


Well, actually, your 3D drawing is actually a collage of 2D shapes, so you can "see" inside both ends of the pipe, for example. You probably got to your mind. If you draw an x% chord at both ends of the pipe (align the right end, it should have the same angle as the left) Then attach them so that you have a plane representing the surface of the water, which should give you enough points to sketch the filled part of the pipe, and a few Fill commands after you are filled should do the job.



0


source







All Articles