Author Topic: Feature request: inverse sliced sprite rendering  (Read 11553 times)

bac9

  • Full Member
  • ***
  • Thank You
  • -Given: 2
  • -Receive: 4
  • Posts: 113
    • View Profile
Feature request: inverse sliced sprite rendering
« on: July 15, 2014, 09:51:57 AM »
A few days ago I had to implement Android L-style UI elements of arbitrary size which can change their depth and have to cast appropriately scaled/blurred shadow as that depth slides around during animations. This turned out to be a bigger problem than I have expected, because while the Sliced sprite type allows you to make a shadow that perfectly fits to a rectangle of an arbitrary size (using anchoring), it does not allow you to scale the border quads of the shadow sprite while anchoring the center quad to a parent object.

The problem stems from the fact that Sliced sprites, of course, get their full scale from their height/width properties, and calculate the size of the border quads by subtracting offsets from the resulting area. So, if you want to create a shadow that has a gradient covering 16 pixels around a rectangle, you have to create a sliced sprite with 16px gradients and set up it's border offsets to 16px, then using anchoring with the 16px offset. Now, if you want the very same rectangle to cast a 32px shadow, you can't reuse the same shadow sprite.

I think this issue can be fairly easily fixed, opening a way to a very rich variety of UI elements ranging from shadows with dynamically adjustable radius to reusable frames, animated pixel-perfect edge highlights and so on. Here is how the proposed additional sprite rendering mode can work.



Essentially:

  • Use UISprite properties to set up the dimensions of the center quad instead of the total sprite dimensions
  • Use the pixel size of the borders the sprite has listed in the atlas properties only to set up UV mapping of the 9-quad sprite, and not for the actual size of the border quads
  • Set up the size of the 8 border quads using an additional property "border size or something", be it one int for a uniform offset, or a Vector4 for anchor-like flexible offsets)

How feasible is it to add something like this to NGUI?

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: Feature request: inverse sliced sprite rendering
« Reply #1 on: July 15, 2014, 09:51:05 PM »
Quote
Now, if you want the very same rectangle to cast a 32px shadow, you can't reuse the same shadow sprite.
You can if you use a UITexture or a UI2DSprite. There you specify the slicing information on the widget itself rather than having it be pulled from the atlas. You can also always scale the sprite after the fact. However if you anchor it, then yes you may have issues. To allow the border to be scaled by an arbitrary value you would need to create a custom widget type. Remember, you can always derive from UISprite, UIWidget, UITexture, or any other NGUI widget component and implement your own OnFill logic as you see fit.

bac9

  • Full Member
  • ***
  • Thank You
  • -Given: 2
  • -Receive: 4
  • Posts: 113
    • View Profile
Re: Feature request: inverse sliced sprite rendering
« Reply #2 on: July 17, 2014, 05:08:34 AM »
Would recently added onPostFill delegate work for that sort of modification?
Looks like it's exposing quite a bit of data, probably enough to implement that.

Quote
Buffer offset: 0
Vertex count: 36
V0: 0 / -221 / 0
V1: 0 / -213 / 0
V2: 8 / -213 / 0
V3: 8 / -221 / 0
V4: 0 / -213 / 0
V5: 0 / -8 / 0
V6: 8 / -8 / 0
V7: 8 / -213 / 0
V8: 0 / -8 / 0
V9: 0 / 0 / 0
V10: 8 / 0 / 0
V11: 8 / -8 / 0
V12: 8 / -221 / 0
V13: 8 / -213 / 0
V14: 1272 / -213 / 0
V15: 1272 / -221 / 0
V16: 8 / -213 / 0
V17: 8 / -8 / 0
V18: 1272 / -8 / 0
V19: 1272 / -213 / 0
V20: 8 / -8 / 0
V21: 8 / 0 / 0
V22: 1272 / 0 / 0
V23: 1272 / -8 / 0
V24: 1272 / -221 / 0
V25: 1272 / -213 / 0
V26: 1280 / -213 / 0
V27: 1280 / -221 / 0
V28: 1272 / -213 / 0
V29: 1272 / -8 / 0
V30: 1280 / -8 / 0
V31: 1280 / -213 / 0
V32: 1272 / -8 / 0
V33: 1272 / 0 / 0
V34: 1280 / 0 / 0
V35: 1280 / -8 / 0


UVs size: 36
UV0: 0.65625 / 0.5439453
UV1: 0.65625 / 0.5517578
UV2: 0.6640625 / 0.5517578
UV3: 0.6640625 / 0.5439453
UV4: 0.65625 / 0.5517578
UV5: 0.65625 / 0.5986328
UV6: 0.6640625 / 0.5986328
UV7: 0.6640625 / 0.5517578
UV8: 0.65625 / 0.5986328
UV9: 0.65625 / 0.6064453
UV10: 0.6640625 / 0.6064453
UV11: 0.6640625 / 0.5986328
UV12: 0.6640625 / 0.5439453
UV13: 0.6640625 / 0.5517578
UV14: 0.7109375 / 0.5517578
UV15: 0.7109375 / 0.5439453
UV16: 0.6640625 / 0.5517578
UV17: 0.6640625 / 0.5986328
UV18: 0.7109375 / 0.5986328
UV19: 0.7109375 / 0.5517578
UV20: 0.6640625 / 0.5986328
UV21: 0.6640625 / 0.6064453
UV22: 0.7109375 / 0.6064453
UV23: 0.7109375 / 0.5986328
UV24: 0.7109375 / 0.5439453
UV25: 0.7109375 / 0.5517578
UV26: 0.71875 / 0.5517578
UV27: 0.71875 / 0.5439453
UV28: 0.7109375 / 0.5517578
UV29: 0.7109375 / 0.5986328
UV30: 0.71875 / 0.5986328
UV31: 0.71875 / 0.5517578
UV32: 0.7109375 / 0.5986328
UV33: 0.7109375 / 0.6064453
UV34: 0.71875 / 0.6064453
UV35: 0.71875 / 0.5986328
« Last Edit: July 17, 2014, 05:49:54 AM by bac9 »

bac9

  • Full Member
  • ***
  • Thank You
  • -Given: 2
  • -Receive: 4
  • Posts: 113
    • View Profile
Re: Feature request: inverse sliced sprite rendering
« Reply #3 on: July 17, 2014, 10:04:41 AM »


    The format took a bit of time to figure out, but it works now.
    Few questions I'm still puzzled with, though:

    • Is it safe to directly modify the vertex positions in a mesh? I get issues with distorted mesh persisting even after a component controlling inverse slicing was removed and affected sprite was marked as changed many times, leading me to believe that geometry of the sprites is not rebuilt too often. Is there a way for me to force the geometry rebuilding, so that I will have an option to instantly restore the original state of a mesh once inverse slicing component becomes unnecessary?
    • Is there a way to get a sprite to update it's geometry in the editor so that offset editing can be previewed without using play mode? I tried a few methods provided by NGUI classes, including MarkAsChanged, but nothing works, unfortunately.
    • Are there any potential issues that can arise from a sprite going outside of bounds NGUI expects it to be? Haven't noticed any so far though, and in theory only stuff like minor issues with UIPanel clipping comes to mind.
    [/list]

    Nicki

    • Global Moderator
    • Hero Member
    • *****
    • Thank You
    • -Given: 33
    • -Receive: 141
    • Posts: 1,768
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #4 on: July 17, 2014, 12:52:15 PM »
    Seems to me like you should just make your own UIInverseSlicedSprite class extending from UISprite and take a good look at how the sliced sprite works inside UISprite.

    ArenMook

    • Administrator
    • Hero Member
    • *****
    • Thank You
    • -Given: 337
    • -Receive: 1171
    • Posts: 22,128
    • Toronto, Canada
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #5 on: July 18, 2014, 04:49:58 AM »
    Quote
    Is it safe to directly modify the vertex positions in a mesh? I get issues with distorted mesh persisting even after a component controlling inverse slicing was removed and affected sprite was marked as changed many times, leading me to believe that geometry of the sprites is not rebuilt too often. Is there a way for me to force the geometry rebuilding, so that I will have an option to instantly restore the original state of a mesh once inverse slicing component becomes unnecessary?
    Unless you actually clear the delegate, its reference to your function will remain, and your function will still be called, whether you have your component attached or not.
    Quote
    Is there a way to get a sprite to update it's geometry in the editor so that offset editing can be previewed without using play mode? I tried a few methods provided by NGUI classes, including MarkAsChanged, but nothing works, unfortunately.
    Yes, by creating a custom widget class as Nicki pointed out. Delegate method is for run time.
    Quote
    Are there any potential issues that can arise from a sprite going outside of bounds NGUI expects it to be? Haven't noticed any so far though, and in theory only stuff like minor issues with UIPanel clipping comes to mind.
    If the sprite is outside its bounds, then it won't be culled correctly. You may see it disappear abruptly as you move the scroll view, for example.

    bac9

    • Full Member
    • ***
    • Thank You
    • -Given: 2
    • -Receive: 4
    • Posts: 113
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #6 on: July 21, 2014, 06:27:01 AM »
    I looked up the creation of custom widget classes on the net and stumbled on the following thread:
    http://www.tasharen.com/forum/index.php?topic=3640.0

    You can create a custom widget if you want -- just derive from UISprite or UIWidget, and override the OnFill function.

    I have just tried to do precisely that, and unfortunately encountered the following error:

    Quote
    Assets/SupportScripts/UI/UISpriteInverse.cs(42,30): error CS0115: `UISpriteInverse.OnFill(UIWidget, int, BetterList<UnityEngine.Vector3>, BetterList<UnityEngine.Vector2>, BetterList<UnityEngine.Color32>)' is marked as an override but no suitable method found to override

    As far as I understand, the issue pops up because UISprite overrides the OnFill itself, instead of having it as a virtual method. I admit I'm not very familiar with how override methods in C# are supposed to work when the method of a parent class is actually an override of another method of yet another parent class (UIWidget, in this case) instead of a virtual method, but I guess that's the cause of the error. After all, you can't make a method both virtual and override, which makes it not possible to override the OnFill.

    Is that a relatively recent change in NGUI that was made after the answer I have quoted above? Or maybe I misunderstand the quote and should declare the OnFill method in my inheriting class as "new" instead of "override"?


    _____________________________________________

    Edit: Scratch that, looks like I have overlooked the arguments in my override method and left an UIWidget reference among them, which was not there in actual OnFill method in UISprite and was only used by the delegate method. Intellisense won't highlight that, but that breaks the override. Fixed the arguments to match the UISprite OnFill and errors disappeared - looks like override chains are allowed in C# after all.

    Now, another question is - how can I add a new property drawer to the UISpriteInverse inspector in a similar way you handle Widget and Anchor drawers? I have looked at the custom editor code you use and I'm not sure I'm familiar with the way you do it, me being only experienced with traditional one class/one custom inspector approach which your code doesn't look similar to (you don't even use OnInspectorGUI). Is there some straightforward way of doing it, similar to, let's say, DrawDefaultInspector, that will allow me to add new EditorGUILayout elements to the sprite inspector without breaking or hiding your complex editors?

    Not sure if I worded it clearly, but in essense my question is: how can I add new custom inspector elements to the custom inspector of a class that inherits from a class that already has a custom inspector?

    _____________________________________________

    Edit 2: Solved the custom inspector problem by duplicating all contents of UISpriteInspector class into new custom editor and adding OnInspectorGUI into it like this:

    Quote
       public override void OnInspectorGUI ()
       {
          base.OnInspectorGUI ();
          UISpriteInverse s = target as UISpriteInverse;
          UISpriteInverse.OffsetStyle style = (UISpriteInverse.OffsetStyle)EditorGUILayout.EnumPopup ("Mode", s.style);
          float offsetLeft = EditorGUILayout.Slider ("L", s.offsetLeft, 0f, 128f);
          float offsetRight = EditorGUILayout.Slider ("R", s.offsetRight, 0f, 128f);
          float offsetTop = EditorGUILayout.Slider ("T", s.offsetTop, 0f, 128f);
          float offsetBottom = EditorGUILayout.Slider ("B", s.offsetBottom, 0f, 128f);
          if (GUI.changed)
          {
             NGUIEditorTools.RegisterUndo ("Sliced sprite changed", s.gameObject);
             s.style = style;
             s.offsetLeft = offsetLeft;
             s.offsetRight = offsetRight;
             s.offsetTop = offsetTop;
             s.offsetBottom = offsetBottom;
             s.MarkAsChanged ();
             NGUITools.SetDirty (target);
          }
       }

    Not sure if it's the cleanest way and is sure doesn't look as fancy as the property drawers you have created for widget and anchor variables, but it works.



    And now, here's the new sprite type in action.

    « Last Edit: July 21, 2014, 07:26:50 AM by bac9 »

    r.pedra

    • Full Member
    • ***
    • Thank You
    • -Given: 7
    • -Receive: 20
    • Posts: 131
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #7 on: July 21, 2014, 07:50:49 AM »
    IMHO it's looking weird because of the refresh button on the right. Its shadow doesn't change when you change the "depth" of your rectangle.

    bac9

    • Full Member
    • ***
    • Thank You
    • -Given: 2
    • -Receive: 4
    • Posts: 113
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #8 on: July 21, 2014, 08:06:06 AM »
    IMHO it's looking weird because of the refresh button on the right. Its shadow doesn't change when you change the "depth" of your rectangle.

    The depth change like that won't be used on that particular element of the UI, I'm just using it as an example.



    The titular sliced sprites will mostly be used for other things, like lists on hover and click, to attract focus through depth.

    ArenMook

    • Administrator
    • Hero Member
    • *****
    • Thank You
    • -Given: 337
    • -Receive: 1171
    • Posts: 22,128
    • Toronto, Canada
      • View Profile
    Re: Feature request: inverse sliced sprite rendering
    « Reply #9 on: July 21, 2014, 05:27:37 PM »
    Looks good!