Newer
Older
import { paths } from "../systems/userinput/paths";
import { addMedia } from "../utils/media-utils";
import { ObjectContentOrigins } from "../object-types";
Kevin Lee
committed
* Spawns networked objects when grabbed or when a specified event is fired.
* @namespace network
* @component super-spawner
*/
AFRAME.registerComponent("super-spawner", {
schema: {
/**
* Source of the media asset the spawner will spawn when grabbed. This can be a gltf, video, or image, or a url that the reticiulm media API can resolve to a gltf, video, or image.
*/
src: { default: "" },
/**
* Whether to use the Reticulum media resolution API to interpret the src URL (e.g. find a video URL for Youtube videos.)
*/
resolve: { default: false },
template: { default: "" },
/**
* Spawn the object at a custom position, rather than at the center of the spanwer.
*/
spawnPosition: { type: "vec3" },
/**
* Spawn the object with a custom orientation, rather than copying that of the spawner.
*/
useCustomSpawnRotation: { default: false },
spawnRotation: { type: "vec4" },
/**
* Spawn the object with a custom scale, rather than copying that of the spawner.
*/
useCustomSpawnScale: { default: false },
spawnScale: { type: "vec3" },
/**
* The events to emit for programmatically grabbing and releasing objects
*/
Kevin Lee
committed
grabEvents: { default: ["cursor-grab", "primary_hand_grab"] },
releaseEvents: { default: ["cursor-release", "primary_hand_release"] },
/**
* The spawner will become invisible and ungrabbable for this ammount of time after being grabbed. This can prevent rapidly spawning objects.
*/
spawnCooldown: { default: 1 },
/**
* Center the spawned object on the hand that grabbed it after it finishes loading. By default the object will be grabbed relative to where the spawner was grabbed
*/
Kevin Lee
committed
centerSpawnedObject: { default: false },
/**
* Optional event to listen for to spawn an object on the preferred superHand
Kevin Lee
committed
*/
spawnEvent: { type: "string" },
/**
* The superHand to use if an object is spawned via spawnEvent
Kevin Lee
committed
*/
superHand: { type: "selector" },
/**
* The cursor superHand to use if an object is spawned via spawnEvent
*/
cursorSuperHand: { type: "selector" }
init() {
this.heldEntities = new Map();
this.cooldownTimeout = null;
this.onGrabEnd = this.onGrabEnd.bind(this);
Kevin Lee
committed
this.onSpawnEvent = this.onSpawnEvent.bind(this);
this.sceneEl = document.querySelector("a-scene");
this.el.setAttribute("hoverable-visuals", { cursorController: "#cursor-controller", enableSweepingEffect: false });
this.el.addEventListener("grab-start", this.onGrabStart);
this.el.addEventListener("grab-end", this.onGrabEnd);
Kevin Lee
committed
if (this.data.spawnEvent) {
this.sceneEl.addEventListener(this.data.spawnEvent, this.onSpawnEvent);
}
this.el.removeEventListener("grab-start", this.onGrabStart);
this.el.removeEventListener("grab-end", this.onGrabEnd);
Kevin Lee
committed
if (this.data.spawnEvent) {
this.sceneEl.removeEventListener(this.data.spawnEvent, this.onSpawnEvent);
}
if (this.cooldownTimeout) {
clearTimeout(this.cooldownTimeout);
this.cooldownTimeout = null;
this.el.setAttribute("visible", true);
this.el.classList.add("interactable");
}
remove() {
this.heldEntities.clear();
const userinput = AFRAME.scenes[0].systems.userinput;
const leftPose = userinput.get(paths.actions.leftHand.pose);
const rightPose = userinput.get(paths.actions.rightHand.pose);
const controllerCount = leftPose && rightPose ? 2 : leftPose || rightPose ? 1 : 0;
const using6DOF = controllerCount > 1 && this.el.sceneEl.is("vr-mode");
const hand = using6DOF ? this.data.superHand : this.data.cursorSuperHand;
Kevin Lee
committed
if (this.cooldownTimeout || !hand) {
return;
}
Kevin Lee
committed
const entity = addMedia(this.data.src, this.data.template, ObjectContentOrigins.SPAWNER, this.data.resolve).entity;
Kevin Lee
committed
hand.object3D.getWorldPosition(entity.object3D.position);
hand.object3D.getWorldQuaternion(entity.object3D.quaternion);
if (this.data.useCustomSpawnScale) {
entity.object3D.scale.copy(this.data.spawnScale);
}
Kevin Lee
committed
this.activateCooldown();
await waitForEvent("body-loaded", entity);
Kevin Lee
committed
hand.object3D.getWorldPosition(entity.object3D.position);
Kevin Lee
committed
if (!using6DOF) {
for (let i = 0; i < this.data.grabEvents.length; i++) {
hand.emit(this.data.grabEvents[i], { targetEntity: entity });
Kevin Lee
committed
return;
}
// This tells super-hands we are handling this grab. The user is now "grabbing" the spawner
e.preventDefault();
const hand = e.detail.hand;
const thisGrabId = nextGrabId++;
this.heldEntities.set(hand, thisGrabId);
const entity = addMedia(this.data.src, this.data.template, ObjectContentOrigins.SPAWNER, this.data.resolve).entity;
entity.object3D.position.copy(
this.data.useCustomSpawnPosition ? this.data.spawnPosition : this.el.object3D.position
);
entity.object3D.rotation.copy(
this.data.useCustomSpawnRotation ? this.data.spawnRotation : this.el.object3D.rotation
);
entity.object3D.scale.copy(this.data.useCustomSpawnScale ? this.data.spawnScale : this.el.object3D.scale);
await waitForEvent("body-loaded", entity);
// If we are still holding the spawner with the hand that grabbed to create this entity, release the spawner and grab the entity
if (this.heldEntities.get(hand) === thisGrabId) {
if (this.data.centerSpawnedObject) {
entity.body.position.copy(hand.object3D.position);
}
for (let i = 0; i < this.data.grabEvents.length; i++) {
hand.emit(this.data.releaseEvents[i]);
hand.emit(this.data.grabEvents[i], { targetEntity: entity });
onGrabEnd(e) {
this.heldEntities.delete(e.detail.hand);
if (this.data.spawnCooldown > 0) {
this.el.setAttribute("visible", false);
this.el.classList.remove("interactable");
this.el.setAttribute("visible", true);
this.el.classList.add("interactable");
}, this.data.spawnCooldown * 1000);
}