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

Commit

Permalink
IR-2554 Facer Component (#10366)
Browse files Browse the repository at this point in the history
* add facer component
add node input
change behavior of hierarchy nodes to select on click, not on mouse down

* licensing

* add ability to lock x or y axis

* change onset validation

* denormalize axes object
  • Loading branch information
dinomut1 authored Jun 12, 2024
1 parent 63d67e9 commit 715ce2d
Show file tree
Hide file tree
Showing 13 changed files with 398 additions and 41 deletions.
7 changes: 7 additions & 0 deletions packages/client-core/i18n/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,13 @@
"lbl-callbackID": "Callback ID"
}
},
"facer": {
"name": "Facer",
"description": "A facer component that makes the entity always face the user, or another entity",
"target": "Target Entity",
"xAxis": "X Axis",
"yAxis": "Y Axis"
},
"grabbable": {
"name": "Grabbable",
"description": "A grabbable component that can be picked up and manipulated by the user"
Expand Down
5 changes: 4 additions & 1 deletion packages/editor/src/services/ComponentEditors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,13 @@ import { NewVolumetricComponent } from '@etherealengine/engine/src/scene/compone
import { PlaylistComponent } from '@etherealengine/engine/src/scene/components/PlaylistComponent'
import { InputComponent } from '@etherealengine/spatial/src/input/components/InputComponent'
import { ColliderComponent } from '@etherealengine/spatial/src/physics/components/ColliderComponent'
import { FacerComponent } from '@etherealengine/spatial/src/transform/components/FacerComponent'
import LoopAnimationNodeEditor from '@etherealengine/ui/src/components/editor/properties/animation'
import CameraPropertiesNodeEditor from '@etherealengine/ui/src/components/editor/properties/camera'
import ColliderComponentEditor from '@etherealengine/ui/src/components/editor/properties/collider'
import EnvMapEditor from '@etherealengine/ui/src/components/editor/properties/envmap'
import EnvMapBakeNodeEditor from '@etherealengine/ui/src/components/editor/properties/envMapBake'
import FacerNodeEditor from '@etherealengine/ui/src/components/editor/properties/facer'
import GroundPlaneNodeEditor from '@etherealengine/ui/src/components/editor/properties/groundPlane'
import ImageNodeEditor from '@etherealengine/ui/src/components/editor/properties/image'
import AmbientLightNodeEditor from '@etherealengine/ui/src/components/editor/properties/light/ambient'
Expand Down Expand Up @@ -184,7 +186,8 @@ export const ComponentEditorsState = defineState({
[InputComponent.name]: InputComponentNodeEditor,
[GrabbableComponent.name]: GrabbableComponentNodeEditor,
[ScreenshareTargetComponent.name]: ScreenshareTargetNodeEditor,
[TextComponent.name]: TextNodeEditor
[TextComponent.name]: TextNodeEditor,
[FacerComponent.name]: FacerNodeEditor
} as Record<string, EditorComponentType>
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { RigidBodyComponent } from '@etherealengine/spatial/src/physics/componen
import { TriggerComponent } from '@etherealengine/spatial/src/physics/components/TriggerComponent'
import { GroupComponent } from '@etherealengine/spatial/src/renderer/components/GroupComponent'
import { PostProcessingComponent } from '@etherealengine/spatial/src/renderer/components/PostProcessingComponent'
import { FacerComponent } from '@etherealengine/spatial/src/transform/components/FacerComponent'

export const ComponentShelfCategoriesState = defineState({
name: 'ee.editor.ComponentShelfCategories',
Expand Down Expand Up @@ -128,7 +129,8 @@ export const ComponentShelfCategoriesState = defineState({
SplineTrackComponent,
SplineComponent,
TextComponent,
ScreenshareTargetComponent
ScreenshareTargetComponent,
FacerComponent
]
} as Record<string, Component[]>
}
Expand Down
12 changes: 7 additions & 5 deletions packages/engine/src/scene/components/ParticleSystemComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
AdditiveBlending,
Blending,
BufferGeometry,
DoubleSide,
Material,
MeshBasicMaterial,
Object3D,
Expand Down Expand Up @@ -847,17 +848,18 @@ export const ParticleSystemComponent = defineComponent({
const [dudMaterial] = useDisposable(MeshBasicMaterial, entity, {
color: 0xffffff,
transparent: componentState.value.systemParameters.transparent ?? true,
blending: componentState.value.systemParameters.blending as Blending
blending: componentState.value.systemParameters.blending as Blending,
side: DoubleSide
})
//@todo: this is a hack to make trail rendering mode work correctly. We need to find out why an additional snapshot is needed
useEffect(() => {
if (rootGLTF?.value?.progress !== 100) return
if (refreshed.value) return

if (componentState.systemParameters.renderMode.value === RenderMode.Trail) {
const snapshot = GLTFSnapshotState.cloneCurrentSnapshot(sceneID!)
dispatchAction(GLTFSnapshotAction.createSnapshot(snapshot))
}
//if (componentState.systemParameters.renderMode.value === RenderMode.Trail) {
const snapshot = GLTFSnapshotState.cloneCurrentSnapshot(sceneID!)
dispatchAction(GLTFSnapshotAction.createSnapshot(snapshot))
//}
refreshed.set(true)
}, [rootGLTF?.value?.progress])

Expand Down
3 changes: 2 additions & 1 deletion packages/spatial/src/transform/TransformModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Ethereal Engine. All Rights Reserved.
*/

import { SpawnPoseState } from './SpawnPoseState'
import { FacerSystem } from './systems/FacerSystem'
import { TransformSystem } from './systems/TransformSystem'

export { SpawnPoseState, TransformSystem }
export { SpawnPoseState, TransformSystem, FacerSystem }
51 changes: 51 additions & 0 deletions packages/spatial/src/transform/components/FacerComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
CPAL-1.0 License
The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.
The Original Code is Ethereal Engine.
The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.
All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { EntityUUID, defineComponent } from '@etherealengine/ecs'
export const FacerComponent = defineComponent({
name: 'FacerComponent',
jsonID: 'IR_facer',
onInit: (entity) => ({
target: null as EntityUUID | null,
xAxis: true,
yAxis: true
}),
onSet: (entity, component, props) => {
if (typeof props?.target === 'string') {
component.target.set(props.target)
}
if (typeof props?.xAxis === 'boolean') {
component.xAxis.set(props.xAxis)
}
if (typeof props?.yAxis === 'boolean') {
component.yAxis.set(props.yAxis)
}
},
toJSON: (entity, component) => ({
target: component.target.value,
xAxis: component.xAxis.value,
yAxis: component.yAxis.value
})
})
66 changes: 66 additions & 0 deletions packages/spatial/src/transform/systems/FacerSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
CPAL-1.0 License
The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.
The Original Code is Ethereal Engine.
The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.
All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { Engine, Entity, UUIDComponent, defineQuery, defineSystem, getComponent } from '@etherealengine/ecs'
import { Matrix4, Quaternion, Vector3 } from 'three'
import { FacerComponent } from '../components/FacerComponent'
import { TransformComponent } from '../components/TransformComponent'
import { TransformSystem } from './TransformSystem'

const facerQuery = defineQuery([FacerComponent, TransformComponent])
const srcPosition = new Vector3()
const dstPosition = new Vector3()
const direction = new Vector3()
const zero = new Vector3()
const up = new Vector3(0, 1, 0)
const lookMatrix = new Matrix4()
const lookRotation = new Quaternion()

export const FacerSystem = defineSystem({
uuid: 'ir.spatial.FacerSystem',
insert: { before: TransformSystem },
execute: () => {
const viewerEntity = Engine.instance.viewerEntity
for (const entity of facerQuery()) {
const facer = getComponent(entity, FacerComponent)
const targetEntity: Entity | null = facer.target ? UUIDComponent.getEntityByUUID(facer.target) : viewerEntity
if (!targetEntity) continue
TransformComponent.getWorldPosition(entity, srcPosition)
TransformComponent.getWorldPosition(targetEntity, dstPosition)
direction.subVectors(dstPosition, srcPosition).normalize()
// look at target about enabled axes
if (!facer.xAxis) {
direction.y = 0
}
if (!facer.yAxis) {
direction.x = 0
}
lookMatrix.lookAt(zero, direction, up)
lookRotation.setFromRotationMatrix(lookMatrix)
TransformComponent.setWorldRotation(entity, lookRotation)
TransformComponent.updateFromWorldMatrix(entity)
}
}
})
43 changes: 43 additions & 0 deletions packages/ui/src/components/editor/input/Node/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
CPAL-1.0 License
The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.
The Original Code is Ethereal Engine.
The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.
All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import Component from './index'

const argTypes = {}

export default {
title: 'Editor/Input/Model',
component: Component,
parameters: {
componentSubtitle: 'ModelInput',
jest: 'Model.test.tsx',
design: {
type: 'figma',
url: ''
}
},
argTypes
}
export const Default = { args: Component.defaultProps }
59 changes: 59 additions & 0 deletions packages/ui/src/components/editor/input/Node/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
CPAL-1.0 License
The contents of this file are subject to the Common Public Attribution License
Version 1.0. (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
The License is based on the Mozilla Public License Version 1.1, but Sections 14
and 15 have been added to cover use of software over a computer network and
provide for limited attribution for the Original Developer. In addition,
Exhibit A has been modified to be consistent with Exhibit B.
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
specific language governing rights and limitations under the License.
The Original Code is Ethereal Engine.
The Original Developer is the Initial Developer. The Initial Developer of the
Original Code is the Ethereal Engine team.
All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/

import { Entity, EntityUUID, UUIDComponent, getComponent } from '@etherealengine/ecs'
import { ItemTypes } from '@etherealengine/editor/src/constants/AssetTypes'
import React from 'react'
import { useDrop } from 'react-dnd'
import { InputProps } from '../../../../primitives/tailwind/Input'
import { ControlledStringInput } from '../String'

export interface NodeInputProps extends Omit<InputProps, 'onChange'> {
value: EntityUUID
onChange?: (value: EntityUUID) => void
onRelease?: (value: EntityUUID) => void
inputRef?: React.Ref<any>
}

export function NodeInput({ onRelease, value, ...rest }: NodeInputProps) {
const [{ canDrop, isOver }, dropRef] = useDrop({
accept: [ItemTypes.Node],
async drop(item: any, monitor) {
const entity: Entity = item.value as Entity
const uuid = getComponent(entity, UUIDComponent)
onRelease?.(uuid)
},
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
})

return <ControlledStringInput ref={dropRef} value={value} {...rest} />
}

NodeInput.defaultProps = {}

export default NodeInput
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,27 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
[expandedNodes]
)

/* Event handlers */
const onMouseDown = useCallback(
const onContextMenu = (event: React.MouseEvent<HTMLDivElement>, item: HeirarchyTreeNodeType) => {
event.preventDefault()
event.stopPropagation()

setContextSelectedItem(item)
setAnchorEl(event.currentTarget)
setAnchorPosition({
left: event.clientX + 2,
top: event.clientY - 6
})
}

const handleClose = () => {
setContextSelectedItem(undefined)
setAnchorEl(null)
setAnchorPosition({ left: 0, top: 0 })
}

const onMouseDown = useCallback((e: React.MouseEvent, node: HeirarchyTreeNodeType) => {}, [])

const onClick = useCallback(
(e: MouseEvent, node: HeirarchyTreeNodeType) => {
if (e.detail === 1) {
if (e.ctrlKey) {
Expand All @@ -183,37 +202,15 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
}
}
setPrevClickedNode(node)
} else if (e.detail === 2) {
const editorCameraState = getMutableComponent(Engine.instance.cameraEntity, CameraOrbitComponent)
editorCameraState.focusedEntities.set([node.entity])
editorCameraState.refocus.set(true)
}
},
[prevClickedNode, entityHierarchy]
)

const onContextMenu = (event: React.MouseEvent<HTMLDivElement>, item: HeirarchyTreeNodeType) => {
event.preventDefault()
event.stopPropagation()

setContextSelectedItem(item)
setAnchorEl(event.currentTarget)
setAnchorPosition({
left: event.clientX + 2,
top: event.clientY - 6
})
}

const handleClose = () => {
setContextSelectedItem(undefined)
setAnchorEl(null)
setAnchorPosition({ left: 0, top: 0 })
}

const onClick = useCallback((e: MouseEvent, node: HeirarchyTreeNodeType) => {
if (e.detail === 2) {
const editorCameraState = getMutableComponent(Engine.instance.cameraEntity, CameraOrbitComponent)
editorCameraState.focusedEntities.set([node.entity])
editorCameraState.refocus.set(true)
}
}, [])

const onToggle = useCallback(
(_, node: HeirarchyTreeNodeType) => {
if (expandedNodes.value[sceneURL][node.entity]) collapseNode(node)
Expand Down
Loading

0 comments on commit 715ce2d

Please sign in to comment.