diff --git a/packages/client-core/src/networking/AvatarSpawnSystem.tsx b/packages/client-core/src/networking/AvatarSpawnSystem.tsx index 11a04785da..da6513dacd 100644 --- a/packages/client-core/src/networking/AvatarSpawnSystem.tsx +++ b/packages/client-core/src/networking/AvatarSpawnSystem.tsx @@ -34,7 +34,6 @@ import { getComponent, getOptionalComponent, PresentationSystemGroup, - useComponent, useQuery, UUIDComponent } from '@etherealengine/ecs' @@ -56,7 +55,7 @@ import { AuthState } from '../user/services/AuthService' export const AvatarSpawnReactor = (props: { sceneEntity: Entity }) => { if (!isClient) return null const { sceneEntity } = props - const gltfLoaded = useComponent(sceneEntity, GLTFComponent).progress.value === 100 + const gltfLoaded = GLTFComponent.useSceneLoaded(sceneEntity) const searchParams = useMutableState(SearchParamState) const spawnAvatar = useHookstate(false) diff --git a/packages/client-core/src/systems/LoadingUISystem.tsx b/packages/client-core/src/systems/LoadingUISystem.tsx index d306f00d61..e44b4442f9 100755 --- a/packages/client-core/src/systems/LoadingUISystem.tsx +++ b/packages/client-core/src/systems/LoadingUISystem.tsx @@ -140,9 +140,9 @@ export const LoadingUISystemState = defineState({ const LoadingReactor = (props: { sceneEntity: Entity }) => { const { sceneEntity } = props - const gltfComponent = useComponent(props.sceneEntity, GLTFComponent) + const gltfComponent = useComponent(sceneEntity, GLTFComponent) const loadingProgress = gltfComponent.progress.value - const sceneLoaded = loadingProgress === 100 + const sceneLoaded = GLTFComponent.useSceneLoaded(sceneEntity) const locationState = useMutableState(LocationState) const state = useMutableState(LoadingUISystemState) diff --git a/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts b/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts index 29b0d85a9d..307507df45 100644 --- a/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts +++ b/packages/editor/src/components/hierarchy/HierarchyTreeWalker.ts @@ -23,8 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { ComponentType, getComponent, hasComponent } from '@etherealengine/ecs/src/ComponentFunctions' -import { Entity } from '@etherealengine/ecs/src/Entity' +import { getComponent, hasComponent } from '@etherealengine/ecs/src/ComponentFunctions' +import { Entity, EntityUUID } from '@etherealengine/ecs/src/Entity' import { entityExists } from '@etherealengine/ecs/src/EntityFunctions' import { SourceComponent } from '@etherealengine/engine/src/scene/components/SourceComponent' import { getState } from '@etherealengine/hyperflux' @@ -68,7 +68,7 @@ function buildHierarchyTree( sceneID: string, showModelChildren: boolean ) { - const uuid = node.extensions && (node.extensions[UUIDComponent.jsonID] as ComponentType) + const uuid = node.extensions && (node.extensions[UUIDComponent.jsonID] as EntityUUID) const entity = UUIDComponent.getEntityByUUID(uuid!) if (!entity || !entityExists(entity)) return diff --git a/packages/editor/src/systems/ClickPlacementSystem.tsx b/packages/editor/src/systems/ClickPlacementSystem.tsx index 475a25b9a5..db23567ede 100644 --- a/packages/editor/src/systems/ClickPlacementSystem.tsx +++ b/packages/editor/src/systems/ClickPlacementSystem.tsx @@ -36,7 +36,6 @@ import { getOptionalComponent, removeComponent, setComponent, - useComponent, useOptionalComponent } from '@etherealengine/ecs' import { GLTFComponent } from '@etherealengine/engine/src/gltf/GLTFComponent' @@ -115,7 +114,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { const { parentEntity } = props const clickState = useState(getMutableState(ClickPlacementState)) const editorState = useState(getMutableState(EditorHelperState)) - const gltfComponent = useComponent(parentEntity, GLTFComponent) + const sceneLoaded = GLTFComponent.useSceneLoaded(parentEntity) const errors = useEntityErrors(clickState.placementEntity.value, ModelComponent) // const renderers = defineQuery([RendererComponent]) @@ -132,7 +131,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { // }, [editorState.placementMode]) useEffect(() => { - if (gltfComponent.progress.value < 100) return + if (!sceneLoaded) return if (editorState.placementMode.value === PlacementMode.CLICK) { SelectionState.updateSelection([]) if (clickState.placementEntity.value) return @@ -146,7 +145,7 @@ const ClickPlacementReactor = (props: { parentEntity: Entity }) => { clickState.placementEntity.set(UndefinedEntity) SelectionState.updateSelection(selectedEntities) } - }, [editorState.placementMode, gltfComponent.progress]) + }, [editorState.placementMode, sceneLoaded]) useEffect(() => { if (!clickState.placementEntity.value) return diff --git a/packages/engine/src/gltf/GLTFComponent.tsx b/packages/engine/src/gltf/GLTFComponent.tsx index c2803003e4..af991ac3ac 100644 --- a/packages/engine/src/gltf/GLTFComponent.tsx +++ b/packages/engine/src/gltf/GLTFComponent.tsx @@ -28,6 +28,8 @@ import React, { useEffect } from 'react' import { parseStorageProviderURLs } from '@etherealengine/common/src/utils/parseSceneJSON' import { + Component, + ComponentJSONIDMap, defineComponent, Entity, EntityUUID, @@ -44,13 +46,36 @@ import { dispatchAction, getState, useHookstate } from '@etherealengine/hyperflu import { FileLoader } from '../assets/loaders/base/FileLoader' import { BINARY_EXTENSION_HEADER_MAGIC, EXTENSIONS, GLTFBinaryExtension } from '../assets/loaders/gltf/GLTFExtensions' -import { ModelComponent } from '../scene/components/ModelComponent' import { SourceComponent } from '../scene/components/SourceComponent' import { SceneJsonType } from '../scene/types/SceneTypes' import { migrateSceneJSONToGLTF } from './convertJsonToGLTF' import { GLTFDocumentState, GLTFSnapshotAction } from './GLTFDocumentState' import { ResourcePendingComponent } from './ResourcePendingComponent' +const loadDependencies = { + ['EE_model']: ['scene'] +} as Record + +type ComponentDependencies = Record + +const buildComponentDependencies = (json: GLTF.IGLTF) => { + const dependencies = {} as ComponentDependencies + if (!json.nodes) return dependencies + for (const node of json.nodes) { + if (!node.extensions || !node.extensions[UUIDComponent.jsonID]) continue + const uuid = node.extensions[UUIDComponent.jsonID] as EntityUUID + const extensions = Object.keys(node.extensions) + for (const extension of extensions) { + if (loadDependencies[extension]) { + if (!dependencies[uuid]) dependencies[uuid] = [] + dependencies[uuid].push(ComponentJSONIDMap.get(extension)!) + } + } + } + + return dependencies +} + export const GLTFComponent = defineComponent({ name: 'GLTFComponent', @@ -59,7 +84,8 @@ export const GLTFComponent = defineComponent({ src: '', // internals extensions: {}, - progress: 0 + progress: 0, + dependencies: undefined as ComponentDependencies | undefined } }, @@ -67,41 +93,61 @@ export const GLTFComponent = defineComponent({ if (typeof json?.src === 'string') component.src.set(json.src) }, + useDependenciesLoaded(entity: Entity) { + const dependencies = useComponent(entity, GLTFComponent).dependencies + return !!(dependencies.value && !dependencies.keys?.length) + }, + + useSceneLoaded(entity: Entity) { + const gltfComponent = useComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies + const progress = gltfComponent.progress.value + return !!(dependencies.value && !dependencies.keys?.length) && progress === 100 + }, + + isSceneLoaded(entity: Entity) { + const gltfComponent = getComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies + const progress = gltfComponent.progress + return !!(dependencies && !Object.keys(dependencies).length) && progress === 100 + }, + reactor: () => { const entity = useEntityContext() const gltfComponent = useComponent(entity, GLTFComponent) + const dependencies = gltfComponent.dependencies useGLTFDocument(gltfComponent.src.value, entity) const documentID = useComponent(entity, SourceComponent).value - return + return ( + <> + + {dependencies.value && dependencies.keys?.length ? ( + + ) : null} + + ) } }) const ResourceReactor = (props: { documentID: string; entity: Entity }) => { + const dependenciesLoaded = GLTFComponent.useDependenciesLoaded(props.entity) const resourceQuery = useQuery([SourceComponent, ResourcePendingComponent]) const sourceEntities = useHookstate(SourceComponent.entitiesBySourceState[props.documentID]) useEffect(() => { if (getComponent(props.entity, GLTFComponent).progress === 100) return if (!getState(GLTFDocumentState)[props.documentID]) return - const document = getState(GLTFDocumentState)[props.documentID] - const modelNodes = document.nodes?.filter((node) => !!node.extensions?.[ModelComponent.jsonID]) - if (modelNodes) { - for (const node of modelNodes) { - //check if an entity exists for this node, and has a model component - const uuid = node.extensions![UUIDComponent.jsonID] as EntityUUID - if (!UUIDComponent.entitiesByUUIDState[uuid]) return - const entity = UUIDComponent.entitiesByUUIDState[uuid].value - const model = getOptionalComponent(entity, ModelComponent) - //ensure that model contents have been loaded into the scene - if (!model?.scene) return - } - } + const entities = resourceQuery.filter((e) => getComponent(e, SourceComponent) === props.documentID) if (!entities.length) { - getMutableComponent(props.entity, GLTFComponent).progress.set(100) + if (dependenciesLoaded) getMutableComponent(props.entity, GLTFComponent).progress.set(100) return } @@ -121,14 +167,83 @@ const ResourceReactor = (props: { documentID: string; entity: Entity }) => { const progress = resources.reduce((acc, resource) => acc + resource.progress, 0) const total = resources.reduce((acc, resource) => acc + resource.total, 0) + if (!total) return - const percentage = total === 0 ? 100 : (progress / total) * 100 + const percentage = Math.floor(Math.min((progress / total) * 100, dependenciesLoaded ? 100 : 99)) getMutableComponent(props.entity, GLTFComponent).progress.set(percentage) - }, [resourceQuery, sourceEntities]) + }, [resourceQuery, sourceEntities, dependenciesLoaded]) return null } +const ComponentReactor = (props: { gltfComponentEntity: Entity; entity: Entity; component: Component }) => { + const { gltfComponentEntity, entity, component } = props + const dependencies = loadDependencies[component.jsonID!] + const comp = useComponent(entity, component) + + useEffect(() => { + const compValue = comp.value + for (const key of dependencies) { + if (!compValue[key]) return + } + + // console.log(`All dependencies loaded for entity: ${entity} on component: ${component.jsonID}`) + + const gltfComponent = getMutableComponent(gltfComponentEntity, GLTFComponent) + const uuid = getComponent(entity, UUIDComponent) + gltfComponent.dependencies.set((prev) => { + const dependencyArr = prev![uuid] as Component[] + const index = dependencyArr.findIndex((compItem) => compItem.jsonID === component.jsonID) + dependencyArr.splice(index, 1) + if (!dependencyArr.length) { + delete prev![uuid] + } + return prev + }) + }, [...dependencies.map((key) => comp[key])]) + + return null +} + +const DependencyEntryReactor = (props: { gltfComponentEntity: Entity; uuid: string; components: Component[] }) => { + const { gltfComponentEntity, uuid, components } = props + const entity = UUIDComponent.useEntityByUUID(uuid as EntityUUID) as Entity | undefined + return entity ? ( + <> + {components.map((component) => { + return ( + + ) + })} + + ) : null +} + +const DependencyReactor = (props: { gltfComponentEntity: Entity; dependencies: ComponentDependencies }) => { + const { gltfComponentEntity, dependencies } = props + const entries = Object.entries(dependencies) + + return ( + <> + {entries.map(([uuid, components]) => { + return ( + + ) + })} + + ) +} + const onError = (error: ErrorEvent) => { // console.error(error) } @@ -187,6 +302,9 @@ const useGLTFDocument = (url: string, entity: Entity) => { json = migrateSceneJSONToGLTF(json) } + const dependencies = buildComponentDependencies(json) + state.dependencies.set(dependencies) + dispatchAction( GLTFSnapshotAction.createSnapshot({ source: getComponent(entity, SourceComponent), diff --git a/packages/engine/src/scene/components/ParticleSystemComponent.ts b/packages/engine/src/scene/components/ParticleSystemComponent.ts index 374bffde30..d267efa9a1 100644 --- a/packages/engine/src/scene/components/ParticleSystemComponent.ts +++ b/packages/engine/src/scene/components/ParticleSystemComponent.ts @@ -868,7 +868,7 @@ export const ParticleSystemComponent = defineComponent({ const metadata = useHookstate({ textures: {}, geometries: {}, materials: {} } as ParticleSystemMetadata) const sceneID = useOptionalComponent(entity, SourceComponent)?.value const rootEntity = useHookstate(getMutableState(GLTFSourceState))[sceneID ?? ''].value - const rootGLTF = useOptionalComponent(rootEntity, GLTFComponent) + const sceneLoaded = GLTFComponent.useSceneLoaded(rootEntity) const refreshed = useHookstate(false) const [geoDependency] = useGLTF(componentState.value.systemParameters.instancingGeometry!, entity, (url) => { @@ -890,7 +890,7 @@ export const ParticleSystemComponent = defineComponent({ }) //@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 (!sceneLoaded) return if (refreshed.value) return //if (componentState.systemParameters.renderMode.value === RenderMode.Trail) { @@ -898,7 +898,7 @@ export const ParticleSystemComponent = defineComponent({ dispatchAction(GLTFSnapshotAction.createSnapshot(snapshot)) //} refreshed.set(true) - }, [rootGLTF?.value?.progress]) + }, [sceneLoaded]) useEffect(() => { //add dud material diff --git a/packages/engine/src/visualscript/components/VisualScriptComponent.tsx b/packages/engine/src/visualscript/components/VisualScriptComponent.tsx index e82d5c73e3..4189e15dc6 100644 --- a/packages/engine/src/visualscript/components/VisualScriptComponent.tsx +++ b/packages/engine/src/visualscript/components/VisualScriptComponent.tsx @@ -111,8 +111,7 @@ export const VisualScriptComponent = defineComponent({ }) const LoadReactor = (props: { entity: Entity; gltfAncestor: Entity }) => { - const gltfComponent = useComponent(props.gltfAncestor, GLTFComponent) - const loaded = gltfComponent.progress.value === 100 + const loaded = GLTFComponent.useSceneLoaded(props.gltfAncestor) useEffect(() => { setComponent(props.entity, VisualScriptComponent, { run: true }) diff --git a/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts b/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts index 20e8dd1758..179f8d4aa5 100644 --- a/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts +++ b/packages/spatial/src/common/functions/OnBeforeCompilePlugin.ts @@ -58,7 +58,6 @@ export type PluginType = PluginObjectType | typeof Material.prototype.onBeforeCo /**@deprecated Use setPlugin instead */ export function addOBCPlugin(material: Material, plugin: PluginType): void { material.onBeforeCompile = plugin as any - console.log(material.onBeforeCompile) material.needsUpdate = true } diff --git a/packages/ui/src/components/editor/panels/Viewport/container/index.tsx b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx index e744cfb1b6..580bd920c3 100644 --- a/packages/ui/src/components/editor/panels/Viewport/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Viewport/container/index.tsx @@ -123,6 +123,7 @@ const ViewportDnD = ({ children }: { children: React.ReactNode }) => { const SceneLoadingProgress = ({ rootEntity }) => { const { t } = useTranslation() const progress = useComponent(rootEntity, GLTFComponent).progress.value + const loaded = GLTFComponent.useSceneLoaded(rootEntity) const resourcePendingQuery = useQuery([ResourcePendingComponent]) const root = getComponent(rootEntity, SourceComponent) const sceneModified = useHookstate(getMutableState(GLTFModifiedState)[root]).value @@ -142,12 +143,13 @@ const SceneLoadingProgress = ({ rootEntity }) => { } }, [sceneModified]) - if (progress === 100) return null + if (loaded) return null return ( ) @@ -183,8 +185,8 @@ const ViewPortPanelContainer = () => { {sceneName.value ? : null} {sceneName.value ? ( <> - {rootEntity.value && }
+ {rootEntity.value && } ) : (