import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { Mesh, Object3D, Scene, Texture } from 'three' import { Engine } from '@xrengine/engine/src/ecs/classes/Engine' import { addComponent, ComponentType, getComponent, getComponentState, getOrAddComponent, hasComponent, useComponent } from '@xrengine/engine/src/ecs/functions/ComponentFunctions' import { iterateEntityNode } from '@xrengine/engine/src/ecs/functions/EntityTree' import { InstancingComponent, InstancingStagingComponent, InstancingUnstagingComponent, SampleMode, ScatterMode, ScatterProperties, ScatterState, SourceProperties, TextureRef, VertexProperties } from '@xrengine/engine/src/scene/components/InstancingComponent' import { ModelComponent } from '@xrengine/engine/src/scene/components/ModelComponent' import { NameComponent } from '@xrengine/engine/src/scene/components/NameComponent' import { UUIDComponent } from '@xrengine/engine/src/scene/components/UUIDComponent' import { GRASS_PROPERTIES_DEFAULT_VALUES, MESH_PROPERTIES_DEFAULT_VALUES } from '@xrengine/engine/src/scene/functions/loaders/InstancingFunctions' import getFirstMesh from '@xrengine/engine/src/scene/util/getFirstMesh' import { State, useState } from '@xrengine/hyperflux' import AcUnitIcon from '@mui/icons-material/AcUnit' import { PropertiesPanelButton } from '../inputs/Button' import { ImagePreviewInputGroup } from '../inputs/ImagePreviewInput' import InputGroup from '../inputs/InputGroup' import NumericInputGroup from '../inputs/NumericInputGroup' import SelectInput from '../inputs/SelectInput' import { TexturePreviewInputGroup } from '../inputs/TexturePreviewInput' import CollapsibleBlock from '../layout/CollapsibleBlock' import InstancingGrassProperties from './InstancingGrassProperties' import InstancingMeshProperties from './InstancingMeshProperties' import NodeEditor from './NodeEditor' import { EditorComponentType, traverseScene } from './Util' export const InstancingNodeEditor: EditorComponentType = (props) => { const { t } = useTranslation() const entityState = useState(props.entity) const entity = entityState.value const scatterState = useComponent(entity, InstancingComponent) const scatter = scatterState.value const sampleProps = scatter.sampleProperties as ScatterProperties & VertexProperties const updateProperty = useCallback( function (_, prop: keyof typeof scatter) { const state = scatterState[prop] return (val) => { state.set(val) } }, [entityState] ) const updateSampleProp = useCallback( function (prop: keyof (ScatterProperties & VertexProperties)) { const updateSampleProps = updateProperty(InstancingComponent, 'sampleProperties') return (val) => { const sampleProps = { ...scatter.sampleProperties } sampleProps[prop] = val updateSampleProps(sampleProps) } }, [entityState] ) const texPath = (tex: TextureRef) => tex.src const height = texPath(sampleProps.heightMap) const density = texPath(sampleProps.densityMap) function initialSurfaces(): { label: string; value: string }[] { const surfaces = traverseScene( (eNode) => { return { label: getComponent(eNode, NameComponent) ?? '', value: getComponent(eNode, UUIDComponent) } }, (eNode) => { if (eNode === entity) return false if (hasComponent(eNode, ModelComponent)) { const obj3d = getComponentState(eNode, ModelComponent).scene.value as Scene | undefined if (!obj3d) return false const mesh = getFirstMesh(obj3d) return !!mesh && mesh.geometry.hasAttribute('uv') && mesh.geometry.hasAttribute('normal') } return false } ) return surfaces } const surfaces = useState(initialSurfaces()) const onUnstage = () => { if (!hasComponent(entity, InstancingUnstagingComponent)) { addComponent(entity, InstancingUnstagingComponent, {}) } } const onStage = async () => { if (!hasComponent(entity, InstancingStagingComponent)) { addComponent(entity, InstancingStagingComponent, {}) } } const onReload = async () => { await onUnstage() await onStage() } const onChangeMode = (mode) => { if (scatter.mode === mode) return const scene = getComponentState(entity, ModelComponent).scene! as any if (!scene.value) return const uData = JSON.parse(JSON.stringify(scene.userData.value)) uData[scatter.mode] = scatter.sourceProperties let srcProperties if (uData[mode] !== undefined) { srcProperties = uData[mode] } else { switch (mode) { case ScatterMode.GRASS: srcProperties = GRASS_PROPERTIES_DEFAULT_VALUES break case ScatterMode.MESH: srcProperties = MESH_PROPERTIES_DEFAULT_VALUES break } } scene.userData.set(uData) updateProperty(InstancingComponent, 'sourceProperties')(srcProperties) updateProperty(InstancingComponent, 'mode')(mode) } return ( {[SampleMode.SCATTER, SampleMode.VERTICES].includes(scatter.sampling) && ( <> )} {scatter.mode === ScatterMode.GRASS && ( } onChange={updateProperty(InstancingComponent, 'sourceProperties')} /> )} {scatter.mode === ScatterMode.MESH && ( } onChange={updateProperty(InstancingComponent, 'sourceProperties')} /> )} {scatter.state === ScatterState.UNSTAGED && ( {t('editor:properties:instancing.lbl-load')} )} {scatter.state === ScatterState.STAGING &&

{t('Loading...')}

} {scatter.state === ScatterState.STAGED && ( {t('editor:properties:instancing.lbl-unload')} {t('editor:properties:instancing.lbl-reload')} )}
) } InstancingNodeEditor.iconComponent = AcUnitIcon export default InstancingNodeEditor