diff --git a/src/bit-components.js b/src/bit-components.js
index d4134ea535..e9a8cad58f 100644
--- a/src/bit-components.js
+++ b/src/bit-components.js
@@ -273,6 +273,7 @@ EnvironmentSettings.map = new Map();
// TODO: Store this data elsewhere, since only one or two will ever exist.
export const ObjectMenu = defineComponent({
+ backgroundRef: Types.eid,
pinButtonRef: Types.eid,
unpinButtonRef: Types.eid,
cameraFocusButtonRef: Types.eid,
diff --git a/src/bit-systems/object-menu.ts b/src/bit-systems/object-menu.ts
index 3caf5495c6..ad40db7c46 100644
--- a/src/bit-systems/object-menu.ts
+++ b/src/bit-systems/object-menu.ts
@@ -247,6 +247,11 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) {
world.eid2obj.get(ObjectMenu.scaleButtonRef[menu])!.visible = visible && APP.hubChannel!.can("spawn_and_move_media");
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
+ // visible buttons without accounting for the background plane.
+ const same = ObjectMenuTransform.prevObjectRef[menu] === ObjectMenuTransform.targetObjectRef[menu];
+ world.eid2obj.get(ObjectMenu.backgroundRef[menu])!.visible = visible && same;
+
// Hide unimplemented features for now.
// TODO: Implement and show the buttons.
world.eid2obj.get(ObjectMenu.cameraFocusButtonRef[menu])!.visible = false;
diff --git a/src/inflators/plane.ts b/src/inflators/plane.ts
new file mode 100644
index 0000000000..6a34c4251d
--- /dev/null
+++ b/src/inflators/plane.ts
@@ -0,0 +1,37 @@
+import { addObject3DComponent } from "../utils/jsx-entity";
+import { HubsWorld } from "../app";
+import { EntityID } from "../utils/networking-types";
+import { Mesh, MeshBasicMaterial, PlaneGeometry } from "three";
+
+const MATERIAL_DEFAULTS = {
+ color: "#000000",
+ opacity: 1,
+ transparent: false
+};
+
+const PLANE_DEFAULTS = {
+ width: 1,
+ height: 1,
+ material: MATERIAL_DEFAULTS
+};
+
+export interface MeshBasicMaterialParams {
+ color?: string;
+ opacity?: number;
+ transparent?: boolean;
+}
+
+export interface PlaneParams {
+ width?: number;
+ height?: number;
+ material?: MeshBasicMaterialParams;
+}
+
+export function inflatePlane(world: HubsWorld, eid: EntityID, params: PlaneParams) {
+ params = Object.assign({}, PLANE_DEFAULTS, params);
+ params.material = Object.assign({}, MATERIAL_DEFAULTS, params.material);
+ const geometry = new PlaneGeometry(params.width, params.height);
+ const material = new MeshBasicMaterial({ ...params.material });
+ const obj = new Mesh(geometry, material);
+ addObject3DComponent(world, eid, obj);
+}
diff --git a/src/prefabs/object-menu.tsx b/src/prefabs/object-menu.tsx
index 2bf4513bf8..d3348cd5de 100644
--- a/src/prefabs/object-menu.tsx
+++ b/src/prefabs/object-menu.tsx
@@ -5,6 +5,7 @@ import { Button3D, BUTTON_TYPES } from "./button3D";
import rotateIconSrc from "../assets/rotate-action.png";
import scaleIconSrc from "../assets/scale-action.png";
import removeIconSrc from "../assets/remove-action.png";
+import { Plane } from "./plane";
export async function loadObjectMenuButtonIcons() {
return Promise.all([
@@ -221,6 +222,7 @@ function ScaleButton(props: Attrs) {
// prettier-ignore
const position = {
+ background: [ 0, 0, uiZ - 0.0005] as ArrayVec3,
pin: [ 0, 0.125, uiZ] as ArrayVec3,
unpin: [ 0, 0.125, uiZ] as ArrayVec3,
focus: [-0.25, 0.375, uiZ] as ArrayVec3,
@@ -239,6 +241,7 @@ const position = {
export function ObjectMenuPrefab() {
const refs = {
+ background: createRef(),
pin: createRef(),
unpin: createRef(),
focus: createRef(),
@@ -260,6 +263,7 @@ export function ObjectMenuPrefab() {
name="Interactable Object Menu"
objectMenuTransform={{ center: true }}
objectMenu={{
+ backgroundRef: refs.background,
pinButtonRef: refs.pin,
unpinButtonRef: refs.unpin,
cameraFocusButtonRef: refs.focus,
@@ -276,6 +280,14 @@ export function ObjectMenuPrefab() {
scaleButtonRef: refs.scale
}}
>
+
diff --git a/src/prefabs/plane.tsx b/src/prefabs/plane.tsx
new file mode 100644
index 0000000000..2d3ce8c985
--- /dev/null
+++ b/src/prefabs/plane.tsx
@@ -0,0 +1,19 @@
+/** @jsx createElementEntity */
+import { Attrs, createElementEntity } from "../utils/jsx-entity";
+
+export interface PlaneParams extends Attrs {
+ width?: number;
+ height?: number;
+ material?: MeshBasicMaterialParams;
+}
+
+export interface MeshBasicMaterialParams extends Attrs {
+ color?: string;
+ opacity?: number;
+ transparent?: boolean;
+ // TODO Add the rest of the material properties
+}
+
+export function Plane({ width, height, material, name = "Plane", ...props }: PlaneParams) {
+ return ;
+}
diff --git a/src/utils/jsx-entity.ts b/src/utils/jsx-entity.ts
index 251dd807d3..1be91cfde1 100644
--- a/src/utils/jsx-entity.ts
+++ b/src/utils/jsx-entity.ts
@@ -101,6 +101,7 @@ import { HubsVideoTexture } from "../textures/HubsVideoTexture";
import { inflateMediaLink, MediaLinkParams } from "../inflators/media-link";
import { inflateObjectMenuTarget, ObjectMenuTargetParams } from "../inflators/object-menu-target";
import { inflateObjectMenuTransform, ObjectMenuTransformParams } from "../inflators/object-menu-transform";
+import { inflatePlane, PlaneParams } from "../inflators/plane";
preload(
new Promise(resolve => {
@@ -314,6 +315,7 @@ export interface JSXComponentData extends ComponentData {
networkedFloatyObject?: any;
networkedTransform?: any;
objectMenu?: {
+ backgroundRef: Ref;
pinButtonRef: Ref;
unpinButtonRef: Ref;
cameraFocusButtonRef: Ref;
@@ -365,6 +367,7 @@ export interface JSXComponentData extends ComponentData {
inspectable?: boolean;
objectMenuTransform?: OptionalParams;
objectMenuTarget?: OptionalParams;
+ plane?: PlaneParams;
}
export interface GLTFComponentData extends ComponentData {
@@ -480,7 +483,8 @@ const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = {
video: inflateVideo,
link: inflateLink,
objectMenuTransform: inflateObjectMenuTransform,
- objectMenuTarget: inflateObjectMenuTarget
+ objectMenuTarget: inflateObjectMenuTarget,
+ plane: inflatePlane
};
export const gltfInflators: Required<{ [K in keyof GLTFComponentData]: InflatorFn }> = {