Skip to content
Snippets Groups Projects
position-at-box-shape-border.js 4.31 KiB
Newer Older
joni's avatar
joni committed
import { getBox } from "../utils/auto-box-collider.js";

const PI = Math.PI;
const HALF_PI = PI / 2;
netpro2k's avatar
netpro2k committed
const THREE_HALF_PI = 3 * HALF_PI;
const right = new THREE.Vector3(1, 0, 0);
const forward = new THREE.Vector3(0, 0, 1);
const left = new THREE.Vector3(-1, 0, 0);
const back = new THREE.Vector3(0, 0, -1);
const dirs = {
  left: {
    dir: left,
    rotation: THREE_HALF_PI,
    halfExtent: "x"
  },
  right: {
    dir: right,
    rotation: HALF_PI,
    halfExtent: "x"
  },
  forward: {
    dir: forward,
    rotation: 0,
    halfExtent: "z"
  },
  back: {
    dir: back,
    rotation: PI,
    halfExtent: "z"
  }
};
const inverseHalfExtents = {
  x: "z",
  z: "x"
};

AFRAME.registerComponent("position-at-box-shape-border", {
  schema: {
    target: { type: "string" },
    dirs: { default: ["left", "right", "forward", "back"] }
  },

  init() {
    this.cam = this.el.sceneEl.camera.el.object3D;
netpro2k's avatar
netpro2k committed
    this.halfExtents = new THREE.Vector3();
  update() {
    this.dirs = this.data.dirs.map(d => dirs[d]);
  },

  tick: (function() {
    const camWorldPos = new THREE.Vector3();
    const targetPosition = new THREE.Vector3();
    const pointOnBoxFace = new THREE.Vector3();
    const tempParentWorldScale = new THREE.Vector3();

    return function() {
      if (!this.target) {
        this.targetEl = this.el.querySelector(this.data.target);
        this.target = this.targetEl.object3D;
        if (!this.target) return;
        if (this.targetEl.getAttribute("visible") === false) {
          this.target.scale.setScalar(0.01); // To avoid "pop" of gigantic button first time
          return;
        }
joni's avatar
joni committed
      if (!this.el.getObject3D("mesh")) {
        return;
      }
joni's avatar
joni committed
      if (!this.halfExtents || this.mesh !== this.el.getObject3D("mesh") || this.shape !== this.el.components.shape) {
joni's avatar
joni committed
        this.mesh = this.el.getObject3D("mesh");
        this.shape = this.el.components.shape;

joni's avatar
joni committed
        if (this.el.components.shape) {
          this.shape = this.el.components.shape;
netpro2k's avatar
netpro2k committed
          this.halfExtents.copy(this.shape.data.halfExtents);
joni's avatar
joni committed
        } else {
          const box = getBox(this.el, this.mesh);
joni's avatar
joni committed
          this.halfExtents = box.min
            .clone()
            .negate()
            .add(box.max)
            .multiplyScalar(0.51 / this.el.object3D.scale.x);
        }
      }
      this.cam.getWorldPosition(camWorldPos);

      let minSquareDistance = Infinity;
      let targetDir = this.dirs[0].dir;
      let targetHalfExtentStr = this.dirs[0].halfExtent;
      let targetHalfExtent = this.halfExtents[targetHalfExtentStr];
      let targetRotation = this.dirs[0].rotation;
      for (let i = 0; i < this.dirs.length; i++) {
        const dir = this.dirs[i].dir;
        const halfExtentStr = this.dirs[i].halfExtent;
        const halfExtent = this.halfExtents[halfExtentStr];
        pointOnBoxFace.copy(dir).multiplyScalar(halfExtent);
        this.el.object3D.localToWorld(pointOnBoxFace);
        const squareDistance = pointOnBoxFace.distanceToSquared(camWorldPos);
        if (squareDistance < minSquareDistance) {
          minSquareDistance = squareDistance;
          targetDir = dir;
          targetHalfExtent = halfExtent;
          targetRotation = this.dirs[i].rotation;
          targetHalfExtentStr = halfExtentStr;
joni's avatar
joni committed
      this.target.position.copy(targetPosition.copy(targetDir).multiplyScalar(targetHalfExtent));
      this.target.rotation.set(0, targetRotation, 0);

      tempParentWorldScale.setFromMatrixScale(this.target.parent.matrixWorld);

      const distance = Math.sqrt(minSquareDistance);
      const scale = this.halfExtents[inverseHalfExtents[targetHalfExtentStr]] * distance;
      const targetScale = Math.min(2.0, Math.max(0.5, scale * tempParentWorldScale.x));
      const finalScale = targetScale / tempParentWorldScale.x;
      const isVisible = this.targetEl.getAttribute("visible");
      if (isVisible && !this.wasVisible) {
Greg Fodor's avatar
Greg Fodor committed
        this.targetEl.removeAttribute("animation__show");

        this.targetEl.setAttribute("animation__show", {
          property: "scale",
          dur: 300,
          from: { x: finalScale * 0.8, y: finalScale * 0.8, z: finalScale * 0.8 },
          to: { x: finalScale, y: finalScale, z: finalScale },
          easing: "easeOutElastic"
        });
      } else {
        this.target.scale.setScalar(finalScale);
Greg Fodor's avatar
Greg Fodor committed
      }