UNPKG

@playcanvas/react

Version:

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

213 lines (212 loc) 8.64 kB
import { EnvLighting, Quat, Sky, SKYTYPE_DOME, SKYTYPE_INFINITE } from "playcanvas"; import { useEffect, useRef } from "react"; import { useApp } from "../hooks/use-app.js"; import { createComponentDefinition, getStaticNullApplication, validatePropsWithDefaults, warnOnce } from "../utils/validation.js"; import { Asset } from "playcanvas"; import dedent from "dedent"; const appUUIDs = new Set(); /** * @beta * Environment component * The Environment component is used to set the environment lighting and skybox. * * @example * ```tsx * <Environment /> * ``` */ function Environment(props) { const app = useApp(); // split the sky props and scene props const { center, scale, rotation, depthWrite, type, showSkybox, ...sceneProps } = props; const skyProps = { center, scale, rotation, depthWrite, type, showSkybox }; // Sanitize and validate the props const safeSceneProps = validatePropsWithDefaults(sceneProps, sceneComponentDefinition); const safeSkyProps = validatePropsWithDefaults(skyProps, skyComponentDefinition); /** * We want to ensure that the environment is only set once per app instance. * This is because the environment is a global state and we don't want to * set it multiple times. * * If multiple components are used in the same app instance, we will warn the user * and only the first component will be used. */ const appHasEnvironment = useRef(false); useEffect(() => { const appUUID = app.root.getGuid(); const hasEnvironment = appUUIDs.has(appUUID); appUUIDs.add(appUUID); appHasEnvironment.current = hasEnvironment; if (hasEnvironment) { warnOnce(dedent `Multiple \`<Environment/>\` components have been mounted. Only the first \`<Environment/>\` component will be used.`); } return () => { appUUIDs.delete(appUUID); }; }); /** * Sets the skybox of the environment. */ useEffect(() => { // If the app already has an environment, don't override it if (appHasEnvironment.current) return; const skyBoxAsset = safeSceneProps.skybox; if (!skyBoxAsset) return; const isCubeMap = Array.isArray(skyBoxAsset.resources) && skyBoxAsset.resources.length === 6; let skybox = skyBoxAsset.resource; // If the skybox is not a cube map, try to generate a cube map from it. if (!isCubeMap) { skybox = EnvLighting.generateSkyboxCubemap(skyBoxAsset.resource); } app.scene.skybox = skybox; return () => { if (app?.scene) { app.scene.skybox = null; } }; }, [appHasEnvironment.current, safeSceneProps.skybox?.id]); /** * Sets the environment lighting. */ useEffect(() => { // If the app already has an environment, don't override it if (appHasEnvironment.current) return; app.scene.envAtlas = safeSceneProps?.envAtlas?.resource ?? null; return () => { if (app?.scene) { app.scene.envAtlas = null; } }; }, [appHasEnvironment.current, safeSceneProps.envAtlas?.id]); /** * Sets the remaining environment settings. */ useEffect(() => { if (appHasEnvironment.current) return; app.scene.exposure = safeSceneProps.exposure ?? 1; app.scene.envAtlas = safeSceneProps.envAtlas?.resource ?? null; if (safeSkyProps.rotation) { app.scene.skyboxRotation = new Quat().setFromEulerAngles(safeSkyProps.rotation[0], safeSkyProps.rotation[1], safeSkyProps.rotation[2]); } if (safeSkyProps.scale) { app.scene.sky.node.setLocalScale(...safeSkyProps.scale); } if (safeSkyProps.position) { app.scene.sky.node.setLocalPosition(...safeSkyProps.position); } if (safeSkyProps.center) { app.scene.sky.center.set(...safeSkyProps.center); } app.scene.sky.type = safeSkyProps.type ?? SKYTYPE_DOME; app.scene.sky.depthWrite = safeSkyProps.depthWrite ?? true; // Set the skybox mip level app.scene.skyboxMip = safeSceneProps.skyboxMip ?? 0; app.scene.skyboxLuminance = safeSceneProps.skyboxLuminance ?? 1; app.scene.skyboxIntensity = safeSceneProps.skyboxIntensity ?? 1; app.scene.skyboxHighlightMultiplier = safeSceneProps.skyboxHighlightMultiplier ?? 1; const layer = app?.scene?.layers?.getLayerByName('Skybox'); if (layer) { layer.enabled = safeSkyProps.showSkybox ?? true; } return () => { /** * We have hardcoded the default values for the scene and sky in order to reset them * * This isn't perfect as any changes the the engine defaults will break this. * TODO: Find a better way to reset the scene and sky. */ if (app.scene) { app.scene.exposure = 1; app.scene.skyboxRotation = new Quat().setFromEulerAngles(0, 0, 0); app.scene.sky.node.setLocalScale(1, 1, 1); app.scene.sky.node.setLocalPosition(0, 0, 0); app.scene.sky.center.set(0, 0.05, 0); app.scene.sky.type = SKYTYPE_INFINITE; app.scene.sky.depthWrite = false; app.scene.skyboxMip = 0; app.scene.skyboxLuminance = 0; app.scene.skyboxIntensity = 1; app.scene.skyboxHighlightMultiplier = 1; const layer = app?.scene?.layers?.getLayerByName('Skybox'); if (layer) { layer.enabled = true; } } }; }, [ appHasEnvironment.current, safeSceneProps.exposure, safeSkyProps.type, safeSkyProps.depthWrite, safeSkyProps.showSkybox, safeSceneProps.skyboxMip, safeSceneProps.skyboxLuminance, safeSceneProps.skyboxIntensity, safeSceneProps.skyboxHighlightMultiplier, // compute keys for scale, rotation, and center `scale-${safeSkyProps.scale?.join('-')}`, `rotation-${safeSkyProps.rotation?.join('-')}`, `center-${safeSkyProps.center?.join('-')}`, ]); return null; } ; // Component Definitions const skyComponentDefinition = createComponentDefinition("Sky", () => new Sky(getStaticNullApplication().scene), (sky) => sky.resetSkyMesh()); const sceneComponentDefinition = createComponentDefinition("Scene", () => getStaticNullApplication().scene); skyComponentDefinition.schema = { ...skyComponentDefinition.schema, scale: { validate: (value) => Array.isArray(value) && value.length === 3 && value.every(v => typeof v === 'number'), errorMsg: (value) => `Expected an array of 3 numbers, got \`${typeof value}\``, default: [100, 100, 100], }, rotation: { validate: (value) => Array.isArray(value) && value.length === 3 && value.every(v => typeof v === 'number'), errorMsg: (value) => `Expected an array of 3 numbers, got \`${typeof value}\``, default: [0, 0, 0], }, position: { validate: (value) => Array.isArray(value) && value.length === 3 && value.every(v => typeof v === 'number'), errorMsg: (value) => `Expected an array of 3 numbers, got \`${typeof value}\``, default: [0, 0, 0], }, center: { validate: (value) => Array.isArray(value) && value.length === 3 && value.every(v => typeof v === 'number'), errorMsg: (value) => `Expected an array of 3 numbers, got \`${typeof value}\``, default: [0, 0.05, 0], }, showSkybox: { validate: (value) => typeof value === "boolean", errorMsg: (value) => `Expected a boolean, got \`${typeof value}\``, default: true, } }; sceneComponentDefinition.schema = { ...sceneComponentDefinition.schema, envAtlas: { validate: (value) => value instanceof Asset && value.type === 'texture', errorMsg: (value) => `Expected a \`Asset\` instance, got \`${typeof value}\``, default: null, }, skybox: { validate: (value) => value instanceof Asset && value.type === 'texture', errorMsg: (value) => `Expected a \`Asset\` instance, got \`${typeof value}\``, default: null, }, }; export { Environment }; //# sourceMappingURL=Environment.js.map