first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:36:52 +07:00
commit 6f2eafa33c
14093 changed files with 1253472 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
system: jira
server: jira.unity3d.com
project: SPLB
issuetype: Bug
package: Splines

View File

@@ -0,0 +1,5 @@
{
"timestamp": 1731095432,
"signature": "Fn9/+14U4yUHtG7CsiQn0sYn+OyQE3C0hRUf2mK9rSq0nFeEdt73CM5UbZ6x8qOmY7Bc1yy/wZ+xzrKU41tUqJgX2szsXbsa4/is9gFS+p/WVneJoFIrBV9NrLnFH1panDb6DDn4UiZQI6mXZfzRJST4gk8a3rVmsVVntaKc1khPuTlXzHyi4b5R4WPERtaZy23yN/PqzJ586eF1qnsQc9G75Nvxkwqj3mm5gSsk6SIDcdAThNm5XxxYW6iSI0L2zBag21IDidT1mmlbBy2n1AmpnZXikI6OZh/Hkv3V6eQhVeUmuSkpbBm5cDXXZyaIBxc/3xfYvjNgOEao5CnX/hvzhk/DVyVx6NgLNdzOYDOyfl2a35Q0FZF5Q+W60RGzUVJi02njmIsg0O5H4+efqLmrAsZoymiGIhgPfT4sQa2WktB9t9fYmC1uMsFk//KOXqlAWMc5Led86BLtPuMzetSCUEh4wBVxFnulPxNv21D59wMnp3j9LwPfLa1KmOu/",
"publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg"
}

View File

@@ -0,0 +1,515 @@
# Changelog
All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [2.7.2] - 2024-11-08
### Bug Fixes
- [SPLB-315] Fixed a bug where the `DrawSplineTool` icon was missing in the tools overlay.
- [STO-3434] Fixed a bug where the `DrawSplineTool` would prevent the use of the view tool.
- [STO-3435] Fixed a bug where the `DrawSplineTool` would not display the tool settings anymore.
- [SPLB-296] Fixed a bug where a log message would appear in the console when 2 knots are at the same position with 0-length tangents.
- [SPLB-304] Fixed a bug where an exception would be thrown when using `SplineInstantiate` with linear distance instantiate method and multiple splines.
- Fixed some failures related to the inline API documentation.
- [SPLB-302] Fixed a bug where an exception would be thrown when adding a knot to a closed spline from inspector.
- [SPLB-311] Fixed a bug where `SplineAnimate` component would not function properly with non-uniform scale transforms.
- Fixed compilation errors in builds
- [SPLB-310] Fixed mesh extrude not being saved when a change is applied to a mesh asset.
- [SPLB-303] Fixed a bug where spline section foldout labels (shape extrude, geometry, advanced) did not unfold the sections when clicked.
- [SPLB-294] Fixed a bug where adding new splines by using the `SplineContainer` inspector would dirty all open scenes.
## [2.7.1] - 2024-10-17
### Added
- [STO-3204] Added a **Create Spline** button to enter spline creation when you do not have the Spline tool context activated.
- New `SplineExtrude` and `SplineMesh` option enables new extrusion profiles, including `Circle`, `Square`, `Road`, and `Spline`.
- Added a section in the documentation for the `Source Spline Container` in the `SplineExtrude` component.
### Bug Fixes
- [SPLB-292] Removed the dependency on the UGUI package.
- Removed the toggle to override the target spline in the `SplineExtrude` inspector.
- [SPLB-288] Fixed a bug where a prefab `SplineContainer` would unexpectedly restore splines deleted before entering and exiting playmode.
- [SPLB-284] Fixed a bug that prevented baking instances of multiple `SplineInstantiate` components when multi-selecting.
- [SPLB-277] Fixed a bug where instantiating a prefab with `SplineAnimate` component would throw messages in the console.
- [SPLB-279] Fixed a bug where closing a Spline would not update the auto-smooth knots at the extremities.
## [2.6.1] - 2024-05-23
### Bug Fixes
- [SPLB-275] Fixed a bug where the `SplineContainer` component would throw warning when entering playmode.
- [SPLB-266] Fixed a bug where instantiating a `SplineExtrude` component at runtime would throw errors.
- [SPLB-269] Fixed a bug where instantiating a `SplineAnimate` component at runtime would throw errors.
- [SPLB-260] Fixed a bug where playing a `SplineAnimate` from a start method would not work.
- [SPLB-267] Fixed a bug where undoing a spline insertion in a container would throw errors from the `SplineInstantiate` component.
- [SPLB-261] Fixed a bug where the knot placement tool would not work when multiple scene views were opened.
- [SPLB-253] Fixed a bug where using the knot placement tool in 2D mode would not work if the grids are turned off.
- [SPLB-258] Fixed a bug where destroying a component would throw errors in the console.
## [2.6.0] - 2024-04-15
### Added
- Added new APIs that enable users to directly provide a collection of `float3` knot positions to a `Spline` constructor, or to the new `Add`, `AddRange`, `Insert`, and `InsertRange` methods, instead of using `BezierKnots`.
- Added an event to the `SplineAnimate` component that is triggered when the animation or loop is completed.
### Changed
- Changed supported version in the documentation dropdown selector to 2022.3 and later.
### Bug Fixes
- [SPLB-246] Fixed performance and garbage collection (GC) memory allocation issues when evaluating splines using `SplineContainer` API.
- [STO-3176] Fixed an issue where `FitSplineToPoints` utility would not fit splines correctly.
- [SPLB-249] Fixed a bug where setting a spline game object position was overridden by the knot placement tool.
- Fixed a bug where using the knot placement tool on scaled objects would not create the correct tangents.
- Fixed a bug where changing the scale of a spline game object would not update the scene view handles.
- [SPLB-239] Fixed a bug where reverting overrides of a spline prefab instance would not properly update its visuals.
- [SPLB-238] Fixed a bug where `SplineAnimate` component would continuously output messages to the Console when in playmode and animated object was selected.
- [SPLB-245] Fixed a bug where `SplineAnimate` component would rotate the animated object on playmode exit.
## [2.5.2] - 2023-12-22
### Added
- [SPLB-222] Added safety checks to spline changed events.
### Changed
- [SPLB-233] Removed transform handles while dragging a knot using direct manipulation.
- [SPLB-234] Removed unnecessary spline metadata which was saved on disk.
### Bug Fixes
- [SPLB-230] Fixed a bug where modifying the tangent mode on the last knot would change the spline shape unpredictably.
- [SPLB-225] Fixed a bug where closing a Spline with the `Draw Splines Tool` had inconsistent behaviour.
- [SPLB-236] Fixed a bug where the spline instantiate inspector was throwing an exception when the selection contained objects without that component.
- [SPLB-235] Fixed a bug where hovering a knot was create highlights when dragging tangents.
- [SPLB-234] Fixed a bug where it was impossible to add a knot on the latest added knot.
- [SPLB-214] Fixed an exception that would when adding a spline via the inspector if no other spline exists.
- [SPLB-226] Fixed broken preview when adding knots to a closed spline
## [2.5.1] - 2023-10-17
### Bug Fixes
- [SPLB-208] Fixed a bug where `SplineToolContext` would attempt to draw empty Splines and flood the console with errors.
## [2.5.0] - 2023-10-16
### Added
- Exposed public API for `SplineInstantiate.Seed`.
### Bug Fixes
- [SPLB-201] Fixed error messages showing in edit mode with spline components.
- [SPLB-203] Fixed a bug where baking `SplineInstantiate` instances broke prefab connections.
- [SPLB-196] Fixed a bug where the `Random` state was not restored after an update of the instances in `SplineInstantiate`.
- Fixed a bug where the `SplineCacheUtility` would send null reference exceptions.
- [SPLB-183] Fixed a bug where duplicating the `SplineExtrude` component was not updating the mesh asset reference.
- [SPLB-178] Fixed a bug where linking knots would not trigger a `Spline.changed` event.
- [SPLB-185] Fixed a bug where the Up vector evaluation would return NaN values when normals and curve tangents are parallel.
### Changed
- [SPLB-187] Fixed a bug where `Spline.EvaluatePosition` was allocating memory when called.
- [SPLB-181] Fixed a bug where the Auto tangent mode computed incorrect knot rotations.
- Changed the `SplineComponent` display name in the Inspector to `Spline Container` as a response to user feedback.
## [2.4.0] - 2023-07-26
### Added
- Exposed public APIs for `SplineUtility.ReverseFlow`, `SplineUtility.JoinSplinesOnKnots`, and `SplineUtility.SplitSplineOnKnot`.
### Changed
- For Editor versions of 2023.2 or newer, added clipboard operations for Splines to the Scene view context menu.
- Changed `Splines/Cycle Active Spline` shortcut to Shift-S to remove a conflict with Unity default shortcuts.
- [SPLB-163] Improved performance of spline handles by caching spline data.
- [SPLB-167] Changed the evaluation of up vectors to use a cache instead of re-evaluate the values on every single request.
- In Editor versions of 2023.2 or newer, moved the following actions from the Element Inspector to the Scene view context menu: Link, Unlink, Split, Join, and Reverse Spline Flow.
### Bug Fixes
- [SPLB-176] Fixed a regression where the up vector would not evaluate correctly for some curves.
- Fixed the setter for SplineAnimate.Normalized time to handle ping-pong direction correctly when set.
- Fixed a bug where setting the time value to 0 after reaching the end of the spline animation was not resetting the object position.
- [SPLB-169] Fixed a bug where `CurveUtility.GetNearestPoint` would return incorrect values for the interpolation.
## [2.3.0] - 2023-05-31
### Added
- Exposed public APIs for `SplineTool` and `SplineHandles`.
- Added a method to *bake* SplineInstantiate objects in the scene so the users can modify the resulting GameObjects.
### Bug Fixes
- [SPLB-165] Fixed a bug that prevented actions from being retained with prefab instances that contained splines when entering playmode.
- [SPLB-162] Fixed a bug where the start offset in `SplineAnimate` did not work in Play mode.
- [SPLB-152] Fixed objects created from `SplineInstantiate` not inheriting static flags from parent `GameObject`.
- [SPLB-160] Fixed potential 'look rotation vector is zero' in SplineInstantiate.
- [SPLB-156] Fixed possible null reference exception when accessing curve length.
## [2.2.1] - 2023-04-21
### Added
- [SPLB-100] Added a **Resolution** setting for spline meshes in Splines preference.
### Changed
- [SPLB-100] Improved `SplineMeshHandles` rendering.
### Fixed
- Fixed a bug where spline selection would be incorrect after a spline was deleted.
- [SPLB-146] Fixed a bug that prevented actions from being undone with prefab instances that contained splines .
- [SPLB-148] Fixed a bug in the `LoftRoadBehaviour` component where Undo calls prevented the project from building.
### Changed
- Changed `Knot Placement` tool behaviour regarding selection. Element selection is now empty when you exit the tool.
## [2.2.0] - 2023-03-22
### Added
- [SPLB-72] Added a public version of `SplineDataHandles.DataPointHandles` which requires an additional parameter, `splineID`.
- [SPLB-72] Added the `SplineContainer.SplineAdded`, `SplineContainer.SplineRemoved` and `SplineContainer.SplineReordered` public events and removed those same events from `ISplineContainer`
- [SPLB-72] Added the `SplineUtility.ReorderSpline` public method.
- [SPLB-72] Added an example to the **Loft Road** sample that supports spline data for multiple splines.
- Improved UI for editing points in the Inspector.
- Added preference to disable tangent dragging feature in `Knot Placement` tool.
- Added `ISplineContainer.AddSpline(Spline)` function overload.
- Added new functions on `Spline` to get and set `SplineData`.
- Added new `EmbeddedSplineData` type to easily create and reference `SplineData` stored on arbitrary `Spline` objects.
### Changed
- [SPLB-127] Changes made to a spline in the Inspector no longer invoke the `Spline.Changed` event with the `SplineModification.Default` type. The `Spline.Changed` event is still invoked, but with specific modification types like `SplineModification.KnotModified`.
- [SPLB-118] Changed the documentation of `SplineMath.PointLineNearestPoint` and `SplineMath.RayLineNearestPoint` to explain the returned values more precisely.
- [SPLB-96] Changed the default tension of tangents in **Auto** mode so they are smoother. **Auto** mode is now closer to the Catmull-Rom behavior of Splines 1.0.
- [SPLB-72] Removed the `WidthSplineData` and `MultipleRoadBehaviour` components from the samples.
- Reduced heap allocations when modifying Spline properties.
- [SPLB-92] Modified the Inspector to use `SplineModification.KnotRemoved` events to remove knots and pass the index for each removed knot.
- [SPLB-92] Modified the `Spline.SetTangentMode` and `Spline.SetAutoSmoothTension` methods to invoke `SplineModification.KnotModified` events for each modified knot and pass the index.
- Increased the threshold for tangent dragging when placing knots with the `Knot Placement` tool.
### Fixed
- [SPLB-141] Fixed a bug where `SplineExtrude` component would overwrite existing Mesh assets.
- [SPLB-129] Fixed a bug where `CurveUtility.EvaluateUpVector` caused burst errors.
- [SPLB-138] Fixed a bug where `SplineJobs.EvaluatePositionTangentNormal` returned incorrect values.
- [SPLB-136] Fixed a bug where undoing actions on a spline was not marking the spline as dirty.
- [SPLB-123] Fixed an inconsistency between inserting a `BezierKnot` using Editor tools and runtime API.
- [SPLB-130] Fixed a bug that would crash the Editor if the `SplineInstantiate` component had its parent GameObject in the list of items to instantiate.
- [SPLB-90] Fixed an issue where `Spline.SetKnotNoNotify` triggered change callbacks.
- [SPLB-77] Fixed a null reference exception when spline animate didn't have a spline referenced.
- [SPLB-125] Fixed issue where the spline would have different state during the `Spline.Changed` callback depending if it was modified using the API or inspector.
- [SPLB-74] Fixed a bug that caused an error when knots were deleted from the Inspector.
- [SPLB-121] Fixed a bug where spline data changes done using the Inspector would not undo correctly.
- [SPLB-92] Fixed an issue where adding knot using the inspector would trigger `SplineModification.KnotInserted` event twice.
- [SPLB-107] Fixed a bug where a duplicates of a spline with the Spline Instantiate component would have visible child GameObjects in the Hierarchy window.
- [SPLB-65] Fixed an issue where it wasn't possible to add a knot to a closed spline that is empty or contains a single knot.
- [SPLB-104] Fixed an issue where duplicating a spline using the Inspector would not copy knot link data.
- [SPLB-82] Fixed an issue where it would not be possible to undo adding a knot from the Inspector.
- [SPLB-84] Fixed an issue where the obsolete Spline Edit Mode would show in the Debug Inspector.
- [SPLB-135] Fixed an issue where the Auto Smooth tangent mode would not smooth a spline's curves correctly.
- [SPLB-104] Fixed an issue where shared knots would lose selection when a spline is selected using the Inspector.
- [SPLB-115] Fixed an issue where using the Width tool on MacOS (Metal) would render visual artifacts outside of the Scene view.
- [SPLB-98] Fixed an issue where `LoftRoadBehaviour` would create incorrect mesh geometry normals.
- [STO-2791] Fixed an issue where spline data's reorderable list would break if the data was not the field of a `MonoBehaviour`.
- [SPLB-86] Fixed a bug that caused a spline to change its shape when **Reverse Spline Flow** action was used.
- [SPLB-103] Fixed an issue where the spline tool settings' Visuals popup would display toggle checkboxes on the wrong size and be clipped in some editor versions.
- [SPLB-87] Fixed an issue where the cached Spline length could be incorrect after an undo action.
- Fixed an issue where modifying knot values or order in the Inspector could affect unassociated knot data.
- Fixed use of obsolete `Objects.FindObjectsOfType` API in Unity 2023.1 and newer.
- Fixed incorrect icon import settings.
- Fixed a bug that would prevent Spline Instantiate from instantiating when it was loaded from an addressable build in the Editor.
- Fixed a bug where calling `SplineDataHandles.DataPointHandles` without a `splineID` argument would crash.
- Fixed a bug that would cause `SplineData` handles to interfere with scene navigation.
- Fixed SplineData handles not setting `GUI.changed` correctly.
- Fixed an issue where `SplineSelection` errors could put the Scene view into an unrecoverable broken state.
- Added a new property drawer for `int` interpreted as a spline index, `SplineIndexAttribute`.
- Fixed a bug where modifying and undoing changes to `Spline` values in the Inspector resulted in an incorrect `Spline` length calculation.
- Added `SplineData.OnSplineModified` function to attempt to preserve knot indices when knots are inserted or removed. This is called automatically for all embedded `SplineData` collections.
## [2.1.0] - 2022-10-18
### Added
### Changed
### Fixed
- [STO-2745] Fixed an issue where the Speed Tilt tool's arrow handle cap would flicker when the View tool toggled on or off.
- [STO-2744] Fixed a bug where tangents would have the wrong orientation.
## [2.1.0-pre.1] - 2022-10-12
### Added
- New `SplineUtility.EvaluateNurbs` function to evaluate positions on NURBS curves.
- New `SplineUtility.FitSplineToPoints` function to fit a spline to a provided set of curve points.
- Added a new public static class, `InterpolatorUtility`, to improve the discoverability of `IInterpolator` implementations.
- Added the functionality to offset the starting position of a `SplineAnimate` component's animation.
- Added copy, paste, and duplicate support to Spline tools.
- Exposed API to draw spline and curve handles.
- Added settings to generate a 3D mesh around spline handles to better visualize depth.
- Added the functionality to disable specific Spline tool handles.
- Added the functionality to tweak knot position without being in the position tool.
- Added spline index to the Element Inspector when a spline element is selected.
- Updated public API documentation.
- Updated built-in Spline components to support spline containers with multiple splines.
- New `SplineUtility.ReducePoints` function to remove redundant points on a line and still retain the original shape.
- New `Spline.SetAutoSmoothTangentTension` function to specify the tension used when tangents are calculated.
- Added `Spline Filter` to the Spline tools settings.
- Added spline selection from the Spline Inspector.
- Added support to show knot indices in the Scene View.
- New `Paint` example shows how to create a spline from a list of points.
- Updated the `SplineUtility.GetAutoSmoothTangent` function to use centripetal parameterization to calculate the tangents in **Auto** tangent mode.
- New `SplineUtility.GetAutoSmoothTangent` function takes the current knot position and then uses centripetal parameterization to calculate the tangents in **Auto** tangent mode.
- Added new built-in Spline shapes: Rounded Square, Polygon, Helix, and Star.
- Added the functionality to to delete tangents.
- Removed the disc that displayed around selected knots.
- Added a disc that displays when a user has the Tweak tool enabled and hovers over a tangent.
- Added transparency to the disc that displays when a knot or tangent is hovered on if the disc occludes an object in the scene.
- Reduced the transparency of the Tweak tool handles when they occlude an object in the scene.
- New `EditorSplineUtility.SetKnotPlacementTool` function to set the active context to `SplineToolContext` and the active tool to `KnotPlacementTool`.
### Changed
- [STO-2666] Disabled the Visual dropdown button when using `KnotPlacementTool`.
- Modified spline element handles to use `Element Color`.
- [STO-2708] Modified spline deletion to remove empty splines.
- [STO-2682] Unified `Draw Splines Tool` naming across menus and documentation.
- [STO-2681] Attenuated the color of the tangents and the curve highlights when they are behind objects.
- [STO-2700] Modified Spline Instantiate so it no longer serializes instances in the Scene or prefabs.
- [STO-2682] Unified `Draw Splines Tool` naming across menus and docs.
- [STO-2728] Changed the label of the `SplineAnimate` component's `World` alignment mode to `World Space` in the Inspector.
- Modified the `Draw Splines Tool` to clear any Spline element selection when it activates.
- [STO-2490] Made active element selection consistent with standard Editor behavior for GameObjects. Now you can hold Shift and click a knot to set it as the active element.
- Spline element handles now use the `Element Selection` and `Element Preselection` colors.
- Changed tangent's shapes to diamonds.
- Modified the `Knot Placement Tool` to have a live preview for segments with auto-smoothed knots.
- Dependency on Unity Physics Module is now optional.
- Modified `SplinePath` to support the `Closed` property of Splines.
- Removed and merged redundant Sample scenes.
- Reduced the size of the flow indicator handle.
- Changed default colors and thickness for spline elements and curves.
- Improved the line visibility of handles and segments.
- Changed Burst from required dependency to optional.
- Burst compile `SplineJobs.EvaluationPosition` when Burst package is available.
### Fixed
- [STO-2746] Fixed an issue where it was impossible to delete knots on mac.
- [STO-2743] Fixed an issue where a curve's highlight would flicker when hovering near a knot.
- [STO-2729] Fixed an issue where reordering knots would break knot links until moved.
- [STO-2730] Fixed the curve highlight color when using the Tweak tool.
- [STO-2693] Fixed a bug that prevented users from adding and reordering knots in the Inspector when the spline comes from a class that inherits from `ISplineContainer`.
- [STO-2725] Fixed a bug where knots, discs, and the normal line of knots would use incorrect colors.
- [STO-2726] Fixed a bug where knots handles were drawn under curves handles.
- [STO-2727] Corrected a typo in the Loop Mode of the `SplineAnimate`.
- [STO-2653] Fixed a bug where the Tweak tool's guide plane would flicker when drawn directly on a surface.
- [STO-2731] Fixed View Tool not working when Spline Context was active.
- [STO-2700] Fixed spline instantiate having a delay before regenerating the instances.
- [STO-2679] Fixed the segmentation of the curve highlight.
- [STO-2665] Fixed sample scenes not rendering correctly when URP or HDRP was used.
- [STO-2702] Removed the **Dist** label in the Inspector when the `SplineInstantiate` component is set to `Exact`.
- [STO-2656] Fixed a bug where hovering on linked knots would display discs on each linked knot.
- [STO-2686] Fixed an issue where inserting a knot on the closing curve would result in other knots moving around.
- [STO-2685] Fixed a bug where `LoftRoadBehavior `would throw exceptions with knots that had linear tangents.
- [STO-2657] Fixed a bug where spline gizmos would appear at unexpected moments.
- [STO-2655] Fixed a bug that caused knots to highlight with the wrong color.
- [STO-2687] Fixed an error that would occur when deleting the last knot of a spline.
- [STO-2684] Fixed a bug that prevented Select All from selecting elements.
- [STO-2680] Fixed a bug where `SplineMesh.Extrude` would create twisted mesh geometry.
- [STO-2701] Fixed a bug where `LoftRoadBehavior` would either throw an exception or extrude incorrectly when the spline was in Linear tangent mode or if it was shorter than 1 unit length.
- [STO-2705] Fixed a bug where `SplineInstantiate` was not instantiating correctly when the instantiation method was set to `Method.InstanceCount`.
- [STO-2668] Fixed a bug where Spline element inspector values would not update when a knot is modified.
- [STO-2706] Fixed a bug where selecting a knot from the inspector was desynchronizing the tool selection.
- [STO-2696] Fixed a bug where clearing knot selection was not updating in the inspector.
- [STO-2689] Fixed the behavior of the spline inspector selection when clicking on a selected element.
- [STO-2688] Fixed a delay in the scene view update after changing selection from the spline inspector.
- [STO-2669] Fixed a bug where modifying a spline in the Inspector would not invoke the `Changed` callback.
- [STO-2695] Fixed a bug that would throw an exception when the `SplineAnimate` component was destroyed.
- [STO-2658] Fixed a bug that would delay the color change when you hover over a segment.
- [STO-2692] Fixed an issue where deleting a spline would result in errors.
- [STO-2716] Added missing tooltips to the Element Inspector.
- [STO-2636] Fixed an issue that prevented spline framing if the active tool context was not set to Spline.
- [STO-2714] Fixed transformation and direct manipulation tools not working correctly when spline has non-uniform scale.
- [STO-2698] Fixed a bug that could cause a knot link to desync if a linked knot was modified in the Inspector.
- [SPLB-54] Fixed a bug where flow arrows and curve highlights would not be centered on a spline's segments between knots.
- [SPLB-44] Fixed a bug where tangent selection would remain after changing to a tangent mode without modifiable tangents.
- [STO-2632] Fixed Spline Selection Undo when selecting a single element.
- Fixed a bug where the spline inspector was not working if the `Spline` object was not stored in a ISplineContainer.
- [SPLB-40] Fixed a bug where a tangent's Magnitude field in the `Element Inspector` created NaN values.
- [SPLB-39] Fixed a bug where knots from separate splines would link to the wrong knot.
- [SPLB-38] Fixed incorrect auto-smooth knots on reversed splines.
- Fixed a bug where the SplineContainer reorderable list broke the LinkKnots collection.
- Fixed a bug that caused the Inspector to display incorrect spline indexes.
- Fixed spline selection intercepting scene view navigation shortcuts.
- Fixed a bug where setting the Spline Instantiate component's instantiation items with the Inspector would have no effect.
- Fixed a potential exception that occurred when opening scenes with splines created in the 1.0 version of this package.
- Fixed tangent and knot handles incorrectly highlighting while a tool is engaged.
- Fixed a bug where deleting some element selections would result in an exception being thrown.
- Fixed a bug where undoing after deleting a selection would not re-select the restored elements.
- [STO-2690] Fixed a bug that prevented data points from being added to a spline when the spline was clicked on.
- [STO-2691] Fixed a bug where moving a data point along a spline behaved incorrectly.
- Fixed compile errors in sample scenes when building player.
- Added an ellipsis to the Draw Spline Tool menu item label.
- Fixed `Spline Tool Context` not working with `ISplineContainer` implementations that define a valid `KnotCollection`.
## [2.0.0-pre.2] - 2022-05-11
### Added
- Added the ability to edit multiple spline elements in the element inspector.
- Added functionality to join knots from different splines.
- Added functionality to reverse the flow of a spline.
- Added `SplinePath`, `SplineSlice`, and `SplineRange` types. These types enable interpolation and evaluation of partial or complete sections of discrete splines.
### Changed
- Modified rounding to be based on camera distance.
- [STO-2704] Changed `SplineUtility.GetBounds` to account for tangent positions when calculating bounds.
- Updated the design of the tangent mode fields in the Element Inspector.
- Added a dropdown menu to select tangent modes to the Element Inspector.
- Updated the `Draw Splines Tool` to display only one tangent when a new knot is created.
- Simplified tangents in the `Draw Splines Tool` by removing the interactable handle .
- Renamed `Knot Placement Tool` to `Draw Splines Tool`.
- Modified the `Draw Splines Tool` to account for multiple splines.
### Fixed
- Fixed SplineInspector knot removal not keeping metadata consistent (KnotLinks).
- Fixed an issue that caused auto-smoothed tangents to show in the `Draw Splines Tool` and be selectable by rect selection.
- Added `ReadOnly` to knot's and length's `NativeArray` to fix IndexOutOfRangeException on `NativeSpline`.
- Fixed tangents when closing the spline to keep user-defined values.
- Fixed index error in the `Spline.SendSizeChangeEvent` method.
- Fixed a case where inserting a knot would not update adjacent knots with "auto-smooth" tangent mode.
## [2.0.0-pre.1] - 2022-04-19
### Added
- Added structs and utility methods that use the [Job System](https://docs.unity3d.com/Manual/JobSystem.html) to evaluate splines.
### Changed
- Separated tangent and bezier modes in the Element Inspector.
- Added a feature to split splines at a knot from the Element Inspector.
- Added tool settings to change the default knot type.
- Added ON icons for tangent modes.
- Moved Spline creation menu items to `GameObject/Spline`.
- Modified the Spline Inspector to be reactive to spline element selections in the Scene View.
- New icons set for Spline-related items.
- Hiding knot handles if the EditorTool is not a SplineTool
- Tweaked the spline property drawer to make it a bit more clean.
- Changed the knot rotation property in the inspector to a Vector3Field instead of a QuaternionField.
- Added a new editor API to change the tangent mode of knots.
- Deprecated `Spline.EditType`. Tangent modes are now stored per knot. Use `Spline.GetTangentMode` and `Spline.SetTangentMode` to get and set tangent modes.
- Added ability to link and unlink knots using Element Inspector.
### Fixed
- [1411976] Fixed undo crash in SplineInstantiate component.
- Fixed scale offset in SplineInstantiate component.
- [1410919] Fixed SplineData Inspector PathIndexUnit when updating unit.
- Fixed issues with spline editor tools changes sometimes being overwritten
- Fixed `SplineUtility.Evaluate` incorrectly evaluating the up vector.
- [1403359] Fixed issue where `SplineExtrude` component would not update mesh after an undo operation.
- [1403386] Fixing SplineData Inspector triggering to SplineData.changed events.
- Fixing console InvalidOperationException when creating a Spline with a locked Inspector.
- [1384457] Fix for an exception being sometimes thrown when drawing a spline and rotating the scene view.
- [1384448] Fixed incorrect Rect Selection when using Shift or CTRL/CMD modifiers.
- [1413605] Fixed Linear to Bezier Edit Type conversion incorrectly leaving tangents set to zero length.
- [1413603] Spline creation menu items now respect the preference to place new objects at world origin.
- `SplineFactory.CreateSquare` now respects the `radius` argument.
## [1.0.0] - 2022-02-25
### Changed
- New icons set for Spline-related items.
- `SplineContainer` inspector is now more user-friendly.
- Fixed issue where Spline Inspector fields would not accept negative values.
- Fixed issue where the X shortcut would only cycle through World/Local handle orientations and ignore Parent/Element.
- Fixed samples compatibility issues on 2021.2.
- Spline Inspector no longer shows 2 editable tangent fields for Knots that only have one tangent.
- Fixed poor performance when manipulating long continuous tangents.
- `SplineUtility.ConvertIndexUnit` now wraps when returning normalized interpolations.
- Fixed issue where Knot rotation would not properly align to the surface the Knot is placed on.
- Fixed Spline length serialization issue that would result in incorrect Spline evaluations and length calculations.
- Updated Knot and Tangent handle design.
## [1.0.0-pre.9] - 2022-01-26
### Changed
- Adding new API to interact with SplineData Handles
- Adding a `SplineInstantiate` component and updating associated samples.
- Added a `SplineAnimate` component and sample scene.
### Fixed
- [1395734] Fixing SplineUtility errors with Spline made of 1 knot.
- Fixing Tangent Out when switching from Broken Tangents to Continuous Tangents Mode.
- Fixing Preview Curve for Linear and Catcall Rom when Closing Spline.
## [1.0.0-pre.8] - 2021-12-21
### Changed
- Added a `SplineExtrude` component and an accompanying ExtrudeSpline sample scene.
- When using a spline transform tool, CTRL/CMD + A now selects all spline elements.
- Improving Spline Inspector overlay.
- `SplineUtility.CalculateLength` now accepts `T : ISpline` instead of `Spline`.
### Fixed
- [1384451] Fixing knot handles size being too large.
- [1386704] Fixing SplineData Inspector not being displayed.
- Fixing wrong Spline length when editing spline using the inspector.
- [1384455] Fix single element selections breaking the undo stack.
- [1384448] Fix for CTRL/CMD + Drag not performing a multi selection.
- [1384457] Fix for an exception being sometimes thrown when drawing a spline and rotating the scene view.
- [1384520] Fixing stack overflow when entering playmode.
- Fixing SplineData conversion being wrong with KnotIndex.
## [1.0.0-pre.7] - 2021-11-17
### Changed
- Disable unstable GC alloc tests.
## [1.0.0-pre.6] - 2021-11-15
### Changed
- Replace references to 'time' with 'interpolation ratio' or 't'.
- Move distance to interpolation caching and lookup methods to `CurveUtility`, and document their use.
- Fix compile errors when opened in Unity 2021.2.
- Removed `Spline.ToNativeSpline`, use `new NativeSpline(ISpline)` instead.
- Removed `Spline.ToNativeArray`.
### Fixed
- Fixed issue where hidden start/end knot tangents would be selectable.
- Fixed active tangentOut incorrectly mirroring against tangentIn when changing tangent mode via shortcut.
- Fixed Knot Placement tool preview curve disappearing when cursor hovers over first knot.
- Fixed issue where knot would not align to tangents when switching from broken to mirrored or continuous modes.
- Fixed issue where hovering first knot while placing tangents would hide the last placed knot, its tangents and the preview curve.
## [1.0.0-pre.5] - 2021-11-02
- Initial release

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 06ba237cdfbe2264f89158773bd6268b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
* [About Splines](index.md)
* [Splines upgrade guide](upgrade-guide.md)
* [Create a spline](create-a-spline.md)
* [Manipulate splines](manipulate-splines.md)
* [Reverse the flow of a spline](reverse-spline-flow.md)
* [Join splines](join-knots.md)
* [Knots](knots.md)
* [Link and unlink knots](link-unlink-knots.md)
* [Split knots](split-knots.md)
* [Tangents](tangents.md)
* [Tangent modes](tangent-modes.md)
* [Select a tangent mode](select-tangent-mode.md)
* [Select a default tangent mode](select-default-tangent.md)
* [Animate along a spline](animate.md)
* [Animate a GameObject along a spline](animate-spline.md)
* [Change the alignment of an animated GameObject](animate-alignment.md)
* [Configure the movement of a GameObject](animate-movement.md)
* [Spline Animate component reference](animate-component.md)
* [Extrude a mesh along a spline](extrude.md)
* [Create a 3D mesh along a spline](extrude-mesh.md)
* [Spline Extrude component reference](extrude-component.md)
* [Spline Element Inspector overlay reference](element-inspector.md)
* [Spline component reference](spline-component.md)
* [Spline Instantiate component reference](instantiate-component.md)

View File

@@ -0,0 +1,20 @@
# Change the orientation and alignment of the animated GameObject
Select the orientation and alignment that a GameObject uses when it animates along a spline.
To change the orientation and alignment of the animated GameObject, do the following in the GameObject's **Spline Animate** component:
1. In the **Up Axis** dropdown, select which axis the animated GameObject uses as its up direction. The y-axis is the default up direction.
1. In the **Forward Axis** dropdown, select which axis the animated GameObject uses as its forward direction. The z-axis is the default forward direction.
1. In the **Align To** dropdown, select a space to orient the animated GameObject to:
* Select **None** to set no alignment for the animated GameObject. The GameObject's rotation is unaffected.
* Select **Spline Element** to align animated GameObjects to an interpolated orientation calculated from the rotation of the knots closest to its position.
* Select **Spline Object** to align animated GameObjects to the orientation of the target spline.
* Select **World Space** to align animated items to world space orientation.
## Additional resources
* [Animate a GameObject along a spline](animate-spline.md)
* [Control the animated GameObject's movement](animate-movement.md)
* [Spline Animate component reference](animate-component.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,27 @@
# Spline Animate component reference
Use the Spline Animate component to animate the position and rotation of a GameObject along a spline.
| **Property** | **Description** |
| :-------------------- | :------------------------ |
| **Spline** | Select a GameObject that has an attached Spline component you want to animate on. |
| **Up Axis** | Select which axis the animated GameObject uses as its up direction. The y-axis is the default up direction. |
| **Forward Axis** | Select which axis the animated GameObject uses as its forward direction. The z-axis is the default forward direction. |
| **Align To** | Select one of the following spaces to orient animated GameObjects to: </br> <ul><li>**None**: Set no alignment for the animated GameObject. The GameObject's rotation is unaffected. </li><li>**Spline Element**: Align animated GameObjects to an interpolated orientation calculated from the rotation of the knots closest to its position.</li> <li>**Spline Object**: Align animated GameObjects to the orientation of the target spline.</li> <li>**World Space**: Align animated items to world space orientation. </li> |
| **Play On Awake** | Start the animation when the GameObject first loads. |
| **Start Offset** | Set a distance on the target spline to start the GameObject's animation at. The range is 0 through 1. A value of 0 starts the animation at the beginning of the spline and a value of 1 starts the animation at the end of the spline. |
| **Method** | Select the animation method that the animation uses. </br> The **Time** method animates the GameObject along the spline from over a period of time measured in seconds. The **Speed** method animates the GameObject along the spline at a set speed measured in meters per second. |
| **Duration** | Set the period of time that it takes for the GameObject to complete its animation along the spline. </br> This property is visible only when you enable the **Time** method. |
| **Speed** | Set the speed that the GameObject animates along the spline at. </br> This property is visible only when you enable the **Speed** method. |
| **Easing** | Select the easing mode that the animation uses. Easing varies the speed of the animation to make it seem more natural and organic. The following easing modes are available: <ul><li>**None**: Set no easing on the animation. The animation speed is linear. </li><li>**Ease In Only**: The animation starts slowly and then speeds up. </li><li>**Ease Out Only**: The animation slows down at the end of its sequence. </li><li>**Ease In-Out**: The animation starts slowly, speeds up, and then ends slowly. **Ease In-Out** is a combination of **Ease In** and **Ease Out**. </li></ul> |
| **Loop Mode** | Select the loop mode that the animation uses. Loop modes cause the animation to repeat after it finishes. The following loop modes are available: <ul><li>**Once**: Set the animation to play only once. </li><li>**Loop Continuous**: Set the animation to restart from its beginning after it finishes. </li><li>**Ease In Then Continuous**: Set the animation to start slowly and then restart from its beginning after it finishes. If **Ease In Only** looping is set, then the easing applies only to the first animation loop. </li><li>**Ping Pong**: Set the animation to play in reverse after it finishes. The animation plays repeatedly. </li></ul> |
| **Preview** | Play, pause, or reset the animation. |
|**Time** | Select a specific time in the sequence of the animation. |
## Additional resources
* [Animate a GameObject along a spline](animate-spline.md)
* [Control the animated GameObject's movement](animate-movement.md)
* [Change the alignment of an animated GameObject](animate-alignment.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,32 @@
# Configure an animated GameObject's movement
Control how an animation begins on a spline, its speed, and the animation method it uses.
To control the movement of an animated GameObject, do the following in the GameObject's **Spline Animate** component:
1. To start the animation when the GameObject first loads, enable **Play On Awake**.
1. To set a distance on the spline to start the GameObject's animation at, enter a value for the **Start Offset** property. The range is 0 through 1. A value of 0 starts the animation at the beginning of the spline and a value of 1 starts the animation at the end of the spline.
1. In the **Method** dropdown, select an animation method:
* Select **Time** to animate the GameObject along the spline over a period of time measured in seconds.
* Select **Speed** to animate the GameObject along the spline at a set speed measured in meters per second.
1. In the **Easing** dropdown, select an easing mode for the animation to use:
* Select **None** to add no easing to the animation. The animation speed is linear.
* Select **Ease In Only** to have the animation start slowly and then speed up.
* Select **Ease Out Only** to have the animation slow down at the end of its sequence.
* Select **Ease In-Out** to have the animation start slowly, speed up, and then end slowly. **Ease In-Out** is a combination of **Ease In** and **Ease Out**.
> [!NOTE]
> Easing varies the speed of the animation to make it seem more natural and organic.
1. In the **Loop Mode** dropdown, select if and how the animation repeats after its initial sequence finishes:
* Select **Once** to play the animation once.
* Select **Loop Continuous** to restart the animation from the beginning after it finishes.
* Select **Ease In Then Continuous** to have the animation start slowly and then restart from its beginning after it finishes. If **Ease In Only** looping is set, then the easing applies only to the first animation loop.
* Select **Ping Pong** to have the animation play in reverse after it finishes. The animation plays repeatedly.
## Additional resources
* [Animate a GameObject along a spline](animate-spline.md)
* [Change the alignment of an animated GameObject](animate-alignment.md)
* [Spline Animate component reference](animate-component.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,23 @@
# Animate a GameObject along a spline
Move a GameObject along a spline.
Use the [Spline Animate](animate-component.md) component to define the movement of cameras, characters, or other GameObjects in the Editor.
You must have a GameObject with a **Spline** component attached to it in your scene to select as the target spline for the **Spline Animate** component.
By default, the **Spline Animate** component uses the **Time** method to animate a GameObject with set to complete after 1 second. To change what animation method your GameObject uses and how it moves along its target spline, refer to **[Configure the movement of a GameObject](animate-movement.md)**.
To animate a GameObject along a spline:
1. Add the **Spline Animate** component to a GameObject that you want to animate along a spline.
1. In the **Spline Animate** component, for the **Spline** property, select a GameObject that has an attached Spline component that you want to animate on.
1. To view the animation in the Scene view, select **Play** in the **Spline Animate** component's **Preview** panel.
## Additional resources
* [Control the animated GameObject's movement](animate-movement.md)
* [Change the alignment of an animated GameObject](animate-alignment.md)
* [Spline Animate component reference](animate-component.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,13 @@
# Animate along a spline
Move a GameObject along a spline.
To refer to a sample scene that demonstrates how the **Spline Animate** component can animate the position and rotation of a GameObject along a spline, [import the **Animate Component** sample scene](index.md#import-splines-samples).
| **Topic** | **Description** |
| :-------------------- | :----------------------- |
| **[Animate a GameObject along a spline](animate-spline.md)** | Use the Spline Animate component to move a GameObject along a spline. |
| **[Change the alignment of an animated GameObject](animate-alignment.md)** | Specify how a GameObject orients and aligns to the spline it's animated on. |
| **[Configure the movement of a GameObject](animate-movement.md)** | Control how an animation begins on a spline, the speed of the animation, and the animation method it uses. |
| **[Spline Animate component reference](animate-component.md)** | Understand the properties of the Spline Animate component. |

View File

@@ -0,0 +1,62 @@
## Quick Start
Splines are defined as implementing the `ISpline` interface. There are two default implementations: a mutable `Spline` class and an immutable `NativeSpline`.
To see more examples of common API use cases, [import the Splines package samples](https://docs.unity3d.com/Packages/com.unity.splines@latest/index.html?subfolder=/manual/index.html%23import-splines-samples) and review the `Runtime/SplineCommonAPI.cs` class.
Splines are represented in the scene using the `SplineContainer` MonoBehaviour.
```cs
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
class CreateSplineAtStart : MonoBehaviour
{
void Start()
{
// Add a SplineContainer component to this GameObject.
var container = gameObject.AddComponent<SplineContainer>();
// Create a new Spline on the SplineContainer.
var spline = container.AddSpline();
// Set some knot values.
var knots = new BezierKnot[3];
knots[0] = new BezierKnot(new float3(0f, 0f, 0f));
knots[1] = new BezierKnot(new float3(1f, 1f, 0f));
knots[2] = new BezierKnot(new float3(2f, -1f, 0f));
spline.Knots = knots;
}
}
```
The Splines package provides Editor tools for the `SplineContainer` component and `Spline` objects. Other `ISpline` and `ISplineContainer` derived types cannot be edited by the default tools.
Use `SplineUtility` to extract information from `ISpline` objects. For example, use `SplineUtility` to get a position at some interpolation.
```cs
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
[RequireComponent(typeof(SplineContainer))]
class EvaluateSpline : MonoBehaviour
{
[SerializeField]
float m_Interpolation = .5f;
[SerializeField]
GameObject m_Prefab;
void Start()
{
Spline spline = GetComponent<SplineContainer>()[0];
SplineUtility.Evaluate(spline, m_Interpolation, out float3 position, out float3 direction, out float3 up);
var rotation = Quaternion.LookRotation(direction, up);
Instantiate(m_Prefab, position, rotation);
}
}
```

View File

@@ -0,0 +1,29 @@
# Create a spline
Splines are paths made up of control points called knots. Segments connect knots to other knots. You can place knots onto the surface of objects or [align them to the grid](https://docs.unity3d.com/Manual/GridSnapping.html). Knots can include tangents which control the curvature of the segment at that knot.
## Create a new spline
To create a spline:
> [!NOTE]
> Before you add a knot to a spline, the spline's default location is 0, 0, 0. After you create the spline's first knot, the spline takes that knot's location.
1. Do one of the following:
* In the **Scene** view, from the **Tools** overlay, select **Create Spline**.
* Go to **GameObject** &gt; **Spline** &gt; **Draw Splines Tool**.
* In the [Hierarchy window](xref:Hierarchy), right-click and select **Spline** &gt; **Draw Splines Tool**.
1. Click in the [Scene view](xref:UsingTheSceneView) to create knots for the spline's path to follow. If you want to add a curve to the knot's segment, click and drag to create a knot with tangents.
> [!TIP]
> When you use the Draw Splines Tool, if you click on the surface of a GameObject, the knot snaps to that surface. Otherwise, the knot snaps to the grid.
1. To exit the **Draw Splines Tool**, select a tool in the **Tools** overlay or press Escape.
## Add to an existing spline container
To add a spline to a GameObject's existing spline container:
1. In the **Scene** view, select your spline GameObject.
1. From the **Tools** overlay, select the **Spline** context.
1. From the **Tools** overlay, select **Create Spline**.
1. Create a new spline. It's added to the existing spline container as a new spline. You can attach it to a knot in the existing spline, or leave it disconnected.

View File

@@ -0,0 +1,28 @@
# Element Inspector overlay reference
Use the Element Inspector overlay to configure knots, tangents, and the flow of a spline.
To access the Element Inspector overlay, select knots or tangents on splines in the Scene view.
| **Property** | **Description** |
| :-------------------- | :-------------------------|
| **Knot position** | Set a knot's position. </br></br> This property is visible only when you select knots. |
| **Knot rotation** | Set a knot's rotation. </br></br> This property is visible only when you select knots. |
| **Linear** | Select the **[Linear tangent mode](tangent-modes.md#linear-tangent-mode)** for a knot's tangents. |
| **Auto** | Select the **[Auto tangent mode](tangent-modes.md#auto-tangent-mode)** for a knot's tangents. |
| **Bezier** | Select the **[Bezier tangent mode](tangent-modes.md#bezier-tangent-mode)** for a knot's tangents. |
| **Bezier** | Select the **Bezier** mode for a knot's tangents if the knot is in [Bezier tangent mode](tangent-modes.md#bezier-tangent-mode). </br> </br> The following Bezier modes are available: <ul><li> [**Mirrored**](tangent-modes.md#mirrored-bezier-mode) </li> <li> [**Continuous**](tangent-modes.md#continuous-bezier-mode) </li> <li> [**Broken**](tangent-modes.md#broken-bezier-mode) </li> </ul> |
| **In** | Set the magnitude and direction of the knot's **In** tangent. The **In** tangent defines the curvature of the segment that enters the knot. </br> </br> This property is visible only when you select knots. |
| **Out**| Set the magnitude and direction of the knot's **Out** tangent. The **Out** tangent defines the curvature of the segment that exits the knot. </br> </br> This property is visible only when you select knots. |
| **Magnitude** | Set the magnitude of a tangent. </br> </br> A tangent's magnitude determines how much that tangent affects the curvature of its respective segment. A high magnitude value increases a tangent's influence on a segment's curvature and a low value decreases it. </br> </br> This property is visible only when you select tangents. |
| **Direction** | Set the direction that a tangent points to from its knot. </br> </br> This property is visible only when you select tangents. |
## Additional resources
- [**Tangent modes**](tangent-modes.md)
- [**Link and unlink knots**](link-unlink-knots.md)
- [**Split knots**](split-knots.md)
- [**Join knots**](join-knots.md)
- [**Reverse the flow of a spline**](reverse-spline-flow.md)

View File

@@ -0,0 +1,58 @@
# Spline Extrude component reference
Use the Spline Extrude component to customize the geometry of the mesh you extrude on a spline.
**Note:** If you add Spline Extrude to a GameObject that already has a mesh, that mesh is replaced with a new one.
## Source Spline Container
Select a GameObject that has an attached Spline component you want to extrude a mesh on.
You can use a Spline as a source for the extruded mesh of one or more GameObjects. Edits you make to the source Spline then update those GameObjects. For example, if you want to create a street with a sidewalk, you can create a Spline for the street, and then use the street Spline as a source for a sidewalk GameObject. Any changes you make to the street layout update the sidewalk to match.
## Shape Extrude
| **Property** | | **Description** |
| --- | --- | --- |
| **Type** > **Circle**| | Create a shape with a round cross-section. |
| | **Circle Settings** > **Sides**| Increase to create a smoother surface. The minimum value is `2`. |
| **Type** > **Square** | | Create a shape with a square cross-section. |
| **Type** > **Road** | | Create a shape with a flat cross-section and a slight lip. |
| **Type** > **Spline Profile** | | Use a different spline as a template for the current spline. |
| | **Spline Profile Settings** > **Template** | Select the spline container you want to use as a template. |
| | **Spline Profile Settings** > **Spline Index** | If the template container has more than one spline, select the spline you want.|
| | **Spline Profile Settings** > **Side Count** | Increase to create a smoother surface. The minimum value is `2`. |
| | **Spline Profile Settings** > **Axis** | Which of the template spline axes to follow when drawing this spline. |
## Geometry
| **Property** | **Description** |
| --- | --- |
| **Auto Refresh Generation** | Allow the mesh to update at runtime if the spline changes. |
| **Frequency** | How many times a second to refresh the mesh. |
| **Radius** | Width of the extrusion, measured from the spline path. |
| **Segments Per Unit** | Increase the value to create smoother curves. |
| **Cap Ends** | Fill the ends of an open-ended mesh. |
| **Flip Normals** | Reveal the inside of the 3D shape. |
## Advanced
| **Property** | **Description** |
| --- | --- |
| **Range** | To extrude only some of the spline, define a range. `0` is the first spline point, and `100` is the last one. |
| **Percentage** | Another way to control the **Range**. |
| **Update Colliders** | If the spline has a collider, update the collider to match the 3D shape as you change the extrusion properties. |
## Mesh Filter and Mesh Renderer components
When you add the Spline Extrude component to a GameObject, the Unity Editor adds two more components that all 3D GameObject need:
* [Mesh Renderer Component](https://docs.unity3d.com/Manual/class-MeshRenderer.html)
* [Mesh Filter Component](https://docs.unity3d.com/Manual/class-MeshFilter.html)
## Additional resources
* [Create a 3D mesh along a spline](extrude-mesh.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,16 @@
# Create a 3D mesh along a spline
Use the [Spline Extrude](extrude-component.md) component to extrude a mesh along a spline to create shapes such as tubes, wires, ropes, and noodles.
To create a 3D mesh along a spline:
1. [Create a spline](create-a-spline.md).
1. Add the **Spline Extrude** component to the spline GameObject. This component controls the properties of the extruded mesh. To customize the properties, refer to the [Spline Extrude component reference](extrude-component.md).
1. Adding a **Spline Extrude** component automatically adds a **Mesh Filter** and **Mesh Renderer** component to the GameObject. These components are necessary for all 3D GameObjects. Refer to [Mesh Renderer Component](https://docs.unity3d.com/Manual/class-MeshRenderer.html) and [Mesh Filter Component](https://docs.unity3d.com/Manual/class-MeshFilter.html) for more information.
## Additional resources
* [Create a spline](create-a-spline.md)
* [Spline Extrude component reference](extrude-component.md)
* [Use components](xref:UsingComponents)

View File

@@ -0,0 +1,10 @@
# Extrude a mesh along a spline
Extrude a mesh along a spline to create shapes such as tubes, wires, and roads.
To refer to a sample scene that demonstrates how the Spline Extrude component can extrude a mesh along a spline, [import the **Extrude Spline and Nearest Point** sample scene](index.md#import-splines-samples).
| **Topic** | **Description** |
| :-------------------- | :----------------------- |
| **[Create a 3D mesh along a spline](extrude-mesh.md)** | Use the Spline Extrude component to extrude a mesh along a spline. |
| **[Spline Extrude component reference](extrude-component.md)** | Understand the properties of the Spline Extrude component. |

View File

@@ -0,0 +1,15 @@
apiRules:
- exclude:
uidRegex: Tests$
type: Namespace
- exclude:
uidRegex: Debug$
type: Namespace
- exclude:
hasAttribute:
uid: System.ObsoleteAttribute
type: Member
- exclude:
hasAttribute:
uid: System.ObsoleteAttribute
type: Type

View File

@@ -0,0 +1,60 @@
# Getting started with Splines
## Creating the Asset
To create a spline game object there are three different methods.
In the Unity menu, go to **GameObject** > **Spline** > **New Spline**.
<br/><img src="images/getting-started-create-spline-unity-menu.png" alt="Create Spline, Unity Menu" width="350"/>
In the Hierarchy window. RMB > **Spline** > **New Spline**
<br/><img src="images/getting-started-create-spline-hierarchy.png" alt="Create Spline, Hierarchy window" width="350"/>
In the Inspector window, on a GameObject, **Add Component** > **Spline Container** (Script).
<br/><img src="images/getting-started-create-spline-inspector.png" alt="Create Spline, Inspector window" width="350"/>
For more information, see also [Spline Container](spline-container.md).
## Component Editor Tools
The **Knot Placement**, **Knot Move**, and the **Tangent Move** tools are available in the [Component Editor Tools](https://docs.unity3d.com/Manual/UsingCustomEditorTools.html#ToolModesAccessSceneViewPanel) overlay in the Scene window. The **Knot Placement** tool will be automatically engaged after the spline is created with the Unity menu or the Hierarchy window.
<br/><img src="images/getting-started-component-editor-tools.png" alt="Component editor tools" width="350"/>
### Knot Placement Tool
Use the **Knot Placement** tool to add knots. ![](images/KnotPlacementTool.png "Knot Placement Tool")
When the tool is engaged, you can place knots on a surface, such as, a Terrain object or a mesh face.
<br/><img src="images/getting-started-knot-placement.gif" alt="Point Placement" width="350"/>
Clicking on the first point will close the spline.
<br/><img src="images/getting-started-close-loop.gif" alt="Close Loop" width="350"/>
Placing knots not on a surface will place it on the grid instead.
Use **Ctrl** + **z** to delete the last created knot.
Use the **Esc** key to exit the **Knot Placement** creation.
### Knot Move Tool
Use the **Knot Move** tool to move knots.<img src="images/KnotMoveTool.png" alt="Knot Move Tool"/>
You can then select one or more knots to get a position handle to move them around.
<br/><img src="images/getting-started-knot-tool-move-handle.gif" alt="Knot Move Handle" width="350"/>
Clicking on the spline will create a new knot.
<br/><img src="images/getting-started-knot-tool-move-create.gif" alt="Knot Create" width="350"/>
Selecting a knot and then pressing the delete key will delete the knot.
<br/><img src="images/getting-started-knot-tool-move-delete.gif" alt="Knots Delete" width="350"/>
### Tangent Move Tool
Use the **Tangent Move** tool to move knot tangents. <img src="images/TangentMoveTool.png" alt="Tangent Move Tool"/>
This can tool can only be engaged when in the Inspector window on the Spline Container the Edit Mode Type property is set to **Bezier**.
<br/><img src="images/getting-started-knot-tool-tangent-type.png" alt="Edit Mode Type" width="350"/>
The tangent can be manipulated by moving the tangent handle.
<br/><img src="images/getting-started-knot-tool-tangent-handles.gif" alt="Tangent Movement" width="350"/>
Holding Shift while using a tangent handle will display the radial rotation gizmo.
<br/><img src="images/getting-started-knot-tool-tangent-shift.gif" alt="Tangent Shift Rotation" width="350"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -0,0 +1,32 @@
# About Splines
Work with curves and paths. Use the Splines package to generate objects and behaviors along paths, create trajectories, and draw shapes.
The Splines package contains:
* The tools to create and manipulate splines in the Unity Editor.
* The framework to customize the standard spline editing tools in this package.
* A standard data format and storage model for commonly used splines.
* Samples of implementations that address common spline use cases, such as creating a road, animating a GameObject's position and rotation along a spline, instantiating prefabs along a spline to create environments, and more.
## Supported Unity versions
Splines is supported in Unity version 2022.3 and later.
## Install Splines
Before you can use Splines, you must install the Splines package from the Package Manager.
To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Manual/upm-ui-install.html).
## Import Splines samples
The Splines package contains sample scenes that demonstrate different use cases for splines.
To import these sample scenes, you must install the Splines package.
To import sample scenes from the Splines package:
1. Go to **Window** > **Package Manager** to open the Package Manager.
1. In the Package Manager, select **Splines**.
1. Expand the **Samples** section.
1. Next to **Spline Examples**, select **Import**.

View File

@@ -0,0 +1,27 @@
# Spline Instantiate component reference
Use the Spline Instantiate component to instantiate GameObjects on a spline.
| **Property** | **Description** |
| :-------------------- | :------------------------ |
| **Container** | Select a GameObject that has an attached Spline component you want to instantiate GameObjects or prefabs on. |
| **Items To Instantiate** | Create a list of GameObjects or prefabs you want to instantiate. For each element in the list, select a GameObject or prefab, and set a probability for that item to instantiate on the spline. |
| **Up Axis** | Select the axis instantiated items use as their up direction. The y-axis is the default up direction. |
| **Forward Axis** | Select the axis instantiated items use as their forward direction. The z-axis is the default forward direction. |
| **Align To** | Select the space that instantiated items orient to. </br> The following spaces are available: </br> <ul> <li>**Spline Element**: Align instantiated items to an interpolated orientation calculated from the rotation of the knots closest to where each item instantiates. </li> <li>**Spline Object**: Align the instantiated items to the orientation of the target spline. </li> <li>**World Space**: Align instantiated items to world space orientation. </li> </ul> |
| **Instantiate Method** | Select how to instantiate items on the spline. </br> The following instantiate methods are available: <ul> <li> **Instance Count**: Instantiate a number of items on the target spline. </li> <li> **Spline Distance**: Instantiate items separated by distance intervals measured along the spline. </li> <li>**Linear Distance**: Instantiate items separated by distance intervals measured linearly in world space. </li></ul>|
| **Count** | Set a number or an inclusive random range of items to instantiate. </br> This property is visible only when you select the **Instance Count** instantiate method. |
| **Spacing (Spline)** | Set the distance interval to instantiate items at. The distance is measured along the spline. You can set an exact distance or an inclusive random range of values.</br> This property is visible only when you select the **Spline Distance** instantiate method. |
| **Spacing (Linear)** | Set the distance interval to instantiate items at. The distance is measured linearly in world space. </br> To instantiate as many items that can fit on the spline without overlap, select **Auto**. </br> This property is visible only when you select the **Linear Distance** instantiate method. |
| **Position Offset** | Enable to instantiate items at a position relative to the spline. |
| **Rotation Offset** | Enable to instantiate items with a rotation relative to the original GameObject. |
| **Scale Offset** | Enable to instantiate items with a scale relative to the original GameObject. |
| **Override space** | Enable to apply the offset to a coordinate space you select. If you don't enable **Override space**, the offset applies to the coordinate space you set **Align to** to. </br> This property is visible only when you enable **Position Offset**, **Rotation Offset**, or **Scale Offset**. |
| **X** | Set the offset's x-axis value. You can set an exact value or an inclusive random range of values. </br> This property is visible only when you enable **Position Offset**, **Rotation Offset**, or **Scale Offset**. |
| **Y** | Set the offset's y-axis value. You can set an exact value or an inclusive random range of values. </br> This property is visible only when you enable **Position Offset**, **Rotation Offset**, or **Scale Offset**. |
| **Z** | Set the offset's z-axis value. You can set an exact value or an inclusive random range of values. </br> This property is visible only when you enable **Position Offset**, **Rotation Offset**, or **Scale Offset**. |
| **Auto Refresh Generation** | Enable the automatic regeneration of instantiated items when you change the spline or its instantiation values. |
| **Randomize** | Regenerate all values set to random ranges, and then instantiate items again. |
| **Regenerate** | Instantiate items on the spline again. |
| **Clear** | Remove all instantiated items from the spline. |

View File

@@ -0,0 +1,14 @@
# Join splines
Join knots to connect the ends of two splines to each other.
When you join two knots, the new, joined knot takes the position of the active knot. The active knot is the last knot you selected. To join knots, both knots must each have only one segment and be part of different splines attached to the same Spline component.
If you join the knots of two splines that flow in different directions, the new spline takes the direction of the active knot.
To join two splines:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select two knots that are from different splines and have only one segment each.
1. In the Scene view, right-click to open the context menu.
1. In the Scene view context menu, and select **Join Knots**.

View File

@@ -0,0 +1,11 @@
# Knots
Knots are connected control points that make up splines. Knots connect to each other with segments. A knot can have a tangent that controls the curvature of the segments that knot connects to.
Use knots to change the shape of a spline. For more information, refer to [Manipulate splines](manipulate-splines.md).
| **Topic** | **Description** |
| :-------------------- | :----------------------- |
| [**Link and unlink knots**](link-unlink-knots.md) | Set knots to the same position and link them. When a linked knot moves, all the knots it links to also move.|
| [**Split knots**](split-knots.md)| Divide a knot with two segments into two knots.|

View File

@@ -0,0 +1,50 @@
# Link and unlink knots
You can link knots from splines attached to the same Spline component. Linked knots share a position. When you move a knot, its linked knots also move.
Use linked knots to create a spline with branched paths. For example, you can create a spline that represents the following:
- A road with merging lanes
- A river with tributaries
- A diverging sets of mountain paths.
You can link knots in the Scene view context menu or with the **Draw Splines Tool** in the [Scene view](xref:UsingTheSceneView). Use the Scene view context menu to unlink knots.
> [!NOTE]
> You can link more than two knots to each other. If you link knots that are already linked to other knots, all the knots link to each other.
## Link knots with the Draw Splines Tool
To create a linked knot with the **Draw Splines Tool**:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. In the Tools overlay, select the **Draw Splines Tool**.
1. Select a knot that has two segments to create a knot that links to it.
The new knot is the first knot of a new spline.
> [!NOTE]
> If you use the **Draw Splines Tool** to create a knot on a linked knot, then that new knot is added to the existing link.
## Link knots in the Scene view context menu
To link knots in the Scene view context menu:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select at least two knots.
1. In the Scene view, right-click to open the context menu.
1. In the Scene view context menu, select **Link Knots**.
## Unlink knots
To unlink knots:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select a linked knot.
1. In the Scene view, right-click to open the context menu.
1. In the Scene view context menu, select **Unlink Knots**.
## Additional resources
* [Knots](knots.md)

View File

@@ -0,0 +1,44 @@
# Manipulate splines
You can move, rotate, and scale splines like other GameObjects. Refer to [Positioning GameObjects](xref:PositioningGameObjects) in the Unity User Manual to learn more.
Knots and tangents determine a spline's path and shape. To change a spline's path or shape, manipulate a spline's knots and tangents.
## Select knots or tangents in the Scene view
Activate the **Spline** tool context to use the Move, Rotate, and Scale tools on knots or tangents in the Scene view. Once your active tool context is **Spline**, a spline's knots and tangents are visible and you can select them in the Scene view. When your active tool context is **Spline**, you can't select GameObjects that aren't splines in the Scene view.
To select the knots or tangents of a spline in the Scene view:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. In the Scene view, select a knot or tangent.
1. To select multiple knots and tangents, do one of the following:
* Click and drag to draw a box over multiple knots and tangents.
* Hold Shift and then click the knots or tangents you want to select.
## Toggle tool handle positions with splines
You can toggle tool handle positions for knots and tangents like you would with other GameObjects. When you have multiple knots and tangents selected, a tool's handle position affects the behavior of some transform tools, such as the **Rotate** and **Scale** tools.
Use the tool handle position toggles in the [Tool Settings Overlay](https://docs.unity3d.com/Documentation/Manual/overlays.html) to select the following tool handle positions for knots and tangents:
* **Pivot**: Set the tool handle at the active element's pivot point. The active element is the last item you selected. If you select multiple knots or tangents:
* The **Rotate** tool rotates the active element around its own pivot point and then applies that same rotation to the other knots in the selection.
* The **Scale** tool scales the knots and tangents from each element's own pivot point.
* **Center**: Set the tool handle at the center of a selection. If you select multiple knots or tangents:
* The **Rotate** tool rotates the knots or tangents around a handle centered between the selected elements.
* The **Scale** tool scales the knots or tangents from a handle centered between the selected elements.
## Toggle tool handle rotation with splines
You can toggle tool handle rotations for knots and tangents like you would with other GameObjects. Besides the default tool handle rotation settings, **Global** and **Local**, knots and tangents have the **Parent** and **Element** handle rotations. When you have multiple knots and tangents selected, a tool's handle rotation setting affects the behavior of some transform tools, such as the **Rotate** and **Scale** tools.
Use the tool handle rotation position toggles in the [Tool Settings Overlay](xref:overlays) to select the following tool handle rotation positions for knots and tangents:
* **Global**: Clamp a spline element to world space orientation.
* **Local**: Keep a spline element's rotation relative to its parent spline.
* **Parent**: Set spline elements to take their parent element's orientation. For example, a tangent with its tool handle rotation set to **Parent** keeps its orientation relative to its parent knot. A knot with its tool handle rotation set to **Parent** keeps its orientation relative to its parent spline GameObject.
* **Element**: Set the tool handle to the active element's orientation.

View File

@@ -0,0 +1,24 @@
# Reverse the flow of a spline
Reverse the flow direction of a spline.
Each spline has a flow direction that moves from its first knot to its last knot. Arrows on the segments and knots of a spline indicate the flow direction of the spline.
If you reverse the flow of a spline, you create a spline that mirrors the original spline. The following occurs:
* The knot indices of the spline reverse. For example, the first knot of the original spline becomes the last knot of the reversed spline.
* For knots in the [Bezier tangent mode](tangent-modes#bezier-tangent-mode), the In tangent becomes the Out tangent and the Out tangent becomes the In tangent.
To reverse the flow of a spline:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select a knot on the spline.
1. In the Scene view, right-click to open the context menu.
1. In the Scene view context menu, select **Reverse Spline Flow**.
## Additional resources
* [Knots](knots.md)
* [Tangent modes](tangent-modes.md)

View File

@@ -0,0 +1,11 @@
# Select a default tangent mode
You can select a [tangent mode](tangent-modes.md) for the **Draw Splines Tool** to use by default.
In each tangent mode, you can click and drag to create a knot in the **[Mirrored Bezier mode](tangent-modes.md#mirrored-bezier-mode)**.
Select a default [tangent mode](tangent-modes.md) for the **Draw Splines Tool**:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. In the Tools overlay, select the **Draw Splines Tool**.
1. In the Tools Settings overlay, select **[Auto](tangent-modes.md#auto-tangent-mode)** or **[Linear](tangent-modes.md#linear-tangent-mode)** from the dropdown menu.

View File

@@ -0,0 +1,9 @@
# Select tangent mode
You can select a [tangent mode](tangent-modes.md) for a knot. The tangent mode determines the curve between selected knots or all the knots on a selected spline.
To select a tangent mode:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select the knots or tangents that you want to select a tangent mode for.
1. In the **Element Inspector** overlay, select a [tangent mode](tangent-modes.md).

View File

@@ -0,0 +1,5 @@
---
title: select-spline
---
In the [Hierarchy window](xref:Hierarchy) or [Scene view](xref:UsingTheSceneView), select a GameObject with a Spline component.

View File

@@ -0,0 +1,5 @@
---
title: set-spline-context
---
In the [Tools overlay](xref:overlays), set the tool context to **Spline**.

View File

@@ -0,0 +1,13 @@
# Spline component reference
The Spline component attaches splines to a GameObject. The Spline component can contain an unlimited number of splines.
You can use the Spline component to manipulate the transform values of a spline's knots and tangents.
| **Property** | **Description** |
| :-------------------- | :-------------------------|
| **Spline** | Contains the knots that make up a spline. |
| **Closed** | Create a segment between the first and last knots of this spline.|
| **Knot** | Set the position, rotation, and tangents of a knot.|

View File

@@ -0,0 +1,16 @@
# Split knots
Split knots to separate a spline into two splines.
You can only split a knot that has two segments. Split knots each have one segment.
> [!NOTE]
> If you split a knot on a closed spline, the knot where the closure is becomes two [linked knots](link-unlink-knots.md) in two separate splines. These linked knots keep the shape of the original spline.
To split a knot:
1. [!include[select-spline](.\\snippets\\select-spline.md)]
1. [!include[set-spline-context](.\\snippets\\set-spline-context.md)]
1. Select a knot with two segments.
1. In the Scene view, right-click to open the context menu.
1. In the Scene view context menu, select **Split Knot**.

View File

@@ -0,0 +1,69 @@
# Tangent modes
You can [select a tangent mode](select-tangent-mode.md) for knots that determines how their tangents are calculated.
Knots support the following tangent modes:
* [Linear](#linear-tangent-mode)
* [Auto](#auto-tangent-mode)
* [Bezier](#bezier-tangent-mode)
## Linear tangent mode
Use the **Linear** tangent mode to create a spline with straight lines or sharp corners.
The **Linear** tangent mode sets a knot's tangents to a length of `0` so that they point directly at the preceding and following knots.
In **Linear** mode, tangents are automatically computed and cannot be directly manipulated in the Scene view.
## Auto tangent mode
Use the **Auto** mode to create splines with smooth curves.
The **Auto** tangent mode calculates a knot's tangents based on the positions of its preceding and following knots. When you create a new knot on a spline in **Auto** mode, the preceding knot's segment curve adjusts according to the position of the new knot. If you rotate a knot in **Auto** mode, its tangents do not rotate with it.
In **Auto** mode, tangents are automatically computed and cannot be directly manipulated in the Scene view.
> [!NOTE]
> The **Auto** tangent mode creates Catmull-Rom splines.
## Bezier tangent mode
Use the **Bezier** tangent mode to create splines with tangents you can directly manipulate and modify in the Scene view.
You can select the following **Bezier** modes for knots in the **Bezier** tangent mode:
* [**Mirrored**](#mirrored-bezier-mode)
* [**Continuous**](#continuous-bezier-mode)
* [**Broken**](#broken-bezier-moode)
### Mirrored Bezier mode
Set a knot's tangents to point in opposite directions and have equal lengths.
A knot in **Mirrored** mode always points to its **Out** tangent. If you move tangents in **Mirrored** mode, the parent knot rotates to point to its **Out** tangent. If you rotate a knot in **Mirrored** mode, its tangents rotate with it.
> [!NOTE]
> For splines with non-uniform scaling, a knot in **Mirrored** mode might not point to its **Out** tangent. Non-uniform scaling is when the Scale in a Transform has different values for the x-axis, y-axis, and z-axis. For example, a spline with Scale values of (1 , 5, 10) has non-uniform scaling.
If you select a tangent and set it to **Mirrored** mode, it mirrors the opposite tangent. For example, if you set an Out tangent to **Mirrored**, the In tangent's length and direction change, but the Out tangent's length and direction do not change.
### Continuous Bezier mode
Align a knot's tangents so they always point in opposite directions. The length of tangents in **Continuous** mode are independent of each other and you can set them to different values.
A knot in **Continuous** mode always points to its **Out** tangent. If you move tangents in **Continuous** mode, the parent knot rotates to point to its **Out** tangent. If you rotate a knot in **Continuous** mode, its tangents rotate with it.
> [!NOTE]
> For splines with non-uniform scaling, a knot in **Continuous** mode might not point to its **Out** tangent. Non-uniform scaling is when the Scale in a Transform has different values for the x-axis, y-axis, and z-axis. For example, a spline with Scale values of (1 , 5, 10) has non-uniform scaling.
If you select a tangent and set it to **Continuous** mode, it aligns with the opposite tangent. For example, if you set an Out tangent to **Continuous**, the In tangent's direction changes, but the Out tangent's direction does not change.
### Broken Bezier mode
Dissociate a knot's tangents from each other. Use the **Broken** mode to directly manipulate each tangent's length and direction.
If you rotate a knot in **Broken** mode, its tangents rotate with it.

View File

@@ -0,0 +1,13 @@
# Tangents
Tangents define the in and out curvature of a knot's segments.
Tangents have a length and direction. Length determines how much that tangent affects the curvature of its respective segment. A large length value increases a tangent's influence on a segment's curvature and a small length decreases it. Direction determines where the tangent points to from its parent knot.
A spline or knot's tangent mode determines how their tangents are calculated. If a knot is in the **Bezier** tangent mode, then you can directly manipulate its tangents in the Scene view. If a knot is in the [Auto](tangent-modes.md#auto-tangent-mode) or [Linear](tangent-modes.md#linear-tangent-mode) tangent mode, its tangents are calculated automatically.
| **Topic** | **Description** |
| :-------------------- |:---------------------------------------------------------------------|
| **[Tangent modes](tangent-modes.md)** | Understand the different tangent modes. |
| **[Select a tangent mode](select-tangent-mode.md)**| Select a tangent mode for a knot. |
| **[Select a default tangent mode](select-default-tangent.md)**| Select the default tangent mode that the **Draw Splines Tool** uses. |

View File

@@ -0,0 +1,3 @@
# Splines upgrade guide
This is a new package release. In future package versions, this page will display a list of the actions you need to take to upgrade your project to that version.

View File

@@ -0,0 +1,3 @@
# What's new
This is a new package release. In future package versions, this page will display a summary of updates and changes for that version.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7be002d6127770a45b33b37bd3ba98ac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Tests")]
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Tests")]
[assembly: InternalsVisibleTo("Unity.Splines.Editor.Debug")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c854ca5f1d7eed04aa92565d35431e96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e88477d7eedfb4ca8b5d8912575efa76
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,352 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Splines;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace UnityEditor.Splines
{
[CustomEditor(typeof(SplineAnimate))]
[CanEditMultipleObjects]
class SplineAnimateEditor : UnityEditor.Editor
{
List<VisualElement> m_Roots = new ();
List<Slider> m_ProgressSliders = new ();
List<FloatField> m_ElapsedTimeFields = new ();
List<EnumField> m_ObjectForwardFields = new ();
List<EnumField> m_ObjectUpFields = new ();
SerializedProperty m_MethodProperty;
SerializedProperty m_ObjectForwardProperty;
SerializedProperty m_ObjectUpProperty;
SerializedProperty m_StartOffsetProperty;
SerializedObject m_TransformSO;
SplineAnimate m_SplineAnimate;
const string k_UxmlPath = "Packages/com.unity.splines/Editor/Resources/UI/UXML/splineanimate-inspector.uxml";
static VisualTreeAsset s_TreeAsset;
static StyleSheet s_ThemeStyleSheet;
SplineAnimate[] m_Components;
void OnEnable()
{
m_SplineAnimate = target as SplineAnimate;
if (m_SplineAnimate == null)
return;
m_SplineAnimate.Updated += OnSplineAnimateUpdated;
try {
m_MethodProperty = serializedObject.FindProperty("m_Method");
m_ObjectForwardProperty = serializedObject.FindProperty("m_ObjectForwardAxis");
m_ObjectUpProperty = serializedObject.FindProperty("m_ObjectUpAxis");
m_StartOffsetProperty = serializedObject.FindProperty("m_StartOffset");
}
catch (Exception)
{
return;
}
m_TransformSO = new SerializedObject(m_SplineAnimate.transform);
m_Components = targets.Select(x => x as SplineAnimate).Where(y => y != null).ToArray();
foreach (var animate in m_Components)
{
if (animate.Container != null)
animate.RecalculateAnimationParameters();
}
m_Roots.Clear();
m_ObjectForwardFields.Clear();
m_ObjectUpFields.Clear();
m_ProgressSliders.Clear();
m_ElapsedTimeFields.Clear();
EditorApplication.update += OnEditorUpdate;
Spline.Changed += OnSplineChange;
SplineContainer.SplineAdded += OnContainerSplineSetModified;
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
}
void OnDisable()
{
if(m_SplineAnimate != null)
m_SplineAnimate.Updated -= OnSplineAnimateUpdated;
if (!EditorApplication.isPlaying)
{
foreach (var animate in m_Components)
{
if (animate.Container != null)
{
animate.RecalculateAnimationParameters();
animate.Restart(false);
}
}
}
EditorApplication.update -= OnEditorUpdate;
Spline.Changed -= OnSplineChange;
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
}
void OnEditorUpdate()
{
if (!EditorApplication.isPlaying)
{
if (m_SplineAnimate.Container != null && m_SplineAnimate.IsPlaying)
{
m_SplineAnimate.Update();
RefreshProgressFields();
}
}
else if(m_SplineAnimate.IsPlaying)
RefreshProgressFields();
}
void OnSplineChange(Spline spline, int knotIndex, SplineModification modificationType)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var animate in m_Components)
{
if (animate.Container != null && animate.Container.Splines.Contains(spline))
animate.RecalculateAnimationParameters();
}
}
void OnContainerSplineSetModified(SplineContainer container, int spline)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var animate in m_Components)
{
if (animate.Container == container)
animate.RecalculateAnimationParameters();
}
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
if (s_TreeAsset == null)
s_TreeAsset = (VisualTreeAsset)AssetDatabase.LoadAssetAtPath(k_UxmlPath, typeof(VisualTreeAsset));
s_TreeAsset.CloneTree(root);
if (s_ThemeStyleSheet == null)
s_ThemeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"Packages/com.unity.splines/Editor/Stylesheets/SplineAnimateInspector{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss");
root.styleSheets.Add(s_ThemeStyleSheet);
var methodField = root.Q<PropertyField>("method");
methodField.RegisterValueChangeCallback((_) => { RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex); });
RefreshMethodParamFields((SplineAnimate.Method)m_MethodProperty.enumValueIndex);
var objectForwardField = root.Q<EnumField>("object-forward");
objectForwardField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectForwardProperty, m_ObjectUpProperty));
var objectUpField = root.Q<EnumField>("object-up");
objectUpField.RegisterValueChangedCallback((evt) => OnObjectAxisFieldChange(evt, m_ObjectUpProperty, m_ObjectForwardProperty));
var playButton = root.Q<Button>("play");
playButton.SetEnabled(!EditorApplication.isPlaying);
playButton.clicked += OnPlayClicked;
var pauseButton = root.Q<Button>("pause");
pauseButton.SetEnabled(!EditorApplication.isPlaying);
pauseButton.clicked += OnPauseClicked;
var resetButton = root.Q<Button>("reset");
resetButton.SetEnabled(!EditorApplication.isPlaying);
resetButton.clicked += OnResetClicked;
var progressSlider = root.Q<Slider>("normalized-progress");
progressSlider.SetEnabled(!EditorApplication.isPlaying);
progressSlider.RegisterValueChangedCallback((evt) => OnProgressSliderChange(evt.newValue));
var elapsedTimeField = root.Q<FloatField>("elapsed-time");
elapsedTimeField.SetEnabled(!EditorApplication.isPlaying);
elapsedTimeField.RegisterValueChangedCallback((evt) => OnElapsedTimeFieldChange(evt.newValue));
var startOffsetField = root.Q<PropertyField>("start-offset");
startOffsetField.RegisterValueChangeCallback((evt) =>
{
m_SplineAnimate.StartOffset = m_StartOffsetProperty.floatValue;
if (!EditorApplication.isPlayingOrWillChangePlaymode)
{
m_SplineAnimate.Restart(false);
OnElapsedTimeFieldChange(elapsedTimeField.value);
}
});
m_Roots.Add(root);
m_ProgressSliders.Add(progressSlider);
m_ElapsedTimeFields.Add(elapsedTimeField);
m_ObjectForwardFields.Add(objectForwardField);
m_ObjectUpFields.Add(objectUpField);
return root;
}
void RefreshMethodParamFields(SplineAnimate.Method method)
{
foreach (var root in m_Roots)
{
var durationField = root.Q<PropertyField>("duration");
var maxSpeedField = root.Q<PropertyField>("max-speed");
if (method == (int)SplineAnimate.Method.Time)
{
durationField.style.display = DisplayStyle.Flex;
maxSpeedField.style.display = DisplayStyle.None;
}
else
{
durationField.style.display = DisplayStyle.None;
maxSpeedField.style.display = DisplayStyle.Flex;
}
}
}
void RefreshProgressFields()
{
for (int i = 0; i < m_ProgressSliders.Count && i < m_ElapsedTimeFields.Count; ++i)
{
var progressSlider = m_ProgressSliders[i];
var elapsedTimeField = m_ElapsedTimeFields[i];
if (progressSlider == null || elapsedTimeField == null)
continue;
progressSlider.SetValueWithoutNotify(m_SplineAnimate.GetLoopInterpolation(false));
elapsedTimeField.SetValueWithoutNotify(m_SplineAnimate.ElapsedTime);
}
}
void OnProgressSliderChange(float progress)
{
m_SplineAnimate.Pause();
m_SplineAnimate.NormalizedTime = progress;
RefreshProgressFields();
}
void OnElapsedTimeFieldChange(float elapsedTime)
{
m_SplineAnimate.Pause();
m_SplineAnimate.ElapsedTime = elapsedTime;
RefreshProgressFields();
}
void OnObjectAxisFieldChange(ChangeEvent<Enum> changeEvent, SerializedProperty axisProp, SerializedProperty otherAxisProp)
{
if (changeEvent.newValue == null)
return;
var newValue = (SplineAnimate.AlignAxis)changeEvent.newValue;
var previousValue = (SplineAnimate.AlignAxis)changeEvent.previousValue;
// Swap axes if the picked value matches that of the other axis field
if (newValue == (SplineAnimate.AlignAxis)otherAxisProp.enumValueIndex)
{
otherAxisProp.enumValueIndex = (int)previousValue;
serializedObject.ApplyModifiedProperties();
}
// Prevent the user from configuring object's forward and up as opposite axes
if (((int) newValue) % 3 == otherAxisProp.enumValueIndex % 3)
{
axisProp.enumValueIndex = (int)previousValue;
serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
foreach (var objectForwardField in m_ObjectForwardFields)
objectForwardField.SetValueWithoutNotify((SplineComponent.AlignAxis)m_ObjectForwardProperty.enumValueIndex);
foreach (var objectUpField in m_ObjectUpFields)
objectUpField.SetValueWithoutNotify((SplineComponent.AlignAxis)m_ObjectUpProperty.enumValueIndex);
}
void OnPlayClicked()
{
if (!m_SplineAnimate.IsPlaying)
{
m_SplineAnimate.RecalculateAnimationParameters();
if (m_SplineAnimate.NormalizedTime == 1f)
m_SplineAnimate.Restart(true);
else
m_SplineAnimate.Play();
}
}
void OnPauseClicked()
{
m_SplineAnimate.Pause();
}
void OnResetClicked()
{
m_SplineAnimate.RecalculateAnimationParameters();
m_SplineAnimate.Restart(false);
RefreshProgressFields();
}
void OnSplineAnimateUpdated(Vector3 position, Quaternion rotation)
{
if (m_SplineAnimate == null)
return;
if (!EditorApplication.isPlaying)
{
m_TransformSO.Update();
var localPosition = position;
var localRotation = rotation;
if (m_SplineAnimate.transform.parent != null)
{
localPosition = m_SplineAnimate.transform.parent.worldToLocalMatrix.MultiplyPoint3x4(position);
localRotation = Quaternion.Inverse(m_SplineAnimate.transform.parent.rotation) * localRotation;
}
m_TransformSO.FindProperty("m_LocalPosition").vector3Value = localPosition;
m_TransformSO.FindProperty("m_LocalRotation").quaternionValue = localRotation;
m_TransformSO.ApplyModifiedProperties();
}
}
[DrawGizmo(GizmoType.Selected | GizmoType.Active)]
static void DrawSplineAnimateGizmos(SplineAnimate splineAnimate, GizmoType gizmoType)
{
if (splineAnimate.Container == null)
return;
const float k_OffsetGizmoSize = 0.15f;
splineAnimate.Container.Evaluate(splineAnimate.StartOffsetT, out var offsetPos, out var forward, out var up);
#if UNITY_2022_2_OR_NEWER
using (new Handles.DrawingScope(Handles.elementColor))
#else
using (new Handles.DrawingScope(SplineHandleUtility.knotColor))
#endif
if (Vector3.Magnitude(forward) <= Mathf.Epsilon)
{
if (splineAnimate.StartOffsetT < 1f)
forward = splineAnimate.Container.EvaluateTangent(Mathf.Min(1f, splineAnimate.StartOffsetT + 0.01f));
else
forward = splineAnimate.Container.EvaluateTangent(splineAnimate.StartOffsetT - 0.01f);
}
Handles.ConeHandleCap(-1, offsetPos, Quaternion.LookRotation(Vector3.Normalize(forward), up), k_OffsetGizmoSize * HandleUtility.GetHandleSize(offsetPos), EventType.Repaint);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7292ebc78c5d44eaa80048f8fd7d5fe6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using System;
using UnityEngine;
using UnityEditor;
class SplineComponentEditor : Editor
{
static GUIStyle s_FoldoutStyle;
internal static readonly string k_Helpbox = L10n.Tr("Instantiated Objects need a SplineContainer target to be created.");
protected bool Foldout(bool foldout, GUIContent content)
{
return Foldout(foldout, content, false);
}
public static bool Foldout(bool foldout, GUIContent content, bool toggleOnLabelClick)
{
if (s_FoldoutStyle == null)
{
s_FoldoutStyle = new GUIStyle(EditorStyles.foldout);
s_FoldoutStyle.fontStyle = FontStyle.Bold;
}
return EditorGUILayout.Foldout(foldout, content, toggleOnLabelClick, s_FoldoutStyle);
}
internal struct LabelWidthScope : IDisposable
{
float previousWidth;
public LabelWidthScope(float width)
{
previousWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = width;
}
public void Dispose()
{
EditorGUIUtility.labelWidth = previousWidth;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9103eb7dcd041455886438d75625d19c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
// Multi-object selection is not supported
[CustomEditor(typeof(SplineContainer))]
class SplineContainerEditor : UnityEditor.Editor
{
SerializedProperty m_SplineProperty;
SerializedProperty splinesProperty => m_SplineProperty ??= serializedObject.FindProperty("m_Splines");
static GUIStyle s_HelpLabelStyle;
static GUIStyle HelpLabelStyle
{
get
{
if (s_HelpLabelStyle == null)
{
s_HelpLabelStyle = new GUIStyle(EditorStyles.helpBox);
s_HelpLabelStyle.padding = new RectOffset(2, 2, 2, 2);
}
return s_HelpLabelStyle;
}
}
static GUIContent m_HelpLabelContent;
const string k_HelpBoxIconPath = "SplineEditMode-Info";
static GUIContent m_HelpLabelContentIcon;
const string k_ComponentMessage = "Use the Spline Edit Mode in the Scene Tools Overlay to edit this Spline.";
public void OnEnable()
{
m_HelpLabelContent = EditorGUIUtility.TrTextContent(k_ComponentMessage);
m_HelpLabelContentIcon = new GUIContent(PathIcons.GetIcon(k_HelpBoxIconPath));
Undo.undoRedoPerformed += UndoRedoPerformed;
}
public void OnDisable()
{
Undo.undoRedoPerformed -= UndoRedoPerformed;
}
void UndoRedoPerformed()
{
foreach (var t in targets)
{
var container = t as SplineContainer;
if (container != null)
{
container.ClearCaches();
foreach (var spline in container.Splines)
spline.SetDirty(SplineModification.Default);
}
}
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// [SPLB-132] Reverting to custom helpbox as the default helpbox style as a trouble to handle custom icons
// when using a screen with PixelPerPoints different than 1. This is done in trunk by setting the
// Texture2d.pixelsPerPoints which is an internal property than cannot be access from here.
EditorGUILayout.BeginHorizontal(HelpLabelStyle);
EditorGUIUtility.SetIconSize(new Vector2(32f, 32f));
EditorGUILayout.LabelField(m_HelpLabelContentIcon,
GUILayout.Width(34), GUILayout.MinHeight(34), GUILayout.ExpandHeight(true));
EditorGUIUtility.SetIconSize(Vector2.zero);
EditorGUILayout.LabelField(m_HelpLabelContent,
new GUIStyle(EditorStyles.label){wordWrap = HelpLabelStyle.wordWrap, fontSize = HelpLabelStyle.fontSize, padding = new RectOffset(-2, 0, 0, 0)},
GUILayout.ExpandHeight(true));
EditorGUILayout.EndHorizontal();
SplineReorderableList.Get(splinesProperty).DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
bool HasFrameBounds()
{
foreach (var o in targets)
{
var target = (SplineContainer) o;
foreach (var spline in target.Splines)
if (spline.Count > 0)
return true;
}
return false;
}
Bounds OnGetFrameBounds()
{
List<SplineInfo> splines = new List<SplineInfo>();
EditorSplineUtility.GetSplinesFromTargets(targets, splines);
return EditorSplineUtility.GetBounds(splines);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9b17d23c06da64c139386c53a0b59281
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,304 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Splines;
using UnityEngine.Splines.ExtrusionShapes;
namespace UnityEditor.Splines
{
[CustomEditor(typeof(SplineExtrude))]
[CanEditMultipleObjects]
class SplineExtrudeEditor : SplineComponentEditor
{
SerializedProperty m_Container;
SerializedProperty m_RebuildOnSplineChange;
SerializedProperty m_RebuildFrequency;
SerializedProperty m_SegmentsPerUnit;
SerializedProperty m_Capped;
SerializedProperty m_Radius;
SerializedProperty m_Range;
SerializedProperty m_Shape;
SerializedProperty m_UpdateColliders;
SerializedProperty m_FlipNormals;
static readonly GUIContent k_RangeContent = new GUIContent(L10n.Tr("Range"), L10n.Tr("The section of the Spline to extrude."));
static readonly GUIContent k_AdvancedContent = new GUIContent(L10n.Tr("Advanced"), L10n.Tr("Advanced Spline Extrude settings."));
static readonly GUIContent k_PercentageContent = new GUIContent(L10n.Tr("Percentage"), L10n.Tr("The section of the Spline to extrude in percentages."));
static readonly GUIContent k_ShapeContent = new GUIContent(L10n.Tr("Shape Extrude"), L10n.Tr("Shape Extrude settings."));
static readonly GUIContent k_ShapeSettings = EditorGUIUtility.TrTextContent("Settings");
static readonly GUIContent k_GeometryContent = new GUIContent(L10n.Tr("Geometry"), L10n.Tr("Mesh Geometry settings."));
static readonly string k_SourceSplineContainer = L10n.Tr("Source Spline Container");
static readonly string k_CapEnds = L10n.Tr("Cap Ends");
static readonly string k_AutoRefreshGeneration = L10n.Tr("Auto Refresh Generation");
static readonly string k_To = L10n.Tr("to");
static readonly string k_From = L10n.Tr("from");
SplineExtrude[] m_Components;
bool m_AnyMissingMesh;
protected void OnEnable()
{
m_Container = serializedObject.FindProperty("m_Container");
m_RebuildOnSplineChange = serializedObject.FindProperty("m_RebuildOnSplineChange");
m_RebuildFrequency = serializedObject.FindProperty("m_RebuildFrequency");
m_SegmentsPerUnit = serializedObject.FindProperty("m_SegmentsPerUnit");
m_Capped = serializedObject.FindProperty("m_Capped");
m_Radius = serializedObject.FindProperty("m_Radius");
m_Range = serializedObject.FindProperty("m_Range");
m_UpdateColliders = serializedObject.FindProperty("m_UpdateColliders");
m_Shape = serializedObject.FindProperty("m_Shape");
m_FlipNormals = serializedObject.FindProperty("m_FlipNormals");
m_Components = targets.Select(x => x as SplineExtrude).Where(y => y != null).ToArray();
m_AnyMissingMesh = false;
EditorSplineUtility.AfterSplineWasModified += OnSplineModified;
SplineContainer.SplineAdded += OnContainerSplineSetModified;
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
}
void OnDisable()
{
EditorSplineUtility.AfterSplineWasModified -= OnSplineModified;
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
}
void OnSplineModified(Spline spline)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var extrude in m_Components)
{
if (extrude.Container != null && extrude.Splines.Contains(spline))
extrude.Rebuild();
}
}
void OnContainerSplineSetModified(SplineContainer container, int spline)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var extrude in m_Components)
{
if (extrude.Container == container)
extrude.Rebuild();
}
}
void SetShapeType(ShapeType type)
{
foreach (var extrude in m_Components)
{
if (ShapeTypeUtility.GetShapeType(extrude.Shape) == type)
continue;
Undo.RecordObject(extrude, "Set Extrude Shape");
extrude.Shape = ShapeTypeUtility.CreateShape(type);
m_Shape.isExpanded = true;
}
}
bool CanCapEnds()
{
foreach (var extrude in m_Components)
{
if (!extrude.CanCapEnds)
return false;
}
return true;
}
void SetRebuildOnSplineChange(bool value)
{
foreach (var extrude in m_Components)
{
Undo.RecordObject(extrude, "Set Rebuild on Spline Change.");
extrude.RebuildOnSplineChange = value;
}
}
void CreateMeshAssets(SplineExtrude[] components)
{
foreach (var extrude in components)
{
if (!extrude.TryGetComponent<MeshFilter>(out var filter) || filter.sharedMesh == null)
filter.sharedMesh = extrude.CreateMeshAsset();
}
m_AnyMissingMesh = false;
}
void Rebuild()
{
foreach (var extrude in m_Components)
extrude.Rebuild();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
m_AnyMissingMesh = m_Components.Any(x => x.TryGetComponent<MeshFilter>(out var filter) && filter.sharedMesh == null);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Container, new GUIContent(k_SourceSplineContainer, m_Container.tooltip));
if (m_Container.objectReferenceValue == null)
EditorGUILayout.HelpBox(k_Helpbox, MessageType.Warning);
// shape section
m_Shape.isExpanded = Foldout(m_Shape.isExpanded, k_ShapeContent, true);
if (m_Shape.isExpanded)
{
EditorGUI.indentLevel++;
EditorGUI.showMixedValue = m_Shape.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
var shapeType = ShapeTypeUtility.GetShapeType(m_Shape.managedReferenceValue);
shapeType = (ShapeType)EditorGUILayout.EnumPopup(L10n.Tr("Type"), shapeType);
if (EditorGUI.EndChangeCheck())
SetShapeType(shapeType);
EditorGUI.showMixedValue = false;
if (m_Shape.hasVisibleChildren)
EditorGUILayout.PropertyField(m_Shape, k_ShapeSettings, true);
EditorGUI.indentLevel--;
}
// https://unityeditordesignsystem.unity.com/patterns/content-organization recommends 8px spacing for
// vertical groups. padding already adds 4 so just nudge that up for a total of 8
EditorGUILayout.Space(4);
// geometry section
m_Radius.isExpanded = Foldout(m_Radius.isExpanded, k_GeometryContent, true);
if (m_Radius.isExpanded)
{
EditorGUI.indentLevel++;
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_RebuildOnSplineChange, new GUIContent(k_AutoRefreshGeneration, m_RebuildOnSplineChange.tooltip));
if (m_RebuildOnSplineChange.boolValue)
{
EditorGUI.BeginDisabledGroup(!m_RebuildOnSplineChange.boolValue);
using (new LabelWidthScope(80f))
EditorGUILayout.PropertyField(m_RebuildFrequency, new GUIContent() { text = L10n.Tr("Frequency") });
EditorGUI.EndDisabledGroup();
}
else
{
if (GUILayout.Button(new GUIContent(L10n.Tr("Regenerate"))))
Rebuild();
}
if (EditorGUI.EndChangeCheck() && !m_RebuildOnSplineChange.boolValue)
{
// This is needed to set m_RebuildRequested to the appropriate value.
SetRebuildOnSplineChange(m_RebuildOnSplineChange.boolValue);
}
EditorGUILayout.EndHorizontal();
if (m_AnyMissingMesh)
{
GUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(" ");
if (GUILayout.Button("Create Mesh Asset"))
CreateMeshAssets(m_Components);
GUILayout.EndHorizontal();
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Radius);
if (EditorGUI.EndChangeCheck())
m_Radius.floatValue = Mathf.Clamp(m_Radius.floatValue, .00001f, 1000f);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_SegmentsPerUnit);
if (EditorGUI.EndChangeCheck())
m_SegmentsPerUnit.floatValue = Mathf.Clamp(m_SegmentsPerUnit.floatValue, .00001f, 4096f);
var canCapEnds = CanCapEnds();
using (new EditorGUI.DisabledScope(!canCapEnds))
{
EditorGUILayout.PropertyField(m_Capped, new GUIContent(k_CapEnds, m_Capped.tooltip));
if (m_Capped.boolValue && !canCapEnds)
m_Capped.boolValue = false;
}
EditorGUILayout.PropertyField(m_FlipNormals);
EditorGUI.indentLevel--;
}
// advanced section
EditorGUILayout.Space(4);
m_Range.isExpanded = Foldout(m_Range.isExpanded, k_AdvancedContent, true);
if (m_Range.isExpanded)
{
EditorGUI.indentLevel++;
EditorGUI.showMixedValue = m_Range.hasMultipleDifferentValues;
var range = m_Range.vector2Value;
EditorGUI.BeginChangeCheck();
EditorGUILayout.MinMaxSlider(k_RangeContent, ref range.x, ref range.y, 0f, 1f);
if (EditorGUI.EndChangeCheck())
m_Range.vector2Value = range;
EditorGUI.indentLevel++;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(k_PercentageContent);
EditorGUI.indentLevel--;
EditorGUI.indentLevel--;
EditorGUI.BeginChangeCheck();
var newRange = new Vector2(range.x, range.y);
using (new LabelWidthScope(30f))
newRange.x = EditorGUILayout.FloatField(k_From, range.x * 100f) / 100f;
using (new LabelWidthScope(15f))
newRange.y = EditorGUILayout.FloatField(k_To, range.y * 100f) / 100f;
if (EditorGUI.EndChangeCheck())
{
newRange.x = Mathf.Min(Mathf.Clamp(newRange.x, 0f, 1f), range.y);
newRange.y = Mathf.Max(newRange.x, Mathf.Clamp(newRange.y, 0f, 1f));
m_Range.vector2Value = newRange;
}
EditorGUI.indentLevel++;
EditorGUILayout.EndHorizontal();
EditorGUI.showMixedValue = false;
EditorGUILayout.PropertyField(m_UpdateColliders);
EditorGUI.indentLevel--;
}
serializedObject.ApplyModifiedProperties();
if (EditorGUI.EndChangeCheck())
{
foreach (var extrude in m_Components)
extrude.Rebuild();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8ae5aa24d97048439e76e9392765c364
timeCreated: 1637689699

View File

@@ -0,0 +1,661 @@
using System.Linq;
using UnityEditor;
using UnityEditor.Splines;
using UnityEngine;
using UnityEngine.Splines;
class SplineInstantiateGizmoDrawer
{
[DrawGizmo(GizmoType.Selected | GizmoType.Active)]
static void DrawSplineInstantiateGizmos(SplineInstantiate scr, GizmoType gizmoType)
{
var instances = scr.instances;
foreach(var instance in instances)
{
var pos = instance.transform.position;
Handles.color = Color.red;
Handles.DrawAAPolyLine(3f,new []{ pos, pos + 0.25f * instance.transform.right });
Handles.color = Color.green;
Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.up});
Handles.color = Color.blue;
Handles.DrawAAPolyLine(3f,new []{pos, pos + 0.25f * instance.transform.forward});
}
}
}
[CustomPropertyDrawer (typeof(SplineInstantiate.InstantiableItem))]
class InstantiableItemDrawer : PropertyDrawer
{
static readonly string k_ProbabilityTooltip = L10n.Tr("Probability for that element to appear.");
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight;
}
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
{
var prefabProperty = property.FindPropertyRelative(nameof(SplineInstantiate.InstantiableItem.Prefab));
var probaProperty = property.FindPropertyRelative(nameof(SplineInstantiate.InstantiableItem.Probability));
var headerLine = ReserveSpace(EditorGUIUtility.singleLineHeight, ref rect);
using(new SplineInstantiateEditor.LabelWidthScope(0f))
EditorGUI.ObjectField(ReserveLineSpace(headerLine.width - 100, ref headerLine), prefabProperty, new GUIContent(""));
ReserveLineSpace(10, ref headerLine);
EditorGUI.LabelField(ReserveLineSpace(15, ref headerLine), new GUIContent("%", k_ProbabilityTooltip));
probaProperty.floatValue = EditorGUI.FloatField(ReserveLineSpace(60, ref headerLine), probaProperty.floatValue);
}
static Rect ReserveSpace(float height, ref Rect total)
{
Rect current = total;
current.height = height;
total.y += height;
return current;
}
static Rect ReserveLineSpace(float width, ref Rect total)
{
Rect current = total;
current.width = width;
total.x += width;
return current;
}
}
[CustomPropertyDrawer (typeof(SplineInstantiate.AlignAxis))]
class ItemAxisDrawer : PropertyDrawer
{
static int s_LastUpAxis;
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
{
var enumValue = property.intValue;
if(property.name == "m_Up")
{
property.intValue = (int)( (SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue));
s_LastUpAxis = property.intValue;
}
else
{
property.intValue = (int)((SplineInstantiate.AlignAxis)EditorGUI.EnumPopup(rect, label, (SplineInstantiate.AlignAxis)enumValue,
(item) =>
{
int axisItem = (int)(SplineInstantiate.AlignAxis)item;
return !(axisItem == s_LastUpAxis || axisItem == (s_LastUpAxis + 3) % 6);
}));
}
}
}
[CustomEditor(typeof(SplineInstantiate),false)]
[CanEditMultipleObjects]
class SplineInstantiateEditor : SplineComponentEditor
{
enum SpawnType
{
Exact,
Random
}
SerializedProperty m_SplineContainer;
SerializedProperty m_ItemsToInstantiate;
SerializedProperty m_InstantiateMethod;
SerializedProperty m_Seed;
SerializedProperty m_Space;
SerializedProperty m_UpAxis;
SerializedProperty m_ForwardAxis;
SerializedProperty m_Spacing;
SerializedProperty m_PositionOffset;
SerializedProperty m_RotationOffset;
SerializedProperty m_ScaleOffset;
SerializedProperty m_AutoRefresh;
static readonly string[] k_SpacingTypesLabels = new []
{
L10n.Tr("Count"),
L10n.Tr("Spacing (Spline)"),
L10n.Tr("Spacing (Linear)")
};
//Setup Section
static readonly string k_Setup = L10n.Tr("Instantiated Object Setup");
static readonly string k_ObjectUp = L10n.Tr("Up Axis");
static readonly string k_ObjectUpTooltip = L10n.Tr("Object axis to use as Up Direction when instantiating on the Spline (default is Y).");
static readonly string k_ObjectForward = L10n.Tr("Forward Axis");
static readonly string k_ObjectForwardTooltip = L10n.Tr("Object axis to use as Forward Direction when instantiating on the Spline (default is Z).");
static readonly string k_AlignTo = L10n.Tr("Align To");
static readonly string k_AlignToTooltip = L10n.Tr("Define the space to use to orientate the instantiated object.");
static readonly string k_Instantiation = L10n.Tr("Instantiation");
static readonly string k_Method = L10n.Tr("Instantiate Method");
static readonly string k_MethodTooltip = L10n.Tr("How instances are generated along the spline.");
static readonly string k_Max = L10n.Tr("Max");
static readonly string k_Min = L10n.Tr("Min");
SpawnType m_SpacingType;
//Offsets
static readonly string k_PositionOffset = L10n.Tr("Position Offset");
static readonly string k_PositionOffsetTooltip = L10n.Tr("Whether or not to use a position offset.");
static readonly string k_RotationOffset = L10n.Tr("Rotation Offset");
static readonly string k_RotationOffsetTooltip = L10n.Tr("Whether or not to use a rotation offset.");
static readonly string k_ScaleOffset = L10n.Tr("Scale Offset");
static readonly string k_ScaleOffsetTooltip = L10n.Tr("Whether or not to use a scale offset.");
//Generation
static readonly string k_Generation = L10n.Tr("Generation");
static readonly string k_AutoRefresh = L10n.Tr("Auto Refresh Generation");
static readonly string k_AutoRefreshTooltip = L10n.Tr("Automatically refresh the instances when the spline or the values are changed.");
static readonly string k_Seed = L10n.Tr("Randomization Seed");
static readonly string k_SeedTooltip = L10n.Tr("Value used to initialize the pseudorandom number generator of the instances.");
static readonly string k_Randomize = L10n.Tr("Randomize");
static readonly string k_RandomizeTooltip = L10n.Tr("Compute a new randomization of the instances along the spline.");
static readonly string k_Regenerate = L10n.Tr("Regenerate");
static readonly string k_RegenerateTooltip = L10n.Tr("Regenerate the instances along the spline.");
static readonly string k_Clear = L10n.Tr("Clear");
static readonly string k_ClearTooltip = L10n.Tr("Clear the instances along the spline.");
static readonly string k_Bake = L10n.Tr("Bake Instances");
static readonly string k_BakeTooltip = L10n.Tr("Bake the instances in the SceneView for custom edition and destroy that SplineInstantiate component.");
bool m_PositionFoldout;
bool m_RotationFoldout;
bool m_ScaleFoldout;
enum OffsetType
{
Exact,
Random
};
SplineInstantiate[] m_Components;
SplineInstantiate[] components
{
get
{
//in case of multiple selection where some objects do not have a SplineInstantiate component, m_Components might be null
if (m_Components == null)
m_Components = targets.Select(x => x as SplineInstantiate).Where(y => y != null).ToArray();
return m_Components;
}
}
protected void OnEnable()
{
Spline.Changed += OnSplineChanged;
EditorSplineUtility.AfterSplineWasModified += OnSplineModified;
SplineContainer.SplineAdded += OnContainerSplineSetModified;
SplineContainer.SplineRemoved += OnContainerSplineSetModified;
}
bool Initialize()
{
if (m_Components != null && m_Components.Length > 0)
return true;
m_SplineContainer = serializedObject.FindProperty("m_Container");
m_ItemsToInstantiate = serializedObject.FindProperty("m_ItemsToInstantiate");
m_InstantiateMethod = serializedObject.FindProperty("m_Method");
m_Space = serializedObject.FindProperty("m_Space");
m_UpAxis = serializedObject.FindProperty("m_Up");
m_ForwardAxis = serializedObject.FindProperty("m_Forward");
m_Spacing = serializedObject.FindProperty("m_Spacing");
m_PositionOffset = serializedObject.FindProperty("m_PositionOffset");
m_RotationOffset = serializedObject.FindProperty("m_RotationOffset");
m_ScaleOffset = serializedObject.FindProperty("m_ScaleOffset");
m_Seed = serializedObject.FindProperty("m_Seed");
m_AutoRefresh = serializedObject.FindProperty("m_AutoRefresh");
if (m_Spacing != null)
m_SpacingType = Mathf.Approximately(m_Spacing.vector2Value.x, m_Spacing.vector2Value.y) ? SpawnType.Exact : SpawnType.Random;
else
m_SpacingType = SpawnType.Exact;
m_Components = targets.Select(x => x as SplineInstantiate).Where(y => y != null).ToArray();
return m_Components != null && m_Components.Length > 0;
}
void OnDisable()
{
m_Components = null;
Spline.Changed -= OnSplineChanged;
EditorSplineUtility.AfterSplineWasModified -= OnSplineModified;
SplineContainer.SplineAdded -= OnContainerSplineSetModified;
SplineContainer.SplineRemoved -= OnContainerSplineSetModified;
}
void OnSplineModified(Spline spline)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var instantiate in components)
{
if(instantiate == null)
continue;
if (instantiate.Container != null && instantiate.Container.Splines.Contains(spline))
instantiate.SetSplineDirty(spline);
}
}
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modification)
{
OnSplineModified(spline);
}
void OnContainerSplineSetModified(SplineContainer container, int spline)
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return;
foreach (var instantiate in components)
{
if (instantiate.Container == container)
instantiate.UpdateInstances();
}
}
public override void OnInspectorGUI()
{
if(!Initialize())
return;
serializedObject.Update();
var dirtyInstances = false;
var updateInstances = false;
EditorGUILayout.PropertyField(m_SplineContainer);
if(m_SplineContainer.objectReferenceValue == null)
EditorGUILayout.HelpBox(k_Helpbox, MessageType.Warning);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_ItemsToInstantiate);
dirtyInstances = EditorGUI.EndChangeCheck();
DoSetupSection();
dirtyInstances |= DoInstantiateSection();
updateInstances |= DisplayOffsets();
EditorGUILayout.LabelField(k_Generation, EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_Seed, new GUIContent(k_Seed, k_SeedTooltip));
var newSeed = EditorGUI.EndChangeCheck();
dirtyInstances |= newSeed;
updateInstances |= newSeed;
EditorGUILayout.PropertyField(m_AutoRefresh, new GUIContent(k_AutoRefresh, k_AutoRefreshTooltip));
EditorGUI.indentLevel--;
serializedObject.ApplyModifiedProperties();
EditorGUILayout.Separator();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
if (GUILayout.Button(new GUIContent(k_Randomize, k_RandomizeTooltip), GUILayout.MaxWidth(100f)))
{
Undo.SetCurrentGroupName("Change SplineInstantiate Seed");
var group = Undo.GetCurrentGroup();
foreach (var splineInstantiate in m_Components)
{
Undo.RecordObject(splineInstantiate, $"Change SplineInstantiate Seed for {splineInstantiate.gameObject.name}");
splineInstantiate.Randomize();
}
Undo.CollapseUndoOperations(group);
updateInstances = true;
}
var isInstancesCountGreaterThanZero = false;
foreach (var splineInstantiate in m_Components)
{
if (splineInstantiate.instances.Count > 0)
{
isInstancesCountGreaterThanZero = true;
break;
}
}
if (GUILayout.Button(new GUIContent(k_Regenerate, k_RegenerateTooltip), GUILayout.MaxWidth(100f)))
updateInstances = true;
GUI.enabled = isInstancesCountGreaterThanZero;
if (GUILayout.Button(new GUIContent(k_Clear, k_ClearTooltip), GUILayout.MaxWidth(100f)))
{
Undo.SetCurrentGroupName("Clear SplineInstantiate");
var group = Undo.GetCurrentGroup();
foreach (var splineInstantiate in m_Components)
{
Undo.RecordObject(splineInstantiate, $"Clear SplineInstantiate for {splineInstantiate.gameObject.name}");
splineInstantiate.Clear();
}
Undo.CollapseUndoOperations(group);
}
if (GUILayout.Button(new GUIContent(k_Bake, k_BakeTooltip), GUILayout.MaxWidth(100f)))
{
foreach (var splineInstantiate in m_Components)
BakeInstances(splineInstantiate);
}
GUI.enabled = true;
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Separator();
foreach (var splineInstantiate in m_Components)
{
if (dirtyInstances)
splineInstantiate.SetDirty();
if (updateInstances)
splineInstantiate.UpdateInstances();
}
if (dirtyInstances || updateInstances)
SceneView.RepaintAll();
}
void DoSetupSection()
{
EditorGUILayout.LabelField(k_Setup, EditorStyles.boldLabel);
GUILayout.Space(5f);
EditorGUI.indentLevel++;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_UpAxis, new GUIContent(k_ObjectUp, k_ObjectUpTooltip));
EditorGUILayout.PropertyField(m_ForwardAxis, new GUIContent(k_ObjectForward, k_ObjectForwardTooltip));
if(EditorGUI.EndChangeCheck())
{
//Insuring axis integrity
if(m_ForwardAxis.intValue == m_UpAxis.intValue || m_ForwardAxis.intValue == ( m_UpAxis.intValue + 3 ) % 6)
m_ForwardAxis.intValue = ( m_ForwardAxis.intValue + 1 ) % 6;
}
EditorGUILayout.PropertyField(m_Space, new GUIContent(k_AlignTo, k_AlignToTooltip));
EditorGUI.indentLevel--;
}
bool DoInstantiateSection()
{
var dirty = false;
Vector2 spacingV2 = m_Spacing.vector2Value;
EditorGUILayout.LabelField(k_Instantiation, EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_InstantiateMethod, new GUIContent(k_Method, k_MethodTooltip), EditorStyles.boldFont );
if(EditorGUI.EndChangeCheck())
{
if(m_SpacingType == SpawnType.Random && m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance)
m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN);
dirty = true;
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(new GUIContent(k_SpacingTypesLabels[m_InstantiateMethod.intValue]));
EditorGUI.indentLevel--;
GUILayout.Space(2f);
EditorGUI.BeginChangeCheck();
float spacingX = m_Spacing.vector2Value.x;
var isExact = m_SpacingType == SpawnType.Exact;
if(isExact || m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
{
using(new LabelWidthScope(30f))
spacingX = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ?
EditorGUILayout.IntField(new GUIContent(isExact ? string.Empty : k_Min), (int)m_Spacing.vector2Value.x, GUILayout.MinWidth(50f)) :
EditorGUILayout.FloatField(new GUIContent(isExact ? L10n.Tr("Dist") : k_Min), m_Spacing.vector2Value.x, GUILayout.MinWidth(50f));
}
if(isExact)
{
spacingV2 = new Vector2(spacingX, spacingX);
}
else if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
{
using(new LabelWidthScope(30f))
{
var spacingY = (SplineInstantiate.Method)m_InstantiateMethod.intValue == SplineInstantiate.Method.InstanceCount ?
EditorGUILayout.IntField(new GUIContent(k_Max), (int)m_Spacing.vector2Value.y, GUILayout.MinWidth(50f)) :
EditorGUILayout.FloatField(new GUIContent(k_Max), m_Spacing.vector2Value.y, GUILayout.MinWidth(50f));
if(spacingX > m_Spacing.vector2Value.y)
spacingY = spacingX;
else if(spacingY < m_Spacing.vector2Value.x)
spacingX = spacingY;
spacingV2 = new Vector2(spacingX, spacingY);
}
}
if(EditorGUI.EndChangeCheck())
m_Spacing.vector2Value = spacingV2;
EditorGUI.BeginChangeCheck();
if(m_InstantiateMethod.intValue != (int)SplineInstantiate.Method.LinearDistance)
m_SpacingType = (SpawnType)EditorGUILayout.EnumPopup(m_SpacingType, GUILayout.MinWidth(30f));
else
m_SpacingType = (SpawnType)EditorGUILayout.Popup(m_SpacingType == SpawnType.Exact ? 0 : 1,
new []{"Exact", "Auto"}, GUILayout.MinWidth(30f));
if(EditorGUI.EndChangeCheck())
{
if(m_SpacingType == SpawnType.Exact)
m_Spacing.vector2Value = new Vector2(spacingV2.x, spacingV2.x);
else if(m_InstantiateMethod.intValue == (int)SplineInstantiate.Method.LinearDistance)
m_Spacing.vector2Value = new Vector2(spacingV2.x, float.NaN);
dirty = true;
}
EditorGUILayout.EndHorizontal();
return dirty;
}
bool DoOffsetProperties(
SerializedProperty offsetProperty, GUIContent content, bool foldoutValue, out bool newFoldoutValue)
{
bool changed = false;
newFoldoutValue = foldoutValue;
EditorGUILayout.BeginHorizontal();
using(new LabelWidthScope(0f))
{
var setupProperty = offsetProperty.FindPropertyRelative("setup");
var setup = (SplineInstantiate.Vector3Offset.Setup)setupProperty.intValue;
var hasOffset = ( setup & SplineInstantiate.Vector3Offset.Setup.HasOffset ) != 0;
EditorGUI.BeginChangeCheck();
hasOffset = EditorGUILayout.Toggle(hasOffset, GUILayout.MaxWidth(20f));
if(EditorGUI.EndChangeCheck())
{
if(hasOffset)
setup |= SplineInstantiate.Vector3Offset.Setup.HasOffset;
else
setup &= ~SplineInstantiate.Vector3Offset.Setup.HasOffset;
setupProperty.intValue = (int)setup;
changed = true;
}
EditorGUILayout.Space(10f);
using(new EditorGUI.DisabledScope(!hasOffset))
{
newFoldoutValue = Foldout(foldoutValue, content, hasOffset) && hasOffset;
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
if(newFoldoutValue)
{
EditorGUILayout.BeginHorizontal();
var hasCustomSpace = ( setup & SplineInstantiate.Vector3Offset.Setup.HasCustomSpace ) != 0;
EditorGUI.BeginChangeCheck();
var space = m_Space.intValue < 1 ? "Spline Element" : m_Space.intValue == 1 ? "Spline Object" : "World";
hasCustomSpace = EditorGUILayout.Toggle(new GUIContent("Override space", L10n.Tr("Override current space (" + space + ")")), hasCustomSpace);
if(EditorGUI.EndChangeCheck())
{
if(hasCustomSpace)
setup |= SplineInstantiate.Vector3Offset.Setup.HasCustomSpace;
else
setup &= ~SplineInstantiate.Vector3Offset.Setup.HasCustomSpace;
setupProperty.intValue = (int)setup;
changed = true;
}
var spaceProperty = offsetProperty.FindPropertyRelative("space");
using(new EditorGUI.DisabledScope(!hasCustomSpace))
{
var type = (SplineInstantiate.OffsetSpace)spaceProperty.intValue;
EditorGUI.BeginChangeCheck();
type = (SplineInstantiate.OffsetSpace)EditorGUILayout.EnumPopup(type);
if(EditorGUI.EndChangeCheck())
{
spaceProperty.intValue = (int)type;
changed = true;
}
}
EditorGUILayout.EndHorizontal();
var minProperty = offsetProperty.FindPropertyRelative("min");
var maxProperty = offsetProperty.FindPropertyRelative("max");
var minPropertyValue = minProperty.vector3Value;
var maxPropertyValue = maxProperty.vector3Value;
float min, max;
SerializedProperty randomProperty;
for(int i = 0; i < 3; i++)
{
string label = i == 0 ? "X" : i == 1 ? "Y" : "Z";
EditorGUILayout.BeginHorizontal();
using(new LabelWidthScope(30f))
EditorGUILayout.LabelField(label);
randomProperty = offsetProperty.FindPropertyRelative("random"+label);
GUILayout.FlexibleSpace();
if(randomProperty.boolValue)
{
EditorGUI.BeginChangeCheck();
using(new LabelWidthScope(30f))
{
min = EditorGUILayout.FloatField("from", minPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f));
max = EditorGUILayout.FloatField(" to", maxPropertyValue[i], GUILayout.MinWidth(95f), GUILayout.MaxWidth(95f));
}
if(EditorGUI.EndChangeCheck())
{
if(min > maxPropertyValue[i])
maxPropertyValue[i] = min;
if(max < minPropertyValue[i])
minPropertyValue[i] = max;
minPropertyValue[i] = min;
maxPropertyValue[i] = max;
minProperty.vector3Value = minPropertyValue;
maxProperty.vector3Value = maxPropertyValue;
changed = true;
}
}
else
{
EditorGUI.BeginChangeCheck();
using(new LabelWidthScope(30f))
min = EditorGUILayout.FloatField("is ", minPropertyValue[i], GUILayout.MinWidth(193f), GUILayout.MaxWidth(193f));
if(EditorGUI.EndChangeCheck())
{
minPropertyValue[i] = min;
if(min > maxPropertyValue[i])
maxPropertyValue[i] = min;
minProperty.vector3Value = minPropertyValue;
maxProperty.vector3Value = maxPropertyValue;
changed = true;
}
}
EditorGUI.BeginChangeCheck();
var isOffsetRandom = randomProperty.boolValue ? OffsetType.Random : OffsetType.Exact;
using(new LabelWidthScope(0f))
isOffsetRandom = (OffsetType)EditorGUILayout.EnumPopup(isOffsetRandom,GUILayout.MinWidth(100f), GUILayout.MaxWidth(200f));
if(EditorGUI.EndChangeCheck())
{
randomProperty.boolValue = isOffsetRandom == OffsetType.Random;
changed = true;
}
EditorGUILayout.EndHorizontal();
}
}
}
}
return changed;
}
bool DisplayOffsets()
{
var updateNeeded = DoOffsetProperties(m_PositionOffset, new GUIContent(k_PositionOffset, k_PositionOffsetTooltip), m_PositionFoldout, out m_PositionFoldout);
updateNeeded |= DoOffsetProperties(m_RotationOffset, new GUIContent(k_RotationOffset, k_RotationOffsetTooltip), m_RotationFoldout, out m_RotationFoldout);
updateNeeded |= DoOffsetProperties(m_ScaleOffset, new GUIContent(k_ScaleOffset, k_ScaleOffsetTooltip), m_ScaleFoldout, out m_ScaleFoldout);
return updateNeeded;
}
/// <summary>
/// Bake the instances into the scene and destroy this SplineInstantiate component.
/// Making changes to the spline after baking will not affect the instances anymore.
/// </summary>
void BakeInstances(SplineInstantiate splineInstantiate)
{
Undo.SetCurrentGroupName("Baking SplineInstantiate instances");
var group = Undo.GetCurrentGroup();
splineInstantiate.UpdateInstances();
for (int i = 0; i < splineInstantiate.instances.Count; ++i)
{
var newInstance = splineInstantiate.instances[i];
newInstance.name = "Instance-" + i;
newInstance.hideFlags = HideFlags.None;
newInstance.transform.SetParent(splineInstantiate.gameObject.transform, true);
Undo.RegisterCreatedObjectUndo(newInstance, "Baking instance");
}
splineInstantiate.instances.Clear();
if(splineInstantiate.InstancesRoot != null)
Undo.DestroyObjectImmediate(splineInstantiate.InstancesRoot);
Undo.DestroyObjectImmediate(splineInstantiate);
Undo.CollapseUndoOperations(group);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c574a2f698921d1458f6dfba4b5e1229
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6042ea3ccf1fb5c45b533a9d67e956b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,295 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class CurveHandles
{
const float k_CurveLineWidth = 4f;
const float k_PreviewCurveOpacity = 0.5f;
static readonly Vector3[] s_CurveDrawingBuffer = new Vector3[SplineCacheUtility.CurveDrawResolution + 1];
static readonly Vector3[] s_FlowTriangleVertices = new Vector3[3];
/// <summary>
/// Creates handles for a BezierCurve.
/// </summary>
/// <param name="controlID">The controlID of the curve to create highlights for.</param>
/// <param name="curve">The <see cref="BezierCurve"/> to create handles for.</param>
public static void Draw(int controlID, BezierCurve curve)
{
if(Event.current.type == EventType.Repaint)
Draw(controlID, curve, false, true);
}
/// <summary>
/// Creates handles for a BezierCurve.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to create handles for.</param>
/// <param name="activeSpline">Whether the curve is part of the active spline.</param>
internal static void Draw(BezierCurve curve, bool activeSpline)
{
if(Event.current.type == EventType.Repaint)
Draw(0, curve, false, activeSpline);
}
/// <summary>
/// Creates highlights for a BezierCurve to make it easier to select.
/// </summary>
/// <param name="controlID">The controlID of the curve to create highlights for.</param>
/// <param name="curve">The <see cref="BezierCurve"/> to create highlights for.</param>
/// <param name="spline">The <see cref="ISpline"/> (if any) that the curve belongs to.</param>
/// <param name="curveIndex">The curve's index if it belongs to a spline - otherwise -1.</param>
/// <param name="knotA">The knot at the start of the curve.</param>
/// <param name="knotB">The knot at the end of the curve.</param>
/// <param name="activeSpline">Whether the curve is part of the active spline.</param>
internal static void DrawWithHighlight(
int controlID,
ISpline spline,
int curveIndex,
float4x4 localToWorld,
SelectableKnot knotA,
SelectableKnot knotB,
bool activeSpline)
{
var evt = Event.current;
switch(evt.GetTypeForControl(controlID))
{
case EventType.Layout:
case EventType.MouseMove:
if (!SplineHandles.ViewToolActive() && activeSpline)
{
var curve = spline.GetCurve(curveIndex).Transform(localToWorld);
var dist = DistanceToCurve(curve);
HandleUtility.AddControl(controlID, Mathf.Max(0, dist - SplineHandleUtility.pickingDistance));
//Trigger repaint on MouseMove to update highlight visuals from SplineHandles
if (evt.type == EventType.MouseMove || controlID == HandleUtility.nearestControl)
{
SplineHandleUtility.GetNearestPointOnCurve(curve, out _, out var t);
var curveMidT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, curveIndex);
var hoveredKnot = t <= curveMidT ? knotA : knotB;
if (!(SplineHandleUtility.lastHoveredElement is SelectableKnot knot) || !knot.Equals(hoveredKnot))
{
if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == controlID)
{
SplineHandleUtility.SetLastHoveredElement(hoveredKnot, controlID);
SceneView.RepaintAll();
}
}
}
}
break;
case EventType.MouseDown:
var curveMD = spline.GetCurve(curveIndex).Transform(localToWorld);
if (!SplineHandles.ViewToolActive() && HandleUtility.nearestControl == controlID)
{
//Clicking a knot selects it
if (evt.button != 0)
break;
GUIUtility.hotControl = controlID;
evt.Use();
SplineHandleUtility.GetNearestPointOnCurve(curveMD, out _, out var t);
SplineSelectionUtility.HandleSelection(t <= .5f ? knotA : knotB, false);
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
GUIUtility.hotControl = 0;
evt.Use();
}
break;
}
}
/// <summary>
/// Draws flow on a BezierCurve to indicate the direction.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to create highlights for.</param>
/// <param name="spline">The <see cref="ISpline"/> (if any) that the curve belongs to.</param>
/// <param name="curveIndex">The curve's index if it belongs to a spline - otherwise -1.</param>
internal static void DrawFlow(BezierCurve curve, ISpline spline, int curveIndex)
{
if(Event.current.type != EventType.Repaint)
return;
var arrow = SplineCacheUtility.GetCurveArrow(spline, curveIndex, curve);
s_FlowTriangleVertices[0] = arrow.positions[0];
s_FlowTriangleVertices[1] = arrow.positions[1];
s_FlowTriangleVertices[2] = arrow.positions[2];
using (new Handles.DrawingScope(SplineHandleUtility.lineColor, arrow.trs))
{
using (new ZTestScope(CompareFunction.Less))
Handles.DrawAAConvexPolygon(s_FlowTriangleVertices);
}
using (new Handles.DrawingScope(SplineHandleUtility.lineBehindColor, arrow.trs))
{
using (new ZTestScope(CompareFunction.Greater))
Handles.DrawAAConvexPolygon(s_FlowTriangleVertices);
}
}
static void Draw(int controlID, BezierCurve curve, bool preview, bool activeSpline)
{
var evt = Event.current;
switch (evt.type)
{
case EventType.Layout:
case EventType.MouseMove:
if (!SplineHandles.ViewToolActive() && activeSpline)
{
var dist = DistanceToCurve(curve);
HandleUtility.AddControl(controlID, Mathf.Max(0, dist - SplineHandleUtility.pickingDistance));
}
break;
case EventType.Repaint:
var prevColor = Handles.color;
FillCurveDrawingBuffer(curve);
var color = SplineHandleUtility.lineColor;
if (preview)
color.a *= k_PreviewCurveOpacity;
Handles.color = color;
using (new ZTestScope(CompareFunction.Less))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, s_CurveDrawingBuffer);
color = SplineHandleUtility.lineBehindColor;
if (preview)
color.a *= k_PreviewCurveOpacity;
Handles.color = color;
using (new ZTestScope(CompareFunction.Greater))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, s_CurveDrawingBuffer);
Handles.color = prevColor;
break;
}
}
static void FillCurveDrawingBuffer(BezierCurve curve)
{
SplineCacheUtility.GetCurvePositions(curve, s_CurveDrawingBuffer);
}
internal static float DistanceToCurve(BezierCurve curve)
{
FillCurveDrawingBuffer(curve);
return DistanceToCurve();
}
static float DistanceToCurve()
{
float dist = float.MaxValue;
for (var i = 0; i < s_CurveDrawingBuffer.Length - 1; ++i)
{
var a = s_CurveDrawingBuffer[i];
var b = s_CurveDrawingBuffer[i + 1];
dist = Mathf.Min(HandleUtility.DistanceToLine(a, b), dist);
}
return dist;
}
internal static void DoCurveHighlightCap(SelectableKnot knot)
{
if(Event.current.type != EventType.Repaint)
return;
if(knot.IsValid())
{
var spline = knot.SplineInfo.Spline;
var localToWorld = knot.SplineInfo.LocalToWorld;
if(knot.KnotIndex > 0 || spline.Closed)
{
var curve = spline.GetCurve(spline.PreviousIndex(knot.KnotIndex)).Transform(localToWorld);
var curveMiddleT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, spline.PreviousIndex(knot.KnotIndex));
DrawCurveHighlight(curve, 1f, curveMiddleT);
}
if(knot.KnotIndex < spline.Count - 1 || spline.Closed)
{
var curve = spline.GetCurve(knot.KnotIndex).Transform(localToWorld);
var curveMiddleT = EditorSplineUtility.GetCurveMiddleInterpolation(curve, spline, knot.KnotIndex);
DrawCurveHighlight(curve, 0f, curveMiddleT);
}
}
}
static void DrawCurveHighlight(BezierCurve curve, float startT, float endT)
{
FillCurveDrawingBuffer(curve);
var growing = startT <= endT;
var color = Handles.color;
color.a = growing ? 1f : 0f;
using (new ZTestScope(CompareFunction.Less))
using (new Handles.DrawingScope(color))
DrawAAPolyLineForCurveHighlight(color, startT, endT, 1f, growing);
using (new ZTestScope(CompareFunction.Greater))
using (new Handles.DrawingScope(color))
DrawAAPolyLineForCurveHighlight(color, startT, endT, 0.3f, growing);
}
static void DrawAAPolyLineForCurveHighlight(Color color, float startT, float endT, float colorAlpha, bool growing)
{
for (int i = 1; i <= SplineCacheUtility.CurveDrawResolution; ++i)
{
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_CurveLineWidth, new[] { s_CurveDrawingBuffer[i - 1], s_CurveDrawingBuffer[i] });
var current = ((float)i / (float)SplineCacheUtility.CurveDrawResolution);
if (growing)
{
if (current > endT)
color.a = 0f;
else if (current > startT)
color.a = (1f - (current - startT) / (endT - startT)) * colorAlpha;
}
else
{
if (current < endT)
color.a = 0f;
else if (current > endT && current < startT)
color.a = (current - endT) / (startT - endT) * colorAlpha;
}
Handles.color = color;
}
}
/// <summary>
/// Creates the set of control points that make up a curve.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to create control points for.</param>
public static void DrawControlNet(BezierCurve curve)
{
Handles.color = Color.green;
Handles.DotHandleCap(-1, curve.P0, Quaternion.identity, HandleUtility.GetHandleSize(curve.P0) * .04f, Event.current.type);
Handles.color = Color.red;
Handles.DotHandleCap(-1, curve.P1, Quaternion.identity, HandleUtility.GetHandleSize(curve.P1) * .04f, Event.current.type);
Handles.color = Color.yellow;
Handles.DotHandleCap(-1, curve.P2, Quaternion.identity, HandleUtility.GetHandleSize(curve.P2) * .04f, Event.current.type);
Handles.color = Color.blue;
Handles.DotHandleCap(-1, curve.P3, Quaternion.identity, HandleUtility.GetHandleSize(curve.P3) * .04f, Event.current.type);
Handles.color = Color.gray;
Handles.DrawDottedLine(curve.P0, curve.P1, 2f);
Handles.DrawDottedLine(curve.P1, curve.P2, 2f);
Handles.DrawDottedLine(curve.P2, curve.P3, 2f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 77221e48184d50742984ffe3b4192f2a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,201 @@
using Unity.Mathematics;
using UnityEditor.SettingsManagement;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Splines
{
static class DirectManipulation
{
[UserSetting("Tweak Mode", "Plane Color")]
static readonly Pref<Color> s_GuidePlaneColor = new Pref<Color>("Handles.DirectManipulation.PlaneColor", new Color(1f, 1f, 1f, 5f/255f));
[UserSetting("Tweak Mode", "Snap to Guide Enabled")]
static readonly Pref<bool> s_SnapToGuide = new Pref<bool>("Handles.DirectManipulation.SnapToGuide", true);
[UserSetting("Tweak Mode", "Snap to Guide Distance")]
static readonly Pref<float> s_SnapToGuideDistance = new Pref<float>("Handles.DirectManipulation.SnapToGuideDistance", 7f);
static readonly Vector3[] s_VertexBuffer = new Vector3[4];
public static bool IsDragging => s_IsDragging;
static readonly Vector3 k_GuidePlaneZTestOffset = new Vector3(0.001f, 0.001f, 0.001f);
static Vector3 s_InitialPosition;
static Quaternion s_InitialRotation;
static Vector2 s_InitialMousePosition;
static bool s_IsDragging;
#if UNITY_2022_2_OR_NEWER
static bool IncrementalSnapActive => EditorSnapSettings.incrementalSnapActive;
#else
static bool IncrementalSnapActive => false;
#endif
const float k_HandleColorAlphaFactor = 0.3f;
static bool ShouldMoveOnNormal(int controlId) => GUIUtility.hotControl == controlId && Event.current.alt;
public static void BeginDrag(Vector3 position, Quaternion rotation)
{
s_InitialPosition = position;
s_InitialRotation = rotation;
s_InitialMousePosition = Event.current.mousePosition;
}
public static Vector3 UpdateDrag(int controlId)
{
var position = ShouldMoveOnNormal(controlId)
? MoveOnNormal(Event.current.mousePosition, s_InitialPosition, s_InitialRotation)
: MoveOnPlane(Event.current.mousePosition, s_InitialPosition, s_InitialRotation, IncrementalSnapActive);
s_IsDragging = true;
return position;
}
public static void EndDrag()
{
s_IsDragging = false;
}
public static void DrawHandles(int controlId, Vector3 position)
{
if (GUIUtility.hotControl != controlId || !s_IsDragging)
return;
EditorGUIUtility.AddCursorRect(new Rect(0, 0, 100000, 10000), MouseCursor.MoveArrow);
if (ShouldMoveOnNormal(controlId))
{
var yDir = s_InitialRotation * Vector3.up;
DrawGuideAxis(s_InitialPosition, yDir, Handles.yAxisColor);
}
else
{
var zDir = s_InitialRotation * Vector3.forward;
var xDir = s_InitialRotation * Vector3.right;
DrawGuidePlane(s_InitialPosition, xDir, zDir, position);
DrawGuideDottedLine(s_InitialPosition, zDir, position);
DrawGuideDottedLine(s_InitialPosition, xDir, position);
DrawGuideAxis(s_InitialPosition, zDir, Handles.zAxisColor);
DrawGuideAxis(s_InitialPosition, xDir, Handles.xAxisColor);
}
}
static (Vector3 projection, float distance) GetSnapToGuideData(Vector3 current, Vector3 origin, Vector3 axis)
{
var projection = Vector3.Project(current - origin, axis);
var screenPos = HandleUtility.WorldToGUIPoint(origin + projection);
var distance = Vector2.Distance(screenPos, Event.current.mousePosition);
return (projection, distance);
}
static Vector3 MoveOnPlane(Vector2 mousePosition, Vector3 origin, Quaternion rotation, bool snapping)
{
var ray = HandleUtility.GUIPointToWorldRay(mousePosition);
var manipPlane = new Plane(rotation * Vector3.up, origin);
var position = manipPlane.Raycast(ray, out float distance)
? ray.origin + ray.direction * distance
: origin;
var dir = position - origin;
var forward = GetSnapToGuideData(position, origin, rotation * Vector3.forward);
var right = GetSnapToGuideData(position, origin, rotation * Vector3.right);
if (!snapping && s_SnapToGuide)
{
if (forward.distance < s_SnapToGuideDistance || right.distance < s_SnapToGuideDistance)
{
var snapToForward = forward.distance < right.distance;
var axis = (snapToForward ? forward : right).projection;
return origin + axis;
}
}
if(Mathf.Approximately(dir.magnitude, 0f))
dir = Vector3.forward;
var translation = Handles.SnapValue(Quaternion.Inverse(rotation) * dir, new Vector3(EditorSnapSettings.move.x, 0, EditorSnapSettings.move.z));
return origin + rotation * translation;
}
static Vector3 MoveOnNormal(Vector2 mousePosition, Vector3 origin, Quaternion rotation)
{
var upAxis = rotation * Vector3.up;
var translation = upAxis * Handles.SnapValue(HandleUtility.CalcLineTranslation(s_InitialMousePosition, mousePosition, origin, upAxis), EditorSnapSettings.move.y);
return origin + translation;
}
static void DrawGuideAxis(Vector3 origin, Vector3 axis, Color color)
{
var start = origin - axis.normalized * 10000f;
var end = origin + axis.normalized * 10000f;
using (new ZTestScope(CompareFunction.Less))
using (new Handles.DrawingScope(color))
{
Handles.DrawLine(origin, start, 0f);
Handles.DrawLine(origin, end, 0f);
}
color = new Color(color.r, color.g, color.b, color.a * k_HandleColorAlphaFactor);
using (new ZTestScope(CompareFunction.Greater))
using (new Handles.DrawingScope(color))
{
Handles.DrawLine(origin, start, 0f);
Handles.DrawLine(origin, end, 0f);
}
}
static void DrawGuidePlane(Vector3 origin, Vector3 axisX, Vector3 axisZ, Vector3 position)
{
var xAxisProjection = Vector3.Project(position - origin, axisX);
var zAxisProjection = Vector3.Project(position - origin, axisZ);
var cross = math.cross(xAxisProjection, zAxisProjection);
var normal = math.normalizesafe(cross);
var scaledOffset = k_GuidePlaneZTestOffset * HandleUtility.GetHandleSize(origin);
var calculatedOffset = new Vector3(scaledOffset.x * normal.x, scaledOffset.y * normal.y, scaledOffset.z * normal.z);
position += calculatedOffset;
origin += calculatedOffset;
s_VertexBuffer[0] = origin;
s_VertexBuffer[1] = origin + Vector3.Project(position - origin, axisX);
s_VertexBuffer[2] = position;
s_VertexBuffer[3] = origin + Vector3.Project(position - origin, axisZ);
DrawGuidePlane(Matrix4x4.identity);
}
static void DrawGuidePlane(Matrix4x4 matrix)
{
var color = s_GuidePlaneColor.value;
using (new ZTestScope(CompareFunction.Less))
using (new Handles.DrawingScope(matrix))
Handles.DrawSolidRectangleWithOutline(s_VertexBuffer, color, Color.clear);
color = new Color(s_GuidePlaneColor.value.r, s_GuidePlaneColor.value.g, s_GuidePlaneColor.value.b,
s_GuidePlaneColor.value.a * k_HandleColorAlphaFactor);
using (new ZTestScope(CompareFunction.Greater))
using (new Handles.DrawingScope(matrix))
Handles.DrawSolidRectangleWithOutline(s_VertexBuffer, color, Color.clear);
}
static void DrawGuideDottedLine(Vector3 origin, Vector3 axis, Vector3 position)
{
using (new ZTestScope(CompareFunction.Less))
Handles.DrawDottedLine(origin + Vector3.Project(position - origin, axis), position, 3f);
var color = new Color(Handles.color.r, Handles.color.g, Handles.color.b, Handles.color.a * k_HandleColorAlphaFactor);
using (new ZTestScope(CompareFunction.Greater))
using (new Handles.DrawingScope(color))
Handles.DrawDottedLine(origin + Vector3.Project(position - origin, axis), position, 3f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 485daebcb9b4a264ba4f878081549056
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,322 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Splines
{
static class KnotHandles
{
const float k_ColorAlphaFactor = 0.3f;
const float k_KnotRotDiscRadius = 0.18f;
const float k_KnotRotDiscWidthDefault = 1.5f;
const float k_KnotRotDiscWidthHover = 3f;
const float k_KnotHandleWidth = 2f;
static readonly List<SelectableKnot> k_KnotBuffer = new List<SelectableKnot>();
static readonly Vector3[] k_HandlePoints = new Vector3[11];
static List<(SelectableKnot knot, bool selected, bool hovered, Color knotColor, Color discColor, bool linkedKnot)> s_Knots = new ();
internal static void Do(int controlId, SelectableKnot knot, bool selected = false, bool hovered = false)
{
if (Event.current.GetTypeForControl(controlId) != EventType.Repaint)
return;
//Hovered might not be available if a TRS tool is in use
hovered &= SplineHandleUtility.IsHoverAvailableForSplineElement();
var knotColor = SplineHandleUtility.elementColor;
var rotationDiscColor = SplineHandleUtility.elementPreselectionColor;
if (hovered)
knotColor = SplineHandleUtility.elementPreselectionColor;
else if (selected)
knotColor = SplineHandleUtility.elementSelectionColor;
Draw(knot.Position, knot.Rotation, knotColor, selected, hovered, rotationDiscColor, k_KnotRotDiscWidthHover);
DrawKnotIndices(knot);
}
internal static void Draw(int controlId, SelectableKnot knot)
{
if (Event.current.GetTypeForControl(controlId) != EventType.Repaint)
return;
if(!knot.IsValid())
return;
var selected = SplineSelection.Contains(knot);
var knotHovered = SplineHandleUtility.IsElementHovered(controlId);
//Retrieving linked knots
EditorSplineUtility.GetKnotLinks(knot, k_KnotBuffer);
var drawLinkedKnotHandle = k_KnotBuffer.Count != 1;
var mainKnot = knot;
SelectableKnot lastHovered = new SelectableKnot();
// Retrieving the last hovered element
// SplineHandleUtility.lastHoveredElement is pointing either to:
// - the hovered Knot and the ID is pointing to the controlID of that knot in that case
// - if a curve is hovered, the element is the knot closest to the hovered part of the curve (start or end knot depending)
// and the controlID is the one of the curve
var lastHoveredElementIsKnot = SplineHandleUtility.lastHoveredElement is SelectableKnot;
if (lastHoveredElementIsKnot)
lastHovered = (SelectableKnot)SplineHandleUtility.lastHoveredElement;
var isCurveId = SplineHandles.IsCurveId(SplineHandleUtility.lastHoveredElementId);
var curveIsHovered = lastHoveredElementIsKnot &&
k_KnotBuffer.Contains(lastHovered) &&
isCurveId;
var hovered = knotHovered || (curveIsHovered && knot.Equals(lastHovered));
if (drawLinkedKnotHandle)
{
if (curveIsHovered)
{
drawLinkedKnotHandle = false;
if (!knot.Equals(lastHovered))
{
if (!SplineSelection.Contains(knot))
return;
hovered = false;
mainKnot = lastHovered;
}
}
else
{
foreach (var linkedKnot in k_KnotBuffer)
{
if (!hovered)
{
var kSelected = SplineSelection.Contains(linkedKnot);
// If the current knot in not selected but other linked knots are, skip rendering
if (!selected && kSelected)
return;
// If current knot is selected but not k, don't consider k as a potential knot
if (selected && !kSelected)
{
drawLinkedKnotHandle = false;
continue;
}
}
//Main knot is the older one, the one on the spline of lowest range and the knot of lowest index
if ((!SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(linkedKnot.SplineInfo)) &&
(linkedKnot.SplineInfo.Index < mainKnot.SplineInfo.Index ||
linkedKnot.SplineInfo.Index == mainKnot.SplineInfo.Index && linkedKnot.KnotIndex < mainKnot.KnotIndex))
mainKnot = linkedKnot;
}
}
}
//Hovered might not be available if a TRS tool is in use
hovered &= SplineHandleUtility.IsHoverAvailableForSplineElement();
var knotColor = SplineHandleUtility.elementColor;
var highlightColor = SplineHandleUtility.elementPreselectionColor;
var rotationDiscColor = SplineHandleUtility.elementPreselectionColor;
if (hovered)
{
knotColor = SplineHandleUtility.elementPreselectionColor;
highlightColor = SplineHandleUtility.elementPreselectionColor;
}
else if (selected)
{
knotColor = SplineHandleUtility.elementSelectionColor;
highlightColor = SplineHandleUtility.elementSelectionColor;
}
if (SplineHandleUtility.canDrawOnCurves && (hovered || selected))
{
using (new Handles.DrawingScope(highlightColor))
CurveHandles.DoCurveHighlightCap(knot);
}
if (knot.Equals(mainKnot))
{
s_Knots.Add((knot, selected, hovered, knotColor, rotationDiscColor, drawLinkedKnotHandle));
DrawKnotIndices(knot);
}
}
internal static void ClearVisibleKnots()
{
if (Event.current.type != EventType.Repaint)
return;
s_Knots.Clear();
}
internal static void DrawVisibleKnots()
{
if (Event.current.type != EventType.Repaint)
return;
foreach (var knotInfo in s_Knots)
Draw(knotInfo.knot.Position, knotInfo.knot.Rotation, knotInfo.knotColor, knotInfo.selected, knotInfo.hovered, knotInfo.discColor, k_KnotRotDiscWidthHover, knotInfo.linkedKnot);
}
static void DrawKnotIndices(SelectableKnot knot)
{
if (!SplineHandleSettings.ShowKnotIndices)
return;
var hasLinkedKnots = !(k_KnotBuffer.Count == 1 && k_KnotBuffer.Contains(knot));
if (k_KnotBuffer != null && k_KnotBuffer.Count > 0 && hasLinkedKnots)
{
var stringBuilder = new System.Text.StringBuilder("[");
for (var i = 0; i < k_KnotBuffer.Count; i++)
{
stringBuilder.Append($"({k_KnotBuffer[i].SplineInfo.Index},{k_KnotBuffer[i].KnotIndex})");
if (i != k_KnotBuffer.Count - 1)
stringBuilder.Append(", ");
}
stringBuilder.Append("]");
Handles.Label(knot.Position, stringBuilder.ToString());
}
else
{
Handles.Label(knot.Position, $"[{knot.KnotIndex}]");
}
}
internal static void Draw(SelectableKnot knot, Color knotColor, bool selected, bool hovered)
{
EditorSplineUtility.GetKnotLinks(knot, k_KnotBuffer);
var mainKnot = knot;
if(k_KnotBuffer.Count != 1)
{
foreach(var k in k_KnotBuffer)
{
//Main knot is the older one, the one on the spline of lowest range and the knot of lowest index
if(k.SplineInfo.Index < mainKnot.SplineInfo.Index ||
k.SplineInfo.Index == mainKnot.SplineInfo.Index && k.KnotIndex < mainKnot.KnotIndex)
mainKnot = k;
}
}
if(!mainKnot.Equals(knot))
return;
Draw(knot.Position, knot.Rotation, knotColor, selected, hovered, knotColor, k_KnotRotDiscWidthDefault, k_KnotBuffer.Count != 1);
}
internal static void Draw(Vector3 position, Quaternion rotation, Color knotColor, bool selected, bool hovered)
{
Draw(position, rotation, knotColor, selected, hovered, knotColor, k_KnotRotDiscWidthDefault);
}
static void UpdateHandlePoints(float size)
{
var startIndex = 5;
k_HandlePoints[startIndex] = Vector3.forward * k_KnotRotDiscRadius * size;
var r = Vector3.right * SplineHandleUtility.knotDiscRadiusFactorDefault * size;
//The first and last element should be in the middle of the points list to get a better visual
for(int i = 0; i < 9; i++)
{
var index = ( i + startIndex + 1 ) % k_HandlePoints.Length;
var pos = Quaternion.Euler(0, ( 1f - i / 8f ) * 180f, 0) * r;
k_HandlePoints[index] = pos;
if(index == k_HandlePoints.Length - 1)
{
startIndex += 1;
k_HandlePoints[0] = pos;
}
}
}
internal static void DrawInformativeKnot(SelectableKnot knot, float sizeFactor = 0.5f)
{
if (Event.current.type != EventType.Repaint)
return;
EditorSplineUtility.GetKnotLinks(knot, k_KnotBuffer);
var drawLinkedKnotHandle = k_KnotBuffer.Count != 1;
if(drawLinkedKnotHandle)
{
foreach(var k in k_KnotBuffer)
{
//If the current knot in not selected but other linked knots are, skip rendering
if(SplineSelection.Contains(k))
return;
}
}
DrawInformativeKnotVisual(knot, SplineHandleUtility.lineColor, sizeFactor);
}
static void DrawInformativeKnotVisual(SelectableKnot knot, Color knotColor, float sizeFactor = 0.5f)
{
var position = knot.Position;
var size = HandleUtility.GetHandleSize(position);
using(new Handles.DrawingScope(knotColor, Matrix4x4.TRS(position, knot.Rotation, Vector3.one)))
{
Handles.DrawSolidDisc(Vector3.zero, Vector3.up, size * SplineHandleUtility.knotDiscRadiusFactorSelected * sizeFactor);
}
}
static void Draw(Vector3 position, Quaternion rotation, Color knotColor, bool selected, bool hovered, Color discColor, float rotationDiscWidth, bool linkedKnots = false)
{
var size = HandleUtility.GetHandleSize(position);
using (new ZTestScope(CompareFunction.Less))
{
using (new Handles.DrawingScope(knotColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
DrawKnotShape(size, selected, linkedKnots);
if (hovered)
{
using (new Handles.DrawingScope(discColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
SplineHandleUtility.DrawAAWireDisc(Vector3.zero, Vector3.up, k_KnotRotDiscRadius * size, rotationDiscWidth);
}
}
using (new ZTestScope(CompareFunction.Greater))
{
var newKnotColor = new Color(knotColor.r, knotColor.g, knotColor.b, knotColor.a * k_ColorAlphaFactor);
using (new Handles.DrawingScope(newKnotColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
DrawKnotShape(size, selected, linkedKnots);
if (hovered)
{
var newDiscColor = new Color(discColor.r, discColor.g,
discColor.b, discColor.a * k_ColorAlphaFactor);
using (new Handles.DrawingScope(newDiscColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
SplineHandleUtility.DrawAAWireDisc(Vector3.zero, Vector3.up, k_KnotRotDiscRadius * size, rotationDiscWidth);
}
}
}
static void DrawKnotShape(float size, bool selected, bool linkedKnots)
{
if (!linkedKnots)
{
UpdateHandlePoints(size);
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, k_KnotHandleWidth, k_HandlePoints);
if (selected)
Handles.DrawAAConvexPolygon(k_HandlePoints);
}
else
{
// Knot disc
if (selected)
{
var radius = selected ? SplineHandleUtility.knotDiscRadiusFactorSelected : SplineHandleUtility.knotDiscRadiusFactorHover;
Handles.DrawSolidDisc(Vector3.zero, Vector3.up, radius * size);
}
else
Handles.DrawWireDisc(Vector3.zero, Vector3.up, SplineHandleUtility.knotDiscRadiusFactorDefault * size, SplineHandleUtility.handleWidth * SplineHandleUtility.aliasedLineSizeMultiplier);
}
Handles.DrawAAPolyLine(Vector3.zero, Vector3.up * 2f * SplineHandleUtility.sizeFactor * size);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b96c3a8372a8f9945bc2e2b906ac79a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,228 @@
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
class SplineElementRectSelector
{
enum Mode
{
None,
Replace,
Add,
Subtract
}
static class Styles
{
public static readonly GUIStyle selectionRect = GUI.skin.FindStyle("selectionRect");
}
Rect m_Rect;
Vector2 m_StartPos;
Mode m_Mode;
Mode m_InitialMode;
static readonly HashSet<ISelectableElement> s_SplineElementsCompareSet = new HashSet<ISelectableElement>();
static readonly List<ISelectableElement> s_SplineElementsBuffer = new List<ISelectableElement>();
static readonly HashSet<ISelectableElement> s_PreRectSelectionElements = new HashSet<ISelectableElement>();
public void OnGUI(IReadOnlyList<SplineInfo> splines)
{
int id = GUIUtility.GetControlID(FocusType.Passive);
Event evt = Event.current;
switch (evt.GetTypeForControl(id))
{
case EventType.Layout:
case EventType.MouseMove:
HandleUtility.AddDefaultControl(id);
if (m_Mode != Mode.None)
{
// If we've started rect select in Add or Subtract modes, then if we were in a Replace
// mode just before (i.e. the shift or action has been released temporarily),
// we need to bring back the pre rect selection elements into current selection.
if (m_InitialMode != Mode.Replace && RefreshSelectionMode())
{
SplineSelection.Clear();
s_SplineElementsCompareSet.Clear();
if (m_Mode != Mode.Replace)
{
foreach (var element in s_PreRectSelectionElements)
SplineSelection.Add(element);
}
m_Rect = GetRectFromPoints(m_StartPos, evt.mousePosition);
UpdateSelection(m_Rect, splines);
}
}
break;
case EventType.Repaint:
if (GUIUtility.hotControl == id && m_Rect.size != Vector2.zero)
{
Handles.BeginGUI();
Styles.selectionRect.Draw(m_Rect, GUIContent.none, false, false, false, false);
Handles.EndGUI();
}
break;
case EventType.MouseDown:
if (SplineHandles.ViewToolActive())
return;
if (HandleUtility.nearestControl == id && evt.button == 0)
{
m_StartPos = evt.mousePosition;
m_Rect = new Rect(Vector3.zero, Vector2.zero);
BeginSelection(splines);
GUIUtility.hotControl = id;
evt.Use();
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == id)
{
m_Rect = GetRectFromPoints(m_StartPos, evt.mousePosition);
evt.Use();
UpdateSelection(m_Rect, splines);
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == id)
{
GUIUtility.hotControl = 0;
evt.Use();
EndSelection(m_Rect, splines);
}
break;
}
}
void BeginSelection(IReadOnlyList<SplineInfo> splines)
{
RefreshSelectionMode();
m_InitialMode = m_Mode;
s_SplineElementsCompareSet.Clear();
s_SplineElementsBuffer.Clear();
if (m_Mode == Mode.Replace)
{
SplineSelection.Clear();
s_PreRectSelectionElements.Clear();
}
else
SplineSelection.GetElements(splines, s_PreRectSelectionElements);
}
void UpdateSelection(Rect rect, IReadOnlyList<SplineInfo> splines)
{
//Get all elements in rect
s_SplineElementsBuffer.Clear();
for (int i = 0; i < splines.Count; ++i)
{
var splineData = splines[i];
for (int j = 0; j < splineData.Spline.Count; ++j)
if(!SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(splineData))
GetElementSelection(rect, splineData, j, s_SplineElementsBuffer);
}
foreach (var splineElement in s_SplineElementsBuffer)
{
//Compare current frame buffer with last frame's to find new additions/removals
var wasInRectLastFrame = s_SplineElementsCompareSet.Remove(splineElement);
if (m_Mode == Mode.Replace || m_Mode == Mode.Add)
{
var canAdd = m_Mode == Mode.Replace ? true : !s_PreRectSelectionElements.Contains(splineElement);
if (!wasInRectLastFrame && canAdd)
SplineSelection.Add(splineElement);
}
else if (m_Mode == Mode.Subtract && !wasInRectLastFrame)
{
SplineSelection.Remove(splineElement);
}
}
//Remaining spline elements from last frame are removed from selection (or added if mode is subtract)
foreach (var splineElement in s_SplineElementsCompareSet)
{
if (m_Mode == Mode.Replace || m_Mode == Mode.Add)
{
// If we're in Add mode, don't remove elements that were in select prior to rect selection
if (m_Mode == Mode.Add && s_PreRectSelectionElements.Contains(splineElement))
continue;
SplineSelection.Remove(splineElement);
}
else if (m_Mode == Mode.Subtract && s_PreRectSelectionElements.Contains(splineElement))
SplineSelection.Add(splineElement);
}
//Move current elements buffer to hash set for next frame compare
s_SplineElementsCompareSet.Clear();
foreach (var splineElement in s_SplineElementsBuffer)
s_SplineElementsCompareSet.Add(splineElement);
}
bool RefreshSelectionMode()
{
var modeBefore = m_Mode;
if (Event.current.shift)
m_Mode = Mode.Add;
else if (EditorGUI.actionKey)
m_Mode = Mode.Subtract;
else
m_Mode = Mode.Replace;
// Return true if the mode has changed
return m_Mode != modeBefore;
}
void GetElementSelection(Rect rect, SplineInfo splineInfo, int index, List<ISelectableElement> results)
{
var knot = splineInfo.Spline[index];
var localToWorld = splineInfo.LocalToWorld;
var worldKnot = knot.Transform(localToWorld);
Vector3 screenSpace = HandleUtility.WorldToGUIPointWithDepth(worldKnot.Position);
if (screenSpace.z > 0 && rect.Contains(screenSpace))
results.Add(new SelectableKnot(splineInfo, index));
var tangentIn = new SelectableTangent(splineInfo, index, BezierTangent.In);
if (SplineSelectionUtility.IsSelectable(tangentIn))
{
screenSpace = HandleUtility.WorldToGUIPointWithDepth(worldKnot.Position + math.rotate(worldKnot.Rotation, worldKnot.TangentIn));
if (screenSpace.z > 0 && rect.Contains(screenSpace))
results.Add(tangentIn);
}
var tangentOut = new SelectableTangent(splineInfo, index, BezierTangent.Out);
if (SplineSelectionUtility.IsSelectable(tangentOut))
{
screenSpace = HandleUtility.WorldToGUIPointWithDepth(worldKnot.Position + math.rotate(worldKnot.Rotation, worldKnot.TangentOut));
if (screenSpace.z > 0 && rect.Contains(screenSpace))
results.Add(tangentOut);
}
}
void EndSelection(Rect rect, IReadOnlyList<SplineInfo> splines)
{
m_Mode = m_InitialMode = Mode.None;
}
static Rect GetRectFromPoints(Vector2 a, Vector2 b)
{
Vector2 min = new Vector2(Mathf.Min(a.x, b.x), Mathf.Min(a.y, b.y));
Vector2 max = new Vector2(Mathf.Max(a.x, b.x), Mathf.Max(a.y, b.y));
return new Rect(min, max - min);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d68b08563e248f4a02b78fb710d36bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System;
using UnityEngine;
using UnityEditor.SettingsManagement;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class SplineHandleSettings
{
[UserSetting]
static readonly Pref<bool> s_FlowDirectionEnabled = new Pref<bool>("Handles.FlowDirectionEnabled", true);
[UserSetting]
static readonly Pref<bool> s_ShowAllTangents = new Pref<bool>("Handles.ShowAllTangents", true);
static readonly Pref<bool> s_ShowKnotIndices = new Pref<bool>("Handles.ShowKnotIndices", false);
[UserSetting]
static UserSetting<bool> s_ShowMesh = new UserSetting<bool>(PathSettings.instance,"Handles.Debug.ShowMesh", false, SettingsScope.User);
[UserSetting]
static UserSetting<Color> s_MeshColor = new UserSetting<Color>(PathSettings.instance, "Handles.Debug.MeshColor", Color.white, SettingsScope.User);
[UserSetting]
static UserSetting<float> s_MeshSize = new UserSetting<float>(PathSettings.instance, "Handles.Debug.MeshSize", 0.1f, SettingsScope.User);
[UserSetting]
static UserSetting<int> s_MeshResolution = new UserSetting<int>(PathSettings.instance, "Handles.Debug.MeshResolution", SplineUtility.DrawResolutionDefault, SettingsScope.User);
[UserSettingBlock("Spline Mesh")]
static void HandleDebugPreferences(string searchContext)
{
EditorGUI.BeginChangeCheck();
s_MeshColor.value = SettingsGUILayout.SettingsColorField("Color", s_MeshColor, searchContext);
s_MeshSize.value = SettingsGUILayout.SettingsSlider("Size", s_MeshSize, 0.01f, 1f, searchContext);
s_MeshResolution.value = SettingsGUILayout.SettingsSlider("Resolution", s_MeshResolution, 4, 100, searchContext);
if(EditorGUI.EndChangeCheck())
SceneView.RepaintAll();
}
public static bool FlowDirectionEnabled
{
get => s_FlowDirectionEnabled;
set => s_FlowDirectionEnabled.SetValue(value);
}
public static bool ShowAllTangents
{
get => s_ShowAllTangents;
set => s_ShowAllTangents.SetValue(value);
}
public static bool ShowKnotIndices
{
get => s_ShowKnotIndices;
set => s_ShowKnotIndices.SetValue(value);
}
public static bool ShowMesh
{
get => s_ShowMesh;
set => s_ShowMesh.SetValue(value);
}
public static Color SplineMeshColor => s_MeshColor;
public static float SplineMeshSize => s_MeshSize;
public static int SplineMeshResolution => s_MeshResolution;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8bff491709f26ca4d924ad2c5f505fb0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,464 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
/// <summary>
/// This class provides the ability to draw a handle for a spline.
/// </summary>
public static class SplineHandles
{
/// <summary>
/// The scope used to draw a spline. This is managing several purposes when using SplineHandles.DrawSomething().
/// This ensure selection is working properly, and that hovering an element is highlighting the correct related
/// elements (for instance hovering a tangent highlights the opposite one when needed and the knot as well).
/// </summary>
public class SplineHandleScope : IDisposable
{
int m_NearestControl;
/// <summary>
/// Defines a new scope to draw spline elements in.
/// </summary>
public SplineHandleScope()
{
m_NearestControl = HandleUtility.nearestControl;
Clear();
SplineHandleUtility.minElementId = GUIUtility.GetControlID(FocusType.Passive);
}
/// <summary>
/// Called automatically when the `SplineHandleScope` is disposed.
/// </summary>
public void Dispose()
{
SplineHandleUtility.maxElementId = GUIUtility.GetControlID(FocusType.Passive);
var evtType = Event.current.type;
if ( (evtType == EventType.MouseMove || evtType == EventType.Layout)
&& HandleUtility.nearestControl == m_NearestControl)
SplineHandleUtility.ResetLastHoveredElement();
}
}
/// <summary>
/// The color of sections of spline curve handles that are behind objects in the Scene view.
/// </summary>
public static Color lineBehindColor => SplineHandleUtility.lineBehindColor;
/// <summary>
/// The color of sections of spline curves handles that are in front of objects in the Scene view.
/// </summary>
public static Color lineColor => SplineHandleUtility.lineColor;
/// <summary>
/// The color of tangent handles for a spline.
/// </summary>
public static Color tangentColor => SplineHandleUtility.tangentColor;
/// <summary>
/// The distance to pick a spline knot, tangent, or curve handle at.
/// </summary>
public static float pickingDistance => SplineHandleUtility.pickingDistance;
static List<int> s_ControlIDs = new();
static List<int> s_CurveIDs = new();
static readonly List<SelectableKnot> k_KnotBuffer = new ();
static Dictionary<SelectableKnot, int> s_KnotsIDs = new ();
// todo Tools.viewToolActive should be handling the modifier check, but 2022.2 broke this
internal static bool ViewToolActive()
{
return Tools.viewToolActive || Tools.current == Tool.View || (Event.current.modifiers & EventModifiers.Alt) == EventModifiers.Alt;
}
static void Clear()
{
s_CurveIDs.Clear();
s_KnotsIDs.Clear();
}
/// <summary>
/// Creates handles for a set of splines. These handles display the knots, tangents, and segments of a spline.
/// These handles support selection and the direct manipulation of spline elements.
/// </summary>
/// <param name="splines">The set of splines to draw handles for.</param>
public static void DoHandles(IReadOnlyList<SplineInfo> splines)
{
Profiler.BeginSample("SplineHandles.DoHandles");
using (new SplineHandleScope())
{
// Drawing done in two separate passes to make sure the curves are drawn behind the spline elements.
// Draw the curves.
for (int i = 0; i < splines.Count; ++i)
{
DoSegmentsHandles(splines[i]);
}
DoKnotsAndTangentsHandles(splines);
}
Profiler.EndSample();
}
internal static bool IsCurveId(int id)
{
return s_CurveIDs.Contains(id);
}
/// <summary>
/// Creates knot and tangent handles for a spline. Call `DoKnotsAndTangentsHandles` in a `SplineHandleScope`.
/// This method is used internally by `DoHandles`.
/// </summary>
/// <param name="spline">The spline to create knot and tangent handles for.</param>
public static void DoKnotsAndTangentsHandles(SplineInfo spline)
{
SplineHandleUtility.UpdateElementColors();
KnotHandles.ClearVisibleKnots();
// Draw the spline elements.
DrawSplineElements(spline);
//Drawing knots on top of all other elements and above other splines
KnotHandles.DrawVisibleKnots();
}
/// <summary>
/// Creates knot and tangent handles for multiple splines. Call `DoKnotsAndTangentsHandles` in a `SplineHandleScope`.
/// This method is used internally by `DoHandles`.
/// </summary>
/// <param name="splines">The splines to create knot and tangent handles for.</param>
public static void DoKnotsAndTangentsHandles(IReadOnlyList<SplineInfo> splines)
{
SplineHandleUtility.UpdateElementColors();
KnotHandles.ClearVisibleKnots();
// Draw the spline elements.
for (int i = 0; i < splines.Count; ++i)
DrawSplineElements(splines[i]);
//Drawing knots on top of all other elements and above other splines
KnotHandles.DrawVisibleKnots();
}
/// <summary>
/// Creates segment handles for a spline. Call `DoCurvesHandles` in a `SplineHandleScope`.
/// This method is used internally by `DrawHandles`.
/// </summary>
/// <param name="splineInfo">The splineInfo of the spline to draw knots and tangents for.</param>
public static void DoSegmentsHandles(SplineInfo splineInfo)
{
var spline = splineInfo.Spline;
if (spline == null || spline.Count < 2)
return;
var localToWorld = splineInfo.LocalToWorld;
// If the spline isn't closed, skip the last index of the spline
int lastIndex = spline.Closed ? spline.Count - 1 : spline.Count - 2;
if (SplineHandleSettings.ShowMesh)
{
using (var nativeSpline = new NativeSpline(spline, localToWorld))
using (var mesh = new SplineMeshHandle<NativeSpline>())
using (new ZTestScope(UnityEngine.Rendering.CompareFunction.Less))
{
mesh.Do(nativeSpline, SplineHandleSettings.SplineMeshSize, SplineHandleSettings.SplineMeshColor, SplineHandleSettings.SplineMeshResolution);
}
}
s_ControlIDs.Clear();
for (int idIndex = 0; idIndex < lastIndex + 1; ++idIndex)
{
var id = GUIUtility.GetControlID(FocusType.Passive);
s_ControlIDs.Add(id);
s_CurveIDs.Add(id);
}
var drawHandlesAsActive = !SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(splineInfo);
//Draw all the curves at once
SplineCacheUtility.GetCachedPositions(spline, out var positions);
using (new Handles.DrawingScope(SplineHandleUtility.lineColor, localToWorld))
{
using (new ZTestScope(CompareFunction.Less))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, 4f, positions);
}
using (new Handles.DrawingScope(SplineHandleUtility.lineBehindColor, localToWorld))
{
using (new ZTestScope(CompareFunction.Greater))
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex, 4f, positions);
}
if (drawHandlesAsActive)
{
for (int curveIndex = 0; curveIndex < lastIndex + 1; ++curveIndex)
{
if (SplineHandleSettings.FlowDirectionEnabled && Event.current.type == EventType.Repaint)
{
var curve = spline.GetCurve(curveIndex).Transform(localToWorld);
CurveHandles.DrawFlow(curve, spline, curveIndex);
}
}
}
for (int curveIndex = 0; curveIndex < lastIndex + 1; ++curveIndex)
{
CurveHandles.DrawWithHighlight(
s_ControlIDs[curveIndex],
spline,
curveIndex,
localToWorld,
new SelectableKnot(splineInfo, curveIndex),
new SelectableKnot(splineInfo, SplineUtility.NextIndex(curveIndex, spline.Count, spline.Closed)),
drawHandlesAsActive);
}
SplineHandleUtility.canDrawOnCurves = true;
}
static void DrawSplineElements(SplineInfo splineInfo)
{
var spline = splineInfo.Spline;
var drawHandlesAsActive = !SplineSelection.HasActiveSplineSelection() || SplineSelection.Contains(splineInfo);
if (drawHandlesAsActive)
{
for (int knotIndex = 0; knotIndex < spline.Count; ++knotIndex)
DrawKnotWithTangentsHandles_Internal(new SelectableKnot(splineInfo, knotIndex));
}
else
{
for (int knotIndex = 0; knotIndex < spline.Count; ++knotIndex)
KnotHandles.DrawInformativeKnot(new SelectableKnot(splineInfo, knotIndex));
}
}
/// <summary>
/// Creates handles for a knot and its tangents if those tangents are modifiable.
/// These handles support the selection and direct manipulation of spline elements.
/// Call `DoKnotWithTangentsHandles` in a `SplineHandleScope`.
/// </summary>
/// <param name="knot">The knot to draw handles for.</param>
public static void DoKnotWithTangentsHandles(SelectableKnot knot)
{
KnotHandles.ClearVisibleKnots();
DrawKnotWithTangentsHandles_Internal(knot);
KnotHandles.DrawVisibleKnots();
}
static void DrawKnotWithTangentsHandles_Internal(SelectableKnot knot)
{
var splineInfo = knot.SplineInfo;
if (SplineUtility.AreTangentsModifiable(splineInfo.Spline.GetTangentMode(knot.KnotIndex)))
DoTangentsHandles(knot);
DrawKnotHandles_Internal(knot);
}
/// <summary>
/// Create handles for a knot. These handles the support selection and direct manipulation of spline elements.
/// Call `DoKnotHandles` in a `SplineHandleScope`.
/// </summary>
/// <param name="knot">The knot to draw handles for.</param>
public static void DoKnotHandles(SelectableKnot knot)
{
KnotHandles.ClearVisibleKnots();
DrawKnotHandles_Internal(knot);
KnotHandles.DrawVisibleKnots();
}
static void DrawKnotHandles_Internal(SelectableKnot knot)
{
var id = GetKnotID(knot);
SelectionHandle(id, knot);
KnotHandles.Draw(id, knot);
}
/// <summary>
/// Create handles for a knot's tangents if those tangents are modifiable. `DoTangentsHandles` does not create handles for the knot.
/// These handles support the selection and direct manipulation of the spline elements.
/// Call `DoTangentsHandles` in a `SplineHandleScope`.
/// </summary>
/// <param name="knot">The knot to draw tangent handles for.</param>
public static void DoTangentsHandles(SelectableKnot knot)
{
if(!knot.IsValid())
return;
var splineInfo = knot.SplineInfo;
var spline = splineInfo.Spline;
var knotIndex = knot.KnotIndex;
var tangentIn = new SelectableTangent(splineInfo, knotIndex, BezierTangent.In);
var tangentOut = new SelectableTangent(splineInfo, knotIndex, BezierTangent.Out);
var controlIdIn = GUIUtility.GetControlID(FocusType.Passive);
var controlIdOut = GUIUtility.GetControlID(FocusType.Passive);
// Tangent In
if (GUIUtility.hotControl == controlIdIn || SplineHandleUtility.ShouldShowTangent(tangentIn) && (spline.Closed || knotIndex != 0))
{
SelectionHandle(controlIdIn, tangentIn);
TangentHandles.Draw(controlIdIn, tangentIn);
}
// Tangent Out
if (GUIUtility.hotControl == controlIdOut || SplineHandleUtility.ShouldShowTangent(tangentOut) && (spline.Closed || knotIndex + 1 != spline.Count))
{
SelectionHandle(controlIdOut, tangentOut);
TangentHandles.Draw(controlIdOut, tangentOut);
}
}
static int GetKnotID(SelectableKnot knot)
{
EditorSplineUtility.GetKnotLinks(knot, k_KnotBuffer);
//If a linked knot as already been assigned, return the same id
if (s_KnotsIDs.ContainsKey(k_KnotBuffer[0]))
return s_KnotsIDs[k_KnotBuffer[0]];
//else compute a new id and record it
var id = GUIUtility.GetControlID(FocusType.Passive);
s_KnotsIDs.Add(k_KnotBuffer[0], id);
return id;
}
static void SelectionHandle<T>(int id, T element)
where T : struct, ISelectableElement
{
Event evt = Event.current;
EventType eventType = evt.GetTypeForControl(id);
switch (eventType)
{
case EventType.Layout:
case EventType.MouseMove:
if (!ViewToolActive())
{
HandleUtility.AddControl(id, SplineHandleUtility.DistanceToCircle(element.Position, SplineHandleUtility.pickingDistance));
if (GUIUtility.hotControl == 0 && HandleUtility.nearestControl == id)
SplineHandleUtility.SetLastHoveredElement(element, id);
}
break;
case EventType.MouseDown:
if (HandleUtility.nearestControl == id && evt.button == 0)
{
GUIUtility.hotControl = id;
evt.Use();
DirectManipulation.BeginDrag(element.Position, EditorSplineUtility.GetElementRotation(element));
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == id)
{
EditorSplineUtility.RecordObject(element.SplineInfo, "Move Knot");
var pos = TransformOperation.ApplySmartRounding(DirectManipulation.UpdateDrag(id));
if (element is SelectableTangent tangent)
EditorSplineUtility.ApplyPositionToTangent(tangent, pos);
else
element.Position = pos;
GUI.changed = true;
evt.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == id && evt.button == 0)
{
if (!DirectManipulation.IsDragging)
SplineSelectionUtility.HandleSelection(element);
DirectManipulation.EndDrag();
GUI.changed = true;
evt.Use();
GUIUtility.hotControl = 0;
}
break;
case EventType.Repaint:
DirectManipulation.DrawHandles(id, element.Position);
break;
}
}
/// <summary>
/// Draws a handle for a spline.
/// </summary>
/// <param name="spline">The target spline.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
public static void DoSpline<T>(T spline) where T : ISpline => DoSpline(-1, spline);
/// <summary>
/// Draws a handle for a spline.
/// </summary>
/// <param name="controlID">The spline mesh controlID.</param>
/// <param name="spline">The target spline.</param>
/// <typeparam name="T">A type implementing ISpline.</typeparam>
public static void DoSpline<T>(int controlID, T spline) where T : ISpline
{
for(int i = 0; i < spline.GetCurveCount(); ++i)
CurveHandles.Draw(controlID, spline.GetCurve(i));
}
/// <summary>
/// Draws a handle for a <see cref="BezierCurve"/>.
/// </summary>
/// <param name="curve">The <see cref="BezierCurve"/> to create handles for.</param>
public static void DoCurve(BezierCurve curve) => CurveHandles.Draw(-1, curve);
/// <summary>
/// Draws a handle for a <see cref="BezierCurve"/>.
/// </summary>
/// <param name="controlID">The spline mesh controlID.</param>
/// <param name="curve">The <see cref="BezierCurve"/> to create handles for.</param>
public static void DoCurve(int controlID, BezierCurve curve) => CurveHandles.Draw(controlID, curve);
/// <summary>
/// Draws handles for a knot. These handles are drawn only during repaint events and not on selection.
/// </summary>
/// <param name="knot">The <see cref="SelectableKnot"/> to create handles for.</param>
/// <param name="selected">Set to true to draw the knot handle as a selected element.</param>
/// <param name="hovered">Set to true to draw the knot handle as a hovered element.</param>
public static void DrawKnot(SelectableKnot knot, bool selected = false, bool hovered = false)
=> DrawKnot(-1, knot, selected, hovered);
/// <summary>
/// Draws handles for a knot. These handles are drawn only during repaint events and not on selection.
/// </summary>
/// <param name="controlID">The controlID of the tangent to create handles for.</param>
/// <param name="knot">The <see cref="SelectableKnot"/> to create handles for.</param>
/// <param name="selected">Set to true to draw the knot handle as a selected element.</param>
/// <param name="hovered">Set to true to draw the knot handle as a hovered element.</param>
public static void DrawKnot(int controlID, SelectableKnot knot, bool selected = false, bool hovered = false)
{
KnotHandles.Do(controlID, knot, selected, hovered);
}
/// <summary>
/// Draws handles for a tangent. These handles are drawn only during repaint events and not on selection.
/// </summary>
/// <param name="tangent">The <see cref="SelectableTangent"/> to create handles for.</param>
/// <param name="selected">Set to true to draw the tangent handle as a selected element.</param>
/// <param name="hovered">Set to true to draw the tangent handle as a hovered element.</param>
public static void DrawTangent(SelectableTangent tangent, bool selected = false, bool hovered = false) => DrawTangent(-1, tangent, selected, hovered);
/// <summary>
/// Draws handles for a tangent. These handles are drawn only during repaint events and not on selection.
/// </summary>
/// <param name="controlID">The controlID of the tangent to create handles for.</param>
/// <param name="tangent">The <see cref="SelectableTangent"/> to create handles for.</param>
/// <param name="selected">Set to true to draw the tangent handle as a selected element.</param>
/// <param name="hovered">Set to true to draw the tangent handle as a hovered element.</param>
public static void DrawTangent(int controlID, SelectableTangent tangent, bool selected = false, bool hovered = false)
{
TangentHandles.Do(controlID, tangent, selected, hovered);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa11edcb768491b4b8b90ccf074e663f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,188 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
using Object = UnityEngine.Object;
namespace UnityEditor.Splines
{
/// <summary>
/// Creates a cylinder mesh along a spline.
/// </summary>
/// <typeparam name="T">The type of ISpline.</typeparam>
public class SplineMeshHandle<T> : IDisposable where T : ISpline
{
class SplineMeshDrawingScope : IDisposable
{
Material m_Material;
int m_HandleZTestId;
int m_BlendSrcModeId;
int m_BlendDstModeId;
float m_PreviousZTest;
int m_PreviousBlendSrcMode;
int m_PreviousBlendDstMode;
public SplineMeshDrawingScope(Material material, Color color)
{
Shader.SetGlobalColor("_HandleColor", color);
Shader.SetGlobalFloat("_HandleSize", 1f);
Shader.SetGlobalMatrix("_ObjectToWorld", Handles.matrix);
if (material == null)
{
m_Material = HandleUtility.handleMaterial;
m_HandleZTestId = Shader.PropertyToID("_HandleZTest");
m_BlendSrcModeId = Shader.PropertyToID("_BlendSrcMode");
m_BlendDstModeId = Shader.PropertyToID("_BlendDstMode");
m_PreviousZTest = m_Material.GetFloat(m_HandleZTestId);
m_PreviousBlendSrcMode = m_Material.GetInt(m_BlendSrcModeId);
m_PreviousBlendDstMode = m_Material.GetInt(m_BlendDstModeId);
m_Material.SetFloat(m_HandleZTestId, (float)Handles.zTest);
m_Material.SetInt(m_BlendSrcModeId, (int)UnityEngine.Rendering.BlendMode.One);
m_Material.SetInt(m_BlendDstModeId, (int)UnityEngine.Rendering.BlendMode.One);
m_Material.SetPass(0);
}
else
material.SetPass(0);
}
public void Dispose()
{
if (m_Material != null)
{
m_Material.SetFloat(m_HandleZTestId, m_PreviousZTest);
m_Material.SetInt(m_BlendSrcModeId, m_PreviousBlendSrcMode);
m_Material.SetInt(m_BlendDstModeId, m_PreviousBlendDstMode);
}
}
}
Mesh m_Mesh;
Material m_Material;
/// <summary>
/// Creates a new mesh handle. This class implements IDisposable to clean up allocated mesh resources. Call
/// <see cref="Dispose"/> when you are finished with the instance.
/// </summary>
public SplineMeshHandle()
{
m_Mesh = new Mesh()
{
hideFlags = HideFlags.HideAndDontSave
};
m_Material = null;
}
/// <summary>
/// Create a new mesh handle. This class implements IDisposable to clean up allocated mesh resources. Call
/// <see cref="Dispose"/> when you are finished with the instance.
/// </summary>
/// <param name="material">The material to render the cylinder mesh with.</param>
public SplineMeshHandle(Material material) : base()
{
m_Material = material;
}
/// <summary>
/// The material to render this mesh with. If null, a default material is used.
/// </summary>
public Material material
{
get => m_Material;
set => m_Material = value;
}
/// <summary>
/// Draws a 3D mesh from a spline.
/// </summary>
/// <param name="spline">The target spline.</param>
/// <param name="size">The width to use for the spline mesh.</param>
/// <param name="color">The color to use for the spline mesh in normal mode.</param>
/// <param name="resolution">The resolution to use for the mesh, defines the number of segments per unit
/// with default value of <see cref="SplineUtility.DrawResolutionDefault"/>.</param>
public void Do(T spline, float size, Color color, int resolution = SplineUtility.DrawResolutionDefault)
{
if(Event.current.type != EventType.Repaint)
return;
Do(-1, spline, size, color, resolution);
}
/// <summary>
/// Draws a 3D mesh handle from a spline.
/// </summary>
/// <param name="controlID">The spline mesh controlID.</param>
/// <param name="spline">The target spline.</param>
/// <param name="size">The width to use for the spline mesh.</param>
/// <param name="color">The color to use for the spline mesh in normal mode.</param>
/// <param name="resolution">The resolution to use for the mesh, defines the number of segments per unit
/// with default value of <see cref="SplineUtility.DrawResolutionDefault"/>.</param>
public void Do(int controlID, T spline, float size, Color color, int resolution = SplineUtility.DrawResolutionDefault)
{
using (new Handles.DrawingScope(color))
Do(controlID, spline, size, resolution);
}
/// <summary>
/// Draws a 3D mesh from a spline.
/// </summary>
/// <param name="spline">The target spline.</param>
/// <param name="size">The width to use for the spline mesh.</param>
/// <param name="resolution">The resolution to use for the mesh, defines the number of segments per unit
/// with default value of <see cref="SplineUtility.DrawResolutionDefault"/>.</param>
public void Do(T spline, float size, int resolution = SplineUtility.DrawResolutionDefault)
{
if (Event.current.type != EventType.Repaint)
return;
Do(-1, spline, size, resolution);
}
/// <summary>
/// Draws a 3D mesh handle from a spline.
/// </summary>
/// <param name="controlID">The spline mesh controlID.</param>
/// <param name="spline">The target spline.</param>
/// <param name="size">The width to use for the spline mesh.</param>
/// <param name="resolution">The resolution to use for the mesh, defines the number of segments per unit
/// with default value of <see cref="SplineUtility.DrawResolutionDefault"/>.</param>
public void Do(int controlID, T spline, float size, int resolution = SplineUtility.DrawResolutionDefault)
{
var evt = Event.current;
switch (evt.type)
{
case EventType.MouseMove:
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
HandleUtility.AddControl(controlID, SplineUtility.GetNearestPoint(spline, ray, out _, out _));
break;
case EventType.Repaint:
var segments = SplineUtility.GetSubdivisionCount(spline.GetLength(), resolution);
SplineMesh.Extrude(spline, m_Mesh, size, 8, segments, !spline.Closed);
var color = GUIUtility.hotControl == controlID
? Handles.selectedColor
: HandleUtility.nearestControl == controlID
? Handles.preselectionColor
: Handles.color;
using (new SplineMeshDrawingScope(m_Material, color))
Graphics.DrawMeshNow(m_Mesh, Handles.matrix);
break;
}
}
/// <summary>
/// Destroys the 3D mesh.
/// </summary>
public void Dispose() => Object.DestroyImmediate(m_Mesh);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b7822ae247afa5945befac0bc63be0bb
timeCreated: 1654094147

View File

@@ -0,0 +1,238 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Splines;
namespace UnityEditor.Splines
{
static class TangentHandles
{
const float k_ColorAlphaFactor = 0.3f;
const float k_TangentLineWidthDefault = 2f;
const float k_TangentLineWidthHover = 3.5f;
const float k_TangentLineWidthSelected = 4.5f;
const float k_TangentStartOffsetFromKnot = 0.22f;
const float k_TangentEndOffsetFromHandle = 0.11f;
const float k_TangentHandleWidth = 2f;
const float k_TangentRotWidthDefault = 1.5f;
const float k_TangentRotDiscWidth = 3f;
internal static void Do(int controlId, SelectableTangent tangent, bool selected = false, bool hovered = false)
{
var owner = tangent.Owner;
Draw(
controlId,
tangent.Position,
EditorSplineUtility.GetElementRotation(math.length(tangent.LocalPosition) > 0 ? (ISelectableElement)tangent : tangent.Owner),
owner.Position,
selected,
false,
hovered,
false,
owner.Mode,
true);
}
internal static void DrawInformativeTangent(SelectableTangent tangent, bool active = true)
{
DrawInformativeTangent(tangent.Position, tangent.Owner.Position, active);
}
internal static void DrawInformativeTangent(Vector3 position, Vector3 knotPosition, bool active = true)
{
if (Event.current.type != EventType.Repaint)
return;
var tangentColor = SplineHandleUtility.elementColor;
if (!active)
tangentColor = Handles.secondaryColor;
var tangentArmColor = tangentColor == SplineHandleUtility.elementColor
? SplineHandleUtility.tangentColor
: tangentColor;
using (new ColorScope(tangentArmColor))
{
var toTangent = position - knotPosition;
var toTangentNorm = math.normalize(toTangent);
var length = math.length(toTangent);
var knotHandleOffset = HandleUtility.GetHandleSize(knotPosition) * k_TangentStartOffsetFromKnot;
length = Mathf.Max(0f, length - knotHandleOffset);
knotPosition += (Vector3)toTangentNorm * knotHandleOffset;
SplineHandleUtility.DrawLineWithWidth(knotPosition, knotPosition + (Vector3)toTangentNorm * length, k_TangentLineWidthDefault);
}
}
internal static void Draw(Vector3 position, Vector3 knotPosition, float3 normal, bool active = true)
{
var knotToTangentDirection = position - knotPosition;
var rotation = quaternion.LookRotationSafe(knotToTangentDirection, normal);
Draw(-1, position, rotation, knotPosition, false, false, false, TangentMode.Broken, active);
}
internal static void Draw(int controlId, SelectableTangent tangent, bool active = true)
{
var (pos, rot) = SplineCacheUtility.GetTangentPositionAndRotation(tangent);
var owner = tangent.Owner;
Draw(
controlId,
pos,
rot,
owner.Position,
SplineSelection.Contains(tangent),
SplineSelection.Contains(tangent.OppositeTangent),
SplineHandleUtility.IsLastHoveredElement(tangent.OppositeTangent),
owner.Mode,
active);
}
static void Draw(int controlId, Vector3 position, Quaternion rotation, Vector3 knotPosition, bool selected, bool oppositeSelected, bool oppositeHovered, TangentMode mode, bool active)
{
var hovered = SplineHandleUtility.IsHoverAvailableForSplineElement() && SplineHandleUtility.IsElementHovered(controlId);
Draw(controlId, position, rotation, knotPosition, selected, oppositeSelected, hovered, oppositeHovered, mode, active);
}
static void Draw(int controlId, Vector3 position, Quaternion rotation, Vector3 knotPosition, bool selected, bool oppositeSelected, bool hovered, bool oppositeHovered, TangentMode mode, bool active)
{
if (Event.current.GetTypeForControl(controlId) != EventType.Repaint)
return;
var size = HandleUtility.GetHandleSize(position);
var tangentColor = SplineHandleUtility.elementColor;
if (hovered)
tangentColor = SplineHandleUtility.elementPreselectionColor;
else if (selected)
tangentColor = SplineHandleUtility.elementSelectionColor;
if (!active)
tangentColor = Handles.secondaryColor;
var tangentArmColor = tangentColor == SplineHandleUtility.elementColor ?
SplineHandleUtility.tangentColor :
tangentColor;
if (mode == TangentMode.Mirrored)
{
if(oppositeHovered)
tangentArmColor = SplineHandleUtility.elementPreselectionColor;
else if(tangentArmColor == SplineHandleUtility.tangentColor && oppositeSelected)
tangentArmColor =SplineHandleUtility.elementSelectionColor;
}
var rotationDiscWidth = k_TangentRotWidthDefault;
if (hovered)
rotationDiscWidth = k_TangentRotDiscWidth;
using (new ZTestScope(CompareFunction.Less))
{
// Draw tangent arm.
using (new ColorScope(tangentArmColor))
DrawTangentArm(position, knotPosition, size, mode, selected, hovered, oppositeSelected, oppositeHovered);
// Draw tangent shape.
using (new Handles.DrawingScope(tangentColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
DrawTangentShape(size, selected);
}
using (new ZTestScope(CompareFunction.Greater))
{
// Draw tangent arm.
var newTangentArmColor = new Color(tangentArmColor.r, tangentArmColor.g, tangentArmColor.b, tangentArmColor.a * k_ColorAlphaFactor);
using (new ColorScope(newTangentArmColor))
DrawTangentArm(position, knotPosition, size, mode, selected, hovered, oppositeSelected, oppositeHovered);
// Draw tangent shape.
var newDiscColor = new Color(tangentColor.r, tangentColor.g, tangentColor.b, tangentColor.a * k_ColorAlphaFactor);
using (new Handles.DrawingScope(newDiscColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
DrawTangentShape(size, selected);
}
// Draw tangent disc on hover.
if (hovered)
{
var tangentHandleOffset = size * k_TangentEndOffsetFromHandle;
using (new ZTestScope(CompareFunction.Less))
{
using (new Handles.DrawingScope(tangentColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
SplineHandleUtility.DrawAAWireDisc(Vector3.zero, Vector3.up, tangentHandleOffset, rotationDiscWidth);
}
using (new ZTestScope(CompareFunction.Greater))
{
var newDiscColor = new Color(tangentColor.r, tangentColor.g, tangentColor.b, tangentColor.a * k_ColorAlphaFactor);
using (new Handles.DrawingScope(newDiscColor, Matrix4x4.TRS(position, rotation, Vector3.one)))
SplineHandleUtility.DrawAAWireDisc(Vector3.zero, Vector3.up, tangentHandleOffset, rotationDiscWidth);
}
}
}
static void DrawTangentArm(Vector3 position, Vector3 knotPosition, float size, TangentMode mode, bool selected, bool hovered, bool oppositeSelected, bool oppositeHovered)
{
var width = k_TangentLineWidthDefault;
if (!DirectManipulation.IsDragging)
{
if (selected || (mode != TangentMode.Broken && oppositeSelected))
width = k_TangentLineWidthSelected;
else if (hovered || (mode != TangentMode.Broken && oppositeHovered))
width = k_TangentLineWidthHover;
}
var startPos = knotPosition;
var toTangent = position - knotPosition;
var toTangentNorm = math.normalize(toTangent);
var length = math.length(toTangent);
var knotHandleSize = HandleUtility.GetHandleSize(startPos);
var knotHandleOffset = knotHandleSize * k_TangentStartOffsetFromKnot;
var tangentHandleOffset = size * k_TangentEndOffsetFromHandle;
// Reduce the length slightly, so that there's some space between tangent line endings and handles.
length = Mathf.Max(0f, length - knotHandleOffset - tangentHandleOffset);
startPos += (Vector3)toTangentNorm * knotHandleOffset;
SplineHandleUtility.DrawLineWithWidth(startPos + (Vector3)toTangentNorm * length, startPos, width, SplineHandleUtility.denseLineAATex);
}
static void DrawTangentShape(float size, bool selected)
{
var midVector = new Vector3(-.5f, 0, .5f);
if (selected)
{
var factor = 0.7f;
var radius = (selected ? SplineHandleUtility.knotDiscRadiusFactorSelected : SplineHandleUtility.knotDiscRadiusFactorHover) * size;
// As Handles.DrawAAConvexPolygon has no thickness parameter, we're drawing a AA Polyline here so that the polygon has thickness when viewed from a shallow angle.
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex,
k_TangentHandleWidth,
factor * radius * midVector,
factor * radius * Vector3.forward,
factor * radius * Vector3.right,
-factor * radius * Vector3.forward,
-factor * radius * Vector3.right,
factor * radius * midVector);
Handles.DrawAAConvexPolygon(
radius * midVector,
radius * Vector3.forward,
radius * Vector3.right,
-radius * Vector3.forward,
-radius * Vector3.right,
radius * midVector);
}
else
{
var radius = SplineHandleUtility.knotDiscRadiusFactorDefault * size;
//Starting the polyline in the middle of a segment and not to a corner to get an invisible connection.
//Otherwise the connection is really visible in the corner as a small part is missing there.
Handles.DrawAAPolyLine(SplineHandleUtility.denseLineAATex,
k_TangentHandleWidth,
radius * midVector,
radius * Vector3.forward,
radius * Vector3.right,
-radius * Vector3.forward,
-radius * Vector3.right,
radius * midVector);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More