From 348c602fb754506575a4f4818e2fc15a9f555cae Mon Sep 17 00:00:00 2001 From: Ryan Inch Date: Sun, 17 Nov 2024 02:38:23 -0500 Subject: [PATCH] Revert "Merge branch 'master' into addons (likely breaks the branch)" Revert the changes from the last merge as the functionality from the "Scene grabbables support" PR is expected to be present in this branch and has stuff built on top of it. This reverts commit e449f6647d978e872644a55e684506b49fd01d41, reversing changes made to 7a99b6ff538e0e771b4d5c08fae198a21e7d0a22. --- src/bit-components.js | 10 +- src/bit-systems/camera-tool.js | 2 +- src/bit-systems/interactable-system.ts | 41 ++++ 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 | 231 +++++++++++++++++++--- src/inflators/spawner.ts | 1 - 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 | 23 ++- src/utils/network-schemas.ts | 3 + src/utils/networked-media-frame-schema.ts | 34 +++- src/utils/networked-rigid-body.ts | 31 +++ 28 files changed, 496 insertions(+), 139 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 116d4d84ec..09d6bb53d8 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; @@ -158,7 +160,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..d7aa7e3442 --- /dev/null +++ b/src/bit-systems/interactable-system.ts @@ -0,0 +1,41 @@ +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); + 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.`); + } + } + }); +} diff --git a/src/bit-systems/media-loading.ts b/src/bit-systems/media-loading.ts index a1f3752bf4..a31dcd9051 100644 --- a/src/bit-systems/media-loading.ts +++ b/src/bit-systems/media-loading.ts @@ -361,7 +361,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)) { @@ -408,7 +408,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)) { @@ -419,7 +419,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 007cf050d1..189993a663 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,7 +12,7 @@ export enum COLLISION_LAYERS { DEFAULT_INTERACTABLE = INTERACTABLES | ENVIRONMENT | AVATAR | HANDS | MEDIA_FRAMES | TRIGGERS, 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 c3de15d409..7d03ff2ca9 100644 --- a/src/inflators/model.tsx +++ b/src/inflators/model.tsx @@ -21,15 +21,7 @@ export type GLTFLinkResolverFn = ( ) => void; // 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"]; export function inflateComponents( world: HubsWorld, diff --git a/src/inflators/rigid-body.ts b/src/inflators/rigid-body.ts index 54f2cf55e5..fee7717ffe 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,13 @@ export enum Type { KINEMATIC } +export enum CollisionGroup { + OBJECTS = "objects", + ENVIRONMENT = "environment", + TRIGGERS = "triggers", + AVATARS = "avatars" +} + export enum ActivationState { ACTIVE_TAG = 0, ISLAND_SLEEPING = 1, @@ -17,6 +25,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 +86,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 +143,156 @@ 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", + TRIGGERS = "triggers", + 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.TRIGGERS]: COLLISION_LAYERS.TRIGGERS, + [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/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 ad37adb8e2..c5f57b5d18 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,13 +36,14 @@ 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"; import { HOLDABLE_FLAGS } from "../inflators/holdable"; 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); @@ -55,11 +56,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; } @@ -350,7 +359,7 @@ export function mediaFramesSystem(world, physicsSystem) { if (MediaFrame.flags[frame] & MEDIA_FRAME_FLAGS.ACTIVE) { 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) || @@ -381,7 +390,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" }); } } } @@ -395,7 +404,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 4019f991ad..82db35efc8 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 | COLLISION_LAYERS.TRIGGERS @@ -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 70b8fdbc41..68ff4ae74a 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"; @@ -76,12 +75,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 62f00bd392..2281f524fb 100644 --- a/src/systems/hubs-systems.ts +++ b/src/systems/hubs-systems.ts @@ -92,6 +92,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"; import { SystemConfigT } from "../types"; declare global { @@ -247,6 +248,7 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene hubsSystems.hoverMenuSystem.tick(); hubsSystems.positionAtBorderSystem.tick(); hubsSystems.twoPointStretchingSystem.tick(); + interactableSystem(world); floatyObjectSystem(world); hubsSystems.holdableButtonSystem.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 e7febe9a88..6a4dcab82c 100644 --- a/src/utils/bit-utils.ts +++ b/src/utils/bit-utils.ts @@ -26,8 +26,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!; } @@ -37,7 +44,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 10f4daca00..497bcfa054 100644 --- a/src/utils/jsx-entity.ts +++ b/src/utils/jsx-entity.ts @@ -69,7 +69,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"; @@ -77,7 +76,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"; @@ -92,13 +90,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"; @@ -259,13 +256,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 { destroyAtExtremeDistance?: 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; @@ -400,7 +397,11 @@ export interface GLTFComponentData extends ComponentData { audioTarget: AudioTargetParams; audioSettings: SceneAudioSettings; mediaLink: MediaLinkParams; + rigidbody?: OptionalParams; + // TODO GLTFPhysicsShapeParams + physicsShape?: AmmoShapeParams; text?: TextParams; + grabbable?: GrabbableParams; // deprecated spawnPoint?: true; @@ -444,7 +445,9 @@ export const commonInflators: Required<{ [K in keyof ComponentDataT]: InflatorFn audioZone: inflateAudioZone, audioParams: inflateAudioParams, mediaFrame: inflateMediaFrame, - text: inflateText + text: inflateText, + networkedTransform: createDefaultInflator(NetworkedTransform), + networked: createDefaultInflator(Networked) }; export const jsxInflators: Required<{ [K in keyof ComponentDataT]: InflatorFn }> = { @@ -468,8 +471,6 @@ export const jsxInflators: Required<{ [K in keyof ComponentDataT]: InflatorFn }> networkedFloatyObject: createDefaultInflator(NetworkedFloatyObject), makeKinematicOnRelease: createDefaultInflator(MakeKinematicOnRelease), destroyAtExtremeDistance: createDefaultInflator(DestroyAtExtremeDistance), - networkedTransform: createDefaultInflator(NetworkedTransform), - networked: createDefaultInflator(Networked), objectMenu: createDefaultInflator(ObjectMenu), mirrorMenu: createDefaultInflator(MirrorMenu), followInFov: inflateFollowInFov, @@ -536,6 +537,8 @@ export const gltfInflators: Required<{ [K in keyof ComponentDataT]: InflatorFn } heightfield: inflateHeightField, audioSettings: inflateAudioSettings, mediaLink: inflateMediaLink, + rigidbody: inflateGLTFRigidBody, + physicsShape: inflateAmmoShape, text: inflateGLTFText }; diff --git a/src/utils/network-schemas.ts b/src/utils/network-schemas.ts index 643196f302..87ee038ee8 100644 --- a/src/utils/network-schemas.ts +++ b/src/utils/network-schemas.ts @@ -5,6 +5,7 @@ import { NetworkedFloatyObject, NetworkedMediaFrame, NetworkedPDF, + NetworkedRigidBody, NetworkedText, NetworkedTransform, NetworkedVideo, @@ -18,6 +19,7 @@ import { NetworkedVideoSchema } from "./networked-video-schema"; import { NetworkedWaypointSchema } from "./networked-waypoint-schema"; import type { CursorBuffer, EntityID } from "./networking-types"; import { NetworkedTextSchema } from "./networked-text-schema"; +import { NetworkedRigidBodySchema } from "./networked-rigid-body"; export interface StoredComponent { version: number; @@ -49,6 +51,7 @@ schemas.set(NetworkedFloatyObject, { }); schemas.set(NetworkedPDF, NetworkedPDFSchema); schemas.set(NetworkedText, NetworkedTextSchema); +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) +};