【问题标题】:Preventing camera from going 'through' aframe object防止相机“通过”框架对象
【发布时间】:2017-09-13 15:39:56
【问题描述】:

假设我有一个圆柱体模型,我将其加载到我的 webvr 场景中。我怎样才能使物体坚固? IE。用户(相机视图)不能在对象内占据任何位置或“穿过”对象。我怎样才能做到这一点?

<a-scene>
  <a-assets>
    <a-asset-item id="cube-obj" src="cube.obj"></a-asset-item>
  </a-assets>
  <a-entity id="cameraWrapper" position="0 2 10" rotation="0 0 0">
    <a-camera near="0.1" user-height="0" id="camera" listener></a-camera>
  </a-entity>
  <a-entity obj-model="obj: #cube-obj; mtl: #cube-mtl" scale="1 1 1" rotation="-90 0 0"></a-entity>
  <a-plane position="0 4 4" rotation="-90 0 -90" width="4" height="4" color="#7BC8A4"></a-plane>
  <a-sky color="#ECECEC"></a-sky>
</a-scene>

【问题讨论】:

  • 我建议删除此问题的编辑内容,以使其重点突出并与未来的读者相关。如需使用 aframe-physics-system 的帮助,请随时在 GitHub 上打开问题。 :)

标签: html three.js aframe webvr


【解决方案1】:

自从我来到这里寻找一个更简单的答案(不是处理自定义,而是处理标准 AFrame 对象),也许这不是你想要的。但是,如果您只是要求“防止相机穿过 AFrame 对象”,也许这会对您有所帮助。

在下面的示例中,我使用AFrame Physics System 来管理物理。 我从AFrame Extras 复制了kinematic-body 组件,名称为kinema-body(参见嵌入式脚本),只是因为它被标记为“已弃用”,因此它可能会在未来消失。

您将看到如何在场景中移动,但是当到达一个物体时,您无法穿过它。这在桌面和 Oculus Go 中都适用于我。诀窍很简单:相机体现在kinema-body 装备中,物体(平面和盒子)是static-body。当kinema-body 接触到static-body 时,它会停止(嗯,或者尝试绕过它,这取决于它的移动方式)。

<!DOCTYPE html>

<html>
  <head>
    <script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
    <script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v5.0.0/dist/aframe-extras.min.js"></script>
    <script src="//cdn.rawgit.com/donmccurdy/aframe-physics-system/v3.3.0/dist/aframe-physics-system.min.js"></script>
    <script>
/**
 * Kinema body.
 *
 * Based on kinematic-body, from AFrame Extras (for now, basically a copy,
 *   just because I read it is deprecated in AFrame Extras)
 *
 *   https://github.com/donmccurdy/aframe-extras/blob/master/src/misc/kinematic-body.js
 *
 * Managed dynamic body, which moves but is not affected (directly) by the
 * physics engine. This is not a true kinematic body, in the sense that we are
 * letting the physics engine _compute_ collisions against it and selectively
 * applying those collisions to the object. The physics engine does not decide
 * the position/velocity/rotation of the element.
 *
 * Used for the camera object, because full physics simulation would create
 * movement that feels unnatural to the player. Bipedal movement does not
 * translate nicely to rigid body physics.
 *
 * See: http://www.learn-cocos2d.com/2013/08/physics-engine-platformer-terrible-idea/
 * And: http://oxleygamedev.blogspot.com/2011/04/player-physics-part-2.html
 */
const EPS = 0.000001;

AFRAME.registerComponent('kinema-body', {
  dependencies: ['velocity'],

  /*******************************************************************
   * Schema
   */

  schema: {
    mass:           { default: 5 },
    radius:         { default: 1.3 },
    linearDamping:  { default: 0.05 },
    enableSlopes:   { default: true },
    enableJumps:    { default: false },
  },

  /*******************************************************************
   * Lifecycle
   */

  init: function () {
    this.system = this.el.sceneEl.systems.physics;
    this.system.addComponent(this);

    const el = this.el,
        data = this.data,
        position = (new CANNON.Vec3()).copy(el.object3D.getWorldPosition(new THREE.Vector3()));

    this.body = new CANNON.Body({
      material: this.system.getMaterial('staticMaterial'),
      position: position,
      mass: data.mass,
      linearDamping: data.linearDamping,
      fixedRotation: true
    });
    this.body.addShape(
      new CANNON.Sphere(data.radius),
      new CANNON.Vec3(0, data.radius, 0)
    );

    this.body.el = this.el;
    this.el.body = this.body;
    this.system.addBody(this.body);

    if (el.hasAttribute('wasd-controls')) {
      console.warn('[kinema-body] Not compatible with wasd-controls, use movement-controls.');
    }
  },

  remove: function () {
    this.system.removeBody(this.body);
    this.system.removeComponent(this);
    delete this.el.body;
  },

  /*******************************************************************
   * Update
   */

  /**
   * Checks CANNON.World for collisions and attempts to apply them to the
   * element automatically, in a player-friendly way.
   *
   * There's extra logic for horizontal surfaces here. The basic requirements:
   * (1) Only apply gravity when not in contact with _any_ horizontal surface.
   * (2) When moving, project the velocity against exactly one ground surface.
   *     If in contact with two ground surfaces (e.g. ground + ramp), choose
   *     the one that collides with current velocity, if any.
   */
  beforeStep: function (t, dt) {
    if (!dt) return;

    const el = this.el;
    const data = this.data
    const body = this.body;

    if (!data.enableJumps) body.velocity.set(0, 0, 0);
    body.position.copy(el.getAttribute('position'));
  },

  step: (function () {
    const velocity = new THREE.Vector3(),
        normalizedVelocity = new THREE.Vector3(),
        currentSurfaceNormal = new THREE.Vector3(),
        groundNormal = new THREE.Vector3();

    return function (t, dt) {
      if (!dt) return;

      let body = this.body,
          data = this.data,
          didCollide = false,
          height, groundHeight = -Infinity,
          groundBody,
          contacts = this.system.getContacts();

      dt = Math.min(dt, this.system.data.maxInterval * 1000);

      groundNormal.set(0, 0, 0);
      velocity.copy(this.el.getAttribute('velocity'));
      body.velocity.copy(velocity);

      for (var i = 0, contact; contact = contacts[i]; i++) {
        // 1. Find any collisions involving this element. Get the contact
        // normal, and make sure it's oriented _out_ of the other object and
        // enabled (body.collisionReponse is true for both bodies)
        if (!contact.enabled) { continue; }
        if (body.id === contact.bi.id) {
          contact.ni.negate(currentSurfaceNormal);
        } else if (body.id === contact.bj.id) {
          currentSurfaceNormal.copy(contact.ni);
        } else {
          continue;
        }

        didCollide = body.velocity.dot(currentSurfaceNormal) < -EPS;
        if (didCollide && currentSurfaceNormal.y <= 0.5) {
          // 2. If current trajectory attempts to move _through_ another
          // object, project the velocity against the collision plane to
          // prevent passing through.
          velocity.projectOnPlane(currentSurfaceNormal);
        } else if (currentSurfaceNormal.y > 0.5) {
          // 3. If in contact with something roughly horizontal (+/- 45º) then
          // consider that the current ground. Only the highest qualifying
          // ground is retained.
          height = body.id === contact.bi.id
            ? Math.abs(contact.rj.y + contact.bj.position.y)
            : Math.abs(contact.ri.y + contact.bi.position.y);
          if (height > groundHeight) {
            groundHeight = height;
            groundNormal.copy(currentSurfaceNormal);
            groundBody = body.id === contact.bi.id ? contact.bj : contact.bi;
          }
        }
      }

      normalizedVelocity.copy(velocity).normalize();
      if (groundBody && (!data.enableJumps || normalizedVelocity.y < 0.5)) {
        if (!data.enableSlopes) {
          groundNormal.set(0, 1, 0);
        } else if (groundNormal.y < 1 - EPS) {
          groundNormal.copy(this.raycastToGround(groundBody, groundNormal));
        }

        // 4. Project trajectory onto the top-most ground object, unless
        // trajectory is > 45º.
        velocity.projectOnPlane(groundNormal);

      } else if (this.system.driver.world) {
        // 5. If not in contact with anything horizontal, apply world gravity.
        // TODO - Why is the 4x scalar necessary.
        // NOTE: Does not work if physics runs on a worker.
        velocity.add(this.system.driver.world.gravity.scale(dt * 4.0 / 1000));
      }

      body.velocity.copy(velocity);
      this.el.setAttribute('velocity', body.velocity);
      this.el.setAttribute('position', body.position);
    };
  }()),

  /**
   * When walking on complex surfaces (trimeshes, borders between two shapes),
   * the collision normals returned for the player sphere can be very
   * inconsistent. To address this, raycast straight down, find the collision
   * normal, and return whichever normal is more vertical.
   * @param  {CANNON.Body} groundBody
   * @param  {CANNON.Vec3} groundNormal
   * @return {CANNON.Vec3}
   */
  raycastToGround: function (groundBody, groundNormal) {
    let ray,
        hitNormal,
        vFrom = this.body.position,
        vTo = this.body.position.clone();

    ray = new CANNON.Ray(vFrom, vTo);
    ray._updateDirection(); // TODO - Report bug.
    ray.intersectBody(groundBody);

    if (!ray.hasHit) return groundNormal;

    // Compare ABS, in case we're projecting against the inside of the face.
    hitNormal = ray.result.hitNormalWorld;
    return Math.abs(hitNormal.y) > Math.abs(groundNormal.y) ? hitNormal : groundNormal;
  }
});
    </script>
  <body>
    <a-scene physics="debug: true">

      <a-box static-body position="0 0 0" height="3" width="4" color="red"></a-box>
      <a-plane static-body position="0 0 0" rotation="-90 0 0" width="8" height="14" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>

      <a-entity kinema-body="radius: 0.8" movement-controls="fly: false" position="0 0 4" look-controls>
        <a-entity camera position="0 1.6 0" ></a-entity>
      </a-entity>

    </a-scene>
  </body>
</html>

【讨论】:

  • 这段代码运行正常,相机碰撞到静态物体,但是有一个bug,进入编辑器模式(Ctrl+alt+i)时相机掉下来,永远往下掉。进入编辑器模式时似乎没有检测到静态正文。
【解决方案2】:

这取决于您计划支持哪些设备,以及您如何允许用户 导航您的场景。对于大多数 VR 体验,请遵循最佳实践,并且仅 与用户的动作成比例地移动相机。如果用户步骤 在房间尺度空间中前进并且相机被“阻挡”,这是一个非常糟糕的 经验。对于大多数 VR 应用程序,最好使用 locomotion teleportation, 设计您的场景以避开障碍物,或探索更多创意 在世界范围内移动用户的方式。

适用于使用游戏手柄或 WASD 控件的非 VR 桌面体验,或适用于 VR 场景 如果摄像头在车内,您可以添加一个 physics engine 到 防止穿过障碍物。

^我会尽快将其添加到 A-Frame 常见问题解答或文档中。这已添加到A-Frame FAQ。这是example using checkpointsexample using a physics engine

【讨论】:

  • 我将它用于桌面应用程序,因此我尝试使用物理引擎。我使用生成对象,例如盒子,但我似乎无法在物理引擎中加载 3d 模型。您有从资产加载 3d 模型的示例吗?
  • 或者有没有办法至少在物理场景中渲染任意网格?
  • 参见文档的Body Shapes 部分。您可以加载任意网格,但可能希望将它们模拟为盒子或外壳。默认情况下,它会尝试模拟完整的网格几何体,这并没有得到很好的支持。
  • 所以这似乎适用于 wasd 控制的运动,但帧动画仍然通过对象。有没有办法将物理引擎扩展到动画运动?
  • 还没有,没有。你需要通过物理引擎来管理你的动画,比如el.body.applyImpulse(...)。请参阅物理系统文档。这有点复杂。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 2016-01-23
  • 1970-01-01
  • 2011-05-27
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
相关资源
最近更新 更多