如果把π进行可视化,会碰撞出什么效果呢!

780 阅读2分钟

灵感来源于B站的一段视频π就是艺术,将3.1415926等数字按照以上一个点为中心进行的角度绘制,每一个数字计算出的点的位置加上上一个点位的坐标计算出最终绘制位置

准备工作

视频上应该是用视频软件制作的,作为一个前端,可以使用前端的技术实现canvas,js等,这里我用THREE.JS进行绘制,先创建一个场景

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      body {
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <script src="../../three.js-master\build\three.js"></script>
    <script src="../../three.js-master/examples/js/controls/OrbitControls.js"></script>
    <script src="../../three.js-master\examples\js\loaders\OBJLoader.js"></script>
    <script src="../../three.js-master\examples\js\libs\stats.min.js"></script>
    <script>
      var scene, camera, renderer, container;
      var light, controls, group, stats;
      init();
      animate();
      function init() {
        // 创建容器
        container = document.createElement("div");
        document.body.appendChild(container);
        // 创建渲染函数
        renderer = new THREE.WebGLRenderer({
          antialias: true, //抗锯齿
        });
        renderer.setPixelRatio(window.devicePixelRatio); //设置渲染的比例
        renderer.setSize(window.innerWidth, window.innerHeight); //设置渲染的尺寸
        container.appendChild(renderer.domElement);

        // 创建fps监控器
        stats = new Stats();
        container.appendChild(stats.dom);
        // 创建相机
        camera = new THREE.PerspectiveCamera(
          45,
          window.innerWidth / window.innerHeight,
          1,
          10000
        );
        camera.position.set(0, 30, 0); //设置相机位置

        // 设置鼠标操作(控制器)
        controls = new THREE.OrbitControls(
          camera,
          document.querySelector("canvas")
        );
        controls.update(); //更新控制器

        // 设置场景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xa0a0a0);

        // 设置光照
        light = new THREE.HemisphereLight(0xffffff, 0x444444);
        light.position.set(0, 200, 0);
        scene.add(light);
        // 创建参考线
        var axisHelper = new THREE.AxisHelper(250);
        scene.add(axisHelper);
        // 根据窗口自适应改变
        window.addEventListener("resize", onWindowResize, false);

        var geometry = new THREE.BoxBufferGeometry(1, 1, 1);
        var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
        var cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
      }
      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      }

      function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }
    </script>
  </body>
</html>

π里面的数字只有0-9,所以先将一个圆按照是等分进行划分,每个角度为36°, image.png

比如第一个数字是3,那角度为36*3=108°,假设半径为10,定义x、y为108°在圆上的坐标,通过公式计算出x,y的值,在3d世界制作的,所以会有一个z的概念,为了方便观察,将y作为z轴坐标,y轴设置为0,所以输出为一个new THREE.Vector3()的三维向量

function drawLine(n, v3) {
    v3 = v3 || new THREE.Vector3()
    var R = 10; //圆弧半径
    var N = 36; //分段数量
    var deg = (Number(n) || 10) * N; // 角度
    var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
    var angle = ((0.2 * Math.PI) / N) * deg;
    var x = R * Math.sin(angle);
    var y = -R * Math.cos(angle);
    const vector3 = new THREE.Vector3(x, 0, y);
    let new_V = vector3.add(v3);
    // console.log(new_V)
    return new_V;
}

第一个参数为π的某一个值,v3是上一个点的坐标,默认坐标为0,

// 输入
drawLine(3)
// 输出
Vector3 {x: 9.510565162951536, y: 0, z: 3.0901699437494736}

第一个点确定了 第二个点也计算出来,两点确定一条线段,接下来绘制这条线段,使用threejs的new THREE.Line

function createLine() {
    var material = new THREE.LineBasicMaterial({
      color: 0xffffff,
      vertexColors: true,
    });

    var line;

    line = new THREE.Line(geometry1, material);

    scene.add(line);
}

现在线上没有任何点位信息和颜色信息,需要将之前计算的点位更新到这条线里

function comLine(i, s) {
    if (i > 0) {
      lastV3 = v3.clone();
    }
    if (s) {
      if (i > pi.length - 2) {
        clearInterval(s);
      }
    }
    v3 = drawLine(pi[i], lastV3);

    vertices.push(...v3.toArray());
    geometry1.setAttribute(
      "position",
      new THREE.Float32BufferAttribute(vertices, 3)
    );
    geometry1.setAttribute(
      "color",
      new THREE.Float32BufferAttribute(
        transColor(
          vertices.length / 3,
          new THREE.Color(0xebeae6),
          new THREE.Color(0xe66c7b)
        ),
        3
      )
    );
  }

i参数是循环到第几位数,s是setInterval标识

第一个数字为3 绘制结果:

image.png

那条彩色的线,就是从起点0的位置到半径为10角度为108位置的点, 那么第二个点将作为下一个数字1的起点,再进行绘制

image.png

可以先看一下动态效果图的效果

11111.gif

我最开始准备了小数点后40w位,gif图录制不下,可以到网站上访问,速度是10毫秒绘制一次

这张图可能比较大

11111.gif

体验地址

π的40w位