Предотвращение прохождения камерой «сквозь» объект в кадре

Допустим, у меня есть модель цилиндра, которую я загружаю в свою веб-сцену. Как я могу сделать объект твердым? т.е. пользователь (вид с камеры) не может занимать какую-либо позицию внутри объекта или перемещаться «сквозь» объект. Как я могу этого добиться?

<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>

person bear    schedule 17.04.2017    source источник
comment
Я бы рекомендовал удалить правки из этого вопроса, чтобы он оставался целенаправленным и актуальным для будущих читателей. Чтобы получить помощь по использованию aframe-physics-system, не стесняйтесь открывать вопрос на GitHub. :)   -  person Don McCurdy    schedule 19.04.2017


Ответы (2)


Это зависит от того, какие устройства вы планируете поддерживать и как вы позволяете пользователям перемещаться по вашей сцене. В большинстве случаев в виртуальной реальности следуйте рекомендациям и перемещайте камеру только пропорционально движениям пользователя. Если пользователь делает шаг вперед в масштабе комнаты, а камера «заблокирована», это очень плохой опыт. Для большинства приложений виртуальной реальности лучше выполнять передвижение с помощью телепортации. в сторону или исследуйте более творческие способы перемещения пользователей по миру.

Для настольных игр без виртуальной реальности с геймпадом или элементами управления WASD или для сцен виртуальной реальности, где камера находится внутри автомобиля, вы можете добавить физический движок для предотвращения движения через препятствия.

^Я постараюсь добавить это в FAQ или документацию A-Frame в ближайшее время. Это было добавлено в ссылку Часто задаваемые вопросы по A-Frame. Вот пример использования контрольных точек и пример использования физического движка.

person Don McCurdy    schedule 18.04.2017
comment
Я использую его для настольного приложения, поэтому я попытался использовать физический движок. Я работаю с генеративными объектами, такими как коробка, но, похоже, я не могу загрузить 3D-модель в физический движок. У вас есть пример, который загружает 3D-модель из активов? - person bear; 18.04.2017
comment
или есть способ, по крайней мере, отобразить произвольную сетку внутри физической сцены? - person bear; 18.04.2017
comment
См. раздел документации Формы тела. Вы можете загружать произвольные сетки, но, вероятно, захотите смоделировать их как коробку или корпус. По умолчанию он пытается имитировать полную геометрию сетки, что плохо поддерживается. - person Don McCurdy; 18.04.2017
comment
так что это, кажется, работает для движения, контролируемого wasd, но анимация кадра все еще проходит через объекты. Есть ли способ расширить физический движок для анимированного движения? - person bear; 19.04.2017
comment
Еще нет, нет. Вам нужно будет управлять своей анимацией через физический движок, делая такие вещи, как el.body.applyImpulse(...). См. документацию по физической системе. Это немного сложнее. - person Don McCurdy; 19.04.2017
comment
Не могли бы вы привести пример для этого? Я просмотрел документацию, и мне непонятно, из какого элемента в примере вызывается applyImpulse. Когда я запускаю свой пример, el.body кажется неопределенным. Есть какие-нибудь намеки на то, как это решить? - person bear; 19.04.2017
comment
Я думаю, что мы вышли за рамки комментариев о переполнении стека, не могли бы вы открыть проблему в репо? :) - person Don McCurdy; 19.04.2017
comment
ОК, открыл новый вопрос stackoverflow.com/questions/43503796/ и удалить изменения. - person bear; 19.04.2017

Поскольку я приземлился здесь в поисках более простого ответа (не имея дело с пользовательскими, а стандартными объектами AFrame), возможно, это не то, что вам нужно. Но если вы просто просите «предотвратить прохождение камеры через объекты AFrame», возможно, это вам поможет.

В приведенном ниже примере я использую Систему физики AFrame для управления физикой. Я скопировал компонент kinematic-body из AFrame Extras с именем 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>
person jgbarah    schedule 27.12.2018
comment
Этот код работает правильно, камера сталкивается со статическими телами, но есть баг, что камера падает при входе в режим редактора (Ctrl+alt+i) и уходит вниз навсегда. Кажется, что не обнаруживает статическое тело при входе в режим редактора. - person Iker; 21.04.2020