diff --git a/cmake/modules/FindUFE.cmake b/cmake/modules/FindUFE.cmake index 022ffe8c89..0972ba5b92 100644 --- a/cmake/modules/FindUFE.cmake +++ b/cmake/modules/FindUFE.cmake @@ -135,6 +135,13 @@ set(UFE_LIGHTS_SUPPORT FALSE CACHE INTERNAL "ufeLights") if (UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/lightHandler.h") set(UFE_LIGHTS_SUPPORT TRUE CACHE INTERNAL "ufeLights") message(STATUS "Maya has UFE lights API") + + set(UFE_VOLUME_LIGHTS_SUPPORT FALSE CACHE INTERNAL "ufeVolumeLights") + file(STRINGS ${UFE_INCLUDE_DIR}/ufe/light.h UFE_HAS_API REGEX "VolumeProps") + if(UFE_HAS_API) + set(UFE_VOLUME_LIGHTS_SUPPORT TRUE CACHE INTERNAL "ufeVolumeLights") + message(STATUS "Maya has UFE VolumeLights API") + endif() endif() set(UFE_MATERIALS_SUPPORT FALSE CACHE INTERNAL "ufeMaterials") diff --git a/lib/mayaUsd/ufe/CMakeLists.txt b/lib/mayaUsd/ufe/CMakeLists.txt index 79c06062d5..2149de543f 100644 --- a/lib/mayaUsd/ufe/CMakeLists.txt +++ b/lib/mayaUsd/ufe/CMakeLists.txt @@ -52,6 +52,12 @@ if (UFE_LIGHTS_SUPPORT) PRIVATE UFE_LIGHTS_SUPPORT=1 ) + if (UFE_VOLUME_LIGHTS_SUPPORT) + target_compile_definitions(${PROJECT_NAME} + PRIVATE + UFE_VOLUME_LIGHTS_SUPPORT=1 + ) + endif() endif() if (UFE_MATERIALS_SUPPORT) diff --git a/lib/mayaUsd/ufe/UsdLight.cpp b/lib/mayaUsd/ufe/UsdLight.cpp index cb8863f175..a6eab751f3 100644 --- a/lib/mayaUsd/ufe/UsdLight.cpp +++ b/lib/mayaUsd/ufe/UsdLight.cpp @@ -20,8 +20,12 @@ #include +#include +#include #include +#include #include +#include #include #include #include @@ -70,7 +74,7 @@ class SetValueUndoableCommandImpl MAYAUSD_VERIFY_CLASS_SETUP(Ufe::Light, UsdLight); UsdLight::UsdLight(const UsdUfe::UsdSceneItem::Ptr& item) - : Light() + : UFE_LIGHT_BASE() , _item(item) { } @@ -94,14 +98,32 @@ Ufe::Light::Type UsdLight::type() const if (usdPrim.IsA()) { return Ufe::Light::Directional; - } else if (usdPrim.IsA()) { + } else if ( + usdPrim.IsA() +#ifdef UFE_VOLUME_LIGHTS_SUPPORT + || usdPrim.IsA()) { +#else + ) { +#endif return Ufe::Light::Area; } else if (usdPrim.IsA()) { const UsdLuxShapingAPI shapingAPI(usdPrim); return shapingAPI.GetShapingConeAngleAttr().IsValid() ? Ufe::Light::Spot +#ifdef UFE_VOLUME_LIGHTS_SUPPORT + : Ufe::Light::Sphere; +#else : Ufe::Light::Point; +#endif + +#ifdef UFE_VOLUME_LIGHTS_SUPPORT + } else if (usdPrim.IsA()) { + return Ufe::Light::Cylinder; + } else if (usdPrim.IsA()) { + return Ufe::Light::Disk; + } else if (usdPrim.IsA()) { + return Ufe::Light::Dome; +#endif } - // In case of unknown light type, fallback to point light return Ufe::Light::Point; } @@ -365,6 +387,9 @@ UsdSphereInterface::spherePropsCmd(float radius, bool asPoint) void UsdSphereInterface::sphereProps(float radius, bool asPoint) { + if (asPoint) { + radius = 0.0f; + } setLightSphereProps(_item->prim(), Ufe::Light::SphereProps { radius, asPoint }); } @@ -446,6 +471,104 @@ Ufe::Light::NormalizeUndoableCommand::Ptr UsdAreaInterface::normalizeCmd(bool nl return pCmd; } +#ifdef UFE_VOLUME_LIGHTS_SUPPORT + +UFE_LIGHT_BASE::VolumeProps getLightCylinderVolumeProps(const UsdPrim& prim) +{ + const UsdLuxCylinderLight lightSchema(prim); + const PXR_NS::UsdAttribute radiusAttribute = lightSchema.GetRadiusAttr(); + const PXR_NS::UsdAttribute lengthAttribute = lightSchema.GetLengthAttr(); + + UFE_LIGHT_BASE::VolumeProps vp; + radiusAttribute.Get(&vp.radius); + lengthAttribute.Get(&vp.length); + return vp; +} + +UFE_LIGHT_BASE::VolumeProps getLightDiskVolumeProps(const UsdPrim& prim) +{ + const UsdLuxDiskLight lightSchema(prim); + const PXR_NS::UsdAttribute radiusAttribute = lightSchema.GetRadiusAttr(); + + UFE_LIGHT_BASE::VolumeProps vp; + radiusAttribute.Get(&vp.radius); + return vp; +} + +UFE_LIGHT_BASE::VolumeProps getLightDomeVolumeProps(const UsdPrim& prim) +{ + UFE_LIGHT_BASE::VolumeProps vp; + return vp; +} + +void setLightVolumeProps(const UsdPrim& prim, const UFE_LIGHT_BASE::VolumeProps& attrVal) +{ + const UsdLuxSphereLight lightSchema(prim); + const PXR_NS::UsdAttribute lightAttribute = lightSchema.GetRadiusAttr(); + + lightAttribute.Set(attrVal.radius); +} + +void UsdCylinderInterface::volumeProps(float radius, float length) +{ + setLightVolumeProps(_item->prim(), UFE_LIGHT_BASE::VolumeProps { radius, length }); +} +void UsdDiskInterface::volumeProps(float radius) +{ + setLightVolumeProps(_item->prim(), UFE_LIGHT_BASE::VolumeProps { radius }); +} + +void UsdDomeInterface::volumeProps(float radius) +{ + setLightVolumeProps(_item->prim(), UFE_LIGHT_BASE::VolumeProps { radius }); +} + +// Cylinder Light +UFE_LIGHT_BASE::VolumeProps UsdCylinderInterface::volumeProps() const +{ + return getLightCylinderVolumeProps(_item->prim()); +} + +UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr +UsdCylinderInterface::volumePropsCmd(float radius, float length) +{ + auto pCmd = std::make_shared>( + _item->path(), setLightVolumeProps); + pCmd->set(UFE_LIGHT_BASE::VolumeProps { radius, length }); + return pCmd; +} + +// Disk Light +UFE_LIGHT_BASE::VolumeProps UsdDiskInterface::volumeProps() const +{ + return getLightDiskVolumeProps(_item->prim()); +} + +UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr UsdDiskInterface::volumePropsCmd(float radius) +{ + auto pCmd = std::make_shared>( + _item->path(), setLightVolumeProps); + + pCmd->set(UFE_LIGHT_BASE::VolumeProps { radius, 0 }); + return pCmd; +} + +// Dome light +UFE_LIGHT_BASE::VolumeProps UsdDomeInterface::volumeProps() const +{ + return getLightDomeVolumeProps(_item->prim()); +} + +UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr UsdDomeInterface::volumePropsCmd(float radius) +{ + auto pCmd = std::make_shared>( + _item->path(), setLightVolumeProps); + + pCmd->set(UFE_LIGHT_BASE::VolumeProps { radius, 0 }); + return pCmd; +} +#endif + void UsdAreaInterface::normalize(bool ln) { setLightNormalize(_item->prim(), ln); } bool UsdAreaInterface::normalize() const { return getLightNormalize(_item->prim()); } @@ -470,5 +593,22 @@ std::shared_ptr UsdLight::areaInterfaceImpl() return std::make_shared(_item); } +#ifdef UFE_VOLUME_LIGHTS_SUPPORT +std::shared_ptr UsdLight::cylinderInterfaceImpl() +{ + return std::make_shared(_item); +} + +std::shared_ptr UsdLight::diskInterfaceImpl() +{ + return std::make_shared(_item); +} + +std::shared_ptr UsdLight::domeInterfaceImpl() +{ + return std::make_shared(_item); +} +#endif + } // namespace ufe } // namespace MAYAUSD_NS_DEF diff --git a/lib/mayaUsd/ufe/UsdLight.h b/lib/mayaUsd/ufe/UsdLight.h index 618e720004..84272e843a 100644 --- a/lib/mayaUsd/ufe/UsdLight.h +++ b/lib/mayaUsd/ufe/UsdLight.h @@ -26,11 +26,17 @@ #include #include +#if defined(UFE_VOLUME_LIGHTS_SUPPORT) && (UFE_MAJOR_VERSION == 5) +#define UFE_LIGHT_BASE Ufe::Light_v5_5 +#else +#define UFE_LIGHT_BASE Ufe::Light +#endif + namespace MAYAUSD_NS_DEF { namespace ufe { //! \brief Interface to control lights through USD. -class MAYAUSD_CORE_PUBLIC UsdLight : public Ufe::Light +class MAYAUSD_CORE_PUBLIC UsdLight : public UFE_LIGHT_BASE { public: typedef std::shared_ptr Ptr; @@ -86,6 +92,11 @@ class MAYAUSD_CORE_PUBLIC UsdLight : public Ufe::Light std::shared_ptr sphereInterfaceImpl() override; std::shared_ptr coneInterfaceImpl() override; std::shared_ptr areaInterfaceImpl() override; +#ifdef UFE_VOLUME_LIGHTS_SUPPORT + std::shared_ptr cylinderInterfaceImpl() override; + std::shared_ptr diskInterfaceImpl() override; + std::shared_ptr domeInterfaceImpl() override; +#endif private: UsdUfe::UsdSceneItem::Ptr _item; @@ -156,6 +167,57 @@ class UsdAreaInterface : public Ufe::Light::AreaInterface UsdUfe::UsdSceneItem::Ptr _item; }; +#ifdef UFE_VOLUME_LIGHTS_SUPPORT +class UsdCylinderInterface : public UFE_LIGHT_BASE::CylinderInterface +{ +public: + UsdCylinderInterface(const UsdUfe::UsdSceneItem::Ptr& item) + : _item(item) + { + } + + UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr + volumePropsCmd(float radius, float length) override; + void volumeProps(float radius, float length) override; + UFE_LIGHT_BASE::VolumeProps volumeProps() const override; + +private: + UsdUfe::UsdSceneItem::Ptr _item; +}; + +class UsdDiskInterface : public UFE_LIGHT_BASE::DiskInterface +{ +public: + UsdDiskInterface(const UsdUfe::UsdSceneItem::Ptr& item) + : _item(item) + { + } + + UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr volumePropsCmd(float radius) override; + void volumeProps(float radius) override; + UFE_LIGHT_BASE::VolumeProps volumeProps() const override; + +private: + UsdUfe::UsdSceneItem::Ptr _item; +}; + +class UsdDomeInterface : public UFE_LIGHT_BASE::DomeInterface +{ +public: + UsdDomeInterface(const UsdUfe::UsdSceneItem::Ptr& item) + : _item(item) + { + } + + UFE_LIGHT_BASE::VolumePropsUndoableCommand::Ptr volumePropsCmd(float radius) override; + void volumeProps(float radius) override; + UFE_LIGHT_BASE::VolumeProps volumeProps() const override; + +private: + UsdUfe::UsdSceneItem::Ptr _item; +}; +#endif + } // namespace ufe } // namespace MAYAUSD_NS_DEF diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index ce61db54c7..bcbf7c94fd 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -168,6 +168,7 @@ foreach(script ${TEST_SCRIPT_FILES}) "UFE_MATERIAL_HAS_HASMATERIAL=${UFE_MATERIAL_HAS_HASMATERIAL}" "UFE_CAMERA_HAS_RENDERABLE=${UFE_CAMERA_HAS_RENDERABLE}" "UFE_SCENE_SEGMENT_HANDLER_ROOT_PATH=${UFE_SCENE_SEGMENT_HANDLER_ROOT_PATH}" + "UFE_VOLUME_LIGHTS_SUPPORT=${UFE_VOLUME_LIGHTS_SUPPORT}" ) # Add a ctest label to these tests for easy filtering. diff --git a/test/lib/ufe/testLight.py b/test/lib/ufe/testLight.py index d7be4bc5bc..ae5e736589 100644 --- a/test/lib/ufe/testLight.py +++ b/test/lib/ufe/testLight.py @@ -29,6 +29,7 @@ from maya.api import OpenMaya as om import ufe +import os from functools import partial import unittest @@ -96,7 +97,12 @@ def _TestSpotLight(self, ufeLight, usdLight): def _TestPointLight(self, ufeLight, usdLight): # Trust that the USD API works correctly, validate that UFE gives us # the same answers - self.assertEqual(ufeLight.type(), ufe.Light.Point) + if (os.getenv('UFE_VOLUME_LIGHTS_SUPPORT', 'FALSE') == 'TRUE'): + # With Ufe volume light support point light will be treated as a special kind of + # sphere light where the gizmo will be handled in Maya. + self.assertEqual(ufeLight.type(), ufe.Light.Sphere) + else: + self.assertEqual(ufeLight.type(), ufe.Light.Point) self._TestIntensity(ufeLight, usdLight) self._TestDiffuse(ufeLight, usdLight) self._TestSpecular(ufeLight, usdLight) @@ -136,8 +142,30 @@ def _TestAreaLight(self, ufeLight, usdLight): self._TestAreaProps(ufeLight, usdLight) self.assertEqual(None, ufeLight.coneInterface()) self.assertEqual(None, ufeLight.sphereInterface()) - self.assertEqual(None, ufeLight.directionalInterface()) + self.assertEqual(None, ufeLight.directionalInterface()) + + # Test VolumeLight support + def _TestCylinderLight(self, ufeLight, usdLight): + # Trust that the USD API works correctly, validate that UFE gives us + # the same answers + self.assertEqual(ufeLight.type(), ufe.Light.Cylinder) + self.assertEqual(None, ufeLight.diskInterface()) + self.assertEqual(None, ufeLight.domeInterface()) + + def _TestDiskLight(self, ufeLight, usdLight): + # Trust that the USD API works correctly, validate that UFE gives us + # the same answers + self.assertEqual(ufeLight.type(), ufe.Light.Disk) + self.assertEqual(None, ufeLight.cylinderInterface()) + self.assertEqual(None, ufeLight.domeInterface()) + def _TestDomeLight(self, ufeLight, usdLight): + # Trust that the USD API works correctly, validate that UFE gives us + # the same answers + self.assertEqual(ufeLight.type(), ufe.Light.Dome) + self.assertEqual(None, ufeLight.cylinderInterface()) + self.assertEqual(None, ufeLight.diskInterface()) + def _TestIntensity(self, ufeLight, usdLight): usdAttr = usdLight.GetAttribute('inputs:intensity') self.assertAlmostEqual(usdAttr.Get(), ufeLight.intensity()) @@ -251,6 +279,46 @@ def testUsdLight(self): usdAreaLight = usdUtils.getPrimFromSceneItem(arealightItem) self._TestAreaLight(ufeAreaLight, usdAreaLight) + @unittest.skipUnless(os.getenv('UFE_VOLUME_LIGHTS_SUPPORT', 'FALSE') == 'TRUE', 'UFE has volume light support.') + def testUsdVolumeLights(self): + self._StartTest('SimpleLight') + mayaPathSegment = mayaUtils.createUfePathSegment('|stage|stageShape') + # test cylinder light + cylinderlightUsdPathSegment = usdUtils.createUfePathSegment('/lights/cylinderLight') + cylinderlightPath = ufe.Path([mayaPathSegment, cylinderlightUsdPathSegment]) + cylinderlightItem = ufe.Hierarchy.createItem(cylinderlightPath) + + if (hasattr(ufe, "Light_v5_5")): + ufeCylinderLight = ufe.Light_v5_5.light(cylinderlightItem) + else: + ufeCylinderLight = ufe.light.light(cylinderlightItem) + usdCylinderLight = usdUtils.getPrimFromSceneItem(cylinderlightItem) + self._TestCylinderLight(ufeCylinderLight, usdCylinderLight) + + # test disk light + disklightUsdPathSegment = usdUtils.createUfePathSegment('/lights/diskLight') + disklightPath = ufe.Path([mayaPathSegment, disklightUsdPathSegment]) + disklightItem = ufe.Hierarchy.createItem(disklightPath) + + if (hasattr(ufe, "Light_v5_5")): + ufeDiskLight = ufe.Light_v5_5.light(disklightItem) + else: + ufeDiskLight = ufe.Light.light(disklightItem) + usdDiskLight = usdUtils.getPrimFromSceneItem(disklightItem) + self._TestDiskLight(ufeDiskLight, usdDiskLight) + + # test dome light + domelightUsdPathSegment = usdUtils.createUfePathSegment('/lights/domeLight') + domelightPath = ufe.Path([mayaPathSegment, domelightUsdPathSegment]) + domelightItem = ufe.Hierarchy.createItem(domelightPath) + + if (hasattr(ufe, "Light_v5_5")): + ufeDomeLight = ufe.Light_v5_5.light(domelightItem) + else: + ufeDomeLight = ufe.Light.light(domelightItem) + usdDomeLight = usdUtils.getPrimFromSceneItem(domelightItem) + self._TestDomeLight(ufeDomeLight, usdDomeLight) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/test/testSamples/light/SimpleLight.usda b/test/testSamples/light/SimpleLight.usda index 35460d6ea0..7ba33fc91c 100644 --- a/test/testSamples/light/SimpleLight.usda +++ b/test/testSamples/light/SimpleLight.usda @@ -8,45 +8,67 @@ def Xform "lights" { def SphereLight "spotLight" { + float inputs:intensity = 20000 + color3f inputs:shadow:color = (1, 0, 0) + bool inputs:shadow:enable = 1 + float inputs:shaping:cone:angle = 50 + float inputs:shaping:cone:softness = 0.1 + float inputs:shaping:focus = 0.5 double3 xformOp:translate = (0, 1, 0) uniform token[] xformOpOrder = ["xformOp:translate"] - - float inputs:intensity = 20000 - bool inputs:shadow:enable = true - color3f inputs:shadow:color = (1, 0, 0) - - float inputs:shaping:focus = 0.5 - float inputs:shaping:cone:angle = 50 - float inputs:shaping:cone:softness = 0.1 } - + def SphereLight "pointLight" { - float inputs:intensity = 200 - bool inputs:shadow:enable = false - color3f inputs:shadow:color = (1, 1, 0) + float inputs:intensity = 200 + float inputs:radius = 0 + color3f inputs:shadow:color = (1, 1, 0) + bool inputs:shadow:enable = 0 + bool treatAsPoint = 1 } - + def DistantLight "directionalLight" { - float inputs:angle = 0.2 - + float inputs:angle = 0.2 color3f inputs:color = (0, 0, 1) - - float inputs:intensity = 20000 - bool inputs:shadow:enable = true - color3f inputs:shadow:color = (1, 0, 0) + float inputs:intensity = 20000 + color3f inputs:shadow:color = (1, 0, 0) + bool inputs:shadow:enable = 1 } - + def RectLight "areaLight" { - bool inputs:normalize = true - color3f inputs:color = (0, 1, 0) - - float inputs:intensity = 20000 - bool inputs:shadow:enable = true - color3f inputs:shadow:color = (1, 0, 0) - } + float inputs:intensity = 20000 + bool inputs:normalize = 1 + color3f inputs:shadow:color = (1, 0, 0) + bool inputs:shadow:enable = 1 + } + + def CylinderLight "cylinderLight" + { + color3f inputs:shadow:color + bool inputs:shadow:enable = 1 + } + + def DiskLight "diskLight" + { + color3f inputs:shadow:color + bool inputs:shadow:enable = 1 + float3 xformOp:scale = (1, 0, 1) + uniform token[] xformOpOrder = ["xformOp:scale"] + } + + def DomeLight "domeLight" + { + color3f inputs:shadow:color + bool inputs:shadow:enable = 1 + } + + def PortalLight "portalLight" + { + color3f inputs:shadow:color + bool inputs:shadow:enable = 1 + } }