@playcanvas/react
Version:
A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.
301 lines • 17.1 kB
JavaScript
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