#usda 1.0
(
    "This file describes the USD Lux light schemata for code generation."
    subLayers = [
        @usdGeom/schema.usda@
    ]
)

over "GLOBAL" (
    customData = {
        string libraryName      = "usdLux"
        string libraryPath      = "pxr/usd/usdLux"
        dictionary libraryTokens = {
            dictionary lightLink = {
                string doc = """
                This token represents the collection name to use
                with UsdCollectionAPI to represent light-linking
                of a UsdLuxLight prim.
                """
            }
            dictionary shadowLink = {
                string doc = """
                This token represents the collection name to use
                with UsdCollectionAPI to represent shadow-linking
                of a UsdLuxLight prim.
                """
            }
            dictionary filterLink = {
                string doc = """
                This token represents the collection name to use
                with UsdCollectionAPI to represent filter-linking
                of a UsdLuxLightFilter prim.
                """
            }
        }
    }
)
{
}

class "Light" (
    inherits = </Xformable>
    doc = """Base class for all lights.

    <b>Linking</b>

    Lights can be linked to geometry.  Linking controls which geometry
    a light illuminates, and which geometry casts shadows from the light.

    Linking is specified as collections (UsdCollectionAPI) which can
    be accessed via GetLightLinkCollection() and GetShadowLinkCollection().
    Note that these collections have their includeRoot set to true,
    so that lights will illuminate and cast shadows from all objects
    by default.  To illuminate only a specific set of objects, there
    are two options.  One option is to modify the collection paths
    to explicitly exclude everything else, assuming it is known;
    the other option is to set includeRoot to false and explicitly
    include the desired objects.  These are complementary approaches
    that may each be preferable depending on the scenario and how
    to best express the intent of the light setup.
    """
    customData = {
        string extraIncludes = """
#include "pxr/usd/usd/collectionAPI.h" """
    }
    prepend apiSchemas = ["CollectionAPI:lightLink", "CollectionAPI:shadowLink"]
) {
    uniform bool collection:lightLink:includeRoot = 1 (
        customData = {
            token apiName = ""
        }
    )
    uniform bool collection:shadowLink:includeRoot = 1 (
        customData = {
            token apiName = ""
        }
    )
    float intensity = 1 (
        doc = """Scales the power of the light linearly."""
    )
    float exposure = 0 (
        doc = """Scales the power of the light exponentially as a power
        of 2 (similar to an F-stop control over exposure).  The result
        is multiplied against the intensity."""
    )
    float diffuse = 1.0 (
        displayName = "Diffuse Multiplier"
        doc = """A multiplier for the effect of this light on the diffuse
        response of materials.  This is a non-physical control."""
    )
    float specular = 1.0 (
        displayName = "Specular Multiplier"
        doc = """A multiplier for the effect of this light on the specular
        response of materials.  This is a non-physical control."""
    )
    bool normalize = false (
        displayName = "Normalize Power"
        doc = """Normalizes power by the surface area of the light.
        This makes it easier to independently adjust the power and shape
        of the light, by causing the power to not vary with the area or
        angular size of the light."""
    )
    color3f color = (1, 1, 1) (
       doc = """The color of emitted light, in energy-linear terms."""
    )
    bool enableColorTemperature = false (
        displayName = "Enable Color Temperature"
        doc = """Enables using colorTemperature."""
    )
    float colorTemperature = 6500 (
        displayName = "Color Temperature"
        doc = """Color temperature, in degrees Kelvin, representing the
        white point.  The default is a common white point, D65.  Lower
        values are warmer and higher values are cooler.  The valid range
        is from 1000 to 10000. Only takes effect when
        enableColorTemperature is set to true.  When active, the
        computed result multiplies against the color attribute.
        See UsdLuxBlackbodyTemperatureAsRgb()."""
    )
    rel filters (
        doc = """Relationship to the light filters that apply to this light."""
    )
}

class "ListAPI" (
    inherits = </APISchemaBase>
    doc = """API schema to support discovery and publishing of lights in a scene.

    \\section UsdLuxListAPI_Discovery Discovering Lights via Traversal
    
    To motivate this API, consider what is required to discover all
    lights in a scene.  We must load all payloads and traverse all prims:

    \\code
    01  // Load everything on the stage so we can find all lights,
    02  // including those inside payloads
    03  stage->Load();
    04  
    05  // Traverse all prims, checking if they are of type UsdLuxLight
    06  // (Note: ignoring instancing and a few other things for simplicity)
    07  SdfPathVector lights;
    08  for (UsdPrim prim: stage->Traverse()) {
    09      if (prim.IsA<UsdLuxLight>()) {
    10          lights.push_back(i->GetPath());
    11      }
    12  }
    \\endcode

    This traversal -- suitably elaborated to handle certain details --
    is the first and simplest thing UsdLuxListAPI provides.
    UsdLuxListAPI::ComputeLightList() performs this traversal and returns
    all lights in the scene:

    \\code
    01  UsdLuxListAPI listAPI(stage->GetPseudoRoot());
    02  SdfPathVector lights = listAPI.ComputeLightList();
    \\endcode

    \\section UsdLuxListAPI_LightList Publishing a Cached Light List

    Consider a USD client that needs to quickly discover lights but
    wants to defer loading payloads and traversing the entire scene
    where possible, and is willing to do up-front computation and
    caching to achieve that.

    UsdLuxListAPI provides a way to cache the computed light list,
    by publishing the list of lights onto prims in the model
    hierarchy.  Consider a big set that contains lights:

    \\code
    01  def Xform "BigSetWithLights" (
    02      kind = "assembly"
    03      payload = @BigSetWithLights.usd@   // Heavy payload
    04  ) {
    05      // Pre-computed, cached list of lights inside payload
    06      rel lightList = [
    07          <./Lights/light_1>,
    08          <./Lights/light_2>,
    09          ...
    10      ]
    11      token lightList:cacheBehavior = "consumeAndContinue";
    12  }
    \\endcode

    The lightList relationship encodes a set of lights, and the
    lightList:cacheBehavior property provides fine-grained
    control over how to use that cache.  (See details below.)

    The cache can be created by first invoking
    ComputeLightList(ComputeModeIgnoreCache) to pre-compute the list
    and then storing the result with UsdLuxListAPI::StoreLightList().

    To enable efficient retrieval of the cache, it should be stored
    on a model hierarchy prim.  Furthermore, note that while you can
    use a UsdLuxListAPI bound to the pseudo-root prim to query the
    lights (as in the example above) because it will perform a
    traversal over descendants, you cannot store the cache back to the
    pseduo-root prim.

    To consult the cached list, we invoke
    ComputeLightList(ComputeModeConsultModelHierarchyCache):

    \\code
    01  // Find and load all lights, using lightList cache where available
    02  UsdLuxListAPI list(stage->GetPseudoRoot());
    03  SdfPathSet lights = list.ComputeLightList(
    04      UsdLuxListAPI::ComputeModeConsultModelHierarchyCache);
    05  stage.LoadAndUnload(lights, SdfPathSet());
    \\endcode

    In this mode, ComputeLightList() will traverse the model
    hierarchy, accumulating cached light lists.

    \\section UsdLuxListAPI_CacheBehavior Controlling Cache Behavior

    The lightList:cacheBehavior property gives additional fine-grained
    control over cache behavior:

    \\li The fallback value, "ignore", indicates that the lightList should
    be disregarded.  This provides a way to invalidate cache entries.
    Note that unless "ignore" is specified, a lightList with an empty
    list of targets is considered a cache indicating that no lights
    are present.

    \\li The value "consumeAndContinue" indicates that the cache should
    be consulted to contribute lights to the scene, and that recursion
    should continue down the model hierarchy in case additional lights
    are added as descedants. This is the default value established when
    StoreLightList() is invoked. This behavior allows the lights within
    a large model, such as the BigSetWithLights example above, to be
    published outside the payload, while also allowing referencing and
    layering to add additional lights over that set.

    \\li The value "consumeAndHalt" provides a way to terminate recursive
    traversal of the scene for light discovery. The cache will be
    consulted but no descendant prims will be examined.

    \\section UsdLuxListAPI_Instancing Instancing

    Where instances are present, UsdLuxListAPI::ComputeLightList() will
    return the instance-unique paths to any lights discovered within
    those instances.  Lights within a UsdGeomPointInstancer will
    not be returned, however, since they cannot be referred to
    solely via paths.
"""
) {
    rel lightList (
        doc = """Relationship to lights in the scene."""
    )
    token lightList:cacheBehavior (
        doc = """Controls how the lightList should be interpreted.
        Valid values are:
        - consumeAndHalt: The lightList should be consulted,
          and if it exists, treated as a final authoritative statement
          of any lights that exist at or below this prim, halting
          recursive discovery of lights.
        - consumeAndContinue: The lightList should be consulted,
          but recursive traversal over nameChildren should continue
          in case additional lights are added by descendants.
        - ignore: The lightList should be entirely ignored.  This
          provides a simple way to temporarily invalidate an existing
          cache.  This is the fallback behavior.
        """
        allowedTokens = ["consumeAndHalt", "consumeAndContinue", "ignore"]
    )
}

class "ShapingAPI" (
    inherits = </APISchemaBase>
    doc = """Controls for shaping a light's emission."""
) {
    float shaping:focus = 0 (
        displayGroup = "Shaping"
        doc = """A control to shape the spread of light.  Higher focus
        values pull light towards the center and narrow the spread.
        Implemented as an off-axis cosine power exponent.
        TODO: clarify semantics"""
    )
    color3f shaping:focusTint = (0, 0, 0) (
        displayGroup = "Shaping"
        doc = """Off-axis color tint.  This tints the emission in the
        falloff region.  The default tint is black.
        TODO: clarify semantics"""
    )
    float shaping:cone:angle = 90 (
        displayGroup = "Shaping"
        doc = """Angular limit off the primary axis to restrict the
        light spread."""
    )
    float shaping:cone:softness = 0 (
        displayGroup = "Shaping"
        doc = """Controls the cutoff softness for cone angle.
        TODO: clarify semantics"""
    )
    asset shaping:ies:file (
        displayGroup = "Shaping"
        doc = """An IES (Illumination Engineering Society) light
        profile describing the angular distribution of light."""
    )
    float shaping:ies:angleScale = 0 (
        displayGroup = "Shaping"
        doc = """Rescales the angular distribution of the IES profile.
        TODO: clarify semantics"""
    )
    bool shaping:ies:normalize = false (
        displayGroup = "Shaping"
        doc = """Normalizes the IES profile so that it affects the shaping
        of the light while preserving the overall energy output."""
    )
}

class "ShadowAPI" (
    inherits = </APISchemaBase>
    doc = """Controls to refine a light's shadow behavior.  These are
    non-physical controls that are valuable for visual lighting work."""
) {
    bool shadow:enable = true (
        displayGroup = "Shadows"
        doc = """Enables shadows to be cast by this light."""
    )
    rel shadow:include (
        displayGroup = "Shadows"
        displayName = "Shadow Include Subset"
        doc = "Set of geometry to consider for the purpose of casting shadows from a light.  If this is not specified, all geometry is used for shadowing."
    )
    rel shadow:exclude (
        displayGroup = "Shadows"
        displayName = "Shadow Exclude Subset"
        doc = "Set of geometry to ignore for the purpose of casting shadows from a light.  If this is not specified, all geometry is used for shadowing."
    )
    color3f shadow:color = (0, 0, 0) (
        displayGroup = "Shadows"
        doc = """The color of shadows cast by the light.  This is a
        non-physical control.  The default is to cast black shadows."""
    )
    float shadow:distance = -1.0 (
        displayGroup = "Shadows"
        doc = """The maximum distance shadows are cast.
        The default value (-1) indicates no limit.
        """
    )
    float shadow:falloff = -1.0 (
        displayGroup = "Shadows"
        doc = """The near distance at which shadow falloff begins.
        The default value (-1) indicates no falloff.
        """
    )
    float shadow:falloffGamma = 1.0 (
        displayGroup = "Shadows"
        doc = """A gamma (i.e., exponential) control over shadow strength
        with linear distance within the falloff zone.
        This requires the use of shadowDistance and shadowFalloff."""
    )
}

class LightFilter "LightFilter" (
    inherits = </Xformable>
    doc = """A light filter modifies the effect of a light.
    Lights refer to filters via relationships so that filters may be
    shared.

    <b>Linking</b>

    Filters can be linked to geometry.  Linking controls which geometry
    a light-filter affects, when considering the light filters attached
    to a light illuminating the geometry.

    Linking is specified as a collection (UsdCollectionAPI) which can
    be accessed via GetFilterLinkCollection().
    Note however that there are extra semantics in how UsdLuxLightFilter
    uses its collection: if a collection is empty, the filter is treated
    as linked to <i>all</i> geometry for the respective purpose.
    UsdCollectionAPI and UsdCollectionAPI::MembershipQuery are unaware
    of this filter-specific interpretation.
    """
    customData = {
        string extraIncludes = """
#include "pxr/usd/usd/collectionAPI.h" """
    }
    prepend apiSchemas = ["CollectionAPI:filterLink"]
) {
    uniform bool collection:filterLink:includeRoot = 1 (
        customData = {
            token apiName = ""
        }
    )
}

class DistantLight "DistantLight" (
    inherits = </Light>
    doc = """Light emitted from a distant source along the -Z axis.
    Also known as a directional light."""
) {
    float angle = 0.53 (
        doc = """Angular size of the light in degrees.
        As an example, the Sun is approximately 0.53 degrees as seen from Earth.
        Higher values broaden the light and therefore soften shadow edges.
        """
    )
    float intensity = 50000 (
        doc = """Scales the emission of the light linearly.
        The DistantLight has a high default intensity to approximate the Sun."""
    )
}

class DiskLight "DiskLight" (
    inherits = </Light>
    doc = """Light emitted from one side of a circular disk.
    The disk is centered in the XY plane and emits light along the -Z axis."""
) {
    float radius = 0.5 (
        doc = "Radius of the disk."
    )
}

class RectLight "RectLight" (
    inherits = </Light>
    doc = """Light emitted from one side of a rectangle.
    The rectangle is centered in the XY plane and emits light along the -Z axis.
    The rectangle is 1 unit in length in the X and Y axis.  In the default 
    position, a texture file's min coordinates should be at (+X, +Y) and 
    max coordinates at (-X, -Y)."""
) {
    float width = 1 (
        doc = "Width of the rectangle, in the local X axis."
    )
    float height = 1 (
        doc = "Height of the rectangle, in the local Y axis."
    )
    asset texture:file (
        doc = """A color texture to use on the rectangle."""
    )
}

class SphereLight "SphereLight" (
    inherits = </Light>
    doc = """Light emitted outward from a sphere."""
) {
    float radius = 0.5 (
        doc = "Radius of the sphere."
    )
    bool treatAsPoint = false (
        doc = """A hint that this light can be treated as a 'point'
        light (effectively, a zero-radius sphere) by renderers that
        benefit from non-area lighting. Renderers that only support
        area lights can disregard this."""
    )
}

class CylinderLight "CylinderLight" (
    inherits = </Light>
    doc = """Light emitted outward from a cylinder.
    The cylinder is centered at the origin and has its major axis on the X axis.
    The cylinder does not emit light from the flat end-caps.
    """
) {
    float length = 1 (
        doc = "Width of the rectangle, in the local X axis."
    )
    float radius = 0.5 (
        doc = "Radius of the cylinder."
    )
    bool treatAsLine = false (
        doc = """A hint that this light can be treated as a 'line'
        light (effectively, a zero-radius cylinder) by renderers that
        benefit from non-area lighting. Renderers that only support
        area lights can disregard this."""
    )
}

class GeometryLight "GeometryLight" (
    inherits = </Light>
    doc = """Light emitted outward from a geometric prim (UsdGeomGprim),
    which is typically a mesh."""
) {
    rel geometry (
        doc = """Relationship to the geometry to use as the light source."""
    )
}

class DomeLight "DomeLight" (
    inherits = </Light>
    doc = """Light emitted inward from a distant external environment,
    such as a sky or IBL light probe.  The orientation of a dome light with a
    latlong texture is expected to match the OpenEXR specification for latlong 
    environment maps.  From the OpenEXR documentation:
    
    -------------------------------------------------------------------------
    Latitude-Longitude Map:
    
    The environment is projected onto the image using polar coordinates
    (latitude and longitude).  A pixel's x coordinate corresponds to
    its longitude, and the y coordinate corresponds to its latitude.
    Pixel (dataWindow.min.x, dataWindow.min.y) has latitude +pi/2 and
    longitude +pi; pixel (dataWindow.max.x, dataWindow.max.y) has
    latitude -pi/2 and longitude -pi.
    
    In 3D space, latitudes -pi/2 and +pi/2 correspond to the negative and
    positive y direction.  Latitude 0, longitude 0 points into positive
    z direction; and latitude 0, longitude pi/2 points into positive x
    direction.
    
    The size of the data window should be 2*N by N pixels (width by height),
    where N can be any integer greater than 0.
    -------------------------------------------------------------------------
"""
) {
    asset texture:file (
        doc = """A color texture to use on the dome, such as an HDR (high
        dynamic range) texture intended for IBL (image based lighting)."""
    )
    token texture:format = "automatic" (
        allowedTokens = ["automatic", "latlong", "mirroredBall", "angular", "cubeMapVerticalCross"]
        doc = """Specifies the parameterization of the color map file.
        Valid values are:
        - automatic: Tries to determine the layout from the file itself.
          For example, Renderman texture files embed an explicit
          parameterization.
        - latlong: Latitude as X, longitude as Y.
        - mirroredBall: An image of the environment reflected in a
          sphere, using an implicitly orthogonal projection.
        - angular: Similar to mirroredBall but the radial dimension
          is mapped linearly to the angle, providing better sampling
          at the edges.
        - cubeMapVerticalCross: A cube map with faces laid out as a
          vertical cross.
        """
    )
    rel portals (
        doc = """Optional portals to guide light sampling."""
    )
}

class LightPortal "LightPortal" (
    inherits = </Xformable>
    doc = """A rectangular portal in the local XY plane that guides sampling
    of a dome light.  Transmits light in the -Z direction.
    The rectangle is 1 unit in length."""
) {
}
