Newer
Older
const PI = Math.PI;
const HALF_PI = PI / 2;
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;
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.targetEl.getAttribute("visible") === false) {
this.target.scale.setScalar(0.01); // To avoid "pop" of gigantic button first time
return;
}
if (!this.halfExtents || this.mesh !== this.el.getObject3D("mesh") || this.shape !== this.el.components.shape) {
this.shape = this.el.components.shape;
if (this.el.components.shape) {
this.shape = this.el.components.shape;
const box = getBox(this.el, this.mesh);
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;
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) {
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);
this.wasVisible = isVisible;