From ec12e4c3a04f9ed23a5f027f583d5c3213bd3dc0 Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Thu, 2 May 2024 13:09:07 +0200 Subject: [PATCH 1/3] Scene grabbables support --- src/bit-components.js | 10 +- src/bit-systems/camera-tool.js | 2 +- src/bit-systems/interactable-system.ts | 37 ++++ src/bit-systems/media-loading.ts | 6 +- src/bit-systems/object-menu.ts | 4 +- src/bit-systems/scene-loading.ts | 1 - src/components/body-helper.js | 2 + src/components/media-loader.js | 6 +- src/constants.ts | 2 +- src/inflators/media-frame.js | 42 +++- src/inflators/model.tsx | 10 +- src/inflators/rigid-body.ts | 228 +++++++++++++++++++--- src/inflators/spawner.ts | 1 - src/prefabs/media.tsx | 2 +- src/react-components/room/RoomSidebar.js | 4 +- src/systems/bit-constraints-system.js | 16 +- src/systems/bit-media-frames.js | 31 +-- src/systems/bit-physics.ts | 48 ++++- src/systems/floaty-object-system.js | 14 +- src/systems/hold-system.js | 12 +- src/systems/hubs-systems.ts | 2 + src/systems/networked-transform.js | 16 +- src/systems/on-ownership-lost.js | 7 +- src/systems/physics-system.js | 23 +-- src/utils/bit-utils.ts | 13 +- src/utils/jsx-entity.ts | 27 +-- src/utils/network-schemas.ts | 3 + src/utils/networked-media-frame-schema.ts | 34 +++- src/utils/networked-rigid-body.ts | 31 +++ 29 files changed, 492 insertions(+), 142 deletions(-) create mode 100644 src/bit-systems/interactable-system.ts create mode 100644 src/utils/networked-rigid-body.ts diff --git a/src/bit-components.js b/src/bit-components.js index 1bafeafa25..7febf81698 100644 --- a/src/bit-components.js +++ b/src/bit-components.js @@ -21,7 +21,9 @@ export const Owned = defineComponent(); export const EntityStateDirty = defineComponent(); export const NetworkedMediaFrame = defineComponent({ capturedNid: Types.ui32, - scale: [Types.f32, 3] + scale: [Types.f32, 3], + flags: Types.ui8, + mediaType: Types.ui8 }); NetworkedMediaFrame.capturedNid[$isStringType] = true; @@ -119,7 +121,11 @@ export const Rigidbody = defineComponent({ activationState: Types.ui8, collisionFilterGroup: Types.ui32, collisionFilterMask: Types.ui32, - flags: Types.ui8 + flags: Types.ui8, + initialCollisionFilterMask: Types.ui32 +}); +export const NetworkedRigidBody = defineComponent({ + prevType: Types.ui8 }); export const PhysicsShape = defineComponent({ bodyId: Types.ui16, diff --git a/src/bit-systems/camera-tool.js b/src/bit-systems/camera-tool.js index 0ce7cf8411..f403300cf4 100644 --- a/src/bit-systems/camera-tool.js +++ b/src/bit-systems/camera-tool.js @@ -211,7 +211,7 @@ function rotateWithRightClick(world, camera) { userinput.get(paths.device.mouse.buttonRight) ) { const rightCursor = anyEntityWith(world, RemoteRight); - physicsSystem.updateRigidBodyOptions(camera, { type: "kinematic" }); + physicsSystem.updateRigidBody(camera, { type: "kinematic" }); transformSystem.startTransform(world.eid2obj.get(camera), world.eid2obj.get(rightCursor), { mode: "cursor" }); diff --git a/src/bit-systems/interactable-system.ts b/src/bit-systems/interactable-system.ts new file mode 100644 index 0000000000..309f5600ec --- /dev/null +++ b/src/bit-systems/interactable-system.ts @@ -0,0 +1,37 @@ +import { addComponent, defineQuery, enterQuery } from "bitecs"; +import { HubsWorld } from "../app"; +import { Holdable, MediaContentBounds, Networked, Rigidbody } from "../bit-components"; +import { getBox } from "../utils/auto-box-collider"; +import { Mesh, Vector3 } from "three"; +import { takeSoftOwnership } from "../utils/take-soft-ownership"; +import { EntityID } from "../utils/networking-types"; +import { COLLISION_LAYERS } from "../constants"; + +const tmpVector = new Vector3(); + +const interactableQuery = defineQuery([Holdable, Rigidbody, Networked]); +const interactableEnterQuery = enterQuery(interactableQuery); +export function interactableSystem(world: HubsWorld) { + interactableEnterQuery(world).forEach((eid: EntityID) => { + // Somebody must own a scene grabbable otherwise the networked transform will fight with physics. + if (Networked.creator[eid] === APP.getSid("scene") && Networked.owner[eid] === APP.getSid("reticulum")) { + takeSoftOwnership(world, eid); + } + + const obj = world.eid2obj.get(eid); + let hasMesh = false; + obj?.traverse(child => { + if ((child as Mesh).isMesh) { + hasMesh = true; + } + }); + + // If it has media frame collision mask, it needs to have content bounds + if (hasMesh && Rigidbody.collisionFilterMask[eid] & COLLISION_LAYERS.MEDIA_FRAMES) { + const box = getBox(obj, obj); + box.getSize(tmpVector); + addComponent(world, MediaContentBounds, eid); + MediaContentBounds.bounds[eid].set(tmpVector.toArray()); + } + }); +} diff --git a/src/bit-systems/media-loading.ts b/src/bit-systems/media-loading.ts index 98832a80dd..b82e7cfcc4 100644 --- a/src/bit-systems/media-loading.ts +++ b/src/bit-systems/media-loading.ts @@ -359,7 +359,7 @@ export function mediaLoadingSystem(world: HubsWorld) { jobs.add(eid, clearRollbacks => loadAndAnimateMedia(world, eid, clearRollbacks)); }); - mediaLoadingExitQuery(world).forEach(function (eid) { + mediaLoadingExitQuery(world).forEach(function (eid: EntityID) { jobs.stop(eid); if (MediaImageLoaderData.has(eid)) { @@ -406,7 +406,7 @@ export function mediaLoadingSystem(world: HubsWorld) { } }); - mediaLoadingQuery(world).forEach(eid => { + mediaLoadingQuery(world).forEach((eid: EntityID) => { const mediaLoaderObj = world.eid2obj.get(eid)!; transformPosition.fromArray(NetworkedTransform.position[eid]); if (mediaLoaderObj.position.near(transformPosition, 0.001)) { @@ -417,7 +417,7 @@ export function mediaLoadingSystem(world: HubsWorld) { mediaLoadedEnterQuery(world).forEach(() => APP.scene?.emit("listed_media_changed")); mediaLoadedExitQuery(world).forEach(() => APP.scene?.emit("listed_media_changed")); - mediaRefreshEnterQuery(world).forEach(eid => { + mediaRefreshEnterQuery(world).forEach((eid: EntityID) => { if (!jobs.has(eid)) { jobs.add(eid, clearRollbacks => refreshMedia(world, eid, clearRollbacks)); } diff --git a/src/bit-systems/object-menu.ts b/src/bit-systems/object-menu.ts index 51c6702a73..e7545c9936 100644 --- a/src/bit-systems/object-menu.ts +++ b/src/bit-systems/object-menu.ts @@ -105,7 +105,7 @@ function startRotation(world: HubsWorld, menuEid: EntityID, targetEid: EntityID) } const transformSystem = APP.scene!.systems["transform-selected-object"]; const physicsSystem = AFRAME.scenes[0].systems["hubs-systems"].physicsSystem; - physicsSystem.updateRigidBodyOptions(Rigidbody.bodyId[targetEid], { type: "kinematic" }); + physicsSystem.updateRigidBody(Rigidbody.bodyId[targetEid], { type: "kinematic" }); const rightCursorEid = anyEntityWith(world, RemoteRight)!; transformSystem.startTransform(world.eid2obj.get(targetEid)!, world.eid2obj.get(rightCursorEid)!, { mode: TRANSFORM_MODE.CURSOR @@ -137,7 +137,7 @@ function startScaling(world: HubsWorld, menuEid: EntityID, targetEid: EntityID) // TODO: Remove the dependency with AFRAME const transformSystem = (AFRAME as any).scenes[0].systems["transform-selected-object"]; const physicsSystem = AFRAME.scenes[0].systems["hubs-systems"].physicsSystem; - physicsSystem.updateRigidBodyOptions(Rigidbody.bodyId[targetEid], { type: "kinematic" }); + physicsSystem.updateRigidBody(Rigidbody.bodyId[targetEid], { type: "kinematic" }); const rightCursorEid = anyEntityWith(world, RemoteRight)!; scalingHandler = new ScalingHandler(world.eid2obj.get(targetEid), transformSystem); scalingHandler!.objectToScale = world.eid2obj.get(targetEid); diff --git a/src/bit-systems/scene-loading.ts b/src/bit-systems/scene-loading.ts index c782c001b6..5f5a176d91 100644 --- a/src/bit-systems/scene-loading.ts +++ b/src/bit-systems/scene-loading.ts @@ -7,7 +7,6 @@ import { HeightFieldTag, NavMesh, Networked, - PhysicsShape, SceneLoader, ScenePreviewCamera, SceneRoot, diff --git a/src/components/body-helper.js b/src/components/body-helper.js index e7b17d63d7..96496da1e0 100644 --- a/src/components/body-helper.js +++ b/src/components/body-helper.js @@ -1,6 +1,7 @@ import { addComponent, removeComponent } from "bitecs"; import { CONSTANTS } from "three-ammo"; import { Rigidbody } from "../bit-components"; +import { updateBodyParams } from "../inflators/rigid-body"; const ACTIVATION_STATE = CONSTANTS.ACTIVATION_STATE, TYPE = CONSTANTS.TYPE; @@ -41,6 +42,7 @@ AFRAME.registerComponent("body-helper", { this.uuid = this.system.addBody(this.el.object3D, this.data); const eid = this.el.object3D.eid; addComponent(APP.world, Rigidbody, eid); + updateBodyParams(eid, this.data); Rigidbody.bodyId[eid] = this.uuid; //uuid is a lie, it's actually an int }, diff --git a/src/components/media-loader.js b/src/components/media-loader.js index 9c89bbddcf..94961d74f6 100644 --- a/src/components/media-loader.js +++ b/src/components/media-loader.js @@ -284,8 +284,10 @@ 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.object3D, this.el.getObject3D("mesh")).getSize(new THREE.Vector3()); - addComponent(APP.world, MediaContentBounds, el.eid); - MediaContentBounds.bounds[el.eid].set(contentBounds.toArray()); + if (el.eid) { + addComponent(APP.world, MediaContentBounds, el.eid); + MediaContentBounds.bounds[el.eid].set(contentBounds.toArray()); + } el.emit("media-loaded"); }; diff --git a/src/constants.ts b/src/constants.ts index 7dadf4f4ec..1fcb5e30e8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -11,7 +11,7 @@ export enum COLLISION_LAYERS { DEFAULT_INTERACTABLE = INTERACTABLES | ENVIRONMENT | AVATAR | HANDS | MEDIA_FRAMES, UNOWNED_INTERACTABLE = INTERACTABLES | HANDS | MEDIA_FRAMES, DEFAULT_SPAWNER = INTERACTABLES | HANDS -}; +} export enum AAModes { NONE = "NONE", diff --git a/src/inflators/media-frame.js b/src/inflators/media-frame.js index 84db6250ef..d1dccaaace 100644 --- a/src/inflators/media-frame.js +++ b/src/inflators/media-frame.js @@ -15,14 +15,28 @@ export const AxisAlignType = { }; export const MEDIA_FRAME_FLAGS = { - SCALE_TO_BOUNDS: 1 << 0 + SCALE_TO_BOUNDS: 1 << 0, + ACTIVE: 1 << 1, + SNAP_TO_CENTER: 1 << 2, + LOCKED: 1 << 3 +}; + +export const MediaTypes = { + all: MediaType.ALL, + "all-2d": MediaType.ALL_2D, + model: MediaType.MODEL, + image: MediaType.IMAGE, + video: MediaType.VIDEO, + pdf: MediaType.PDF }; const DEFAULTS = { bounds: { x: 1, y: 1, z: 1 }, mediaType: "all", scaleToBounds: true, - align: { x: "center", y: "center", z: "center" } + align: { x: "center", y: "center", z: "center" }, + active: true, + locked: false }; export function inflateMediaFrame(world, eid, componentProps) { componentProps = Object.assign({}, DEFAULTS, componentProps); @@ -68,17 +82,16 @@ export function inflateMediaFrame(world, eid, componentProps) { addComponent(world, MediaFrame, eid, true); addComponent(world, NetworkedMediaFrame, eid, true); + NetworkedMediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.ACTIVE; + if (componentProps.snapToCenter) { + NetworkedMediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.SNAP_TO_CENTER; + } + if (!hasComponent(world, Networked, eid)) addComponent(world, Networked, eid); // Media types accepted - MediaFrame.mediaType[eid] = { - all: MediaType.ALL, - "all-2d": MediaType.ALL_2D, - model: MediaType.MODEL, - image: MediaType.IMAGE, - video: MediaType.VIDEO, - pdf: MediaType.PDF - }[componentProps.mediaType]; + MediaFrame.mediaType[eid] = MediaTypes[componentProps.mediaType]; + NetworkedMediaFrame.mediaType[eid] = MediaFrame.mediaType[eid]; // Bounds MediaFrame.bounds[eid].set([componentProps.bounds.x, componentProps.bounds.y, componentProps.bounds.z]); // Axis alignment @@ -101,6 +114,15 @@ export function inflateMediaFrame(world, eid, componentProps) { if (componentProps.scaleToBounds) flags |= MEDIA_FRAME_FLAGS.SCALE_TO_BOUNDS; MediaFrame.flags[eid] = flags; + if (componentProps.active) { + NetworkedMediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.ACTIVE; + MediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.ACTIVE; + } + if (componentProps.locked) { + NetworkedMediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.LOCKED; + MediaFrame.flags[eid] |= MEDIA_FRAME_FLAGS.LOCKED; + } + inflateRigidBody(world, eid, { type: Type.KINEMATIC, collisionGroup: COLLISION_LAYERS.MEDIA_FRAMES, diff --git a/src/inflators/model.tsx b/src/inflators/model.tsx index 5feefedd72..ad26136b51 100644 --- a/src/inflators/model.tsx +++ b/src/inflators/model.tsx @@ -14,15 +14,7 @@ function camelCase(s: string) { export type ModelParams = { model: Object3D }; // These components are all handled in some special way, not through inflators -const ignoredComponents = [ - "visible", - "frustum", - "frustrum", - "shadow", - "networked", - "animation-mixer", - "loop-animation" -]; +const ignoredComponents = ["visible", "frustum", "frustrum", "shadow", "animation-mixer", "loop-animation"]; function inflateComponents( world: HubsWorld, diff --git a/src/inflators/rigid-body.ts b/src/inflators/rigid-body.ts index 54f2cf55e5..d68f2d664c 100644 --- a/src/inflators/rigid-body.ts +++ b/src/inflators/rigid-body.ts @@ -1,7 +1,8 @@ import { addComponent } from "bitecs"; -import { Rigidbody } from "../bit-components"; +import { NetworkedRigidBody, Rigidbody } from "../bit-components"; import { HubsWorld } from "../app"; import { CONSTANTS } from "three-ammo"; +import { COLLISION_LAYERS } from "../constants"; export enum Type { STATIC = 0, @@ -9,6 +10,12 @@ export enum Type { KINEMATIC } +export enum CollisionGroup { + OBJECTS = "objects", + ENVIRONMENT = "environment", + AVATARS = "avatars" +} + export enum ActivationState { ACTIVE_TAG = 0, ISLAND_SLEEPING = 1, @@ -17,6 +24,23 @@ export enum ActivationState { DISABLE_SIMULATION = 4 } +export type BodyParams = { + type: string; + mass: number; + gravity: { x: number; y: number; z: number }; + linearDamping: number; + angularDamping: number; + linearSleepingThreshold: number; + angularSleepingThreshold: number; + angularFactor: { x: number; y: number; z: number }; + activationState: string; + emitCollisionEvents: boolean; + disableCollision: boolean; + collisionFilterGroup: number; + collisionFilterMask: number; + scaleAutoUpdate: boolean; +}; + export type RigidBodyParams = { type: Type; mass: number; @@ -61,10 +85,50 @@ export const getTypeString = (eid: number) => { return Object.values(CONSTANTS.TYPE)[Rigidbody.type[eid]]; }; -export const getActivationStateString = (eid: number) => { +export const getStringFromActivationState = (eid: number) => { return Object.values(CONSTANTS.ACTIVATION_STATE)[Rigidbody.activationState[eid]]; }; +export const getActivationStateFromString = (activationState: string) => { + switch (activationState) { + case CONSTANTS.ACTIVATION_STATE.ACTIVE_TAG: + return ActivationState.ACTIVE_TAG; + case CONSTANTS.ACTIVATION_STATE.DISABLE_DEACTIVATION: + return ActivationState.DISABLE_DEACTIVATION; + case CONSTANTS.ACTIVATION_STATE.DISABLE_SIMULATION: + return ActivationState.DISABLE_SIMULATION; + case CONSTANTS.ACTIVATION_STATE.ISLAND_SLEEPING: + return ActivationState.ISLAND_SLEEPING; + case CONSTANTS.ACTIVATION_STATE.WANTS_DEACTIVATION: + return ActivationState.WANTS_DEACTIVATION; + } + return ActivationState.ACTIVE_TAG; +}; + +export const getTypeFromBodyType = (type: string) => { + switch (type) { + case "static": + return Type.STATIC; + case "dynamic": + return Type.DYNAMIC; + case "kinematic": + return Type.KINEMATIC; + } + return Type.KINEMATIC; +}; + +export const getBodyTypeFromType = (type: Type) => { + switch (type) { + case Type.STATIC: + return "static"; + case Type.DYNAMIC: + return "dynamic"; + case Type.KINEMATIC: + return "kinematic"; + } + return "kinematic"; +}; + export const getBodyFromRigidBody = (eid: number) => { return { mass: Rigidbody.mass[eid], @@ -78,44 +142,154 @@ export const getBodyFromRigidBody = (eid: number) => { y: Rigidbody.angularFactor[eid][1], z: Rigidbody.angularFactor[eid][2] }, - activationState: getActivationStateString(eid), - emitCollisionEvents: Rigidbody.flags[eid] & RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS, - scaleAutoUpdate: Rigidbody.flags[eid] & RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE, + activationState: getStringFromActivationState(eid), + emitCollisionEvents: (Rigidbody.flags[eid] & RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS) !== 0, + scaleAutoUpdate: (Rigidbody.flags[eid] & RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE) !== 0, type: getTypeString(eid), - disableCollision: Rigidbody.flags[eid] & RIGID_BODY_FLAGS.DISABLE_COLLISION, + disableCollision: (Rigidbody.flags[eid] & RIGID_BODY_FLAGS.DISABLE_COLLISION) !== 0, collisionFilterGroup: Rigidbody.collisionFilterGroup[eid], collisionFilterMask: Rigidbody.collisionFilterMask[eid] }; }; -const updateRigidBody = (eid: number, params: RigidBodyParams) => { - Rigidbody.type[eid] = params.type; - Rigidbody.mass[eid] = params.mass; - Rigidbody.gravity[eid].set(params.gravity); - Rigidbody.linearDamping[eid] = params.linearDamping; - Rigidbody.angularDamping[eid] = params.angularDamping; - Rigidbody.linearSleepingThreshold[eid] = params.linearSleepingThreshold; - Rigidbody.angularSleepingThreshold[eid] = params.angularSleepingThreshold; - Rigidbody.angularFactor[eid].set(params.angularFactor); - Rigidbody.activationState[eid] = params.activationState; - params.emitCollisionEvents && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS); - params.disableCollision && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.DISABLE_COLLISION); - Rigidbody.collisionFilterGroup[eid] = params.collisionGroup; - Rigidbody.collisionFilterMask[eid] = params.collisionMask; - params.scaleAutoUpdate && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE); +export const updateBodyParams = (eid: number, params: Partial) => { + const currentParams = getBodyFromRigidBody(eid); + const bodyParams = Object.assign({}, currentParams, params) as BodyParams; + + Rigidbody.type[eid] = getTypeFromBodyType(bodyParams.type); + Rigidbody.mass[eid] = bodyParams.mass; + Rigidbody.gravity[eid].set([bodyParams.gravity.x, bodyParams.gravity.y, bodyParams.gravity.z]); + Rigidbody.linearDamping[eid] = bodyParams.linearDamping; + Rigidbody.angularDamping[eid] = bodyParams.angularDamping; + Rigidbody.linearSleepingThreshold[eid] = bodyParams.linearSleepingThreshold; + Rigidbody.angularSleepingThreshold[eid] = bodyParams.angularSleepingThreshold; + Rigidbody.angularFactor[eid].set([ + bodyParams.angularFactor.x, + bodyParams.angularFactor.y, + bodyParams.angularFactor.z + ]); + Rigidbody.activationState[eid] = getActivationStateFromString(params.activationState!); + bodyParams.emitCollisionEvents && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS); + bodyParams.disableCollision && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.DISABLE_COLLISION); + Rigidbody.collisionFilterGroup[eid] = bodyParams.collisionFilterGroup; + Rigidbody.collisionFilterMask[eid] = bodyParams.collisionFilterMask; + bodyParams.scaleAutoUpdate && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE); }; -export const updateRigiBodyParams = (eid: number, params: Partial) => { - const currentParams = getBodyFromRigidBody(eid); - const bodyParams = Object.assign({}, currentParams, params); - updateRigidBody(eid, bodyParams); +export const updateRigidBodyParams = (eid: number, params: Partial) => { + if (params.type !== undefined) { + Rigidbody.type[eid] = params.type; + } + if (params.mass !== undefined) { + Rigidbody.mass[eid] = params.mass; + } + if (params.gravity !== undefined) { + Rigidbody.gravity[eid].set(params.gravity); + } + if (params.linearDamping !== undefined) { + Rigidbody.linearDamping[eid] = params.linearDamping; + } + if (params.angularDamping !== undefined) { + Rigidbody.angularDamping[eid] = params.angularDamping; + } + if (params.linearSleepingThreshold !== undefined) { + Rigidbody.linearSleepingThreshold[eid] = params.linearSleepingThreshold; + } + if (params.angularSleepingThreshold !== undefined) { + Rigidbody.angularSleepingThreshold[eid] = params.angularSleepingThreshold; + } + if (params.angularFactor !== undefined) { + Rigidbody.angularFactor[eid].set(params.angularFactor); + } + if (params.activationState !== undefined) { + Rigidbody.activationState[eid] = params.activationState; + } + if (params.emitCollisionEvents !== undefined) { + Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS; + } + if (params.disableCollision !== undefined) { + Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.DISABLE_COLLISION; + } + if (params.scaleAutoUpdate !== undefined) { + Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE; + } + if (params.collisionGroup !== undefined) { + Rigidbody.collisionFilterGroup[eid] = params.collisionGroup; + } + if (params.collisionMask !== undefined) { + Rigidbody.collisionFilterMask[eid] = params.collisionMask; + } }; export function inflateRigidBody(world: HubsWorld, eid: number, params: Partial) { - const bodyParams = Object.assign({}, DEFAULTS, params); + const bodyParams = Object.assign({}, DEFAULTS, params) as RigidBodyParams; addComponent(world, Rigidbody, eid); - updateRigidBody(eid, bodyParams); + addComponent(world, NetworkedRigidBody, eid); + + Rigidbody.type[eid] = bodyParams.type; + Rigidbody.mass[eid] = bodyParams.mass; + Rigidbody.gravity[eid].set(bodyParams.gravity); + Rigidbody.linearDamping[eid] = bodyParams.linearDamping; + Rigidbody.angularDamping[eid] = bodyParams.angularDamping; + Rigidbody.linearSleepingThreshold[eid] = bodyParams.linearSleepingThreshold; + Rigidbody.angularSleepingThreshold[eid] = bodyParams.angularSleepingThreshold; + Rigidbody.angularFactor[eid].set(bodyParams.angularFactor); + Rigidbody.activationState[eid] = bodyParams.activationState; + bodyParams.emitCollisionEvents && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.EMIT_COLLISION_EVENTS); + bodyParams.disableCollision && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.DISABLE_COLLISION); + Rigidbody.collisionFilterGroup[eid] = bodyParams.collisionGroup; + Rigidbody.collisionFilterMask[eid] = bodyParams.collisionMask; + Rigidbody.initialCollisionFilterMask[eid] = bodyParams.collisionMask; + bodyParams.scaleAutoUpdate && (Rigidbody.flags[eid] |= RIGID_BODY_FLAGS.SCALE_AUTO_UPDATE); + NetworkedRigidBody.prevType[eid] = bodyParams.type; + + return eid; +} + +export enum GLTFRigidBodyType { + STATIC = "static", + DYNAMIC = "dynamic", + KINEMATIC = "kinematic" +} + +export enum GLTFRigidBodyCollisionGroup { + OBJECTS = "objects", + ENVIRONMENT = "environment", + AVATARS = "avatars", + MEDIA_FRAMES = "media-frames" +} + +const GLTF_DEFAULTS = { + ...DEFAULTS, + type: GLTFRigidBodyType.DYNAMIC, + collisionGroup: GLTFRigidBodyCollisionGroup.OBJECTS, + collisionMask: [GLTFRigidBodyCollisionGroup.AVATARS] +}; + +const gltfGroupToLayer = { + [GLTFRigidBodyCollisionGroup.OBJECTS]: COLLISION_LAYERS.INTERACTABLES, + [GLTFRigidBodyCollisionGroup.ENVIRONMENT]: COLLISION_LAYERS.ENVIRONMENT, + [GLTFRigidBodyCollisionGroup.AVATARS]: COLLISION_LAYERS.AVATAR, + [GLTFRigidBodyCollisionGroup.MEDIA_FRAMES]: COLLISION_LAYERS.MEDIA_FRAMES +} as const; + +export interface GLTFRigidBodyParams + extends Partial> { + type?: GLTFRigidBodyType; + collisionGroup?: GLTFRigidBodyCollisionGroup; + collisionMask?: GLTFRigidBodyCollisionGroup[]; +} + +export function inflateGLTFRigidBody(world: HubsWorld, eid: number, params: GLTFRigidBodyParams) { + const bodyParams = Object.assign({}, GLTF_DEFAULTS, params); + + inflateRigidBody(world, eid, { + ...bodyParams, + type: Object.values(GLTFRigidBodyType).indexOf(bodyParams.type), + collisionGroup: gltfGroupToLayer[bodyParams.collisionGroup], + collisionMask: bodyParams.collisionMask.reduce((acc, m) => acc | gltfGroupToLayer[m], 0) + }); return eid; } diff --git a/src/inflators/spawner.ts b/src/inflators/spawner.ts index 78c6be0310..1587fadb39 100644 --- a/src/inflators/spawner.ts +++ b/src/inflators/spawner.ts @@ -42,7 +42,6 @@ export function inflateSpawner(world: HubsWorld, eid: number, props: SpawnerPara inflateRigidBody(world, eid, { mass: 0, - type: Type.STATIC, collisionGroup: COLLISION_LAYERS.INTERACTABLES, collisionMask: COLLISION_LAYERS.DEFAULT_SPAWNER }); diff --git a/src/prefabs/media.tsx b/src/prefabs/media.tsx index 9d30cc75ad..cee802a0c2 100644 --- a/src/prefabs/media.tsx +++ b/src/prefabs/media.tsx @@ -25,7 +25,7 @@ export function MediaPrefab(params: MediaLoaderParams): EntityDef { rigidbody={{ type: Type.KINEMATIC, collisionGroup: COLLISION_LAYERS.INTERACTABLES, - collisionMask: COLLISION_LAYERS.HANDS + collisionMask: COLLISION_LAYERS.DEFAULT_INTERACTABLE }} scale={[1, 1, 1]} inspectable diff --git a/src/react-components/room/RoomSidebar.js b/src/react-components/room/RoomSidebar.js index 98438a0b1c..f722075a8b 100644 --- a/src/react-components/room/RoomSidebar.js +++ b/src/react-components/room/RoomSidebar.js @@ -70,12 +70,12 @@ function SceneAttribution({ attribution }) { } SceneAttribution.propTypes = { - attribution: { + attribution: PropTypes.shape({ name: PropTypes.string, title: PropTypes.string, author: PropTypes.string, url: PropTypes.string - } + }) }; // To assist with content control, we avoid displaying scene links to users who are not the scene diff --git a/src/systems/bit-constraints-system.js b/src/systems/bit-constraints-system.js index 1fa79aea1a..d75ed8a7b1 100644 --- a/src/systems/bit-constraints-system.js +++ b/src/systems/bit-constraints-system.js @@ -19,8 +19,10 @@ import { ConstraintHandLeft, ConstraintHandRight, ConstraintRemoteLeft, - ConstraintRemoteRight + ConstraintRemoteRight, + NetworkedRigidBody } from "../bit-components"; +import { Type, getBodyFromRigidBody, getBodyTypeFromType } from "../inflators/rigid-body"; const queryRemoteRight = defineQuery([HeldRemoteRight, OffersRemoteConstraint]); const queryEnterRemoteRight = enterQuery(queryRemoteRight); @@ -45,7 +47,7 @@ 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.updateRigidBody(eid, grabBodyOptions); physicsSystem.addConstraint(interactor, Rigidbody.bodyId[eid], Rigidbody.bodyId[interactor], {}); addComponent(world, Constraint, eid); addComponent(world, constraintComponent, eid); @@ -57,8 +59,16 @@ function remove(world, offersConstraint, constraintComponent, physicsSystem, int const eid = findAncestorEntity(world, entities[i], ancestor => hasComponent(world, Rigidbody, ancestor)); if (!entityExists(world, eid)) continue; if (hasComponent(world, offersConstraint, entities[i]) && hasComponent(world, Rigidbody, eid)) { - physicsSystem.updateRigidBodyOptions(eid, releaseBodyOptions); + physicsSystem.updateRigidBody(eid, { + type: getBodyTypeFromType(NetworkedRigidBody.prevType[eid]), + ...releaseBodyOptions + }); physicsSystem.removeConstraint(interactor); + if (Rigidbody.type[eid] === Type.DYNAMIC) { + physicsSystem.activateBody(Rigidbody.bodyId[eid]); + // This shouldn't be necessary but for some reason it doesn't activate the body if we don't update the body afterwards + physicsSystem.updateRigidBody(eid, getBodyFromRigidBody(eid)); + } removeComponent(world, constraintComponent, eid); if ( !hasComponent(world, ConstraintHandLeft, eid) && diff --git a/src/systems/bit-media-frames.js b/src/systems/bit-media-frames.js index f56ed3ad79..d264bc52a9 100644 --- a/src/systems/bit-media-frames.js +++ b/src/systems/bit-media-frames.js @@ -19,13 +19,13 @@ import { MediaContentBounds, MediaFrame, MediaImage, - MediaLoaded, MediaPDF, MediaVideo, Networked, NetworkedMediaFrame, Owned, - Rigidbody + Rigidbody, + Holdable } from "../bit-components"; import { MediaType } from "../utils/media-utils"; import { cloneObject3D, disposeNode, setMatrixWorld } from "../utils/three-utils"; @@ -36,12 +36,13 @@ import { addObject3DComponent } from "../utils/jsx-entity"; import { updateMaterials } from "../utils/material-utils"; import { MEDIA_FRAME_FLAGS, AxisAlignType } from "../inflators/media-frame"; import { Matrix4, NormalBlending, Quaternion, RGBAFormat, Vector3 } from "three"; +import { COLLISION_LAYERS } from "../constants"; const EMPTY_COLOR = 0x6fc0fd; const HOVER_COLOR = 0x2f80ed; const FULL_COLOR = 0x808080; -const mediaFramesQuery = defineQuery([MediaFrame]); +const mediaFramesQuery = defineQuery([MediaFrame, NetworkedMediaFrame]); const enteredMediaFramesQuery = enterQuery(mediaFramesQuery); const exitedMediaFramesQuery = exitQuery(mediaFramesQuery); @@ -54,11 +55,19 @@ function mediaTypeMaskFor(world, eid) { mediaTypeMask |= el.components["media-image"] && MediaType.IMAGE; mediaTypeMask |= el.components["media-pdf"] && MediaType.PDF; } else { - const mediaEid = findChildWithComponent(world, MediaLoaded, eid); - mediaTypeMask |= hasComponent(world, GLTFModel, mediaEid) && MediaType.MODEL; - mediaTypeMask |= hasComponent(world, MediaVideo, mediaEid) && MediaType.VIDEO; - mediaTypeMask |= hasComponent(world, MediaImage, mediaEid) && MediaType.IMAGE; - mediaTypeMask |= hasComponent(world, MediaPDF, mediaEid) && MediaType.PDF; + const rigidBody = findAncestorWithComponent(world, Rigidbody, eid); + if (rigidBody && Rigidbody.collisionFilterMask[rigidBody] & COLLISION_LAYERS.MEDIA_FRAMES) { + const interactable = findChildWithComponent(world, Holdable, eid); + if (interactable) { + mediaTypeMask |= hasComponent(world, GLTFModel, interactable) && MediaType.MODEL; + mediaTypeMask |= hasComponent(world, MediaVideo, interactable) && MediaType.VIDEO; + mediaTypeMask |= hasComponent(world, MediaImage, interactable) && MediaType.IMAGE; + mediaTypeMask |= hasComponent(world, MediaPDF, interactable) && MediaType.PDF; + if (mediaTypeMask === 0) { + mediaTypeMask |= MediaType.MODEL; + } + } + } } return mediaTypeMask; } @@ -322,7 +331,7 @@ export function mediaFramesSystem(world, physicsSystem) { if (capturedEid && isCapturedOwned && !isCapturedHeld && !isFrameDeleting && isCapturedColliding) { snapToFrame(world, frame, capturedEid); - physicsSystem.updateRigidBodyOptions(capturedEid, { type: "kinematic" }); + physicsSystem.updateRigidBody(capturedEid, { type: "kinematic" }); } else if ( (isFrameOwned && MediaFrame.capturedNid[frame] && world.deletedNids.has(MediaFrame.capturedNid[frame])) || (capturedEid && isCapturedOwned && !isCapturedColliding) || @@ -353,7 +362,7 @@ export function mediaFramesSystem(world, physicsSystem) { obj.updateMatrices(); tmpVec3.setFromMatrixScale(obj.matrixWorld).toArray(NetworkedMediaFrame.scale[frame]); snapToFrame(world, frame, capturable); - physicsSystem.updateRigidBodyOptions(capturable, { type: "kinematic" }); + physicsSystem.updateRigidBody(capturable, { type: "kinematic" }); } } @@ -366,7 +375,7 @@ export function mediaFramesSystem(world, physicsSystem) { // TODO: If you are resetting scale because you lost a race for the frame, // you should probably also move the object away from the frame. setMatrixScale(world.eid2obj.get(capturedEid), MediaFrame.scale[frame]); - physicsSystem.updateRigidBodyOptions(capturedEid, { type: "dynamic" }); + physicsSystem.updateRigidBody(capturedEid, { type: "dynamic" }); } MediaFrame.capturedNid[frame] = NetworkedMediaFrame.capturedNid[frame]; diff --git a/src/systems/bit-physics.ts b/src/systems/bit-physics.ts index a07c325956..d9b891d2c4 100644 --- a/src/systems/bit-physics.ts +++ b/src/systems/bit-physics.ts @@ -1,10 +1,12 @@ import { defineQuery, enterQuery, entityExists, exitQuery, hasComponent, Not } from "bitecs"; -import { Object3DTag, Rigidbody, PhysicsShape, AEntity } from "../bit-components"; +import { Object3DTag, Rigidbody, PhysicsShape, AEntity, TextTag } from "../bit-components"; import { getShapeFromPhysicsShape } from "../inflators/physics-shape"; -import { findAncestorWithComponent } from "../utils/bit-utils"; +import { findAncestorWithComponent, hasAnyComponent } from "../utils/bit-utils"; import { getBodyFromRigidBody } from "../inflators/rigid-body"; import { HubsWorld } from "../app"; import { PhysicsSystem } from "./physics-system"; +import { EntityID } from "../utils/networking-types"; +import { Object3D } from "three"; const rigidbodyQuery = defineQuery([Rigidbody, Object3DTag, Not(AEntity)]); const rigidbodyEnteredQuery = enterQuery(rigidbodyQuery); @@ -13,23 +15,49 @@ const shapeQuery = defineQuery([PhysicsShape]); const shapeEnterQuery = enterQuery(shapeQuery); const shapeExitQuery = exitQuery(shapeQuery); +// We don't want to add physics shape for some child entities +// ie. If the object already has a physics shape we don't want to create another one. +// If the object has a text component, we don't want to create a physics shape for it. +const NO_PHYSICS_COMPONENTS = [Rigidbody, TextTag]; + function addPhysicsShapes(world: HubsWorld, physicsSystem: PhysicsSystem, eid: number) { const bodyId = PhysicsShape.bodyId[eid]; const obj = world.eid2obj.get(eid)!; - const shape = getShapeFromPhysicsShape(eid); - const shapeId = physicsSystem.addShapes(bodyId, obj, shape); - PhysicsShape.shapeId[eid] = shapeId; + + // Avoid adding physics shapes for child entities with specific components. + if (obj) { + const hidden = new Map(); + obj.traverse((child: Object3D) => { + if (child.eid! !== eid && hasAnyComponent(world, NO_PHYSICS_COMPONENTS, child.eid!)) { + hidden.set(child.eid!, child.parent!.eid!); + } + }); + for (let child of hidden.keys()) { + const childObj = world.eid2obj.get(child)!; + childObj.removeFromParent(); + } + + const shape = getShapeFromPhysicsShape(eid); + const shapeId = physicsSystem.addShapes(bodyId, obj, shape); + PhysicsShape.shapeId[eid] = shapeId; + + hidden.forEach((parent: EntityID, child: EntityID) => { + const parentObj = world.eid2obj.get(parent)!; + const childObj = world.eid2obj.get(child)!; + parentObj.add(childObj); + }); + } } export const physicsCompatSystem = (world: HubsWorld, physicsSystem: PhysicsSystem) => { - rigidbodyEnteredQuery(world).forEach(eid => { + rigidbodyEnteredQuery(world).forEach((eid: EntityID) => { const obj = world.eid2obj.get(eid); const body = getBodyFromRigidBody(eid); const bodyId = physicsSystem.addBody(obj, body); Rigidbody.bodyId[eid] = bodyId; }); - shapeEnterQuery(world).forEach(eid => { + shapeEnterQuery(world).forEach((eid: EntityID) => { const bodyEid = findAncestorWithComponent(world, Rigidbody, eid); if (bodyEid) { PhysicsShape.bodyId[eid] = Rigidbody.bodyId[bodyEid]; @@ -39,9 +67,11 @@ export const physicsCompatSystem = (world: HubsWorld, physicsSystem: PhysicsSyst } }); - shapeExitQuery(world).forEach(eid => physicsSystem.removeShapes(PhysicsShape.bodyId[eid], PhysicsShape.shapeId[eid])); + shapeExitQuery(world).forEach((eid: EntityID) => + physicsSystem.removeShapes(PhysicsShape.bodyId[eid], PhysicsShape.shapeId[eid]) + ); - rigidbodyExitedQuery(world).forEach(eid => { + rigidbodyExitedQuery(world).forEach((eid: EntityID) => { if (entityExists(world, eid) && hasComponent(world, PhysicsShape, eid)) { physicsSystem.removeShapes(PhysicsShape.bodyId[eid], PhysicsShape.shapeId[eid]); // The PhysicsShape is still on this entity! diff --git a/src/systems/floaty-object-system.js b/src/systems/floaty-object-system.js index f857fa8e70..24fd720ae5 100644 --- a/src/systems/floaty-object-system.js +++ b/src/systems/floaty-object-system.js @@ -53,7 +53,7 @@ function makeKinematicOnRelease(world) { const physicsSystem = AFRAME.scenes[0].systems["hubs-systems"].physicsSystem; makeKinematicOnReleaseExitQuery(world).forEach(eid => { if (!entityExists(world, eid) || !hasComponent(world, Owned, eid)) return; - physicsSystem.updateRigidBodyOptions(eid, { type: "kinematic" }); + physicsSystem.updateRigidBody(eid, { type: "kinematic" }); }); } @@ -73,14 +73,14 @@ export const floatyObjectSystem = world => { const physicsSystem = AFRAME.scenes[0].systems["hubs-systems"].physicsSystem; enteredFloatyObjectsQuery(world).forEach(eid => { - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { type: "kinematic", gravity: { x: 0, y: 0, z: 0 } }); }); enterHeldFloatyObjectsQuery(world).forEach(eid => { - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { gravity: { x: 0, y: 0, z: 0 }, type: "dynamic", collisionFilterMask: COLLISION_LAYERS.HANDS | COLLISION_LAYERS.MEDIA_FRAMES @@ -95,7 +95,7 @@ export const floatyObjectSystem = world => { const bodyData = physicsSystem.bodyUuidToData.get(bodyId); if (FloatyObject.flags[eid] & FLOATY_OBJECT_FLAGS.MODIFY_GRAVITY_ON_RELEASE) { if (bodyData.linearVelocity < 1.85) { - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { gravity: { x: 0, y: 0, z: 0 }, angularDamping: FloatyObject.flags[eid] & FLOATY_OBJECT_FLAGS.REDUCE_ANGULAR_FLOAT ? 0.89 : 0.5, linearDamping: 0.95, @@ -105,7 +105,7 @@ export const floatyObjectSystem = world => { }); addComponent(world, MakeStaticWhenAtRest, eid); } else { - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { gravity: { x: 0, y: FloatyObject.releaseGravity[eid], z: 0 }, angularDamping: 0.01, linearDamping: 0.01, @@ -131,7 +131,7 @@ export const floatyObjectSystem = world => { const angle = Math.random() * Math.PI * 2; const x = Math.cos(angle); const z = Math.sin(angle); - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { gravity: { x, y: force, z }, angularDamping: 0.01, linearDamping: 0.01, @@ -142,7 +142,7 @@ export const floatyObjectSystem = world => { removeComponent(world, MakeStaticWhenAtRest, eid); } } else { - physicsSystem.updateRigidBodyOptions(eid, { + physicsSystem.updateRigidBody(eid, { collisionFilterMask: COLLISION_LAYERS.DEFAULT_INTERACTABLE, gravity: { x: 0, y: -9.8, z: 0 } }); diff --git a/src/systems/hold-system.js b/src/systems/hold-system.js index 72316e506a..6c2c1abd37 100644 --- a/src/systems/hold-system.js +++ b/src/systems/hold-system.js @@ -13,8 +13,7 @@ import { HeldHandLeft, AEntity, Networked, - MediaLoader, - Deletable + Rigidbody } from "../bit-components"; import { canMove } from "../utils/permissions-utils"; import { canMove as canMoveEntity } from "../utils/bit-permissions-utils"; @@ -75,12 +74,15 @@ export function isAEntityPinned(world, eid) { // Alternate solution: Simply recognize an entity as pinned if its any // ancestor is pinned (in hold-system) unless there is a case that // descendant entity under pinned entity wants to be grabbable. +// +// Update: As now we are supporting grabbable scene objects, we look for the +// root holdable entity with a rigid body as the only rigid body +// in the hierarchy should be at the root of the grabbable object. function grab(world, userinput, queryHovered, held, grabPath) { const hovered = queryHovered(world)[0]; - // Special path for Dropped/Pasted Media with new loader enabled. Check the comment above. - const mediaRoot = findAncestorWithComponents(world, [Deletable, MediaLoader, Holdable], hovered); - const target = mediaRoot ? mediaRoot : hovered; + const interactable = findAncestorWithComponents(world, [Holdable, Rigidbody], hovered); + const target = interactable ? interactable : hovered; const isEntityPinned = isPinned(target) || isAEntityPinned(world, target); if ( diff --git a/src/systems/hubs-systems.ts b/src/systems/hubs-systems.ts index 96a6e8c86f..e656237cd3 100644 --- a/src/systems/hubs-systems.ts +++ b/src/systems/hubs-systems.ts @@ -93,6 +93,7 @@ import { linkedPDFSystem } from "../bit-systems/linked-pdf-system"; import { inspectSystem } from "../bit-systems/inspect-system"; import { snapMediaSystem } from "../bit-systems/snap-media-system"; import { scaleWhenGrabbedSystem } from "../bit-systems/scale-when-grabbed-system"; +import { interactableSystem } from "../bit-systems/interactable-system"; declare global { interface Window { @@ -240,6 +241,7 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene hubsSystems.hoverMenuSystem.tick(); hubsSystems.positionAtBorderSystem.tick(); hubsSystems.twoPointStretchingSystem.tick(); + interactableSystem(world); hubsSystems.holdableButtonSystem.tick(); hubsSystems.hoverButtonSystem.tick(); diff --git a/src/systems/networked-transform.js b/src/systems/networked-transform.js index 9a501c25d7..81ab689a6c 100644 --- a/src/systems/networked-transform.js +++ b/src/systems/networked-transform.js @@ -1,12 +1,24 @@ -import { addComponent, defineQuery, hasComponent } from "bitecs"; -import { LinearRotate, LinearScale, LinearTranslate, NetworkedTransform, Owned } from "../bit-components"; +import { addComponent, defineQuery, hasComponent, enterQuery } from "bitecs"; +import { LinearRotate, LinearScale, LinearTranslate, NetworkedTransform, Owned, Networked } from "../bit-components"; import { millisecondsBetweenTicks } from "../bit-systems/networking"; const query = defineQuery([NetworkedTransform]); const tmpVec = new THREE.Vector3(); const tmpQuat = new THREE.Quaternion(); + +const networkedTransformEnterQuery = enterQuery(query); export function networkedTransformSystem(world) { + networkedTransformEnterQuery(world).forEach(eid => { + // If it's a scene object that has not been owned yet, + // we initialize its networked transform to the initial object transform. + if (Networked.creator[eid] === APP.getSid("scene") && Networked.owner[eid] === APP.getSid("reticulum")) { + const obj = world.eid2obj.get(eid); + NetworkedTransform.position[eid].set(obj.position.toArray()); + NetworkedTransform.rotation[eid].set(obj.quaternion.toArray()); + NetworkedTransform.scale[eid].set(obj.scale.toArray()); + } + }); const ents = query(world); for (let i = 0; i < ents.length; i++) { const eid = ents[i]; diff --git a/src/systems/on-ownership-lost.js b/src/systems/on-ownership-lost.js index ae055891c6..e392265ebf 100644 --- a/src/systems/on-ownership-lost.js +++ b/src/systems/on-ownership-lost.js @@ -1,4 +1,3 @@ -import { COLLISION_LAYERS } from "../constants"; import { exitQuery, defineQuery, removeComponent, hasComponent, entityExists } from "bitecs"; import { Held, @@ -13,7 +12,6 @@ import { // TODO this seems wrong, nothing sets it back unless its a floaty object const exitOwned = exitQuery(defineQuery([Owned])); const componentsToRemove = [Held, HeldHandRight, HeldHandLeft, HeldRemoteRight, HeldRemoteLeft]; -const kinematicOptions = { type: "kinematic", collisionFilterMask: COLLISION_LAYERS.UNOWNED_INTERACTABLE }; export function onOwnershipLost(world) { const physicsSystem = AFRAME.scenes[0].systems["hubs-systems"].physicsSystem; @@ -27,7 +25,10 @@ export function onOwnershipLost(world) { } if (hasComponent(world, Rigidbody, eid)) { - physicsSystem.updateRigidBodyOptions(eid, kinematicOptions); + physicsSystem.updateRigidBody(eid, { + type: "kinematic", + collisionFilterMask: Rigidbody.initialCollisionFilterMask[eid] + }); } } } diff --git a/src/systems/physics-system.js b/src/systems/physics-system.js index 1489cafad9..20e92fcc06 100644 --- a/src/systems/physics-system.js +++ b/src/systems/physics-system.js @@ -3,7 +3,7 @@ import { AmmoDebugConstants, DefaultBufferSize } from "ammo-debug-drawer"; import configs from "../utils/configs"; import ammoWasmUrl from "ammo.js/builds/ammo.wasm.wasm"; import { Rigidbody } from "../bit-components"; -import { updateRigiBodyParams } from "../inflators/rigid-body"; +import { updateBodyParams } from "../inflators/rigid-body"; const MESSAGE_TYPES = CONSTANTS.MESSAGE_TYPES, TYPE = CONSTANTS.TYPE, @@ -231,6 +231,7 @@ export class PhysicsSystem { const bodyId = this.nextBodyUuid; this.nextBodyUuid += 1; + object3D.updateMatrices(); this.workerHelpers.addBody(bodyId, object3D, options); this.bodyUuidToData.set(bodyId, { @@ -250,7 +251,7 @@ export class PhysicsSystem { updateRigidBody(eid, options) { const bodyId = Rigidbody.bodyId[eid]; - updateRigiBodyParams(eid, options); + updateBodyParams(eid, options); if (this.bodyUuidToData.has(bodyId)) { this.bodyUuidToData.get(bodyId).options = options; this.workerHelpers.updateBody(bodyId, options); @@ -259,18 +260,6 @@ export class PhysicsSystem { } } - updateRigidBodyOptions(eid, options) { - const bodyId = Rigidbody.bodyId[eid]; - updateRigiBodyParams(eid, options); - const bodyData = this.bodyUuidToData.get(bodyId); - if (!bodyData) { - // TODO: Fix me. - console.warn("updateBodyOptions called for invalid bodyId"); - return; - } - this.workerHelpers.updateBody(bodyId, Object.assign(this.bodyUuidToData.get(bodyId).options, options)); - } - removeBody(uuid) { const bodyData = this.bodyUuidToData.get(uuid); if (!bodyData) { @@ -285,8 +274,10 @@ export class PhysicsSystem { if (bodyData.isInitialized) { delete this.indexToUuid[bodyData.index]; bodyData.collisions.forEach(otherId => { - const otherData = this.bodyUuidToData.get(otherId).collisions; - otherData.splice(otherData.indexOf(uuid), 1); + const collisions = this.bodyUuidToData.get(otherId)?.collisions; + // This can happen when removing multiple bodies in a frame + if (!collisions) return; + collisions.splice(collisions.indexOf(uuid), 1); }); this.bodyUuids.splice(this.bodyUuids.indexOf(uuid), 1); this.bodyUuidToData.delete(uuid); diff --git a/src/utils/bit-utils.ts b/src/utils/bit-utils.ts index 009fb52882..bfc954b308 100644 --- a/src/utils/bit-utils.ts +++ b/src/utils/bit-utils.ts @@ -25,8 +25,15 @@ export function hasAnyComponent(world: HubsWorld, components: Component[], eid: return false; } -export function findAncestorEntity(world: HubsWorld, eid: number, predicate: (eid: number) => boolean) { - const obj = findAncestor(world.eid2obj.get(eid)!, (o: Object3D) => !!(o.eid && predicate(o.eid))) as Object3D | null; +export function findAncestorEntity( + world: HubsWorld, + eid: number, + predicate: (eid: number, world: HubsWorld) => boolean +) { + const obj = findAncestor( + world.eid2obj.get(eid)!, + (o: Object3D) => !!(o.eid && predicate(o.eid, world)) + ) as Object3D | null; return obj && obj.eid!; } @@ -36,7 +43,7 @@ export function findAncestorEntities(world: HubsWorld, eid: number, predicate: ( } export function findAncestorWithComponent(world: HubsWorld, component: Component, eid: number) { - return findAncestorEntity(world, eid, otherId => hasComponent(world, component, otherId)); + return findAncestorEntity(world, eid, (otherId, world) => hasComponent(world, component, otherId)); } export function findAncestorsWithComponent(world: HubsWorld, component: Component, eid: number): EntityID[] { diff --git a/src/utils/jsx-entity.ts b/src/utils/jsx-entity.ts index 15e9e9c9f8..8273eef3c9 100644 --- a/src/utils/jsx-entity.ts +++ b/src/utils/jsx-entity.ts @@ -70,7 +70,6 @@ import { inflateSpawnpoint, inflateWaypoint, WaypointParams } from "../inflators import { inflateReflectionProbe, ReflectionProbeParams } from "../inflators/reflection-probe"; import { HubsWorld } from "../app"; import { Group, Material, Object3D, Texture } from "three"; -import { AlphaMode } from "./create-image-mesh"; import { MediaLoaderParams } from "../inflators/media-loader"; import { preload } from "./preload"; import { DirectionalLightParams, inflateDirectionalLight } from "../inflators/directional-light"; @@ -78,7 +77,6 @@ import { AmbientLightParams, inflateAmbientLight } from "../inflators/ambient-li import { HemisphereLightParams, inflateHemisphereLight } from "../inflators/hemisphere-light"; import { PointLightParams, inflatePointLight } from "../inflators/point-light"; import { SpotLightParams, inflateSpotLight } from "../inflators/spot-light"; -import { ProjectionMode } from "./projection-mode"; import { inflateSkybox, SkyboxParams } from "../inflators/skybox"; import { inflateSpawner, SpawnerParams } from "../inflators/spawner"; import { inflateVideoTextureTarget, VideoTextureTargetParams } from "../inflators/video-texture-target"; @@ -93,13 +91,12 @@ import { inflateAudioParams } from "../inflators/audio-params"; import { AudioSourceParams, inflateAudioSource } from "../inflators/audio-source"; import { AudioTargetParams, inflateAudioTarget } from "../inflators/audio-target"; import { PhysicsShapeParams, inflatePhysicsShape } from "../inflators/physics-shape"; -import { inflateRigidBody, RigidBodyParams } from "../inflators/rigid-body"; +import { inflateGLTFRigidBody, inflateRigidBody, RigidBodyParams } from "../inflators/rigid-body"; import { AmmoShapeParams, inflateAmmoShape } from "../inflators/ammo-shape"; import { BoxColliderParams, inflateBoxCollider } from "../inflators/box-collider"; import { inflateTrimesh } from "../inflators/trimesh"; import { HeightFieldParams, inflateHeightField } from "../inflators/heightfield"; import { inflateAudioSettings } from "../inflators/audio-settings"; -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"; @@ -258,13 +255,15 @@ export interface ComponentData { hemisphereLight?: HemisphereLightParams; pointLight?: PointLightParams; spotLight?: SpotLightParams; - grabbable?: GrabbableParams; billboard?: { onlyY: boolean }; mirror?: MirrorParams; audioZone?: AudioZoneParams; audioParams?: AudioSettings; mediaFrame?: any; text?: TextParams; + networked?: any; + networkedTransform?: any; + grabbable?: GrabbableParams; } type OptionalParams = Partial | true; @@ -309,7 +308,6 @@ export interface JSXComponentData extends ComponentData { quack?: true; // @TODO Define all the anys - networked?: any; textButton?: any; hoverButton?: any; hoverableVisuals?: any; @@ -317,7 +315,6 @@ export interface JSXComponentData extends ComponentData { physicsShape?: OptionalParams; floatyObject?: any; networkedFloatyObject?: any; - networkedTransform?: any; objectMenu?: { backgroundRef: Ref; pinButtonRef: Ref; @@ -399,6 +396,10 @@ export interface GLTFComponentData extends ComponentData { audioTarget: AudioTargetParams; audioSettings: SceneAudioSettings; mediaLink: MediaLinkParams; + rigidbody?: OptionalParams; + // TODO GLTFPhysicsShapeParams + physicsShape?: AmmoShapeParams; + grabbable?: GrabbableParams; // deprecated spawnPoint?: true; @@ -429,7 +430,6 @@ declare global { } export const commonInflators: Required<{ [K in keyof ComponentData]: InflatorFn }> = { - grabbable: inflateGrabbable, billboard: createDefaultInflator(Billboard), // inflators that create Object3Ds @@ -442,7 +442,10 @@ export const commonInflators: Required<{ [K in keyof ComponentData]: InflatorFn audioZone: inflateAudioZone, audioParams: inflateAudioParams, mediaFrame: inflateMediaFrame, - text: inflateText + text: inflateText, + networkedTransform: createDefaultInflator(NetworkedTransform), + networked: createDefaultInflator(Networked), + grabbable: inflateGrabbable }; const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = { @@ -466,8 +469,6 @@ const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = { networkedFloatyObject: createDefaultInflator(NetworkedFloatyObject), makeKinematicOnRelease: createDefaultInflator(MakeKinematicOnRelease), destroyAtExtremeDistance: createDefaultInflator(DestroyAtExtremeDistance), - networkedTransform: createDefaultInflator(NetworkedTransform), - networked: createDefaultInflator(Networked), objectMenu: createDefaultInflator(ObjectMenu), mirrorMenu: createDefaultInflator(MirrorMenu), followInFov: inflateFollowInFov, @@ -533,7 +534,9 @@ export const gltfInflators: Required<{ [K in keyof GLTFComponentData]: InflatorF trimesh: inflateTrimesh, heightfield: inflateHeightField, audioSettings: inflateAudioSettings, - mediaLink: inflateMediaLink + mediaLink: inflateMediaLink, + rigidbody: inflateGLTFRigidBody, + physicsShape: inflateAmmoShape }; function jsxInflatorExists(name: string): name is keyof JSXComponentData { diff --git a/src/utils/network-schemas.ts b/src/utils/network-schemas.ts index b97517af7e..86637b4dbf 100644 --- a/src/utils/network-schemas.ts +++ b/src/utils/network-schemas.ts @@ -5,6 +5,7 @@ import { NetworkedFloatyObject, NetworkedMediaFrame, NetworkedPDF, + NetworkedRigidBody, NetworkedTransform, NetworkedVideo, NetworkedWaypoint @@ -16,6 +17,7 @@ import { NetworkedTransformSchema } from "./networked-transform-schema"; import { NetworkedVideoSchema } from "./networked-video-schema"; import { NetworkedWaypointSchema } from "./networked-waypoint-schema"; import type { CursorBuffer, EntityID } from "./networking-types"; +import { NetworkedRigidBodySchema } from "./networked-rigid-body"; export interface StoredComponent { version: number; @@ -46,6 +48,7 @@ schemas.set(NetworkedFloatyObject, { ...defineNetworkSchema(NetworkedFloatyObject) }); schemas.set(NetworkedPDF, NetworkedPDFSchema); +schemas.set(NetworkedRigidBody, NetworkedRigidBodySchema); export const networkableComponents = Array.from(schemas.keys()); diff --git a/src/utils/networked-media-frame-schema.ts b/src/utils/networked-media-frame-schema.ts index 175b022af6..19cd816dc6 100644 --- a/src/utils/networked-media-frame-schema.ts +++ b/src/utils/networked-media-frame-schema.ts @@ -8,16 +8,30 @@ const runtimeSerde = defineNetworkSchema(NetworkedMediaFrame); const migrations = new Map(); migrations.set(0, ({ data }: StoredComponent) => { - return { version: 1, data }; + return { version: 2, data }; }); function apply(eid: EntityID, { version, data }: StoredComponent) { - if (version !== 1) return false; - - const { capturedNid, scale }: { capturedNid: string; scale: ArrayVec3 } = data; - write(NetworkedMediaFrame.capturedNid, eid, capturedNid); - write(NetworkedMediaFrame.scale, eid, scale); - return true; + if (version === 1) { + const { capturedNid, scale }: { capturedNid: string; scale: ArrayVec3 } = data; + write(NetworkedMediaFrame.capturedNid, eid, capturedNid); + write(NetworkedMediaFrame.scale, eid, scale); + write(NetworkedMediaFrame.flags, eid, 0); + return true; + } else if (version === 2) { + const { + capturedNid, + scale, + flags, + mediaType + }: { capturedNid: string; scale: ArrayVec3; flags: number; mediaType: number } = data; + write(NetworkedMediaFrame.capturedNid, eid, capturedNid); + write(NetworkedMediaFrame.scale, eid, scale); + write(NetworkedMediaFrame.flags, eid, flags); + write(NetworkedMediaFrame.mediaType, eid, mediaType); + return true; + } + return false; } export const NetworkedMediaFrameSchema: NetworkSchema = { @@ -26,10 +40,12 @@ export const NetworkedMediaFrameSchema: NetworkSchema = { deserialize: runtimeSerde.deserialize, serializeForStorage: function serializeForStorage(eid: EntityID) { return { - version: 1, + version: 2, data: { capturedNid: read(NetworkedMediaFrame.capturedNid, eid), - scale: read(NetworkedMediaFrame.scale, eid) + scale: read(NetworkedMediaFrame.scale, eid), + flags: read(NetworkedMediaFrame.flags, eid), + mediaType: read(NetworkedMediaFrame.mediaType, eid) } }; }, diff --git a/src/utils/networked-rigid-body.ts b/src/utils/networked-rigid-body.ts new file mode 100644 index 0000000000..41b9cf0aaa --- /dev/null +++ b/src/utils/networked-rigid-body.ts @@ -0,0 +1,31 @@ +import { NetworkedRigidBody } from "../bit-components"; +import { defineNetworkSchema } from "./define-network-schema"; +import { deserializerWithMigrations, Migration, NetworkSchema, read, StoredComponent, write } from "./network-schemas"; +import type { EntityID } from "./networking-types"; + +const runtimeSerde = defineNetworkSchema(NetworkedRigidBody); + +const migrations = new Map(); + +function apply(eid: EntityID, { version, data }: StoredComponent) { + if (version !== 1) return false; + + const { prevType }: { prevType: number } = data; + write(NetworkedRigidBody.prevType, eid, prevType); + return true; +} + +export const NetworkedRigidBodySchema: NetworkSchema = { + componentName: "networked-rigid-body", + serialize: runtimeSerde.serialize, + deserialize: runtimeSerde.deserialize, + serializeForStorage: function serializeForStorage(eid: EntityID) { + return { + version: 1, + data: { + prevType: read(NetworkedRigidBody.prevType, eid) + } + }; + }, + deserializeFromStorage: deserializerWithMigrations(migrations, apply) +}; From ae050ee316dfadec1463678b342bdf69b2cdb8fb Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Wed, 8 May 2024 10:09:20 +0200 Subject: [PATCH 2/3] Revert media collision mask --- src/prefabs/media.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prefabs/media.tsx b/src/prefabs/media.tsx index cee802a0c2..9d30cc75ad 100644 --- a/src/prefabs/media.tsx +++ b/src/prefabs/media.tsx @@ -25,7 +25,7 @@ export function MediaPrefab(params: MediaLoaderParams): EntityDef { rigidbody={{ type: Type.KINEMATIC, collisionGroup: COLLISION_LAYERS.INTERACTABLES, - collisionMask: COLLISION_LAYERS.DEFAULT_INTERACTABLE + collisionMask: COLLISION_LAYERS.HANDS }} scale={[1, 1, 1]} inspectable From c23b7d4c913b33f184adc46ea1608c7a863d5bf7 Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Wed, 8 May 2024 10:36:15 +0200 Subject: [PATCH 3/3] Add warning for negative scale when creating interactables bounds --- src/bit-systems/interactable-system.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bit-systems/interactable-system.ts b/src/bit-systems/interactable-system.ts index 309f5600ec..d7aa7e3442 100644 --- a/src/bit-systems/interactable-system.ts +++ b/src/bit-systems/interactable-system.ts @@ -29,9 +29,13 @@ export function interactableSystem(world: HubsWorld) { // If it has media frame collision mask, it needs to have content bounds if (hasMesh && Rigidbody.collisionFilterMask[eid] & COLLISION_LAYERS.MEDIA_FRAMES) { const box = getBox(obj, obj); - box.getSize(tmpVector); - addComponent(world, MediaContentBounds, eid); - MediaContentBounds.bounds[eid].set(tmpVector.toArray()); + if (!box.isEmpty()) { + box.getSize(tmpVector); + addComponent(world, MediaContentBounds, eid); + MediaContentBounds.bounds[eid].set(tmpVector.toArray()); + } else { + console.error(`Couldn't create content bounds for entity ${eid}. It seems to be empty or have negative scale.`); + } } }); }