使用外旋轮曲线和傅里叶变换绘制/渲染3D物体[动画]

8
注意事项:在我有更多的声望点数之前他们不允许我嵌入图像(抱歉),但是所有链接都是发布在imgur上的图片!:) 谢谢
我复制了一种方法,可以使用傅里叶变换来动画化任何单一路径(1个封闭路径)。这会创建旋转圆圈的动画(epicycles),这些圆圈彼此旋转,并遵循输入的点,跟踪路径作为连续的环/函数。
我想将此系统应用于3D。我能够想到的两种方法是使用球坐标系(两个复平面)或三个Epicycles-->一个用于每个轴(x,y,z),具有它们各自的参数方程。这可能是开始的最佳方式!!
2个周期,一个用于X轴,一个用于Y轴:img 图片:一个周期-> 复数-> 用于X和Yimg 傅里叶变换背景!!!:
•欧拉公式允许我们将复平面中的每个点分解为角度(指数函数的参数)和振幅(Cn系数)
•在这种意义上,每个无限级数中的项表示为以半径cn为半径的圆上的一个点,偏移2πnt/T弧度
•下面的图像显示了如何将相位/振幅的一组复数之和可视化为在复平面中连接的一组圆。每个红线都表示序列和中的一个项:cne2πi(nT)t
•添加总和的每个分项仅对在复杂空间中连接这些红色向量进行拼接:
旋转动画圆:img 圆到动画绘制:
•如果您具有在二维(x-y)空间中的线条绘制,可以将其数学描述为参数函数。(两个单变量函数,均以辅助变量(T在本例中)为基础:
例如,下面是马的简单线条绘制,以及通过图像中的黑色像素的参数路径,然后将该路径分解为其X和Y组件: Horse Path 此时,我们需要计算这两条路径的傅里叶逼近,并使用该逼近的系数来确定所需最终可视化所需的圆的相位和振幅。

Python代码: 此示例使用的Python代码可在guithub上找到。

我已经成功地将该过程动画化为2D,但我想将其应用于3D。

以下代码表示2D动画--> 我已经让它工作:

[使用JavaScript和P5.js库]

傅里叶算法(fourier.js):

//  a + bi
class Complex {
  constructor(a, b) {
    this.re = a;
    this.im = b;
  }

  add(c) {
    this.re += c.re;
    this.im += c.im;
  }

  mult(c) {
    const re = this.re * c.re - this.im * c.im;
    const im = this.re * c.im + this.im * c.re;
    return new Complex(re, im);
  }
}



function dft(x) {
  const X = [];
  const Values = [];
  const N = x.length;
  for (let k = 0; k < N; k++) {
    let sum = new Complex(0, 0);
    for (let n = 0; n < N; n++) {
      const phi = (TWO_PI * k * n) / N;
      const c = new Complex(cos(phi), -sin(phi));
      sum.add(x[n].mult(c));
    }
    sum.re = sum.re / N;
    sum.im = sum.im / N;

    let freq = k;
    let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
    let phase = atan2(sum.im, sum.re);
    X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
Values[k] = {phase};
  console.log(Values[k]);

  }
  return X;
}

Sketch功能/动画(Sketch.js):

let x = [];
let fourierX;
let time = 0;
let path = [];

function setup() {
  createCanvas(800, 600);
  const skip = 1;
  for (let i = 0; i < drawing.length; i += skip) {
    const c = new Complex(drawing[i].x, drawing[i].y);
    x.push(c);
  }
  fourierX = dft(x);
  fourierX.sort((a, b) => b.amp - a.amp);
}

function epicycles(x, y, rotation, fourier) {
  for (let i = 0; i < fourier.length; i++) {
    let prevx = x;
    let prevy = y;
    let freq = fourier[i].freq;
    let radius = fourier[i].amp;
    let phase = fourier[i].phase;
    x += radius * cos(freq * time + phase + rotation);
    y += radius * sin(freq * time + phase + rotation);

    stroke(255, 100);
    noFill();
    ellipse(prevx, prevy, radius * 2);
    stroke(255);
    line(prevx, prevy, x, y);
  }
  return createVector(x, y);
}

function draw() {
  background(0);

  let v = epicycles(width / 2, height / 2, 0, fourierX);
  path.unshift(v);

  beginShape();
  noFill();
  for (let i = 0; i < path.length; i++) {
    vertex(path[i].x, path[i].y);
  }
  endShape();

  const dt = TWO_PI / fourierX.length;
  time += dt;

最重要的是!路径/坐标: (这是一个三角形)

let drawing = [

  { y:  -8.001009734    , x:    -50 },
  { y:  -7.680969345    , x:    -49 },
  { y:  -7.360928956    , x:    -48 },
  { y:  -7.040888566    , x:    -47 },
  { y:  -6.720848177    , x:    -46 },
  { y:  -6.400807788    , x:    -45 },
  { y:  -6.080767398    , x:    -44 },
  { y:  -5.760727009    , x:    -43 },
  { y:  -5.440686619    , x:    -42 },
  { y:  -5.12064623 , x:    -41 },
  { y:  -4.800605841    , x:    -40 },
...
...

  { y:  -8.001009734    , x:    -47 },
  { y:  -8.001009734    , x:    -48 },
  { y:  -8.001009734    , x:    -49 },


];


2
就3D渲染而言,https://threejs.org/ 是一个难以超越的JavaScript库。它有大量的示例和广泛的用户群。FWIW。 - Trentium
谢谢你的推荐,我会试一试。你认为它能够在三维中复制我在二维中所拥有的旋转圆圈等效果吗?:D - CWrecker
不确定这里的问题是什么。 - Trilarion
2个回答

3
这篇回答是针对以下问题:“你认为[three.js]能够在3D中复制我在2D中的旋转圆和其他东西吗?”
如果您只是想制作最终产品,那么three.js是一个强大的图形库,基于webGL构建,我认为它足够简单,即使初学者也可以尝试使用,但它具有非常深入的功能,可以产生非常复杂的3D效果。(浏览https://threejs.org/examples/中的示例,您就会明白。)
我正在进行自己的three.js项目,并快速创建了一个环形圆圈的示例练习。这涉及从以下参考资料中提取零件和部分...
* https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene * https://threejs.org/examples/#misc_controls_orbit * https://threejs.org/examples/#webgl_geometry_shapes (该three.js示例是一个很好的资源,展示了呈现形状的多种方式。)
结果是一个简单的场景,其中一个圆圈在另一个圆圈周围运行,允许使用鼠标控制绕场景旋转,从不同的角度和距离查看。

<html>
  <head>
    <title>Epicyclic Circles</title>
    <style>
      body { margin: 0; }
      canvas { width: 100%; height: 100% }
    </style>
  </head>
  <body>

    <script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"></script>
    <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>

    <script>

      // Set up the basic scene, camera, and lights.
      
      var scene = new THREE.Scene();
      scene.background = new THREE.Color( 0xf0f0f0 );
      
      var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
      scene.add(camera)
      
      var light = new THREE.PointLight( 0xffffff, 0.8 );
      camera.add( light );
      
      camera.position.z = 50;
        
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize( window.innerWidth, window.innerHeight );
      document.body.appendChild( renderer.domElement );
      
      // Add the orbit controls to permit viewing the scene from different angles via the mouse.
      
      controls = new THREE.OrbitControls( camera, renderer.domElement );
      controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
      controls.dampingFactor = 0.25;
      controls.screenSpacePanning = false;
      controls.minDistance = 0;
      controls.maxDistance = 500;
      
      // Create center and epicyclic circles, extruding them to give them some depth.
      
      var extrudeSettings = { depth: 2, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: .25, bevelThickness: .25 };

      var arcShape1 = new THREE.Shape();
      arcShape1.moveTo( 0, 0 );
      arcShape1.absarc( 0, 0, 15, 0, Math.PI * 2, false );
      var holePath1 = new THREE.Path();
      holePath1.moveTo( 0, 10 );
      holePath1.absarc( 0, 10, 2, 0, Math.PI * 2, true );
      arcShape1.holes.push( holePath1 );
        
      var geometry1 = new THREE.ExtrudeBufferGeometry( arcShape1, extrudeSettings );
      var mesh1 = new THREE.Mesh( geometry1, new THREE.MeshPhongMaterial( { color: 0x804000 } ) );
      scene.add( mesh1 );
      
      var arcShape2 = new THREE.Shape();
      arcShape2.moveTo( 0, 0 );
      arcShape2.absarc( 0, 0, 15, 0, Math.PI * 2, false );
      var holePath2 = new THREE.Path();
      holePath2.moveTo( 0, 10 );
      holePath2.absarc( 0, 10, 2, 0, Math.PI * 2, true );
      arcShape2.holes.push( holePath2 );
          
      var geometry2 = new THREE.ExtrudeGeometry( arcShape2, extrudeSettings );
      var mesh2 = new THREE.Mesh( geometry2, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
      scene.add( mesh2 );

      // Define variables to hold the current epicyclic radius and current angle.
      var mesh2AxisRadius = 30;
      var mesh2AxisAngle = 0;

      var animate = function () {
        requestAnimationFrame( animate );

        // During each animation frame, let's rotate the objects on their center axis,  
        // and also set the position of the epicyclic circle.
        
        mesh1.rotation.z -= 0.02;

        mesh2.rotation.z += 0.02;
        mesh2AxisAngle += 0.01;
        mesh2.position.set ( mesh2AxisRadius * Math.cos(mesh2AxisAngle), mesh2AxisRadius * Math.sin(mesh2AxisAngle), 0 );

        renderer.render( scene, camera );
      };

      animate();
    </script>
  </body>
</html>

请注意,我在animate函数中使用了基本的三角函数来定位环绕中心圆的外摆圆,并且为了旋转圆圈的速度而进行了调整(而不是进行精确计算),但可能有更好的“three.js”方式通过矩阵或内置函数来完成此操作。鉴于您显然具有扎实的数学背景,我认为在将二维多重外摆圆模型转换为三维时,您不会遇到任何问题。
希望这有助于您决定如何继续开发程序的3D版本。

我必须承认,threeJS 令人印象深刻。在看了你的演示和一些例子之后,我认为它完美地处理我的项目。感谢你的推荐! - CWrecker
好的,还有一个问题要问你。如果你读了btilly关于如何计算它的建议,那么就提供了上下文。我想能够基本上使用我的现有代码(在2D中使用P5js库)并将其应用于3D threeJS场景。我想我可以制作三个平面(我已经在ThreeJS中完成了这个),并使用我的P5代码在每个平面上进行动画。你知道这方面的方法吗? - CWrecker
我为此添加了一个新帖子,因为我认为这是一个更普遍的问题。请看:https://dev59.com/g7Xna4cB1Zd3GeqPHDr7 - CWrecker

2
我建议的方法如下。从参数化路径v(t) = (v_x(t), v_y(t), v_z(t))开始。考虑以下投影到X-Y平面上:v1(t) = (v_x(t)/2, v_y(t), 0)。以及相应的投影到X-Z平面上:v2(t) = (v_x(t)/2, 0, v_z(t))
当我们将这些投影相加时,我们得到原始曲线。但是每个投影现在都是一个封闭的二维曲线,而您已经有了任意封闭二维曲线的解决方案。因此,解决每个问题。然后交错它们,以获得投影,其中您的第一个圆在X-Y平面上,第二个圆在X-Z平面上,第三个圆在X-Y平面上,第四个圆在X-Z平面上...它们相加得到您的答案!"最初的回答"

1
有趣的想法。你打算如何在可视化方面进行计算?喜欢这个想法! - CWrecker
而且为什么vx要除以2? - CWrecker
1
@CamdenS vx被除以2,因为X分量被传递到X-Y和X-Z周期。将它们相加,就可以得到v_x。可视化是...需要思考的事情... :-/ - btilly
哦,我明白了,感谢您的回复。我会继续努力在可视化部分工作的,哈哈..谢谢。 - CWrecker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接