UNPKG

@playcanvas/react

Version:

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

126 lines 5.67 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { FILLMODE_NONE, FILLMODE_FILL_WINDOW, FILLMODE_KEEP_ASPECT, RESOLUTION_AUTO, Application as PlayCanvasApplication, Mouse, TouchDevice, RESOLUTION_FIXED, } from 'playcanvas'; import { AppContext, ParentContext } from './hooks'; import { PointerEventsContext } from './contexts/pointer-events-context'; import { usePicker } from './utils/picker'; import { PhysicsProvider } from './contexts/physics-context'; import { validateAndSanitizeProps, createComponentDefinition, getNullApplication } from './utils/validation'; /** * The **Application** component is the root node of the PlayCanvas React API. It creates a canvas element * and initializes a PlayCanvas application instance. * * @param {ApplicationProps} props - The props to pass to the application component. * @returns {React.ReactNode} - The application component. * * @example * <Application> * <Entity /> * </Application> */ export const Application = ({ children, className = 'pc-app', style = { width: '100%', height: '100%' }, ...props }) => { const canvasRef = useRef(null); return (_jsxs(_Fragment, { children: [_jsx("canvas", { className: className, style: style, ref: canvasRef }), _jsx(ApplicationWithoutCanvas, { canvasRef: canvasRef, ...props, children: children })] })); }; /** * An alternative Application component that does not create a canvas element. * This allows you to create a canvas independently from PlayCanvas and pass it in as a ref. * * @param {ApplicationWithoutCanvasProps} props - The props to pass to the application component. * @returns {React.ReactNode} The application component. * * @example * const canvasRef = useRef<HTMLCanvasElement>(null); * * return ( * <> * <canvas ref={canvasRef} /> * <ApplicationWithoutCanvas canvasRef={canvasRef}> * <Entity /> * </ApplicationWithoutCanvas> * </> * ); */ export const ApplicationWithoutCanvas = (props) => { const { children, ...propsToValidate } = props; const validatedProps = validateAndSanitizeProps(propsToValidate, componentDefinition); const { canvasRef, fillMode = FILLMODE_NONE, resolutionMode = RESOLUTION_AUTO, maxDeltaTime = 0.1, timeScale = 1, usePhysics = false, ...otherProps } = validatedProps; const graphicsDeviceOptions = { alpha: true, depth: true, stencil: true, antialias: true, premultipliedAlpha: true, preserveDrawingBuffer: false, powerPreference: 'default', failIfMajorPerformanceCaveat: false, desynchronized: false, xrCompatible: false, ...otherProps.graphicsDeviceOptions }; const [app, setApp] = useState(null); const appRef = useRef(null); const pointerEvents = useMemo(() => new Set(), []); usePicker(appRef.current, canvasRef.current, pointerEvents); useLayoutEffect(() => { const canvas = canvasRef.current; if (canvas && !appRef.current) { const localApp = new PlayCanvasApplication(canvas, { mouse: new Mouse(canvas), touch: new TouchDevice(canvas), graphicsDeviceOptions }); localApp.start(); localApp.setCanvasFillMode(fillMode); localApp.setCanvasResolution(resolutionMode); appRef.current = localApp; setApp(localApp); } return () => { if (!appRef.current) return; appRef.current.destroy(); appRef.current = null; setApp(null); }; }, [canvasRef, fillMode, resolutionMode, ...Object.values(graphicsDeviceOptions)]); // These app properties can be updated without re-rendering useLayoutEffect(() => { if (!app) return; app.maxDeltaTime = maxDeltaTime; app.timeScale = timeScale; }, [app, maxDeltaTime, timeScale]); if (!app) return null; return (_jsx(PhysicsProvider, { enabled: usePhysics, app: app, children: _jsx(AppContext.Provider, { value: appRef.current, children: _jsx(PointerEventsContext.Provider, { value: pointerEvents, children: _jsx(ParentContext.Provider, { value: appRef.current?.root, children: children }) }) }) })); }; const componentDefinition = createComponentDefinition("Application", () => getNullApplication(), (app) => app.destroy()); componentDefinition.schema = { ...componentDefinition.schema, canvasRef: { validate: (value) => { return value !== null && typeof value === 'object' && 'current' in value; }, errorMsg: (value) => `canvasRef must be a React ref object. Received: ${value}`, default: null }, usePhysics: { validate: (value) => typeof value === 'boolean', errorMsg: (value) => `usePhysics must be a boolean. Received: ${value}`, default: false }, fillMode: { validate: (value) => typeof value === 'string' && [FILLMODE_NONE, FILLMODE_FILL_WINDOW, FILLMODE_KEEP_ASPECT].includes(value), errorMsg: () => `"fillMode" must be one of: ${FILLMODE_NONE}, ${FILLMODE_FILL_WINDOW}, ${FILLMODE_KEEP_ASPECT}`, default: FILLMODE_NONE }, resolutionMode: { validate: (value) => typeof value === 'string' && [RESOLUTION_AUTO, RESOLUTION_FIXED].includes(value), errorMsg: () => `"resolutionMode" must be one of: ${RESOLUTION_AUTO}, ${RESOLUTION_FIXED}`, default: RESOLUTION_AUTO } }; //# sourceMappingURL=Application.js.map