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-ons support #6468

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eccb840
Initial add-ons API commit
keianhzo Jan 29, 2024
4e69cb6
Try to get some more info about the build error
keianhzo Feb 15, 2024
1b48d6e
Do not install dev dependencies in production
keianhzo Feb 15, 2024
3cc6d09
Revert tests
keianhzo Feb 15, 2024
f3d84cc
organize types
keianhzo Feb 15, 2024
4ff2d0e
Test
keianhzo Feb 22, 2024
460930f
Omit dev packages when installing deps in production
keianhzo Apr 10, 2024
3726836
Package lock update
keianhzo Apr 10, 2024
2781f1f
Increase log level
keianhzo Apr 10, 2024
ca5a91e
Try --legacy-peer-deps
keianhzo Apr 10, 2024
4a432f9
Use webpack in addons instead of tsup
keianhzo Apr 17, 2024
f0da86a
Revert docker image changes
keianhzo Apr 17, 2024
174c6fa
Remove portals addon
keianhzo Apr 17, 2024
56f6567
Update addon version
keianhzo Apr 18, 2024
f3d12c4
Update addons
keianhzo Apr 24, 2024
2240897
BGs initial support
keianhzo May 8, 2024
0956082
Packages updates
keianhzo May 8, 2024
5920ae4
Better handle of bitECS/addons state
keianhzo May 9, 2024
fe03f9c
Add lost trigger collision group
keianhzo May 10, 2024
4f4f186
Skip homepage when creating a new room
keianhzo May 10, 2024
5beeac1
Update BGs add-on
keianhzo May 10, 2024
b16f757
Add-ons docs
keianhzo May 13, 2024
e03ce8b
Fix format
keianhzo May 13, 2024
672c680
Fix image size
keianhzo May 13, 2024
644d101
Restore dockerfile
keianhzo May 13, 2024
d616838
Add add-on preferences update support
keianhzo May 21, 2024
129b460
Add post-processing addon
keianhzo May 23, 2024
7c20ad5
Use legacy peers for storybook
keianhzo May 23, 2024
905abd9
Pin addons versions
keianhzo May 24, 2024
c02bb8d
remove unnecessary legacy-peer-deps
keianhzo Jun 3, 2024
5b9ce83
Fix admin store access
keianhzo Jun 3, 2024
bccf94d
Update admin store ref
keianhzo Jun 3, 2024
064441a
Fix addon configuration
keianhzo Jun 13, 2024
392782e
Merge branch 'master' into addons
Exairnous Nov 15, 2024
ec5835b
[Mozilla Branding Removal] Update dependency links
Exairnous Nov 15, 2024
b0c1a7a
Unpin addons versions
Exairnous Nov 15, 2024
7a99b6f
Make dependency URL capitalization consistent
Exairnous Nov 15, 2024
e449f66
Merge branch 'master' into addons (likely breaks the branch)
Exairnous Nov 17, 2024
348c602
Revert "Merge branch 'master' into addons (likely breaks the branch)"
Exairnous Nov 17, 2024
2d34e73
Merge branch 'master' into addons
Exairnous Dec 29, 2024
b1aa095
Documents that several addons are included with the addons branch
DougReeder Feb 25, 2025
926422f
Update doc/add-ons.md
DougReeder Feb 27, 2025
6f3d92a
Update doc/add-ons.md
DougReeder Feb 27, 2025
0d9c116
Merge pull request #6531 from Hubs-Foundation/addons-doc
Exairnous Mar 4, 2025
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
Prev Previous commit
Next Next commit
BGs initial support
  • Loading branch information
keianhzo committed May 8, 2024
commit 22408979465ba6f6537e08aa229b38b7f8423de2
3 changes: 2 additions & 1 deletion addons.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"addons": [
"hubs-duck-addon",
"hubs-portals-addon"
"hubs-portals-addon",
"hubs-behavior-graphs-addon"
]
}
76 changes: 56 additions & 20 deletions src/addons.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { App } from "./app";
import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader";
import { App, HubsWorld } from "./app";
import { prefabs } from "./prefabs/prefabs";

import {
Expand All @@ -12,6 +13,11 @@ import {
import configs from "./utils/configs";
import { commonInflators, gltfInflators, jsxInflators } from "./utils/jsx-entity";
import { networkableComponents, schemas } from "./utils/network-schemas";
import { gltfPluginsExtra } from "./components/gltf-model-plus";
import { GLTFLinkResolverFn, gltfLinkResolvers } from "./inflators/model";
import { Object3D } from "three";
import { extraSections } from "./react-components/debug-panel/ECSSidebar";
import { shouldUseNewLoader } from "./hubs";

function getNextIdx(slot: Array<SystemConfigT>, system: SystemConfigT) {
return slot.findIndex(item => {
Expand All @@ -25,16 +31,14 @@ function registerSystem(system: SystemConfigT) {
slot = APP.addon_systems.setup;
} else if (system.order < SystemOrderE.PostPhysics) {
slot = APP.addon_systems.prePhysics;
} else if (system.order < SystemOrderE.MatricesUpdate) {
} else if (system.order < SystemOrderE.BeforeMatricesUpdate) {
slot = APP.addon_systems.postPhysics;
} else if (system.order < SystemOrderE.BeforeRender) {
slot = APP.addon_systems.beforeRender;
slot = APP.addon_systems.postPhysics;
} else if (system.order < SystemOrderE.AfterRender) {
slot = APP.addon_systems.afterRender;
} else if (system.order < SystemOrderE.PostProcessing) {
slot = APP.addon_systems.postProcessing;
slot = APP.addon_systems.beforeRender;
} else {
slot = APP.addon_systems.tearDown;
slot = APP.addon_systems.afterRender;
}
const nextIdx = getNextIdx(slot, system);
slot.splice(nextIdx, 0, system);
Expand Down Expand Up @@ -92,6 +96,10 @@ export interface InternalAddonConfigT {
config?: JSON | undefined;
}
type AddonConfigT = Omit<InternalAddonConfigT, "enabled" | "config">;
export type AdminAddonConfig = {
enabled: boolean;
config: JSON;
};

const pendingAddons = new Map<AddonIdT, InternalAddonConfigT>();
export const addons = new Map<AddonIdT, AddonConfigT>();
Expand All @@ -101,6 +109,44 @@ export function registerAddon(id: AddonIdT, config: AddonConfigT) {
pendingAddons.set(id, config);
}

export type GLTFParserCallbackFn = (parser: GLTFParser) => GLTFLoaderPlugin;
export function registerGLTFLoaderPlugin(callback: GLTFParserCallbackFn): void {
gltfPluginsExtra.push(callback);
}
export function registerGLTFLinkResolver(resolver: GLTFLinkResolverFn): void {
gltfLinkResolvers.push(resolver);
}
export function registerECSSidebarSection(section: (world: HubsWorld, selectedObj: Object3D) => React.JSX.Element) {
extraSections.push(section);
}

export function getAddonConfig(id: string): AdminAddonConfig {
const adminAddonsConfig = configs.feature("addons_config");
let adminAddonConfig = {
enabled: false,
config: {} as JSON
};
if (adminAddonsConfig && id in adminAddonsConfig) {
adminAddonConfig = adminAddonsConfig[id];
}
return adminAddonConfig;
}

export function isAddonEnabled(app: App, id: string): boolean {
let enabled = false;
if (shouldUseNewLoader()) {
if (app.hub?.user_data && "addons" in app.hub?.user_data && id in app.hub.user_data["addons"]) {
enabled = app.hub.user_data.addons[id];
} else {
const adminAddonsConfig = getAddonConfig(id);
if (adminAddonsConfig) {
enabled = adminAddonsConfig.enabled;
}
}
}
return enabled;
}

export function onAddonsInit(app: App) {
app.scene?.addEventListener("hub_updated", () => {
for (const [id, addon] of pendingAddons) {
Expand All @@ -110,13 +156,7 @@ export function onAddonsInit(app: App) {
addons.set(id, addon);
}

if (app.hub?.user_data && `addon_${id}` in app.hub.user_data) {
addon.enabled = app.hub.user_data[`addon_${id}`];
} else {
addon.enabled = false;
}

if (!addon.enabled) {
if (!isAddonEnabled(app, id)) {
continue;
}

Expand Down Expand Up @@ -171,12 +211,8 @@ export function onAddonsInit(app: App) {
}

if (addon.onReady) {
let config;
const addonsConfig = configs.feature("addons_config");
if (addonsConfig && id in addonsConfig) {
config = addonsConfig[id];
}
addon.onReady(app, config);
const adminAddonConfig = getAddonConfig(id);
addon.onReady(app, adminAddonConfig.config);
}
}
pendingAddons.clear();
Expand Down
10 changes: 5 additions & 5 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PositionalAudio,
Scene,
sRGBEncoding,
Texture,
WebGLRenderer
} from "three";
import { AudioSettings, SourceType } from "./components/audio-params";
Expand Down Expand Up @@ -54,6 +55,7 @@ export interface HubsWorld extends IWorld {
nid2eid: Map<number, number>;
eid2obj: Map<number, Object3D>;
eid2mat: Map<number, Material>;
eid2tex: Map<number, Texture>;
time: { delta: number; elapsed: number; tick: number };
}

Expand Down Expand Up @@ -110,12 +112,9 @@ export class App {
setup: new Array<{ order: number; system: SystemT }>(),
prePhysics: new Array<{ order: number; system: SystemT }>(),
postPhysics: new Array<{ order: number; system: SystemT }>(),
matricesUpdate: new Array<{ order: number; system: SystemT }>(),
beforeMatricesUpdate: new Array<{ order: number; system: SystemT }>(),
beforeRender: new Array<{ order: number; system: SystemT }>(),
render: new Array<{ order: number; system: SystemT }>(),
afterRender: new Array<{ order: number; system: SystemT }>(),
postProcessing: new Array<{ order: number; system: SystemT }>(),
tearDown: new Array<{ order: number; system: SystemT }>()
afterRender: new Array<{ order: number; system: SystemT }>()
};

RENDER_ORDER = {
Expand All @@ -135,6 +134,7 @@ export class App {
// TODO: Create accessor / update methods for these maps / set
this.world.eid2obj = new Map();
this.world.eid2mat = new Map();
this.world.eid2tex = new Map();

this.world.nid2eid = new Map();
this.world.deletedNids = new Set();
Expand Down
47 changes: 45 additions & 2 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,43 @@ export const MediaFrame = defineComponent({
previewingNid: Types.eid,
flags: Types.ui8
});
export const MediaRoot = defineComponent();
export const NetworkedText = defineComponent({
text: Types.ui8,
anchorX: Types.ui8,
anchorY: Types.ui8,
color: Types.ui32,
curveRadius: Types.f32,
direction: Types.ui8,
fillOpacity: Types.f32,
fontUrl: Types.ui8,
fontSize: Types.f32,
letterSpacing: Types.f32,
lineHeight: Types.ui8,
textAlign: Types.ui8,
outlineWidth: Types.ui8,
outlineColor: Types.ui32,
outlineBlur: Types.ui8,
outlineOffsetX: Types.ui8,
outlineOffsetY: Types.ui8,
outlineOpacity: Types.f32,
strokeWidth: Types.ui8,
strokeColor: Types.ui32,
strokeOpacity: Types.ui32,
textIndent: Types.ui32,
whiteSpace: Types.ui8,
overflowWrap: Types.ui8,
opacity: Types.f32,
side: Types.ui8,
maxWidth: Types.f32
});
NetworkedText.text[$isStringType] = true;
NetworkedText.lineHeight[$isStringType] = true;
NetworkedText.outlineWidth[$isStringType] = true;
NetworkedText.outlineBlur[$isStringType] = true;
NetworkedText.outlineOffsetX[$isStringType] = true;
NetworkedText.outlineOffsetY[$isStringType] = true;
NetworkedText.strokeWidth[$isStringType] = true;
export const TextTag = defineComponent();
export const ReflectionProbe = defineComponent();
export const Slice9 = defineComponent({
Expand All @@ -61,7 +98,9 @@ export const SpotLightTag = defineComponent();
export const CursorRaycastable = defineComponent();
export const RemoteHoverTarget = defineComponent();
export const NotRemoteHoverTarget = defineComponent();
export const Holdable = defineComponent();
export const Holdable = defineComponent({
flags: Types.ui8
});
export const RemoveNetworkedEntityButton = defineComponent();
export const Interacted = defineComponent();
export const HandRight = defineComponent();
Expand Down Expand Up @@ -276,9 +315,12 @@ export const LoopAnimation = defineComponent();
*/
export const LoopAnimationData = new Map();
export const NetworkedVideo = defineComponent({
src: Types.ui8,
time: Types.f32,
flags: Types.ui8
flags: Types.ui8,
projection: Types.ui8
});
NetworkedVideo.src[$isStringType] = true;
export const VideoMenuItem = defineComponent();
export const VideoMenu = defineComponent({
videoRef: Types.eid,
Expand Down Expand Up @@ -393,6 +435,7 @@ export const Billboard = defineComponent({
onlyY: Types.ui8
});
export const MaterialTag = defineComponent();
export const TextureTag = defineComponent();
export const UVScroll = defineComponent({
speed: [Types.f32, 2],
increment: [Types.f32, 2],
Expand Down
8 changes: 8 additions & 0 deletions src/bit-systems/audio-emitter-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AudioType, SourceType } from "../components/audio-params";
import { AudioSystem } from "../systems/audio-system";
import { applySettings, getCurrentAudioSettings, updateAudioSettings } from "../update-audio-settings";
import { addObject3DComponent, swapObject3DComponent } from "../utils/jsx-entity";
import { EntityID } from "../utils/networking-types";

export type AudioObject3D = StereoAudio | PositionalAudio;
type AudioConstructor<T> = new (listener: ThreeAudioListener) => T;
Expand Down Expand Up @@ -55,6 +56,13 @@ function swapAudioType<T extends AudioObject3D>(
swapObject3DComponent(world, eid, newAudio);
}

export function swapAudioSrc(world: HubsWorld, videoEid: EntityID, audioEid: EntityID) {
const audio = world.eid2obj.get(audioEid)! as AudioObject3D;
const video = MediaVideoData.get(videoEid)!;
audio.setMediaElementSource(video);
video.volume = 1;
}

export function makeAudioEntity(world: HubsWorld, source: number, sourceType: SourceType, audioSystem: AudioSystem) {
const eid = addEntity(world);
APP.sourceType.set(eid, sourceType);
Expand Down
8 changes: 6 additions & 2 deletions src/bit-systems/loop-animation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addComponent, defineQuery, enterQuery, exitQuery, hasComponent, removeComponent } from "bitecs";
import { AnimationAction, AnimationClip, AnimationMixer, LoopRepeat } from "three";
import { AnimationClip, LoopRepeat } from "three";
import {
MixerAnimatable,
MixerAnimatableData,
Expand Down Expand Up @@ -47,12 +47,16 @@ const getActiveClips = (

export function loopAnimationSystem(world: HubsWorld): void {
loopAnimationInitializeEnterQuery(world).forEach((eid: number): void => {
const params = LoopAnimationInitializeData.get(eid)!;
if (!params.length) {
return;
}

const object = world.eid2obj.get(eid)!;
const mixer = MixerAnimatableData.get(eid)!;

addComponent(world, LoopAnimation, eid);

const params = LoopAnimationInitializeData.get(eid)!;
const activeAnimations = [];

for (let i = 0; i < params.length; i++) {
Expand Down
6 changes: 4 additions & 2 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
Rigidbody,
MediaLoaderOffset,
MediaVideo,
NetworkedTransform
NetworkedTransform,
MediaRoot
} from "../bit-components";
import { inflatePhysicsShape, Shape } from "../inflators/physics-shape";
import { ErrorObject } from "../prefabs/error-object";
Expand Down Expand Up @@ -204,7 +205,7 @@ class UnsupportedMediaTypeError extends Error {
}
}

type MediaInfo = {
export type MediaInfo = {
accessibleUrl: string;
canonicalUrl: string;
canonicalAudioUrl: string | null;
Expand Down Expand Up @@ -283,6 +284,7 @@ function* loadMedia(world: HubsWorld, eid: EntityID) {
try {
const urlData = (yield resolveMediaInfo(src)) as MediaInfo;
media = yield* loadByMediaType(world, eid, urlData);
addComponent(world, MediaRoot, media);
addComponent(world, MediaLoaded, media);
addComponent(world, MediaInfo, media);
MediaInfo.accessibleUrl[media] = APP.getSid(urlData.accessibleUrl);
Expand Down
Loading
Loading