aframe-extras
Version:
Add-ons and examples for A-Frame VR.
213 lines (188 loc) • 6.11 kB
JavaScript
/* global AFRAME, THREE */
import nipplejs from "nipplejs";
AFRAME.registerComponent("nipple-controls", {
schema: {
enabled: { default: true },
mode: { default: "dynamic", oneOf: ["static", "semi", "dynamic"] },
rotationSensitivity: { default: 1.0 },
moveJoystickEnabled: { default: true },
lookJoystickEnabled: { default: true },
sideMargin: { default: "30px" },
bottomMargin: { default: "70px" },
moveJoystickPosition: { default: "left", oneOf: ["left", "right"] },
lookJoystickPosition: { default: "right", oneOf: ["left", "right"] },
},
init() {
this.dVelocity = new THREE.Vector3();
this.lookVector = new THREE.Vector2();
const lookControls = this.el.querySelector("[look-controls]").components["look-controls"];
this.pitchObject = lookControls.pitchObject;
this.yawObject = lookControls.yawObject;
this.rigRotation = this.el.object3D.rotation;
this.moveData = undefined;
this.lookData = undefined;
this.moving = false;
this.rotating = false;
},
update(oldData) {
if (
this.data.moveJoystickPosition !== oldData.moveJoystickPosition ||
this.data.sideMargin !== oldData.sideMargin ||
this.data.bottomMargin !== oldData.bottomMargin ||
this.data.mode !== oldData.mode
) {
this.removeMoveJoystick();
}
if (
this.data.lookJoystickPosition !== oldData.lookJoystickPosition ||
this.data.sideMargin !== oldData.sideMargin ||
this.data.bottomMargin !== oldData.bottomMargin ||
this.data.mode !== oldData.mode
) {
this.removeLookJoystick();
}
if (this.data.enabled && this.data.moveJoystickEnabled) {
this.createMoveJoystick();
} else {
this.removeMoveJoystick();
}
if (this.data.enabled && this.data.lookJoystickEnabled) {
this.createLookJoystick();
} else {
this.removeLookJoystick();
}
},
pause() {
this.moving = false;
this.rotating = false;
},
remove() {
this.removeMoveJoystick();
this.removeLookJoystick();
},
isVelocityActive() {
return this.data.enabled && this.moving;
},
getVelocityDelta() {
this.dVelocity.set(0, 0, 0);
if (this.isVelocityActive()) {
const force = this.moveData.force < 1 ? this.moveData.force : 1;
const angle = this.moveData.angle.radian;
const x = Math.cos(angle) * force;
const z = -Math.sin(angle) * force;
this.dVelocity.set(x, 0, z);
}
return this.dVelocity; // We don't do a clone() here, the Vector3 will be modified by the calling code but that's fine.
},
isRotationActive() {
return this.data.enabled && this.rotating;
},
updateRotation(dt) {
if (!this.isRotationActive()) return;
const force = this.lookData.force < 1 ? this.lookData.force : 1;
const angle = this.lookData.angle.radian;
const lookVector = this.lookVector;
lookVector.x = Math.cos(angle) * force;
lookVector.y = Math.sin(angle) * force;
lookVector.multiplyScalar((this.data.rotationSensitivity * dt) / 1000);
this.yawObject.rotation.y -= lookVector.x;
let x = this.pitchObject.rotation.x + lookVector.y;
x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, x));
this.pitchObject.rotation.x = x;
},
tick: function (t, dt) {
this.updateRotation(dt);
},
initLeftZone() {
const leftZone = document.createElement("div");
leftZone.setAttribute("id", "joystickLeftZone");
leftZone.setAttribute(
"style",
`position:absolute;${this.data.moveJoystickPosition}:${this.data.sideMargin};bottom:${this.data.bottomMargin};z-index:1`
);
this.el.sceneEl.appendChild(leftZone);
this.leftZone = leftZone;
},
initRightZone() {
const rightZone = document.createElement("div");
rightZone.setAttribute("id", "joystickRightZone");
rightZone.setAttribute(
"style",
`position:absolute;${this.data.lookJoystickPosition}:${this.data.sideMargin};bottom:${this.data.bottomMargin};z-index:1`
);
this.el.sceneEl.appendChild(rightZone);
this.rightZone = rightZone;
},
createMoveJoystick() {
if (this.moveJoystick) return;
this.initLeftZone();
const options = {
mode: this.data.mode,
zone: this.leftZone,
color: "white",
fadeTime: 0,
};
this.leftZone.style.width = "100px";
if (this.data.mode === "static") {
this.leftZone.style.height = "100px";
options.position = { left: "50%", bottom: "50%" };
} else {
this.leftZone.style.height = "400px";
}
this.moveJoystick = nipplejs.create(options);
this.moveJoystick.on("move", (evt, data) => {
this.moveData = data;
this.moving = true;
});
this.moveJoystick.on("end", (evt, data) => {
this.moving = false;
});
},
createLookJoystick() {
if (this.lookJoystick) return;
this.initRightZone();
const options = {
mode: this.data.mode,
zone: this.rightZone,
color: "white",
fadeTime: 0,
};
this.rightZone.style.width = "100px";
if (this.data.mode === "static") {
this.rightZone.style.height = "100px";
options.position = { left: "50%", bottom: "50%" };
} else {
this.rightZone.style.height = "400px";
}
this.lookJoystick = nipplejs.create(options);
this.lookJoystick.on("move", (evt, data) => {
this.lookData = data;
this.rotating = true;
});
this.lookJoystick.on("end", (evt, data) => {
this.rotating = false;
});
},
removeMoveJoystick() {
if (this.moveJoystick) {
this.moveJoystick.destroy();
this.moveJoystick = undefined;
}
this.moveData = undefined;
if (this.leftZone && this.leftZone.parentNode) {
this.leftZone.remove();
this.leftZone = undefined;
}
},
removeLookJoystick() {
if (this.lookJoystick) {
this.lookJoystick.destroy();
this.lookJoystick = undefined;
}
this.lookData = undefined;
if (this.rightZone && this.rightZone.parentNode) {
this.rightZone.remove();
this.rightZone = undefined;
}
},
});