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