Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for loading media with bitecs behind feature flag. #5677

Merged
merged 85 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
4077884
[Work in progress] Load images without AFRAME
johnshaughnessy Aug 4, 2022
7885b32
Stub out video
johnshaughnessy Aug 9, 2022
87b0ef6
Add models to media-loading system
johnshaughnessy Aug 9, 2022
e95c2e7
Resize and recenter media after it loads
johnshaughnessy Aug 10, 2022
12e82d8
Add chain for sequential coroutines.
johnshaughnessy Aug 11, 2022
c07ccf4
Animate the proxy object
johnshaughnessy Aug 11, 2022
b7e9a9f
Start loading videos
johnshaughnessy Aug 12, 2022
b47b18b
Simplify coroutines and cancelables
johnshaughnessy Aug 16, 2022
3da1980
Use AbortControllers and AbortSignals
johnshaughnessy Aug 16, 2022
1e831b2
Merge remote-tracking branch 'origin/master' into feature/load-media
johnshaughnessy Aug 16, 2022
d5c6e95
Fix error handling in coroutine / cancelable
johnshaughnessy Aug 16, 2022
77554f8
Merge remote-tracking branch 'origin/add-typescript' into feature/loa…
johnshaughnessy Aug 16, 2022
cd6962f
Add networked video playback state
johnshaughnessy Aug 16, 2022
73af100
Separate the menus from the video entity
johnshaughnessy Aug 16, 2022
9a92bd8
Fix cursor hovering on video menu
johnshaughnessy Aug 17, 2022
8fc2978
Formatting
johnshaughnessy Aug 17, 2022
0346a37
Remove unused code
johnshaughnessy Aug 17, 2022
7df8801
Add type stub for troika-three-text
netpro2k Aug 17, 2022
9d94e7a
Convert jsx-entities to ts, type JSX better
netpro2k Aug 17, 2022
db63f2f
Minor type fixes
netpro2k Aug 17, 2022
1a98179
Fix taking ownership of loaded media
johnshaughnessy Aug 17, 2022
a75023e
Remove NOCOMMIT
johnshaughnessy Aug 17, 2022
889fefc
Add seek-slider to video
johnshaughnessy Aug 18, 2022
4392a9f
Update play/pause button label
johnshaughnessy Aug 18, 2022
c9e485d
Fix hovering on playhead
johnshaughnessy Aug 18, 2022
4816d40
Minor tweaks to video menu layout
netpro2k Aug 18, 2022
99f2a49
Intersecting In The Plane Of!
johnshaughnessy Aug 19, 2022
42d6e14
Basic audio playback for videos
netpro2k Aug 19, 2022
64965b9
Fix minor issue where time label is one frame late
johnshaughnessy Aug 19, 2022
91d3181
Rename mediaVideoSystem -> videoSystem
johnshaughnessy Aug 19, 2022
e920990
Toggle video play/pause on click. Grab to move.
johnshaughnessy Aug 22, 2022
91c620c
Fix action set toggle: Hands do not hover on videos
johnshaughnessy Aug 22, 2022
3a11b77
Add deleteEntitySystem
johnshaughnessy Aug 23, 2022
b85e4a9
Add animation to entity deletion
johnshaughnessy Aug 23, 2022
c5bb832
Add transparent play/pause indicators
johnshaughnessy Aug 23, 2022
caaa4b9
Merge branch 'add-typescript' into feature/load-media
netpro2k Aug 24, 2022
e9d552a
Fix asset imports by using babel-loader for TS
netpro2k Aug 24, 2022
4173296
Convert dash-case inflators to camelCase
netpro2k Aug 25, 2022
3615803
Support pasting files
johnshaughnessy Aug 25, 2022
8a2948f
Handle dropping files
johnshaughnessy Aug 26, 2022
ec02ce0
Add newloader query string param as feature flag
johnshaughnessy Aug 26, 2022
3c44d34
Fix classic video menus / interactions
johnshaughnessy Aug 26, 2022
08c8cf9
Update todo
johnshaughnessy Aug 26, 2022
dae8bf9
Remove todo file
johnshaughnessy Aug 26, 2022
6c5825a
Delete unused file
johnshaughnessy Aug 26, 2022
fd2ad95
Remove unused imports
johnshaughnessy Aug 26, 2022
8ce2940
Remove MediaLoader component after load
johnshaughnessy Aug 29, 2022
f8153d2
Undo change to cursor controller
johnshaughnessy Aug 29, 2022
98c2434
Fix grabbing videos
johnshaughnessy Aug 29, 2022
6c9d132
Remove unused animation mixer system
johnshaughnessy Aug 30, 2022
8a7c711
Add Constraint components for floaty-object
johnshaughnessy Aug 30, 2022
c60191d
Move utils/onpaste to load-media-on-paste-or-drop
johnshaughnessy Aug 30, 2022
80569f5
Add TextureCacheKey component.
johnshaughnessy Aug 30, 2022
df99c68
Warn on unrecognized component.
johnshaughnessy Aug 30, 2022
eec20d2
Fix button props
johnshaughnessy Aug 30, 2022
2c291b3
Use MediaParams in inflateMediaLoader
johnshaughnessy Aug 30, 2022
6f9f96b
Throw error if url parsing fails
johnshaughnessy Aug 30, 2022
11fcd85
Simplify function parameters
johnshaughnessy Aug 30, 2022
b5b81a6
Consolidate MEDIA_TYPE and MediaType
johnshaughnessy Aug 30, 2022
5e695e9
Simplify loader function signatures
johnshaughnessy Aug 30, 2022
157b0ea
Simplify loadVideoTexture parameters
johnshaughnessy Aug 30, 2022
b89f1e7
Remove unnecessary workaround
johnshaughnessy Aug 30, 2022
4edc977
Remove log
johnshaughnessy Aug 30, 2022
fc4b58c
Simplify method signatures
johnshaughnessy Aug 30, 2022
f3c31d2
Simplify function parameters
johnshaughnessy Aug 30, 2022
a35e2d1
Simplify function parameters
johnshaughnessy Aug 30, 2022
10680ea
Remove unused code
johnshaughnessy Aug 30, 2022
7a73e21
Use AnimationConfig type for calls to animate
johnshaughnessy Aug 30, 2022
e21a247
Remove unused code
johnshaughnessy Aug 30, 2022
9a4252e
Remove unnecessary workaround
johnshaughnessy Aug 30, 2022
b32448c
Avoid extra copy
johnshaughnessy Aug 30, 2022
8b1f2ce
Preload assets. Dry up model loading
johnshaughnessy Aug 31, 2022
c89a192
Rename MediaParams -> MediaLoaderParams
johnshaughnessy Aug 31, 2022
359cad5
Rename fetchUrlData -> resolveMediaInfo
johnshaughnessy Aug 31, 2022
c526275
Restore dontHoverAndHold.
johnshaughnessy Aug 31, 2022
d54c184
rename
johnshaughnessy Aug 31, 2022
df37251
Add ECS Debug sidebar on ecsDebug qs param
johnshaughnessy Aug 31, 2022
dbeeea6
Remove duplicate exclude block
netpro2k Sep 1, 2022
bea7e3f
delint
netpro2k Sep 1, 2022
a83deb6
Rename couroutine timer functions
netpro2k Sep 1, 2022
889a9c7
Fix video menu position updating issues
netpro2k Sep 1, 2022
8a89e6f
Merge remote-tracking branch 'origin/master' into feature/load-media
netpro2k Sep 1, 2022
a45e692
Fix ECS Debug button label
netpro2k Sep 2, 2022
59c7513
Clean up loading cube removal
netpro2k Sep 2, 2022
b92010f
Delint
netpro2k Sep 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module.exports = {
[
"@babel/env",
{
exclude: ["transform-regenerator"],
// targets are defined in .browserslistrc
useBuiltIns: "entry",
// This should be kept up to date with thee version in package.json
Expand Down
27 changes: 15 additions & 12 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "three";
import { AudioSettings, SourceType } from "./components/audio-params";
import { DialogAdapter } from "./naf-dialog-adapter";
import { waitForPreloads } from "./utils/preload";

declare global {
interface Window {
Expand All @@ -33,7 +34,7 @@ declare global {
const APP: App;
}

interface HubsWorld extends IWorld {
export interface HubsWorld extends IWorld {
scene: Scene;
nameToComponent: {
object3d: typeof Object3DTag;
Expand Down Expand Up @@ -69,17 +70,17 @@ export class App {
store = new Store();
mediaSearchStore = new MediaSearchStore();

audios = new Map<AElement, PositionalAudio | Audio>();
sourceType = new Map<AElement, SourceType>();
audioOverrides = new Map<AElement, AudioSettings>();
zoneOverrides = new Map<AElement, AudioSettings>();
audios = new Map<AElement | number, PositionalAudio | Audio>();
sourceType = new Map<AElement | number, SourceType>();
audioOverrides = new Map<AElement | number, AudioSettings>();
zoneOverrides = new Map<AElement | number, AudioSettings>();
gainMultipliers = new Map<AElement | number, number>();
supplementaryAttenuation = new Map<AElement | number, number>();
clippingState = new Set<AElement | number>();
mutedState = new Set<AElement | number>();
isAudioPaused = new Set<AElement | number>();
audioDebugPanelOverrides = new Map<SourceType, AudioSettings>();
sceneAudioDefaults = new Map<SourceType, AudioSettings>();
gainMultipliers = new Map<AElement, number>();
supplementaryAttenuation = new Map<AElement, number>();
clippingState = new Set<AElement>();
mutedState = new Set<AElement>();
isAudioPaused = new Set<AElement>();

world: HubsWorld = createWorld();

Expand Down Expand Up @@ -218,8 +219,10 @@ export class App {

// This gets called after all system and component init functions
sceneEl.addEventListener("loaded", () => {
renderer.setAnimationLoop(mainTick);
sceneEl.renderStarted = true;
waitForPreloads().then(() => {
renderer.setAnimationLoop(mainTick);
sceneEl.renderStarted = true;
});
});

return {
Expand Down
40 changes: 40 additions & 0 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export const HeldHandLeft = defineComponent();
export const HeldRemoteRight = defineComponent();
export const HeldRemoteLeft = defineComponent();
export const Held = defineComponent();
export const Constraint = defineComponent();
export const ConstraintHandRight = defineComponent();
export const ConstraintHandLeft = defineComponent();
export const ConstraintRemoteRight = defineComponent();
export const ConstraintRemoteLeft = defineComponent();
export const OffersRemoteConstraint = defineComponent();
export const HandCollisionTarget = defineComponent();
export const OffersHandConstraint = defineComponent();
Expand Down Expand Up @@ -120,3 +125,38 @@ export const CameraTool = defineComponent({
sndToggleRef: Types.eid
});
export const MyCameraTool = defineComponent();
export const MediaLoader = defineComponent({
src: Types.ui32,
flags: Types.ui8
});
MediaLoader.src[$isStringType] = true;

export const TextureCacheKey = defineComponent({
src: Types.ui32,
version: Types.ui8
});
TextureCacheKey.src[$isStringType] = true;

export const MediaVideo = defineComponent({
autoPlay: Types.ui8
});

export const AnimationMixer = defineComponent();
export const NetworkedVideo = defineComponent({
time: Types.f32,
flags: Types.ui8
});

export const VideoMenuItem = defineComponent();
export const VideoMenu = defineComponent({
videoRef: Types.eid,
timeLabelRef: Types.eid,
trackRef: Types.eid,
headRef: Types.eid,
playIndicatorRef: Types.eid,
pauseIndicatorRef: Types.eid
});

export const AudioEmitter = defineComponent();
export const AudioSettingsChanged = defineComponent();
export const Deletable = defineComponent();
9 changes: 5 additions & 4 deletions src/bit-systems/camera-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function updateUI(world, camera) {
CameraTool.state[camera] === CAMERA_STATE.RECORDING_VIDEO;

const inVR = AFRAME.scenes[0].is("vr-mode");
const showViewfinder = !inVR || (hasComponent(world, Held, camera) || !isIdle);
const showViewfinder = !inVR || hasComponent(world, Held, camera) || !isIdle;
screenObj.visible = showViewfinder;
selfieScreenObj.visible = showViewfinder;

Expand Down Expand Up @@ -249,7 +249,8 @@ function updateUI(world, camera) {

// TODO HACK hidden objects are still not having their matricies updated correctly
// Seems like a regression of #5421
snapMenuObj.matrixNeedsUpdate = true;
// updateMatrices should be checking forceWorldUpdate instead of parent.childrenNeedMatrixWorldUpdate
snapMenuObj.childrenNeedMatrixWorldUpdate = true;
}

let snapPixels;
Expand Down Expand Up @@ -289,7 +290,7 @@ const cameraToolEnterQuery = enterQuery(cameraToolQuery);
const cameraToolExitQuery = exitQuery(cameraToolQuery);

export function cameraToolSystem(world) {
cameraToolEnterQuery(world).forEach(function(eid) {
cameraToolEnterQuery(world).forEach(function (eid) {
const renderTarget = new THREE.WebGLRenderTarget(RENDER_WIDTH, RENDER_HEIGHT, {
format: THREE.RGBAFormat,
minFilter: THREE.LinearFilter,
Expand Down Expand Up @@ -317,7 +318,7 @@ export function cameraToolSystem(world) {
renderTargets.set(eid, renderTarget);
});

cameraToolExitQuery(world).forEach(function(eid) {
cameraToolExitQuery(world).forEach(function (eid) {
const renderTarget = renderTargets.get(eid);
renderTarget.dispose();
renderTargets.delete(eid);
Expand Down
54 changes: 54 additions & 0 deletions src/bit-systems/delete-entity-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defineQuery, exitQuery, hasComponent, removeEntity } from "bitecs";
import { Vector3 } from "three";
import { HubsWorld } from "../app";
import { Deletable, HoveredRemoteLeft, HoveredRemoteRight } from "../bit-components";
import { paths } from "../systems/userinput/paths";
import { animate } from "../utils/animate";
import { findAncestorEntity } from "../utils/bit-utils";
import { coroutine } from "../utils/coroutine";
import { easeOutQuadratic } from "../utils/easing";

// TODO Move to coroutine.ts when it exists
// TODO Figure out the appropriate type and use it everywhere
export type Coroutine = Generator<Promise<void>, void, unknown>;

const END_SCALE = new Vector3().setScalar(0.001);
function* animateThenRemoveEntity(world: HubsWorld, eid: number): Coroutine {
const obj = world.eid2obj.get(eid)!;
yield* animate({
properties: [[obj.scale.clone(), END_SCALE]],
durationMS: 400,
easing: easeOutQuadratic,
fn: ([scale]: [Vector3]) => {
obj.scale.copy(scale);
obj.matrixNeedsUpdate = true;
}
});
removeEntity(world, eid);
}

const deletableQuery = defineQuery([Deletable]);
const deletableExitQuery = exitQuery(deletableQuery);
const hoveredRightQuery = defineQuery([HoveredRemoteRight]);
const hoveredLeftQuery = defineQuery([HoveredRemoteLeft]);
const coroutines = new Map();

function deleteTheDeletableAncestor(world: HubsWorld, eid: number) {
const ancestor = findAncestorEntity(world, eid, (e: number) => hasComponent(world, Deletable, e));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what to call Deletable. Removable is equally bad.. RemovableRoot, UserRemovable? Not very good either..

if (ancestor && !coroutines.has(ancestor)) {
coroutines.set(ancestor, coroutine(animateThenRemoveEntity(world, ancestor)));
}
}

export function deleteEntitySystem(world: HubsWorld, userinput: any) {
deletableExitQuery(world).forEach(function (eid) {
coroutines.delete(eid);
});
if (userinput.get(paths.actions.cursor.right.deleteEntity)) {
hoveredRightQuery(world).forEach(eid => deleteTheDeletableAncestor(world, eid));
netpro2k marked this conversation as resolved.
Show resolved Hide resolved
}
if (userinput.get(paths.actions.cursor.left.deleteEntity)) {
hoveredLeftQuery(world).forEach(eid => deleteTheDeletableAncestor(world, eid));
}
coroutines.forEach(c => c());
}
166 changes: 166 additions & 0 deletions src/bit-systems/media-loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { LoadingObject } from "../prefabs/loading-object";
import { ErrorObject } from "../prefabs/error-object";
import { easeOutQuadratic } from "../utils/easing";
import { loadImage } from "../utils/load-image";
import { loadVideo } from "../utils/load-video";
import { loadModel } from "../utils/load-model";
import { MediaType, resolveMediaInfo } from "../utils/media-utils";
import { defineQuery, enterQuery, exitQuery, hasComponent, removeComponent, removeEntity } from "bitecs";
import { MediaLoader, Networked } from "../bit-components";
import { crTimeout, crClearTimeout, cancelable, coroutine, makeCancelable } from "../utils/coroutine";
import { takeOwnership } from "../systems/netcode";
import { renderAsEntity } from "../utils/jsx-entity";
import { animate } from "../utils/animate";

const loaderForMediaType = {
[MediaType.IMAGE]: (world, { accessibleUrl, contentType }) => loadImage(world, accessibleUrl, contentType),
[MediaType.VIDEO]: (world, { accessibleUrl }) => loadVideo(world, accessibleUrl),
[MediaType.MODEL]: (world, { accessibleUrl }) => loadModel(world, accessibleUrl)
};

export const MEDIA_LOADER_FLAGS = {
RECENTER: 1 << 0,
RESIZE: 1 << 1
};

function assignNetworkIds(world, mediaEid, mediaLoaderEid) {
const rootNid = APP.getString(Networked.id[mediaLoaderEid]);
let i = 0;
world.eid2obj.get(mediaEid).traverse(function (obj) {
if (obj.eid && hasComponent(world, Networked, obj.eid)) {
const eid = obj.eid;
Networked.id[eid] = APP.getSid(`${rootNid}.${i}`);
APP.world.nid2eid.set(Networked.id[eid], eid);
Networked.creator[eid] = Networked.creator[mediaLoaderEid];
Networked.owner[eid] = Networked.owner[mediaLoaderEid];
if (APP.getSid(NAF.clientId) === Networked.owner[mediaLoaderEid]) takeOwnership(world, eid);
i += 1;
}
});
}

function resizeAndRecenter(world, media, eid) {
const resize = MediaLoader.flags[eid] & MEDIA_LOADER_FLAGS.RESIZE;
const recenter = MediaLoader.flags[eid] & MEDIA_LOADER_FLAGS.RECENTER;
if (!resize && !recenter) return;

const mediaObj = world.eid2obj.get(media);
const box = new THREE.Box3();
box.setFromObject(mediaObj);

let scale = 1;
if (resize) {
const size = new THREE.Vector3();
box.getSize(size);
// Might want to consider avatar scale someday
scale = 0.5 / Math.max(size.x, size.y, size.z);
mediaObj.scale.setScalar(scale);
mediaObj.matrixNeedsUpdate = true;
}

if (recenter) {
const center = new THREE.Vector3();
box.getCenter(center);
mediaObj.position.copy(center).multiplyScalar(-1 * scale);
mediaObj.matrixNeedsUpdate = true;
}
}

function* animateScale(world, media) {
const mediaObj = world.eid2obj.get(media);

const onAnimate = ([position, scale]) => {
mediaObj.position.copy(position);
mediaObj.scale.copy(scale);
mediaObj.matrixNeedsUpdate = true;
};

const startScale = new THREE.Vector3().setScalar(0.0001);
const endScale = new THREE.Vector3().setScalar(mediaObj.scale.x);

const startPosition = new THREE.Vector3().copy(mediaObj.position).multiplyScalar(startScale.x);
const endPosition = new THREE.Vector3().copy(mediaObj.position);

// Set the initial position and yield one frame
// because the first frame that we render a new object is slow
// TODO: We could move uploading textures to the GPU to the loader,
// so that we don't hitch here
onAnimate([startPosition, startScale]);
yield Promise.resolve();

yield* animate({
properties: [
[startPosition, endPosition],
[startScale, endScale]
],
durationMS: 400,
easing: easeOutQuadratic,
fn: onAnimate
});
}

function add(world, child, parent) {
const parentObj = world.eid2obj.get(parent);
const childObj = world.eid2obj.get(child);
parentObj.add(childObj);

// TODO: Fix this in THREE.Object3D.add
childObj.matrixWorldNeedsUpdate = true;
}

function* loadMedia(world, eid) {
let loadingObjEid = 0;
const addLoadingObjectTimeout = crTimeout(() => {
loadingObjEid = renderAsEntity(world, LoadingObject());
add(world, loadingObjEid, eid);
}, 400);
yield makeCancelable(() => loadingObjEid && removeEntity(world, loadingObjEid));
const src = APP.getString(MediaLoader.src[eid]);
let media;
try {
const urlData = yield resolveMediaInfo(src);
media = yield* loaderForMediaType[urlData.mediaType](world, urlData);
} catch (e) {
console.error(e);
media = renderAsEntity(world, ErrorObject());
}
crClearTimeout(addLoadingObjectTimeout);
loadingObjEid && removeEntity(world, loadingObjEid);
return media;
}

function* loadAndAnimateMedia(world, eid, signal) {
const { value: media, canceled } = yield* cancelable(loadMedia(world, eid), signal);
if (!canceled) {
assignNetworkIds(world, media, eid);
resizeAndRecenter(world, media, eid);
add(world, media, eid);
yield* animateScale(world, media);
removeComponent(world, MediaLoader, eid);
}
}

const jobs = new Set();
const abortControllers = new Map();
const mediaLoaderQuery = defineQuery([MediaLoader]);
const mediaLoaderEnterQuery = enterQuery(mediaLoaderQuery);
const mediaLoaderExitQuery = exitQuery(mediaLoaderQuery);
export function mediaLoadingSystem(world) {
mediaLoaderEnterQuery(world).forEach(function (eid) {
const ac = new AbortController();
abortControllers.set(eid, ac);
jobs.add(coroutine(loadAndAnimateMedia(world, eid, ac.signal)));
});

mediaLoaderExitQuery(world).forEach(function (eid) {
const ac = abortControllers.get(eid);
ac.abort();
abortControllers.delete(eid);
});

jobs.forEach(c => {
if (c().done) {
jobs.delete(c);
}
});
}
Loading