3

我正在尝试开发一种 Java3D 方法,用于以从当前观察方向到对象中心方向的增量旋转宇宙。

换句话说,我希望 3D 宇宙旋转 100 步,这样我单击的对象会逐渐移动到屏幕中心。

我已经在 StackOverflow(以及 Web)上查看了 3D 旋转问题的各种答案,但几乎所有这些答案都特定于旋转对象,而不是世界本身。

我还尝试查看我的线性代数,但这并不能帮助我确定满足我要求的特定于 Java 的函数。

到目前为止,我已经尝试定义一组增量 XYZ 坐标,并在每次通过循环时动态使用 lookAt()。这几乎可行,但是我看不到任何方法可以从一个完整的旋转传递到下一个完整的旋转传递中保留或获取视点值;每个旋转通道都从查看原点开始。

我还尝试通过获取目标和开始变换之间的差异并除以增量数(并删除缩放值)来定义旋转矩阵,然后在每次通过时将该增量旋转矩阵添加到当前视图方向环形。这对于增量值 1 来说效果很好。但是将旋转分成两个或多个增量总是会生成“BadTransformException:ViewPlatform 上方的非全等变换”错误。(我已在 Java3D API 参考中阅读了有关此异常的少量文档;它可能是用乌尔都语编写的,因为我可以从中得到所有信息。似乎没有像这样的 3D 上下文术语的简单英语定义Google 可以看到的任何地方的“仿射”或“剪切”或“全等”或“均匀”。)

然后,我尝试将我的代码提供一个 AxisAngle4d,获取角度(以弧度为单位),将该角度划分为我想要的增量,并按增量角度值旋转。好吧,这旋转了世界,但离我选择的对象不远,而且没有我能看到的任何模式。

绝望中,我尝试在提取的角度上使用 rotX 和 rotY (将 Z 设置为端点),甚至盲目地在那里扔了几个 Math.cos() 和 Math.sin() 包装器。仍然没有喜悦。

我的直觉告诉我,我已经掌握了基础知识,并且在 Java3D 中有一个相对简单的解决方案。但很明显,我正在撞到一堵理解墙。与其继续这样做,我想我会继续看看这里是否有人可以提出 Java3D 中的解决方案。代码是首选,但我愿意尝试遵循线性代数中的解释,如果这能让我找到代码解决方案。

下面是我使用 Java 的 Timer 方法来安排轮换增量的方法的核心。我需要帮助的部分就在 ActionListener 之前。大概这就是魔术代码会创建某种增量旋转值的地方,我可以(在循环中)将其应用于当前视图方向,以便旋转宇宙而不会出现“不一致”错误。

  private void flyRotate(double endX, double endY, double endZ)
  {
    // Rotate universe by increments until target object is centered in view
    // 
    // REQUIREMENTS
    // 1. Rotate the universe by NUMROTS increments from an arbitrary (non-origin)
    //   3D position and starting viewpoint to an ending viewpoint using the
    //   shortest path and preserving the currently defined "up" vector.
    // 2. Use the Java Timer() method to schedule the visual update for each
    //   incremental rotation.
    //
    // GLOBALS
    // rotLoop contains the integer loop counter for rotations (init'd to 0)
    // viewTransform3D contains rotation/translation for current viewpoint
    // t3d is a reusable Transform3D variable
    // vtg contains the view platform transform group
    // NUMROTS contains the number of incremental rotations to perform
    //
    // INPUTS
    // endX, endY, endZ contain the 3D position of the target object
    //
    // NOTE: Java3D v1.5.1 or later is required for the Vector3D getX(),
    //   getY(), and getZ() methods to work.

    final int delay = 20; // milliseconds between firings
    final int pause = 10; // milliseconds before starting

    // Get translation components of starting viewpoint vector
    Vector3d viewVector = new Vector3d();
    viewTransform3D.get(viewVector);
    final double startX = viewVector.getX();
    final double startY = viewVector.getY();
    final double startZ = viewVector.getZ();

    // Don't try to rotate to the location of the current viewpoint
    if (startX != endX || startY != endY || startZ != endZ)
    {
      // Get a copy of the starting view transform
      t3d = new Transform3D(viewTransform3D);

      // Define the initial eye/camera position and the "up" vector
      // Note: "up = +Y" is just the initial naive implementation
      Point3d  eyePoint = new Point3d(startX,startY,startZ);
      Vector3d upVector = new Vector3d(0.0,1.0,0.0);

      // Get target view transform
      // (Presumably something like this is necessary to get a transform
      // containing the ending rotation values.)
      Transform3D tNew = new Transform3D();
      Point3d viewPointTarg = new Point3d(endX,endY,endZ);
      tNew.lookAt(eyePoint,viewPointTarg,upVector);
      tNew.invert();

      // Get a copy of the target view transform usable by the Listener
      final Transform3D tRot = new Transform3D(tNew);

      //
      // (obtain either incremental rotation angle
      // or congruent rotation transform here)
      //

      ActionListener taskPerformer = new ActionListener()
      {
        public void actionPerformed(ActionEvent evt)
        {
          if (++rotLoop <= NUMROTS)
          {
            // Apply incremental angle or rotation transform to the
            // current view
            t3d = magic(tRot);

            // Communicate the rotation to the view platform transform group
            vtg.setTransform(t3d);
          }
          else
          {
            timerRot.stop();
            rotLoop = 0;
            viewTransform3D = t3d;
          }
        }
      };

      // Set timer for rotation steps
      timerRot = new javax.swing.Timer(delay,taskPerformer);
      timerRot.setInitialDelay(pause);
      timerRot.start();
    }
  }

就像这些事情经常发生的情况一样,通过退后一步重新思考问题,可能有更好的方法来完成我在这里尝试完成的工作。我也愿意接受建设性的建议。

非常感谢您对此提供的任何帮助!


更新

让我尝试更具体地定义目标。

我有一个包含许多 Sphere 对象的 Java3D 宇宙。我可以单击每个对象并动态获取其预定义的 XYZ 坐标。

在任何时候,我都在用“相机”在特定的 XYZ 位置和视图方向查看所有当前可见的对象,这些对象包含在保存旋转矩阵和平移向量的变换中。

(注意:我既可以旋转宇宙,也可以使用鼠标进行平移,而无需单击对象。因此,有时包含相机当前旋转矩阵和平移向量的视图变换不会指向具有已知 XYZ 的任何目标对象坐标。)

给定相机变换和对象的 XYZ 坐标,我想围绕我当前的相机位置旋转宇宙,直到所选对象位于屏幕中央。我想将其作为一系列离散增量旋转来执行,每个旋转都被渲染,以便可见宇宙在查看窗口中看起来“旋转”,直到所选对象居中。(我正在跟进这个对象的翻译;那部分至少是有效的!)

示例:假设相机在原点,“向上”沿 Y 轴为 1.0,并且所选对象以我左侧十个单位为中心。假设我有一个 180 度的视野,我可以单击屏幕左侧一直可见的球体的一半,以及屏幕顶部和底部之间的一半。

当我说出这个词时,宇宙中的每个可见物体都应该从我的左到右以一系列均匀间隔的步长(比如说 50 个)移动,直到所选物体在屏幕中完全居中。

在编码方面,我需要编写 Java3D 代码,通过它我可以围绕一条穿过我的相机位置(当前位于 0,0,0)并且与 Y 轴完全对齐的假想线旋转宇宙宇宙坐标系。(即,旋转轴扫过一个平面,其中 Z 始终等于相机位置的 Z 分量。)

复杂的要求是:

  1. 相机可以在 3D 空间中的某个位置进行平移,而不是原点。
  2. 相对于相机的当前位置和视图,对象可以位于 3D 空间中的任何位置,包括可见但完全不在屏幕上(视锥体之外)。
  3. 旋转应该走最短的路径——一次不要让宇宙旋转超过 180 度。
  4. 作为旋转过程的第一步,不应该有任何可见宇宙的“跳跃”或“扭曲”;即,应该保留当前的“向上”向量(不是宇宙的绝对“向上”向量)。

那么问题来了:给定一个保存(虚拟)相机当前平移和旋转信息的变换,以及目标对象在宇宙空间中的 XYZ 坐标,Java3D 代码将围绕相机以 N 个相等的步长旋转宇宙,直到该对象是在屏幕中央?

大概这个解决方案分为两部分:首先,一些 3D 数学(用 Java3D 表示)来计算仅给定相机变换和对象的 XYZ 坐标的增量旋转信息;其次,一个循环[将增量旋转应用于当前查看变换并更新屏幕],直到循环计数器等于增量数。

打败我的是 3D 数学部分。我没有看到也无法从当前相机变换和目标对象位置获取某种形式的增量旋转信息,然后我可以将其应用于相机变换。至少,我还没有找到任何不会导致跳跃或扭曲或不等增量移动步骤的方法(或“ViewPlatform 上方的非全等变换”异常)。

必须有一个简单的解决方案......

4

1 回答 1

0

因此,如果我理解正确,您的目标是旋转相机,使其以所选对象为中心,但旋转不应是任意矢量,而应保持相机的“向上”方向。

一个可能可行的解决方案:

  1. 首先,计算必要的“向上”向量的旋转角度(我们称之为 A),以便相机面向您想要的对象。
  2. 其次,计算沿“向上”向量所需的平移距离/方向(我们称之为 D),以便对象根据需要与相机对齐。这可能只是相机/对象之间 Z/Y 坐标的差异。
  3. 通过将 A/D 与 N 相乘来找到 dA 和 dD,N 是您想要使运动平滑的增量数。
  4. 在定时器/时间循环中,将 A/D 分别增加 dA/dD N 次,将它们变为最终值。请记住,您正在围绕它的“向上”矢量和当前位置旋转相机,而不是关于原点。

如果您想要更平滑、更逼真的旋转,请考虑使用SLERP

于 2012-05-18T00:15:49.383 回答