Как нарисовать нижнюю половину 3D трубы с помощью C # - PullRequest
3 голосов
/ 30 марта 2012

Я создаю хранилище (Рисунок A) с DrawStorage ().Для заполнения хранилища я использую FillWater ().Я хотел заполнить хранилище водой в соответствии с уровнем 0-100% (пустой-заполненный), таким как рисунок C, но текущий вывод генерируется из FillWater (), как рисунок B. Как заполнить хранилище, чтобы оно выглядело как рисунокС?Сложность заключается в том, как заполнить хранилище так, чтобы оно выглядело как 3D (Рисунок C)

Извините, если мой английский не очень хорош.Я надеюсь, что помощь тех, кто является экспертами, спасибо.

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();
    }

Ответы [ 2 ]

1 голос
/ 31 марта 2012

У меня был некоторый ограниченный успех в создании 3D-эффекта с использованием порядка прорисовки и отсечения. Код ниже, как правило, работает, но имеет некоторые проблемы, когда заполнение составляет около 0% или 100% ... Я думаю, что это можно исправить.

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 );
}
0 голосов
/ 30 марта 2012

Ну, на самом деле ваш 3D-рисунок на самом деле представляет собой коллаж 2-мерных фигур, поэтому вы можете, например, «видеть» внутри обоих концов трубы. Вы, вероятно, можете попасть туда таким образом, ум. Если вы рисуете аккорд в x% на обоих концах трубы (переберите правый конец, он должен иметь тот же угол, что и левый) Затем соедините их так, чтобы у вас была плоскость, представляющая поверхность воды, которая должна дать вам достаточно очков, чтобы очертить залитую часть трубы, и пара команд Fill, как только у вас будет закрашенный контур, должна выполнить эту работу.

...