UNPKG

@playcanvas/react

Version:

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

301 lines 17.1 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * Test Component Fixtures for GLTF Integration Tests * * These components demonstrate various GLTF modification patterns and serve as * both test fixtures and documentation examples. They are extracted from * integration tests to make the tests cleaner and more maintainable. * * Each component showcases a specific use case of the GLTF modification API, * making it easy to understand different patterns and test scenarios. */ import React from 'react'; import { Gltf } from "../../components/Gltf.js"; import { Modify } from "../../components/Modify.js"; import { Application } from "../../../Application.js"; import { Entity } from "../../../Entity.js"; import { Render } from "../../../components/Render.js"; import { Light } from "../../../components/Light.js"; import { Camera } from "../../../components/Camera.js"; import { AppContext } from "../../../hooks/index.js"; /** * Basic Gltf component with rendering enabled. * * This is the simplest use case - just render the GLTF asset with default visuals. * The asset will be instantiated and added to the scene when render is true. */ export const BasicGltf = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, render: true }, asset.id) })); }; /** * Gltf component with Modify.Node children (no rendering). * * Demonstrates that the GLTF will instantiate even when render={false} if there * are Modify.Node children present. This allows you to modify the scene without * rendering visuals, useful for server-side processing or headless testing. */ export const GltfWithModifications = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, render: false, children: _jsx(Modify.Node, { path: "**[light]", children: _jsx(Modify.Light, { remove: true }) }) }, asset.id) })); }; /** * Gltf component with no modifications (empty). * * When both render={false} and no Modify.Node children are present, the GLTF * will not instantiate. This is useful for testing lazy instantiation behavior. */ export const EmptyGltf = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, render: false }, asset.id) })); }; /** * Remove component from exact path. * * Removes a component from a specific entity matched by an exact path string. * The path must match the full hierarchy path (e.g., "RootNode.Body.Head"). * This is useful for targeting specific entities in the scene graph. */ export const RemoveComponentFromPath = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Light, { remove: true }) }) }, asset.id) })); }; /** * Remove components from all matching entities using component filter. * * Uses component filters (e.g., "[light]", "**[light]") to match all entities * that have a specific component type, regardless of their position in the hierarchy. * This is useful for bulk operations like removing all lights from a scene. */ export const RemoveAllComponentsByFilter = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Light, { remove: true }) }) }, asset.id) })); }; /** * Remove render components using single-level wildcard. * * Uses the single-level wildcard (*) to match direct children at one level. * For example, "RootNode.Body.*" matches all direct children of Body (Head, LeftArm, RightArm) * but not grandchildren. This is useful for operations on sibling entities. */ export const RemoveRenderWithWildcard = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Render, { remove: true }) }) }, asset.id) })); }; /** * Remove components using multi-level wildcard. * * Uses the multi-level wildcard (**) to match entities at any depth in the hierarchy. * For example, "Scene.**.Head" matches all "Head" entities anywhere under Scene, * regardless of how deeply nested they are. This is useful for finding entities * across complex hierarchies. */ export const RemoveWithMultiLevelWildcard = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Render, { remove: true }) }) }, asset.id) })); }; /** * Remove components using combined path and filter. * * Combines path patterns with component filters for precise targeting. * For example, "LightingRig.MainLights.*[light]" matches all direct children * of MainLights that have a light component. This allows you to be very specific * about which entities to modify. */ export const RemoveWithCombinedPathAndFilter = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Light, { remove: true }) }) }, asset.id) })); }; /** * Add new entity as child to matched path. * * Adds a new React Entity component as a child to all entities matching the path. * The new entity will be instantiated and added to the PlayCanvas scene hierarchy. * This is useful for dynamically adding props, lights, or other components to existing entities. */ export const AddChildEntity = ({ asset, path, childName, childType = 'box' }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Entity, { name: childName, children: _jsx(Render, { type: childType }) }) }) }, asset.id) })); }; /** * Add multiple children to matched path. * * Adds multiple new entities as children to all entities matching the path. * Each entity will be instantiated and added to the scene. This demonstrates * how to add multiple siblings to an existing entity in the hierarchy. */ export const AddMultipleChildren = ({ asset, path, childNames }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: childNames.map(name => (_jsx(Entity, { name: name }, name))) }) }, asset.id) })); }; /** * Clear children and replace with new entity. * * Uses the clearChildren prop to remove all existing children from matched entities, * then adds the new children specified in the Modify.Node. This is useful for * completely replacing a subtree of the hierarchy, such as replacing all weapon * attachments with new ones. */ export const ClearChildrenAndReplace = ({ asset, path, newChildName }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, clearChildren: true, children: _jsx(Entity, { name: newChildName }) }) }, asset.id) })); }; /** * Modify component with prop merge/overwrite. * * Modifies an existing component by merging or overwriting its properties. * The props specified in Modify.Light will be applied to the existing light component, * preserving other properties. This is useful for adjusting component settings * without removing and recreating the component. */ export const ModifyComponent = ({ asset, path, intensity = 2 }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Light, { intensity: intensity }) }) }, asset.id) })); }; /** * Modify component with multiple props. * * Demonstrates modifying multiple properties of an existing component at once. * All specified props will be merged onto the existing component. This shows * how to perform complex modifications like changing both the light type and color. */ export const ModifyComponentMultiple = ({ asset, path, type = 'directional', color = 'red' }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: path, children: _jsx(Modify.Light, { type: type, color: color }) }) }, asset.id) })); }; /** * Apply multiple non-conflicting rules. * * Demonstrates applying multiple Modify.Node rules that don't conflict with each other. * Each rule operates independently on different entities or different component types. * The system processes all rules and applies them to their respective targets. */ export const MultipleNonConflictingRules = ({ asset, path1, path2 }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsxs(Gltf, { asset: asset, children: [_jsx(Modify.Node, { path: path1, children: _jsx(Modify.Light, { remove: true }) }), _jsx(Modify.Node, { path: path2, children: _jsx(Modify.Render, { remove: true }) })] }, asset.id) })); }; /** * Demonstrate specificity-based conflict resolution. * * Shows how the system resolves conflicts when multiple rules target the same component. * Rules with higher specificity (more specific paths) win over less specific ones. * For example, "RootNode.Body.Head" (specificity 300) beats "**[light]" (specificity 1). * This allows you to have general rules with specific overrides. */ export const SpecificityConflictResolution = ({ asset, lowSpecificityPath, highSpecificityPath, intensity = 2 }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsxs(Gltf, { asset: asset, children: [_jsx(Modify.Node, { path: lowSpecificityPath, children: _jsx(Modify.Light, { remove: true }) }), _jsx(Modify.Node, { path: highSpecificityPath, children: _jsx(Modify.Light, { intensity: intensity }) })] }, asset.id) })); }; /** * Handle rules affecting same entity (remove component + add child). * * Demonstrates applying multiple actions to the same entity in a single Modify.Node. * You can remove components and add children in the same rule. All actions are * applied to entities matching the path. This shows the flexibility of the API. */ export const SameEntityMultipleRules = ({ asset, path, childName }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsxs(Modify.Node, { path: path, children: [_jsx(Modify.Light, { remove: true }), _jsx(Entity, { name: childName })] }) }, asset.id) })); }; /** * Use predicate function for matching. * * Instead of a path string, you can provide a function that receives each entity * and returns true if it should be matched. This allows for complex custom matching * logic that can't be expressed with path patterns. For example, matching entities * based on custom properties, component values, or complex conditions. */ export const PredicateFunctionMatching = ({ asset, predicate }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsx(Modify.Node, { path: predicate, children: _jsx(Modify.Render, { remove: true }) }) }, asset.id) })); }; /** * Complex modification chain with multiple rules. * * Demonstrates a realistic use case combining multiple rules that work together: * 1. Remove all existing lights from the scene * 2. Add a new light to a specific location * 3. Modify all render components to cast shadows * * This shows how different rules can be composed to achieve complex scene transformations. */ export const ComplexModificationChain = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsxs(Gltf, { asset: asset, children: [_jsx(Modify.Node, { path: "**[light]", children: _jsx(Modify.Light, { remove: true }) }), _jsx(Modify.Node, { path: "Scene.Characters.Player.Body.Head", children: _jsx(Entity, { name: "HeadLight" }) }), _jsx(Modify.Node, { path: "**[render]", children: _jsx(Modify.Render, { castShadows: true }) })] }, asset.id) })); }; /** * Nested hierarchy modifications. * * Shows how to modify entities at different levels of the hierarchy using wildcards. * Combines single-level wildcards (*) with clearChildren to perform complex * restructuring operations. This is useful for reorganizing scene hierarchies. */ export const NestedHierarchyModifications = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsxs(Gltf, { asset: asset, children: [_jsx(Modify.Node, { path: "Scene.Environment", clearChildren: true, children: _jsx(Entity, { name: "NewSun" }) }), _jsx(Modify.Node, { path: "Scene.Characters.*.Body.Head", children: _jsx(Entity, { name: "Hat" }) })] }, asset.id) })); }; /** * Conditional rendering based on props. * * Demonstrates how to conditionally apply rules based on React props or state. * This allows for dynamic modification behavior that can change based on user input, * game state, or other conditions. The rules are reactively updated when props change. */ export const ConditionalRendering = ({ asset, removeLights }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: removeLights && (_jsx(Modify.Node, { path: "**[light]", children: _jsx(Modify.Light, { remove: true }) })) }, asset.id) })); }; /** * Handle asset without resource. * * Tests the edge case where an asset has no resource (null). The Gltf component * should handle this gracefully without throwing errors. This is important for * handling loading states or failed asset loads. */ export const AssetWithoutResource = ({ asset }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset }, asset.id) })); }; /** * Handle non-matching paths (should do nothing). * * Demonstrates behavior when a path doesn't match any entities in the hierarchy. * The system should gracefully handle this case without errors. No modifications * are applied, and the scene remains unchanged. This is important for handling * dynamic content where paths might not always exist. */ export const NonMatchingPaths = ({ asset, path }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsxs(Modify.Node, { path: path, children: [_jsx(Modify.Light, { remove: true }), _jsx(Modify.Light, { intensity: 99 })] }) }, asset.id) })); }; /** * Helper component to capture Application root entity for testing. * * Uses the useApp hook to access the PlayCanvas Application instance and * capture its root entity. This is useful in tests to verify that entities * are correctly parented to the application root. */ export const AppRootSpy = ({ onRootCaptured }) => { const onRootCapturedRef = React.useRef(onRootCaptured); // Keep ref updated React.useEffect(() => { onRootCapturedRef.current = onRootCaptured; }, [onRootCaptured]); // Use AppContext directly to avoid throwing when app isn't ready const appContext = React.useContext(AppContext); React.useEffect(() => { if (appContext?.root) { onRootCapturedRef.current(appContext.root); } }, [appContext]); return null; }; /** * Gltf with AppRootSpy for testing parent relationships. * * Combines Gltf with AppRootSpy to enable testing of parent-child relationships * in the scene hierarchy. This is useful for verifying that instantiated entities * are correctly added to the application root. */ export const GltfWithAppSpy = ({ asset, onRootCaptured }) => { return (_jsxs(Application, { deviceTypes: ['null'], children: [_jsx(Gltf, { asset: asset }, asset.id), _jsx(AppRootSpy, { onRootCaptured: onRootCaptured })] })); }; /** * Asset without resource with spy (for testing edge cases). * * Combines AssetWithoutResource with AppRootSpy to test edge cases while * still being able to verify the application root state. Useful for testing * that no entities are added when an asset has no resource. */ export const AssetWithoutResourceWithSpy = ({ asset, onRootCaptured }) => { return (_jsxs(Application, { deviceTypes: ['null'], children: [_jsx(Gltf, { asset: asset }, asset.id), _jsx(AppRootSpy, { onRootCaptured: onRootCaptured })] })); }; /** * Add component to entity that already has that component type. * * Tests the warning when trying to add a component (Light, Render, Camera) to an entity * that already has that component. This should trigger a warnOnce warning suggesting * to use Modify.ComponentName instead. */ export const AddComponentToExisting = ({ asset, path, component }) => { return (_jsx(Application, { deviceTypes: ['null'], children: _jsx(Gltf, { asset: asset, children: _jsxs(Modify.Node, { path: path, children: [component === 'light' && _jsx(Light, { type: "directional", intensity: 2 }), component === 'render' && _jsx(Render, { type: "box" }), component === 'camera' && _jsx(Camera, { fov: 75 })] }) }, asset.id) })); }; //# sourceMappingURL=test-components.js.map