浅尝flutter中的3D效果

作者: YuTao_ | 来源:发表于2018-04-04 17:41 被阅读261次

    本篇教程展示在flutter中导入obj模型。

    先看一下效果图

    flutter3D效果.png
    flutter3D效果2.png

    废话不多说,直接上代码。
    在新建一个Object_3D.dart文件,代码如下:

    library flutter_3d_obj;
    
    import 'dart:io';
    import 'dart:math' as Math;
    import 'dart:ui';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart' show rootBundle;
    import 'package:flutter/widgets.dart';
    import 'package:meta/meta.dart';
    import 'package:vector_math/vector_math.dart' show Vector3;
    import 'package:vector_math/vector_math.dart' as V;
    
    class Object_3D extends StatefulWidget {
      Object_3D(
          {@required this.size,
            @required this.path,
            @required this.asset,
            this.angleX,
            this.angleY,
            this.angleZ,
            this.zoom = 100.0}) {
        if (angleX != null || angleY != null || angleZ != null) {
          useInternal = false;
        }
      }
    
      Size size;
      bool asset;
      String path;
      double zoom;
      double angleX;
      double angleY;
      double angleZ;
      bool useInternal = true;
    
      @override
      _Object_3DState createState() => new _Object_3DState(path, useInternal, asset);
    }
    
    class _Object_3DState extends State<Object_3D> {
      _Object_3DState(this.path, this.useInternal, bool asset) {
        if(asset){
          rootBundle.loadString(this.path).then((String value) {
            setState(() {
              object = value;
            });
          });
        }else{
          File file = new File(this.path);
          file.readAsString().then((String value) {
            setState(() {
              object = value;
            });
          });
        }
      }
    
      bool useInternal;
      String path;
    
      double angleX = 15.0;
      double angleY = 45.0;
      double angleZ = 0.0;
    
      double _previousX = 0.0;
      double _previousY = 0.0;
    
      double zoom;
      String object = "V 1 1 1 1";
    
      File file;
    
      void _updateCube(DragUpdateDetails data) {
        if (angleY > 360.0) {
          angleY = angleY - 360.0;
        }
        if (_previousY > data.globalPosition.dx) {
          setState(() {
            angleY = angleY - 1;
          });
        }
        if (_previousY < data.globalPosition.dx) {
          setState(() {
            angleY = angleY + 1;
          });
        }
        _previousY = data.globalPosition.dx;
    
        if (angleX > 360.0) {
          angleX = angleX - 360.0;
        }
        if (_previousX > data.globalPosition.dy) {
          setState(() {
            angleX = angleX - 1;
          });
        }
        if (_previousX < data.globalPosition.dy) {
          setState(() {
            angleX = angleX + 1;
          });
        }
        _previousX = data.globalPosition.dy;
      }
    
      void _updateY(DragUpdateDetails data) {
        _updateCube(data);
      }
    
      void _updateX(DragUpdateDetails data) {
        _updateCube(data);
      }
    
      @override
      Widget build(BuildContext context) {
        return new GestureDetector(
          child: new CustomPaint(
            painter: new _ObjectPainter(
                widget.size,
                object,
                useInternal ? angleX : widget.angleX,
                useInternal ? angleY : widget.angleY,
                useInternal ? angleZ : widget.angleZ,
                widget.zoom),
            size: widget.size,
          ),
          onHorizontalDragUpdate: _updateY,
          onVerticalDragUpdate: _updateX,
        );
      }
    }
    
    class _ObjectPainter extends CustomPainter {
      double _zoomFactor = 100.0;
    
      final double _rotation = 5.0; // in degrees
      double _translation = 0.1 / 100;
      final double _scalingFactor = 10.0 / 100.0; // in percent
    
      final double ZERO = 0.0;
    
      final String object;
    
      double _viewPortX = 0.0, _viewPortY = 0.0;
    
      List<Vector3> vertices;
      List<dynamic> faces;
      V.Matrix4 T;
      Vector3 camera;
      Vector3 light;
    
      double angleX;
      double angleY;
      double angleZ;
    
      Color color;
    
      Size size;
    
      _ObjectPainter(this.size, this.object, this.angleX, this.angleY, this.angleZ,
          this._zoomFactor) {
        _translation *= _zoomFactor;
        camera = new Vector3(0.0, 0.0, 0.0);
        light = new Vector3(0.0, 0.0, 100.0);
        color = new Color.fromARGB(255, 255, 255, 255);
        _viewPortX = (size.width / 2).toDouble();
        _viewPortY = (size.height / 2).toDouble();
      }
    
      Map<String, List<List<int>>> _parseObjString(String objString) {
        List vertices = <Vector3>[];
        List faces = <List<int>>[];
        List<int> face = [];
    
        List lines = objString.split("\n");
    
        Vector3 vertex;
    
        lines.forEach((String line) {
          line = line.replaceAll(new RegExp(r"\s+$"), "");
          List<String> chars = line.split(" ");
    
          // vertex
          if (chars[0] == "v") {
            vertex = new Vector3(double.parse(chars[1]), double.parse(chars[2]),
                double.parse(chars[3]));
    
            vertices.add(vertex);
    
            // face
          } else if (chars[0] == "f") {
            for (var i = 1; i < chars.length; i++) {
              face.add(int.parse(chars[i].split("/")[0]));
            }
    
            faces.add(face);
            face = [];
          }
        });
    
        return {'vertices': vertices, 'faces': faces};
      }
    
      bool _shouldDrawFace(List face) {
        var normalVector = _normalVector3(
            vertices[face[0] - 1], vertices[face[1] - 1], vertices[face[2] - 1]);
    
        var dotProduct = normalVector.dot(camera);
        double vectorLengths = normalVector.length * camera.length;
    
        double angleBetween = dotProduct / vectorLengths;
    
        return angleBetween < 0;
      }
    
      Vector3 _normalVector3(Vector3 first, Vector3 second, Vector3 third) {
        Vector3 secondFirst = new Vector3.copy(second);
        secondFirst.sub(first);
        Vector3 secondThird = new Vector3.copy(second);
        secondThird.sub(third);
    
        return new Vector3(
            (secondFirst.y * secondThird.z) - (secondFirst.z * secondThird.y),
            (secondFirst.z * secondThird.x) - (secondFirst.x * secondThird.z),
            (secondFirst.x * secondThird.y) - (secondFirst.y * secondThird.x));
      }
    
      double _scalarMultiplication(Vector3 first, Vector3 second) {
        return (first.x * second.x) + (first.y * second.y) + (first.z * second.z);
      }
    
      Vector3 _calcDefaultVertex(Vector3 vertex) {
        T = new V.Matrix4.translationValues(_viewPortX, _viewPortY, ZERO);
        T.scale(_zoomFactor, -_zoomFactor);
    
        T.rotateX(_degreeToRadian(angleX != null ? angleX : 0.0));
        T.rotateY(_degreeToRadian(angleY != null ? angleY : 0.0));
        T.rotateZ(_degreeToRadian(angleZ != null ? angleZ : 0.0));
    
        return T.transform3(vertex);
      }
    
      double _degreeToRadian(double degree) {
        return degree * (Math.PI / 180.0);
      }
    
      List<dynamic> _drawFace(List<Vector3> verticesToDraw, List face) {
        List<dynamic> list = <dynamic>[];
        Paint paint = new Paint();
        Vector3 normalizedLight = new Vector3.copy(light).normalized();
    
        var normalVector = _normalVector3(verticesToDraw[face[0] - 1],
            verticesToDraw[face[1] - 1], verticesToDraw[face[2] - 1]);
    
        Vector3 jnv = new Vector3.copy(normalVector).normalized();
    
        double koef = _scalarMultiplication(jnv, normalizedLight);
    
        if (koef < 0.0) {
          koef = 0.0;
        }
    
        Color newColor = new Color.fromARGB(255, 0, 0, 0);
    
        Path path = new Path();
    
        newColor = newColor.withRed((color.red.toDouble() * koef).round());
        newColor = newColor.withGreen((color.green.toDouble() * koef).round());
        newColor = newColor.withBlue((color.blue.toDouble() * koef).round());
        paint.color = newColor;
        paint.style = PaintingStyle.fill;
    
        bool lastPoint = false;
        double firstVertexX, firstVertexY, secondVertexX, secondVertexY;
    
        for (int i = 0; i < face.length; i++) {
          if (i + 1 == face.length) {
            lastPoint = true;
          }
    
          if (lastPoint) {
            firstVertexX = verticesToDraw[face[i] - 1][0].toDouble();
            firstVertexY = verticesToDraw[face[i] - 1][1].toDouble();
            secondVertexX = verticesToDraw[face[0] - 1][0].toDouble();
            secondVertexY = verticesToDraw[face[0] - 1][1].toDouble();
          } else {
            firstVertexX = verticesToDraw[face[i] - 1][0].toDouble();
            firstVertexY = verticesToDraw[face[i] - 1][1].toDouble();
            secondVertexX = verticesToDraw[face[i + 1] - 1][0].toDouble();
            secondVertexY = verticesToDraw[face[i + 1] - 1][1].toDouble();
          }
    
          if (i == 0) {
            path.moveTo(firstVertexX, firstVertexY);
          }
    
          path.lineTo(secondVertexX, secondVertexY);
        }
        var z = 0.0;
        face.forEach((int x) {
          z += verticesToDraw[x - 1].z;
        });
    
        path.close();
        list.add(path);
        list.add(paint);
        return list;
      }
    
      @override
      void paint(Canvas canvas, Size size) {
    
        Map parsedFile = _parseObjString(object);
        vertices = parsedFile["vertices"];
        faces = parsedFile["faces"];
    
        List<Vector3> verticesToDraw = [];
        vertices.forEach((vertex) {
          verticesToDraw.add(new Vector3.copy(vertex));
        });
    
        for (int i = 0; i < verticesToDraw.length; i++) {
          verticesToDraw[i] = _calcDefaultVertex(verticesToDraw[i]);
        }
    
        final List avgOfZ = <Map>[];
        for (int i = 0; i < faces.length; i++) {
          List face = faces[i];
          double z = 0.0;
          face.forEach((int x) {
            z += verticesToDraw[x - 1].z;
          });
          Map data = <String, dynamic>{
            "index": i,
            "z": z,
          };
          avgOfZ.add(data);
        }
        avgOfZ.sort((Map a, Map b) => a['z'].compareTo(b['z']));
    
        for (int i = 0; i < faces.length; i++) {
          List face = faces[avgOfZ[i]["index"]];
          if (_shouldDrawFace(face) || true) {
            final List<dynamic> faceProp = _drawFace(verticesToDraw, face);
            canvas.drawPath(faceProp[0], faceProp[1]);
          }
        }
      }
    
      @override
      bool shouldRepaint(_ObjectPainter old) =>
          old.object != object ||
              old.angleX != angleX ||
              old.angleY != angleY ||
              old.angleZ != angleZ ||
              old._zoomFactor != _zoomFactor;
    }
    
    
    

    看不懂?没关系,会用就行了。

    再到main.dart引入:

    import './Object_3D.dart';
    

    mian.dart的完整代码很简单:

    import 'package:flutter/material.dart';
    import './Object_3D.dart';
    
    void main() => runApp(new MaterialApp(
       home:new MyApp())
    );
    
    class MyApp extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Flutter3D',
          home: new Scaffold(
            appBar: new AppBar(
              title: const Text("Flutter3D"),
            ),
            body:new Center(
                  child: new Object_3D(size: const Size(400.0, 400.0),path: "assets/file.obj",asset:true),//assets/file.obj为我们的本地obj文件,需要到pubspec.yaml中进行配置
                ),
          ),
        );
      }
    }
    
    
    

    assets/file.obj为我们的本地obj文件,需要到pubspec.yaml中进行配置:

    flutter:
      assets:
        - assets/file.obj
    

    不要忘了把file.obj放到新建的assets文件夹里,最后送上obj地址file.obj,祝大家玩得开心。

    相关文章

      网友评论

      • spkingr:太厉害了吧?custompaint里一堆代码原理是什么?
      • 05e032453743:obj文件是3ds文件吗
        YuTao_:@无为_ce04 3dmax导出的obj。具体我也不是很清楚,做这种项目一般都要一个建模的

      本文标题:浅尝flutter中的3D效果

      本文链接:https://www.haomeiwen.com/subject/xthlhftx.html