Light falloff vs. intensity vs. model units

can any rhino dev please explain the reasoning behind this behavior, as I have not found it documented anywhere

it seems pretty clear it is based on falloff vs. model scale, the discrepancy between intensity value seen in the UI and that which is actually found on the light

but I am just inferring that, and don’t know if there may be further unexpected behavior based on other factors

I believe that was to ensure that the intensity is moved into the 1m realm.

what I am requesting is for someone to look at the code and confirm that the relationship between these values is as I have inferred

since rhino hides the obj props > light panel when my renderer is current, I have to duplicate in my own light panel the color, intensity, and falloff controls, and I need to be able to provide the same behavior

Hmm, I don’t think Rhino should be hiding that, unless you explicitly tell Rhino to not show the panel? I’ll have to get @maxsoder in on this part of the discussion, but my understanding is that if you use Rhino lights the panel should still just show.

I’ll ask tomorrow more about the conversion. It is not happening where I thought it would, so I need to get a hold of @maxsoder or @andy for this too.

thanks, as far as I know it is not in my control whether the light panel is shown or not, and it would be great if it was shown

will still like to know the logic of that conversion, in case I can use it to improve my translation of light values

For this I logged: RH-80979 Light properties panel hidden when using 3rd party renderer

I tested with a C# and Cpp test render plugins that do not have their own implementation of a (PropertyPageType.Light) page and the obj properties .> light properties page is shown (rhinos own version).

I also tested Bella, but it has it´s own implementation so I get the Bella one showing. I wonder if something else is causing the problem. The test plugins that I have are quite simple. Especially the C# one.

this sounds the same as the behavior I see here – if I comment-out adding my light properties page in the PlugIn.ObjectPropertiesPages override, then rhino continues to show its own light properties page

and that is the problem: if you need to show custom light properties, then the user loses access to the base color/intensity/falloff properties in rhino’s page, so you need to include your own controls for those, and set the underlying rhino color/intensity/falloff values

you could hook those values only to your own internal lights, and it would work inside of your own display mode, but would be out of sync with rhino’s own rendered view, and other display modes

solutions that come to mind include

  1. don’t hide the native rhino light properties (similar to how the view properties are not hidden, just because a plugin needs to add its own ObjectPropertyPageType.View page)
  2. document the exact behavior of rhino’s light properties page so that we can re-implement those properties correctly
  3. let the plugin decide via SupportsFeature, as is done with RenderFeature.FocalBlur
  4. allow plugins to add properties to the native light properties page, as with the PlugIn.SunCustomSections override

the first seems best to me, since it is backward compatible with existing plugins without recompiling, and without having them reimplement intricate behavior that could easily change in the future

1 Like

I see what you mean. I will update the YT and have a talk with Andy.

1 Like

thanks! (that yt is a 404 here by the way)

Should be open now.
-wim

2 Likes

to not forget the original topic for anyone who would like to know, here is what I get so far, if you want to mimic the rhino light properties page behavior

if your user has changed a color/falloff/intensity control in your UI, you need to do something along these lines

if (input.name() == "color")
{
    foreach (var lo in GetSelectedObjects<LightObject>())
    {
        lo.LightGeometry.Diffuse = ((Color4f)value).ToSys();
        lo.CommitChanges();
    }
    _doc.Views.Redraw();
}
else if (input.name() == "falloff")
{
    foreach (var lo in GetSelectedObjects<LightObject>())
    {
        var scaleBefore = lo.LightGeometry.IntensityScale(_doc);
        lo.LightGeometry.SetFalloff(value as string);
        var scaleAfter = lo.LightGeometry.IntensityScale(_doc);
        var scaleFactor = scaleBefore / scaleAfter;
        lo.LightGeometry.Intensity *= scaleFactor;
        lo.CommitChanges();
    }
    _doc.Views.Redraw();
}
else if (input.name() == "intensity")
{
    foreach (var lo in GetSelectedObjects<LightObject>())
    {
        var scaleFactorInv = lo.LightGeometry.IntensityScaleInv(_doc);
        lo.LightGeometry.Intensity = (double)value * scaleFactorInv;
        lo.CommitChanges();
    }
    _doc.Views.Redraw();
}

then, when reading rhino light values to set your UI control values, you need to do something like this

foreach (var ui in _section.AttrUIs)
{
    if (ui.Key == "color")
    {
        (ui.Value.Ctrl as UI.ColorSlider).Value = lo.LightGeometry.Diffuse.ToRgba();
    }
    else if (ui.Key == "falloff")
    {
        switch (lo.LightGeometry.Falloff())
        {
            case Extensions.FalloffType.Constant: (ui.Value.Ctrl as UI.DropDown).SelectedIndex = 2; break;
            case Extensions.FalloffType.Linear:   (ui.Value.Ctrl as UI.DropDown).SelectedIndex = 1; break;
            default: /* InvSq or other */         (ui.Value.Ctrl as UI.DropDown).SelectedIndex = 0; break;
        }
    }
    else if (ui.Key == "intensity")
    {
        var scaleFactor = lo.LightGeometry.IntensityScale(doc);
        (ui.Value.Ctrl as UI.NumericSlider).Value = lo.LightGeometry.Intensity * scaleFactor;
    }
}

and this code uses these extension methods, to simplify the light falloff api, encapsulate the document model units involvement, and make it usable while targeting the rhino 6 sdk, which lacks the Constant/Linear/InverseSquaredAttenuationVector static properties that define the falloff modes

public enum FalloffType
{
    Constant,
    Linear,
    InvSquare
}

public static FalloffType Falloff(this Light light)
{
    if (light == null)
        return FalloffType.InvSquare;

    var constant  = new Vector3d(1.0, 0.0, 0.0);
    var linear    = new Vector3d(0.0, 1.0, 0.0);
    var invSquare = new Vector3d(0.0, 0.0, 1.0);

    if (light.AttenuationVector.Equals(constant))
        return FalloffType.Constant;
    else if (light.AttenuationVector.Equals(linear))
        return FalloffType.Linear;
    else if (light.AttenuationVector.Equals(invSquare))
        return FalloffType.InvSquare;

    return FalloffType.InvSquare;
}

public static void SetFalloff(this Light light, string mode)
{
    if (light == null)
        return;

    var constant  = new Vector3d(1.0, 0.0, 0.0);
    var linear    = new Vector3d(0.0, 1.0, 0.0);
    var invSquare = new Vector3d(0.0, 0.0, 1.0);

    if (mode == "constant")
        light.AttenuationVector = constant;
    else if (mode == "linear")
        light.AttenuationVector = linear;
    else
        light.AttenuationVector = invSquare;
}

public static bool NeedsIntensityScaling(this Light light)
{
    return light.Falloff() != FalloffType.Constant;
}

public static double IntensityScale(this Light light, RhinoDoc doc)
{
    var scale = 1.0;

    if (light.NeedsIntensityScaling())
        scale *= util.DocScale(doc);

    return scale;
}

public static double IntensityScaleInv(this Light light, RhinoDoc doc)
{
    var scale = 1.0;

    if (light.NeedsIntensityScaling())
        scale /= util.DocScale(doc);

    return scale;
}

(there are some other helpers involved but you can infer what they do)