Skip to content

Commit

Permalink
Merge pull request Hubs-Foundation#6394 from mozilla/bitecs-video-men…
Browse files Browse the repository at this point in the history
…u-updates

Update video menu layout
  • Loading branch information
keianhzo authored Dec 4, 2023
2 parents 860435e + 230af63 commit 8895b7b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 97 deletions.
90 changes: 38 additions & 52 deletions src/bit-systems/video-menu-system.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addComponent, defineQuery, enterQuery, entityExists, hasComponent, removeComponent } from "bitecs";
import { Mesh, MeshBasicMaterial, Object3D, Plane, Ray, Vector3 } from "three";
import { addComponent, defineQuery, entityExists, hasComponent, removeComponent } from "bitecs";
import { Object3D, Plane, Ray, Vector3 } from "three";
import { clamp, mapLinear } from "three/src/math/MathUtils";
import { Text as TroikaText } from "troika-three-text";
import { HubsWorld } from "../app";
Expand All @@ -9,6 +9,7 @@ import {
Held,
HeldRemoteRight,
HoveredRemoteRight,
Interacted,
MediaVideo,
MediaVideoData,
NetworkedVideo,
Expand All @@ -18,9 +19,6 @@ import {
import { timeFmt } from "../components/media-video";
import { takeOwnership } from "../utils/take-ownership";
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";
Expand Down Expand Up @@ -51,7 +49,6 @@ const intersectInThePlaneOf = (() => {
})();

type Job<T> = () => IteratorResult<undefined, T>;
let rightMenuIndicatorCoroutine: Job<void> | null = null;

function findVideoMenuTarget(world: HubsWorld, menu: EntityID, sceneIsFrozen: boolean) {
if (VideoMenu.videoRef[menu] && !entityExists(world, VideoMenu.videoRef[menu])) {
Expand Down Expand Up @@ -111,6 +108,31 @@ function flushToObject3Ds(world: HubsWorld, menu: EntityID, frozen: boolean) {
}
}

function clicked(world: HubsWorld, eid: EntityID) {
return hasComponent(world, Interacted, eid);
}

function handleClicks(world: HubsWorld, menu: EntityID) {
const videoEid = VideoMenu.videoRef[menu];
const video = MediaVideoData.get(videoEid)!;
const audioEid = Emitter2Audio.get(videoEid)!;
if (clicked(world, VideoMenu.playIndicatorRef[menu])) {
video.play();
APP.isAudioPaused.delete(audioEid);
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);
}
} else if (clicked(world, VideoMenu.pauseIndicatorRef[menu])) {
video.pause();
APP.isAudioPaused.add(audioEid);
if (hasComponent(world, NetworkedVideo, videoEid)) {
takeOwnership(world, videoEid);
addComponent(world, EntityStateDirty, videoEid);
}
}
}

let intersectionPoint = new Vector3();
export function videoMenuSystem(world: HubsWorld, userinput: any, sceneIsFrozen: boolean) {
const rightVideoMenu = videoMenuQuery(world)[0];
Expand All @@ -121,32 +143,19 @@ export function videoMenuSystem(world: HubsWorld, userinput: any, sceneIsFrozen:
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 playIndicatorObj = world.eid2obj.get(VideoMenu.playIndicatorRef[eid])!;
const pauseIndicatorObj = world.eid2obj.get(VideoMenu.pauseIndicatorRef[eid])!;
if (video.paused) {
playIndicatorObj.visible = true;
pauseIndicatorObj.visible = false;
} else {
playIndicatorObj.visible = false;
pauseIndicatorObj.visible = true;
}

handleClicks(world, eid);

const videoIsFacingCamera = isFacingCamera(world.eid2obj.get(videoEid)!);
const yRot = videoIsFacingCamera ? 0 : Math.PI;
if (menuObj.rotation.y !== yRot) {
Expand Down Expand Up @@ -182,30 +191,7 @@ export function videoMenuSystem(world: HubsWorld, userinput: any, sceneIsFrozen:
const slider = world.eid2obj.get(VideoMenu.sliderRef[eid])!;
slider.position.setY(-(ratio / 2) + 0.025);
slider.matrixNeedsUpdate = true;

if (rightMenuIndicatorCoroutine && rightMenuIndicatorCoroutine().done) {
rightMenuIndicatorCoroutine = null;
}
});

flushToObject3Ds(world, rightVideoMenu, sceneIsFrozen);
}

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)!;
yield* animate({
properties: [
[START_SCALE, END_SCALE],
[0.75, 0]
],
durationMS: 700,
easing: easeOutQuadratic,
fn: ([scale, opacity]: [Vector3, number]) => {
obj.scale.copy(scale);
obj.matrixNeedsUpdate = true;
((obj as Mesh).material as MeshBasicMaterial).opacity = opacity;
}
});
}
10 changes: 7 additions & 3 deletions src/hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,14 @@ import PinningHelper from "./utils/pinning-helper";
import { sleep } from "./utils/async-utils";
import { platformUnsupported } from "./support";
import { renderAsEntity } from "./utils/jsx-entity";
import { VideoMenuPrefab } from "./prefabs/video-menu";
import { VideoMenuPrefab, loadVideoMenuButtonIcons } from "./prefabs/video-menu";
import { loadObjectMenuButtonIcons, ObjectMenuPrefab } from "./prefabs/object-menu";
import { LinkHoverMenuPrefab } from "./prefabs/link-hover-menu";
import { PDFMenuPrefab } from "./prefabs/pdf-menu";
import { loadWaypointPreviewModel, WaypointPreview } from "./prefabs/waypoint-preview";
import { preload } from "./utils/preload";

window.APP = new App();
renderAsEntity(APP.world, VideoMenuPrefab());
renderAsEntity(APP.world, VideoMenuPrefab());
function addToScene(entityDef, visible) {
return getScene().then(scene => {
const eid = renderAsEntity(APP.world, entityDef);
Expand All @@ -211,6 +209,12 @@ preload(addToScene(PDFMenuPrefab(), false));
preload(loadObjectMenuButtonIcons().then(() => addToScene(ObjectMenuPrefab(), false)));
preload(addToScene(LinkHoverMenuPrefab(), false));
preload(loadWaypointPreviewModel().then(() => addToScene(WaypointPreview(), false)));
preload(
loadVideoMenuButtonIcons().then(() => {
addToScene(VideoMenuPrefab(), false);
addToScene(VideoMenuPrefab(), false);
})
);

const store = window.APP.store;
store.update({ preferences: { shouldPromptForRefresh: false } }); // Clear flag that prompts for refresh from preference screen
Expand Down
73 changes: 31 additions & 42 deletions src/prefabs/video-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
/** @jsx createElementEntity */
import { BoxBufferGeometry, Mesh, MeshBasicMaterial, PlaneBufferGeometry } from "three";
import { Label } from "../prefabs/camera-tool";
import { AlphaMode } from "../utils/create-image-mesh";
import { createElementEntity, createRef } from "../utils/jsx-entity";
import { ProjectionMode } from "../utils/projection-mode";

import { textureLoader } from "../utils/media-utils";
import { Attrs, createElementEntity, createRef } from "../utils/jsx-entity";
import playImageUrl from "../assets/images/sprites/notice/play.png";
import pauseImageUrl from "../assets/images/sprites/notice/pause.png";
import { TextureCache } from "../utils/texture-cache";
import { BUTTON_TYPES, Button3D } from "./button3D";
import { loadTexture, loadTextureFromCache } from "../utils/load-texture";

export async function loadVideoMenuButtonIcons() {
return Promise.all([loadTexture(playImageUrl, 1, "image/png"), loadTexture(pauseImageUrl, 1, "image/png")]);
}

const playTexture = textureLoader.load(playImageUrl);
const pauseTexture = textureLoader.load(pauseImageUrl);
const uiZ = 0.001;

function Slider({ trackRef, headRef, ...props }: any) {
return (
<entity {...props} name="Slider">
<entity
name="Slider:Track"
videoMenuItem
object3D={
new Mesh(
new PlaneBufferGeometry(1.0, 0.05),
new MeshBasicMaterial({ opacity: 0.5, color: 0x000000, transparent: true })
)
}
object3D={new Mesh(new PlaneBufferGeometry(1.0, 0.05), new MeshBasicMaterial({ color: 0x000000 }))}
cursorRaycastable
remoteHoverTarget
holdable
Expand All @@ -33,16 +28,34 @@ function Slider({ trackRef, headRef, ...props }: any) {
>
<entity
name="Slider:Head"
object3D={new Mesh(new BoxBufferGeometry(0.05, 0.05, 0.05), new MeshBasicMaterial())}
object3D={new Mesh(new BoxBufferGeometry(0.05, 0.05, 0.05), new MeshBasicMaterial({ color: 0xffffff }))}
ref={headRef}
/>
</entity>
</entity>
);
}

interface VideoButtonProps extends Attrs {
buttonIcon: string;
}

function VideoActionButton({ buttonIcon, ...props }: VideoButtonProps) {
const { texture, cacheKey } = loadTextureFromCache(buttonIcon, 1);
return (
<Button3D
position={[0, 0, uiZ]}
scale={[1, 1, 1]}
width={0.2}
height={0.2}
type={BUTTON_TYPES.DEFAULT}
icon={{ texture, cacheKey, scale: [0.165, 0.165, 0.165] }}
{...props}
/>
);
}

export function VideoMenuPrefab() {
const uiZ = 0.001;
const timeLabelRef = createRef();
const sliderRef = createRef();
const headRef = createRef();
Expand All @@ -65,32 +78,8 @@ export function VideoMenuPrefab() {
position={[0.5 - 0.02, halfHeight - 0.02, uiZ]}
/>
<Slider ref={sliderRef} trackRef={trackRef} headRef={headRef} position={[0, -halfHeight + 0.025, uiZ]} />
<entity
ref={playIndicatorRef}
position={[0, 0, uiZ]}
scale={[0.25, 0.25, 0.25]}
image={{
texture: playTexture,
ratio: 1,
projection: ProjectionMode.FLAT,
alphaMode: AlphaMode.BLEND,
cacheKey: TextureCache.key(playImageUrl, 1)
}}
visible={false}
/>
<entity
ref={pauseIndicatorRef}
position={[0, 0, uiZ]}
scale={[0.25, 0.25, 0.25]}
image={{
texture: pauseTexture,
ratio: 1,
projection: ProjectionMode.FLAT,
alphaMode: AlphaMode.BLEND,
cacheKey: TextureCache.key(pauseImageUrl, 1)
}}
visible={false}
/>
<VideoActionButton ref={playIndicatorRef} name={"Play Button"} buttonIcon={playImageUrl} />
<VideoActionButton ref={pauseIndicatorRef} name={"Pause Button"} buttonIcon={pauseImageUrl} />
</entity>
);
}

0 comments on commit 8895b7b

Please sign in to comment.