Skip to content

Commit

Permalink
BitECS object list support
Browse files Browse the repository at this point in the history
  • Loading branch information
keianhzo authored and takahirox committed Sep 1, 2023
1 parent 9b216c8 commit fc9e988
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 62 deletions.
7 changes: 6 additions & 1 deletion src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,12 @@ export const MediaLoader = defineComponent({
});
MediaLoader.src[$isStringType] = true;
MediaLoader.fileId[$isStringType] = true;
export const MediaLoaded = defineComponent();
export const MediaLoaded = defineComponent({
contentType: Types.ui32,
src: Types.ui32
});
MediaLoaded.contentType[$isStringType] = true;
MediaLoaded.src[$isStringType] = true;
export const LoadedByMediaLoader = defineComponent();
export const MediaContentBounds = defineComponent({
bounds: [Types.f32, 3]
Expand Down
10 changes: 10 additions & 0 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ function* loadMedia(world: HubsWorld, eid: EntityID) {
}
media = yield* loader(world, eid, urlData);
addComponent(world, MediaLoaded, media);
const srcSid = APP.getSid(urlData.accessibleUrl);
MediaLoaded.src[media] = srcSid;
const contentTypeSid = APP.getSid(urlData.contentType);
MediaLoaded.contentType[media] = contentTypeSid;
} catch (e) {
console.error(e);
media = renderAsEntity(world, ErrorObject());
Expand Down Expand Up @@ -252,6 +256,9 @@ const jobs = new JobRunner();
const mediaLoaderQuery = defineQuery([MediaLoader]);
const mediaLoaderEnterQuery = enterQuery(mediaLoaderQuery);
const mediaLoaderExitQuery = exitQuery(mediaLoaderQuery);
const mediaLoadedQuery = defineQuery([MediaLoaded]);
const mediaLoadedEnterQuery = enterQuery(mediaLoadedQuery);
const mediaLoadedExitQuery = exitQuery(mediaLoadedQuery);
export function mediaLoadingSystem(world: HubsWorld) {
mediaLoaderEnterQuery(world).forEach(function (eid) {
jobs.add(eid, clearRollbacks => loadAndAnimateMedia(world, eid, clearRollbacks));
Expand All @@ -261,5 +268,8 @@ export function mediaLoadingSystem(world: HubsWorld) {
jobs.stop(eid);
});

mediaLoadedEnterQuery(world).forEach(eid => APP.scene?.emit("listed_media_changed"));
mediaLoadedExitQuery(world).forEach(eid => APP.scene?.emit("listed_media_changed"));

jobs.tick();
}
4 changes: 2 additions & 2 deletions src/components/media-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ AFRAME.registerComponent("media-loader", {
setMatrixWorld(mesh, originalMeshMatrix);
} else {
// Move the mesh such that the center of its bounding box is in the same position as the parent matrix position
const box = getBox(this.el, mesh);
const box = getBox(this.el.object3D, mesh);
const scaleCoefficient = fitToBox ? getScaleCoefficient(0.5, box) : 1;
const { min, max } = box;
center.addVectors(min, max).multiplyScalar(0.5 * scaleCoefficient);
Expand Down Expand Up @@ -283,7 +283,7 @@ AFRAME.registerComponent("media-loader", {
}

// TODO this does duplicate work in some cases, but finish() is the only consistent place to do it
const contentBounds = getBox(this.el, this.el.getObject3D("mesh")).getSize(new THREE.Vector3());
const contentBounds = getBox(this.el.object3D, this.el.getObject3D("mesh")).getSize(new THREE.Vector3());
addComponent(APP.world, MediaContentBounds, el.eid);
MediaContentBounds.bounds[el.eid].set(contentBounds.toArray());

Expand Down
2 changes: 1 addition & 1 deletion src/components/super-spawner.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ AFRAME.registerComponent("super-spawner", {
? 1
: 0.5;

const scaleCoefficient = getScaleCoefficient(boxSize, getBox(spawnedEntity, spawnedEntity.object3D));
const scaleCoefficient = getScaleCoefficient(boxSize, getBox(spawnedEntity.object3D, spawnedEntity.object3D));
this.spawnedMediaScale.divideScalar(scaleCoefficient);
},

Expand Down
1 change: 1 addition & 0 deletions src/prefabs/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function MediaPrefab(params: MediaLoaderParams): EntityDef {
collisionMask: COLLISION_LAYERS.HANDS
}}
scale={[1, 1, 1]}
inspectable
/>
);
}
35 changes: 29 additions & 6 deletions src/react-components/room/hooks/useObjectList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import React, { useState, useEffect, useContext, createContext, useCallback, Chi
import PropTypes from "prop-types";
import { mediaSort, getMediaType } from "../../../utils/media-sorting.js";

function getDisplayString(el) {
// Having a listed-media component does not guarantee the existence of a media-loader component,
// so don't crash if there isn't one.
const url = (el.components["media-loader"] && el.components["media-loader"].data.src) || "";
function getDisplayString(elOrEid) {
let url;
if (!elOrEid.isEntity) {
const srcSid = MediaLoaded.src[elOrEid];
url = APP.getString(srcSid);
} else {
// Having a listed-media component does not guarantee the existence of a media-loader component,
// so don't crash if there isn't one.
url = (elOrEid.components["media-loader"] && elOrEid.components["media-loader"].data.src) || "";
}

const split = url.split("/");
const resourceName = split[split.length - 1].split("?")[0];
let httpIndex = -1;
Expand Down Expand Up @@ -67,6 +74,7 @@ function handleDeselect(scene, object, callback) {
}
}

const queryListedMedia = defineQuery([MediaLoaded]);
export function ObjectListProvider({ scene, children }) {
const [objects, setObjects] = useState([]);
const [focusedObject, setFocusedObject] = useState(null); // The object currently shown in the viewport
Expand All @@ -83,7 +91,16 @@ export function ObjectListProvider({ scene, children }) {
el
}));

setObjects(objects);
const bitObjects = queryListedMedia(APP.world)
.sort(mediaSort)
.map(eid => ({
id: APP.world.eid2obj.get(eid)?.id,
name: getDisplayString(eid),
type: getMediaType(eid),
el: eid
}));

setObjects([...objects, ...bitObjects]);
}

let timeout;
Expand Down Expand Up @@ -111,7 +128,13 @@ export function ObjectListProvider({ scene, children }) {
const inspectedEl = cameraSystem.inspectable && cameraSystem.inspectable.el;

if (inspectedEl) {
const object = objects.find(o => o.el === inspectedEl);
const object = objects.find(o => {
if (!o.el.isEntity) {
return o.el === inspectedEl.eid;
} else {
return o.el === inspectedEl;
}
});

if (object) {
setSelectedObject(object);
Expand Down
37 changes: 29 additions & 8 deletions src/react-components/room/object-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,33 @@ import { removeNetworkedObject } from "../../utils/removeNetworkedObject";
import { rotateInPlaceAroundWorldUp, affixToWorldUp } from "../../utils/three-utils";
import { getPromotionTokenForFile } from "../../utils/media-utils";
import { hasComponent } from "bitecs";
import { Static } from "../../bit-components";
import { isPinned as getPinnedState } from "../../bit-systems/networking";
import { MediaLoaded, Pinnable, Pinned, Static } from "../../bit-components";
import { deleteTheDeletableAncestor } from "../../bit-systems/delete-entity-system";

export function isMe(object) {
return object.el.id === "avatar-rig";
}

export function isPlayer(object) {
return !!object.el.components["networked-avatar"];
if (object.el.isEntity) {
return !!object.el.components["networked-avatar"];
} else {
// TODO Add when networked avatar is migrated
return false;
}
}

export function getObjectUrl(object) {
const mediaLoader = object.el.components["media-loader"];

const url =
mediaLoader && ((mediaLoader.data.mediaOptions && mediaLoader.data.mediaOptions.href) || mediaLoader.data.src);
let url;
if (object.el.isEntity) {
const mediaLoader = object.el.components["media-loader"];
url =
mediaLoader && ((mediaLoader.data.mediaOptions && mediaLoader.data.mediaOptions.href) || mediaLoader.data.src);
} else {
const urlSid = MediaLoaded.src[object.el];
url = APP.getString(urlSid);
}

if (url && !url.startsWith("hubs://")) {
return url;
Expand Down Expand Up @@ -53,6 +64,9 @@ export function usePinObject(hubChannel, scene, object) {
useEffect(() => {
const el = object.el;

// TODO Add when pinning is migrated
if (!el.isEntity) return;

function onPinStateChanged() {
setIsPinned(getPinnedState(el.eid));
}
Expand All @@ -69,7 +83,10 @@ export function usePinObject(hubChannel, scene, object) {

let userOwnsFile = false;

if (el.components["media-loader"]) {
if (!el.isEntity) {
// TODO Add when pinning is migrated
return false;
} else if (el.components["media-loader"]) {
const { fileIsOwned, fileId } = el.components["media-loader"].data;
userOwnsFile = fileIsOwned || (fileId && getPromotionTokenForFile(fileId));
}
Expand Down Expand Up @@ -114,7 +131,11 @@ export function useGoToSelectedObject(scene, object) {

export function useRemoveObject(hubChannel, scene, object) {
const removeObject = useCallback(() => {
removeNetworkedObject(scene, object.el);
if (object.el.isEntity) {
removeNetworkedObject(scene, object.el);
} else {
deleteTheDeletableAncestor(APP.world, object.el);
}
}, [scene, object]);

const el = object.el;
Expand Down
64 changes: 41 additions & 23 deletions src/systems/camera-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@ import { qsGet } from "../utils/qs_truthy";
const customFOV = qsGet("fov");
const enableThirdPersonMode = qsTruthy("thirdPerson");
import { Layers } from "../camera-layers";
import { Inspectable } from "../bit-components";
import { findAncestorWithComponent } from "../utils/bit-utils";

function getInspectableInHierarchy(elOrEid) {
if (elOrEid.isEntity) {
let inspectable = elOrEid;
while (inspectable) {
if (isTagged(inspectable, "inspectable")) {
return inspectable.object3D;
}
inspectable = inspectable.parentNode;
}

function getInspectableInHierarchy(el) {
let inspectable = el;
while (inspectable) {
if (isTagged(inspectable, "inspectable")) {
return inspectable.object3D;
console.warn("could not find inspectable in hierarchy");
return elOrEid.object3D;
} else {
let inspectable = findAncestorWithComponent(APP.world, Inspectable, elOrEid);
if (!inspectable) {
console.warn("could not find inspectable in hierarchy");
inspectable = elOrEid;
}
inspectable = inspectable.parentNode;
return APP.world.eid2obj.get(inspectable);
}
console.warn("could not find inspectable in hierarchy");
return el.object3D;
}

function pivotFor(el) {
Expand All @@ -36,9 +48,15 @@ function pivotFor(el) {
return child.object3D;
}

export function getInspectableAndPivot(el) {
const inspectable = getInspectableInHierarchy(el);
const pivot = pivotFor(inspectable.el);
function getInspectableAndPivot(elOrEid) {
const inspectable = getInspectableInHierarchy(elOrEid);
let pivot;
if (elOrEid.isEntity) {
pivot = pivotFor(inspectable.el);
} else {
// TODO Add support for pivotFor (avatars only)
pivot = inspectable;
}
return { inspectable, pivot };
}

Expand Down Expand Up @@ -119,22 +137,16 @@ const moveRigSoCameraLooksAtPivot = (function () {
decompose(camera.matrixWorld, cwp, cwq);
rig.getWorldQuaternion(cwq);

const box = getBox(inspectable.el, inspectable.el.getObject3D("mesh") || inspectable, true);
const box = getBox(inspectable, inspectable, true);
if (box.min.x === Infinity) {
// fix edgecase where inspectable object has no mesh / dimensions
box.min.subVectors(owp, defaultBoxMax);
box.max.addVectors(owp, defaultBoxMax);
}
box.getCenter(center);
const vrMode = inspectable.el.sceneEl.is("vr-mode");
const vrMode = APP.scene.is("vr-mode");
const dist =
calculateViewingDistance(
inspectable.el.sceneEl.camera.fov,
inspectable.el.sceneEl.camera.aspect,
box,
center,
vrMode
) * distanceMod;
calculateViewingDistance(APP.scene.camera.fov, APP.scene.camera.aspect, box, center, vrMode) * distanceMod;
target.position.addVectors(
owp,
oForw
Expand Down Expand Up @@ -252,8 +264,8 @@ export class CameraSystem {
this.mode = NEXT_MODES[this.mode] || 0;
}

inspect(el, distanceMod, fireChangeEvent = true) {
const { inspectable, pivot } = getInspectableAndPivot(el);
inspect(elOrEid, distanceMod, fireChangeEvent = true) {
const { inspectable, pivot } = getInspectableAndPivot(elOrEid);

this.verticalDelta = 0;
this.horizontalDelta = 0;
Expand Down Expand Up @@ -282,7 +294,13 @@ export class CameraSystem {
this.viewingCamera.updateMatrices();
this.snapshot.matrixWorld.copy(this.viewingRig.object3D.matrixWorld);

this.snapshot.audio = !(inspectable.el && isTagged(inspectable.el, "preventAudioBoost")) && getAudio(inspectable);
let preventAudioBoost = false;
if (inspectable.el) {
preventAudioBoost = isTagged(inspectable.el, "preventAudioBoost");
} else {
// TODO Add when avatar is migrated
}
this.snapshot.audio = !preventAudioBoost && getAudio(inspectable);
if (this.snapshot.audio) {
this.snapshot.audio.updateMatrices();
this.snapshot.audioTransform.copy(this.snapshot.audio.matrixWorld);
Expand Down
16 changes: 8 additions & 8 deletions src/utils/auto-box-collider.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,25 @@ export const computeObjectAABB = (function () {
})();

const rotation = new THREE.Euler();
export function getBox(entity, boxRoot, worldSpace) {
export function getBox(obj, boxRoot, worldSpace) {
const box = new THREE.Box3();

rotation.copy(entity.object3D.rotation);
entity.object3D.rotation.set(0, 0, 0);
rotation.copy(obj.rotation);
obj.rotation.set(0, 0, 0);

entity.object3D.updateMatrices(true, true);
obj.updateMatrices(true, true);
boxRoot.updateMatrices(true, true);
boxRoot.updateMatrixWorld(true);

computeObjectAABB(boxRoot, box, false);

if (!box.isEmpty()) {
if (!worldSpace) {
entity.object3D.worldToLocal(box.min);
entity.object3D.worldToLocal(box.max);
obj.worldToLocal(box.min);
obj.worldToLocal(box.max);
}
entity.object3D.rotation.copy(rotation);
entity.object3D.matrixNeedsUpdate = true;
obj.rotation.copy(rotation);
obj.matrixNeedsUpdate = true;
}

boxRoot.matrixWorldNeedsUpdate = true;
Expand Down
6 changes: 4 additions & 2 deletions src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import {
MaterialTag,
VideoTextureSource,
Quack,
Mirror,
MixerAnimatableInitialize
MixerAnimatableInitialize,
Inspectable
} from "../bit-components";
import { inflateMediaLoader } from "../inflators/media-loader";
import { inflateMediaFrame } from "../inflators/media-frame";
Expand Down Expand Up @@ -359,6 +359,7 @@ export interface JSXComponentData extends ComponentData {
waypointPreview?: boolean;
pdf?: PDFParams;
loopAnimation?: LoopAnimationParams;
inspectable?: boolean;
}

export interface GLTFComponentData extends ComponentData {
Expand Down Expand Up @@ -464,6 +465,7 @@ const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = {
quack: createDefaultInflator(Quack),
mixerAnimatable: createDefaultInflator(MixerAnimatableInitialize),
loopAnimation: inflateLoopAnimationInitialize,
inspectable: createDefaultInflator(Inspectable),
// inflators that create Object3Ds
object3D: addObject3DComponent,
slice9: inflateSlice9,
Expand Down
Loading

0 comments on commit fc9e988

Please sign in to comment.