Name: Password: Sign in
Android Canvas 的力学仿真(实验) 中的文章

本文在 署名-非商业性使用-相同方式共享 3.0 版权协议下发布, 转载请注明出自 kyleslight.net

三点弯曲梁主应力迹线的绘制

上一篇我们已经利用材料力学在理论上求得了主应力在整个梁上的分布,结合第一篇 Android Canvas 绘图准备,现在我们可以尝试完成第一个模型的主应力迹线的绘制。

1 架构

我们的最终目标当然不止是三点弯曲梁这样的结构,我们需要抽象出一个更为通用的结构类( 例如 Structure ),它可能需要包含:

  • 所有的外部关键点(用来提供边界)
  • 所有结构中的点(用来表征应力场在各个位置点的信息)
  • 计算应力
  • 坐标转换
  • 绘图

Structure 以及由它继承而来的结构类(例如 TPBStructure,即三点弯曲梁 )该如何分配上述数据与方法并没有严格的定义;考虑实际的需求,对于一些特殊的要求(例如计算应力)放在具体类中实现比较好( 想实现通用的应力求解方法需要计算力学,那就是另外一个故事了 )

所以总体上来看的原则是,涉及产生点数据,施加力以及求解应力的方法时我们把它放在具体的类中,它应该不知道如何进行坐标转换也不知道如何绘图,只把自己的事情做好,其他的任务交给 Structure 就好:)

至于 ActivityView 以及 Point 这些之前介绍过了,在这一篇会稍许有些不同,不过大体上是类似的。

2 Activity 与 View

开始我们会来到 MainActivity,它主要的作用是分流,将我们引导到不同结构的 Activity

public class MainActivity extends ActionBarActivity {

    Button TPBBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        TPBBtn = (Button) findViewById(R.id.TPBBtn);
        TPBBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent("android.intent.action.TPB"));
            }
        });
    }
}

activity_main 没什么好说的,长下面这个样子:

点击后我们来到 TPBActivity,也就是我们绘制的 Activity

public class TPBActivity extends Activity {
    StructureView structureView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
        structureView = new StructureView(this);
        setContentView(structureView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        structureView.pause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        structureView.resume();
    }
}

我们自定义了一个 StructureView,通过它我们拿到了 Canvas,实例化 TPBStructure 对象( 放在 onSizeChanged 这个钩子里面是因为构造函数内部拿不到 Canvas 正确的大小),以及发起重绘( 利用这个特性我们可以隔一段时间改变外部作用力的属性从而给迹线的绘制增加动态 ):

public class StructureView extends SurfaceView implements Runnable {
    Thread t;
    SurfaceHolder holder;
    boolean canDraw = false;
    TPBStructure tpbStructure;

    //test
    float left = -0.3f;
    float right = 0.3f;
    float step = 0.01f;
    float forceX = 0f;
    //test

    public StructureView(Context context) {
        super(context);
        holder = getHolder();
    }

    @Override
    public void run() {

        while (canDraw) {
            if(!holder.getSurface().isValid()) continue;
            Canvas canvas = holder.lockCanvas();
            canvas.drawColor(Color.BLACK);
            drawContent(canvas);
            holder.unlockCanvasAndPost(canvas);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // do initialization here
        tpbStructure = new TPBStructure(5f, 1e6f, w, h);
    }

    public void pause() {
        canDraw = false;
        while (true) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            break;
        }
        t = null;
    }

    public void resume() {
        canDraw = true;
        t = new Thread(this);
        t.start();
    }

    public void drawContent(Canvas canvas) {
        // update here

        try {
            t.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testForcePostion();
        tpbStructure.drawForceTOP(canvas);
        tpbStructure.drawOutside(canvas);
        tpbStructure.drawInside(canvas);
        tpbStructure.drawContour(canvas);
    }

    public void testForcePostion() {
        if (forceX + step > right) step = -step;
        if (forceX + step < left) step = -step;
        forceX += step;
        tpbStructure.applyForce(forceX);
    }
}

3 Structure

然后我们来到了 Structure,它包含了所有关于具体绘制的方法:

  • drawOutside:绘制轮廓,它拿到了模型外部点后,根据坐标依次连点画线
  • drawInside:绘制内部点,和外部点类似,它同样是拿到了内部点的坐标,然后在每个地方以一个较小的半径画圆(实际应该是画点)
  • drawForceTOP:由于我们的力随时会变化,所以它会动态获取力的位置,然后画一个直线与倒三角形( 模拟 Arrow )
  • drawContour:绘制等值线图;和等高线地形图类似,每一个离散的点都包含有主应力值,因而我们可以首先将主应力正则化,(根据主应力变化区间以及比例)将每一个主应力 map 到 0 到 1 间的一个值,根据这个值我们可以人为规定一个区间界限( 例如将总应力划分为 10 段 ),这样任何一个正则应力值就会落在一个区间中,被绘制以相同的颜色(根据数值与 HSV 颜色的转换);从宏观上看,区间的分界表征了主应力分界的地方,换句话说它们是主应力迹线

很明显,对于结构内部的点是如何产生的,它们的应力大小是多少,Structure 一点也不关心,他只知道与画图有关的一切。

顺带一提,有几个很自然但是容易被人们忽略的事实:

  • 我们的笛卡尔坐标与自然书写的坐标在 y 轴上方向并不相同,Cnavas 使用的是自然书写坐标
  • 绘图的坐标原点也应该在视图的几何中心,这样比较符合多数人的直觉与审美
  • 具体结构并不需要知道坐标转换的存在,在它的世界中坐标被取为便于受力分析的方式

基于以上三点我们还需要一个 standardPoint 方法用于将一个普通的点映射到真实应该被渲染的地方。

将以上结合就变成了这样子:

public class Structure {
    // Main info

    // Force
    float forceX, forceValue;

    // Critical geometry value
    final float RADIUS = 5f;
    final float CONTOURRADIUS = 3f;
    final int CONTOURNUM = 20;
    final float CONTOURNEARRANGE = 0.12f;
    final float SCALEX = 1600f;
    final float SCALEY = 2000f;
    final float ARROWHEIGHT = 0.02f;
    final float ARROWHALFWIDTH = 0.01f;

    // Points in structure
    ArrayList<Point> pointsInside = new ArrayList<>();

    // Points in surface
    ArrayList<Point> pointsOutside = new ArrayList<>();

    float canvasWidth;
    float canvasHeight;

    float MaxStress = -1e15f;
    float MinStress = 1e15f;

    public Structure(float F_value, float F_X, float width, float height) {
        forceX = 0;
        canvasWidth = width;
        canvasHeight = height;
        generatePointsOutside();
        generatePointsInside();
    }

    protected void generatePointsOutside() {}
    protected void generatePointsInside() {}

    public void drawOutside(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(3);
        for (int i = 0; i < pointsOutside.size() - 1; i++) {
            Point tempPoint = standardPoint(pointsOutside.get(i));
            Point nextPoint = standardPoint(pointsOutside.get(i + 1));
            canvas.drawLine(tempPoint.get_x(), tempPoint.get_y(), nextPoint.get_x(), nextPoint.get_y(), paint);
        }
        Point lastPoint = standardPoint(pointsOutside.get(pointsOutside.size() - 1));
        Point firstPoint = standardPoint(pointsOutside.get(0));
        canvas.drawLine(lastPoint.get_x(), lastPoint.get_y(),
                firstPoint.get_x(), firstPoint.get_y(), paint);
    }

    public void drawInside(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        for (int i = 0; i < pointsInside.size(); i++) {
            Point tempPoint = standardPoint(pointsInside.get(i));
            canvas.drawCircle(tempPoint.get_x(), tempPoint.get_y(), RADIUS, paint);
        }
    }

    protected void applyForce(float f_x) {}
    protected float getTOP() {return 0f;}
    protected float getFORCESTART() {return 0f;}

    protected void drawForceTOP(Canvas canvas) {
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(10);

        Point start = standardPoint(new Point(forceX, getFORCESTART()));
        Point end = standardPoint(new Point(forceX, getTOP()));
        canvas.drawLine(start.get_x(), start.get_y(), end.get_x(), end.get_y(), paint);

        Point point_1 = standardPoint(new Point(forceX, getTOP()));
        Point point_2 = standardPoint(new Point(forceX - ARROWHALFWIDTH, getTOP() - ARROWHEIGHT));
        Point point_3 = standardPoint(new Point(forceX + ARROWHALFWIDTH, getTOP() - ARROWHEIGHT));

        // draw arrow
        Path path = new Path();
        path.setFillType(Path.FillType.EVEN_ODD);
        path.moveTo(point_1.get_x(), point_1.get_y());
        path.lineTo(point_1.get_x(), point_1.get_y());
        path.lineTo(point_2.get_x(), point_2.get_y());
        path.lineTo(point_3.get_x(), point_3.get_y());
        path.close();

        canvas.drawPath(path, paint);
    }

    public void drawContour(Canvas canvas) {
        float stressRange = MaxStress - MinStress;
        float contourLineRange = stressRange / CONTOURNUM;
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);

        for (int i = 0; i < pointsInside.size(); i++) {
            Point tempPoint = standardPoint(pointsInside.get(i));
            float tempStressValue = pointsInside.get(i).get_principalStressValue() - MinStress;
            int nearContourLine = (int)(tempStressValue / contourLineRange);
            float HSV_H = (float)360 * (CONTOURNUM - nearContourLine) / CONTOURNUM;
            paint.setColor(Color.HSVToColor(new float[]{ HSV_H, 1f, 1f } ));
//            if (Math.abs(tempStressValue - nearContourLine * contourLineRange) / contourLineRange < CONTOURNEARRANGE) {
                canvas.drawCircle(tempPoint.get_x(), tempPoint.get_y(), CONTOURRADIUS, paint);
//            }
        }
    }

    protected Point standardPoint(Point oldP) {
        float x = canvasWidth / 2 + oldP.get_x() * SCALEX;
        float y = canvasHeight / 2 + oldP.get_y() * SCALEY;
        return new Point(x, y);
    }
}

4 TPBStructure

TPBStructure 实现实际结构内部信息的封装,为上层(绘图)提供良好的数据抽象。它主要由这样几个部分构成:

  • generatePointsOutside 用于根据所提供边界产生轮廓点的坐标
  • generatePointsInside 用于根据轮廓产生内部点对象
  • applyForce 是计算主应力的地方,传入外部荷载,它会遍历所有内部点,调用 getPrincipalStress 赋予当前点的应力值
  • getPrincipalStress 会调用 getNormalStressgetShearStress 得到该点的正应力与切应力的值,然后根据 λ1=12σ+σ24+τ2 计算主拉应力;它同时会保留最大应力与最小应力的值用于给绘制中等值线的正则化提供帮助
  • getNormalStressgetShearStress 分别根据 σ=MzyIzτ=SbIzFs 计算当前点正应力与切应力的值 (具体计算方还需要用到 MzFs 的分布)

具体实现如下:

public class TPBStructure  extends Structure{
    // Material parameters
    final float TPBLength = 0.6f;
    float I_x = 0; // I_x = 1/12 * b * h^3
    float M_max = 0;
    final float b = 0.05f;
    final float h = 0.1f;

    // Critical geometry value
    final float LEFT = -TPBLength / 2f;
    final float RIGHT = TPBLength / 2f;
    final float TOP = -h / 2f;
    final float BOTTOM = h / 2f;

    final float TOPLAYER = 0.04f;
    final float FORCESTART = TOP - TOPLAYER;

    final int division_x = 250;
    final int division_y = 200;

    float F_s_left;
    float F_s_right;

    // Points define the surface

    public TPBStructure(float F_value, float F_X, float width, float height) {
        super(F_value, F_X, width, height);
        forceX = F_X;
        forceValue = F_value;
    }

    @Override
    protected void generatePointsOutside() {
        float[] pointOutsideData = {
                LEFT, TOP,
                LEFT, BOTTOM,
                RIGHT ,BOTTOM,
                RIGHT, TOP
        };
        for (int i = 0; i < pointOutsideData.length; i += 2) {
            pointsOutside.add(new Point(pointOutsideData[i], pointOutsideData[i + 1]));
        }
    }

    @Override
    protected void generatePointsInside() {
        float gridX = TPBLength / division_x;
        float gridY = h / division_y;
        for (int i = 0; i < division_x; i++) {
            for (int j = 0; j < division_y; j++) {
                pointsInside.add(new Point(gridX * i - TPBLength / 2, gridY * j - h / 2));
            }
        }
    }

    @Override
    protected void applyForce(float f_x) {
        forceX = f_x;
        forceValue = 10000;
        M_max = -(TPBLength / 2f + forceX) * (TPBLength / 2f - forceX) * forceValue / TPBLength;
        I_x = 1f / 12f * b * h * h * h;
        F_s_left = forceValue * (TPBLength / 2f - forceX) / TPBLength;
        F_s_right = forceValue * (TPBLength / 2f + forceX) / TPBLength;
        for (int i = 0; i < pointsInside.size(); i++) {
            float tempStressValue = getPrincipalStress(pointsInside.get(i));
            MaxStress = tempStressValue > MaxStress ? tempStressValue : MaxStress;
            MinStress = tempStressValue < MinStress ? tempStressValue : MinStress;
            pointsInside.get(i).set_principalStressValue(tempStressValue);
        }
    }

    // Get principal stress value on each point
    protected float getPrincipalStress(Point p) {
        float normalStress = getNormalStress(p);
        float shearStress = getShearStress(p);

        float principalStress = normalStress / 2f + (float)Math.sqrt(normalStress * normalStress / 4f + shearStress * shearStress);

        return principalStress;
    }

    // Get Normal Stress
    protected float getNormalStress(Point p) {
        float tempX = p.get_x();
        float ratio = tempX < forceX ? (TPBLength / 2 + tempX) / (TPBLength / 2 + forceX) : (TPBLength / 2 - tempX) / (TPBLength / 2 - forceX);
        float M = M_max * ratio;
        float normalStressValue = M * p.get_y() / I_x;
        return normalStressValue;
    }

    // Get Shear Stress
    protected float getShearStress(Point p) {
        float shearStressValue;
        float y = p.get_y();
        float x = p.get_x();
        float F_s = x < forceX ? F_s_left : F_s_right;
        float staticMoment = b/2f * (h*h /4f - y*y); // S_z = b/2 * (h^2 /4 - y^2)
        shearStressValue = F_s * staticMoment / (b * I_x);
        return shearStressValue;
    }

    @Override
    protected float getFORCESTART() {
        return FORCESTART;
    }

    @Override
    public float getTOP() {
        return TOP;
    }
}

5 结果

是的,这东西可以跑,而且看起来效果不错:)

Copyright (c) 2014-2016 Kyles Light.
Powered by Tornado.
鄂 ICP 备 15003296 号