2

我想从 Geometry / Wolfram Mathematica 人那里得到帮助。我想在 JavaScript (p5.js) 环境中可视化这个 3D Rose。

我期望产生的玫瑰图

该图最初是Paul Nylander在 2004-2006 年使用 wolfram 语言生成的,下面是代码:

Rose[x_, theta_] := Module[{
  phi = (Pi/2)Exp[-theta/(8 Pi)], 
  X = 1 - (1/2)((5/4)(1 - Mod[3.6 theta, 2 Pi]/Pi)^2 - 1/4)^2}, 
  y = 1.95653 x^2 (1.27689 x - 1)^2 Sin[phi]; 
  r = X(x Sin[phi] + y Cos[phi]); 
  {r Sin[theta], r Cos[theta], X(x Cos[phi] - y Sin[phi]), EdgeForm[]
}];

ParametricPlot3D[
  Rose[x, theta], {x, 0, 1}, {theta, -2 Pi, 15 Pi}, 
  PlotPoints -> {25, 576}, LightSources -> {{{0, 0, 1}, RGBColor[1, 0, 0]}}, 
  Compiled -> False
]

我尝试在 JavaScript 中实现该代码,如下所示。

function rose(){
  for(let theta = 0; theta < 2700; theta += 3){
    beginShape(POINTS);
    for(let x = 2.3; x < 3.3; x += 0.02){
      let phi = (180/2) * Math.exp(- theta / (8*180));
      let X = 1 - (1/2) * pow(((5/4) * pow((1 - (3.6 * theta % 360)/180), 2) - 1/4), 2);
      let y = 1.95653 * pow(x, 2) * pow((1.27689*x - 1), 2) * sin(phi);
      let r = X * (x*sin(phi) + y*cos(phi));

      let pX = r * sin(theta);
      let pY = r * cos(theta);
      let pZ = (-X * (x * cos(phi) - y * sin(phi)))-200;
  
      vertex(pX, pY, pZ);
    }
    endShape();
  }
}

但我在下面得到了这个结果

我的 JS 实现的结果 我的 JS 实现的结果

与原来的不同,顶部的花瓣过于拉伸。

我怀疑

let y = 1.95653 * pow(x, 2) * pow((1.27689*x - 1), 2) * sin(phi);

可能应该像下面...

let y = pow(1.95653*x, 2*pow(1.27689*x - 1, 2*sin(theta)));

但这与原著相去甚远。

我的 JS 实现的结果

也许我在问一个愚蠢的问题,但我已经被困了好几天了。

如果您看到错误,请告诉我。提前谢谢你


更新:

我将 x 范围更改为原始定义的 0~1。还简化了如下所示的 JS 代码以查找错误。

function rose_debug(){
  for(let theta = 0; theta < 15*PI; theta += PI/60){
    beginShape(POINTS);
    for(let x = 0.0; x < 1.0; x += 0.005){
      let phi = (PI/2) * Math.exp(- theta / (8*PI));
      let y = pow(x, 4) * sin(phi);
      let r = (x * sin(phi) + y * cos(phi));

      let pX = r * sin(theta);
      let pY = r * cos(theta);
      let pZ = x * cos(phi) - y * sin(phi);
      vertex(pX, pY, pZ);
    }
    endShape();
  }
}

但是结果还是保持比例不对↓↓↓ 在此处输入图像描述

此外,当我在“let y =...”行中删除术语“sin(phi)”时,如下所示

let y = pow(x, 4);

然后我得到了一个有点像下面的原图 在此处输入图像描述

此时我开始怀疑原始方程的错误,但我发现了 Jorge García Tíscar(西班牙语)的另一篇文章,它成功地用 wolfram 语言实现了完全相同的 3D 玫瑰。

在此处输入图像描述

所以,现在我真的不知道原来的方程式是如何形成的


更新2:已解决

我按照 Trentium 的建议(下面的答案 2)坚持将 0 ~ 1 作为 x 的范围,然后将 r 和 X 乘以任意数字。

for(let x = 0; x < 1; x += 0.05){
r = r * 200;
X = X * 200;

然后我得到了这个正确的结果,看起来和原来的完全一样 在此处输入图像描述

简化的最终代码:

function rose_debug3(){
  for(let x = 0; x <= 1; x += 0.05){
    beginShape(POINTS);
    for(let theta = -2*PI; theta <= 15*PI; theta += 17*PI/2000){
      let phi = (PI / 2) * Math.exp(- theta / (8 * PI));
      let X = 1 - (1/2) * ((5/4) * (1 - ((3.6 * theta) % (2*PI))/PI) ** 2 - 1/4) ** 2;
      let y = 1.95653 * (x ** 2) * ((1.27689*x - 1) ** 2) * sin(phi);
      let r = X * (x * sin(phi) + y * cos(phi));

      if(0 < r){
        const factor = 200;
        let pX = r * sin(theta)*factor;
        let pY = r * cos(theta)*factor;
        let pZ = X * (x * cos(phi) - y * sin(phi))*factor;
        vertex(pX, pY, pZ);
      }
    }
    endShape();
  }
}

我最初得到垂直拉伸图形的原因是 x 的范围。我认为改变 x 的范围只会影响图形的整个大小。但实际上,范围影响如下。

(1): 0 ~ x ~ 1, (2): 0 ~ x ~ 1.2

在此处输入图像描述在此处输入图像描述

(3):0~x~1.5,(4):0~x~2.0

在此处输入图像描述在此处输入图像描述

(5): 翻转 (4)

在此处输入图像描述

到目前为止,我看到了像上面(5)这样的结果,没有意识到正确的形状隐藏在那个图中。

非常感谢 Trentium 对我的帮助!

4

2 回答 2

2

由于此回复与我之前的回复大相径庭,因此我添加了一个新答案...

在 ThreeJS 中渲染rose算法时(对不起,我不是 P5 人)很明显,在生成点时,只有具有正半径的点才会被渲染。否则,多余的点会被渲染到远离玫瑰花瓣的地方。

注:运行代码片段时,使用鼠标缩放和旋转渲染的玫瑰。

<script type="module">

  import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.module.js';
  import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.115.0/examples/jsm/controls/OrbitControls.js';

  //
  // Set up the ThreeJS environment.
  //
  var renderer = new THREE.WebGLRenderer();
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
  camera.position.set( 0, 0, 100 );
  camera.lookAt( 0, 0, 0 );

  var scene = new THREE.Scene();
  
  let controls = new OrbitControls(camera, renderer.domElement);

  //
  // Create the points.
  //
  function rose( xLo, xHi, xCount, thetaLo, thetaHi, thetaCount ){
    let vertex = [];
    let colors = [];
    let radius = [];
    for( let x = xLo; x <= xHi; x += ( xHi - xLo ) / xCount ) {
      for( let theta = thetaLo; theta <= thetaHi; theta += ( thetaHi - thetaLo ) / thetaCount ) {
        let phi = ( Math.PI / 2 ) * Math.exp( -theta / ( 8 * Math.PI ) );
        let X = 1 - ( 1 / 2 ) * ( ( 5 / 4 ) * ( 1 - ( ( 3.6 * theta ) % ( 2 * Math.PI ) ) / Math.PI ) ** 2 - 1 / 4 ) ** 2;
        let y = 1.95653 * ( x ** 2 ) * ( (1.27689 * x - 1) ** 2 ) * Math.sin( phi );
        let r = X * ( x * Math.sin( phi ) + y * Math.cos( phi ) ); 

        //
        // Fix: Ensure radius is positive, and scale up accordingly...
        //
        if ( 0 < r ) {
        
          const factor = 20;
          
          r = r * factor;
          radius.push( r );
          X = X * factor;

          vertex.push( r * Math.sin( theta ), r * Math.cos( theta ), X * ( x * Math.cos( phi ) - y * Math.sin( phi ) ) );
        }
      }
    }
    
    //
    // For the fun of it, lets adjust the color of the points based on the radius
    // of the point such that the larger the radius, the deeper the red.
    //
    let rLo = Math.min( ...radius );
    let rHi = Math.max( ...radius );
    for ( let i = 0; i < radius.length; i++ ) {
      let clr = new THREE.Color( Math.floor( 0x22 + ( 0xff - 0x22 ) * ( ( radius[ i ] - rLo ) / ( rHi - rLo ) ) ) * 0x10000 + 0x002222 );
      colors.push( clr.r, clr.g, clr.b );
    }
    
    return [ vertex, colors, radius ];
  }

  //
  // Create the geometry and mesh, and add to the THREE scene.
  //
  const geometry = new THREE.BufferGeometry();
  
  
  let [ positions, colors, radius ] = rose( 0, 1, 20, -2 * Math.PI, 15 * Math.PI, 2000 );
  
  geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
  geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );

  const material = new THREE.PointsMaterial( { size: 4, vertexColors: true, depthTest: false, sizeAttenuation: false } );

  const mesh = new THREE.Points( geometry, material );
  scene.add( mesh );
        
  //
  // Render...
  // 
  var animate = function () {
    requestAnimationFrame( animate );
    renderer.render( scene, camera );
  };

  animate();
</script>

几个名人:

  • 调用rose( xLo, xHi, xCount, thetaLo, thetaHi, thetaCount )时,上限thetaHi可以从Math.PI15 * Math.PI变化,这会改变花瓣的数量。
  • 两者都xCount改变thetaCount​​点的密度。Wolfram 示例分别使用 25 和 576,但这是为了创建几何网格,而如果创建点场,则需要增加点的密度。因此,在代码中,值是 20 和 2000。

享受!

于 2022-02-19T15:32:34.853 回答
1

大概上面的算法是引用cos()sin()处理角度而不是弧度的函数,但是在使用非三角变换时使用角度的任何地方,结果都是不正确的。

例如,以下使用弧度的公式...

  • phi = (Pi/2)Exp[-theta/(8 Pi)]

...被错误地翻译成...

  • phi = ( 180 / 2 ) * Math.exp( -theta / ( 8 * 180 ) )

为了测试,我们假设theta = 2。使用弧度的原始公式...

  • phi = ( Math.PI / 2 ) * Math.exp( -2 / ( 8 * Math.PI ) )
  • = 1.451 弧度
  • = 83.12 度

...现在使用度数的不正确版本会返回不同的角度...

  • phi = ( 180 / 2 ) * Math.exp( -2 / ( 8 * 180 ) )
  • = 89.88 度
  • = 1.569 弧度

翻译错误的表达式也会出现类似的问题...

  • pow( ( 1 - ( 3.6 * theta % 360 ) / 180 ), 2 )

底线:坚持弧度。

PS请注意,可能还有其他问题,但首先需要更正使用弧度而不是度数...

于 2022-02-18T20:06:10.997 回答