diff --git a/src/bit-components.js b/src/bit-components.js index 4580877cdd..3ff4bd257b 100644 --- a/src/bit-components.js +++ b/src/bit-components.js @@ -305,6 +305,7 @@ export const ObjectMenu = defineComponent({ handlingTargetRef: Types.eid, flags: Types.ui8 }); +export const ObjectDropped = defineComponent(); // TODO: Store this data elsewhere, since only one or two will ever exist. export const LinkHoverMenu = defineComponent({ targetObjectRef: Types.eid, diff --git a/src/bit-systems/object-menu.ts b/src/bit-systems/object-menu.ts index 9f4db41376..7ac02f4d83 100644 --- a/src/bit-systems/object-menu.ts +++ b/src/bit-systems/object-menu.ts @@ -14,9 +14,20 @@ import { Rigidbody, Deleting, Deletable, - MediaLoader + MediaLoader, + ObjectDropped, + FloatyObject, + Owned, + MediaVideo, + MediaImage, + MediaPDF } from "../bit-components"; -import { anyEntityWith, findAncestorWithComponent, findAncestorWithComponents } from "../utils/bit-utils"; +import { + anyEntityWith, + findAncestorWithComponent, + findAncestorWithComponents, + hasAnyComponent +} from "../utils/bit-utils"; import { createNetworkedEntity } from "../utils/create-networked-entity"; import HubChannel from "../utils/hub-channel"; import type { EntityID } from "../utils/networking-types"; @@ -28,7 +39,8 @@ import { TRANSFORM_MODE } from "../components/transform-object-button"; import { ScalingHandler } from "../components/scale-button"; import { canPin, setPinned } from "../utils/bit-pinning-helper"; import { ObjectMenuTransformFlags } from "../inflators/object-menu-transform"; - +import { COLLISION_LAYERS } from "../constants"; +import { FLOATY_OBJECT_FLAGS } from "../systems/floaty-object-system"; // Working variables. const _vec3_1 = new Vector3(); const _vec3_2 = new Vector3(); @@ -178,7 +190,7 @@ function handleClicks(world: HubsWorld, menu: EntityID, hubChannel: HubChannel) ObjectMenu.flags[menu] &= ~ObjectMenuFlags.Visible; deleteTheDeletableAncestor(world, ObjectMenu.targetRef[menu]); } else if (clicked(world, ObjectMenu.dropButtonRef[menu])) { - console.log("Clicked drop"); + addComponent(world, ObjectDropped, ObjectMenu.targetRef[menu]); } else if (clicked(world, ObjectMenu.inspectButtonRef[menu])) { console.log("Clicked inspect"); } else if (clicked(world, ObjectMenu.deserializeDrawingButtonRef[menu])) { @@ -247,6 +259,8 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) { const canISpawnMove = APP.hubChannel.can("spawn_and_move_media"); const canIPin = !!(target && canPin(APP.hubChannel, target)); const isEntityPinned = isPinned(target); + const media = MediaLoader.mediaRef[target]; + const isVideoImagePdf = hasAnyComponent(world, [MediaVideo, MediaImage, MediaPDF], media); // Parent visibility doesn't block raycasting, so we must set each button to be invisible // TODO: Ensure that children of invisible entities aren't raycastable @@ -259,6 +273,8 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) { world.eid2obj.get(ObjectMenu.scaleButtonRef[menu])!.visible = visible && (!isEntityPinned || canIPin) && canISpawnMove; world.eid2obj.get(ObjectMenu.openLinkButtonRef[menu])!.visible = visible; + world.eid2obj.get(ObjectMenu.dropButtonRef[menu])!.visible = + !isVideoImagePdf && !isEntityPinned && !hasComponent(world, ObjectDropped, target); // 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. @@ -272,7 +288,6 @@ function updateVisibility(world: HubsWorld, menu: EntityID, frozen: boolean) { world.eid2obj.get(ObjectMenu.deserializeDrawingButtonRef[menu])!.visible = false; world.eid2obj.get(ObjectMenu.mirrorButtonRef[menu])!.visible = false; world.eid2obj.get(ObjectMenu.inspectButtonRef[menu])!.visible = false; - world.eid2obj.get(ObjectMenu.dropButtonRef[menu])!.visible = false; world.eid2obj.get(ObjectMenu.refreshButtonRef[menu])!.visible = false; } @@ -280,6 +295,8 @@ const hoveredQuery = defineQuery([HoveredRemoteRight]); const heldQuery = defineQuery([HeldRemoteRight]); const heldEnterQuery = enterQuery(heldQuery); const heldExitQuery = exitQuery(heldQuery); +const objectDroppedQuery = defineQuery([ObjectDropped]); +const objectDroppedEnterQuery = enterQuery(objectDroppedQuery); export function objectMenuSystem(world: HubsWorld, sceneIsFrozen: boolean, hubChannel: HubChannel) { const menu = anyEntityWith(world, ObjectMenu)!; @@ -301,5 +318,25 @@ export function objectMenuSystem(world: HubsWorld, sceneIsFrozen: boolean, hubCh scalingHandler.tick(); } } + + objectDroppedEnterQuery(world).forEach(eid => { + takeOwnership(world, eid); + if (!hasComponent(world, Owned, eid)) return; + + const physicsSystem = APP.scene?.systems["hubs-systems"].physicsSystem; + FloatyObject.flags[eid] &= ~FLOATY_OBJECT_FLAGS.MODIFY_GRAVITY_ON_RELEASE; + physicsSystem.updateRigidBody(eid, { + type: "dynamic", + gravity: { x: 0, y: -9.8, z: 0 }, + angularDamping: 0.01, + linearDamping: 0.01, + linearSleepingThreshold: 1.6, + angularSleepingThreshold: 2.5, + collisionFilterMask: COLLISION_LAYERS.DEFAULT_INTERACTABLE + }); + + physicsSystem.activateBody(Rigidbody.bodyId[eid]); + }); + updateVisibility(world, menu, sceneIsFrozen); } diff --git a/src/prefabs/object-menu.tsx b/src/prefabs/object-menu.tsx index dc7e26a853..57b5cf46bd 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 dropIconSrc from "../assets/drop-action.png"; import { Plane } from "./plane"; import { FrontSide } from "three"; import { Layers } from "../camera-layers"; @@ -13,7 +14,8 @@ export async function loadObjectMenuButtonIcons() { return Promise.all([ loadTexture(rotateIconSrc, 1, "image/png"), loadTexture(scaleIconSrc, 1, "image/png"), - loadTexture(removeIconSrc, 1, "image/png") + loadTexture(removeIconSrc, 1, "image/png"), + loadTexture(dropIconSrc, 1, "image/png") ]); } @@ -93,14 +95,15 @@ function RemoveButton(props: Attrs) { } function DropButton(props: Attrs) { + const { texture, cacheKey } = loadTextureFromCache(dropIconSrc, 1); return ( ); @@ -230,7 +233,7 @@ const position = { focus: [-0.25, 0.375, uiZ] as ArrayVec3, track: [ 0.25, 0.375, uiZ] as ArrayVec3, remove: [ 0, -0.275, uiZ] as ArrayVec3, - drop: [ 0.1, -0.625, uiZ] as ArrayVec3, + drop: [ 0, -0.425, uiZ] as ArrayVec3, inspect: [ -0.1, -0.625, uiZ] as ArrayVec3, deserializeDrawing: [ -0.3, -0.625, uiZ] as ArrayVec3, openLink: [ 0.25, -0.275, uiZ] as ArrayVec3, diff --git a/src/systems/bit-constraints-system.js b/src/systems/bit-constraints-system.js index 0af21892b7..1fa79aea1a 100644 --- a/src/systems/bit-constraints-system.js +++ b/src/systems/bit-constraints-system.js @@ -44,6 +44,7 @@ const releaseBodyOptions = { activationState: ACTIVE_TAG }; function add(world, physicsSystem, interactor, constraintComponent, entities) { for (let i = 0; i < entities.length; i++) { const eid = findAncestorEntity(world, entities[i], ancestor => hasComponent(world, Rigidbody, ancestor)); + if (!entityExists(world, eid)) continue; physicsSystem.updateRigidBodyOptions(eid, grabBodyOptions); physicsSystem.addConstraint(interactor, Rigidbody.bodyId[eid], Rigidbody.bodyId[interactor], {}); addComponent(world, Constraint, eid);