Skip to content

Commit

Permalink
Merge pull request Hubs-Foundation#6287 from mozilla/bitecs-object-li…
Browse files Browse the repository at this point in the history
…st-pinning

bitECS New loader pinning updates
  • Loading branch information
keianhzo authored Nov 29, 2023
2 parents 93116bd + 36a06e5 commit 17eac2a
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 153 deletions.
1 change: 0 additions & 1 deletion src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,5 @@ export const MediaLink = defineComponent({
MediaLink.src[$isStringType] = true;
export const ObjectMenuTransform = defineComponent({
targetObjectRef: Types.eid,
prevObjectRef: Types.eid,
flags: Types.ui8
});
4 changes: 1 addition & 3 deletions src/bit-systems/object-menu-transform-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function transformMenu(world: HubsWorld, menu: EntityID) {

// Calculate the menu offset based on visible elements
const center = (ObjectMenuTransform.flags[menu] & ObjectMenuTransformFlags.Center) !== 0 ? true : false;
if (center && ObjectMenuTransform.targetObjectRef[menu] !== ObjectMenuTransform.prevObjectRef[menu]) {
if (center) {
getAABB(menuObj, aabb);
aabb.getCenter(tmpVec1);
getAABB(menuObj, aabb, true);
Expand Down Expand Up @@ -100,8 +100,6 @@ function transformMenu(world: HubsWorld, menu: EntityID) {

setMatrixWorld(menuObj, tmpMat4);
}

ObjectMenuTransform.prevObjectRef[menu] = ObjectMenuTransform.targetObjectRef[menu];
}

const menuQuery = defineQuery([ObjectMenuTransform]);
Expand Down
32 changes: 22 additions & 10 deletions src/bit-systems/object-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Deleting,
Deletable
} from "../bit-components";
import { anyEntityWith, findAncestorWithComponents } from "../utils/bit-utils";
import { anyEntityWith, findAncestorWithComponent, findAncestorWithComponents } from "../utils/bit-utils";
import { createNetworkedEntity } from "../utils/create-networked-entity";
import HubChannel from "../utils/hub-channel";
import type { EntityID } from "../utils/networking-types";
Expand Down Expand Up @@ -220,7 +220,8 @@ function handleHeldExit(world: HubsWorld, eid: EntityID, menuEid: EntityID) {
}

function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) {
const target = ObjectMenu.targetRef[menu];
if (!APP.hubChannel) return;
let target = ObjectMenu.targetRef[menu];
const visible = !!(target && frozen) && (ObjectMenu.flags[menu] & ObjectMenuFlags.Visible) !== 0;

// TODO We are handling menus visibility in a similar way for all the object menus, we
Expand All @@ -235,16 +236,27 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) {
const obj = world.eid2obj.get(menu)!;
obj.visible = visible;

// We need the media loader entity as that's the entity that's actually pinned and we
// need to check its state to show/hide certain buttons
// TODO At this moment all objects that have an object menu have been loaded by a media loader
// but this might not be true in the future if we allow adding object menus to arbitrary objects.
const mediaLoader = findAncestorWithComponent(world, MediaContentBounds, target);
target = mediaLoader ? mediaLoader : target;

const canISpawnMove = APP.hubChannel.can("spawn_and_move_media");
const canIPin = !!(target && canPin(APP.hubChannel, target));
const isEntityPinned = isPinned(target);

// Parent visibility doesn't block raycasting, so we must set each button to be invisible
// TODO: Ensure that children of invisible entities aren't raycastable
world.eid2obj.get(ObjectMenu.unpinButtonRef[menu])!.visible = visible && isPinned(target);
world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible =
visible && !isPinned(target) && canPin(APP.hubChannel!, target);
world.eid2obj.get(ObjectMenu.removeButtonRef[menu])!.visible =
visible && !isPinned(target) && APP.hubChannel!.can("spawn_and_move_media");
world.eid2obj.get(ObjectMenu.cloneButtonRef[menu])!.visible = visible && APP.hubChannel!.can("spawn_and_move_media");
world.eid2obj.get(ObjectMenu.rotateButtonRef[menu])!.visible = visible && APP.hubChannel!.can("spawn_and_move_media");
world.eid2obj.get(ObjectMenu.scaleButtonRef[menu])!.visible = visible && APP.hubChannel!.can("spawn_and_move_media");
world.eid2obj.get(ObjectMenu.unpinButtonRef[menu])!.visible = visible && isEntityPinned && canIPin;
world.eid2obj.get(ObjectMenu.pinButtonRef[menu])!.visible = visible && !isEntityPinned && canIPin;
world.eid2obj.get(ObjectMenu.removeButtonRef[menu])!.visible = visible && !isEntityPinned && canISpawnMove;
world.eid2obj.get(ObjectMenu.cloneButtonRef[menu])!.visible = visible && canISpawnMove;
world.eid2obj.get(ObjectMenu.rotateButtonRef[menu])!.visible =
visible && (!isEntityPinned || canIPin) && canISpawnMove;
world.eid2obj.get(ObjectMenu.scaleButtonRef[menu])!.visible =
visible && (!isEntityPinned || canIPin) && canISpawnMove;
world.eid2obj.get(ObjectMenu.openLinkButtonRef[menu])!.visible = visible;

// This is a hacky way of giving a chance to the object-menu-transform system to center the menu based on the
Expand Down
16 changes: 15 additions & 1 deletion src/bit-systems/pdf-menu-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EntityStateDirty,
HoveredRemoteRight,
Interacted,
MediaContentBounds,
MediaPDF,
NetworkedPDF,
ObjectMenuTransform,
Expand All @@ -15,6 +16,7 @@ import type { EntityID } from "../utils/networking-types";
import { takeOwnership } from "../utils/take-ownership";
import { PDFResourcesMap } from "./pdf-system";
import { ObjectMenuTransformFlags } from "../inflators/object-menu-transform";
import { canPin } from "../utils/bit-pinning-helper";

function clicked(world: HubsWorld, eid: EntityID) {
return hasComponent(world, Interacted, eid);
Expand Down Expand Up @@ -88,11 +90,23 @@ function flushToObject3Ds(world: HubsWorld, menu: EntityID, frozen: boolean) {
ObjectMenuTransform.flags[menu] &= ~ObjectMenuTransformFlags.Enabled;
}

// The media loader entity is the entity that's is actually pinned and decides
// the pinnable state of the pdf component so we need to check the media loader entity pin
// state to show/hide certain buttons. The media loader component is not present anymore after
// the media has been loaded but it will always have a MediaContentBounds.
// TODO We should use something more meaningful than MediaContentBounds for the media loader root entity
// or rename it to something like MediaRoot.
let canIPin = false;
const mediaLoader = findAncestorWithComponent(world, MediaContentBounds, target);
if (mediaLoader && canPin(APP.hubChannel!, mediaLoader)) {
canIPin = true;
}

[PDFMenu.prevButtonRef[menu], PDFMenu.nextButtonRef[menu]].forEach(buttonRef => {
const buttonObj = world.eid2obj.get(buttonRef)!;
// Parent visibility doesn't block raycasting, so we must set each button to be invisible
// TODO: Ensure that children of invisible entities aren't raycastable
buttonObj.visible = visible;
buttonObj.visible = visible && canIPin;
});

if (target) {
Expand Down
123 changes: 72 additions & 51 deletions src/bit-systems/video-menu-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
Held,
HeldRemoteRight,
HoveredRemoteRight,
MediaContentBounds,
MediaVideo,
MediaVideoData,
NetworkedVideo,
ObjectMenuTransform,
Owned,
VideoMenu
} from "../bit-components";
import { timeFmt } from "../components/media-video";
Expand All @@ -21,11 +23,11 @@ import { paths } from "../systems/userinput/paths";
import { animate } from "../utils/animate";
import { coroutine } from "../utils/coroutine";
import { easeOutQuadratic } from "../utils/easing";
import { isFacingCamera } from "../utils/three-utils";
import { Emitter2Audio } from "./audio-emitter-system";
import { EntityID } from "../utils/networking-types";
import { findAncestorWithComponent, hasAnyComponent } from "../utils/bit-utils";
import { ObjectMenuTransformFlags } from "../inflators/object-menu-transform";
import { isPinned } from "./networking";

const videoMenuQuery = defineQuery([VideoMenu]);
const hoveredQuery = defineQuery([HoveredRemoteRight]);
Expand Down Expand Up @@ -119,70 +121,87 @@ export function videoMenuSystem(world: HubsWorld, userinput: any, sceneIsFrozen:
videoMenuQuery(world).forEach(function (eid) {
const videoEid = VideoMenu.videoRef[eid];
if (!videoEid) return;
const menuObj = world.eid2obj.get(eid)!;
const video = MediaVideoData.get(videoEid)!;
const togglePlayVideo = userinput.get(paths.actions.cursor.right.togglePlayVideo);
if (togglePlayVideo) {
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);
}

const playIndicatorObj = world.eid2obj.get(VideoMenu.playIndicatorRef[eid])!;
const pauseIndicatorObj = world.eid2obj.get(VideoMenu.pauseIndicatorRef[eid])!;

const audioEid = Emitter2Audio.get(videoEid)!;
if (video.paused) {
video.play();
APP.isAudioPaused.delete(audioEid);
playIndicatorObj.visible = true;
pauseIndicatorObj.visible = false;
rightMenuIndicatorCoroutine = coroutine(animateIndicator(world, VideoMenu.playIndicatorRef[eid]));
} else {
video.pause();
APP.isAudioPaused.add(audioEid);
playIndicatorObj.visible = false;
pauseIndicatorObj.visible = true;
rightMenuIndicatorCoroutine = coroutine(animateIndicator(world, VideoMenu.pauseIndicatorRef[eid]));
}
}
const ratio = MediaVideo.ratio[videoEid];

const videoIsFacingCamera = isFacingCamera(world.eid2obj.get(videoEid)!);
const yRot = videoIsFacingCamera ? 0 : Math.PI;
if (menuObj.rotation.y !== yRot) {
menuObj.rotation.y = yRot;
menuObj.matrixNeedsUpdate = true;
// The media loader entity is the entity that's is actually pinned and decides
// the pinnable state of the video component so we need to check the media loader entity pin
// state to show/hide certain buttons. The media loader component is not present anymore after
// the media has been loaded but it will always have a MediaContentBounds.
// TODO We should use something more meaningful than MediaContentBounds for the media loader root entity
// or rename it to something like MediaRoot.
let canIPin = false;
const mediaRoot = findAncestorWithComponent(world, MediaContentBounds, videoEid)!;
if (isPinned(mediaRoot) && !hasComponent(world, Owned, mediaRoot)) {
canIPin = true;
}

const headObj = world.eid2obj.get(VideoMenu.headRef[eid])!;
if (hasComponent(world, HeldRemoteRight, VideoMenu.trackRef[eid])) {
const trackObj = world.eid2obj.get(VideoMenu.trackRef[eid])!;
intersectInThePlaneOf(trackObj, userinput.get(paths.actions.cursor.right.pose), intersectionPoint);
if (intersectionPoint) {
const newPosition = headObj.parent!.worldToLocal(intersectionPoint);
video.currentTime =
mapLinear(clamp(newPosition.x, -sliderHalfWidth, sliderHalfWidth), -sliderHalfWidth, sliderHalfWidth, 0, 1) *
video.duration;
const slider = world.eid2obj.get(VideoMenu.sliderRef[eid])!;
const playIndicatorObj = world.eid2obj.get(VideoMenu.playIndicatorRef[eid])!;
const pauseIndicatorObj = world.eid2obj.get(VideoMenu.pauseIndicatorRef[eid])!;
if (canIPin) {
const togglePlayVideo = userinput.get(paths.actions.cursor.right.togglePlayVideo);
if (togglePlayVideo) {
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);
}

const audioEid = Emitter2Audio.get(videoEid)!;
if (video.paused) {
video.play();
APP.isAudioPaused.delete(audioEid);
playIndicatorObj.visible = true;
pauseIndicatorObj.visible = false;
rightMenuIndicatorCoroutine = coroutine(animateIndicator(world, VideoMenu.playIndicatorRef[eid]));
} else {
video.pause();
APP.isAudioPaused.add(audioEid);
playIndicatorObj.visible = false;
pauseIndicatorObj.visible = true;
rightMenuIndicatorCoroutine = coroutine(animateIndicator(world, VideoMenu.pauseIndicatorRef[eid]));
}
}
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);

if (hasComponent(world, HeldRemoteRight, VideoMenu.trackRef[eid])) {
const trackObj = world.eid2obj.get(VideoMenu.trackRef[eid])!;
intersectInThePlaneOf(trackObj, userinput.get(paths.actions.cursor.right.pose), intersectionPoint);
if (intersectionPoint) {
const newPosition = headObj.parent!.worldToLocal(intersectionPoint);
video.currentTime =
mapLinear(
clamp(newPosition.x, -sliderHalfWidth, sliderHalfWidth),
-sliderHalfWidth,
sliderHalfWidth,
0,
1
) * video.duration;
}
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);
}
}
headObj.visible = true;
headObj.position.x = mapLinear(video.currentTime, 0, video.duration, -sliderHalfWidth, sliderHalfWidth);
headObj.matrixNeedsUpdate = true;

slider.visible = true;
slider.position.setY(-(ratio / 2) + 0.025);
slider.matrixNeedsUpdate = true;
} else {
headObj.visible = false;
slider.visible = false;
playIndicatorObj.visible = false;
pauseIndicatorObj.visible = false;
}
headObj.position.x = mapLinear(video.currentTime, 0, video.duration, -sliderHalfWidth, sliderHalfWidth);
headObj.matrixNeedsUpdate = true;

const ratio = MediaVideo.ratio[videoEid];

const timeLabel = world.eid2obj.get(VideoMenu.timeLabelRef[eid])! as TroikaText;
timeLabel.text = `${timeFmt(video.currentTime)} / ${timeFmt(video.duration)}`;
timeLabel.position.setY(ratio / 2 - 0.02);
timeLabel.matrixNeedsUpdate = true;

const slider = world.eid2obj.get(VideoMenu.sliderRef[eid])!;
slider.position.setY(-(ratio / 2) + 0.025);
slider.matrixNeedsUpdate = true;

if (rightMenuIndicatorCoroutine && rightMenuIndicatorCoroutine().done) {
rightMenuIndicatorCoroutine = null;
}
Expand All @@ -195,6 +214,7 @@ const START_SCALE = new Vector3().setScalar(0.05);
const END_SCALE = new Vector3().setScalar(0.25);
function* animateIndicator(world: HubsWorld, eid: number) {
const obj = world.eid2obj.get(eid)!;
obj.visible = true;
yield* animate({
properties: [
[START_SCALE, END_SCALE],
Expand All @@ -208,4 +228,5 @@ function* animateIndicator(world: HubsWorld, eid: number) {
((obj as Mesh).material as MeshBasicMaterial).opacity = opacity;
}
});
obj.visible = false;
}
3 changes: 0 additions & 3 deletions src/load-media-on-paste-or-drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ export async function spawnFromFileList(files: FileList) {
.then(function (response: UploadResponse) {
const srcUrl = new URL(response.origin);
srcUrl.searchParams.set("token", response.meta.access_token);
window.APP.store.update({
uploadPromotionTokens: [{ fileId: response.file_id, promotionToken: response.meta.promotion_token }]
});
return {
src: srcUrl.href,
recenter: true,
Expand Down
Loading

0 comments on commit 17eac2a

Please sign in to comment.