Threejs - 环在旋转之前不会出现?

Threejs - Ring doesn't appear until rotated?

在 Three.js 中,我正在尝试制作一个交互式点击并拖动以旋转的对象,该对象由一个环和一条线组成。我 运行 遇到了一个奇怪的问题,在第一次旋转之前,圆环不会出现。我的代码如下。

为什么直到对象旋转才出现圆环?

编辑:我发现 this.ring.rotateX(Math.PI / 2) 似乎是阻止环出现的原因。

初始化(环不可见)

经过微小的旋转(环可见)

<!doctype html>
<html>

<head>
  <meta charset="utf-8" />
  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js'></script>
  <!-- TheJim01: Adding CDN jquery for snippet -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <!-- <script src="js/jquery.min.js"></script> -->
  <!--    <script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script>-->
</head>

<body>
  <canvas id="threejs_covering_canvas" width="1000" height="500"></canvas>

  <script>
    const canvas = $('#threejs_covering_canvas')[0];
    const scene = new THREE.Scene();
    const camera = new THREE.OrthographicCamera(-10, 10, 5, -5, 1, 1000);

    camera.position.z = 5;

    const renderer = new THREE.WebGLRenderer({
      canvas
    });

    document.body.appendChild(renderer.domElement);

    function vecToSurfaceNormalRGB(vec) {

      // normals are in range [-1, 1], so this maps to [0, 1]
      let red = 0.5 * vec.x + 0.5;
      let green = 0.5 * vec.y + 0.5;
      let blue = 0.5 * vec.z + 0.5;

      return new THREE.Color(red, green, blue);
    }

    class KoenderinkCircle extends THREE.Object3D {

      constructor(dir, origin) {

        super();
        this.type = 'KoenderinkCircle';

        this._axis = new THREE.Vector3();

        this._lineGeometry = new THREE.BufferGeometry();
        this._lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 1, 0], 3));
        this.line = new THREE.Line(this._lineGeometry, new THREE.LineBasicMaterial({
          color: 0xffff00,
          toneMapped: false,
          linewidth: 4
        }));
        this.add(this.line)

        // THREE.RingBufferGeometry
        this._ringGeometry = new THREE.RingBufferGeometry(0.5, 1, 100);
        this.ring = new THREE.Mesh(this._ringGeometry, new THREE.MeshNormalMaterial({
          side: THREE.DoubleSide
        }));
        // Rotate the ring such that line is normal to plane the ring lies on.
        this.ring.rotateX(Math.PI / 2);
        this.add(this.ring);

        this.position.copy(origin);
        this.updateLineColor();
        this.setDirection(dir)

      }

      setDirection(dir) {

        // dir is assumed to be normalized

        if (dir.y > 0.99999) {

          this.quaternion.set(0, 0, 0, 1);

        } else if (dir.y < -0.99999) {

          this.quaternion.set(1, 0, 0, 0);

        } else {

          this._axis.set(dir.z, 0, -dir.x).normalize();

          const radians = Math.acos(dir.y);

          this.quaternion.setFromAxisAngle(this._axis, radians);

        }

      }

      updateLineColor() {

        let lineDirection = new THREE.Vector3();
        this.ring.getWorldDirection(lineDirection);
        // For some reason, ring points in opposite direction.
        // Multiply by -1 to get correct direction.
        lineDirection.multiplyScalar(-1.)

        // TODO: Why does color work correctly only on the front half, not the back half?
        this.line.material.color.set(vecToSurfaceNormalRGB(lineDirection.normalize()));
      }

    }

    // var koenderinkIndicatorDirection = new THREE.Vector3(1, 1, 0).normalize();
    var koenderinkIndicatorDirection = new THREE.Vector3(0, 1, 0).normalize();
    var koenderinkIndicatorPosition = new THREE.Vector3(-4, 2, 0);
    const koenderinkIndicator = new KoenderinkCircle(
      koenderinkIndicatorDirection,
      koenderinkIndicatorPosition)
    scene.add(koenderinkIndicator);


    var pointer = new THREE.Vector3();
    var mouseStartOrthographicPosition = new THREE.Vector3();
    var mousePenultimateOrthographicPosition = new THREE.Vector3();
    var mouseCurrentOrthographicPosition = new THREE.Vector3();

    var distanceCurrentMinusPenultimate = new THREE.Vector3();
    var distancePenultimateMinusStart = new THREE.Vector3();
    var distanceCurrentMinusStart = new THREE.Vector3();
    var angle;
    var axis = new THREE.Vector3();
    let lineWorldDirection = new THREE.Vector3();
    let koenderinkIndicatorWorldDirection = new THREE.Vector3();
    // This vector will be used to check that rotation doesn't permit pointing
    // line in negative Z direction.
    let negativeZVector = new THREE.Vector3(0, 0, -1);

    const radius = 1;
    const radiusSquared = radius * radius;
    let rotationQuaternion = new THREE.Quaternion();
    let mouseDown = false;

    function setMouseCurrentOrthographicPosition(event) {

      let canvasBoundingBox = canvas.getBoundingClientRect();
      let sketchpadCurrentHeight = canvasBoundingBox.bottom - canvasBoundingBox.top;
      let sketchpadCurrentWidth = canvasBoundingBox.right - canvasBoundingBox.left;

      // Compute mouse location relative to canvasBoundingBox, then normalize to [-1, 1]
      // I think this is correct

      pointer.x = ((event.clientX - canvasBoundingBox.left) / sketchpadCurrentWidth) * 2 - 1;
      pointer.y = -((event.clientY - canvasBoundingBox.top) / sketchpadCurrentHeight) * 2 + 1;

      // First computes location of mouse on z=0 plane
      // See: 
      // Third argument is irrelevant.
      mouseCurrentOrthographicPosition.set(pointer.x, pointer.y, 0);

      // Projects from ThreeJS-independent "normalized device coordinate space" (i.e. -1 to 1)
      // to "world space" i.e. the coordinates used by ThreeJS
      mouseCurrentOrthographicPosition.unproject(camera);

      return mouseCurrentOrthographicPosition
    }

    canvas.addEventListener('mousedown', function(event) {
      // Save mouseStartOrthographicPosition.
      mouseStartOrthographicPosition.copy(setMouseCurrentOrthographicPosition(event));

      // Initialize the 2nd most recent othographic position.
      mousePenultimateOrthographicPosition.copy(mouseStartOrthographicPosition);
      mouseDown = true;

      // console.log('mousedown set to True');
    });

    canvas.addEventListener('mouseup', function(event) {
      mouseDown = false;
    });

    function rotateIndicator(event) {

      if (mouseDown) {

        setMouseCurrentOrthographicPosition(event);

        distanceCurrentMinusPenultimate.subVectors(mouseCurrentOrthographicPosition, mousePenultimateOrthographicPosition);
        distanceCurrentMinusStart.subVectors(mouseCurrentOrthographicPosition, mouseStartOrthographicPosition);
        distancePenultimateMinusStart.subVectors(mousePenultimateOrthographicPosition, mouseStartOrthographicPosition);

        let cond1 = Math.pow(distancePenultimateMinusStart.x, 2) + Math.pow(distancePenultimateMinusStart.y, 2) + 0.0001 < radiusSquared;
        let cond2 = Math.pow(distanceCurrentMinusStart.x, 2) + Math.pow(distanceCurrentMinusStart.y, 2) + 0.0001 < radiusSquared;

        if (cond1 && cond2) {

          let v0 = new THREE.Vector3(
            distancePenultimateMinusStart.x,
            distancePenultimateMinusStart.y,
            Math.sqrt(radiusSquared - Math.pow(distancePenultimateMinusStart.x, 2) - Math.pow(distancePenultimateMinusStart.y, 2))
          ).normalize()

          let v1 = new THREE.Vector3(
            distanceCurrentMinusStart.x,
            distanceCurrentMinusStart.y,
            Math.sqrt(radiusSquared - Math.pow(distanceCurrentMinusStart.x, 2) - Math.pow(distanceCurrentMinusStart.y, 2))
          ).normalize()

          angle = Math.acos(Math.max(Math.min(v0.dot(v1), 0.999), -0.999));

          axis.crossVectors(v0, v1).normalize()

          rotationQuaternion.setFromAxisAngle(axis, angle);

          // For some reason, the line world direction is rotated 90 degrees towards the camera.
          // i.e. if the line is pointing vertically up, the world direction is (0, 0, 1)
          // if the line is pointing towards the camera, the world direction is (0, -1, 0)
          console.log('koenderinkIndicator axis: ', koenderinkIndicator._axis)
          koenderinkIndicator.getWorldDirection(koenderinkIndicatorWorldDirection);
          console.log('lineWorldDirection Before Quaternion: ', koenderinkIndicatorWorldDirection)

          // Apply rotation to lineWorldDirection
          lineWorldDirection.applyQuaternion(rotationQuaternion)

          // console.log('lineWorldDirection After Quaternion: ', lineWorldDirection)

          // Check whether rotation would result in line facing backwards.
          let dotWithZDirection = lineWorldDirection.dot(negativeZVector)

          // console.log('Dot with Z Direction: ', dotWithZDirection)

          koenderinkIndicator.applyQuaternion(rotationQuaternion);
          koenderinkIndicator.updateLineColor();

        }

        mousePenultimateOrthographicPosition.copy(mouseCurrentOrthographicPosition);

      }
    }

    canvas.addEventListener('mousemove', rotateIndicator);

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

</html>

平面物体本质上没有体积。它们实际上是二维的。因此,您的初始视图是“在边缘”看圆环,这意味着您正在沿着没有维度值的平面看,因此该对象看起来是不可见的。 “稍微”移动视图足以使您的视点不再与环在同一平面上,因此环出现。

如果您希望能够从各个角度看到圆环,请考虑给它一些深度,比如一个非常短的圆柱体(高度仍大于零),上面有一个孔。