UNPKG

@playcanvas/react

Version:

A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.

111 lines 4.17 kB
"use client"; import { useMemo } from 'react'; import { useParent } from "../../hooks/use-parent.js"; import { useGltf } from "./use-gltf.js"; /** * Hook to find entities by path relative to the current parent context * Uses the unified PathMatcher for consistent syntax with Modify.Node * @param path - String path or predicate function to match entities * @returns The matched entity, array of entities (for wildcards), or null if not found * @example * ```tsx * // Find by path * const handEntity = useEntity('arm.hand'); * // Find by predicate * const lightsEntity = useEntity((entity) => entity.c?.light !== undefined); * // Find with wildcard * const allChildren = useEntity('*'); * // Find with component filter * const lights = useEntity('**[light]'); * ``` */ export function useEntity(path) { const parentEntity = useParent(); const { hierarchyCache, pathMatcher } = useGltf(); return useMemo(() => { if (!parentEntity) { return null; } let matches = []; // If path is a predicate function, use the (fast) predicate helper if (typeof path === 'function') { matches = findEntitiesByPredicate(parentEntity, path, hierarchyCache); // If path is a string, use the (powerful) pattern helper } else { // Guard against empty string path if (path === '') return null; matches = findEntitiesByPattern(parentEntity, path, pathMatcher); } // --- Return logic --- // For wildcard/predicate, always return array or null const hasWildcards = typeof path === 'function' || (typeof path === 'string' && (path.includes('*') || path.includes('['))); if (matches.length === 0) { return null; } if (matches.length === 1 && !hasWildcards) { // Exact path with single match return matches[0]; } // Multiple matches or wildcard/predicate return matches; }, [path, parentEntity, hierarchyCache, pathMatcher]); } /** * Helper to find entities by predicate function (relative) * Traverses descendants of root and checks predicate */ function findEntitiesByPredicate(root, predicate, hierarchyCache) { const matches = []; function traverse(entity) { // O(1) lookup using GUID as Map key const metadata = hierarchyCache.get(entity.getGuid()); if (metadata && predicate(entity, metadata)) { matches.push(entity); } // Traverse children for (const child of entity.children) { traverse(child); } } // Start traversal from the children, not the root itself for (const child of root.children) { traverse(child); } return matches; } /** * Helper to find entities by string pattern (relative) * Uses the powerful PathMatcher for consistent syntax */ function findEntitiesByPattern(rootEntity, pattern, pathMatcher) { const matches = []; /** * Recursive traversal that builds relative paths * @param entity The entity to check * @param relativePath The path relative to rootEntity */ function traverse(entity, relativePath) { // Check if *this* entity matches the pattern if (pathMatcher.match(pattern, relativePath, entity)) { matches.push(entity); } // Traverse children, building the next relative path for (const child of entity.children) { const childEntity = child; const childName = childEntity.name; const childRelativePath = relativePath ? `${relativePath}.${childName}` : childName; traverse(childEntity, childRelativePath); } } // This is the key: start the traversal from the *children* // of the rootEntity. This makes all paths relative. // 'useEntity("Head")' (from 'Body') will build a relativePath of 'Head' // and match it against the pattern 'Head'. for (const child of rootEntity.children) { traverse(child, child.name); } return matches; } //# sourceMappingURL=use-entity.js.map