Search Unity

Feedback Splines Feedback

Discussion in 'World Building' started by Rowlan, Oct 30, 2021.

  1. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I'm assuming that storing it three ways is because the conversion is expensive? In any case, from a non-programer perspective storing in any format that massively changes the position of that data when knots are changed feels broken. And while there are valid reasons to want the data in any of these formats, it seems like it should only operate to the user as data which does not exhibit bad behavior. For my system, I feed it to my shaders in knot space because I can use the T value from a bezier interpolation to quickly lookup the associated data - but then this creates an issue for users if I store it that way. So I suppose what I'll have to do is store it in distance, then convert it into knot space for my shaders. I guess the valid use case for knot space is if people want to associate data directly with the knots, and not allow users to move it off-knot, etc. Hmm...

    Which is implemented in exactly none of the examples. Now, one can argue that examples don't have to be production ready, but I think when coming from Unity they should at least be a good guide into how to make something production ready, because it's a sure fire way for you to see all the issues that come up, and the amount of code which would need to be written to make things compatible with your system, as well as test that system. And likely you'd come up with an abstraction which handles this all automatically (or with a lot less code) after writing it several times. And since this code will be copied by many people, things that code does not handle will propagate to every system made with it.

    I think being able to reference a spline in serialization would be a big help. Having to do all this by index and keep it all in sync via callbacks is a fragile affair, and easy to get wrong. Further, tools will be written that do things to spline data outside of either of our code bases, which can easily get that data out of sync in new and interesting ways. So the more direct the binding of the data, the less chance of that happening. Right now, learning this system and handling all its edge cases is about as complex as grabbing an existing open source spline system and modifying it to your needs.

    For instance, the callbacks you mention don't seem to be on Spline Container or the spline class - but assuming I just can't find them, a spline is added or removed from the container - in theory I can reorder the spline data to match but what about when it's reordered in the list? Does that get called as a remove and then an add? Do I have to keep the old data around in case an add comes in which matches it?


    > 3. You have to completely rewrite the spline editing classes to work with multiple splines, as the ones included in the package don't work.

    If something is not working, please let us know with a bug report![/QUOTE]

    Bug reports:

    IN-20272
    IN-20273
    IN-20275

    For the editor, I modified your editor as described a few posts up (to call the editor functions on all the splines instead of just the first one), but only the last spline works. So to fix it I had to pull out all those editor functions and rewrite them (but I've deleted that at this point since there were so many other issues trying to get multi-spline to work).
     
    NotaNaN likes this.
  2. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Oh, and BTW, this is just my use case of wanted to have spline width, which is now my own class since yours is in samples and doesn't support any of this stuff. Which means any other asset which wants to use width will not recognize my version, and have to add its own concept of width to the spline, such that there are now 2 different width controls.
     
  3. gabrielw_unity

    gabrielw_unity

    Unity Technologies

    Joined:
    Feb 19, 2018
    Posts:
    963
    Apparently this caused some confusion, I'll try to clarify that. Splines isn't abandoned, our team isn't pushed out - we're simply finishing (as of 2.1, still to come) the initial set of features we planned out via UX research. Greyboxing tools are the new feature focus, while Splines matures.

    If you missed the opportunity to connect with the research, genuinely sorry to hear - ironically, it's always a struggle for us to get enough participants early on. Would love to get more early input. I'd say ... try to watch for official posts requesting feedback here on the forum. Or, ping me directly if you see a WIP feature that is really important to you!
     
    frarf, KarimZoPr0, karl_jones and 2 others like this.
  4. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,797
    No confusion. The feature is "supported".

    The same way, say, probuilder is supported.

    Remind me, can you delete an edge in probuilder yet?

    In any case, I'll go back to supporting my forum presence, so don't bother replying (or a mod can delete this or whatever).
     
  5. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Yeah, reordering splines will simply break the association, because it doesn't call add or remove events, it just reorders them so the arrays no longer match up.
     
  6. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Assuming the issues with associating spline data and multi splining get addressed, I'm going to lay out the likely scenario which I think will form with asset store authors using the spline tools (which, IMO, I think is something everyone would benefit from if done right).

    MicroVerse uses Unity splines to offer non-destructive path and area editing to terrains. These can be used for more than paths, you can add the loft mesh component from the examples to easily create a river or road for instance. You can use the SplineInstantiate component to instantiate objects along the side of the path, like fences, etc.

    Rivers need width controls though. Since the width system is in examples, I can either:

    A) Require the user to install the samples and use its width system, which is odd.
    B) Copy the code into my own project, or create my own width system.

    If we follow choice A, then other assets who choose choice A can work with my asset! Yay! But requiring samples to be installed for my asset to compile seems very, very wrong. Plus this installs into the asset folder under a samples directory that screams delete me.

    If we follow choice B, then my asset no longer works with, say, Loft Road, or any other asset which has the concept of width, because that component is either looking for the width system in samples, or for its own custom width implementation. For instance, an AI asset that drives cars within the road area, another river system, etc. So now everyone will be duplicating this code and integrating a variant of it into their asset, so you'll have 4 Loft Road components to choose from which are all slightly different, and potentially getting out of sync with any fixes Unity makes.

    So what you'll end up with is a bunch of partially compatible splines in the scene, with common data that could be shared, but won't because the concept of width is not part of the spline system, but rather custom data. And a bunch of duplicated and modified code, which may or may not be updated. And the solution to this will be similar to @Rowlan 's integration of RAM into MicroVerse - a component that simply slaves the data of one spline system to another, keeping them in sync, or doing all that manually.

    Neither of these choices seem great to me, and I'd argue that while width is a common enough property to exist as part of the main package - I also totally get why you wouldn't want it to be, and would want a system for truly custom data to exist along a spline. Moving width to the main package kicks the can down the road a bit, because it's such a common thing, but there's no reason there wouldn't be other properties that multiple packages find in common (friction for car games, etc).

    So spitballing, I could imagine a system where there can be commonly named/type'd custom data added to splines, which is less general than the current system, but much easier to work with and be shared between projects. For instance, if you could declare a SplineData under a key name and type, something like:

    Code (CSharp):
    1.  
    2.  
    3. // add a new tool to the spline editing options under the key name Width which stores a single float value
    4. // use horizontal handles to adjust the width value
    5. // store that data as normalized 0-1 values
    6. // If this tool already exists, this will be ignored.
    7. SplineUtility.AddSplineDataTool<float>("Width", SplineEditorControl.HorizontalHandles, PathIndexUnit.Normalized);
    Then to get that data:

    Code (CSharp):
    1.  
    2. // note that type and name must match. If someone creates a float2 width, they're creating a different control.
    3. var data = SplineContainer.Spline.GetSplineData<float>("Width");
    4.  
    Then all that's needed to have width be a common thing is to declare the same tool name/type and grab the same data from the spline when reading it. This could just use a fixed up version of the current system to implement this with a few control options and types (types : color, float, vectors controls: horizontal, vertical, input field).

    Now there's a lot I don't love about this, in that it feels kinda undefined, much like having a vector4 on particles for custom data, or some set number of unnamed vertex attributes (texcoord0, texcoord1, etc). But it's practical, and makes adding custom one off data to a spline very easy, while also making it so common conventions for common data types are easy to share between different tools. And it is more general than simply having a Vector4 for user data on splines, or something like that.

    Anyway, I'm certainly not married to this idea - but I think thinking a bit about how Splines, and the common data which tools will require of them, can be easy shared, has a lot of benefits.
     
    Last edited: Oct 19, 2022
    NotaNaN likes this.
  7. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    Performance was not a consideration, as the format is only specifying how a DataPoint.Index is interpreted at the SplineData level. Functionally all three are roughly equivalent in terms of efficiency. The decision to present these options was for flexibility reasons.

    The PathIndexUnit.Knot format is actually not just a knot index, but rather knot index with an interpolation to the next knot stored in the fractional part.

    That's true, we did not put a strong focus on samples for tool developers. The primary focus of our recent work has been usability of Spline tools for users, not the editor API. There is absolutely room for improvement in the editor API, and I expect we will expand this support as we gather feedback.

    Agreed. It's something we have spent a lot of time thinking about as well. It's part of the reason that our editor API is so limited at the moment - we don't have a simple answer for external tool developers yet. What we do internally is fragile and difficult to implement correctly. It's not the API we want to support for our users.

    Yes, these callbacks are invoked from the SplineReorderableListUtility. They may be new as of 2.0 or 2.1, I don't remember off the top of my head.

    The Samples were overhauled for multiple paths somewhat late in the 2.0 previews. Looking at them now I don't see anything obvious not working with multiple Splines, but I may be missing something.

    Thanks, I'll follow up on those. It doesn't look like they've cleared incoming QA yet, I'll see about manually triaging.

    By editor are you referring to the Inspector? I don't understand the problem. If you have a gist or small snippet I could provide a better answer.

    Interestingly, we considered this exact approach when starting work on Splines. I don't remember off-hand why we didn't go with this. If I were to guess, we were likely considering DOTS compatibility and wanting to minimize the footprint of the default types.

    As an aside, thank you for taking the time to raise your feedback. It's clear that you have spent time working with this code and that kind of insight is not something that we take for granted.

    We will be focused on delivering value across multiple features but will also continue our support and maintenance for Splines. Please continue to log bug reports as this is the best method for us to prioritize these efforts.
     
    KarimZoPr0 and NotaNaN like this.
  8. Rowlan

    Rowlan

    Joined:
    Aug 4, 2016
    Posts:
    4,300
    The split by 3 dimensions for the PathIndexUnit is way too complicated, eg having the the range of t different by enum. Personally I'd prefer simple wrapper methods to this approach.

    Suggestion: Take a look at MicroVerse and the Spline usage. It's really super impressive what Jason pulled off and in that regard an excellent advertising for your work on Splines. Really looking forward to more features :)
     
    KarimZoPr0 and Deleted User like this.
  9. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    This is quite a scattering of callbacks all over the code required to simply keep some data in sync.


    Thats the thing about code- until you try to implement something with it, you don't really know if it works. And it's pretty clear no one tried using SplineData<T> with multiple splines.


    I'm referring to the code I posted in this very thread:


     
    KarimZoPr0 likes this.
  10. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Of note, I'm not saying you have to expand the default data structure here - you could still store it as a bunch of parallel arrays of data, with a dozen callbacks you have to manage from different systems to keep it in sync, but that becomes your problem instead of a problem every engineer using your system has to solve.
     
    KarimZoPr0 likes this.
  11. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Also, SplineReorderableListUtility is private, so we can't use it to register with the reorderable callbacks as you recommend..
     
    Last edited: Nov 11, 2022
    KarimZoPr0 likes this.
  12. Ereroa

    Ereroa

    Joined:
    Apr 27, 2017
    Posts:
    10
    How to I get to add a knot in spline.
    I can't get both Insert and Add to work.

    I want to add positions of all the child gameobject in one parent gameobject as a knots in spline.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Splines;
    5.  
    6. public class SplineGrabCurvy : MonoBehaviour
    7. {
    8.  
    9.     public GameObject CurvySpline;
    10.     public List<Vector3> positions = new List<Vector3>();
    11.     private Spline spline;
    12.  
    13. void Awake()
    14. {
    15.     Spline spline = GetComponent<Spline>();
    16. }
    17.  
    18.    public void GrabChildPositions()
    19.    {
    20.     Debug.Log("Say Something");
    21.     positions.Clear();
    22.     int pathChild = CurvySpline.transform.childCount;
    23.             for (int i = 0; i < pathChild; i++)
    24.         {
    25.             positions.Add(new Vector3(CurvySpline.transform.GetChild(i).transform.localPosition.x, CurvySpline.transform.GetChild(i).transform.localPosition.y, CurvySpline.transform.GetChild(i).transform.localPosition.z));
    26.         }
    27.    }
    28.     /// <param name="spline"></param>
    29.       public void GenerateKnots()
    30.    {
    31.  
    32.  
    33.         //spline.clear();
    34.         int pathChild = CurvySpline.transform.childCount;
    35.              
    36.         for (int i = 0; i < pathChild; i++)
    37.             {
    38.                 BezierKnot pKnot = new BezierKnot(CurvySpline.transform.GetChild(i).transform.localPosition,0,0,Quaternion.identity);
    39.                 //float3 point = new float3(CurvySpline.transform.GetChild(i).transform.localPosition.x, CurvySpline.transform.GetChild(i).transform.localPosition.y, CurvySpline.transform.GetChild(i).transform.localPosition.z);
    40.                 //transform.GetComponent<Spline>().Spline.Add(pKnot, TangentMode.AutoSmooth);
    41.                 spline.Insert(i, pKnot, TangentMode.AutoSmooth);
    42.  
    43.                 //spline.Add(pKnot);
    44.                 //spline[i] = knot;
    45.             }
    46.          
    47.    }
    48. }
     
  13. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    Try this
    Code (CSharp):
    1. class CreateSplineFromChildren : MonoBehaviour
    2. {
    3.     Spline m_Spline;
    4.  
    5.     void Awake()
    6.     {
    7.         m_Spline = GetComponent<SplineContainer>().Spline;
    8.     }
    9.  
    10.     void Update()
    11.     {
    12.         var childCount = transform.childCount;
    13.         var knots = new List<BezierKnot>(childCount);
    14.         for (int i = 0, c = childCount; i < c; ++i)
    15.             knots.Add(new BezierKnot(transform.GetChild(i).position));
    16.         m_Spline.Knots = knots;
    17.         m_Spline.SetTangentMode(new SplineRange(0, childCount), TangentMode.AutoSmooth);
    18.     }
    19. }
    20.  
     
    fwalker likes this.
  14. Rowlan

    Rowlan

    Joined:
    Aug 4, 2016
    Posts:
    4,300
    Are there plans about making the selection API public? If not, it would otherwise be helpful to have a helper function that gets the current selection of splines and knots. And an event handler for selection changes. Just checked 2.1, there's no way for me to get the selection via API.
     
  15. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Spline width not being in the main package means I have to copy all the code which uses width out of the samples and modify it to use my width component instead. This is basically just encouraging everyone to branch their own versions of your components and let them get out of date, etc. It also means when someone adds, say, loft road to their spline, they end up with two different sets of width control points on their spline which is uber confusing and means they have to match the second set of widths to the first set.
     
    shikhrr likes this.
  16. DuncanFewkes

    DuncanFewkes

    Joined:
    May 2, 2017
    Posts:
    2
    Yeah, I came across this too. Seems like a very simple use case - have thing following the spline with ability to change the thing's speed. But it just jumps around like mad because when you change maxSpeed, it calls CalculateDuration() and sets a new m_Duration for the entire spline length, then jumps the follower to position based on current elapsed time and the new duration for the entire spline.

    I mean, c'mon!
    Why am I having to override "SplineAnimate" to hack out huge parts of it just to get something to follow a spline at a dynamic speed?
     
  17. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I think partially because it's in examples, and the current split between what people expect from a spline system and what's in examples and whats in the package is just a bit off. Most spline packages on the store would have the examples as reusable and well formed functions of the spline system, not examples that may not do what you want and are expected to be modified. Spline width is also another one that is strangely in examples - like it's great to show off how to do custom data with the spline package, but I really don't want 3 different width controls on my spline because 3 different systems needed it and added their own version because they can't use the one in samples reliably.
     
    DuncanFewkes likes this.
  18. DuncanFewkes

    DuncanFewkes

    Joined:
    May 2, 2017
    Posts:
    2
    I suppose so. It just hits me as bizarre that they've got all these examples laid out, some of which do an awful lot of cool stuff, but then the very basic "make thing follow, but allow me to change speed at runtime" is absent.
    Even the Car follow example that I found after posting that rant doesn't have it! I thought I'd have to come back and apologise because I'd been daft and missed that the functionality was staring me in the face, but nope - in the car example I can add different speeds to a list of knots in the spline (or somesuch?) - but not change the car's speed at runtime by pressing buttons or from a different script (my use-case is changing speed according to audio level of whatever music is playing on the PC).

    It wasn't a lot of effort to hack one together, just surprising that it wasn't already there. Ah well.
     
  19. adslitw

    adslitw

    Joined:
    Aug 23, 2012
    Posts:
    275
    @kaarrrllll @gabrielw_unity how would you recommend I increase the thickness of the 'gizmo line' (i.e. the thin line drawn in the editor for each spline)? It's useful to use it as a visualisation to see where splines are missing in levels, but it's so thin it's very hard to see! I've tried changing the colour, but some kind of thickness control would be awesome too.
     
  20. jonagill

    jonagill

    Joined:
    Oct 5, 2019
    Posts:
    12
    It looks like `SplineComponent.AlignAxis` is basically busted in Inspectors in Splines 2.1.0, as there is an extremely hacky property drawer that forces it to use `SplineInstantiateEditor`-specific behavior everywhere it's rendered. This causes random enum values to be unselectable in the inspectors for my custom components. Can this PropertyDrawer be removed or rewritten so that we can serialize AlignAxes like any regular enum?
     

    Attached Files:

  21. gabrielw_unity

    gabrielw_unity

    Unity Technologies

    Joined:
    Feb 19, 2018
    Posts:
    963
    Hey! :) Hrm, can you file this as a bug? Seems pretty clear to me that it shouldn't work this way. Sorry for the trouble!

    Good point, I think we're all aware of this but it keeps falling off the stack. I'll check :)
     
  22. frotagonist

    frotagonist

    Joined:
    May 15, 2020
    Posts:
    7
    Is there more documentation on how to edit knot locations and amount of knots during run time? Everything I see is how to predefine the spline in the editor
     
    DragonCoder likes this.
  23. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    The API doc for Spline is here, https://docs.unity3d.com/Packages/com.unity.splines@2.1/api/UnityEngine.Splines.Spline.html

    However it's probably more useful to import the Samples and look at the example code included. The SplineOscillator example in particular shows how to assign knots to a spline.
     
  24. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    I'm increasingly of the belief that Spline/Knot not being a class was a huge mistake and makes everything more difficult than it should be. I realize this made dots integration easier, but at a fatal usability cost.

    For instance, I need splines that can snap to certain points, and be modified if those points move around to keep them in alignment. But this is quite a challenge because the only reference to a thing you can have is to a spline container, which can contain any number of splines and any number of knots within it.

    So when a connection is made, I have to store:

    - The spline container
    - The index of the spline within the container
    - the index of the knot within that spline

    This allows me to adjust the position of the knot when the object moves.

    But then this becomes a complete nightmare when splines are edited. Add a new knot, everything breaks. Add a new spline, everything breaks. Basically you have to write a metric ton of code to try to keep this data correct, instead of just having a pointer to an object that goes null when it's not there anymore.

    Can Unity write a "KnotRef" and "SplineRef" class which handles all of these cases please? This would ensure that the actual callbacks needed to keep data working actually exist, because I'm pretty sure, just as in the SplineData<T> case, they aren't all there and it's impossible to do basic things like this without having the users data break with every edit.
     
  25. one_one

    one_one

    Joined:
    May 20, 2013
    Posts:
    621
    I fully agree. This is also important for coding gameplay functionality, e.g. firing off events if an agent following a spline reaches a certain knot/junction, which I think is a pretty common scenario. Building more complex spline networks and basing gameplay code off that (or also handling junctions/intersections, adding objects at knots etc.) seems quite fragile and difficult right now. Having a convenient (and reliable!) way of referencing knots and splines would make this much easier.
     
    Unifikation likes this.
  26. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    If I understand correctly, `KnotRef` and `SplineRef` would be classes that just wrap a reference to a `SplineContainer` and spline/knot index, and are kept in-sync with spline modifications?

    Practically speaking, I'm not sure we could reliably make that work. Which yes, is a concession to your first point :)

    There are benefits to the current approach, but I do agree that we have some workflow issues that could be improved. Thinking out loud, I'd be more inclined to transition our utility methods and tools to work with knot data through an interface. We could leave our default Spline implementation as is, and introduce an alternative implementation that behaves like the traditional object oriented spline/knot types. The evaluation and utilities would be no trouble to change, but our tooling makes quite a lot of assumptions about what the spline and knot data look like.
     
    DragonCoder likes this.
  27. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Exactly. This is basically what we are required to do to make anything work now, and don't have a way to do.


    Which massively dampens the types of things you can do with the spline system, which would be quite easy to do with my own system, kinda killing the whole point of Unity making one and leading us back to 50 different spline systems. Being able to have a spline knot tied to something in the world is useful for all kinds of things.

    I don't care which way it's wrapped, as long as I can reference a knot inside of a spline and not have it break when the user edits anything. But isn't that just going to have the same issue as KnotRef would? If the internal data is just a struct[] you end up storing indexes into that list and have to reorder them whenever they change. Or is the idea that every change to the OO version regenerates the data to the DoD version from scratch, and thus it's an edit time cost that would be paid? So I'd:

    Code (CSharp):
    1. SplineEditWrapper w  = new SplineEditWrapper(splineContainer);
    2. this.referencedKnot = w.GetSpline(0).GetKnot(0);
    3. this.referencedKnot.Position = transform.position;
    4. w.Compile();
    5.  
    And this would push changes into the actual spline? This might be worse that just giving splines and knots id's that can be assigned and searched for.

    Whatever you go for, the same should be true for SplineData<T>, which also has the exact same set of issues.
     
  28. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Also, is there a way to control the tool context for spline editing? For instance, I want to have an action place the user in spline add point mode. Imagine I'm building some kind of connected network of splines with components on them that generates meshes, etc. I have an intersection piece, and I want the user to click on the intersection they want to branch off of and start creating the new section. Internally it would create a new spline container, spline, and the first point of the spline and put the user into add points mode.

    Without this, the user has to:

    - Create a spline and add at least 2 points
    - Drag it into the hierarchy in the right place
    - Add the component data to the spline and set it all up
    - Move the first point of the spline to the connection point

    This is way, way clumsier than having the system set this up automatically for them and place them directly into edit mode.

    (Note, I cannot have one component on the whole system of splines, mainly because I can't use spline containers because of the referencing issue, and each actual spline needs different component data on it, so each needs to be its own container - a way to prevent users from adding multiple splines would be really useful here, as they are never going to be valid for many use cases and fundamentally break with SplineData<T>).
     
    Unifikation and one_one like this.
  29. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    And the "Lets make everything internal/private so people can't use stuff" strikes again. If I'm correct, I should be able to do:

    ToolManager.SetActiveTool(typeof(KnotPlacementTool));

    However, it's private, so I'll have to reflection it out. Yuck.
     
  30. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Ok, so here's how you do it in case anyone else needs it:

    Given you have a spline you want to select and place in edit mode:

    Code (CSharp):
    1. Selection.activeGameObject = spline.gameObject;
    2. ActiveEditorTracker.sharedTracker.RebuildIfNecessary();
    3.  
    4. EditorApplication.delayCall += SetKnotPlacementTool;
    then:

    Code (CSharp):
    1. void SetKnotPlacementTool()
    2.         {
    3.             string namespaceName = "UnityEditor.Splines";
    4.             string typeName = "KnotPlacementTool";
    5.  
    6.             Assembly assembly = AppDomain.CurrentDomain.GetAssemblies()
    7.                 .SingleOrDefault(a => a.GetTypes().Any(t => t.Namespace == namespaceName));
    8.  
    9.             if (assembly == null)
    10.             {
    11.                 Console.WriteLine("Assembly not found for namespace: " + namespaceName);
    12.                 return;
    13.             }
    14.  
    15.             Type privateType = assembly.GetType(namespaceName + "." + typeName, true);
    16.  
    17.            
    18.             ToolManager.SetActiveContext<SplineToolContext>();
    19.             ToolManager.SetActiveTool(privateType);
    20.         }
    What I still haven't figured out how to do is get it into "adding a new point mode". This will get it into spline editing mode, with draw points active, but it does not continue the current spline if the user doesn't click on the last knot first..
     
    cadmonkey33, one_one, NotaNaN and 3 others like this.
  31. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    From the manual:

    "AfterSplineWasModified
    Invoked once per-frame if a spline property has been modified.
    "

    This does not invoke once per frame, rather there is a delay of about 5 frames or so before it's invoked. This means when I change a spline via the spline editing controls, the update is choppy - but when I make those changes from code and trigger my own refresh, it's super smooth. Is there plans to fix this to do non-amortized updates?
     
    Prodigga likes this.
  32. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    No, I was thinking that we would build support for user implemented spline classes. So you would be able to define you own `MyKnot : IKnot` class and use it in a spline. We would modify the tooling to make this work. In this scenario you would simply not use `SplineData`.

    Yeah, that's not ideal. I opened a ticket to make the tooling classes public.

    They are private now because we intend to expand on custom spline and tooling support in the future, and felt that it was likely these types would change. At this point however I think it's safe to say that the tool types themselves are stable, even if the internal workings are not.

    AfterSplineWasModified corresponds to a single Changed call invoked in EditorApplication.delayCall.

    If you want to respond to changes as they occur, subscribe to Spline.Changed.
     
  33. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Not sure if I follow. Are you saying I could define my own class (not struct) for knot and have the tooling use that, and somehow I'd still be able to convert that into a NativeSpline? What happens if multiple asset store publishers need to do that? Does that make them incompatible splines?


    Unity's policy on this stuff is rather inconsistent- The SRP teams will break literally every shader every 4 months, while other teams the API is sacrosanct and the ability to do things like add nodes to the shader graph get's shut off in case of an API change, limiting what users can do without modifying source. Feels like Unity needs a middle ground here.

    Ah, thanks!

    I recently moved to 2.2 and noticed there is a new key/value system for SplineData<T>. This is great and allows us to share things like width data between packages. However, the width example is still using the old system - is there an example anywhere? I found ItemPickup.cs, but this doesn't have any in-scene controls for setting this data along the spline (create new key, move along spline, etc) - just via the inspector interface.

    Also, is there a way to use the element inspector for my data? My use case is letting the user select an enum or prefab along a spline, so it can't be easily visualized with a handle.. And there doesn't seem to be a way to do something like launch a dialog box for it without changing the SplineData<T> controls, since mouse click, double click, and right mouse button are already mapped to things.

    ----

    Well, that's a day spent trying to figure out how you edit anything other than floats with SplineData<T> in a usable way. The ElementData system is entirely private likely because it only works with internal spline structures like knots/tangents, and it doesn't look like there's any way to use that system with custom data, which would be the perfect place to put that stuff. Drawing a button with handles to open a dialog near the SplineData points doesn't seem to work, it just doesn't draw. I might try double clicking on the data point to open a dialog box, but this will conflict with adding a point, so feels wrong.
     
    Last edited: Apr 17, 2023
  34. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461


    The item pickup example is extremely unintuitive. After adding it to your spline container:

    - You have a field to edit the key or spline in your script, but nothing else. The key is a dropdown with nothing in it.
    - To make it do something, you have to open the spline's Embedded object list, add the key name "pickups". I had to dig out this magic string from the script. Without it there's no binding between the generic object data and the ItemPickup script.
    - Then you can add data points, each which will contain a spline time and object reference.
    - Note that the key value pair has a separate default value entry, which is a bit confusing especially since the inspector window has to be very wide to show it's label.
    - No where is this data visualized or editable on the spline



    Ideally, none of this would be shown on the component itself (except in debug mode, of course), though the internal structure seems fine. What should happen:

    - Add the component to my spline container
    - a new toolbar entry for "Item Pickups" appears in the toolbar, much like LoftRoad does for width
    - I select the tool and doubt click the spline to add a item pickup point, which can be moved around
    - The element inspector shows a UI has the reference to the object, allowing me to change which one I choose
    - The element inspector is a scriptable UI, be that a PropertyDrawer for my class, or some kind of OnElementGUI override. The later is preferable, because it lets me do stuff like have enums stored as ints but displayed as an enum to the user, or lists of objects to pick from, those all coming from the component that is managing the SplineData<T> or Element data..

    Right now the editing interface for this is not really usable, as you have to dig into the script to find a magic string, edit data across multiple components, etc. And best as I can tell, I can't script it enough to give it a good workflow, since I can't use Element Inspector, and unlike SplineData<T> element data does not seem to have a way to be visualized on the spline.
     
    Rowlan, one_one and Prodigga like this.
  35. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    So I managed to get something that's going to work for now, if anyone runs into this same issue. Here's what I ended up doing:

    - Create SplineData<T> for my data as normal, where T is a class structure
    - Make a custom version of SplineDataHandles which it's tool uses instead of the regular one
    - This version exposes a static with the current selection of my class on it, updated in the proper places
    - Some care is needed here to both update it correctly, handle deselection, etc, and handle selection colors for feedback. So in my version, a 'selected' knot stays red
    - Luckily my main component will always be on the SplineContainer - which means the inspector window show's it's custom editor, and I can read the static from the new version of SplineDataHandles and create an editor for it.

    This is all a bit hacky, but does make me think that:

    - SplineDataHandles exposing which thing is currently being edited would be a really useful change to enable this kind of behavior without having to customize so much code.
    - It's still feels hacky and only works if your component is going to be on the SplineContainer object, and feels "out of the spline system". Would much prefer a real solution, as SplineData<T> and the embedded kin are really just knots with custom visual editors, but should be able to be first class citizens using the Element Inspector.

    But hey, I can at least move forward until there's a better solution.
     
    TerraUnity, Rowlan and one_one like this.
  36. kaarrrllll

    kaarrrllll

    Unity Technologies

    Joined:
    Aug 24, 2017
    Posts:
    552
    Yes, exactly. As for compatibility, it does not preclude authors from using `SplineData` in the case where you want to attach multiple different data types from different packages to a single spline. Likewise, although `ISpline` does not enforce any kind of conversion protocol, I imagine most if not all splines would be easily interchangable between types by virtue of implementing IReadOnlyList<IKnot>. Similar to how it is possible to convert between NativeSpline and Spline today.

    This is really good feedback, thank you for taking the time to post it. I'll raise this when planning our next work on Splines.
     
  37. jbooth

    jbooth

    Joined:
    Jan 6, 2014
    Posts:
    5,461
    Some more general feedback, from the perspective of trying to make the Unity spline system feel native to my tools (things I could easily manage if I was using my own system).

    I'd like more access to the editing environment
    It's hard to make the spline system feel "native" to your particular tool, rather it's always a bunch of mode switches to go between your tool and the spline system. As an example, I have pieces being connected by splines - these pieces have a visual sphere as a connection point, when you double click on them, it creates a spline which you can edit with two knots starting at that point and extending outward. This is a very compromised solution, what I really want is to be able to drag out a new spline from the sphere placing you directly into add point mode of the spline, but there isn't enough access to setting the users state in the editing environment to support this.

    Instead the user must:

    - Double click the sphere
    (I automatically put them into spline edit mode with the spline selected, but that's as far as I can take it)
    - Select add mode from the tool menu
    - Click/drag on the last point in the spline to add a new one

    But usually they futz around a bunch clicking on the existing control points, etc, before they figure this out. Even I still futz with it. This might seem minor, but it really makes the spline system feel like it's own system rather than being native to the tool I'm building, and those kinds of things break 'flow' when working with a tool and make you feel like your tripping over something and fighting the toolset.

    The end result is a game object with a spline and a component on it. However, when the user selects that object, they need to take several more steps to be able to edit it - mainly switching into spline mode and selecting a knot, add knot mode, or my custom SplineData<T> tool. However, there's no real reason for this- in many tools, selecting this object would be akin to going into spline editing mode - the knots would become visible and selectable. So this mode switch feels excessive. And while getting rid of the mode switch wouldn't make sense for some tools, it certainly does for many of them.

    I'd like more access to what can and can't be edited, are visible, and how things are anchored in the scene
    Currently when the user creates a spline in my system, I have to jump through a bunch of hoops to keep that spline point in sync with the control point. There's a crap ton of issues with this that are highlighted above (no easy way to reference a knot, etc). But even with those worked around, there's more. To keep the spline knot anchored to the transform it comes from, you have to monitor when anything changes with that control points transform, or the spline's data, or the spline's transform, and force the position of that knot to the location of the control point. To make things line up correctly, I also need to set the tangent of those knots, but the user can still edit them - they just snap back afterwards. This all feels a bit clunky, as handling all possible cases is tricky, especially when a change might have a large operational cost (ie: Converting a bunch of splines into an SDF and regenerating the entire world from scratch). So it's not just a "force a refresh of all the things at all the places and slam all the positions", rather a delicate balance of "What changed and why?".

    Being able to have a knot which can be the effective child of a scene transform? That would solve so many issues. But since a Knot is just a struct in an array, which cannot even be safely referenced, it creates a ton of management hassle for even simple things.

    This also applies for visibility- if I'm using splineData<T> there is no visibility for it when the tool is not active. But knowing that data is there is useful. Right now, I have defaults for the data, which you can override along the spline with splineData<T>, and the very first user claimed they couldn't change things because the defaults were being overridden by splineData<T> in the example scene which had no visual indication. Now, I'm pretty sure I can find a way to render this data when not in that editing mode, but as more data gets added along the spline it becomes more and more likely for this to be an issue.

    Everything I do involves an alternative data model
    Evaluate on splines is SLOW. Like, brutally slow. And most things have to do this lots and lots of times. As such I've developed several different representations of splines that I use to perform these lookups fast.

    MicroVerse converts splines into an SDF on the GPU every time you edit them, and caches it, such that the actual world rendering can just use the SDF instead of the splines directly. To cover this cost, I only update the spline being edited and keep cached copies around for any other splines in the scene. However, the conversion is pretty slow, and to speed it up and do a low res first pass then only do the edges of terrain tiles in high res. I also managed to speed up the shader by about 2x by not using your versions of the evaluate functions and rolling my own - not exactly sure why mine are faster, but they seem to match up.

    My new tool converts splines into LUTs containing a world position and quaternion at 1m resolution in a burst/job - this is done so massive numbers of vertices can be quickly processed along the spline, using the LUT instead of the spline.Evaluate call, which ends up being hundreds of times faster.

    Note that the .cginc is missing a proper evaluate call which gives you the full matrix, otherwise I might have experimented with generating it all on the GPU and doing an AsyncReadback of the results. That said, I don't have a particular ask here, but adding stuff like this to the library might be very useful for others, and how people actually use your tools is information I find very useful.

    Knot creation

    When creating knots it can be very easy to get data you don't want. For instance, roll. I find myself having to spend a lot of time clearing out roll from splines, which is clunky because you end up with floating point precision issues when you enter 0 and get back a non-zero value. And while I can easily add a feature to ignore roll in my evaluation of the spline, I then punt the issue down the line when the user adds some "move along" component to something and has it spinning all over the place. So it seems to me some tooling to fix the data at or after creation time might be a nice addition, rather than everyone adding options for how to interpret it.
     
  38. jjejj87

    jjejj87

    Joined:
    Feb 2, 2013
    Posts:
    1,117
    Quick question: Is there a handy API to check if a point is within an enclosed Spline? Or do I have to implement this myself? My use case is to place splines on a terrain and set an area of mine fields with spline and fill the enclosed area with prefabs (maybe scatter with some noise.)
     
    Ruchir likes this.
  39. Tommy4421

    Tommy4421

    Joined:
    Nov 12, 2016
    Posts:
    5
    There is a infinite loop in the Spline Instantiate script when removing all splines with for example this method:

    while (SplineContainer.Splines.Count > 0)
    SplineContainer.RemoveSplineAt(0);

    Then it will loop forever in:
    SplineInstantiate.cs:836
    while (currentDist <= (splineLength + k_Epsilon) && !terminateSpawning)

    Version 2.3.0.
     
  40. VilFix

    VilFix

    Joined:
    Sep 7, 2020
    Posts:
    3
    Splines version 2.5.2: the Method "EvaluateTangent" of the SplineUtility works right only if the parent of the SplineContainer is not rotated (transform rotation = (0,0,0)).
     
  41. freyzorr

    freyzorr

    Joined:
    Jul 26, 2015
    Posts:
    30
    A bit late to the but just in case this is still helpful to you or anyone else.

    The SplineUtility.EvaluateTangent works directly on the spline itself which exists only in local space and has no transform. The SplineContainer.EvaluateTangent on the other hand does have a transform and will call SplineUtility.EvaluteTarget and convert the result to world space for you. So call through the SplineContainer to get the rotated tangent.
     
    samanabo and adslitw like this.
  42. Wolfos

    Wolfos

    Joined:
    Mar 17, 2011
    Posts:
    951
    Spline.Evaluate doesn't work from a C# thread because it uses Allocator.Temp. I consider this a bug.