首页 > 解决方案 > 如何在 THREE.js 中实现精灵在球体表面上的均匀分布?

问题描述

我正在尝试创建一个单词数据库,其中最重要的单词更靠近球体的顶部,而不太重要的单词则离球体更远。因此,我为每个单词创建了一个具有足够顶点的球体,按照与球体顶部的距离顺序创建了这些顶点的列表,并将文本精灵按排序列表的顺序放置在顶点的位置。

在此处输入图像描述 视频版:https ://i.gyazo.com/aabaf0b4a26f4413dc6a0ebafab2b4bd.mp4

在我的脑海中听起来像是一个很好的计划,但显然球体的几何形状会导致单词离顶部越远越分散。我需要一个表面看起来有点均匀分布的结果。它不必是完美的,只是视觉上比这更接近。

我怎样才能达到预期的效果?

以下是相关方法:

positionDb(db) {
    console.log("mostRelated", db.mostRelated);
    console.log("depthList", this.depthList);
    let mostRelated = db.mostRelated;
    let depthList = this.depthList;
    for (let i = 0; i < mostRelated.length; i++) {
      this.addTextNode(mostRelated[i].data, this.depthList[i].vertice, this.depthList[i].depth);
    }

}
addTextNode(text, vert, distance) {
    let fontSize = 0.5 * (600 / distance);
    let sprite = new THREE.TextSprite({
      fillStyle: '#000000',
      fontFamily: '"Arial", san-serif',
      fontSize: fontSize,
      fontWeight: 'bold',
      text: text
    });
    this.scene.add(sprite);
    sprite.position.set(vert.x, vert.y, vert.z);
    setTimeout(() => {
        sprite.fontFamily = '"Roboto", san-serif';
    }, 1000)
}


this.scene = scene;
this.geometry = new THREE.SphereGeometry(420, 50, 550);
var material = new THREE.MeshBasicMaterial({
    color: 0x0011ff
});
var sphere = new THREE.Mesh(this.geometry, wireframe);
var wireframe = new THREE.WireframeGeometry(this.geometry);
let frontVert = {
    x: 0,
    y: 100,
    z: 0
}
let depthList = [];
this.geometry.vertices.forEach(vertice => {
  let depth = getDistance(frontVert, vertice);
  if (depthList.length === 0) {
    depthList.push({
      depth,
      vertice
    });
  } else {
    let flag = false;
    for (let i = 0; i < depthList.length; i++) {
      let item = depthList[i];
      if (depth < item.depth) {
        flag = true;
        depthList.splice(i, 0, {
          depth,
          vertice
        });
        break;
      }
    }
    if (!flag) depthList.push({
      depth,
      vertice
    });
  }
});

标签: javascriptthree.js

解决方案


也许是斐波那契球体

function fibonacciSphere(numPoints, point) {
  const rnd = 1;
  const offset = 2 / numPoints;
  const increment = Math.PI * (3 - Math.sqrt(5));

  const y = ((point * offset) - 1) + (offset / 2);
  const r = Math.sqrt(1 - Math.pow(y, 2));

  const phi = (point + rnd) % numPoints * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return new THREE.Vector3(x, y, z);
}

例子:

function fibonacciSphere(numPoints, point) {
  const rnd = 1;
  const offset = 2 / numPoints;
  const increment = Math.PI * (3 - Math.sqrt(5));

  const y = ((point * offset) - 1) + (offset / 2);
  const r = Math.sqrt(1 - Math.pow(y, 2));

  const phi = (point + rnd) % numPoints * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return new THREE.Vector3(x, y, z);
}

function main() {
  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  function addTextNode(text, vert) {
    const div = document.createElement('div');
    div.className = 'label';
    div.textContent = text;
    div.style.marginTop = '-1em';
    const label = new THREE.CSS2DObject(div);
    label.position.copy(vert);
    scene.add(label);
  }

  const renderer = new THREE.CSS2DRenderer();
  const container = document.querySelector('#c');
  container.appendChild(renderer.domElement);

  const controls = new THREE.OrbitControls(camera, renderer.domElement);
  
  const numPoints = 50;
  for (let i = 0; i < numPoints; ++i) {
    addTextNode(`p${i}`, fibonacciSphere(numPoints, i));
  }

  function render(time) {
    time *= 0.001;

    // three's poor choice of how to hanlde size strikes again :(
    renderer.setSize(container.clientWidth, container.clientHeight); 
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
body {
  margin: 0;
  overflow: hidden;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
.label {
  color: red;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/examples/js/renderers/CSS2DRenderer.js"></script>
<div id="c"></div>


推荐阅读