Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Equippables Work Again #7936

Merged
merged 4 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/client-core/src/world/startClientSystems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { startSystems } from '@etherealengine/engine/src/ecs/functions/SystemFunctions'
import { ButtonCleanupSystem } from '@etherealengine/engine/src/input/systems/ButtonCleanupSystem'
import { ClientInputSystem } from '@etherealengine/engine/src/input/systems/ClientInputSystem'
import { EquippableSystem } from '@etherealengine/engine/src/interaction/systems/EquippableSystem'
import { InteractiveSystem } from '@etherealengine/engine/src/interaction/systems/InteractiveSystem'
import { MediaControlSystem } from '@etherealengine/engine/src/interaction/systems/MediaControlSystem'
import { MotionCaptureSystem } from '@etherealengine/engine/src/mocap/MotionCaptureSystem'
Expand All @@ -42,7 +43,7 @@ export const startClientSystems = () => {
)

/** Fixed */
startSystems([WorldNetworkActionSystem, AvatarSimulationGroup], { with: SimulationSystemGroup })
startSystems([WorldNetworkActionSystem, EquippableSystem, AvatarSimulationGroup], { with: SimulationSystemGroup })

/** Physics */
startSystems([PhysicsSystem], { after: SimulationSystemGroup })
Expand Down
33 changes: 30 additions & 3 deletions packages/engine/src/avatar/components/AvatarIKComponents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect } from 'react'
import { Quaternion, Vector3 } from 'three'

import { Entity } from '../../ecs/classes/Entity'
import {
Expand Down Expand Up @@ -67,9 +68,35 @@ export const AvatarIKTargetComponent = defineComponent({
* @param hand which hand to get
* @returns {Vector3}
*/
export const getHandTarget = (entity: Entity, hand: XRHandedness): ComponentType<typeof TransformComponent> | null => {

const vec3 = new Vector3()
const quat = new Quaternion()

type HandTargetReturn = { position: Vector3; rotation: Quaternion } | null
export const getHandTarget = (entity: Entity, hand: XRHandedness): HandTargetReturn => {
const networkComponent = getComponent(entity, NetworkObjectComponent)
const targetEntity = NameComponent.entitiesByName[networkComponent.ownerId + '_' + hand]?.[0] // todo, how should be choose which one to use?
if (!targetEntity) return null
return getComponent(targetEntity, TransformComponent)
if (targetEntity) return getComponent(targetEntity, TransformComponent)

const rig = getComponent(entity, AvatarRigComponent)
if (!rig) return getComponent(entity, TransformComponent)

switch (hand) {
case 'left':
return {
position: rig.rig.LeftHand.getWorldPosition(vec3),
rotation: rig.rig.LeftHand.getWorldQuaternion(quat)
}
case 'right':
return {
position: rig.rig.RightHand.getWorldPosition(vec3),
rotation: rig.rig.RightHand.getWorldQuaternion(quat)
}
default:
case 'none':
return {
position: rig.rig.Head.getWorldPosition(vec3),
rotation: rig.rig.Head.getWorldQuaternion(quat)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Entity } from '../../ecs/classes/Entity'
import { defineComponent } from '../../ecs/functions/ComponentFunctions'
import { defineComponent, removeComponent } from '../../ecs/functions/ComponentFunctions'
import { EquippedComponent } from './EquippedComponent'

export const EquipperComponent = defineComponent({
name: 'EquipperComponent',
Expand All @@ -14,5 +15,10 @@ export const EquipperComponent = defineComponent({
if (!json) return

if (typeof json.equippedEntity === 'number') component.equippedEntity.set(json.equippedEntity)
},

onRemove(entity, component) {
const equippedEntity = component.equippedEntity.value
removeComponent(equippedEntity, EquippedComponent)
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Engine } from '../../ecs/classes/Engine'
import { Entity } from '../../ecs/classes/Entity'
import { getComponent, hasComponent, removeComponent } from '../../ecs/functions/ComponentFunctions'
import { NetworkObjectComponent } from '../../networking/components/NetworkObjectComponent'
import { NetworkObjectOwnedTag } from '../../networking/components/NetworkObjectComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { EquippedComponent } from '../components/EquippedComponent'
import { EquipperComponent } from '../components/EquipperComponent'
Expand Down Expand Up @@ -41,16 +40,14 @@ export const unequipEntity = (equipperEntity: Entity): void => {
if (!equipperComponent) return
removeComponent(equipperEntity, EquipperComponent)
const networkComponent = getComponent(equipperComponent.equippedEntity, NetworkObjectComponent)
const networkOwnerComponent = getComponent(equipperComponent.equippedEntity, NetworkObjectOwnedTag)
if (networkComponent.authorityPeerID === Engine.instance.worldNetwork.peerID) {
dispatchAction(
WorldNetworkAction.setEquippedObject({
object: {
networkId: networkComponent.networkId,
ownerId: networkComponent.ownerId
},
equip: false,
attachmentPoint: null!
equip: false
})
)
} else {
Expand Down
2 changes: 0 additions & 2 deletions packages/engine/src/interaction/functions/interactUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export function createInteractUI(entity: Entity, interactMessage: string) {
const nameComponent = getComponent(entity, NameComponent)
addComponent(ui.entity, NameComponent, 'interact-ui-' + nameComponent)

addEntityNodeChild(ui.entity, entity)

const xrui = getComponent(ui.entity, XRUIComponent)
xrui.rootLayer.traverseLayersPreOrder((layer: WebLayer3D) => {
const mat = layer.contentMesh.material as THREE.MeshBasicMaterial
Expand Down
165 changes: 97 additions & 68 deletions packages/engine/src/interaction/systems/EquippableSystem.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { RigidBodyType } from '@dimforge/rapier3d-compat'
import { Vector3 } from 'three'
import { MeshBasicMaterial, Vector3 } from 'three'

import { defineActionQueue, dispatchAction } from '@etherealengine/hyperflux'

import { getHandTarget } from '../../avatar/components/AvatarIKComponents'
import { getAvatarBoneWorldPosition } from '../../avatar/functions/avatarFunctions'
import { isClient } from '../../common/functions/getEnvironment'
import { Engine } from '../../ecs/classes/Engine'
import { EngineActions } from '../../ecs/classes/EngineState'
Expand All @@ -13,43 +14,52 @@ import {
defineQuery,
getComponent,
hasComponent,
removeComponent
removeComponent,
setComponent
} from '../../ecs/functions/ComponentFunctions'
import { entityExists } from '../../ecs/functions/EntityFunctions'
import { defineSystem } from '../../ecs/functions/SystemFunctions'
import { LocalInputTagComponent } from '../../input/components/LocalInputTagComponent'
import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction'
import { Physics } from '../../physics/classes/Physics'
import {
RigidBodyComponent,
RigidBodyDynamicTagComponent,
RigidBodyKinematicPositionBasedTagComponent
} from '../../physics/components/RigidBodyComponent'
import { CollisionGroups } from '../../physics/enums/CollisionGroups'
import { ColliderComponent } from '../../scene/components/ColliderComponent'
import { UUIDComponent } from '../../scene/components/UUIDComponent'
import { TransformComponent } from '../../transform/components/TransformComponent'
import { VisibleComponent } from '../../scene/components/VisibleComponent'
import { LocalTransformComponent, TransformComponent } from '../../transform/components/TransformComponent'
import { ObjectFitFunctions } from '../../xrui/functions/ObjectFitFunctions'
import { BoundingBoxComponent } from '../components/BoundingBoxComponents'
import { EquippableComponent } from '../components/EquippableComponent'
import { EquippedComponent } from '../components/EquippedComponent'
import { EquipperComponent } from '../components/EquipperComponent'
import { changeHand, equipEntity, unequipEntity } from '../functions/equippableFunctions'
import { createInteractUI } from '../functions/interactUI'
import { addInteractableUI, removeInteractiveUI } from './InteractiveSystem'
import { addInteractableUI, InteractableTransitions, removeInteractiveUI } from './InteractiveSystem'

export function setEquippedObjectReceptor(action: ReturnType<typeof WorldNetworkAction.setEquippedObject>) {
const equippedEntity = Engine.instance.getNetworkObject(action.object.ownerId, action.object.networkId)!
if (action.$from === Engine.instance.userId) {
if (action.equip && action.$from === Engine.instance.userId) {
const equipperEntity = Engine.instance.localClientEntity
addComponent(equipperEntity, EquipperComponent, { equippedEntity })
addComponent(equippedEntity, EquippedComponent, { equipperEntity, attachmentPoint: action.attachmentPoint })
setComponent(equipperEntity, EquipperComponent, { equippedEntity })
setComponent(equippedEntity, EquippedComponent, { equipperEntity, attachmentPoint: action.attachmentPoint! })
}
const body = getComponent(equippedEntity, RigidBodyComponent)?.body
if (body) {
if (action.equip) {
addComponent(equippedEntity, RigidBodyKinematicPositionBasedTagComponent, true)
removeComponent(equippedEntity, RigidBodyDynamicTagComponent)
body.setBodyType(RigidBodyType.KinematicPositionBased, false)
if (hasComponent(equippedEntity, LocalTransformComponent)) {
removeComponent(equippedEntity, LocalTransformComponent)
}
if (hasComponent(equippedEntity, ColliderComponent)) {
removeComponent(equippedEntity, ColliderComponent)
}
Physics.changeRigidbodyType(equippedEntity, RigidBodyType.KinematicPositionBased)
} else {
addComponent(equippedEntity, RigidBodyDynamicTagComponent, true)
removeComponent(equippedEntity, RigidBodyKinematicPositionBasedTagComponent)
body.setBodyType(RigidBodyType.Dynamic, false)
Physics.changeRigidbodyType(equippedEntity, RigidBodyType.Dynamic)
}
for (let i = 0; i < body.numColliders(); i++) {
const collider = body.collider(i)
Expand Down Expand Up @@ -89,53 +99,68 @@ export function equipperQueryAll(equipperEntity: Entity) {
const equippedComponent = getComponent(equipperComponent.equippedEntity, EquippedComponent)
const attachmentPoint = equippedComponent.attachmentPoint

const target = getHandTarget(equipperEntity, attachmentPoint ?? 'left')!
const equippableTransform = getComponent(equipperComponent.equippedEntity, TransformComponent)
const target = getHandTarget(equipperEntity, attachmentPoint ?? 'right')!

const rigidbodyComponent = getComponent(equippedEntity, RigidBodyComponent)

if (rigidbodyComponent) {
rigidbodyComponent.targetKinematicPosition.copy(target.position)
rigidbodyComponent.targetKinematicRotation.copy(target.rotation)
rigidbodyComponent.body.setTranslation(target.position, true)
rigidbodyComponent.body.setRotation(target.rotation, true)
}

const equippableTransform = getComponent(equipperComponent.equippedEntity, TransformComponent)
equippableTransform.position.copy(target.position)
equippableTransform.rotation.copy(target.rotation)
}

export function equipperQueryExit(entity: Entity) {
// const equipperComponent = getComponent(entity, EquipperComponent, true)
// const equippedEntity = equipperComponent.equippedEntity
// removeComponent(equippedEntity, EquippedComponent)
}

const vec3 = new Vector3()

// export const onEquippableInteractUpdate = (entity: Entity, xrui: ReturnType<typeof createInteractUI>) => {
//
// const transform = getComponent(xrui.entity, TransformComponent)
// if (!transform || !hasComponent(Engine.instance.localClientEntity, TransformComponent)) return
// transform.position.copy(getComponent(entity, TransformComponent).position)
// transform.rotation.copy(getComponent(entity, TransformComponent).rotation)
// transform.position.y += 1

// const transition = InteractableTransitions.get(entity)!
// const isEquipped = hasComponent(entity, EquippedComponent)
// if (isEquipped) {
// if (transition.state === 'IN') {
// transition.setState('OUT')
// }
// } else {
// getAvatarBoneWorldPosition(Engine.instance.localClientEntity, 'Hips', vec3)
// const distance = vec3.distanceToSquared(transform.position)
// const inRange = distance < 5
// if (transition.state === 'OUT' && inRange) {
// transition.setState('IN')
// }
// if (transition.state === 'IN' && !inRange) {
// transition.setState('OUT')
// }
// }
// transition.update(world, (opacity) => {
// xrui.rootLayer.traverseLayersPreOrder((layer) => {
// const mat = layer.contentMesh.material as MeshBasicMaterial
// mat.opacity = opacity
// })
// })
// }
export const onEquippableInteractUpdate = (entity: Entity, xrui: ReturnType<typeof createInteractUI>) => {
const transform = getComponent(xrui.entity, TransformComponent)
if (!transform || !hasComponent(Engine.instance.localClientEntity, TransformComponent)) return
transform.position.copy(getComponent(entity, TransformComponent).position)
transform.rotation.copy(getComponent(entity, TransformComponent).rotation)
const boundingBox = getComponent(entity, BoundingBoxComponent)
if (boundingBox) {
const boundingBoxHeight = boundingBox.box.max.y - boundingBox.box.min.y
transform.position.y += boundingBoxHeight * 2
} else {
transform.position.y += 0.5
}

const transition = InteractableTransitions.get(entity)!
const isEquipped = hasComponent(entity, EquippedComponent)
if (isEquipped) {
if (transition.state === 'IN') {
transition.setState('OUT')
removeComponent(xrui.entity, VisibleComponent)
}
} else {
getAvatarBoneWorldPosition(Engine.instance.localClientEntity, 'Hips', vec3)
const distance = vec3.distanceToSquared(transform.position)
const inRange = distance < 5
if (transition.state === 'OUT' && inRange) {
transition.setState('IN')
setComponent(xrui.entity, VisibleComponent)
}
if (transition.state === 'IN' && !inRange) {
transition.setState('OUT')
}
}
transition.update(Engine.instance.deltaSeconds, (opacity) => {
if (opacity === 0) {
removeComponent(xrui.entity, VisibleComponent)
}
xrui.container.rootLayer.traverseLayersPreOrder((layer) => {
const mat = layer.contentMesh.material as MeshBasicMaterial
mat.opacity = opacity
})
})
if (hasComponent(xrui.entity, VisibleComponent))
ObjectFitFunctions.lookAtCameraFromPosition(xrui.container, transform.position)
}

/**
* @todo refactor this into i18n and configurable
Expand Down Expand Up @@ -163,22 +188,29 @@ const execute = () => {
if (keys.KeyU?.down) onKeyU()

for (const action of interactedActionQueue()) {
if (action.$from !== Engine.instance.userId) continue
if (!hasComponent(action.targetEntity!, EquippableComponent)) continue
if (
action.$from !== Engine.instance.userId ||
!action.targetEntity ||
!entityExists(action.targetEntity!) ||
!hasComponent(action.targetEntity!, EquippableComponent)
)
continue

const avatarEntity = Engine.instance.localClientEntity

const equipperComponent = getComponent(avatarEntity, EquipperComponent)
if (equipperComponent?.equippedEntity) {
const equippedComponent = getComponent(equipperComponent.equippedEntity, EquippedComponent)
const attachmentPoint = equippedComponent.attachmentPoint
if (attachmentPoint !== action.handedness) {
changeHand(avatarEntity, action.handedness)
} else {
// drop(entity, inputKey, inputValue)
}
/** @todo - figure better way of swapping hands */
// const equippedComponent = getComponent(equipperComponent.equippedEntity, EquippedComponent)
// const attachmentPoint = equippedComponent.attachmentPoint
// if (attachmentPoint !== action.handedness) {
// changeHand(avatarEntity, action.handedness)
// } else {
// drop(entity, inputKey, inputValue)
// }
unequipEntity(avatarEntity)
} else {
equipEntity(avatarEntity, action.targetEntity!, 'none')
equipEntity(avatarEntity, action.targetEntity!, 'right')
}
}

Expand All @@ -190,7 +222,8 @@ const execute = () => {
* @todo use an XRUI pool
*/
for (const entity of equippableQuery.enter()) {
if (isClient) addInteractableUI(entity, createInteractUI(entity, equippableInteractMessage))
if (isClient)
addInteractableUI(entity, createInteractUI(entity, equippableInteractMessage), onEquippableInteractUpdate)
}

for (const entity of equippableQuery.exit()) {
Expand All @@ -200,10 +233,6 @@ const execute = () => {
for (const entity of equipperQuery()) {
equipperQueryAll(entity)
}

for (const entity of equipperQuery.exit()) {
equipperQueryExit(entity)
}
}

export const EquippableSystem = defineSystem({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { ObjectFitFunctions } from '../../xrui/functions/ObjectFitFunctions'
import { InteractableComponent } from '../components/InteractableComponent'
import { gatherAvailableInteractables } from '../functions/gatherAvailableInteractables'
import { createInteractUI } from '../functions/interactUI'
import { EquippableSystem } from './EquippableSystem'

export const InteractState = defineState({
name: 'InteractState',
Expand Down Expand Up @@ -89,10 +90,10 @@ export const addInteractableUI = (

if (!update) {
update = onInteractableUpdate
const transition = createTransitionState(0.25)
transition.setState('OUT')
InteractableTransitions.set(entity, transition)
}
const transition = createTransitionState(0.25)
transition.setState('OUT')
InteractableTransitions.set(entity, transition)

InteractiveUI.set(entity, { xrui, update })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class WorldNetworkAction {
networkId: matchesNetworkId
}),
equip: matches.boolean,
attachmentPoint: matches.literals('left', 'right', 'none'),
attachmentPoint: matches.literals('left', 'right', 'none').optional(),
$cache: true,
$topic: NetworkTopics.world
})
Expand Down
Loading