Skip to content

Commit

Permalink
Prefab workflow (#10121)
Browse files Browse the repository at this point in the history
* refactor: remove categorizedasset type

* add prefab components from drag and drop

* label for resource items

* correct eslint error

* double click to place prefab at origin

* add drop on hierarchypanel

* convert animated model to simple model

* update packages

* update for prefab asset and model component

* add defult prefabs

* update for asset load

* update with ui and changes

* implement dereference WIP. Add GLTF <-> SceneJSON conversion helpers

* add uuid component

* integrate model component into GLTFState

* remove more SceneState references

* remove PrefabComponents

* remove old prefab conditional

* fix child entity indexing
apply parent transform before deletion

* correctly manage transform component conversion to gltf

* fix editor control functions test

* re-add prefab type

* ci/cd

* more ci/cd

---------

Co-authored-by: aditya-mitra <55396651+aditya-mitra@users.noreply.github.com>
Co-authored-by: David Gordon <david@hyperconstruct.io>
Co-authored-by: David Gordon <94419856+dinomut1@users.noreply.github.com>
  • Loading branch information
4 people committed May 17, 2024
1 parent b52f880 commit af6fb0b
Show file tree
Hide file tree
Showing 21 changed files with 607 additions and 51 deletions.
6 changes: 6 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@
"request": "launch",
"type": "node-terminal",
},
{
"command": "cd packages/editor && npm run test",
"name": "npm run test - editor",
"request": "launch",
"type": "node-terminal",
},
{
"command": "cd packages/spatial && npm run test",
"name": "npm run test - spatial",
Expand Down
5 changes: 4 additions & 1 deletion packages/client-core/i18n/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,8 @@
"copyURL": "Copy URL",
"openInNewTab": "Open URL in New Tab",
"deleteAsset": "Delete Asset",
"prefab": "Prefab",
"prefab-search" : "Search Prefabs ...",
"components": "Components",
"components-search": "Search Components ...",
"component-detail": {
Expand Down Expand Up @@ -1119,7 +1121,8 @@
"scene-assets": {
"no-category": "No category selected",
"search-placeholder": "Search for an asset ...",
"preview": "Preview"
"preview" : "Preview",
"info-drag-drop": "Drag and Drop these items into the scene"
}
},
"hierarchy": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
} from '@etherealengine/ecs'
import { previewScreenshot } from '@etherealengine/editor/src/functions/takeScreenshot'
import { useTexture } from '@etherealengine/engine/src/assets/functions/resourceLoaderHooks'
import { SceneState } from '@etherealengine/engine/src/scene/SceneState'
import { GLTFDocumentState } from '@etherealengine/engine/src/gltf/GLTFDocumentState'
import { ModelComponent } from '@etherealengine/engine/src/scene/components/ModelComponent'
import { getModelSceneID } from '@etherealengine/engine/src/scene/functions/loaders/ModelFunctions'
import { defineState, getMutableState, none, useHookstate, useMutableState } from '@etherealengine/hyperflux'
Expand Down Expand Up @@ -152,7 +152,7 @@ const ThumbnailJobReactor = (props: { src: string }) => {
entity: UndefinedEntity
})
const loadPromiseState = useHookstate(null as Promise<any> | null) // for asset loading
const sceneState = useHookstate(getMutableState(SceneState).scenes) // for model rendering
const sceneState = useHookstate(getMutableState(GLTFDocumentState)) // for model rendering
const [tex] = useTexture(state.fileType.value === 'texture' ? props.src : '') // for texture loading

// Load and render image
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/utils/CommonKnownContentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Ethereal Engine. All Rights Reserved.
*/
export const CommonKnownContentTypes = {
material: 'model/material',
prefab: 'model/prefab',
lookdev: 'model/lookdev',
xre: 'prefab/xre',
gltf: 'model/gltf',
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/utils/guessContentType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function guessContentType(url: string): string {
//check for xre gltf extension
if (/\.material\.gltf$/.test(contentPath)) {
return CommonKnownContentTypes.material
} else if (/\.prefab\.gltf$/.test(contentPath)) {
return CommonKnownContentTypes.prefab
} else if (/\.lookdev\.gltf$/.test(contentPath)) {
return CommonKnownContentTypes.lookdev
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
} from '@etherealengine/spatial/src/transform/components/EntityTree'

import MenuItem from '@mui/material/MenuItem'
import { PopoverPosition } from '@mui/material/Popover'
import Popover, { PopoverPosition } from '@mui/material/Popover'

import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService'
import { Engine, EntityUUID, UUIDComponent } from '@etherealengine/ecs'
Expand All @@ -58,8 +58,10 @@ import { EditorState } from '../../services/EditorServices'
import { SelectionState } from '../../services/SelectionServices'
import Search from '../Search/Search'
import useUpload from '../assets/useUpload'
import { PopoverContext } from '../element/PopoverContext'
import { PropertiesPanelButton } from '../inputs/Button'
import { ContextMenu } from '../layout/ContextMenu'
import PrefabList from '../prefabs/PrefabList'
import { HeirarchyTreeNodeType, heirarchyTreeWalker } from './HeirarchyTreeWalker'
import { HierarchyTreeNode, HierarchyTreeNodeProps, RenameNodeData, getNodeElId } from './HierarchyTreeNode'
import styles from './styles.module.scss'
Expand Down Expand Up @@ -440,7 +442,8 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
{MemoTreeNode}
</FixedSizeList>
)

const anchorElement = useHookstate<HTMLButtonElement | null>(null)
const open = !!anchorElement.value
return (
<>
<div className={styles.panelContainer}>
Expand All @@ -460,11 +463,38 @@ function HierarchyPanelContents(props: { sceneURL: string; rootEntityUUID: Entit
fontSize: '12px',
lineHeight: '0.5'
}}
onClick={() => EditorControlFunctions.createObjectFromSceneElement()}
//onClick={() => EditorControlFunctions.createObjectFromSceneElement()}
onClick={(event) => {
anchorElement.set(event.currentTarget)
}}
>
{t('editor:hierarchy.lbl-addEntity')}
</PropertiesPanelButton>
</div>
<PopoverContext.Provider
value={{
handlePopoverClose: () => {
anchorElement.set(null)
}
}}
>
<Popover
id={open ? 'add-component-popover' : undefined}
open={open}
anchorEl={anchorElement.value as HTMLButtonElement}
onClose={() => anchorElement.set(null)}
anchorOrigin={{
vertical: 'center',
horizontal: 'left'
}}
transformOrigin={{
vertical: 'center',
horizontal: 'right'
}}
>
<PrefabList />
</Popover>
</PopoverContext.Provider>
<ContextMenu open={!!anchorEl} anchorEl={anchorEl} anchorPosition={anchorPosition} onClose={handleClose}>
<MenuItem onClick={() => onRenameNode(contextSelectedItem!)}>{t('editor:hierarchy.lbl-rename')}</MenuItem>
<Hotkeys
Expand Down
3 changes: 2 additions & 1 deletion packages/editor/src/components/panels/ViewportPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ const ViewportDnD = () => {
drop(item: SceneElementType, monitor) {
const vec3 = new Vector3()
getCursorSpawnPosition(monitor.getClientOffset() as Vector2, vec3)

EditorControlFunctions.createObjectFromSceneElement([
{ name: item!.componentJsonID },
{ name: (item as SceneElementType).componentJsonID },
{ name: TransformComponent.jsonID, props: { position: vec3 } }
])
}
Expand Down
35 changes: 35 additions & 0 deletions packages/editor/src/components/prefabs/PrefabEditors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
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 config from '@etherealengine/common/src/config'
import { defineState } from '@etherealengine/hyperflux'

export const PrefabShelfCategories = defineState({
name: 'ee.editor.PrefabShelfCategories',
initial: () => {
return {
//hardcode to test replace with parseStorageProviderURLs
'Point Light Prefab': `${config.client.fileServer}/projects/default-project/assets/pointLight.prefab.gltf`,
'Geometry Prefab': `${config.client.fileServer}/projects/default-project/assets/geo.prefab.gltf`,
'Empty Prefab': 'empty'

//will continue to add more prefabs
} as Record<string, string>
}
})
154 changes: 154 additions & 0 deletions packages/editor/src/components/prefabs/PrefabList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
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 { startCase } from 'lodash'
import React, { useRef } from 'react'
import { useTranslation } from 'react-i18next'

import { getMutableState, getState, useHookstate } from '@etherealengine/hyperflux'

import PlaceHolderIcon from '@mui/icons-material/GroupAddOutlined'
import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'

import InputText from '@etherealengine/client-core/src/common/components/InputText'
import { ComponentJsonType } from '@etherealengine/engine/src/scene/types/SceneTypes'
import Typography from '@etherealengine/ui/src/primitives/mui/Typography'
import { ComponentEditorsState } from '../../functions/ComponentEditors'
import { EditorControlFunctions } from '../../functions/EditorControlFunctions'
import { addMediaNode } from '../../functions/addMediaNode'
import { usePopoverContextClose } from '../element/PopoverContext'
import { PrefabShelfCategories } from './PrefabEditors'

const PrefabListItem = ({ item }: { item: string }) => {
const { t } = useTranslation()
const Icon = getState(ComponentEditorsState)[item]?.iconComponent ?? PlaceHolderIcon
const handleClosePopover = usePopoverContextClose()

return (
<ListItemButton
sx={{ pl: 4, bgcolor: 'var(--dockBackground)' }}
onClick={() => {
const PrefabNameShelfCategories = getState(PrefabShelfCategories)
const componentJsons: ComponentJsonType[] = []
const url = PrefabNameShelfCategories[item]
// PrefabNameShelfCategories[item].forEach((component) => {
// componentJsons.push({ name: component.jsonID as string })
// })
//EditorControlFunctions.createObjectFromSceneElement(componentJsons)

//add prefab gltfs in the scene via add media node
if (url === 'empty') {
EditorControlFunctions.createObjectFromSceneElement()
} else {
//inside add media use dereference for model component
addMediaNode(url)
}

handleClosePopover()
}}
>
<ListItemIcon style={{ color: 'var(--textColor)' }}>
<Icon />
</ListItemIcon>
<ListItemText
primary={
<Typography variant="subtitle1" color={'var(--textColor)'}>
{startCase(item.replace('-', ' ').toLowerCase())}
</Typography>
}
secondary={
<Typography variant="caption" color={'var(--textColor)'}>
{t(`editor:layout.assetGrid.component-detail.${item}`)}
</Typography>
}
/>
</ListItemButton>
)
}
const ScenePrefabListItem = ({ categoryItems }: { categoryItems: string[]; isCollapsed: boolean }) => {
return (
<>
<List component={'div'} sx={{ bgcolor: 'var(--dockBackground)', width: '100%' }} disablePadding>
{categoryItems.map((item) => (
<PrefabListItem item={item} />
))}
</List>
</>
)
}

const usePrefabShelfCategories = (search: string) => {
useHookstate(getMutableState(PrefabShelfCategories)).value

if (!search) {
return Object.entries(getState(PrefabShelfCategories))
}

const searchRegExp = new RegExp(search, 'gi')

return Object.entries(getState(PrefabShelfCategories))
.map(([category, items]) => {
const filteredcategory = category.match(searchRegExp)?.length ? category : ''
return [filteredcategory, items] as [string, string]
})
.filter(([_, items]) => !!items.length)
}

export function PrefabList() {
const { t } = useTranslation()
const search = useHookstate({ local: '', query: '' })
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const shelves = usePrefabShelfCategories(search.query.value)
const shelveslist: string[] = []
shelves.map(([category, items]) => {
shelveslist.push(category)
})

const onSearch = (text: string) => {
search.local.set(text)
if (searchTimeout.current) clearTimeout(searchTimeout.current)
searchTimeout.current = setTimeout(() => {
search.query.set(text)
}, 50)
}

return (
<List
sx={{ width: 300, height: 900, bgcolor: 'var(--dockBackground)' }}
subheader={
<div style={{ padding: '0.5rem' }}>
<Typography style={{ color: 'var(--textColor)', textAlign: 'center', textTransform: 'uppercase' }}>
{t('editor:layout.assetGrid.prefab')}
</Typography>
<InputText
placeholder={t('editor:layout.assetGrid.prefab-search')}
value={search.local.value}
sx={{ mt: 1 }}
onChange={(e) => onSearch(e.target.value)}
/>
</div>
}
>
<ScenePrefabListItem categoryItems={shelveslist} isCollapsed={!!search.query.value} />
</List>
)
}

export default PrefabList
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { recursiveHipsLookup } from '@etherealengine/engine/src/avatar/AvatarBon
import { exportRelativeGLTF } from '../../functions/exportGLTF'
import { EditorState } from '../../services/EditorServices'
import BooleanInput from '../inputs/BooleanInput'
import { PropertiesPanelButton } from '../inputs/Button'
import { Button, PropertiesPanelButton } from '../inputs/Button'
import InputGroup from '../inputs/InputGroup'
import ModelInput from '../inputs/ModelInput'
import SelectInput from '../inputs/SelectInput'
Expand Down Expand Up @@ -139,6 +139,9 @@ export const ModelNodeEditor: EditorComponentType = (props) => {
{errors?.LOADING_ERROR ||
(errors?.INVALID_SOURCE && ErrorPopUp({ message: t('editor:properties.model.error-url') }))}
</InputGroup>
<Button onClick={() => modelComponent.dereference.set(true)} disabled={!modelComponent.src.value}>
Dereference
</Button>
<InputGroup name="Camera Occlusion" label={t('editor:properties.model.lbl-cameraOcclusion')}>
<BooleanInput
value={modelComponent.cameraOcclusion.value}
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/constants/AssetTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const ItemTypes = {
Node: 'Node',
Material: 'Material',
Lookdev: 'Lookdev',
Prefab: 'Prefab',
Component: 'Component'
}

Expand Down

0 comments on commit af6fb0b

Please sign in to comment.