├── Plugins ├── BezierSolution │ ├── BezierSolution.Runtime.asmdef │ ├── Demo │ │ ├── OctopusArm.fbx │ │ ├── OctopusArm.png │ │ ├── DemoScene.unity.meta │ │ ├── OctopusArm.mat.meta │ │ ├── OctopusArm.png.meta │ │ ├── OctopusArm.fbx.meta │ │ └── OctopusArm.mat │ ├── README.txt │ ├── Other.meta │ ├── README.txt.meta │ ├── BezierSolution.Runtime.asmdef.meta │ ├── Editor │ │ ├── BezierSolution.Editor.asmdef.meta │ │ ├── BezierWalkerWithTimeEditor.cs │ │ ├── BezierWalkerWithSpeedEditor.cs │ │ ├── BezierUtils.cs.meta │ │ ├── BezierSettings.cs.meta │ │ ├── BezierPointEditor.cs.meta │ │ ├── BezierSplineEditor.cs.meta │ │ ├── BezierWalkerEditor.cs.meta │ │ ├── BezierPointExtraDataDrawer.cs.meta │ │ ├── BezierWalkerWithTimeEditor.cs.meta │ │ ├── BezierWalkerLocomotionEditor.cs.meta │ │ ├── BezierWalkerWithSpeedEditor.cs.meta │ │ ├── ParticlesFollowBezierEditor.cs.meta │ │ ├── BezierSolution.Editor.asmdef │ │ ├── BezierPointExtraDataDrawer.cs │ │ ├── ParticlesFollowBezierEditor.cs │ │ ├── BezierWalkerLocomotionEditor.cs │ │ ├── BezierSplineEditor.cs │ │ ├── BezierWalkerEditor.cs │ │ ├── BezierSettings.cs │ │ └── BezierUtils.cs │ ├── Demo.meta │ ├── Editor.meta │ ├── Attributes.meta │ ├── Utilities.meta │ ├── Other │ │ ├── BezierDataStructures.cs.meta │ │ └── BezierDataStructures.cs │ ├── BezierPoint.cs.meta │ ├── BezierSpline.cs.meta │ ├── Utilities │ │ ├── BezierWalker.cs.meta │ │ ├── BezierAttachment.cs.meta │ │ ├── BezierLineRenderer.cs.meta │ │ ├── BendMeshAlongBezier.cs.meta │ │ ├── BezierWalkerLocomotion.cs.meta │ │ ├── BezierWalkerWithSpeed.cs.meta │ │ ├── BezierWalkerWithTime.cs.meta │ │ ├── ParticlesFollowBezier.cs.meta │ │ ├── BezierWalkerWithSpeed.cs │ │ ├── BezierWalkerWithTime.cs │ │ ├── BezierWalker.cs │ │ ├── ParticlesFollowBezier.cs │ │ ├── BezierWalkerLocomotion.cs │ │ ├── BezierLineRenderer.cs │ │ ├── BezierAttachment.cs │ │ └── BendMeshAlongBezier.cs │ ├── Attributes │ │ ├── MinMaxRangeAttribute.cs.meta │ │ └── MinMaxRangeAttribute.cs │ └── BezierPoint.cs └── BezierSolution.meta ├── .github ├── Images │ ├── Promo.png │ ├── BezierSpline.png │ ├── EndPointHandles.png │ ├── GizmoSettings.png │ ├── BezierAttachment.png │ ├── AutoConstructSpline.png │ ├── BendMeshAlongBezier.png │ ├── BezierLineRenderer.png │ ├── BezierWalkerWithTime.png │ ├── ConstructLinearPath.png │ ├── BezierWalkerLocomotion.png │ ├── BezierWalkerWithSpeed.png │ └── ParticlesFollowBezier.png └── README.md ├── LICENSE.txt.meta ├── package.json.meta ├── Plugins.meta ├── package.json └── LICENSE.txt /Plugins/BezierSolution/BezierSolution.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BezierSolution.Runtime" 3 | } 4 | -------------------------------------------------------------------------------- /.github/Images/Promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/Promo.png -------------------------------------------------------------------------------- /.github/Images/BezierSpline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierSpline.png -------------------------------------------------------------------------------- /.github/Images/EndPointHandles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/EndPointHandles.png -------------------------------------------------------------------------------- /.github/Images/GizmoSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/GizmoSettings.png -------------------------------------------------------------------------------- /.github/Images/BezierAttachment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierAttachment.png -------------------------------------------------------------------------------- /.github/Images/AutoConstructSpline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/AutoConstructSpline.png -------------------------------------------------------------------------------- /.github/Images/BendMeshAlongBezier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BendMeshAlongBezier.png -------------------------------------------------------------------------------- /.github/Images/BezierLineRenderer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierLineRenderer.png -------------------------------------------------------------------------------- /.github/Images/BezierWalkerWithTime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierWalkerWithTime.png -------------------------------------------------------------------------------- /.github/Images/ConstructLinearPath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/ConstructLinearPath.png -------------------------------------------------------------------------------- /.github/Images/BezierWalkerLocomotion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierWalkerLocomotion.png -------------------------------------------------------------------------------- /.github/Images/BezierWalkerWithSpeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/BezierWalkerWithSpeed.png -------------------------------------------------------------------------------- /.github/Images/ParticlesFollowBezier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/.github/Images/ParticlesFollowBezier.png -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/Plugins/BezierSolution/Demo/OctopusArm.fbx -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityBezierSolution/HEAD/Plugins/BezierSolution/Demo/OctopusArm.png -------------------------------------------------------------------------------- /Plugins/BezierSolution/README.txt: -------------------------------------------------------------------------------- 1 | = Bezier Solution (v2.3.4) = 2 | 3 | Documentation: https://github.com/yasirkula/UnityBezierSolution 4 | E-mail: yasirkula@gmail.com -------------------------------------------------------------------------------- /LICENSE.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5b58d74cc14a3f046aad0f277871d023 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c937a63164ab474a8d1eabc8c140359 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Other.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34eb947ee3a327b45911b699fb81c1f5 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 63114691cacd5ed408e0c88e91607640 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/README.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 90c5928df0356a746a1423cce53181b9 3 | timeCreated: 1562520040 4 | licenseType: Free 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/BezierSolution.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 624c061ee4dfdaf4787b9c0bb51b4c1f 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/DemoScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 494545cd2e9cbec47adb2c1adf6066ab 3 | timeCreated: 1472738252 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSolution.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b99279c3e8f544f4d83dd26b3a9b5715 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins/BezierSolution.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e12f244167e9e84d990156d1d5e06b9 3 | folderAsset: yes 4 | timeCreated: 1472735532 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4b55326d0aea81b42b7f407fffdad047 3 | folderAsset: yes 4 | timeCreated: 1620724489 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 519da3ae01006d64ea40c00e92df6614 3 | folderAsset: yes 4 | timeCreated: 1472735538 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Attributes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e3f966a8e1c81a429b6881944ddb88a 3 | folderAsset: yes 4 | timeCreated: 1620639369 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 86440575d46397c488152ba57ee62ed4 3 | folderAsset: yes 4 | timeCreated: 1473251123 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b88831b1ed3c38439c9be5ac64f87c1 3 | timeCreated: 1620040793 4 | licenseType: Free 5 | NativeFormatImporter: 6 | mainObjectFileID: 2100000 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerWithTimeEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace BezierSolution.Extras 4 | { 5 | [CustomEditor( typeof( BezierWalkerWithTime ) )] 6 | [CanEditMultipleObjects] 7 | public class BezierWalkerWithTimeEditor : BezierWalkerEditor 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerWithSpeedEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace BezierSolution.Extras 4 | { 5 | [CustomEditor( typeof( BezierWalkerWithSpeed ) )] 6 | [CanEditMultipleObjects] 7 | public class BezierWalkerWithSpeedEditor : BezierWalkerEditor 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Other/BezierDataStructures.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4cf39235e4779904896b58210ade2e37 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | assetBundleName: 10 | assetBundleVariant: 11 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/BezierPoint.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9cf9c5931a3b3c4ba456c8213d7ebfc 3 | timeCreated: 1472737829 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/BezierSpline.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3dbf94c21a22d74d895b2051a04ba1e 3 | timeCreated: 1620216034 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: -400 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f472603c7062a4747bc569c7740ca872 3 | timeCreated: 1473158123 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e751cb465841a4f43b9297b7be55dcd0 3 | timeCreated: 1620207781 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2e3fd6f386de3644ba3090712795517d 3 | timeCreated: 1542623772 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierPointEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d0566a88081c1b46b6236e5b2575a7c 3 | timeCreated: 1472737433 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSplineEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b74ad81d1f30ec479543aea0a27f6ef 3 | timeCreated: 1472737433 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c496a7cc8ad6654f891021f652a8e5f 3 | timeCreated: 1520346477 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierAttachment.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b6258aafd9a87344833937e5e546638 3 | timeCreated: 1620400687 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierLineRenderer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 09bf7d81c21c8564abb7c13d3e848e94 3 | timeCreated: 1620206953 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Attributes/MinMaxRangeAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 277efd8192415fd4fb70032186bd5ed0 3 | timeCreated: 1620638811 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierPointExtraDataDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d6fe536a44fc10242a73e0114a06befa 3 | timeCreated: 1520266844 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerWithTimeEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e24aeb71e708e4043b77585635edfe8d 3 | timeCreated: 1520346477 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BendMeshAlongBezier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e815427364673446939224ffdf5e5fd 3 | timeCreated: 1620245307 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerLocomotion.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 17b675957a920ef4e8da909ede6d0d04 3 | timeCreated: 1542623444 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerWithSpeed.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 148310ac13c958d4c93f53cdfe985375 3 | timeCreated: 1473251134 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerWithTime.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46d12b9f97514694e962a3395a73e638 3 | timeCreated: 1473251134 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/ParticlesFollowBezier.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 954f54b0dafe0cf488e4e15934cae013 3 | timeCreated: 1510521621 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerLocomotionEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: afc4ab3119c2b58498c79718b6945348 3 | timeCreated: 1520346477 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerWithSpeedEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aba64e4e7209c9b44ae73ece7f2c7164 3 | timeCreated: 1520346477 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/ParticlesFollowBezierEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 652ad8d00b278c04fbf4252c46b7a10f 3 | timeCreated: 1520346477 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSolution.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BezierSolution.Editor", 3 | "references": [ 4 | "BezierSolution.Runtime" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.yasirkula.beziersolution", 3 | "displayName": "Bezier Solution", 4 | "version": "2.3.4", 5 | "documentationUrl": "https://github.com/yasirkula/UnityBezierSolution", 6 | "changelogUrl": "https://github.com/yasirkula/UnityBezierSolution/releases", 7 | "licensesUrl": "https://github.com/yasirkula/UnityBezierSolution/blob/master/LICENSE.txt", 8 | "description": "This asset is a means to create bezier splines in editor and/or during runtime: splines can be created and edited visually in the editor, or by code during runtime.", 9 | "hideInEditor": false 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Süleyman Yasir KULA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierPointExtraDataDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace BezierSolution.Extras 5 | { 6 | [CustomPropertyDrawer( typeof( BezierPoint.ExtraData ) )] 7 | public class BezierPointExtraDataDrawer : PropertyDrawer 8 | { 9 | public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) 10 | { 11 | EditorGUI.BeginProperty( position, label, property ); 12 | position = EditorGUI.PrefixLabel( position, GUIUtility.GetControlID( FocusType.Passive ), label ); 13 | 14 | float quarterWidth = position.width * 0.25f; 15 | Rect c1Rect = new Rect( position.x, position.y, quarterWidth, position.height ); 16 | Rect c2Rect = new Rect( position.x + quarterWidth, position.y, quarterWidth, position.height ); 17 | Rect c3Rect = new Rect( position.x + 2f * quarterWidth, position.y, quarterWidth, position.height ); 18 | Rect c4Rect = new Rect( position.x + 3f * quarterWidth, position.y, quarterWidth, position.height ); 19 | 20 | EditorGUI.PropertyField( c1Rect, property.FindPropertyRelative( "c1" ), GUIContent.none ); 21 | EditorGUI.PropertyField( c2Rect, property.FindPropertyRelative( "c2" ), GUIContent.none ); 22 | EditorGUI.PropertyField( c3Rect, property.FindPropertyRelative( "c3" ), GUIContent.none ); 23 | EditorGUI.PropertyField( c4Rect, property.FindPropertyRelative( "c4" ), GUIContent.none ); 24 | 25 | EditorGUI.EndProperty(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/ParticlesFollowBezierEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace BezierSolution.Extras 5 | { 6 | // This class is used for resetting the particle system attached to a ParticlesFollowBezier 7 | // component when it is selected. Otherwise, particles move in a chaotic way for a while 8 | [CustomEditor( typeof( ParticlesFollowBezier ) )] 9 | [CanEditMultipleObjects] 10 | public class ParticlesFollowBezierEditor : Editor 11 | { 12 | private int particlesReset; 13 | 14 | private void OnEnable() 15 | { 16 | particlesReset = 3; 17 | } 18 | 19 | public override void OnInspectorGUI() 20 | { 21 | base.OnInspectorGUI(); 22 | 23 | if( Application.isPlaying ) 24 | return; 25 | 26 | if( particlesReset > 0 && --particlesReset == 0 ) 27 | { 28 | foreach( Object target in targets ) 29 | { 30 | ResetParticles( ( (ParticlesFollowBezier) target ).GetComponentsInParent() ); 31 | ResetParticles( ( (ParticlesFollowBezier) target ).GetComponentsInChildren() ); 32 | } 33 | } 34 | } 35 | 36 | private void ResetParticles( ParticlesFollowBezier[] targets ) 37 | { 38 | foreach( ParticlesFollowBezier target in targets ) 39 | { 40 | ParticleSystem particleSystem = target.GetComponent(); 41 | if( target.spline != null && particleSystem != null && target.enabled ) 42 | particleSystem.Clear(); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerLocomotionEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BezierSolution.Extras 6 | { 7 | [CustomEditor( typeof( BezierWalkerLocomotion ) )] 8 | [CanEditMultipleObjects] 9 | public class BezierWalkerLocomotionEditor : BezierWalkerEditor 10 | { 11 | private int tailSaveDataStartIndex; 12 | 13 | protected override void SaveInitialData() 14 | { 15 | base.SaveInitialData(); 16 | tailSaveDataStartIndex = initialPositions.Count; 17 | 18 | for( int i = 0; i < walkers.Length; i++ ) 19 | { 20 | List tail = walkers[i].Tail; 21 | for( int j = 0; j < tail.Count; j++ ) 22 | { 23 | initialPositions.Add( tail[j].position ); 24 | initialRotations.Add( tail[j].rotation ); 25 | } 26 | } 27 | } 28 | 29 | protected override void RestoreInitialData() 30 | { 31 | base.RestoreInitialData(); 32 | 33 | int index = tailSaveDataStartIndex; 34 | for( int i = 0; i < walkers.Length; i++ ) 35 | { 36 | if( walkers[i] ) 37 | { 38 | List tail = walkers[i].Tail; 39 | for( int j = 0; j < tail.Count; j++, index++ ) 40 | { 41 | tail[j].position = initialPositions[index]; 42 | tail[j].rotation = initialRotations[index]; 43 | } 44 | } 45 | } 46 | } 47 | 48 | protected override void Simulate( float deltaTime ) 49 | { 50 | for( int i = 0; i < walkers.Length; i++ ) 51 | walkers[i].walker.Execute( deltaTime ); 52 | 53 | base.Simulate( deltaTime ); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerWithSpeed.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | namespace BezierSolution 5 | { 6 | [AddComponentMenu( "Bezier Solution/Bezier Walker With Speed" )] 7 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 8 | public class BezierWalkerWithSpeed : BezierWalker 9 | { 10 | public BezierSpline spline; 11 | public TravelMode travelMode; 12 | 13 | public float speed = 5f; 14 | [SerializeField] 15 | [Range( 0f, 1f )] 16 | private float m_normalizedT = 0f; 17 | 18 | public override BezierSpline Spline { get { return spline; } } 19 | 20 | public override float NormalizedT 21 | { 22 | get { return m_normalizedT; } 23 | set { m_normalizedT = value; } 24 | } 25 | 26 | //public float movementLerpModifier = 10f; 27 | public float rotationLerpModifier = 10f; 28 | 29 | public LookAtMode lookAt = LookAtMode.ZForward; 30 | 31 | private bool isGoingForward = true; 32 | public override bool MovingForward 33 | { 34 | get { return ( speed >= 0f ) == isGoingForward; } 35 | set { isGoingForward = ( speed >= 0f ) == value; } 36 | } 37 | 38 | public UnityEvent onPathCompleted = new UnityEvent(); 39 | private bool onPathCompletedCalledAt1 = false; 40 | private bool onPathCompletedCalledAt0 = false; 41 | 42 | private void Update() 43 | { 44 | Execute( Time.deltaTime ); 45 | } 46 | 47 | public override void Execute( float deltaTime ) 48 | { 49 | transform.position = spline.MoveAlongSpline( ref m_normalizedT, ( isGoingForward ? speed : -speed ) * deltaTime ); 50 | RotateTarget( transform, m_normalizedT, lookAt, rotationLerpModifier * deltaTime ); 51 | PostProcessMovement( travelMode, ref onPathCompletedCalledAt0, ref onPathCompletedCalledAt1, onPathCompleted ); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd20479f79180f440ad81177db5e0b12 3 | timeCreated: 1620724293 4 | licenseType: Free 5 | TextureImporter: 6 | fileIDToRecycleName: {} 7 | serializedVersion: 4 8 | mipmaps: 9 | mipMapMode: 0 10 | enableMipMap: 1 11 | sRGBTexture: 1 12 | linearTexture: 0 13 | fadeOut: 0 14 | borderMipMap: 0 15 | mipMapFadeDistanceStart: 1 16 | mipMapFadeDistanceEnd: 3 17 | bumpmap: 18 | convertToNormalMap: 0 19 | externalNormalMap: 0 20 | heightScale: 0.25 21 | normalMapFilter: 0 22 | isReadable: 0 23 | grayScaleToAlpha: 0 24 | generateCubemap: 6 25 | cubemapConvolution: 0 26 | seamlessCubemap: 0 27 | textureFormat: 1 28 | maxTextureSize: 2048 29 | textureSettings: 30 | filterMode: -1 31 | aniso: -1 32 | mipBias: -1 33 | wrapMode: -1 34 | nPOTScale: 1 35 | lightmap: 0 36 | compressionQuality: 50 37 | spriteMode: 0 38 | spriteExtrude: 1 39 | spriteMeshType: 1 40 | alignment: 0 41 | spritePivot: {x: 0.5, y: 0.5} 42 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 43 | spritePixelsToUnits: 100 44 | alphaUsage: 1 45 | alphaIsTransparency: 0 46 | spriteTessellationDetail: -1 47 | textureType: 0 48 | textureShape: 1 49 | maxTextureSizeSet: 0 50 | compressionQualitySet: 0 51 | textureFormatSet: 0 52 | platformSettings: 53 | - buildTarget: DefaultTexturePlatform 54 | maxTextureSize: 2048 55 | textureFormat: -1 56 | textureCompression: 1 57 | compressionQuality: 50 58 | crunchedCompression: 0 59 | allowsAlphaSplitting: 0 60 | overridden: 0 61 | spriteSheet: 62 | serializedVersion: 2 63 | sprites: [] 64 | outline: [] 65 | spritePackingTag: 66 | userData: 67 | assetBundleName: 68 | assetBundleVariant: 69 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerWithTime.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | namespace BezierSolution 5 | { 6 | [AddComponentMenu( "Bezier Solution/Bezier Walker With Time" )] 7 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 8 | public class BezierWalkerWithTime : BezierWalker 9 | { 10 | public BezierSpline spline; 11 | public TravelMode travelMode; 12 | 13 | public float travelTime = 5f; 14 | [SerializeField] 15 | [Range( 0f, 1f )] 16 | private float m_normalizedT = 0f; 17 | 18 | public bool highQuality = false; 19 | 20 | public override BezierSpline Spline { get { return spline; } } 21 | 22 | public override float NormalizedT 23 | { 24 | get { return m_normalizedT; } 25 | set { m_normalizedT = value; } 26 | } 27 | 28 | public float movementLerpModifier = 10f; 29 | public float rotationLerpModifier = 10f; 30 | 31 | public LookAtMode lookAt = LookAtMode.ZForward; 32 | 33 | private bool isGoingForward = true; 34 | public override bool MovingForward 35 | { 36 | get { return isGoingForward; } 37 | set { isGoingForward = value; } 38 | } 39 | 40 | public UnityEvent onPathCompleted = new UnityEvent(); 41 | private bool onPathCompletedCalledAt1 = false; 42 | private bool onPathCompletedCalledAt0 = false; 43 | 44 | private void Update() 45 | { 46 | Execute( Time.deltaTime ); 47 | } 48 | 49 | public override void Execute( float deltaTime ) 50 | { 51 | float _normalizedT = highQuality ? spline.evenlySpacedPoints.GetNormalizedTAtPercentage( m_normalizedT ) : m_normalizedT; 52 | transform.position = Vector3.Lerp( transform.position, spline.GetPoint( _normalizedT ), movementLerpModifier * deltaTime ); 53 | RotateTarget( transform, _normalizedT, lookAt, rotationLerpModifier * deltaTime ); 54 | 55 | m_normalizedT += ( isGoingForward ? deltaTime : -deltaTime ) / travelTime; 56 | PostProcessMovement( travelMode, ref onPathCompletedCalledAt0, ref onPathCompletedCalledAt1, onPathCompleted ); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.fbx.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44b1882f3e87f214ba0c12a8e3b15d36 3 | timeCreated: 1620040793 4 | licenseType: Free 5 | ModelImporter: 6 | serializedVersion: 19 7 | fileIDToRecycleName: 8 | 100000: tentacle_6_geo 9 | 100002: //RootNode 10 | 400000: tentacle_6_geo 11 | 400002: //RootNode 12 | 2300000: tentacle_6_geo 13 | 3300000: tentacle_6_geo 14 | 4300000: tentacle_6_geo 15 | materials: 16 | importMaterials: 0 17 | materialName: 0 18 | materialSearch: 1 19 | animations: 20 | legacyGenerateAnimations: 4 21 | bakeSimulation: 0 22 | resampleCurves: 1 23 | optimizeGameObjects: 0 24 | motionNodeName: 25 | rigImportErrors: 26 | rigImportWarnings: 27 | animationImportErrors: 28 | animationImportWarnings: 29 | animationRetargetingWarnings: 30 | animationDoRetargetingWarnings: 0 31 | animationCompression: 1 32 | animationRotationError: 0.5 33 | animationPositionError: 0.5 34 | animationScaleError: 0.5 35 | animationWrapMode: 0 36 | extraExposedTransformPaths: [] 37 | clipAnimations: [] 38 | isReadable: 1 39 | meshes: 40 | lODScreenPercentages: [] 41 | globalScale: 1 42 | meshCompression: 0 43 | addColliders: 0 44 | importBlendShapes: 1 45 | swapUVChannels: 0 46 | generateSecondaryUV: 0 47 | useFileUnits: 1 48 | optimizeMeshForGPU: 1 49 | keepQuads: 0 50 | weldVertices: 1 51 | secondaryUVAngleDistortion: 8 52 | secondaryUVAreaDistortion: 15.000001 53 | secondaryUVHardAngle: 88 54 | secondaryUVPackMargin: 4 55 | useFileScale: 1 56 | tangentSpace: 57 | normalSmoothAngle: 60 58 | normalImportMode: 0 59 | tangentImportMode: 3 60 | importAnimation: 1 61 | copyAvatar: 0 62 | humanDescription: 63 | serializedVersion: 2 64 | human: [] 65 | skeleton: [] 66 | armTwist: 0.5 67 | foreArmTwist: 0.5 68 | upperLegTwist: 0.5 69 | legTwist: 0.5 70 | armStretch: 0.05 71 | legStretch: 0.05 72 | feetSpacing: 0 73 | rootMotionBoneName: 74 | rootMotionBoneRotation: {x: 0, y: 0, z: 0, w: 1} 75 | hasTranslationDoF: 0 76 | hasExtraRoot: 0 77 | skeletonHasParents: 1 78 | lastHumanDescriptionAvatarSource: {instanceID: 0} 79 | animationType: 0 80 | humanoidOversampling: 1 81 | additionalBone: 0 82 | userData: 83 | assetBundleName: 84 | assetBundleVariant: 85 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Attributes/MinMaxRangeAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | #if UNITY_EDITOR 3 | using UnityEditor; 4 | #endif 5 | 6 | namespace BezierSolution 7 | { 8 | public class MinMaxRangeAttribute : PropertyAttribute 9 | { 10 | public float min; 11 | public float max; 12 | 13 | public MinMaxRangeAttribute( float min, float max ) 14 | { 15 | this.min = min; 16 | this.max = max; 17 | } 18 | } 19 | } 20 | 21 | #if UNITY_EDITOR 22 | namespace BezierSolution.Extras 23 | { 24 | [CustomPropertyDrawer( typeof( MinMaxRangeAttribute ) )] 25 | public class MixMaxRangeAttributeDrawer : PropertyDrawer 26 | { 27 | private const float MIN_MAX_SLIDER_TEXT_FIELD_WIDTH = 45f; 28 | 29 | // Min-max slider credit: https://github.com/Unity-Technologies/UnityCsReference/blob/61f92bd79ae862c4465d35270f9d1d57befd1761/Editor/Mono/Inspector/LightEditor.cs#L328-L363 30 | public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) 31 | { 32 | MinMaxRangeAttribute minMaxRange = attribute as MinMaxRangeAttribute; 33 | 34 | SerializedProperty minProp = property.FindPropertyRelative( "x" ); 35 | SerializedProperty maxProp = property.FindPropertyRelative( "y" ); 36 | 37 | position = EditorGUI.PrefixLabel( position, label ); 38 | EditorGUI.BeginProperty( position, GUIContent.none, property ); 39 | 40 | Rect minRect = new Rect( position ) { width = MIN_MAX_SLIDER_TEXT_FIELD_WIDTH }; 41 | Rect maxRect = new Rect( position ) { xMin = position.xMax - MIN_MAX_SLIDER_TEXT_FIELD_WIDTH }; 42 | Rect sliderRect = new Rect( position ) { xMin = minRect.xMax + 5f, xMax = maxRect.xMin - 5f }; 43 | 44 | EditorGUI.BeginChangeCheck(); 45 | 46 | EditorGUI.PropertyField( minRect, minProp, GUIContent.none ); 47 | 48 | Vector2 value = property.vector2Value; 49 | EditorGUI.BeginChangeCheck(); 50 | EditorGUI.MinMaxSlider( sliderRect, ref value.x, ref value.y, minMaxRange.min, minMaxRange.max ); 51 | if( EditorGUI.EndChangeCheck() ) 52 | property.vector2Value = value; 53 | 54 | EditorGUI.PropertyField( maxRect, maxProp, GUIContent.none ); 55 | 56 | if( EditorGUI.EndChangeCheck() ) 57 | { 58 | float x = minProp.floatValue; 59 | float y = maxProp.floatValue; 60 | 61 | if( x < minMaxRange.min || x > minMaxRange.max ) 62 | minProp.floatValue = Mathf.Clamp( x, minMaxRange.min, minMaxRange.max ); 63 | if( y < minMaxRange.min || y > minMaxRange.max ) 64 | maxProp.floatValue = Mathf.Clamp( y, minMaxRange.min, minMaxRange.max ); 65 | } 66 | 67 | EditorGUI.EndProperty(); 68 | } 69 | } 70 | } 71 | #endif -------------------------------------------------------------------------------- /Plugins/BezierSolution/Demo/OctopusArm.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_PrefabParentObject: {fileID: 0} 8 | m_PrefabInternal: {fileID: 0} 9 | m_Name: OctopusArm 10 | m_Shader: {fileID: 10752, guid: 0000000000000000f000000000000000, type: 0} 11 | m_ShaderKeywords: _NORMALMAP 12 | m_LightmapFlags: 4 13 | m_EnableInstancingVariants: 0 14 | m_DoubleSidedGI: 0 15 | m_CustomRenderQueue: -1 16 | stringTagMap: {} 17 | disabledShaderPasses: [] 18 | m_SavedProperties: 19 | serializedVersion: 3 20 | m_TexEnvs: 21 | - _BumpMap: 22 | m_Texture: {fileID: 2800000, guid: 89e1b1c005d29cf4598ea861deb35a80, type: 3} 23 | m_Scale: {x: 1, y: 1} 24 | m_Offset: {x: 0, y: 0} 25 | - _DetailAlbedoMap: 26 | m_Texture: {fileID: 0} 27 | m_Scale: {x: 1, y: 1} 28 | m_Offset: {x: 0, y: 0} 29 | - _DetailMask: 30 | m_Texture: {fileID: 0} 31 | m_Scale: {x: 1, y: 1} 32 | m_Offset: {x: 0, y: 0} 33 | - _DetailNormalMap: 34 | m_Texture: {fileID: 0} 35 | m_Scale: {x: 1, y: 1} 36 | m_Offset: {x: 0, y: 0} 37 | - _EmissionMap: 38 | m_Texture: {fileID: 0} 39 | m_Scale: {x: 1, y: 1} 40 | m_Offset: {x: 0, y: 0} 41 | - _MainTex: 42 | m_Texture: {fileID: 2800000, guid: cd20479f79180f440ad81177db5e0b12, type: 3} 43 | m_Scale: {x: 1, y: 1} 44 | m_Offset: {x: 0, y: 0} 45 | - _MetallicGlossMap: 46 | m_Texture: {fileID: 0} 47 | m_Scale: {x: 1, y: 1} 48 | m_Offset: {x: 0, y: 0} 49 | - _OcclusionMap: 50 | m_Texture: {fileID: 0} 51 | m_Scale: {x: 1, y: 1} 52 | m_Offset: {x: 0, y: 0} 53 | - _ParallaxMap: 54 | m_Texture: {fileID: 0} 55 | m_Scale: {x: 1, y: 1} 56 | m_Offset: {x: 0, y: 0} 57 | m_Floats: 58 | - _BumpScale: 1 59 | - _Cutoff: 0.5 60 | - _DetailNormalMapScale: 1 61 | - _DstBlend: 0 62 | - _GlossMapScale: 1 63 | - _Glossiness: 0.5 64 | - _GlossyReflections: 1 65 | - _Metallic: 0 66 | - _Mode: 0 67 | - _OcclusionStrength: 1 68 | - _Parallax: 0.02 69 | - _SamplePoint: 0 70 | - _SmoothnessTextureChannel: 0 71 | - _SpecularHighlights: 1 72 | - _SrcBlend: 1 73 | - _UVSec: 0 74 | - _ZWrite: 1 75 | m_Colors: 76 | - _Color: {r: 1, g: 1, b: 1, a: 1} 77 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 78 | - _SampleRange: {r: 0, g: 1, b: 0, a: 0} 79 | - _VertexRange: {r: 0, g: 1, b: 0, a: 0} 80 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSplineEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | 4 | namespace BezierSolution.Extras 5 | { 6 | [CustomEditor( typeof( BezierSpline ) )] 7 | [CanEditMultipleObjects] 8 | public class BezierSplineEditor : Editor 9 | { 10 | internal BezierSpline[] allSplines; 11 | 12 | public static BezierSplineEditor ActiveEditor { get; private set; } 13 | 14 | private void OnEnable() 15 | { 16 | Object[] splines = targets; 17 | allSplines = new BezierSpline[splines.Length]; 18 | for( int i = 0; i < splines.Length; i++ ) 19 | { 20 | BezierSpline spline = (BezierSpline) splines[i]; 21 | if( spline ) 22 | spline.Refresh(); 23 | 24 | allSplines[i] = spline; 25 | } 26 | 27 | ActiveEditor = this; 28 | 29 | if( BezierUtils.QuickEditSplineMode ) 30 | { 31 | Tools.hidden = true; 32 | 33 | EditorApplication.update -= SceneView.RepaintAll; 34 | EditorApplication.update += SceneView.RepaintAll; 35 | } 36 | 37 | Undo.undoRedoPerformed -= OnUndoRedo; 38 | Undo.undoRedoPerformed += OnUndoRedo; 39 | } 40 | 41 | private void OnDisable() 42 | { 43 | ActiveEditor = null; 44 | Tools.hidden = false; 45 | 46 | Undo.undoRedoPerformed -= OnUndoRedo; 47 | EditorApplication.update -= SceneView.RepaintAll; 48 | } 49 | 50 | private void OnSceneGUI() 51 | { 52 | BezierSpline spline = (BezierSpline) target; 53 | BezierUtils.DrawSplineDetailed( spline ); 54 | 55 | for( int i = 0; i < spline.Count; i++ ) 56 | BezierUtils.DrawBezierPoint( spline[i], i + 1, false ); 57 | 58 | if( BezierSettings.ShowEvenlySpacedPoints ) 59 | BezierUtils.DrawSplineEvenlySpacedPoints( spline ); 60 | 61 | if( BezierUtils.QuickEditSplineMode ) 62 | { 63 | // Execute quick edit mode's scene GUI only once (otherwise things can get ugly when multiple splines are selected) 64 | if( spline == allSplines[0] ) 65 | { 66 | BezierUtils.QuickEditModeSceneGUI( allSplines ); 67 | HandleUtility.AddDefaultControl( 0 ); 68 | } 69 | 70 | return; 71 | } 72 | } 73 | 74 | public override void OnInspectorGUI() 75 | { 76 | BezierUtils.DrawSplineInspectorGUI( allSplines ); 77 | } 78 | 79 | private void OnUndoRedo() 80 | { 81 | for( int i = 0; i < allSplines.Length; i++ ) 82 | { 83 | if( allSplines[i] ) 84 | { 85 | allSplines[i].dirtyFlags |= InternalDirtyFlags.All; 86 | allSplines[i].Refresh(); 87 | } 88 | } 89 | 90 | Repaint(); 91 | } 92 | 93 | private bool HasFrameBounds() 94 | { 95 | return !serializedObject.isEditingMultipleObjects; 96 | } 97 | 98 | private Bounds OnGetFrameBounds() 99 | { 100 | return new Bounds( ( (BezierSpline) target ).transform.position, new Vector3( 1f, 1f, 1f ) ); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalker.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | namespace BezierSolution 5 | { 6 | public enum TravelMode { Once = 0, Loop = 1, PingPong = 2 }; 7 | public enum LookAtMode { None = 0, XForward = 3, YForward = 4, ZForward = 1, SplineExtraData = 2 } 8 | 9 | public abstract class BezierWalker : MonoBehaviour 10 | { 11 | public abstract BezierSpline Spline { get; } 12 | public abstract bool MovingForward { get; set; } 13 | public abstract float NormalizedT { get; set; } 14 | 15 | public abstract void Execute( float deltaTime ); 16 | 17 | public static readonly ExtraDataLerpFunction extraDataLerpAsQuaternionFunction = InterpolateExtraDataAsQuaternion; 18 | 19 | private static BezierPoint.ExtraData InterpolateExtraDataAsQuaternion( BezierPoint.ExtraData data1, BezierPoint.ExtraData data2, float normalizedT ) 20 | { 21 | return Quaternion.LerpUnclamped( data1, data2, normalizedT ); 22 | } 23 | 24 | protected void RotateTarget( Transform target, float normalizedT, LookAtMode lookAt, float lerpSpeed ) 25 | { 26 | Quaternion targetRotation; 27 | switch( lookAt ) 28 | { 29 | case LookAtMode.XForward: 30 | case LookAtMode.YForward: 31 | case LookAtMode.ZForward: 32 | { 33 | BezierSpline.Segment segment = Spline.GetSegmentAt( normalizedT ); 34 | targetRotation = Quaternion.LookRotation( MovingForward ? segment.GetTangent() : -segment.GetTangent(), segment.GetNormal() ); 35 | if( lookAt == LookAtMode.XForward ) 36 | targetRotation *= Quaternion.Euler( 0f, -90f, 0f ); 37 | else if( lookAt == LookAtMode.YForward ) 38 | targetRotation *= Quaternion.Euler( 0f, 90f, 90f ); 39 | 40 | break; 41 | } 42 | case LookAtMode.SplineExtraData: targetRotation = Spline.GetExtraData( normalizedT, extraDataLerpAsQuaternionFunction ); break; 43 | default: return; 44 | } 45 | 46 | target.rotation = Quaternion.Lerp( target.rotation, targetRotation, lerpSpeed ); 47 | } 48 | 49 | protected void PostProcessMovement( TravelMode travelMode, ref bool onPathCompletedCalledAt0, ref bool onPathCompletedCalledAt1, UnityEvent onPathCompleted ) 50 | { 51 | if( MovingForward ) 52 | { 53 | if( NormalizedT >= 1f ) 54 | { 55 | if( travelMode == TravelMode.Once ) 56 | NormalizedT = 1f; 57 | else if( travelMode == TravelMode.Loop ) 58 | NormalizedT -= 1f; 59 | else 60 | { 61 | NormalizedT = 2f - NormalizedT; 62 | MovingForward = !MovingForward; 63 | } 64 | 65 | if( !onPathCompletedCalledAt1 ) 66 | { 67 | onPathCompletedCalledAt1 = true; 68 | onPathCompleted.Invoke(); 69 | } 70 | } 71 | else 72 | onPathCompletedCalledAt1 = false; 73 | } 74 | else 75 | { 76 | if( NormalizedT <= 0f ) 77 | { 78 | if( travelMode == TravelMode.Once ) 79 | NormalizedT = 0f; 80 | else if( travelMode == TravelMode.Loop ) 81 | NormalizedT += 1f; 82 | else 83 | { 84 | NormalizedT = -NormalizedT; 85 | MovingForward = !MovingForward; 86 | } 87 | 88 | if( !onPathCompletedCalledAt0 ) 89 | { 90 | onPathCompletedCalledAt0 = true; 91 | onPathCompleted.Invoke(); 92 | } 93 | } 94 | else 95 | onPathCompletedCalledAt0 = false; 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierWalkerEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace BezierSolution.Extras 7 | { 8 | public abstract class BezierWalkerEditor : Editor where T : BezierWalker 9 | { 10 | protected T[] walkers; 11 | 12 | private bool simulateInEditor; 13 | private double lastUpdateTime; 14 | 15 | protected bool hasInitialData; 16 | protected List initialPositions = new List( 0 ); 17 | protected List initialRotations = new List( 0 ); 18 | protected List initialNormalizedTs = new List( 0 ); 19 | 20 | private void OnEnable() 21 | { 22 | walkers = Array.ConvertAll( targets, ( e ) => (T) e ); 23 | 24 | if( simulateInEditor ) 25 | StartSimulateInEditor(); 26 | } 27 | 28 | private void OnDisable() 29 | { 30 | StopSimulateInEditor(); 31 | } 32 | 33 | public override void OnInspectorGUI() 34 | { 35 | base.OnInspectorGUI(); 36 | 37 | BezierUtils.DrawSeparator(); 38 | EditorGUI.BeginChangeCheck(); 39 | simulateInEditor = GUILayout.Toggle( simulateInEditor, "Simulate In Editor", GUI.skin.button ); 40 | if( EditorGUI.EndChangeCheck() ) 41 | { 42 | if( simulateInEditor ) 43 | StartSimulateInEditor(); 44 | else 45 | StopSimulateInEditor(); 46 | } 47 | } 48 | 49 | private void StartSimulateInEditor() 50 | { 51 | SaveInitialData(); 52 | 53 | for( int i = 0; i < walkers.Length; i++ ) 54 | walkers[i].MovingForward = true; 55 | 56 | lastUpdateTime = EditorApplication.timeSinceStartup; 57 | EditorApplication.update -= SimulateInEditor; 58 | EditorApplication.update += SimulateInEditor; 59 | } 60 | 61 | private void StopSimulateInEditor() 62 | { 63 | EditorApplication.update -= SimulateInEditor; 64 | 65 | if( hasInitialData ) 66 | { 67 | hasInitialData = false; 68 | RestoreInitialData(); 69 | } 70 | 71 | simulateInEditor = false; 72 | } 73 | 74 | protected virtual void SaveInitialData() 75 | { 76 | initialPositions.Clear(); 77 | initialRotations.Clear(); 78 | initialNormalizedTs.Clear(); 79 | 80 | for( int i = 0; i < walkers.Length; i++ ) 81 | { 82 | initialPositions.Add( walkers[i].transform.position ); 83 | initialRotations.Add( walkers[i].transform.rotation ); 84 | initialNormalizedTs.Add( walkers[i].NormalizedT ); 85 | } 86 | 87 | hasInitialData = true; 88 | } 89 | 90 | protected virtual void RestoreInitialData() 91 | { 92 | for( int i = 0; i < walkers.Length; i++ ) 93 | { 94 | if( walkers[i] ) 95 | { 96 | walkers[i].transform.position = initialPositions[i]; 97 | walkers[i].transform.rotation = initialRotations[i]; 98 | walkers[i].NormalizedT = initialNormalizedTs[i]; 99 | } 100 | } 101 | } 102 | 103 | private void SimulateInEditor() 104 | { 105 | if( EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying ) 106 | { 107 | // Stop the simulation if we are about to enter Play mode 108 | StopSimulateInEditor(); 109 | } 110 | else 111 | { 112 | double time = EditorApplication.timeSinceStartup; 113 | Simulate( (float) ( time - lastUpdateTime ) ); 114 | lastUpdateTime = time; 115 | } 116 | } 117 | 118 | protected virtual void Simulate( float deltaTime ) 119 | { 120 | for( int i = 0; i < walkers.Length; i++ ) 121 | walkers[i].Execute( deltaTime ); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/ParticlesFollowBezier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace BezierSolution 5 | { 6 | [AddComponentMenu( "Bezier Solution/Particles Follow Bezier" )] 7 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 8 | [RequireComponent( typeof( ParticleSystem ) )] 9 | [ExecuteInEditMode] 10 | public class ParticlesFollowBezier : MonoBehaviour 11 | { 12 | private const int MAX_PARTICLE_COUNT = 25000; 13 | 14 | public enum FollowMode { Relaxed, Strict }; 15 | 16 | public BezierSpline spline; 17 | public FollowMode followMode = FollowMode.Relaxed; 18 | 19 | private Transform cachedTransform; 20 | private ParticleSystem cachedPS; 21 | private ParticleSystem.MainModule cachedMainModule; 22 | 23 | private ParticleSystem.Particle[] particles; 24 | private List particleData; 25 | 26 | private void Awake() 27 | { 28 | cachedTransform = transform; 29 | cachedPS = GetComponent(); 30 | 31 | cachedMainModule = cachedPS.main; 32 | particles = new ParticleSystem.Particle[cachedMainModule.maxParticles]; 33 | 34 | if( followMode == FollowMode.Relaxed ) 35 | particleData = new List( particles.Length ); 36 | } 37 | 38 | #if UNITY_EDITOR 39 | private void OnEnable() 40 | { 41 | Awake(); 42 | } 43 | #endif 44 | 45 | #if UNITY_EDITOR 46 | private void LateUpdate() 47 | { 48 | if( !UnityEditor.EditorApplication.isPlaying ) 49 | FixedUpdate(); 50 | } 51 | #endif 52 | 53 | private void FixedUpdate() 54 | { 55 | if( spline == null || cachedPS == null ) 56 | return; 57 | 58 | if( particles.Length < cachedMainModule.maxParticles && particles.Length < MAX_PARTICLE_COUNT ) 59 | System.Array.Resize( ref particles, Mathf.Min( cachedMainModule.maxParticles, MAX_PARTICLE_COUNT ) ); 60 | 61 | bool isLocalSpace = cachedMainModule.simulationSpace != ParticleSystemSimulationSpace.World; 62 | int aliveParticles = cachedPS.GetParticles( particles ); 63 | 64 | Vector3 initialPoint = spline.GetPoint( 0f ); 65 | if( followMode == FollowMode.Relaxed ) 66 | { 67 | if( particleData == null ) 68 | particleData = new List( particles.Length ); 69 | 70 | cachedPS.GetCustomParticleData( particleData, ParticleSystemCustomData.Custom1 ); 71 | 72 | // Credit: https://forum.unity3d.com/threads/access-to-the-particle-system-lifecycle-events.328918/#post-2295977 73 | for( int i = 0; i < aliveParticles; i++ ) 74 | { 75 | Vector4 particleDat = particleData[i]; 76 | Vector3 point = spline.GetPoint( 1f - ( particles[i].remainingLifetime / particles[i].startLifetime ) ); 77 | if( !isLocalSpace ) 78 | point = cachedTransform.TransformPoint( point - initialPoint ); 79 | 80 | // Move particles alongside the spline 81 | if( particleDat.w != 0f ) 82 | particles[i].position += point - (Vector3) particleDat; 83 | 84 | particleDat = point; 85 | particleDat.w = 1f; 86 | particleData[i] = particleDat; 87 | } 88 | 89 | cachedPS.SetCustomParticleData( particleData, ParticleSystemCustomData.Custom1 ); 90 | } 91 | else 92 | { 93 | for( int i = 0; i < aliveParticles; i++ ) 94 | { 95 | Vector3 point = spline.GetPoint( 1f - ( particles[i].remainingLifetime / particles[i].startLifetime ) ) - initialPoint; 96 | if( !isLocalSpace ) 97 | point = cachedTransform.TransformPoint( point ); 98 | 99 | particles[i].position = point; 100 | } 101 | } 102 | 103 | cachedPS.SetParticles( particles, aliveParticles ); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierWalkerLocomotion.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace BezierSolution 5 | { 6 | [AddComponentMenu( "Bezier Solution/Bezier Walker Locomotion" )] 7 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 8 | public class BezierWalkerLocomotion : BezierWalker 9 | { 10 | public BezierWalker walker; 11 | 12 | #pragma warning disable 0649 13 | [SerializeField] 14 | private List tailObjects; 15 | public List Tail { get { return tailObjects; } } 16 | 17 | [SerializeField] 18 | private List tailObjectDistances; 19 | public List TailDistances { get { return tailObjectDistances; } } 20 | #pragma warning restore 0649 21 | 22 | public bool highQuality = true; // true by default because when it is set to false, tail objects can jitter too much 23 | 24 | public float movementLerpModifier = 10f; 25 | public float rotationLerpModifier = 10f; 26 | 27 | public LookAtMode lookAt = LookAtMode.ZForward; 28 | 29 | public override BezierSpline Spline { get { return walker.Spline; } } 30 | 31 | public override bool MovingForward 32 | { 33 | get { return walker.MovingForward; } 34 | set { walker.MovingForward = value; } 35 | } 36 | 37 | public override float NormalizedT 38 | { 39 | get { return walker.NormalizedT; } 40 | set { walker.NormalizedT = value; } 41 | } 42 | 43 | private void Start() 44 | { 45 | if( !walker ) 46 | { 47 | Debug.LogError( "Need to attach BezierWalkerLocomotion to a BezierWalker!" ); 48 | Destroy( this ); 49 | } 50 | 51 | if( tailObjects.Count != tailObjectDistances.Count ) 52 | { 53 | Debug.LogError( "One distance per tail object is needed!" ); 54 | Destroy( this ); 55 | } 56 | } 57 | 58 | private void LateUpdate() 59 | { 60 | Execute( Time.deltaTime ); 61 | } 62 | 63 | public override void Execute( float deltaTime ) 64 | { 65 | BezierSpline spline = Spline; 66 | float t = highQuality ? spline.evenlySpacedPoints.GetPercentageAtNormalizedT( NormalizedT ) : NormalizedT; 67 | bool forward = MovingForward; 68 | 69 | for( int i = 0; i < tailObjects.Count; i++ ) 70 | { 71 | Transform tailObject = tailObjects[i]; 72 | Vector3 tailPosition; 73 | float tailNormalizedT; 74 | if( highQuality ) 75 | { 76 | if( forward ) 77 | t -= tailObjectDistances[i] / spline.evenlySpacedPoints.splineLength; 78 | else 79 | t += tailObjectDistances[i] / spline.evenlySpacedPoints.splineLength; 80 | 81 | tailNormalizedT = spline.evenlySpacedPoints.GetNormalizedTAtPercentage( t ); 82 | tailPosition = spline.GetPoint( tailNormalizedT ); 83 | } 84 | else 85 | { 86 | tailPosition = spline.MoveAlongSpline( ref t, forward ? -tailObjectDistances[i] : tailObjectDistances[i] ); 87 | tailNormalizedT = t; 88 | } 89 | 90 | tailObject.position = Vector3.Lerp( tailObject.position, tailPosition, movementLerpModifier * deltaTime ); 91 | RotateTarget( tailObject, tailNormalizedT, lookAt, rotationLerpModifier * deltaTime ); 92 | } 93 | } 94 | 95 | public void AddToTail( Transform transform, float distanceToPreviousObject ) 96 | { 97 | if( transform == null ) 98 | { 99 | Debug.LogError( "Object is null!" ); 100 | return; 101 | } 102 | 103 | tailObjects.Add( transform ); 104 | tailObjectDistances.Add( distanceToPreviousObject ); 105 | } 106 | 107 | public void InsertIntoTail( int index, Transform transform, float distanceToPreviousObject ) 108 | { 109 | if( transform == null ) 110 | { 111 | Debug.LogError( "Object is null!" ); 112 | return; 113 | } 114 | 115 | tailObjects.Insert( index, transform ); 116 | tailObjectDistances.Insert( index, distanceToPreviousObject ); 117 | } 118 | 119 | public void RemoveFromTail( Transform transform ) 120 | { 121 | if( transform == null ) 122 | { 123 | Debug.LogError( "Object is null!" ); 124 | return; 125 | } 126 | 127 | for( int i = 0; i < tailObjects.Count; i++ ) 128 | { 129 | if( tailObjects[i] == transform ) 130 | { 131 | tailObjects.RemoveAt( i ); 132 | tailObjectDistances.RemoveAt( i ); 133 | 134 | return; 135 | } 136 | } 137 | } 138 | 139 | #if UNITY_EDITOR 140 | private void Reset() 141 | { 142 | BezierWalker[] walkerComponents = GetComponents(); 143 | for( int i = 0; i < walkerComponents.Length; i++ ) 144 | { 145 | if( !( walkerComponents[i] is BezierWalkerLocomotion ) && ( (MonoBehaviour) walkerComponents[i] ).enabled ) 146 | { 147 | walker = walkerComponents[i]; 148 | break; 149 | } 150 | } 151 | } 152 | #endif 153 | } 154 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierLineRenderer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BezierSolution 4 | { 5 | [AddComponentMenu( "Bezier Solution/Bezier Line Renderer" )] 6 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 7 | [RequireComponent( typeof( LineRenderer ) )] 8 | [ExecuteInEditMode] 9 | public class BezierLineRenderer : MonoBehaviour 10 | { 11 | #pragma warning disable 0649 12 | [SerializeField] 13 | private BezierSpline m_spline; 14 | public BezierSpline spline 15 | { 16 | get { return m_spline; } 17 | set 18 | { 19 | if( m_spline != value ) 20 | { 21 | if( m_spline ) 22 | m_spline.onSplineChanged -= OnSplineChanged; 23 | 24 | m_spline = value; 25 | 26 | if( m_spline && isActiveAndEnabled ) 27 | { 28 | m_spline.onSplineChanged -= OnSplineChanged; 29 | m_spline.onSplineChanged += OnSplineChanged; 30 | 31 | OnSplineChanged( m_spline, DirtyFlags.All ); 32 | } 33 | } 34 | } 35 | } 36 | 37 | [SerializeField] 38 | [MinMaxRange( 0f, 1f )] 39 | private Vector2 m_splineSampleRange = new Vector2( 0f, 1f ); 40 | public Vector2 SplineSampleRange 41 | { 42 | get { return m_splineSampleRange; } 43 | set 44 | { 45 | value.x = Mathf.Clamp01( value.x ); 46 | value.y = Mathf.Clamp01( value.y ); 47 | 48 | if( m_splineSampleRange != value ) 49 | { 50 | m_splineSampleRange = value; 51 | 52 | if( isActiveAndEnabled ) 53 | OnSplineChanged( m_spline, DirtyFlags.All ); 54 | } 55 | } 56 | } 57 | 58 | [Header( "Line Options" )] 59 | [SerializeField] 60 | [Range( 0, 30 )] 61 | private int m_smoothness = 5; 62 | public int smoothness 63 | { 64 | get { return m_smoothness; } 65 | set 66 | { 67 | if( m_smoothness != value ) 68 | { 69 | m_smoothness = value; 70 | 71 | if( isActiveAndEnabled ) 72 | OnSplineChanged( m_spline, DirtyFlags.All ); 73 | } 74 | } 75 | } 76 | 77 | #if UNITY_EDITOR 78 | [Header( "Other Settings" )] 79 | [SerializeField] 80 | private bool executeInEditMode = false; 81 | 82 | [SerializeField, HideInInspector] 83 | private BezierSpline prevSpline; 84 | #endif 85 | #pragma warning restore 0649 86 | 87 | private LineRenderer lineRenderer; 88 | private Vector3[] lineRendererPoints; 89 | #if UNITY_EDITOR 90 | private bool lineRendererUseWorldSpace = true; 91 | #endif 92 | 93 | private void OnEnable() 94 | { 95 | if( m_spline ) 96 | { 97 | m_spline.onSplineChanged -= OnSplineChanged; 98 | m_spline.onSplineChanged += OnSplineChanged; 99 | 100 | OnSplineChanged( m_spline, DirtyFlags.All ); 101 | } 102 | } 103 | 104 | private void OnDisable() 105 | { 106 | if( m_spline ) 107 | m_spline.onSplineChanged -= OnSplineChanged; 108 | } 109 | 110 | #if UNITY_EDITOR 111 | private void Update() 112 | { 113 | if( lineRenderer && lineRenderer.useWorldSpace != lineRendererUseWorldSpace ) 114 | { 115 | lineRendererUseWorldSpace = !lineRendererUseWorldSpace; 116 | 117 | if( isActiveAndEnabled ) 118 | OnSplineChanged( m_spline, DirtyFlags.All ); 119 | } 120 | } 121 | 122 | private void OnValidate() 123 | { 124 | BezierSpline _spline = m_spline; 125 | m_spline = prevSpline; 126 | spline = prevSpline = _spline; 127 | 128 | if( isActiveAndEnabled ) 129 | OnSplineChanged( m_spline, DirtyFlags.All ); 130 | } 131 | #endif 132 | 133 | private void OnSplineChanged( BezierSpline spline, DirtyFlags dirtyFlags ) 134 | { 135 | #if UNITY_EDITOR 136 | if( !executeInEditMode && !UnityEditor.EditorApplication.isPlaying ) 137 | return; 138 | #endif 139 | 140 | if( ( dirtyFlags & DirtyFlags.SplineShapeChanged ) == DirtyFlags.SplineShapeChanged ) 141 | Refresh( m_smoothness ); 142 | } 143 | 144 | public void Refresh( int smoothness ) 145 | { 146 | if( !m_spline || m_spline.Count < 2 ) 147 | return; 148 | 149 | if( !lineRenderer ) 150 | lineRenderer = GetComponent(); 151 | 152 | smoothness = Mathf.Clamp( smoothness, 1, 30 ); 153 | 154 | int numberOfPoints = ( m_spline.Count - 1 ) * smoothness; 155 | if( !m_spline.loop ) 156 | numberOfPoints++; // spline.GetPoint( 1f ) 157 | else 158 | numberOfPoints += smoothness; // Final point is connected to the first point via lineRenderer.loop, so no "numberOfPoints++" here 159 | 160 | if( lineRendererPoints == null || lineRendererPoints.Length != numberOfPoints ) 161 | lineRendererPoints = new Vector3[numberOfPoints]; 162 | 163 | if( m_splineSampleRange.x <= 0f && m_splineSampleRange.y >= 1f ) 164 | { 165 | int pointIndex = 0; 166 | float smoothnessStep = 1f / smoothness; 167 | for( int i = 0; i < m_spline.Count - 1; i++ ) 168 | { 169 | BezierSpline.Segment segment = new BezierSpline.Segment( m_spline[i], m_spline[i + 1], 0f ); 170 | for( int j = 0; j < smoothness; j++, pointIndex++ ) 171 | lineRendererPoints[pointIndex] = segment.GetPoint( j * smoothnessStep ); 172 | } 173 | 174 | if( !m_spline.loop ) 175 | lineRendererPoints[numberOfPoints - 1] = m_spline.GetPoint( 1f ); 176 | else 177 | { 178 | BezierSpline.Segment segment = new BezierSpline.Segment( m_spline[m_spline.Count - 1], m_spline[0], 0f ); 179 | for( int j = 0; j < smoothness; j++, pointIndex++ ) 180 | lineRendererPoints[pointIndex] = segment.GetPoint( j * smoothnessStep ); 181 | } 182 | } 183 | else 184 | { 185 | float smoothnessStep = ( m_splineSampleRange.y - m_splineSampleRange.x ) / ( numberOfPoints - 1 ); 186 | for( int i = 0; i < numberOfPoints; i++ ) 187 | lineRendererPoints[i] = spline.GetPoint( m_splineSampleRange.x + i * smoothnessStep ); 188 | } 189 | 190 | #if UNITY_EDITOR 191 | lineRendererUseWorldSpace = lineRenderer.useWorldSpace; 192 | #endif 193 | if( !lineRenderer.useWorldSpace ) 194 | { 195 | Vector3 initialPoint = m_spline.GetPoint( 0f ); 196 | for( int i = 0; i < numberOfPoints; i++ ) 197 | lineRendererPoints[i] -= initialPoint; 198 | } 199 | 200 | lineRenderer.positionCount = lineRendererPoints.Length; 201 | lineRenderer.SetPositions( lineRendererPoints ); 202 | lineRenderer.loop = m_spline.loop && m_splineSampleRange.x <= 0f && m_splineSampleRange.y >= 1f; 203 | 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BezierAttachment.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BezierSolution 4 | { 5 | [AddComponentMenu( "Bezier Solution/Bezier Attachment" )] 6 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 7 | [ExecuteInEditMode] 8 | public class BezierAttachment : MonoBehaviour 9 | { 10 | public enum RotationMode { No = 0, UseSplineNormals = 1, UseEndPointRotations = 2 }; 11 | 12 | #pragma warning disable 0649 13 | [SerializeField] 14 | private BezierSpline m_spline; 15 | public BezierSpline spline 16 | { 17 | get { return m_spline; } 18 | set 19 | { 20 | if( m_spline != value ) 21 | { 22 | if( m_spline ) 23 | m_spline.onSplineChanged -= OnSplineChanged; 24 | 25 | m_spline = value; 26 | 27 | if( m_spline && isActiveAndEnabled ) 28 | { 29 | m_spline.onSplineChanged -= OnSplineChanged; 30 | m_spline.onSplineChanged += OnSplineChanged; 31 | 32 | OnSplineChanged( m_spline, DirtyFlags.All ); 33 | } 34 | } 35 | } 36 | } 37 | 38 | [SerializeField] 39 | [Range( 0f, 1f )] 40 | private float m_normalizedT = 0f; 41 | public float normalizedT 42 | { 43 | get { return m_normalizedT; } 44 | set 45 | { 46 | value = Mathf.Clamp01( value ); 47 | 48 | if( m_normalizedT != value ) 49 | { 50 | m_normalizedT = value; 51 | 52 | if( isActiveAndEnabled ) 53 | OnSplineChanged( m_spline, DirtyFlags.All ); 54 | } 55 | } 56 | } 57 | 58 | [Header( "Position" )] 59 | [SerializeField] 60 | private bool m_updatePosition = true; 61 | public bool updatePosition 62 | { 63 | get { return m_updatePosition; } 64 | set 65 | { 66 | if( m_updatePosition != value ) 67 | { 68 | m_updatePosition = value; 69 | 70 | if( m_updatePosition && isActiveAndEnabled ) 71 | OnSplineChanged( m_spline, DirtyFlags.SplineShapeChanged ); 72 | } 73 | } 74 | } 75 | 76 | [SerializeField] 77 | private Vector3 m_positionOffset; 78 | public Vector3 positionOffset 79 | { 80 | get { return m_positionOffset; } 81 | set 82 | { 83 | if( m_positionOffset != value ) 84 | { 85 | m_positionOffset = value; 86 | 87 | if( m_updatePosition && isActiveAndEnabled ) 88 | OnSplineChanged( m_spline, DirtyFlags.SplineShapeChanged ); 89 | } 90 | } 91 | } 92 | 93 | [Header( "Rotation" )] 94 | [SerializeField] 95 | private RotationMode m_updateRotation = RotationMode.UseSplineNormals; 96 | public RotationMode updateRotation 97 | { 98 | get { return m_updateRotation; } 99 | set 100 | { 101 | if( m_updateRotation != value ) 102 | { 103 | m_updateRotation = value; 104 | 105 | if( m_updateRotation != RotationMode.No && isActiveAndEnabled ) 106 | OnSplineChanged( m_spline, DirtyFlags.SplineShapeChanged ); 107 | } 108 | } 109 | } 110 | 111 | [SerializeField] 112 | private Vector3 m_rotationOffset; 113 | public Vector3 rotationOffset 114 | { 115 | get { return m_rotationOffset; } 116 | set 117 | { 118 | if( m_rotationOffset != value ) 119 | { 120 | m_rotationOffset = value; 121 | 122 | if( m_updateRotation != RotationMode.No && isActiveAndEnabled ) 123 | OnSplineChanged( m_spline, DirtyFlags.SplineShapeChanged ); 124 | } 125 | } 126 | } 127 | 128 | #if UNITY_EDITOR 129 | [Header( "Other Settings" )] 130 | [SerializeField] 131 | private bool executeInEditMode = false; 132 | 133 | [SerializeField, HideInInspector] 134 | private BezierSpline prevSpline; 135 | #endif 136 | #pragma warning restore 0649 137 | 138 | private void OnEnable() 139 | { 140 | if( m_spline ) 141 | { 142 | m_spline.onSplineChanged -= OnSplineChanged; 143 | m_spline.onSplineChanged += OnSplineChanged; 144 | 145 | OnSplineChanged( m_spline, DirtyFlags.All ); 146 | } 147 | } 148 | 149 | private void OnDisable() 150 | { 151 | if( m_spline ) 152 | m_spline.onSplineChanged -= OnSplineChanged; 153 | } 154 | 155 | #if UNITY_EDITOR 156 | private void OnValidate() 157 | { 158 | UnityEditor.Undo.RecordObject( transform, "Modify BezierAttachment" ); 159 | 160 | BezierSpline _spline = m_spline; 161 | m_spline = prevSpline; 162 | spline = prevSpline = _spline; 163 | 164 | if( isActiveAndEnabled ) 165 | OnSplineChanged( m_spline, DirtyFlags.All ); 166 | } 167 | 168 | private void LateUpdate() 169 | { 170 | if( transform.hasChanged ) 171 | OnSplineChanged( m_spline, DirtyFlags.All ); 172 | } 173 | #endif 174 | 175 | private void OnSplineChanged( BezierSpline spline, DirtyFlags dirtyFlags ) 176 | { 177 | #if UNITY_EDITOR 178 | if( !executeInEditMode && !UnityEditor.EditorApplication.isPlaying ) 179 | return; 180 | #endif 181 | 182 | RefreshInternal( dirtyFlags ); 183 | } 184 | 185 | public void Refresh() 186 | { 187 | RefreshInternal( DirtyFlags.All ); 188 | } 189 | 190 | private void RefreshInternal( DirtyFlags dirtyFlags ) 191 | { 192 | if( !m_spline || m_spline.Count < 2 ) 193 | return; 194 | 195 | if( !m_updatePosition && m_updateRotation == RotationMode.No ) 196 | return; 197 | 198 | BezierSpline.Segment segment = m_spline.GetSegmentAt( m_normalizedT ); 199 | 200 | switch( m_updateRotation ) 201 | { 202 | case RotationMode.UseSplineNormals: 203 | if( m_rotationOffset == Vector3.zero ) 204 | transform.rotation = Quaternion.LookRotation( segment.GetTangent(), segment.GetNormal() ); 205 | else 206 | transform.rotation = Quaternion.LookRotation( segment.GetTangent(), segment.GetNormal() ) * Quaternion.Euler( m_rotationOffset ); 207 | 208 | break; 209 | case RotationMode.UseEndPointRotations: 210 | if( m_rotationOffset == Vector3.zero ) 211 | transform.rotation = Quaternion.LerpUnclamped( segment.point1.rotation, segment.point2.rotation, segment.localT ); 212 | else 213 | transform.rotation = Quaternion.LerpUnclamped( segment.point1.rotation, segment.point2.rotation, segment.localT ) * Quaternion.Euler( m_rotationOffset ); 214 | 215 | break; 216 | } 217 | 218 | if( m_updatePosition && ( dirtyFlags & DirtyFlags.SplineShapeChanged ) == DirtyFlags.SplineShapeChanged ) 219 | { 220 | if( m_positionOffset == Vector3.zero ) 221 | transform.position = segment.GetPoint(); 222 | else 223 | transform.position = segment.GetPoint() + transform.rotation * m_positionOffset; 224 | } 225 | 226 | #if UNITY_EDITOR 227 | transform.hasChanged = false; 228 | #endif 229 | } 230 | } 231 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Other/BezierDataStructures.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BezierSolution 4 | { 5 | public enum SplineAutoConstructMode { None = 0, Linear = 1, Smooth1 = 2, Smooth2 = 3 }; 6 | 7 | [System.Flags] 8 | internal enum InternalDirtyFlags 9 | { 10 | None = 0, 11 | EndPointTransformChange = 1 << 1, 12 | ControlPointPositionChange = 1 << 2, 13 | NormalChange = 1 << 3, 14 | NormalOffsetChange = 1 << 4, 15 | ExtraDataChange = 1 << 5, 16 | All = EndPointTransformChange | ControlPointPositionChange | NormalChange | NormalOffsetChange | ExtraDataChange 17 | }; 18 | 19 | [System.Flags] 20 | public enum DirtyFlags 21 | { 22 | None = 0, 23 | SplineShapeChanged = 1 << 1, 24 | NormalsChanged = 1 << 2, 25 | ExtraDataChanged = 1 << 3, 26 | All = SplineShapeChanged | NormalsChanged | ExtraDataChanged 27 | }; 28 | 29 | [System.Flags] 30 | public enum PointCacheFlags 31 | { 32 | None = 0, 33 | Positions = 1 << 1, 34 | Normals = 1 << 2, 35 | Tangents = 1 << 3, 36 | Bitangents = 1 << 4, 37 | ExtraDatas = 1 << 5, 38 | All = Positions | Normals | Tangents | Bitangents | ExtraDatas 39 | }; 40 | 41 | public delegate void SplineChangeDelegate( BezierSpline spline, DirtyFlags dirtyFlags ); 42 | public delegate BezierPoint.ExtraData ExtraDataLerpFunction( BezierPoint.ExtraData data1, BezierPoint.ExtraData data2, float normalizedT ); 43 | 44 | public partial class BezierSpline 45 | { 46 | private static readonly ExtraDataLerpFunction defaultExtraDataLerpFunction = BezierPoint.ExtraData.LerpUnclamped; 47 | 48 | public struct Segment 49 | { 50 | public readonly BezierPoint point1, point2; 51 | public readonly float localT; 52 | 53 | public Segment( BezierPoint point1, BezierPoint point2, float localT ) 54 | { 55 | this.point1 = point1; 56 | this.point2 = point2; 57 | this.localT = localT; 58 | } 59 | 60 | public float GetNormalizedT() { return GetNormalizedT( localT ); } 61 | public float GetNormalizedT( float localT ) 62 | { 63 | BezierSpline spline = point1.spline; 64 | return ( point1.index + localT ) / ( spline.m_loop ? spline.Count : ( spline.Count - 1 ) ); 65 | } 66 | 67 | public Vector3 GetPoint() { return GetPoint( localT ); } 68 | public Vector3 GetPoint( float localT ) 69 | { 70 | float oneMinusLocalT = 1f - localT; 71 | 72 | return oneMinusLocalT * oneMinusLocalT * oneMinusLocalT * point1.position + 73 | 3f * oneMinusLocalT * oneMinusLocalT * localT * point1.followingControlPointPosition + 74 | 3f * oneMinusLocalT * localT * localT * point2.precedingControlPointPosition + 75 | localT * localT * localT * point2.position; 76 | } 77 | 78 | public Vector3 GetTangent() { return GetTangent( localT ); } 79 | public Vector3 GetTangent( float localT ) 80 | { 81 | float oneMinusLocalT = 1f - localT; 82 | 83 | return 3f * oneMinusLocalT * oneMinusLocalT * ( point1.followingControlPointPosition - point1.position ) + 84 | 6f * oneMinusLocalT * localT * ( point2.precedingControlPointPosition - point1.followingControlPointPosition ) + 85 | 3f * localT * localT * ( point2.position - point2.precedingControlPointPosition ); 86 | } 87 | 88 | public Vector3 GetNormal() { return GetNormal( localT ); } 89 | public Vector3 GetNormal( float localT ) 90 | { 91 | Vector3[] intermediateNormals = point1.intermediateNormals; 92 | if( intermediateNormals != null && intermediateNormals.Length > 0 ) 93 | { 94 | localT = Mathf.Clamp01( localT ) * ( intermediateNormals.Length - 1 ); 95 | int localStartIndex = (int) localT; 96 | 97 | return ( localStartIndex < intermediateNormals.Length - 1 ) ? Vector3.LerpUnclamped( intermediateNormals[localStartIndex], intermediateNormals[localStartIndex + 1], localT - localStartIndex ) : intermediateNormals[localStartIndex]; 98 | } 99 | 100 | Vector3 startNormal = point1.normal; 101 | Vector3 endNormal = point2.normal; 102 | 103 | Vector3 normal = Vector3.LerpUnclamped( startNormal, endNormal, localT ); 104 | if( normal.y == 0f && normal.x == 0f && normal.z == 0f ) 105 | { 106 | // Don't return Vector3.zero as normal 107 | normal = Vector3.LerpUnclamped( startNormal, endNormal, localT > 0.01f ? ( localT - 0.01f ) : ( localT + 0.01f ) ); 108 | if( normal.y == 0f && normal.x == 0f && normal.z == 0f ) 109 | normal = Vector3.up; 110 | } 111 | 112 | return normal; 113 | } 114 | 115 | public BezierPoint.ExtraData GetExtraData() { return defaultExtraDataLerpFunction( point1.extraData, point2.extraData, localT ); } 116 | public BezierPoint.ExtraData GetExtraData( float localT ) { return defaultExtraDataLerpFunction( point1.extraData, point2.extraData, localT ); } 117 | public BezierPoint.ExtraData GetExtraData( ExtraDataLerpFunction lerpFunction ) { return lerpFunction( point1.extraData, point2.extraData, localT ); } 118 | public BezierPoint.ExtraData GetExtraData( float localT, ExtraDataLerpFunction lerpFunction ) { return lerpFunction( point1.extraData, point2.extraData, localT ); } 119 | } 120 | 121 | public class EvenlySpacedPointsHolder 122 | { 123 | public readonly BezierSpline spline; 124 | public readonly float splineLength; 125 | public readonly float[] uniformNormalizedTs; 126 | 127 | public EvenlySpacedPointsHolder( BezierSpline spline, float splineLength, float[] uniformNormalizedTs ) 128 | { 129 | this.spline = spline; 130 | this.splineLength = splineLength; 131 | this.uniformNormalizedTs = uniformNormalizedTs; 132 | } 133 | 134 | public float GetNormalizedTAtDistance( float distance ) 135 | { 136 | return GetNormalizedTAtPercentage( distance / splineLength ); 137 | } 138 | 139 | public float GetNormalizedTAtPercentage( float percentage ) 140 | { 141 | if( !spline.loop ) 142 | { 143 | if( percentage <= 0f ) 144 | return 0f; 145 | else if( percentage >= 1f ) 146 | return 1f; 147 | } 148 | else 149 | percentage = ( ( percentage % 1f ) + 1f ) % 1f; 150 | 151 | float indexRaw = ( uniformNormalizedTs.Length - 1 ) * percentage; 152 | int index = (int) indexRaw; 153 | return Mathf.LerpUnclamped( uniformNormalizedTs[index], uniformNormalizedTs[index + 1], indexRaw - index ); 154 | } 155 | 156 | public float GetPercentageAtNormalizedT( float normalizedT ) 157 | { 158 | if( !spline.loop ) 159 | { 160 | if( normalizedT <= 0f ) 161 | return 0f; 162 | else if( normalizedT >= 1f ) 163 | return 1f; 164 | } 165 | else 166 | normalizedT = ( ( normalizedT % 1f ) + 1f ) % 1f; 167 | 168 | // Perform binary search 169 | int lowerBound = 0; 170 | int upperBound = uniformNormalizedTs.Length - 1; 171 | while( lowerBound <= upperBound ) 172 | { 173 | int index = lowerBound + ( ( upperBound - lowerBound ) >> 1 ); 174 | float arrElement = uniformNormalizedTs[index]; 175 | if( arrElement < normalizedT ) 176 | lowerBound = index + 1; 177 | else if( arrElement > normalizedT ) 178 | upperBound = index - 1; 179 | else 180 | return index / (float) ( uniformNormalizedTs.Length - 1 ); 181 | } 182 | 183 | float inverseLerp = ( normalizedT - uniformNormalizedTs[lowerBound] ) / ( uniformNormalizedTs[lowerBound - 1] - uniformNormalizedTs[lowerBound] ); 184 | return ( lowerBound - inverseLerp ) / ( uniformNormalizedTs.Length - 1 ); 185 | } 186 | } 187 | 188 | public class PointCache 189 | { 190 | public readonly Vector3[] positions, normals, tangents, bitangents; 191 | public readonly BezierPoint.ExtraData[] extraDatas; 192 | public readonly bool loop; 193 | 194 | public PointCache( Vector3[] positions, Vector3[] normals, Vector3[] tangents, Vector3[] bitangents, BezierPoint.ExtraData[] extraDatas, bool loop ) 195 | { 196 | this.positions = positions; 197 | this.normals = normals; 198 | this.tangents = tangents; 199 | this.bitangents = bitangents; 200 | this.extraDatas = extraDatas; 201 | this.loop = loop; 202 | } 203 | 204 | public Vector3 GetPoint( float percentage ) { return LerpArray( positions, percentage ); } 205 | public Vector3 GetNormal( float percentage ) { return LerpArray( normals, percentage ); } 206 | public Vector3 GetTangent( float percentage ) { return LerpArray( tangents, percentage ); } 207 | public Vector3 GetBitangent( float percentage ) { return LerpArray( bitangents, percentage ); } 208 | 209 | public BezierPoint.ExtraData GetExtraData( float percentage ) { return GetExtraData( percentage, defaultExtraDataLerpFunction ); } 210 | public BezierPoint.ExtraData GetExtraData( float percentage, ExtraDataLerpFunction lerpFunction ) 211 | { 212 | if( !loop ) 213 | { 214 | if( percentage <= 0f ) 215 | return extraDatas[0]; 216 | else if( percentage >= 1f ) 217 | return extraDatas[extraDatas.Length - 1]; 218 | } 219 | else 220 | percentage = ( ( percentage % 1f ) + 1f ) % 1f; 221 | 222 | float t = percentage * ( loop ? extraDatas.Length : ( extraDatas.Length - 1 ) ); 223 | 224 | int startIndex = (int) t; 225 | int endIndex = startIndex + 1; 226 | 227 | if( endIndex == extraDatas.Length ) 228 | endIndex = 0; 229 | 230 | return lerpFunction( extraDatas[startIndex], extraDatas[endIndex], t - startIndex ); 231 | } 232 | 233 | private Vector3 LerpArray( Vector3[] array, float percentage ) 234 | { 235 | if( !loop ) 236 | { 237 | if( percentage <= 0f ) 238 | return array[0]; 239 | else if( percentage >= 1f ) 240 | return array[array.Length - 1]; 241 | } 242 | else 243 | percentage = ( ( percentage % 1f ) + 1f ) % 1f; 244 | 245 | float t = percentage * ( loop ? array.Length : ( array.Length - 1 ) ); 246 | 247 | int startIndex = (int) t; 248 | int endIndex = startIndex + 1; 249 | 250 | if( endIndex == array.Length ) 251 | endIndex = 0; 252 | 253 | return Vector3.LerpUnclamped( array[startIndex], array[endIndex], t - startIndex ); 254 | } 255 | } 256 | } 257 | 258 | public partial class BezierPoint 259 | { 260 | public enum HandleMode { Free, Aligned, Mirrored }; 261 | 262 | [System.Serializable] 263 | public struct ExtraData 264 | { 265 | public float c1, c2, c3, c4; 266 | 267 | public ExtraData( float c1 = 0f, float c2 = 0f, float c3 = 0f, float c4 = 0f ) 268 | { 269 | this.c1 = c1; 270 | this.c2 = c2; 271 | this.c3 = c3; 272 | this.c4 = c4; 273 | } 274 | 275 | public static ExtraData Lerp( ExtraData a, ExtraData b, float t ) 276 | { 277 | t = Mathf.Clamp01( t ); 278 | return new ExtraData( 279 | a.c1 + ( b.c1 - a.c1 ) * t, 280 | a.c2 + ( b.c2 - a.c2 ) * t, 281 | a.c3 + ( b.c3 - a.c3 ) * t, 282 | a.c4 + ( b.c4 - a.c4 ) * t ); 283 | } 284 | 285 | public static ExtraData LerpUnclamped( ExtraData a, ExtraData b, float t ) 286 | { 287 | return new ExtraData( 288 | a.c1 + ( b.c1 - a.c1 ) * t, 289 | a.c2 + ( b.c2 - a.c2 ) * t, 290 | a.c3 + ( b.c3 - a.c3 ) * t, 291 | a.c4 + ( b.c4 - a.c4 ) * t ); 292 | } 293 | 294 | public static implicit operator ExtraData( Vector2 v ) { return new ExtraData( v.x, v.y ); } 295 | public static implicit operator ExtraData( Vector3 v ) { return new ExtraData( v.x, v.y, v.z ); } 296 | public static implicit operator ExtraData( Vector4 v ) { return new ExtraData( v.x, v.y, v.z, v.w ); } 297 | public static implicit operator ExtraData( Quaternion q ) { return new ExtraData( q.x, q.y, q.z, q.w ); } 298 | public static implicit operator ExtraData( Rect r ) { return new ExtraData( r.xMin, r.yMin, r.width, r.height ); } 299 | #if UNITY_2017_2_OR_NEWER 300 | public static implicit operator ExtraData( Vector2Int v ) { return new ExtraData( v.x, v.y ); } 301 | public static implicit operator ExtraData( Vector3Int v ) { return new ExtraData( v.x, v.y, v.z ); } 302 | public static implicit operator ExtraData( RectInt r ) { return new ExtraData( r.xMin, r.yMin, r.width, r.height ); } 303 | #endif 304 | 305 | public static implicit operator Vector2( ExtraData v ) { return new Vector2( v.c1, v.c2 ); } 306 | public static implicit operator Vector3( ExtraData v ) { return new Vector3( v.c1, v.c2, v.c3 ); } 307 | public static implicit operator Vector4( ExtraData v ) { return new Vector4( v.c1, v.c2, v.c3, v.c4 ); } 308 | public static implicit operator Quaternion( ExtraData v ) { return new Quaternion( v.c1, v.c2, v.c3, v.c4 ); } 309 | public static implicit operator Rect( ExtraData v ) { return new Rect( v.c1, v.c2, v.c3, v.c4 ); } 310 | #if UNITY_2017_2_OR_NEWER 311 | public static implicit operator Vector2Int( ExtraData v ) { return new Vector2Int( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ) ); } 312 | public static implicit operator Vector3Int( ExtraData v ) { return new Vector3Int( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ), Mathf.RoundToInt( v.c3 ) ); } 313 | public static implicit operator RectInt( ExtraData v ) { return new RectInt( Mathf.RoundToInt( v.c1 ), Mathf.RoundToInt( v.c2 ), Mathf.RoundToInt( v.c3 ), Mathf.RoundToInt( v.c4 ) ); } 314 | #endif 315 | 316 | public static bool operator ==( ExtraData d1, ExtraData d2 ) { return d1.c1 == d2.c1 && d1.c2 == d2.c2 && d1.c3 == d2.c3 && d1.c4 == d2.c4; } 317 | public static bool operator !=( ExtraData d1, ExtraData d2 ) { return d1.c1 != d2.c1 || d1.c2 != d2.c2 || d1.c3 != d2.c3 || d1.c4 != d2.c4; } 318 | 319 | public override bool Equals( object obj ) { return obj is ExtraData && this == (ExtraData) obj; } 320 | public override int GetHashCode() { return unchecked((int) ( ( ( ( 17 * 23 + c1 ) * 23 + c2 ) * 23 + c3 ) * 23 + c4 )); } 321 | public override string ToString() { return ( (Vector4) this ).ToString(); } 322 | } 323 | } 324 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/BezierPoint.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace BezierSolution 4 | { 5 | [AddComponentMenu( "Bezier Solution/Bezier Point" )] 6 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 7 | public partial class BezierPoint : MonoBehaviour 8 | { 9 | public Vector3 localPosition 10 | { 11 | get { return transform.localPosition; } 12 | set 13 | { 14 | if( transform.localPosition == value ) 15 | return; 16 | 17 | transform.localPosition = value; 18 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 19 | } 20 | } 21 | 22 | #pragma warning disable 0649 23 | [SerializeField, HideInInspector] 24 | private Vector3 m_position; 25 | public Vector3 position 26 | { 27 | get { return m_position; } 28 | set 29 | { 30 | if( transform.position == value ) 31 | return; 32 | 33 | transform.position = value; 34 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 35 | } 36 | } 37 | 38 | public Quaternion localRotation 39 | { 40 | get { return transform.localRotation; } 41 | set 42 | { 43 | if( transform.localRotation == value ) 44 | return; 45 | 46 | transform.localRotation = value; 47 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 48 | } 49 | } 50 | 51 | public Quaternion rotation 52 | { 53 | get { return transform.rotation; } 54 | set 55 | { 56 | if( transform.rotation == value ) 57 | return; 58 | 59 | transform.rotation = value; 60 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 61 | } 62 | } 63 | 64 | public Vector3 localEulerAngles 65 | { 66 | get { return transform.localEulerAngles; } 67 | set 68 | { 69 | if( transform.localEulerAngles == value ) 70 | return; 71 | 72 | transform.localEulerAngles = value; 73 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 74 | } 75 | } 76 | 77 | public Vector3 eulerAngles 78 | { 79 | get { return transform.eulerAngles; } 80 | set 81 | { 82 | if( transform.eulerAngles == value ) 83 | return; 84 | 85 | transform.eulerAngles = value; 86 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 87 | } 88 | } 89 | 90 | public Vector3 localScale 91 | { 92 | get { return transform.localScale; } 93 | set 94 | { 95 | if( transform.localScale == value ) 96 | return; 97 | 98 | transform.localScale = value; 99 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 100 | } 101 | } 102 | 103 | [SerializeField, HideInInspector] 104 | private Vector3 m_precedingControlPointLocalPosition = Vector3.left; 105 | public Vector3 precedingControlPointLocalPosition 106 | { 107 | get { return m_precedingControlPointLocalPosition; } 108 | set 109 | { 110 | if( m_precedingControlPointLocalPosition == value ) 111 | return; 112 | 113 | m_precedingControlPointLocalPosition = value; 114 | m_precedingControlPointPosition = transform.TransformPoint( value ); 115 | 116 | if( m_handleMode == HandleMode.Aligned ) 117 | { 118 | m_followingControlPointLocalPosition = -m_precedingControlPointLocalPosition.normalized * m_followingControlPointLocalPosition.magnitude; 119 | m_followingControlPointPosition = transform.TransformPoint( m_followingControlPointLocalPosition ); 120 | } 121 | else if( m_handleMode == HandleMode.Mirrored ) 122 | { 123 | m_followingControlPointLocalPosition = -m_precedingControlPointLocalPosition; 124 | m_followingControlPointPosition = transform.TransformPoint( m_followingControlPointLocalPosition ); 125 | } 126 | 127 | spline.dirtyFlags |= InternalDirtyFlags.ControlPointPositionChange; 128 | } 129 | } 130 | 131 | [SerializeField, HideInInspector] 132 | private Vector3 m_precedingControlPointPosition; 133 | public Vector3 precedingControlPointPosition 134 | { 135 | get { return m_precedingControlPointPosition; } 136 | set 137 | { 138 | if( m_precedingControlPointPosition == value ) 139 | return; 140 | 141 | m_precedingControlPointPosition = value; 142 | m_precedingControlPointLocalPosition = transform.InverseTransformPoint( value ); 143 | 144 | if( transform.hasChanged ) 145 | { 146 | m_position = transform.position; 147 | m_followingControlPointPosition = transform.TransformPoint( m_followingControlPointLocalPosition ); 148 | 149 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange; 150 | transform.hasChanged = false; 151 | } 152 | 153 | if( m_handleMode == HandleMode.Aligned ) 154 | { 155 | m_followingControlPointPosition = m_position - ( m_precedingControlPointPosition - m_position ).normalized * 156 | ( m_followingControlPointPosition - m_position ).magnitude; 157 | m_followingControlPointLocalPosition = transform.InverseTransformPoint( m_followingControlPointPosition ); 158 | } 159 | else if( m_handleMode == HandleMode.Mirrored ) 160 | { 161 | m_followingControlPointPosition = 2f * m_position - m_precedingControlPointPosition; 162 | m_followingControlPointLocalPosition = transform.InverseTransformPoint( m_followingControlPointPosition ); 163 | } 164 | 165 | spline.dirtyFlags |= InternalDirtyFlags.ControlPointPositionChange; 166 | } 167 | } 168 | 169 | [SerializeField, HideInInspector] 170 | private Vector3 m_followingControlPointLocalPosition = Vector3.right; 171 | public Vector3 followingControlPointLocalPosition 172 | { 173 | get { return m_followingControlPointLocalPosition; } 174 | set 175 | { 176 | if( m_followingControlPointLocalPosition == value ) 177 | return; 178 | 179 | m_followingControlPointLocalPosition = value; 180 | m_followingControlPointPosition = transform.TransformPoint( value ); 181 | 182 | if( m_handleMode == HandleMode.Aligned ) 183 | { 184 | m_precedingControlPointLocalPosition = -m_followingControlPointLocalPosition.normalized * m_precedingControlPointLocalPosition.magnitude; 185 | m_precedingControlPointPosition = transform.TransformPoint( m_precedingControlPointLocalPosition ); 186 | } 187 | else if( m_handleMode == HandleMode.Mirrored ) 188 | { 189 | m_precedingControlPointLocalPosition = -m_followingControlPointLocalPosition; 190 | m_precedingControlPointPosition = transform.TransformPoint( m_precedingControlPointLocalPosition ); 191 | } 192 | 193 | spline.dirtyFlags |= InternalDirtyFlags.ControlPointPositionChange; 194 | } 195 | } 196 | 197 | [SerializeField, HideInInspector] 198 | private Vector3 m_followingControlPointPosition; 199 | public Vector3 followingControlPointPosition 200 | { 201 | get { return m_followingControlPointPosition; } 202 | set 203 | { 204 | if( m_followingControlPointPosition == value ) 205 | return; 206 | 207 | m_followingControlPointPosition = value; 208 | m_followingControlPointLocalPosition = transform.InverseTransformPoint( value ); 209 | 210 | if( transform.hasChanged ) 211 | { 212 | m_position = transform.position; 213 | m_precedingControlPointPosition = transform.TransformPoint( m_precedingControlPointLocalPosition ); 214 | 215 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange; 216 | transform.hasChanged = false; 217 | } 218 | 219 | if( m_handleMode == HandleMode.Aligned ) 220 | { 221 | m_precedingControlPointPosition = m_position - ( m_followingControlPointPosition - m_position ).normalized * 222 | ( m_precedingControlPointPosition - m_position ).magnitude; 223 | m_precedingControlPointLocalPosition = transform.InverseTransformPoint( m_precedingControlPointPosition ); 224 | } 225 | else if( m_handleMode == HandleMode.Mirrored ) 226 | { 227 | m_precedingControlPointPosition = 2f * m_position - m_followingControlPointPosition; 228 | m_precedingControlPointLocalPosition = transform.InverseTransformPoint( m_precedingControlPointPosition ); 229 | } 230 | 231 | spline.dirtyFlags |= InternalDirtyFlags.ControlPointPositionChange; 232 | } 233 | } 234 | 235 | [SerializeField, HideInInspector] 236 | private HandleMode m_handleMode = HandleMode.Mirrored; 237 | public HandleMode handleMode 238 | { 239 | get { return m_handleMode; } 240 | set 241 | { 242 | if( m_handleMode == value ) 243 | return; 244 | 245 | m_handleMode = value; 246 | 247 | if( value == HandleMode.Aligned || value == HandleMode.Mirrored ) 248 | { 249 | // Temporarily change the value of m_precedingControlPointLocalPosition so that it will be different from precedingControlPointLocalPosition 250 | // and precedingControlPointLocalPosition's setter will run 251 | Vector3 _precedingControlPointLocalPosition = m_precedingControlPointLocalPosition; 252 | m_precedingControlPointLocalPosition -= Vector3.one; 253 | 254 | precedingControlPointLocalPosition = _precedingControlPointLocalPosition; 255 | } 256 | 257 | spline.dirtyFlags |= InternalDirtyFlags.ControlPointPositionChange; 258 | } 259 | } 260 | 261 | [SerializeField, HideInInspector] 262 | [UnityEngine.Serialization.FormerlySerializedAs( "normal" )] 263 | private Vector3 m_normal = Vector3.up; 264 | public Vector3 normal 265 | { 266 | get { return m_normal; } 267 | set 268 | { 269 | if( m_normal == value ) 270 | return; 271 | 272 | m_normal = value; 273 | spline.dirtyFlags |= InternalDirtyFlags.NormalChange; 274 | } 275 | } 276 | 277 | [SerializeField, HideInInspector] 278 | [UnityEngine.Serialization.FormerlySerializedAs( "autoCalculatedNormalAngleOffset" )] 279 | private float m_autoCalculatedNormalAngleOffset = 0f; 280 | public float autoCalculatedNormalAngleOffset 281 | { 282 | get { return m_autoCalculatedNormalAngleOffset; } 283 | set 284 | { 285 | if( m_autoCalculatedNormalAngleOffset == value ) 286 | return; 287 | 288 | m_autoCalculatedNormalAngleOffset = value; 289 | spline.dirtyFlags |= InternalDirtyFlags.NormalOffsetChange; 290 | } 291 | } 292 | 293 | [SerializeField, HideInInspector] 294 | private Vector3[] m_intermediateNormals; 295 | public Vector3[] intermediateNormals 296 | { 297 | get { return m_intermediateNormals; } 298 | set 299 | { 300 | // In this special case, don't early exit if the assigned array is the same because one of its elements might have changed. 301 | // We can safely early exit if the assigned value was null or empty, though 302 | if( ( m_intermediateNormals == null || m_intermediateNormals.Length == 0 ) && ( value == null || value.Length == 0 ) ) 303 | return; 304 | 305 | m_intermediateNormals = value; 306 | spline.dirtyFlags |= InternalDirtyFlags.NormalChange; 307 | } 308 | } 309 | 310 | [SerializeField, HideInInspector] 311 | [UnityEngine.Serialization.FormerlySerializedAs( "extraData" )] 312 | private ExtraData m_extraData; 313 | public ExtraData extraData 314 | { 315 | get { return m_extraData; } 316 | set 317 | { 318 | if( m_extraData == value ) 319 | return; 320 | 321 | m_extraData = value; 322 | spline.dirtyFlags |= InternalDirtyFlags.ExtraDataChange; 323 | } 324 | } 325 | #pragma warning restore 0649 326 | 327 | public BezierSpline spline { get; internal set; } 328 | public int index { get; internal set; } 329 | 330 | public BezierPoint previousPoint 331 | { 332 | get 333 | { 334 | if( spline ) 335 | { 336 | if( index > 0 ) 337 | return spline.endPoints[index - 1]; 338 | else if( spline.loop ) 339 | return spline.endPoints[spline.endPoints.Count - 1]; 340 | } 341 | 342 | return null; 343 | } 344 | } 345 | 346 | public BezierPoint nextPoint 347 | { 348 | get 349 | { 350 | if( spline ) 351 | { 352 | if( index < spline.endPoints.Count - 1 ) 353 | return spline.endPoints[index + 1]; 354 | else if( spline.loop ) 355 | return spline.endPoints[0]; 356 | } 357 | 358 | return null; 359 | } 360 | } 361 | 362 | private void Awake() 363 | { 364 | transform.hasChanged = true; 365 | } 366 | 367 | private void OnDestroy() 368 | { 369 | if( spline ) 370 | spline.dirtyFlags |= InternalDirtyFlags.All; 371 | } 372 | 373 | public void CopyTo( BezierPoint other ) 374 | { 375 | other.transform.localPosition = transform.localPosition; 376 | other.transform.localRotation = transform.localRotation; 377 | other.transform.localScale = transform.localScale; 378 | 379 | other.m_handleMode = m_handleMode; 380 | 381 | other.m_precedingControlPointLocalPosition = m_precedingControlPointLocalPosition; 382 | other.m_followingControlPointLocalPosition = m_followingControlPointLocalPosition; 383 | 384 | other.m_normal = m_normal; 385 | other.m_autoCalculatedNormalAngleOffset = m_autoCalculatedNormalAngleOffset; 386 | 387 | other.m_extraData = m_extraData; 388 | } 389 | 390 | public void Refresh() 391 | { 392 | m_position = transform.position; 393 | m_precedingControlPointPosition = transform.TransformPoint( m_precedingControlPointLocalPosition ); 394 | m_followingControlPointPosition = transform.TransformPoint( m_followingControlPointLocalPosition ); 395 | 396 | transform.hasChanged = false; 397 | } 398 | 399 | internal void RefreshIfChanged() 400 | { 401 | if( transform.hasChanged ) 402 | { 403 | Refresh(); 404 | spline.dirtyFlags |= InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange; 405 | } 406 | } 407 | 408 | internal void SetNormalAndResetIntermediateNormals( Vector3 normal, string undo ) 409 | { 410 | if( spline && spline.autoCalculateNormals ) 411 | return; 412 | 413 | #if UNITY_EDITOR 414 | if( !string.IsNullOrEmpty( undo ) ) 415 | UnityEditor.Undo.RecordObject( this, undo ); 416 | #endif 417 | 418 | this.normal = normal; 419 | intermediateNormals = null; 420 | 421 | BezierPoint previousPoint = this.previousPoint; 422 | if( previousPoint && previousPoint.m_intermediateNormals != null && previousPoint.m_intermediateNormals.Length > 0 ) 423 | { 424 | #if UNITY_EDITOR 425 | if( !string.IsNullOrEmpty( undo ) ) 426 | UnityEditor.Undo.RecordObject( previousPoint, undo ); 427 | #endif 428 | 429 | previousPoint.intermediateNormals = null; 430 | } 431 | } 432 | 433 | public void Reset() 434 | { 435 | localPosition = Vector3.zero; 436 | localRotation = Quaternion.identity; 437 | localScale = Vector3.one; 438 | 439 | precedingControlPointLocalPosition = Vector3.left; 440 | followingControlPointLocalPosition = Vector3.right; 441 | 442 | m_normal = Vector3.up; 443 | m_autoCalculatedNormalAngleOffset = 0f; 444 | 445 | m_extraData = new ExtraData(); 446 | 447 | transform.hasChanged = true; 448 | } 449 | 450 | #if UNITY_EDITOR 451 | [ContextMenu( "Invert Spline" )] 452 | private void InvertSplineContextMenu() 453 | { 454 | if( spline ) 455 | spline.InvertSpline( "Invert spline" ); 456 | } 457 | #endif 458 | } 459 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Utilities/BendMeshAlongBezier.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | #if UNITY_EDITOR 3 | using UnityEditor; 4 | using UnityEditor.SceneManagement; 5 | #if UNITY_2018_3_OR_NEWER && !UNITY_2021_2_OR_NEWER 6 | using PrefabStage = UnityEditor.Experimental.SceneManagement.PrefabStage; 7 | using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; 8 | #endif 9 | #endif 10 | 11 | namespace BezierSolution 12 | { 13 | [AddComponentMenu( "Bezier Solution/Bend Mesh Along Bezier" )] 14 | [HelpURL( "https://github.com/yasirkula/UnityBezierSolution" )] 15 | [RequireComponent( typeof( MeshFilter ) )] 16 | [ExecuteInEditMode] 17 | public class BendMeshAlongBezier : MonoBehaviour 18 | { 19 | public enum VectorMode { DontModify = 0, ModifyOriginals = 1, RecalculateFromScratch = 2 }; 20 | public enum Axis { X = 0, Y = 1, Z = 2 }; 21 | 22 | #pragma warning disable 0649 23 | [SerializeField] 24 | private BezierSpline m_spline; 25 | public BezierSpline spline 26 | { 27 | get { return m_spline; } 28 | set 29 | { 30 | if( m_spline != value ) 31 | { 32 | if( m_spline ) 33 | m_spline.onSplineChanged -= OnSplineChanged; 34 | 35 | m_spline = value; 36 | 37 | if( m_spline && isActiveAndEnabled ) 38 | { 39 | m_spline.onSplineChanged -= OnSplineChanged; 40 | m_spline.onSplineChanged += OnSplineChanged; 41 | 42 | OnSplineChanged( m_spline, DirtyFlags.All ); 43 | } 44 | } 45 | } 46 | } 47 | 48 | [SerializeField] 49 | [MinMaxRange( 0f, 1f )] 50 | private Vector2 m_splineSampleRange = new Vector2( 0f, 1f ); 51 | public Vector2 SplineSampleRange 52 | { 53 | get { return m_splineSampleRange; } 54 | set 55 | { 56 | value.x = Mathf.Clamp01( value.x ); 57 | value.y = Mathf.Clamp01( value.y ); 58 | 59 | if( m_splineSampleRange != value ) 60 | { 61 | m_splineSampleRange = value; 62 | OnSplineChanged( m_spline, DirtyFlags.All ); 63 | } 64 | } 65 | } 66 | 67 | [Header( "Bend Options" )] 68 | [SerializeField] 69 | private bool m_highQuality = false; 70 | public bool highQuality 71 | { 72 | get { return m_highQuality; } 73 | set 74 | { 75 | if( m_highQuality != value ) 76 | { 77 | m_highQuality = value; 78 | OnSplineChanged( m_spline, DirtyFlags.All ); 79 | } 80 | } 81 | } 82 | 83 | [SerializeField] 84 | private Axis m_bendAxis = Axis.Y; 85 | public Axis bendAxis 86 | { 87 | 88 | get { return m_bendAxis; } 89 | set 90 | { 91 | if( m_bendAxis != value ) 92 | { 93 | m_bendAxis = value; 94 | 95 | RecalculateVertexRange(); 96 | OnSplineChanged( m_spline, DirtyFlags.All ); 97 | } 98 | } 99 | } 100 | 101 | [SerializeField] 102 | [Range( 0f, 360f )] 103 | private float m_extraRotation = 0f; 104 | public float extraRotation 105 | { 106 | get { return m_extraRotation; } 107 | set 108 | { 109 | value = Mathf.Clamp( value, 0f, 360f ); 110 | 111 | if( m_extraRotation != value ) 112 | { 113 | m_extraRotation = value; 114 | OnSplineChanged( m_spline, DirtyFlags.All ); 115 | } 116 | } 117 | } 118 | 119 | [SerializeField] 120 | private bool m_invertDirection = false; 121 | public bool invertDirection 122 | { 123 | get { return m_invertDirection; } 124 | set 125 | { 126 | if( m_invertDirection != value ) 127 | { 128 | m_invertDirection = value; 129 | OnSplineChanged( m_spline, DirtyFlags.All ); 130 | } 131 | } 132 | } 133 | 134 | [SerializeField] 135 | private Vector2 m_thicknessMultiplier = Vector2.one; 136 | public Vector2 thicknessMultiplier 137 | { 138 | get { return m_thicknessMultiplier; } 139 | set 140 | { 141 | if( m_thicknessMultiplier != value ) 142 | { 143 | m_thicknessMultiplier = value; 144 | OnSplineChanged( m_spline, DirtyFlags.All ); 145 | } 146 | } 147 | } 148 | 149 | [Header( "Vertex Attributes" )] 150 | [SerializeField] 151 | private VectorMode m_normalsMode = VectorMode.ModifyOriginals; 152 | public VectorMode normalsMode 153 | { 154 | get { return m_normalsMode; } 155 | set 156 | { 157 | if( m_normalsMode != value ) 158 | { 159 | m_normalsMode = value; 160 | 161 | if( mesh ) 162 | { 163 | if( m_normalsMode == VectorMode.DontModify && originalNormals != null ) 164 | { 165 | mesh.normals = originalNormals; 166 | originalNormals = null; 167 | } 168 | 169 | if( m_normalsMode != VectorMode.ModifyOriginals ) 170 | normals = null; 171 | } 172 | 173 | OnSplineChanged( m_spline, DirtyFlags.All ); 174 | } 175 | } 176 | } 177 | 178 | [SerializeField] 179 | private VectorMode m_tangentsMode = VectorMode.ModifyOriginals; 180 | public VectorMode tangentsMode 181 | { 182 | get { return m_tangentsMode; } 183 | set 184 | { 185 | if( m_tangentsMode != value ) 186 | { 187 | m_tangentsMode = value; 188 | 189 | if( mesh ) 190 | { 191 | if( m_tangentsMode == VectorMode.DontModify && originalTangents != null ) 192 | { 193 | mesh.tangents = originalTangents; 194 | originalTangents = null; 195 | } 196 | 197 | if( m_tangentsMode != VectorMode.ModifyOriginals ) 198 | tangents = null; 199 | } 200 | 201 | OnSplineChanged( m_spline, DirtyFlags.All ); 202 | } 203 | } 204 | } 205 | 206 | [Header( "Other Settings" )] 207 | [SerializeField] 208 | private bool m_autoRefresh = true; 209 | public bool autoRefresh 210 | { 211 | get { return m_autoRefresh; } 212 | set 213 | { 214 | if( m_autoRefresh != value ) 215 | { 216 | m_autoRefresh = value; 217 | OnSplineChanged( m_spline, DirtyFlags.All ); 218 | } 219 | } 220 | } 221 | 222 | #if UNITY_EDITOR 223 | [SerializeField] 224 | private bool executeInEditMode = false; 225 | 226 | [SerializeField, HideInInspector] 227 | private BezierSpline prevSpline; 228 | [SerializeField, HideInInspector] 229 | private VectorMode prevNormalsMode, prevTangentsMode; 230 | [SerializeField, HideInInspector] 231 | private bool prevHighQuality; 232 | #endif 233 | 234 | [SerializeField, HideInInspector] 235 | private Mesh originalMesh; // If this isn't serialized, then sometimes exceptions occur on undo/redo 236 | #pragma warning restore 0649 237 | 238 | private MeshFilter meshFilter; 239 | 240 | private Mesh mesh; 241 | private Vector3[] vertices, originalVertices; 242 | private Vector3[] normals, originalNormals; 243 | private Vector4[] tangents, originalTangents; 244 | 245 | private float minVertex, _1OverVertexRange; 246 | 247 | private void OnEnable() 248 | { 249 | #if UNITY_EDITOR 250 | // Restore normals and tangents after assembly reload if they are set to DontModify because otherwise they become null automatically (i.e. information gets lost) 251 | if( mesh && originalMesh ) 252 | { 253 | if( m_normalsMode == VectorMode.DontModify ) 254 | mesh.normals = originalMesh.normals; 255 | if( m_tangentsMode == VectorMode.DontModify ) 256 | mesh.tangents = originalMesh.tangents; 257 | } 258 | 259 | EditorSceneManager.sceneSaving -= OnSceneSaving; 260 | EditorSceneManager.sceneSaving += OnSceneSaving; 261 | EditorSceneManager.sceneSaved -= OnSceneSaved; 262 | EditorSceneManager.sceneSaved += OnSceneSaved; 263 | #endif 264 | 265 | if( m_spline ) 266 | { 267 | m_spline.onSplineChanged -= OnSplineChanged; 268 | m_spline.onSplineChanged += OnSplineChanged; 269 | 270 | OnSplineChanged( m_spline, DirtyFlags.All ); 271 | } 272 | } 273 | 274 | private void OnDisable() 275 | { 276 | if( m_spline ) 277 | m_spline.onSplineChanged -= OnSplineChanged; 278 | 279 | #if UNITY_EDITOR 280 | EditorSceneManager.sceneSaving -= OnSceneSaving; 281 | EditorSceneManager.sceneSaved -= OnSceneSaved; 282 | 283 | if( !EditorApplication.isPlaying ) 284 | OnDestroy(); 285 | #endif 286 | } 287 | 288 | private void OnDestroy() 289 | { 290 | MeshFilter _meshFilter = meshFilter; 291 | meshFilter = null; 292 | 293 | if( _meshFilter && originalMesh ) 294 | _meshFilter.sharedMesh = originalMesh; 295 | 296 | if( mesh ) 297 | DestroyImmediate( mesh ); 298 | 299 | #if UNITY_EDITOR && UNITY_2018_3_OR_NEWER 300 | // This allows removing the 'modified' flag of Mesh Filter's Mesh property but these sorts of things 301 | // may cause new problems in edge cases so it is commented out 302 | //if( !EditorApplication.isPlaying && _meshFilter && originalMesh ) 303 | //{ 304 | // // Revert modified status of the prefab instance's MeshFilter Mesh if possible 305 | // MeshFilter prefabMeshFilter = null; 306 | // if( PrefabUtility.GetPrefabInstanceStatus( _meshFilter ) == PrefabInstanceStatus.Connected ) 307 | // prefabMeshFilter = PrefabUtility.GetCorrespondingObjectFromSource( _meshFilter ) as MeshFilter; 308 | 309 | // if( prefabMeshFilter && prefabMeshFilter.sharedMesh == originalMesh ) 310 | // PrefabUtility.RevertPropertyOverride( new SerializedObject( _meshFilter ).FindProperty( "m_Mesh" ), InteractionMode.AutomatedAction ); 311 | //} 312 | #endif 313 | } 314 | 315 | public void Activate() 316 | { 317 | enabled = true; 318 | } 319 | 320 | public void Deactivate() 321 | { 322 | OnDestroy(); 323 | enabled = false; 324 | } 325 | 326 | #if UNITY_EDITOR 327 | private void OnValidate() 328 | { 329 | EditorApplication.update -= OnValidateImplementation; 330 | EditorApplication.update += OnValidateImplementation; 331 | } 332 | 333 | // Calling this code inside OnValidate throws "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate" warnings 334 | private void OnValidateImplementation() 335 | { 336 | EditorApplication.update -= OnValidateImplementation; 337 | 338 | if( !this ) 339 | return; 340 | 341 | BezierSpline _spline = m_spline; 342 | m_spline = prevSpline; 343 | spline = prevSpline = _spline; 344 | 345 | bool _highQuality = m_highQuality; 346 | m_highQuality = prevHighQuality; 347 | highQuality = prevHighQuality = _highQuality; 348 | 349 | VectorMode _normalsMode = m_normalsMode; 350 | m_normalsMode = prevNormalsMode; 351 | normalsMode = prevNormalsMode = _normalsMode; 352 | 353 | VectorMode _tangentsMode = m_tangentsMode; 354 | m_tangentsMode = prevTangentsMode; 355 | tangentsMode = prevTangentsMode = _tangentsMode; 356 | 357 | RecalculateVertexRange(); 358 | 359 | if( !executeInEditMode && !EditorApplication.isPlaying ) 360 | OnDestroy(); 361 | else if( isActiveAndEnabled ) 362 | OnSplineChanged( m_spline, DirtyFlags.All ); 363 | 364 | SceneView.RepaintAll(); 365 | } 366 | 367 | private void OnSceneSaving( UnityEngine.SceneManagement.Scene scene, string path ) 368 | { 369 | // Restore original mesh before saving the scene 370 | if( scene == gameObject.scene ) 371 | OnDestroy(); 372 | } 373 | 374 | private void OnSceneSaved( UnityEngine.SceneManagement.Scene scene ) 375 | { 376 | // Restore modified mesh after saving the scene 377 | if( scene == gameObject.scene && isActiveAndEnabled ) 378 | OnSplineChanged( m_spline, DirtyFlags.All ); 379 | } 380 | #endif 381 | 382 | private void OnSplineChanged( BezierSpline spline, DirtyFlags dirtyFlags ) 383 | { 384 | #if UNITY_EDITOR 385 | if( !executeInEditMode && !EditorApplication.isPlaying ) 386 | return; 387 | 388 | if( BuildPipeline.isBuildingPlayer ) 389 | return; 390 | 391 | #if UNITY_2018_3_OR_NEWER 392 | // Don't execute the script in prefab mode 393 | PrefabStage openPrefabStage = PrefabStageUtility.GetCurrentPrefabStage(); 394 | if( openPrefabStage != null && openPrefabStage.IsPartOfPrefabContents( gameObject ) ) 395 | return; 396 | #endif 397 | #endif 398 | 399 | if( m_autoRefresh && ( dirtyFlags & ( DirtyFlags.SplineShapeChanged | DirtyFlags.NormalsChanged ) ) != DirtyFlags.None ) 400 | Refresh(); 401 | } 402 | 403 | private void Initialize() 404 | { 405 | meshFilter = GetComponent(); 406 | if( meshFilter.sharedMesh ) // It can sometimes be null during undo&redo which causes issues 407 | originalMesh = meshFilter.sharedMesh; 408 | 409 | if( !originalMesh ) 410 | return; 411 | 412 | if( mesh ) 413 | DestroyImmediate( mesh ); 414 | 415 | mesh = Instantiate( originalMesh ); 416 | meshFilter.sharedMesh = mesh; 417 | 418 | #if UNITY_EDITOR 419 | if( !EditorApplication.isPlaying ) 420 | mesh.hideFlags = HideFlags.DontSave; 421 | #endif 422 | 423 | originalVertices = mesh.vertices; 424 | originalNormals = null; 425 | originalTangents = null; 426 | 427 | RecalculateVertexRange(); 428 | } 429 | 430 | private void RecalculateVertexRange() 431 | { 432 | if( originalVertices == null ) 433 | return; 434 | 435 | minVertex = float.PositiveInfinity; 436 | float maxVertex = float.NegativeInfinity; 437 | 438 | switch( m_bendAxis ) 439 | { 440 | case Axis.X: 441 | for( int i = 0; i < originalVertices.Length; i++ ) 442 | { 443 | float vertex = originalVertices[i].x; 444 | if( vertex < minVertex ) 445 | minVertex = originalVertices[i].x; 446 | if( vertex > maxVertex ) 447 | maxVertex = originalVertices[i].x; 448 | } 449 | break; 450 | case Axis.Y: 451 | for( int i = 0; i < originalVertices.Length; i++ ) 452 | { 453 | float vertex = originalVertices[i].y; 454 | if( vertex < minVertex ) 455 | minVertex = originalVertices[i].y; 456 | if( vertex > maxVertex ) 457 | maxVertex = originalVertices[i].y; 458 | } 459 | break; 460 | case Axis.Z: 461 | for( int i = 0; i < originalVertices.Length; i++ ) 462 | { 463 | float vertex = originalVertices[i].z; 464 | if( vertex < minVertex ) 465 | minVertex = originalVertices[i].z; 466 | if( vertex > maxVertex ) 467 | maxVertex = originalVertices[i].z; 468 | } 469 | break; 470 | } 471 | 472 | _1OverVertexRange = 1f / ( maxVertex - minVertex ); 473 | } 474 | 475 | public void Refresh() 476 | { 477 | if( !m_spline ) 478 | return; 479 | 480 | if( !meshFilter || ( meshFilter.sharedMesh && meshFilter.sharedMesh != mesh && meshFilter.sharedMesh != originalMesh ) ) 481 | Initialize(); 482 | 483 | if( !originalMesh ) 484 | return; 485 | 486 | if( vertices == null || vertices.Length != originalVertices.Length ) 487 | vertices = new Vector3[originalVertices.Length]; 488 | 489 | if( m_normalsMode == VectorMode.ModifyOriginals ) 490 | { 491 | if( originalNormals == null ) 492 | originalNormals = originalMesh.normals; 493 | 494 | if( originalNormals == null || originalNormals.Length < originalVertices.Length ) // If somehow above statement returned null 495 | normals = null; 496 | else if( normals == null || normals.Length != originalNormals.Length ) 497 | normals = new Vector3[originalNormals.Length]; 498 | } 499 | else 500 | normals = null; 501 | 502 | if( m_tangentsMode == VectorMode.ModifyOriginals ) 503 | { 504 | if( originalTangents == null ) 505 | originalTangents = originalMesh.tangents; 506 | 507 | if( originalTangents == null || originalTangents.Length < originalVertices.Length ) // If somehow above statement returned null 508 | tangents = null; 509 | else if( tangents == null || tangents.Length != originalTangents.Length ) 510 | tangents = new Vector4[originalTangents.Length]; 511 | } 512 | else 513 | tangents = null; 514 | 515 | Vector2 _splineSampleRange = m_splineSampleRange; 516 | if( m_invertDirection ) 517 | { 518 | float temp = _splineSampleRange.x; 519 | _splineSampleRange.x = _splineSampleRange.y; 520 | _splineSampleRange.y = temp; 521 | } 522 | 523 | bool isSampleRangeForwards = _splineSampleRange.x <= _splineSampleRange.y; 524 | float splineSampleLength = _splineSampleRange.y - _splineSampleRange.x; 525 | bool dontInvertModifiedVertexAttributes = ( m_thicknessMultiplier.x > 0f && m_thicknessMultiplier.y > 0f ); 526 | 527 | BezierSpline.EvenlySpacedPointsHolder evenlySpacedPoints = m_highQuality ? m_spline.evenlySpacedPoints : null; 528 | 529 | Vector3 initialPoint = m_spline.GetPoint( 0f ); 530 | for( int i = 0; i < originalVertices.Length; i++ ) 531 | { 532 | Vector3 vertex = originalVertices[i]; 533 | 534 | float vertexPosition; 535 | Vector3 vertexOffset; 536 | switch( m_bendAxis ) 537 | { 538 | case Axis.X: 539 | vertexPosition = vertex.x; 540 | vertexOffset = new Vector3( vertex.z * m_thicknessMultiplier.x, 0f, vertex.y * m_thicknessMultiplier.y ); 541 | break; 542 | case Axis.Y: 543 | default: 544 | vertexPosition = vertex.y; 545 | vertexOffset = new Vector3( vertex.x * m_thicknessMultiplier.x, 0f, vertex.z * m_thicknessMultiplier.y ); 546 | break; 547 | case Axis.Z: 548 | vertexPosition = vertex.z; 549 | vertexOffset = new Vector3( vertex.y * m_thicknessMultiplier.x, 0f, vertex.x * m_thicknessMultiplier.y ); 550 | break; 551 | } 552 | 553 | float normalizedT = _splineSampleRange.x + ( vertexPosition - minVertex ) * _1OverVertexRange * splineSampleLength; // Remap from [minVertex,maxVertex] to _splineSampleRange 554 | if( m_highQuality ) 555 | normalizedT = evenlySpacedPoints.GetNormalizedTAtPercentage( normalizedT ); 556 | 557 | BezierSpline.Segment segment = m_spline.GetSegmentAt( normalizedT ); 558 | 559 | Vector3 point = segment.GetPoint() - initialPoint; 560 | Vector3 tangent = isSampleRangeForwards ? segment.GetTangent() : -segment.GetTangent(); 561 | Quaternion rotation = Quaternion.AngleAxis( m_extraRotation, tangent ) * Quaternion.LookRotation( segment.GetNormal(), tangent ); 562 | 563 | Vector3 direction = rotation * vertexOffset; 564 | vertices[i] = point + direction; 565 | 566 | if( normals != null ) // The only case this happens is when Normals Mode is ModifyOriginals and the original mesh has normals 567 | normals[i] = rotation * ( dontInvertModifiedVertexAttributes ? originalNormals[i] : -originalNormals[i] ); 568 | if( tangents != null ) // The only case this happens is when Tangents Mode is ModifyOriginals and the original mesh has tangents 569 | { 570 | float tangentW = originalTangents[i].w; 571 | tangents[i] = rotation * ( dontInvertModifiedVertexAttributes ? originalTangents[i] : -originalTangents[i] ); 572 | tangents[i].w = tangentW; 573 | } 574 | } 575 | 576 | mesh.vertices = vertices; 577 | if( m_normalsMode == VectorMode.ModifyOriginals ) 578 | mesh.normals = normals; 579 | if( m_tangentsMode == VectorMode.ModifyOriginals ) 580 | mesh.tangents = tangents; 581 | 582 | if( m_normalsMode == VectorMode.RecalculateFromScratch ) 583 | { 584 | mesh.RecalculateNormals(); 585 | 586 | #if UNITY_EDITOR 587 | // Cache original normals so that we can reset normals in OnValidate when normals are reset back to DontModify 588 | if( originalNormals == null ) 589 | originalNormals = originalMesh.normals; 590 | #endif 591 | } 592 | 593 | if( m_tangentsMode == VectorMode.RecalculateFromScratch ) 594 | { 595 | mesh.RecalculateTangents(); 596 | 597 | #if UNITY_EDITOR 598 | // Cache original tangents so that we can reset tangents in OnValidate when tangents are reset back to DontModify 599 | if( originalTangents == null ) 600 | originalTangents = originalMesh.tangents; 601 | #endif 602 | } 603 | 604 | mesh.RecalculateBounds(); 605 | } 606 | } 607 | } -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Unity Bezier Solution 2 | 3 | ![intro](Images/Promo.png) 4 | 5 | **Available on Asset Store:** https://assetstore.unity.com/packages/tools/level-design/bezier-solution-113074 6 | 7 | **Forum Thread:** https://forum.unity.com/threads/bezier-solution-open-source.440742/ 8 | 9 | **Discord:** https://discord.gg/UJJt549AaV 10 | 11 | **Video:** https://www.youtube.com/watch?v=OpniwcFwSY8 12 | 13 | **[GitHub Sponsors ☕](https://github.com/sponsors/yasirkula)** 14 | 15 | ## ABOUT 16 | 17 | This asset is a means to create bezier splines in editor and/or during runtime: splines can be created and edited visually in the editor, or by code during runtime. It is built upon *Catlike Coding*'s spline tutorial: https://catlikecoding.com/unity/tutorials/curves-and-splines/ 18 | 19 | ## INSTALLATION 20 | 21 | There are 5 ways to install this plugin: 22 | 23 | - import [BezierSolution.unitypackage](https://github.com/yasirkula/UnityBezierSolution/releases) via *Assets-Import Package* 24 | - clone/[download](https://github.com/yasirkula/UnityBezierSolution/archive/master.zip) this repository and move the *Plugins* folder to your Unity project's *Assets* folder 25 | - import it from [Asset Store](https://assetstore.unity.com/packages/tools/level-design/bezier-solution-113074) 26 | - *(via Package Manager)* click the + button and install the package from the following git URL: 27 | - `https://github.com/yasirkula/UnityBezierSolution.git` 28 | - *(via [OpenUPM](https://openupm.com))* after installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: 29 | - `openupm add com.yasirkula.beziersolution` 30 | 31 | ## CREATING & EDITING A NEW SPLINE IN EDITOR 32 | 33 | To create a new spline in the editor, click **GameObject - Bezier Spline**. 34 | 35 | Now you can select the end points of the spline in the Scene view and translate/rotate/scale or delete/duplicate them as you wish (each end point has 2 control points, which can also be translated): 36 | 37 | ![translate](Images/EndPointHandles.png) 38 | 39 | The user interface for the spline editor should be pretty self-explanatory with most variables having explanatory tooltips. 40 | 41 | ![inspector](Images/BezierSpline.png) 42 | 43 | When **Quick Edit Mode** is enabled, new points can quickly be added/inserted to the spline and the existing points can be dragged around/snapped to the scene geometry. 44 | 45 | To reverse the order of the end points in a spline, you can right click the BezierSpline component and click the *Invert Spline* button. 46 | 47 | You can tweak the Scene view gizmos via *Project Settings/yasirkula/Bezier Solution* page (on older versions, this menu is located at *Preferences* window). 48 | 49 | ![gizmo-settings](Images/GizmoSettings.png) 50 | 51 | ## CREATING & EDITING A NEW SPLINE BY CODE 52 | 53 | - **Create a new bezier spline** 54 | 55 | Simply create a new GameObject, attach a BezierSpline component to it (BezierSpline uses `BezierSolution` namespace) and initialize the spline with a minimum of two end points: 56 | 57 | ```csharp 58 | BezierSpline spline = new GameObject().AddComponent(); 59 | spline.Initialize( 2 ); 60 | ``` 61 | 62 | - **Populate the spline** 63 | 64 | `BezierPoint InsertNewPointAt( int index )`: adds a new end point to the spline and returns it 65 | 66 | `BezierPoint DuplicatePointAt( int index )`: duplicates an existing end point and returns it 67 | 68 | `void RemovePointAt( int index )`: removes an end point from the spline 69 | 70 | `void SwapPointsAt( int index1, int index2 )`: swaps indices of two end points 71 | 72 | `void ChangePointIndex( int previousIndex, int newIndex )`: changes an end point's index 73 | 74 | - **Shape the spline** 75 | 76 | You can change the position, rotation, scale and normal values of the end points, as well as the positions of their control points to reshape the spline. 77 | 78 | End points have the following properties to store their transformational data: `position`, `localPosition`, `rotation`, `localRotation`, `eulerAngles`, `localEulerAngles`, `localScale`, `normal`, `autoCalculatedNormalAngleOffset` and `intermediateNormals`. 79 | 80 | Positions of control points can be tweaked using the following properties in BezierPoint: `precedingControlPointPosition`, `precedingControlPointLocalPosition`, `followingControlPointPosition` and `followingControlPointLocalPosition`. The local positions are relative to their corresponding end points. 81 | 82 | End points also have read-only `spline`, `index`, `previousPoint` and `nextPoint` properties. 83 | 84 | ```csharp 85 | // Set first end point's (world) position to 2,3,5 86 | spline[0].position = new Vector3( 2, 3, 5 ); 87 | 88 | // Set second end point's local position to 7,11,13 89 | spline[1].localPosition = new Vector3( 7, 11, 13 ); 90 | 91 | // Set handle mode of first end point to Free to independently adjust each control point 92 | spline[0].handleMode = BezierPoint.HandleMode.Free; 93 | 94 | // Reposition the control points of the first end point 95 | spline[0].precedingControlPointLocalPosition = new Vector3( 0, 0, 1 ); 96 | spline[0].followingControlPointPosition = spline[1].position; 97 | ``` 98 | 99 | - **Auto construct the spline** 100 | 101 | If you don't want to position all the control points manually, but rather generate a nice-looking "continuous" spline that goes through the end points you have created, you can call either **AutoConstructSpline()** or **AutoConstructSpline2()**. These methods are implementations of some algorithms found on the internet (and credited in the source code). If you want these functions to be called automatically when spline's end points are modified, simply change the spline's **autoConstructMode** property. 102 | 103 | ![auto-construct](Images/AutoConstructSpline.png) 104 | 105 | - **Convert spline to a linear path** 106 | 107 | If you want to create a linear path between the end points of the spline, you can call the **ConstructLinearPath()** function. Or, if you want this function to be called automatically when spline's end points are modified, simply set the spline's **autoConstructMode** property to **SplineAutoConstructMode.Linear**. 108 | 109 | ![auto-construct](Images/ConstructLinearPath.png) 110 | 111 | - **Auto calculate the normals** 112 | 113 | If you want to calculate the spline's normal vectors automatically, you can call the **AutoCalculateNormals( float normalAngle = 0f, int smoothness = 10, bool calculateIntermediateNormals = false )** function (or, to call this function automatically when spline's end points are modified, simply change the spline's **autoCalculateNormals**, **autoCalculatedNormalsAngle** and **m_autoCalculatedIntermediateNormalsCount** properties). All resulting normal vectors will be rotated around their Z axis by "normalAngle" degrees. Additionally, each end point's normal vector will be rotated by that end point's "autoCalculatedNormalAngleOffset" degrees. "smoothness" determines how many intermediate steps are taken between each consecutive end point to calculate those end points' normal vectors. More intermediate steps is better but also slower to calculate. When "calculateIntermediateNormals" is enabled, calculated intermediate normals (determined by "smoothness") are cached at each end point. This results in smoother linear interpolation for normals. Otherwise, the intermediate normals aren't stored anywhere and only the end points' normals are used to estimate normals along the spline. 114 | 115 | If auto calculated normals don't look quite right despite modifying the "calculateIntermediateNormals" (*Auto Calculated Intermediate Normals* in the Inspector), "normalAngle" (*Auto Calculated Normals Angle* in the Inspector) and "autoCalculatedNormalAngleOffset" (*Normal Angle* in the Inspector) variables, you can either consider inserting new end points to the sections of the spline that normals don't behave correctly, or setting the normals manually. 116 | 117 | - **Get notified when spline is modified** 118 | 119 | You can register to the spline's **onSplineChanged** event to get notified when some of its properties have changed. This event has the following signature: `delegate void SplineChangeDelegate( BezierSpline spline, DirtyFlags dirtyFlags )`. **DirtyFlags** is an enum flag, meaning that it can have one or more of these values: **SplineShapeChanged**, **NormalsChanged** and/or **ExtraDataChanged**. *SplineShapeChanged* flag means that either the spline's Transform values have changed or some of its end points' Transform values have changed (changing control points may also trigger this flag). *NormalsChanged* flag means that normals of some of the end points have changed and *ExtraDataChanged* flag means that extraDatas of some of the end points have changed. 120 | 121 | BezierSpline also has a **version** property which is automatically increased whenever the spline's properties change. 122 | 123 | **NOTE:** onSplineChanged event is usually invoked in *LateUpdate*. Before it is invoked, *autoConstructMode* and *autoCalculateNormals* properties' values are checked and the relevant auto construction/calculation functions are executed if necessary. 124 | 125 | ## UTILITY FUNCTIONS 126 | 127 | The framework comes with some utility functions. These functions are not necessarily perfect but most of the time, they get the job done. Though, if you want, you can use this framework to just create splines and then apply your own logic to them. 128 | 129 | - `Vector3 GetPoint( float normalizedT )` 130 | 131 | A spline is essentially a mathematical formula with a \[0,1\] clamped input (usually called *t*), which generates a point on the spline. As the name suggests, this function returns a point on the spline. As *t* goes from 0 to 1, the point moves from the first end point to the last end point (or goes back to first end point, if spline is looping). 132 | 133 | - `Vector3 GetTangent( float normalizedT )` 134 | 135 | Tangent is calculated using the first derivative of the spline formula and gives the direction of the movement at a given point on the spline. Can be used to determine which direction an object on the spline should look at at a given point. 136 | 137 | - `Vector3 GetNormal( float normalizedT )` 138 | 139 | Interpolates between the end points' normal vectors. If intermediate normals are calculated, they are interpolated to calculate the result. Otherwise, only the end points' normal vectors are interpolated and the resulting normal vectors may not be correct at some parts of the spline. Inserting new end point(s) to those sections of the spline could resolve this issue. By default, all normal vectors have value (0,1,0). 140 | 141 | - `BezierPoint.ExtraData GetExtraData( float normalizedT )` 142 | 143 | Interpolates between the extra data provided at each end point. This data has 4 float components and can implicitly be converted to Vector2, Vector3, Vector4, Quaternion, Rect, Vector2Int, Vector3Int and RectInt. 144 | 145 | - `BezierPoint.ExtraData GetExtraData( float normalizedT, ExtraDataLerpFunction lerpFunction )` 146 | 147 | Uses a custom function to interpolate between the end points' extra data. For example, BezierWalker components use this function to interpolate the extra data with Quaternion.Lerp. 148 | 149 | - `float GetLengthApproximately( float startNormalizedT, float endNormalizedT, float accuracy = 50f )` 150 | 151 | Calculates the approximate length of a segment of the spline. To calculate the length, the spline is divided into "accuracy" points and the Euclidean distances between these points are summed up. 152 | 153 | **Food For Thought**: BezierSpline has a **length** property which is a shorthand for `GetLengthApproximately( 0f, 1f )`. Its value is cached and won't be recalculated unless the spline is modified. 154 | 155 | - `Segment GetSegmentAt( float normalizedT )` 156 | 157 | Returns the two end points that are closest to *normalizedT*. The *Segment* struct also holds a *localT* value in range \[0,1\], which can be used to interpolate between the properties of these two end points. You can also call the `GetPoint()`, `GetTangent()`, `GetNormal()` and `GetExtraData()` functions of this struct and the returned values will be calculated as if the spline consisted of only these two end points. 158 | 159 | - `Vector3 FindNearestPointTo( Vector3 worldPos, out float normalizedT, float accuracy = 100f, int secondPassIterations = 7, float secondPassExtents = 0.025f )` 160 | 161 | Finds the nearest point on the spline to any given point in 3D space. The normalizedT parameter is optional and it returns the parameter *t* corresponding to the resulting point. To find the nearest point, the spline is divided into "accuracy" points and the nearest point is selected. Then, a binary search is performed in "secondPassIterations" steps in range `[normalizedT-secondPassExtents, normalizedT+secondPassExtents]` to fine-tune the result. 162 | 163 | - `Vector3 FindNearestPointToLine( Vector3 lineStart, Vector3 lineEnd, out Vector3 pointOnLine, out float normalizedT, float accuracy = 100f, int secondPassIterations = 7, float secondPassExtents = 0.025f )` 164 | 165 | Finds the nearest point on the spline to the given line in 3D space. The pointOnLine and normalizedT parameters are optional. 166 | 167 | - `Vector3 MoveAlongSpline( ref float normalizedT, float deltaMovement, int accuracy = 3 )` 168 | 169 | Moves a point (normalizedT) on the spline deltaMovement units ahead and returns the resulting point. The normalizedT parameter is passed by reference to keep track of the new *t* parameter. 170 | 171 | - `EvenlySpacedPointsHolder CalculateEvenlySpacedPoints( float resolution = 10f, float accuracy = 3f )` 172 | 173 | Finds uniformly distributed points along the spline and returns a lookup table. The lookup table isn't refreshed automatically, so it may be invalidated when the spline is modified. This function's *resolution* parameter determines approximately how many points will be calculated per each segment of the spline and accuracy determines how accurate the uniform spacing will be. The default values should work well in most cases. 174 | 175 | **Food For Thought**: BezierSpline has an **evenlySpacedPoints** property which is a shorthand for `CalculateEvenlySpacedPoints( evenlySpacedPointsResolution, evenlySpacedPointsAccuracy )`. Its value is cached and won't be recalculated unless the spline is modified. 176 | 177 | *EvenlySpacedPointsHolder* class has *spline*, *splineLength* and *uniformNormalizedTs* variables. In addition, it has the following convenience functions: 178 | 179 | **GetNormalizedTAtPercentage:** converts a percentage to normalizedT value, i.e. if you enter 0.5f as parameter, it will return the normalizedT value of the spline that corresponds to its actual middle point. 180 | 181 | **GetNormalizedTAtDistance:** finds the normalizedT value that is specified units away from the spline's starting point. 182 | 183 | **GetPercentageAtNormalizedT:** inverse of *GetNormalizedTAtPercentage*. 184 | 185 | - `PointCache GeneratePointCache( EvenlySpacedPointsHolder lookupTable, ExtraDataLerpFunction extraDataLerpFunction, PointCacheFlags cachedData = PointCacheFlags.All, int resolution = 100 )` 186 | 187 | Returns a cache of data for uniformly distributed points along the spline. The cache isn't refreshed automatically, so it may be invalidated when the spline is modified. This function's *resolution* parameter determines how many uniformly distributed points the cache will have. To determine which data should be cached, *cachedData* parameter is used. *PointCacheFlags* is an enum flag, meaning that it can have one or more of these values: **Positions**, **Normals**, **Tangents**, **Bitangents** and/or **ExtraDatas**. *lookupTable* is an optional parameter and, by default, spline's *evenlySpacedPoints* is used. *extraDataLerpFunction* is also an optional parameter and is used only when PointCacheFlags.ExtraDatas is included in cachedData. 188 | 189 | **Food For Thought**: BezierSpline has a **pointCache** property which is a shorthand for `GeneratePointCache( resolution: pointCacheResolution )`. Its value is cached and won't be recalculated unless the spline is modified. 190 | 191 | *PointCache* class has *positions*, *normals*, *tangents*, *bitangents*, *extraDatas* and *loop* variables (loop determines whether or not the spline had its *loop* property set to true while calculating the cache). In addition, it has the following functions: *GetPoint*, *GetNormal*, *GetTangent*, *GetBitangent* and *GetExtraData* (if the required data for a function wasn't included in PointCacheFlags, then the function will throw an exception). If a spline is rarely modified at runtime, then point cache can be used to get points, tangents, normals, etc. along the spline in a cheaper and uniform way. 192 | 193 | ## OTHER COMPONENTS 194 | 195 | The plugin comes with some additional components that may help you move objects or particles along splines. These components are located in the Utilities folder. 196 | 197 | - **BezierWalkerWithSpeed** 198 | 199 | ![walker-with-speed](Images/BezierWalkerWithSpeed.png) 200 | 201 | Moves an object along a spline with constant speed. There are 3 travel modes: Once, Ping Pong and Loop. If *Look At* is Forward, the object will always face forwards (end points' normal vectors will be used as up vectors). If it is SplineExtraData, the extra data stored in the spline's end points is used to determine the rotation. You can modify this extra data from the points' Inspector. The smoothness of the rotation can be adjusted via *Rotation Lerp Modifier*. *Normalized T* determines the starting point. Each time the object completes a lap, its *On Path Completed ()* event is invoked. To see this component in action without entering Play mode, click the *Simulate In Editor* button. 202 | 203 | - **BezierWalkerWithTime** 204 | 205 | ![walker-with-time](Images/BezierWalkerWithTime.png) 206 | 207 | Travels a spline in *Travel Time* seconds. *Movement Lerp Modifier* parameter defines the smoothness applied to the position of the object. If *High Quality* is enabled, the spline will be traversed with constant speed but the calculations can be more expensive. 208 | 209 | - **BezierWalkerLocomotion** 210 | 211 | ![walker-locomotion](Images/BezierWalkerLocomotion.png) 212 | 213 | Allows you to move a number of objects together with this object on a spline. This component must be attached to an object with a BezierWalker component (tail objects don't need a BezierWalker, though). *Look At*, *Movement Lerp Modifier* and *Rotation Lerp Modifier* parameters affect the tail objects. If tail objects jitter too much, enabling *High Quality* may help greatly but the calculations can be more expensive. 214 | 215 | - **ParticlesFollowBezier** 216 | 217 | ![particles-follow-bezier](Images/ParticlesFollowBezier.png) 218 | 219 | Moves particles of a Particle System in the direction of a spline. It is recommended to set the **Simulation Space** of the Particle System to **Local** for increased performance. This component affects particles in one of two ways: 220 | 221 | **Strict:** particles will strictly follow the spline. They will always be aligned to the spline and will reach the end of the spline at the end of their lifetime. This mode performs slightly better than Relaxed mode 222 | 223 | **Relaxed:** properties of the particle system like speed, Noise and Shape will affect the movement of the particles. Particles in this mode will usually look more interesting. If you want the particles to stick with the spline, though, set their speed to 0 224 | 225 | Note that if the **Resimulate** tick of the Particle System is selected, particles may move in a chaotic way for a short time while changing the properties of the particle system from the Inspector. 226 | 227 | - **BezierAttachment** 228 | 229 | ![bezier-attachment](Images/BezierAttachment.png) 230 | 231 | Snaps an object to the specified point of the spline. You can snap the object's position and/or rotation values, optionally with some offsets. Rotation can be snapped in one of two ways: 232 | 233 | **Use Spline Normals:** spline's normal vectors will be used to determine the object's rotation 234 | 235 | **Use End Point Rotations:** the Transform rotation values of the spline's end points will be used to determine the object's rotation 236 | 237 | - **BezierLineRenderer** 238 | 239 | ![bezier-line-renderer](Images/BezierLineRenderer.png) 240 | 241 | Automatically positions a Line Renderer's points so that its shape matches the target spline's shape. It is possible to match the shape of only a portion of the spline by tweaking the *Spline Sample Range* property. If Line Renderer's **Use World Space** property is enabled, then its points will be placed at the spline's current position. Otherwise, the points will be placed relative to the Line Renderer's position and they will rotate/scale with the Line Renderer. 242 | 243 | - **BendMeshAlongBezier** 244 | 245 | ![bend-mesh-along-bezier](Images/BendMeshAlongBezier.png) 246 | 247 | Modifies a MeshFilter's mesh to bend it in the direction of a spline (make sure that the spline's normals are perpendicular to the spline; *Auto Calculate Normals* may help). If *High Quality* is enabled, evenly spaced bezier points will be used so that the mesh bends uniformly but the calculations will be more expensive. If *Auto Refresh* is enabled, the mesh will be refreshed automatically when the spline is modified (at runtime, this has the same effect with disabling the component but in edit mode, disabling the component will restore the original mesh instead). Mesh's normal and tangent vectors can optionally be recalculated in one of two ways: 248 | 249 | **Modify Originals:** the original mesh's normal and tangent vectors will be rotated with the spline 250 | 251 | **Recalculate From Scratch:** Unity's `RecalculateNormals` and/or `RecalculateTangents` functions will be invoked to recalculate these vectors from scratch 252 | 253 | Note that this component doesn't add new vertices to the original mesh, so if the original mesh doesn't have enough vertices in its bend axis, then the bent mesh will have jagged edges on complex splines. 254 | -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace BezierSolution.Extras 6 | { 7 | public static class BezierSettings 8 | { 9 | #region Colors 10 | private static Color? m_splineColor = null; 11 | public static Color NormalSplineColor 12 | { 13 | get 14 | { 15 | if( m_splineColor == null ) 16 | m_splineColor = GetColor( "BezierSolution_SplineColor", new Color( 0.8f, 0.6f, 0.8f, 1f ) ); 17 | 18 | return m_splineColor.Value; 19 | } 20 | set 21 | { 22 | m_splineColor = value; 23 | SetColor( "BezierSolution_SplineColor", value ); 24 | } 25 | } 26 | 27 | private static Color? m_selectedSplineColor = null; 28 | public static Color SelectedSplineColor 29 | { 30 | get 31 | { 32 | if( m_selectedSplineColor == null ) 33 | m_selectedSplineColor = GetColor( "BezierSolution_SelectedSplineColor", new Color( 0.8f, 0.6f, 0.8f, 1f ) ); 34 | 35 | return m_selectedSplineColor.Value; 36 | } 37 | set 38 | { 39 | m_selectedSplineColor = value; 40 | SetColor( "BezierSolution_SelectedSplineColor", value ); 41 | } 42 | } 43 | 44 | private static Color? m_endPointColor = null; 45 | public static Color NormalEndPointColor 46 | { 47 | get 48 | { 49 | if( m_endPointColor == null ) 50 | m_endPointColor = GetColor( "BezierSolution_EndPointColor", Color.white ); 51 | 52 | return m_endPointColor.Value; 53 | } 54 | set 55 | { 56 | m_endPointColor = value; 57 | SetColor( "BezierSolution_EndPointColor", value ); 58 | } 59 | } 60 | 61 | private static Color? m_selectedEndPointColor = null; 62 | public static Color SelectedEndPointColor 63 | { 64 | get 65 | { 66 | if( m_selectedEndPointColor == null ) 67 | m_selectedEndPointColor = GetColor( "BezierSolution_SelectedEndPointColor", Color.yellow ); 68 | 69 | return m_selectedEndPointColor.Value; 70 | } 71 | set 72 | { 73 | m_selectedEndPointColor = value; 74 | SetColor( "BezierSolution_SelectedEndPointColor", value ); 75 | } 76 | } 77 | 78 | private static Color? m_controlPointColor = null; 79 | public static Color NormalControlPointColor 80 | { 81 | get 82 | { 83 | if( m_controlPointColor == null ) 84 | m_controlPointColor = GetColor( "BezierSolution_ControlPointColor", Color.white ); 85 | 86 | return m_controlPointColor.Value; 87 | } 88 | set 89 | { 90 | m_controlPointColor = value; 91 | SetColor( "BezierSolution_ControlPointColor", value ); 92 | } 93 | } 94 | 95 | private static Color? m_selectedControlPointColor = null; 96 | public static Color SelectedControlPointColor 97 | { 98 | get 99 | { 100 | if( m_selectedControlPointColor == null ) 101 | m_selectedControlPointColor = GetColor( "BezierSolution_SelectedControlPointColor", Color.green ); 102 | 103 | return m_selectedControlPointColor.Value; 104 | } 105 | set 106 | { 107 | m_selectedControlPointColor = value; 108 | SetColor( "BezierSolution_SelectedControlPointColor", value ); 109 | } 110 | } 111 | 112 | private static Color? m_quickEditModeNewEndPointColor = null; 113 | public static Color QuickEditModeNewEndPointColor 114 | { 115 | get 116 | { 117 | if( m_quickEditModeNewEndPointColor == null ) 118 | m_quickEditModeNewEndPointColor = GetColor( "BezierSolution_QuickEditNewPointColor", Color.cyan ); 119 | 120 | return m_quickEditModeNewEndPointColor.Value; 121 | } 122 | set 123 | { 124 | m_quickEditModeNewEndPointColor = value; 125 | SetColor( "BezierSolution_QuickEditNewPointColor", value ); 126 | } 127 | } 128 | 129 | private static Color? m_quickEditModeDeleteEndPointColor = null; 130 | public static Color QuickEditModeDeleteEndPointColor 131 | { 132 | get 133 | { 134 | if( m_quickEditModeDeleteEndPointColor == null ) 135 | m_quickEditModeDeleteEndPointColor = GetColor( "BezierSolution_QuickEditDeletePointColor", Color.red ); 136 | 137 | return m_quickEditModeDeleteEndPointColor.Value; 138 | } 139 | set 140 | { 141 | m_quickEditModeDeleteEndPointColor = value; 142 | SetColor( "BezierSolution_QuickEditDeletePointColor", value ); 143 | } 144 | } 145 | 146 | private static Color? m_normalsPreviewColor = null; 147 | public static Color NormalsPreviewColor 148 | { 149 | get 150 | { 151 | if( m_normalsPreviewColor == null ) 152 | m_normalsPreviewColor = GetColor( "BezierSolution_NormalsPreviewColor", Color.blue ); 153 | 154 | return m_normalsPreviewColor.Value; 155 | } 156 | set 157 | { 158 | m_normalsPreviewColor = value; 159 | SetColor( "BezierSolution_NormalsPreviewColor", value ); 160 | } 161 | } 162 | 163 | private static Color? m_evenlySpacedPointsColor = null; 164 | public static Color EvenlySpacedPointsColor 165 | { 166 | get 167 | { 168 | if( m_evenlySpacedPointsColor == null ) 169 | m_evenlySpacedPointsColor = GetColor( "BezierSolution_EvenlySpacedPointsColor", Color.white ); 170 | 171 | return m_evenlySpacedPointsColor.Value; 172 | } 173 | set 174 | { 175 | m_evenlySpacedPointsColor = value; 176 | SetColor( "BezierSolution_EvenlySpacedPointsColor", value ); 177 | } 178 | } 179 | #endregion 180 | 181 | #region Size Adjustments 182 | private static float? m_splineThickness = null; 183 | public static float SplineThickness 184 | { 185 | get 186 | { 187 | if( m_splineThickness == null ) 188 | m_splineThickness = EditorPrefs.GetFloat( "BezierSolution_SplineThickness", 8f ); 189 | 190 | return m_splineThickness.Value; 191 | } 192 | set 193 | { 194 | m_splineThickness = value; 195 | EditorPrefs.SetFloat( "BezierSolution_SplineThickness", value ); 196 | } 197 | } 198 | 199 | private static float? m_endPointSize = null; 200 | public static float EndPointSize 201 | { 202 | get 203 | { 204 | if( m_endPointSize == null ) 205 | m_endPointSize = EditorPrefs.GetFloat( "BezierSolution_EndPointSize", 0.075f ); 206 | 207 | return m_endPointSize.Value; 208 | } 209 | set 210 | { 211 | m_endPointSize = value; 212 | EditorPrefs.SetFloat( "BezierSolution_EndPointSize", value ); 213 | } 214 | } 215 | 216 | private static float? m_selectedEndPointSize = null; 217 | public static float SelectedEndPointSize 218 | { 219 | get 220 | { 221 | if( m_selectedEndPointSize == null ) 222 | m_selectedEndPointSize = EditorPrefs.GetFloat( "BezierSolution_SelectedEndPointSize", 0.075f * 1.5f ); 223 | 224 | return m_selectedEndPointSize.Value; 225 | } 226 | set 227 | { 228 | m_selectedEndPointSize = value; 229 | EditorPrefs.SetFloat( "BezierSolution_SelectedEndPointSize", value ); 230 | } 231 | } 232 | 233 | private static float? m_controlPointSize = null; 234 | public static float ControlPointSize 235 | { 236 | get 237 | { 238 | if( m_controlPointSize == null ) 239 | m_controlPointSize = EditorPrefs.GetFloat( "BezierSolution_ControlPointSize", 0.05f ); 240 | 241 | return m_controlPointSize.Value; 242 | } 243 | set 244 | { 245 | m_controlPointSize = value; 246 | EditorPrefs.SetFloat( "BezierSolution_ControlPointSize", value ); 247 | } 248 | } 249 | 250 | private static float? m_quickEditModeNewEndPointSize = null; 251 | public static float QuickEditModeNewEndPointSize 252 | { 253 | get 254 | { 255 | if( m_quickEditModeNewEndPointSize == null ) 256 | m_quickEditModeNewEndPointSize = EditorPrefs.GetFloat( "BezierSolution_QuickEditNewEndPointSize", 0.075f ); 257 | 258 | return m_quickEditModeNewEndPointSize.Value; 259 | } 260 | set 261 | { 262 | m_quickEditModeNewEndPointSize = value; 263 | EditorPrefs.SetFloat( "BezierSolution_QuickEditNewEndPointSize", value ); 264 | } 265 | } 266 | 267 | private static float? m_normalsPreviewLength = null; 268 | public static float NormalsPreviewLength 269 | { 270 | get 271 | { 272 | if( m_normalsPreviewLength == null ) 273 | m_normalsPreviewLength = EditorPrefs.GetFloat( "BezierSolution_NormalsPreviewLength", 0.35f ); 274 | 275 | return m_normalsPreviewLength.Value; 276 | } 277 | set 278 | { 279 | value = Mathf.Max( value, 0f ); 280 | m_normalsPreviewLength = value; 281 | EditorPrefs.SetFloat( "BezierSolution_NormalsPreviewLength", value ); 282 | } 283 | } 284 | 285 | private static float? m_evenlySpacedPointsSize = null; 286 | public static float EvenlySpacedPointsSize 287 | { 288 | get 289 | { 290 | if( m_evenlySpacedPointsSize == null ) 291 | m_evenlySpacedPointsSize = EditorPrefs.GetFloat( "BezierSolution_EvenlySpacedPointsSize", 0.1f ); 292 | 293 | return m_evenlySpacedPointsSize.Value; 294 | } 295 | set 296 | { 297 | m_evenlySpacedPointsSize = value; 298 | EditorPrefs.SetFloat( "BezierSolution_EvenlySpacedPointsSize", value ); 299 | } 300 | } 301 | 302 | private static float? m_extraDataAsFrustumSize = null; 303 | public static float ExtraDataAsFrustumSize 304 | { 305 | get 306 | { 307 | if( m_extraDataAsFrustumSize == null ) 308 | m_extraDataAsFrustumSize = EditorPrefs.GetFloat( "BezierSolution_ExtraDataFrustumSize", 2.2f ); 309 | 310 | return m_extraDataAsFrustumSize.Value; 311 | } 312 | set 313 | { 314 | m_extraDataAsFrustumSize = value; 315 | EditorPrefs.SetFloat( "BezierSolution_ExtraDataFrustumSize", value ); 316 | } 317 | } 318 | #endregion 319 | 320 | #region Other Settings 321 | private static float? m_splineSmoothness = null; 322 | public static float SplineSmoothness 323 | { 324 | get 325 | { 326 | if( m_splineSmoothness == null ) 327 | m_splineSmoothness = EditorPrefs.GetFloat( "BezierSolution_SplineSmoothness", 10f ); 328 | 329 | return m_splineSmoothness.Value; 330 | } 331 | set 332 | { 333 | value = Mathf.Max( value, 1f ); 334 | m_splineSmoothness = value; 335 | EditorPrefs.SetFloat( "BezierSolution_SplineSmoothness", value ); 336 | } 337 | } 338 | 339 | private static int? m_displayedIntermediateNormalsCount = null; 340 | public static int DisplayedIntermediateNormalsCount 341 | { 342 | get 343 | { 344 | if( m_displayedIntermediateNormalsCount == null ) 345 | m_displayedIntermediateNormalsCount = EditorPrefs.GetInt( "BezierSolution_IntermediateNormals", 8 ); 346 | 347 | return m_displayedIntermediateNormalsCount.Value; 348 | } 349 | set 350 | { 351 | value = Mathf.Clamp( value, 0, 999 ); 352 | m_displayedIntermediateNormalsCount = value; 353 | EditorPrefs.SetInt( "BezierSolution_IntermediateNormals", value ); 354 | } 355 | } 356 | 357 | private static bool? m_moveMultiplePointsInOppositeDirections = null; 358 | public static bool MoveMultiplePointsInOppositeDirections 359 | { 360 | get 361 | { 362 | if( m_moveMultiplePointsInOppositeDirections == null ) 363 | m_moveMultiplePointsInOppositeDirections = EditorPrefs.GetBool( "BezierSolution_OppositeTransformation", false ); 364 | 365 | return m_moveMultiplePointsInOppositeDirections.Value; 366 | } 367 | set 368 | { 369 | m_moveMultiplePointsInOppositeDirections = value; 370 | EditorPrefs.SetBool( "BezierSolution_OppositeTransformation", value ); 371 | } 372 | } 373 | 374 | private static QuickEditModePointPlacement? m_quickEditPointPlacement = null; 375 | public static QuickEditModePointPlacement QuickEditPointPlacement 376 | { 377 | get 378 | { 379 | if( m_quickEditPointPlacement == null ) 380 | m_quickEditPointPlacement = (QuickEditModePointPlacement) EditorPrefs.GetInt( "BezierSolution_QuickEditPointPlacement", (int) QuickEditModePointPlacement.SceneGeometry ); 381 | 382 | return m_quickEditPointPlacement.Value; 383 | } 384 | set 385 | { 386 | m_quickEditPointPlacement = value; 387 | EditorPrefs.SetInt( "BezierSolution_QuickEditPointPlacement", (int) value ); 388 | } 389 | } 390 | 391 | private static bool? m_quickEditSplineModifyNormals = null; 392 | public static bool QuickEditSplineModifyNormals 393 | { 394 | get 395 | { 396 | if( m_quickEditSplineModifyNormals == null ) 397 | m_quickEditSplineModifyNormals = EditorPrefs.GetBool( "BezierSolution_QuickEditModifyNormals", true ); 398 | 399 | return m_quickEditSplineModifyNormals.Value; 400 | } 401 | set 402 | { 403 | m_quickEditSplineModifyNormals = value; 404 | EditorPrefs.SetBool( "BezierSolution_QuickEditModifyNormals", value ); 405 | } 406 | } 407 | 408 | private static bool? m_quickEditSplinePreserveShape = null; 409 | public static bool QuickEditSplinePreserveShape 410 | { 411 | get 412 | { 413 | if( m_quickEditSplinePreserveShape == null ) 414 | m_quickEditSplinePreserveShape = EditorPrefs.GetBool( "BezierSolution_QuickEditPreserveShape", false ); 415 | 416 | return m_quickEditSplinePreserveShape.Value; 417 | } 418 | set 419 | { 420 | m_quickEditSplinePreserveShape = value; 421 | EditorPrefs.SetBool( "BezierSolution_QuickEditPreserveShape", value ); 422 | } 423 | } 424 | #endregion 425 | 426 | #region Visibility Settings 427 | private static bool? m_showControlPoints = null; 428 | public static bool ShowControlPoints 429 | { 430 | get 431 | { 432 | if( m_showControlPoints == null ) 433 | m_showControlPoints = EditorPrefs.GetBool( "BezierSolution_ShowControlPoints", true ); 434 | 435 | return m_showControlPoints.Value; 436 | } 437 | set 438 | { 439 | m_showControlPoints = value; 440 | EditorPrefs.SetBool( "BezierSolution_ShowControlPoints", value ); 441 | } 442 | } 443 | 444 | private static bool? m_showControlPointDirections = null; 445 | public static bool ShowControlPointDirections 446 | { 447 | get 448 | { 449 | if( m_showControlPointDirections == null ) 450 | m_showControlPointDirections = EditorPrefs.GetBool( "BezierSolution_ShowControlPointDirs", true ); 451 | 452 | return m_showControlPointDirections.Value; 453 | } 454 | set 455 | { 456 | m_showControlPointDirections = value; 457 | EditorPrefs.SetBool( "BezierSolution_ShowControlPointDirs", value ); 458 | } 459 | } 460 | 461 | private static bool? m_showEndPointLabels = null; 462 | public static bool ShowEndPointLabels 463 | { 464 | get 465 | { 466 | if( m_showEndPointLabels == null ) 467 | m_showEndPointLabels = EditorPrefs.GetBool( "BezierSolution_ShowEndPointLabels", true ); 468 | 469 | return m_showEndPointLabels.Value; 470 | } 471 | set 472 | { 473 | m_showEndPointLabels = value; 474 | EditorPrefs.SetBool( "BezierSolution_ShowEndPointLabels", value ); 475 | } 476 | } 477 | 478 | private static bool? m_showNormals = null; 479 | public static bool ShowNormals 480 | { 481 | get 482 | { 483 | if( m_showNormals == null ) 484 | m_showNormals = EditorPrefs.GetBool( "BezierSolution_ShowNormals", true ); 485 | 486 | return m_showNormals.Value; 487 | } 488 | set 489 | { 490 | m_showNormals = value; 491 | EditorPrefs.SetBool( "BezierSolution_ShowNormals", value ); 492 | } 493 | } 494 | 495 | private static bool? m_showEvenlySpacedPoints = null; 496 | public static bool ShowEvenlySpacedPoints 497 | { 498 | get 499 | { 500 | if( m_showEvenlySpacedPoints == null ) 501 | m_showEvenlySpacedPoints = EditorPrefs.GetBool( "BezierSolution_ShowEvenlySpacedPoints", false ); 502 | 503 | return m_showEvenlySpacedPoints.Value; 504 | } 505 | set 506 | { 507 | m_showEvenlySpacedPoints = value; 508 | EditorPrefs.SetBool( "BezierSolution_ShowEvenlySpacedPoints", value ); 509 | } 510 | } 511 | 512 | private static bool? m_visualizeExtraDataAsFrustum = null; 513 | public static bool VisualizeExtraDataAsFrustum 514 | { 515 | get 516 | { 517 | if( m_visualizeExtraDataAsFrustum == null ) 518 | m_visualizeExtraDataAsFrustum = EditorPrefs.GetBool( "BezierSolution_VisualizeFrustum", false ); 519 | 520 | return m_visualizeExtraDataAsFrustum.Value; 521 | } 522 | set 523 | { 524 | m_visualizeExtraDataAsFrustum = value; 525 | EditorPrefs.SetBool( "BezierSolution_VisualizeFrustum", value ); 526 | } 527 | } 528 | #endregion 529 | 530 | #if UNITY_2018_3_OR_NEWER 531 | [SettingsProvider] 532 | public static SettingsProvider CreatePreferencesGUI() 533 | { 534 | return new SettingsProvider( "Project/yasirkula/Bezier Solution", SettingsScope.Project ) 535 | { 536 | guiHandler = ( searchContext ) => PreferencesGUI(), 537 | keywords = new System.Collections.Generic.HashSet() { "Bezier", "Spline", "Point", "Normals", "Color", "Size" } 538 | }; 539 | } 540 | #endif 541 | 542 | [MenuItem( "CONTEXT/BezierSpline/Open Settings" )] 543 | [MenuItem( "CONTEXT/BezierPoint/Open Settings" )] 544 | private static void OpenPreferencesWindow( MenuCommand command ) 545 | { 546 | #if UNITY_2018_3_OR_NEWER 547 | SettingsService.OpenProjectSettings( "Project/yasirkula/Bezier Solution" ); 548 | #else 549 | System.Type preferencesWindowType = typeof( EditorWindow ).Assembly.GetType( "UnityEditor.PreferencesWindow" ); 550 | preferencesWindowType.GetMethod( "ShowPreferencesWindow", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).Invoke( null, null ); 551 | 552 | EditorWindow preferencesWindow = EditorWindow.GetWindow( preferencesWindowType ); 553 | if( (bool) preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow ) ) 554 | { 555 | preferencesWindowType.GetMethod( "AddCustomSections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).Invoke( preferencesWindow, null ); 556 | preferencesWindowType.GetField( "m_RefreshCustomPreferences", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, false ); 557 | } 558 | 559 | int targetSectionIndex = -1; 560 | System.Collections.IList sections = (System.Collections.IList) preferencesWindowType.GetField( "m_Sections", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( preferencesWindow ); 561 | for( int i = 0; i < sections.Count; i++ ) 562 | { 563 | if( ( (GUIContent) sections[i].GetType().GetField( "content", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( sections[i] ) ).text == "Bezier Solution" ) 564 | { 565 | targetSectionIndex = i; 566 | break; 567 | } 568 | } 569 | 570 | if( targetSectionIndex >= 0 ) 571 | preferencesWindowType.GetProperty( "selectedSectionIndex", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).SetValue( preferencesWindow, targetSectionIndex, null ); 572 | #endif 573 | } 574 | 575 | #if !UNITY_2018_3_OR_NEWER 576 | [PreferenceItem( "Bezier Solution" )] 577 | #endif 578 | public static void PreferencesGUI() 579 | { 580 | Color c; 581 | float f; 582 | int i; 583 | bool b; 584 | 585 | float labelWidth = EditorGUIUtility.labelWidth; 586 | EditorGUIUtility.labelWidth += 50f; 587 | 588 | EditorGUI.BeginChangeCheck(); 589 | 590 | EditorGUI.BeginChangeCheck(); 591 | c = ColorField( "Selected Spline Color", SelectedSplineColor, new Color( 0.8f, 0.6f, 0.8f, 1f ) ); 592 | if( EditorGUI.EndChangeCheck() ) 593 | SelectedSplineColor = c; 594 | 595 | EditorGUI.BeginChangeCheck(); 596 | c = ColorField( "Unselected Spline Color", NormalSplineColor, new Color( 0.8f, 0.6f, 0.8f, 1f ) ); 597 | if( EditorGUI.EndChangeCheck() ) 598 | NormalSplineColor = c; 599 | 600 | EditorGUI.BeginChangeCheck(); 601 | f = FloatField( "Selected Spline Thickness", SplineThickness, 8f ); 602 | if( EditorGUI.EndChangeCheck() ) 603 | SplineThickness = f; 604 | 605 | EditorGUI.BeginChangeCheck(); 606 | f = FloatField( "Unselected Spline Smoothness", SplineSmoothness, 10f ); 607 | if( EditorGUI.EndChangeCheck() ) 608 | SplineSmoothness = f; 609 | 610 | EditorGUILayout.Space(); 611 | 612 | EditorGUI.BeginChangeCheck(); 613 | c = ColorField( "Selected End Points Color", SelectedEndPointColor, Color.yellow ); 614 | if( EditorGUI.EndChangeCheck() ) 615 | SelectedEndPointColor = c; 616 | 617 | EditorGUI.BeginChangeCheck(); 618 | c = ColorField( "Unselected End Point Color", NormalEndPointColor, Color.white ); 619 | if( EditorGUI.EndChangeCheck() ) 620 | NormalEndPointColor = c; 621 | 622 | EditorGUI.BeginChangeCheck(); 623 | f = FloatField( "Selected End Points Size", SelectedEndPointSize, 0.075f * 1.5f ); 624 | if( EditorGUI.EndChangeCheck() ) 625 | SelectedEndPointSize = f; 626 | 627 | EditorGUI.BeginChangeCheck(); 628 | f = FloatField( "Unselected End Points Size", EndPointSize, 0.075f ); 629 | if( EditorGUI.EndChangeCheck() ) 630 | EndPointSize = f; 631 | 632 | EditorGUI.BeginChangeCheck(); 633 | b = EditorGUILayout.Toggle( "Show End Point Labels", ShowEndPointLabels ); 634 | if( EditorGUI.EndChangeCheck() ) 635 | ShowEndPointLabels = b; 636 | 637 | EditorGUILayout.Space(); 638 | 639 | EditorGUI.BeginChangeCheck(); 640 | b = EditorGUILayout.Toggle( "Show Control Points", ShowControlPoints ); 641 | if( EditorGUI.EndChangeCheck() ) 642 | ShowControlPoints = b; 643 | 644 | EditorGUI.indentLevel++; 645 | 646 | EditorGUI.BeginChangeCheck(); 647 | b = EditorGUILayout.Toggle( "Show Control Point Directions", ShowControlPointDirections ); 648 | if( EditorGUI.EndChangeCheck() ) 649 | ShowControlPointDirections = b; 650 | 651 | EditorGUI.BeginChangeCheck(); 652 | c = ColorField( "Selected Control Point Color", SelectedControlPointColor, Color.green ); 653 | if( EditorGUI.EndChangeCheck() ) 654 | SelectedControlPointColor = c; 655 | 656 | EditorGUI.BeginChangeCheck(); 657 | c = ColorField( "Unselected Control Point Color", NormalControlPointColor, Color.white ); 658 | if( EditorGUI.EndChangeCheck() ) 659 | NormalControlPointColor = c; 660 | 661 | EditorGUI.BeginChangeCheck(); 662 | f = FloatField( "Control Points Size", ControlPointSize, 0.05f ); 663 | if( EditorGUI.EndChangeCheck() ) 664 | ControlPointSize = f; 665 | 666 | EditorGUI.indentLevel--; 667 | 668 | EditorGUILayout.Space(); 669 | 670 | EditorGUI.BeginChangeCheck(); 671 | b = EditorGUILayout.Toggle( "Show Normals", ShowNormals ); 672 | if( EditorGUI.EndChangeCheck() ) 673 | ShowNormals = b; 674 | 675 | EditorGUI.indentLevel++; 676 | 677 | EditorGUI.BeginChangeCheck(); 678 | i = IntField( "Displayed Intermediate Normals", DisplayedIntermediateNormalsCount, 8 ); 679 | if( EditorGUI.EndChangeCheck() ) 680 | DisplayedIntermediateNormalsCount = i; 681 | 682 | EditorGUI.BeginChangeCheck(); 683 | c = ColorField( "Normals Preview Color", NormalsPreviewColor, Color.blue ); 684 | if( EditorGUI.EndChangeCheck() ) 685 | NormalsPreviewColor = c; 686 | 687 | EditorGUI.BeginChangeCheck(); 688 | f = FloatField( "Normals Preview Length", NormalsPreviewLength, 0.35f ); 689 | if( EditorGUI.EndChangeCheck() ) 690 | NormalsPreviewLength = f; 691 | 692 | EditorGUI.indentLevel--; 693 | 694 | EditorGUILayout.Space(); 695 | 696 | EditorGUI.BeginChangeCheck(); 697 | b = EditorGUILayout.Toggle( "Visualize Evenly Spaced Points", ShowEvenlySpacedPoints ); 698 | if( EditorGUI.EndChangeCheck() ) 699 | ShowEvenlySpacedPoints = b; 700 | 701 | EditorGUI.indentLevel++; 702 | 703 | EditorGUI.BeginChangeCheck(); 704 | c = ColorField( "Evenly Spaced Points Color", EvenlySpacedPointsColor, Color.white ); 705 | if( EditorGUI.EndChangeCheck() ) 706 | EvenlySpacedPointsColor = c; 707 | 708 | EditorGUI.BeginChangeCheck(); 709 | f = FloatField( "Evenly Spaced Points Size", EvenlySpacedPointsSize, 0.1f ); 710 | if( EditorGUI.EndChangeCheck() ) 711 | EvenlySpacedPointsSize = f; 712 | 713 | EditorGUI.indentLevel--; 714 | 715 | EditorGUILayout.Space(); 716 | 717 | EditorGUI.BeginChangeCheck(); 718 | c = ColorField( "Quick Edit New Point Color", QuickEditModeNewEndPointColor, Color.cyan ); 719 | if( EditorGUI.EndChangeCheck() ) 720 | QuickEditModeNewEndPointColor = c; 721 | 722 | EditorGUI.BeginChangeCheck(); 723 | c = ColorField( "Quick Edit Delete Point Color", QuickEditModeDeleteEndPointColor, Color.red ); 724 | if( EditorGUI.EndChangeCheck() ) 725 | QuickEditModeDeleteEndPointColor = c; 726 | 727 | EditorGUI.BeginChangeCheck(); 728 | f = FloatField( "Quick Edit New Point Size", QuickEditModeNewEndPointSize, 0.075f ); 729 | if( EditorGUI.EndChangeCheck() ) 730 | QuickEditModeNewEndPointSize = f; 731 | 732 | EditorGUILayout.Space(); 733 | 734 | EditorGUI.BeginChangeCheck(); 735 | b = EditorGUILayout.Toggle( new GUIContent( "Visualize Extra Data As Frustum", "Visualize end points' Extra Data as camera frustum in Scene window" ), VisualizeExtraDataAsFrustum ); 736 | if( EditorGUI.EndChangeCheck() ) 737 | VisualizeExtraDataAsFrustum = b; 738 | 739 | EditorGUI.indentLevel++; 740 | 741 | EditorGUI.BeginChangeCheck(); 742 | f = FloatField( "Frustum Size", ExtraDataAsFrustumSize, 2.2f ); 743 | if( EditorGUI.EndChangeCheck() ) 744 | ExtraDataAsFrustumSize = f; 745 | 746 | EditorGUI.indentLevel--; 747 | 748 | EditorGUIUtility.labelWidth = labelWidth; 749 | 750 | if( EditorGUI.EndChangeCheck() ) 751 | SceneView.RepaintAll(); 752 | } 753 | 754 | private static Color ColorField( string label, Color value, Color defaultValue ) 755 | { 756 | GUILayout.BeginHorizontal(); 757 | Color result = EditorGUILayout.ColorField( label, value ); 758 | if( GUILayout.Button( "Reset", BezierUtils.GL_WIDTH_60 ) ) 759 | result = defaultValue; 760 | GUILayout.EndHorizontal(); 761 | 762 | return result; 763 | } 764 | 765 | private static float FloatField( string label, float value, float defaultValue ) 766 | { 767 | GUILayout.BeginHorizontal(); 768 | float result = EditorGUILayout.FloatField( label, value ); 769 | if( GUILayout.Button( "Reset", BezierUtils.GL_WIDTH_60 ) ) 770 | result = defaultValue; 771 | GUILayout.EndHorizontal(); 772 | 773 | return result; 774 | } 775 | 776 | private static int IntField( string label, int value, int defaultValue ) 777 | { 778 | GUILayout.BeginHorizontal(); 779 | int result = EditorGUILayout.IntField( label, value ); 780 | if( GUILayout.Button( "Reset", BezierUtils.GL_WIDTH_60 ) ) 781 | result = defaultValue; 782 | GUILayout.EndHorizontal(); 783 | 784 | return result; 785 | } 786 | 787 | private static Color GetColor( string pref, Color defaultColor ) 788 | { 789 | if( !EditorPrefs.HasKey( pref ) ) 790 | return defaultColor; 791 | 792 | string[] parts = EditorPrefs.GetString( pref ).Split( ';' ); 793 | return new Color32( byte.Parse( parts[0] ), byte.Parse( parts[1] ), byte.Parse( parts[2] ), byte.Parse( parts[3] ) ); 794 | } 795 | 796 | private static void SetColor( string pref, Color32 value ) 797 | { 798 | EditorPrefs.SetString( pref, string.Concat( value.r.ToString(), ";", value.g.ToString(), ";", value.b.ToString(), ";", value.a.ToString() ) ); 799 | } 800 | } 801 | } -------------------------------------------------------------------------------- /Plugins/BezierSolution/Editor/BezierUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEditor; 4 | using System.Reflection; 5 | using Object = UnityEngine.Object; 6 | 7 | namespace BezierSolution.Extras 8 | { 9 | public enum QuickEditModePointPlacement { SceneGeometry = 0, CameraPlane = 1, XY = 2, XZ = 3, YZ = 4 }; 10 | 11 | public static class BezierUtils 12 | { 13 | private const string PRECEDING_CONTROL_POINT_LABEL = " <--"; 14 | private const string FOLLOWING_CONTROL_POINT_LABEL = " -->"; 15 | 16 | private static readonly Color AUTO_CONSTRUCT_SPLINE_BUTTON_COLOR = new Color( 0.65f, 1f, 0.65f ); 17 | 18 | private static readonly GUIContent LOOP_TEXT = new GUIContent( "Loop", "Connects the first end point and the last end point of the spline" ); 19 | private static readonly GUIContent DRAW_RUNTIME_GIZMOS_TEXT = new GUIContent( "Draw Runtime Gizmos", "Draws the spline during gameplay" ); 20 | private static readonly GUIContent SHOW_CONTROL_POINTS_TEXT = new GUIContent( "Show Control Points", "Shows control points of the end points in Scene window" ); 21 | private static readonly GUIContent SHOW_DIRECTIONS_TEXT = new GUIContent( "Show Directions", "Shows control points' directions in Scene window" ); 22 | private static readonly GUIContent SHOW_POINT_INDICES_TEXT = new GUIContent( "Show Point Indices", "Shows end points' indices in Scene window" ); 23 | private static readonly GUIContent SHOW_NORMALS_TEXT = new GUIContent( "Show Normals", "Shows end points' normal vectors in Scene window" ); 24 | private static readonly GUIContent DISPLAYED_INTERMEDIATE_NORMALS_COUNT_TEXT = new GUIContent( "Displayed Intermediate Normals", "The number of normal vectors to display in-between each end point pair" ); 25 | private static readonly GUIContent AUTO_CALCULATED_NORMALS_ANGLE_TEXT = new GUIContent( "Auto Calculated Normals Angle", "When 'Auto Calculate Normals' button is clicked, all normals will be rotated around their Z axis by the specified amount (each end point's rotation angle can further be customized from the end point's Inspector)" ); 26 | private static readonly GUIContent AUTO_CALCULATED_INTERMEDIATE_NORMALS_TEXT = new GUIContent( "Auto Calculated Intermediate Normals", "When 'Auto Calculate Normals' button is clicked, this many intermediate normal vectors will be calculated and stored for each end point pair. If no intermediate normal vectors are calculated (0), normals of end point pairs will be lerped to estimate the intermediate values" ); 27 | private static readonly GUIContent EVENLY_SPACED_POINTS_RESOLUTION_TEXT = new GUIContent( "Evenly Spaced Points Resolution", "Determines approximately how many points will be calculated per each segment of the spline while generating 'evenlySpacedPoints'. Evenly spaced points are used by numerous utility components when their 'High Quality' option is enabled" ); 28 | private static readonly GUIContent EVENLY_SPACED_POINTS_ACCURACY_TEXT = new GUIContent( "Evenly Spaced Points Accuracy", "Determines how accurate the uniform spacing of 'evenlySpacedPoints' will be" ); 29 | private static readonly GUIContent POINT_CACHE_RESOLUTION_TEXT = new GUIContent( "Point Cache Resolution", "Determines how many uniformly distributed points 'pointCache' will have" ); 30 | private static readonly GUIContent CONSTRUCT_LINEAR_PATH_TEXT = new GUIContent( "Construct Linear Path", "Constructs a completely linear path (end points' Handle Mode will be set to Free)" ); 31 | private static readonly GUIContent AUTO_CONSTRUCT_SPLINE_TEXT = new GUIContent( "Auto Construct Spline", "Constructs a smooth path" ); 32 | private static readonly GUIContent AUTO_CONSTRUCT_SPLINE_2_TEXT = new GUIContent( "Auto Construct Spline 2", "Constructs a smooth path (another algorithm)" ); 33 | private static readonly GUIContent AUTO_CALCULATE_NORMALS_TEXT = new GUIContent( "Auto Calculate Normals", "Attempts to automatically calculate the end points' normal vectors" ); 34 | private static readonly GUIContent AUTO_CONSTRUCT_ALWAYS_TEXT = new GUIContent( "Always", "Applies this method automatically as spline's points change" ); 35 | private static readonly GUIContent QUICK_EDIT_MODE_TEXT = new GUIContent( "Quick Edit Mode", "Quickly add new points to the spline or move the existing points" ); 36 | private static readonly GUIContent QUICK_EDIT_POINT_PLACEMENT_MODE_TEXT = new GUIContent( "Point Placement Mode", "Determines where the dragged or newly created points will be placed:\n- Scene Geometry: the scene geometry under the cursor\n- Camera Plane: Scene camera's local XY plane\n- XY: XY plane\n- XZ: XZ plane\n- YZ: YZ plane" ); 37 | private static readonly GUIContent QUICK_EDIT_MODIFY_NORMALS_TEXT = new GUIContent( "Use Raycast Normals", "While dragging a point or adding a new point, the point's Normal vector will be set to the normal of the scene geometry under the cursor" ); 38 | private static readonly GUIContent QUICK_EDIT_PRESERVE_SPLINE_SHAPE_TEXT = new GUIContent( "Preserve Spline Shape", "While inserting new points along the spline, the spline's shape will be preserved but the neighboring end points' 'Handle Mode' will no longer be 'Mirrored'" ); 39 | 40 | public static readonly GUILayoutOption GL_WIDTH_45 = GUILayout.Width( 45f ); 41 | public static readonly GUILayoutOption GL_WIDTH_60 = GUILayout.Width( 60f ); 42 | public static readonly GUILayoutOption GL_WIDTH_100 = GUILayout.Width( 100f ); 43 | public static readonly GUILayoutOption GL_WIDTH_155 = GUILayout.Width( 155f ); 44 | 45 | private static readonly MethodInfo intersectRayMeshMethod = typeof( HandleUtility ).GetMethod( "IntersectRayMesh", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ); 46 | 47 | public static bool QuickEditSplineMode { get; private set; } 48 | 49 | [MenuItem( "GameObject/Bezier Spline", priority = 35 )] 50 | private static void NewSpline( MenuCommand command ) 51 | { 52 | GameObject spline = new GameObject( "BezierSpline", typeof( BezierSpline ) ); 53 | Undo.RegisterCreatedObjectUndo( spline, "Create Spline" ); 54 | if( command.context ) 55 | Undo.SetTransformParent( spline.transform, ( (GameObject) command.context ).transform, "Create Spline" ); 56 | 57 | spline.transform.localPosition = new Vector3( 0f, 0f, 0f ); 58 | spline.transform.localRotation = Quaternion.identity; 59 | spline.transform.localScale = new Vector3( 1f, 1f, 1f ); 60 | 61 | Selection.activeTransform = spline.transform; 62 | } 63 | 64 | [DrawGizmo( GizmoType.NonSelected | GizmoType.Pickable )] 65 | private static void DrawSplineGizmo( BezierSpline spline, GizmoType gizmoType ) 66 | { 67 | if( spline.Count < 2 ) 68 | return; 69 | 70 | // Make sure that none of the points of the spline are selected 71 | if( BezierPointEditor.ActiveEditor && Array.IndexOf( BezierPointEditor.ActiveEditor.allSplines, spline ) >= 0 ) 72 | return; 73 | 74 | Gizmos.color = BezierSettings.NormalSplineColor; 75 | 76 | Vector3 lastPos = spline[0].position; 77 | float increaseAmount = 1f / ( spline.Count * BezierSettings.SplineSmoothness ); 78 | 79 | for( float i = increaseAmount; i < 1f; i += increaseAmount ) 80 | { 81 | Vector3 pos = spline.GetPoint( i ); 82 | Gizmos.DrawLine( lastPos, pos ); 83 | lastPos = pos; 84 | } 85 | 86 | Gizmos.DrawLine( lastPos, spline.GetPoint( 1f ) ); 87 | } 88 | 89 | [DrawGizmo( GizmoType.Selected | GizmoType.NonSelected )] 90 | private static void DrawPointExtraDataFrustumGizmo( BezierPoint point, GizmoType gizmoType ) 91 | { 92 | if( !BezierSettings.VisualizeExtraDataAsFrustum ) 93 | return; 94 | 95 | // If the either the point or its spline isn't selected, don't show frustum of the point 96 | if( ( !BezierSplineEditor.ActiveEditor || Array.IndexOf( BezierSplineEditor.ActiveEditor.allSplines, point.spline ) < 0 ) && 97 | ( !BezierPointEditor.ActiveEditor || Array.IndexOf( BezierPointEditor.ActiveEditor.allSplines, point.spline ) < 0 ) ) 98 | return; 99 | 100 | Quaternion rotation = point.extraData; 101 | if( Mathf.Approximately( rotation.x * rotation.x + rotation.y * rotation.y + rotation.z * rotation.z + rotation.w * rotation.w, 1f ) ) 102 | { 103 | Matrix4x4 temp = Gizmos.matrix; 104 | Gizmos.matrix = Matrix4x4.TRS( point.position, rotation, Vector3.one * ( BezierSettings.ExtraDataAsFrustumSize * HandleUtility.GetHandleSize( point.position ) ) ); 105 | Gizmos.DrawFrustum( new Vector3( 0f, 0f, 0f ), 60f, 0.18f, 0.01f, 1.5f ); 106 | Gizmos.matrix = temp; 107 | } 108 | } 109 | 110 | public static void DrawSplineDetailed( BezierSpline spline ) 111 | { 112 | if( spline.Count < 2 ) 113 | return; 114 | 115 | BezierPoint endPoint0 = null, endPoint1 = null; 116 | for( int i = 0; i < spline.Count - 1; i++ ) 117 | { 118 | endPoint0 = spline[i]; 119 | endPoint1 = spline[i + 1]; 120 | 121 | DrawBezier( endPoint0, endPoint1 ); 122 | } 123 | 124 | if( spline.loop && endPoint1 != null ) 125 | DrawBezier( endPoint1, spline[0] ); 126 | 127 | // Draw tangent lines on scene view 128 | //Color _tmp = Handles.color; 129 | //Handles.color = Color.cyan; 130 | //for( float i = 0f; i < 1f; i += 0.05f ) 131 | //{ 132 | // Handles.DrawLine( spline.GetPoint( i ), spline.GetPoint( i ) + spline.GetTangent( i ) ); 133 | //} 134 | //Handles.color = _tmp; 135 | } 136 | 137 | public static void DrawSplineEvenlySpacedPoints( BezierSpline spline ) 138 | { 139 | if( Event.current.type == EventType.Repaint ) 140 | { 141 | Color c = Handles.color; 142 | Handles.color = BezierSettings.EvenlySpacedPointsColor; 143 | 144 | float[] evenlySpacedNormalizedTs = spline.evenlySpacedPoints.uniformNormalizedTs; 145 | for( int i = 0; i < evenlySpacedNormalizedTs.Length; i++ ) 146 | { 147 | Vector3 evenlySpacedPoint = spline.GetPoint( evenlySpacedNormalizedTs[i] ); 148 | Handles.SphereHandleCap( 0, evenlySpacedPoint, Quaternion.identity, HandleUtility.GetHandleSize( evenlySpacedPoint ) * BezierSettings.EvenlySpacedPointsSize, EventType.Repaint ); 149 | } 150 | 151 | Handles.color = c; 152 | } 153 | } 154 | 155 | public static void DrawSplineInspectorGUI( BezierSpline[] splines ) 156 | { 157 | if( splines.Length == 0 ) 158 | return; 159 | 160 | for( int i = 0; i < splines.Length; i++ ) 161 | { 162 | if( splines[i].Count < 2 ) 163 | { 164 | if( GUILayout.Button( "Initialize Spline" ) ) 165 | { 166 | Object[] selection = Selection.objects; 167 | for( int j = 0; j < splines.Length; j++ ) 168 | { 169 | BezierSpline spline = splines[j]; 170 | if( spline.Count < 2 ) 171 | { 172 | bool isSplineSelected = false; 173 | for( int k = 0; k < selection.Length; k++ ) 174 | { 175 | if( selection[k] == spline || selection[k] == spline.transform || selection[k] == spline.gameObject ) 176 | { 177 | isSplineSelected = true; 178 | break; 179 | } 180 | } 181 | 182 | spline.Reset(); 183 | 184 | // Try to continue showing spline's scene gizmos after initialization by keeping 185 | // either the spline or a point of it selected 186 | if( !isSplineSelected ) 187 | { 188 | Array.Resize( ref selection, selection.Length + 1 ); 189 | selection[selection.Length - 1] = spline[0].gameObject; 190 | } 191 | } 192 | } 193 | 194 | Selection.objects = selection; 195 | GUIUtility.ExitGUI(); 196 | } 197 | 198 | return; 199 | } 200 | } 201 | 202 | Color c = GUI.backgroundColor; 203 | 204 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.loop == s2.loop ); 205 | EditorGUI.BeginChangeCheck(); 206 | bool loop = EditorGUILayout.Toggle( LOOP_TEXT, splines[0].loop ); 207 | if( EditorGUI.EndChangeCheck() ) 208 | { 209 | for( int i = 0; i < splines.Length; i++ ) 210 | { 211 | BezierSpline spline = splines[i]; 212 | Undo.RecordObject( spline, "Toggle Loop" ); 213 | spline.loop = loop; 214 | SetSplineDirtyWithUndo( spline, "Toggle Loop", InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange ); 215 | } 216 | 217 | SceneView.RepaintAll(); 218 | } 219 | 220 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.drawGizmos == s2.drawGizmos ); 221 | EditorGUI.BeginChangeCheck(); 222 | bool drawGizmos = EditorGUILayout.Toggle( DRAW_RUNTIME_GIZMOS_TEXT, splines[0].drawGizmos ); 223 | if( EditorGUI.EndChangeCheck() ) 224 | { 225 | for( int i = 0; i < splines.Length; i++ ) 226 | { 227 | Undo.RecordObject( splines[i], "Toggle Draw Gizmos" ); 228 | splines[i].drawGizmos = drawGizmos; 229 | } 230 | 231 | SceneView.RepaintAll(); 232 | } 233 | 234 | if( drawGizmos ) 235 | { 236 | EditorGUI.indentLevel++; 237 | 238 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.gizmoColor == s2.gizmoColor ); 239 | EditorGUI.BeginChangeCheck(); 240 | Color gizmoColor = EditorGUILayout.ColorField( "Gizmo Color", splines[0].gizmoColor ); 241 | if( EditorGUI.EndChangeCheck() ) 242 | { 243 | for( int i = 0; i < splines.Length; i++ ) 244 | { 245 | Undo.RecordObject( splines[i], "Change Gizmo Color" ); 246 | splines[i].gizmoColor = gizmoColor; 247 | } 248 | 249 | SceneView.RepaintAll(); 250 | } 251 | 252 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.gizmoSmoothness == s2.gizmoSmoothness ); 253 | EditorGUI.BeginChangeCheck(); 254 | int gizmoSmoothness = EditorGUILayout.IntSlider( "Gizmo Smoothness", splines[0].gizmoSmoothness, 1, 30 ); 255 | if( EditorGUI.EndChangeCheck() ) 256 | { 257 | for( int i = 0; i < splines.Length; i++ ) 258 | { 259 | Undo.RecordObject( splines[i], "Change Gizmo Smoothness" ); 260 | splines[i].gizmoSmoothness = gizmoSmoothness; 261 | } 262 | 263 | SceneView.RepaintAll(); 264 | } 265 | 266 | EditorGUI.indentLevel--; 267 | } 268 | 269 | EditorGUI.showMixedValue = false; 270 | 271 | EditorGUI.BeginChangeCheck(); 272 | bool showControlPoints = EditorGUILayout.Toggle( SHOW_CONTROL_POINTS_TEXT, BezierSettings.ShowControlPoints ); 273 | if( EditorGUI.EndChangeCheck() ) 274 | { 275 | BezierSettings.ShowControlPoints = showControlPoints; 276 | SceneView.RepaintAll(); 277 | } 278 | 279 | if( showControlPoints ) 280 | { 281 | EditorGUI.indentLevel++; 282 | EditorGUI.BeginChangeCheck(); 283 | bool showControlPointDirections = EditorGUILayout.Toggle( SHOW_DIRECTIONS_TEXT, BezierSettings.ShowControlPointDirections ); 284 | if( EditorGUI.EndChangeCheck() ) 285 | { 286 | BezierSettings.ShowControlPointDirections = showControlPointDirections; 287 | SceneView.RepaintAll(); 288 | } 289 | EditorGUI.indentLevel--; 290 | } 291 | 292 | EditorGUI.BeginChangeCheck(); 293 | bool showEndPointLabels = EditorGUILayout.Toggle( SHOW_POINT_INDICES_TEXT, BezierSettings.ShowEndPointLabels ); 294 | if( EditorGUI.EndChangeCheck() ) 295 | { 296 | BezierSettings.ShowEndPointLabels = showEndPointLabels; 297 | SceneView.RepaintAll(); 298 | } 299 | 300 | EditorGUI.BeginChangeCheck(); 301 | bool showNormals = EditorGUILayout.Toggle( SHOW_NORMALS_TEXT, BezierSettings.ShowNormals ); 302 | if( EditorGUI.EndChangeCheck() ) 303 | { 304 | BezierSettings.ShowNormals = showNormals; 305 | SceneView.RepaintAll(); 306 | } 307 | 308 | if( showNormals ) 309 | { 310 | EditorGUI.indentLevel++; 311 | 312 | EditorGUI.BeginChangeCheck(); 313 | Color normalsPreviewColor = EditorGUILayout.ColorField( "Color", BezierSettings.NormalsPreviewColor ); 314 | if( EditorGUI.EndChangeCheck() ) 315 | { 316 | BezierSettings.NormalsPreviewColor = normalsPreviewColor; 317 | SceneView.RepaintAll(); 318 | } 319 | 320 | EditorGUI.BeginChangeCheck(); 321 | float normalsPreviewLength = EditorGUILayout.FloatField( "Length", BezierSettings.NormalsPreviewLength ); 322 | if( EditorGUI.EndChangeCheck() ) 323 | { 324 | BezierSettings.NormalsPreviewLength = normalsPreviewLength; 325 | SceneView.RepaintAll(); 326 | } 327 | 328 | EditorGUI.BeginChangeCheck(); 329 | int displayedIntermediateNormalsCount = EditorGUILayout.IntField( DISPLAYED_INTERMEDIATE_NORMALS_COUNT_TEXT, BezierSettings.DisplayedIntermediateNormalsCount ); 330 | if( EditorGUI.EndChangeCheck() ) 331 | { 332 | BezierSettings.DisplayedIntermediateNormalsCount = displayedIntermediateNormalsCount; 333 | SceneView.RepaintAll(); 334 | } 335 | 336 | EditorGUI.indentLevel--; 337 | } 338 | 339 | EditorGUILayout.Space(); 340 | 341 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.autoCalculatedNormalsAngle == s2.autoCalculatedNormalsAngle ); 342 | EditorGUI.BeginChangeCheck(); 343 | float autoCalculatedNormalsAngle = EditorGUILayout.FloatField( AUTO_CALCULATED_NORMALS_ANGLE_TEXT, splines[0].autoCalculatedNormalsAngle ); 344 | if( EditorGUI.EndChangeCheck() ) 345 | { 346 | for( int i = 0; i < splines.Length; i++ ) 347 | { 348 | Undo.RecordObject( splines[i], "Change Normals Angle" ); 349 | splines[i].autoCalculatedNormalsAngle = autoCalculatedNormalsAngle; 350 | SetSplineDirtyWithUndo( splines[i], "Change Normals Angle", InternalDirtyFlags.NormalOffsetChange ); 351 | } 352 | 353 | SceneView.RepaintAll(); 354 | } 355 | 356 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.autoCalculatedIntermediateNormalsCount == s2.autoCalculatedIntermediateNormalsCount ); 357 | EditorGUI.BeginChangeCheck(); 358 | int autoCalculatedIntermediateNormalsCount = EditorGUILayout.IntField( AUTO_CALCULATED_INTERMEDIATE_NORMALS_TEXT, splines[0].autoCalculatedIntermediateNormalsCount ); 359 | if( EditorGUI.EndChangeCheck() ) 360 | { 361 | for( int i = 0; i < splines.Length; i++ ) 362 | { 363 | Undo.RecordObject( splines[i], "Change Intermediate Normals Count" ); 364 | splines[i].autoCalculatedIntermediateNormalsCount = autoCalculatedIntermediateNormalsCount; 365 | SetSplineDirtyWithUndo( splines[i], "Change Intermediate Normals Count", InternalDirtyFlags.NormalOffsetChange ); 366 | } 367 | 368 | SceneView.RepaintAll(); 369 | } 370 | 371 | EditorGUILayout.Space(); 372 | 373 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.evenlySpacedPointsResolution == s2.evenlySpacedPointsResolution ); 374 | EditorGUI.BeginChangeCheck(); 375 | float evenlySpacedPointsResolution = EditorGUILayout.FloatField( EVENLY_SPACED_POINTS_RESOLUTION_TEXT, splines[0].evenlySpacedPointsResolution ); 376 | if( EditorGUI.EndChangeCheck() ) 377 | { 378 | for( int i = 0; i < splines.Length; i++ ) 379 | { 380 | Undo.RecordObject( splines[i], "Change Evenly Spaced Points Resolution" ); 381 | splines[i].evenlySpacedPointsResolution = evenlySpacedPointsResolution; 382 | SetSplineDirtyWithUndo( splines[i], "Change Evenly Spaced Points Resolution", InternalDirtyFlags.All ); 383 | } 384 | 385 | SceneView.RepaintAll(); 386 | } 387 | 388 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.evenlySpacedPointsAccuracy == s2.evenlySpacedPointsAccuracy ); 389 | EditorGUI.BeginChangeCheck(); 390 | float evenlySpacedPointsAccuracy = EditorGUILayout.FloatField( EVENLY_SPACED_POINTS_ACCURACY_TEXT, splines[0].evenlySpacedPointsAccuracy ); 391 | if( EditorGUI.EndChangeCheck() ) 392 | { 393 | for( int i = 0; i < splines.Length; i++ ) 394 | { 395 | Undo.RecordObject( splines[i], "Change Evenly Spaced Points Accuracy" ); 396 | splines[i].evenlySpacedPointsAccuracy = evenlySpacedPointsAccuracy; 397 | SetSplineDirtyWithUndo( splines[i], "Change Evenly Spaced Points Accuracy", InternalDirtyFlags.All ); 398 | } 399 | 400 | SceneView.RepaintAll(); 401 | } 402 | 403 | EditorGUI.showMixedValue = HasMultipleDifferentValues( splines, ( s1, s2 ) => s1.pointCacheResolution == s2.pointCacheResolution ); 404 | EditorGUI.BeginChangeCheck(); 405 | int pointCacheResolution = EditorGUILayout.IntField( POINT_CACHE_RESOLUTION_TEXT, splines[0].pointCacheResolution ); 406 | if( EditorGUI.EndChangeCheck() ) 407 | { 408 | for( int i = 0; i < splines.Length; i++ ) 409 | { 410 | Undo.RecordObject( splines[i], "Change Point Cache Resolution" ); 411 | splines[i].pointCacheResolution = pointCacheResolution; 412 | SetSplineDirtyWithUndo( splines[i], "Change Point Cache Resolution", InternalDirtyFlags.All ); 413 | } 414 | 415 | SceneView.RepaintAll(); 416 | } 417 | 418 | EditorGUI.showMixedValue = false; 419 | 420 | EditorGUI.BeginChangeCheck(); 421 | bool showEvenlySpacedPoints = EditorGUILayout.Toggle( "Visualize Evenly Spaced Points", BezierSettings.ShowEvenlySpacedPoints ); 422 | if( EditorGUI.EndChangeCheck() ) 423 | { 424 | BezierSettings.ShowEvenlySpacedPoints = showEvenlySpacedPoints; 425 | SceneView.RepaintAll(); 426 | } 427 | 428 | if( showEvenlySpacedPoints ) 429 | { 430 | EditorGUI.indentLevel++; 431 | 432 | EditorGUI.BeginChangeCheck(); 433 | Color evenlySpacedPointsColor = EditorGUILayout.ColorField( "Color", BezierSettings.EvenlySpacedPointsColor ); 434 | if( EditorGUI.EndChangeCheck() ) 435 | { 436 | BezierSettings.EvenlySpacedPointsColor = evenlySpacedPointsColor; 437 | SceneView.RepaintAll(); 438 | } 439 | 440 | EditorGUI.BeginChangeCheck(); 441 | float evenlySpacedPointsSize = EditorGUILayout.FloatField( "Size", BezierSettings.EvenlySpacedPointsSize ); 442 | if( EditorGUI.EndChangeCheck() ) 443 | { 444 | BezierSettings.EvenlySpacedPointsSize = evenlySpacedPointsSize; 445 | SceneView.RepaintAll(); 446 | } 447 | 448 | EditorGUI.indentLevel--; 449 | } 450 | 451 | EditorGUILayout.Space(); 452 | 453 | GUI.backgroundColor = AUTO_CONSTRUCT_SPLINE_BUTTON_COLOR; 454 | ShowAutoConstructButton( splines, CONSTRUCT_LINEAR_PATH_TEXT, SplineAutoConstructMode.Linear ); 455 | ShowAutoConstructButton( splines, AUTO_CONSTRUCT_SPLINE_TEXT, SplineAutoConstructMode.Smooth1 ); 456 | ShowAutoConstructButton( splines, AUTO_CONSTRUCT_SPLINE_2_TEXT, SplineAutoConstructMode.Smooth2 ); 457 | 458 | GUILayout.BeginHorizontal(); 459 | if( GUILayout.Button( AUTO_CALCULATE_NORMALS_TEXT ) ) 460 | { 461 | for( int i = 0; i < splines.Length; i++ ) 462 | { 463 | BezierSpline spline = splines[i]; 464 | Undo.RecordObject( spline, "Auto Calculate Normals" ); 465 | 466 | try 467 | { 468 | spline.autoCalculateNormals = true; 469 | SetSplineDirtyWithUndo( spline, "Auto Calculate Normals", InternalDirtyFlags.NormalOffsetChange ); 470 | } 471 | finally 472 | { 473 | spline.autoCalculateNormals = false; 474 | } 475 | } 476 | 477 | SceneView.RepaintAll(); 478 | } 479 | 480 | EditorGUI.BeginChangeCheck(); 481 | bool autoCalculateNormalsEnabled = GUILayout.Toggle( Array.Find( splines, ( s ) => s.autoCalculateNormals ), AUTO_CONSTRUCT_ALWAYS_TEXT, GUI.skin.button, EditorGUIUtility.wideMode ? GL_WIDTH_100 : GL_WIDTH_60 ); 482 | if( EditorGUI.EndChangeCheck() ) 483 | { 484 | for( int i = 0; i < splines.Length; i++ ) 485 | { 486 | BezierSpline spline = splines[i]; 487 | Undo.RecordObject( spline, "Change Auto Calculate Normals" ); 488 | spline.autoCalculateNormals = autoCalculateNormalsEnabled; 489 | 490 | if( autoCalculateNormalsEnabled ) 491 | SetSplineDirtyWithUndo( spline, "Change Auto Calculate Normals", InternalDirtyFlags.NormalOffsetChange ); 492 | } 493 | 494 | SceneView.RepaintAll(); 495 | } 496 | GUILayout.EndHorizontal(); 497 | 498 | GUI.backgroundColor = c; 499 | 500 | EditorGUILayout.Space(); 501 | 502 | EditorGUI.BeginChangeCheck(); 503 | QuickEditSplineMode = GUILayout.Toggle( QuickEditSplineMode, QUICK_EDIT_MODE_TEXT, GUI.skin.button ); 504 | if( EditorGUI.EndChangeCheck() ) 505 | { 506 | EditorApplication.update -= SceneView.RepaintAll; 507 | 508 | if( QuickEditSplineMode ) 509 | { 510 | Tools.hidden = true; 511 | EditorApplication.update += SceneView.RepaintAll; 512 | } 513 | else if( BezierSplineEditor.ActiveEditor ) 514 | Tools.hidden = false; 515 | 516 | SceneView.RepaintAll(); 517 | } 518 | 519 | if( QuickEditSplineMode ) 520 | { 521 | EditorGUILayout.HelpBox( "- Dragging a point: moves the dragged point\n- CTRL+Left Click: adds a new point to the end of the spline\n- CTRL+Shift+Left Click: inserts a new point along the spline\n- Shift+Left Click: deletes clicked point", MessageType.Info ); 522 | 523 | EditorGUI.indentLevel++; 524 | 525 | BezierSettings.QuickEditPointPlacement = (QuickEditModePointPlacement) EditorGUILayout.EnumPopup( QUICK_EDIT_POINT_PLACEMENT_MODE_TEXT, BezierSettings.QuickEditPointPlacement ); 526 | 527 | if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && Array.Find( splines, ( s ) => !s.autoCalculateNormals ) ) 528 | { 529 | EditorGUI.indentLevel++; 530 | BezierSettings.QuickEditSplineModifyNormals = EditorGUILayout.Toggle( QUICK_EDIT_MODIFY_NORMALS_TEXT, BezierSettings.QuickEditSplineModifyNormals ); 531 | EditorGUI.indentLevel--; 532 | } 533 | 534 | if( Array.Find( splines, ( s ) => s.autoConstructMode == SplineAutoConstructMode.None ) ) 535 | BezierSettings.QuickEditSplinePreserveShape = EditorGUILayout.Toggle( QUICK_EDIT_PRESERVE_SPLINE_SHAPE_TEXT, BezierSettings.QuickEditSplinePreserveShape ); 536 | 537 | EditorGUI.indentLevel--; 538 | } 539 | } 540 | 541 | public static void DrawBezierPoint( BezierPoint point, int pointIndex, bool isSelected ) 542 | { 543 | Color c = Handles.color; 544 | Event e = Event.current; 545 | 546 | if( QuickEditSplineMode ) 547 | isSelected = false; 548 | 549 | Handles.color = isSelected ? BezierSettings.SelectedEndPointColor : BezierSettings.NormalEndPointColor; 550 | float size = isSelected ? BezierSettings.SelectedEndPointSize : BezierSettings.EndPointSize; 551 | 552 | if( QuickEditSplineMode ) 553 | { 554 | if( e.alt || e.control || e.command ) 555 | Handles.DotHandleCap( 0, point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size, EventType.Repaint ); 556 | else if( !e.shift ) 557 | { 558 | // Shift isn't held: move dragged points 559 | 560 | // Draw a ScaleValueHandle for the sole purpose of detecting drag input 561 | EditorGUI.BeginChangeCheck(); 562 | Handles.ScaleValueHandle( 1f, point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size * 6.5f, Handles.DotHandleCap, 1f ); 563 | if( EditorGUI.EndChangeCheck() ) 564 | { 565 | // Point is dragged, snap it to the scene geometry 566 | Vector3 sceneHitPoint, sceneHitNormal; 567 | if( RaycastAgainstScene( point.spline[point.spline.Count - 1], out sceneHitPoint, out sceneHitNormal ) ) 568 | { 569 | Undo.RecordObject( point.transform, "Move point" ); 570 | point.transform.position = sceneHitPoint; 571 | 572 | if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && BezierSettings.QuickEditSplineModifyNormals && !point.spline.autoCalculateNormals ) 573 | point.SetNormalAndResetIntermediateNormals( sceneHitNormal, "Move point" ); 574 | } 575 | } 576 | } 577 | else 578 | { 579 | // Shift is held: delete clicked points 580 | 581 | // Disallow deleting points from splines with only 2 or less points 582 | if( point.spline.Count <= 2 ) 583 | Handles.DotHandleCap( 0, point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size, EventType.Repaint ); 584 | else 585 | { 586 | Handles.color = BezierSettings.QuickEditModeDeleteEndPointColor; 587 | 588 | if( Handles.Button( point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size, size, Handles.DotHandleCap ) ) 589 | { 590 | // When the selected point is deleted, automatically select the next point so that there is still an active BezierPointEditor 591 | // to continue editing this spline 592 | Object[] selection = Selection.objects; 593 | int pointIndexInSelection = Array.IndexOf( selection, point.gameObject ); 594 | if( pointIndexInSelection >= 0 ) 595 | selection[pointIndexInSelection] = point.spline[( point.index + 1 ) % point.spline.Count].gameObject; 596 | 597 | Undo.DestroyObjectImmediate( point.gameObject ); 598 | 599 | if( pointIndexInSelection >= 0 ) 600 | Selection.objects = selection; 601 | } 602 | } 603 | } 604 | } 605 | else if( e.alt || e.button > 0 || ( isSelected && !e.control && !e.command ) ) 606 | Handles.DotHandleCap( 0, point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size, EventType.Repaint ); 607 | else if( Handles.Button( point.position, Quaternion.identity, HandleUtility.GetHandleSize( point.position ) * size, size, Handles.DotHandleCap ) ) 608 | { 609 | if( !e.shift && !e.control && !e.command ) 610 | Selection.activeTransform = point.transform; 611 | else 612 | { 613 | Object[] selection = Selection.objects; 614 | if( !isSelected ) 615 | { 616 | // If point's spline is included in current selection, remove the spline 617 | // from selection since its Scene handles interfere with points' scene handles 618 | bool splineIncludedInSelection = false; 619 | if( point.spline ) 620 | { 621 | for( int i = 0; i < selection.Length; i++ ) 622 | { 623 | if( selection[i] == point.spline || selection[i] == point.spline.transform || selection[i] == point.spline.gameObject ) 624 | { 625 | selection[i] = point.gameObject; 626 | splineIncludedInSelection = true; 627 | break; 628 | } 629 | } 630 | } 631 | 632 | if( !splineIncludedInSelection ) 633 | { 634 | Array.Resize( ref selection, selection.Length + 1 ); 635 | selection[selection.Length - 1] = point.gameObject; 636 | } 637 | } 638 | else 639 | { 640 | for( int i = 0; i < selection.Length; i++ ) 641 | { 642 | if( selection[i] == point || selection[i] == point.transform || selection[i] == point.gameObject ) 643 | { 644 | if( selection.Length == 1 ) 645 | { 646 | // When all points are deselected, select the spline automatically 647 | if( point.spline ) 648 | { 649 | selection[0] = point.spline.gameObject; 650 | break; 651 | } 652 | } 653 | 654 | for( int j = i + 1; j < selection.Length; j++ ) 655 | selection[j - 1] = selection[j]; 656 | 657 | Array.Resize( ref selection, selection.Length - 1 ); 658 | break; 659 | } 660 | } 661 | } 662 | 663 | Selection.objects = selection; 664 | } 665 | } 666 | 667 | Handles.color = c; 668 | 669 | if( BezierSettings.ShowControlPoints ) 670 | { 671 | Handles.DrawLine( point.position, point.precedingControlPointPosition ); 672 | Handles.DrawLine( point.position, point.followingControlPointPosition ); 673 | 674 | Handles.color = isSelected ? BezierSettings.SelectedControlPointColor : BezierSettings.NormalControlPointColor; 675 | 676 | Handles.RectangleHandleCap( 0, point.precedingControlPointPosition, SceneView.lastActiveSceneView.rotation, HandleUtility.GetHandleSize( point.precedingControlPointPosition ) * BezierSettings.ControlPointSize, EventType.Repaint ); 677 | Handles.RectangleHandleCap( 0, point.followingControlPointPosition, SceneView.lastActiveSceneView.rotation, HandleUtility.GetHandleSize( point.followingControlPointPosition ) * BezierSettings.ControlPointSize, EventType.Repaint ); 678 | 679 | Handles.color = c; 680 | } 681 | 682 | if( BezierSettings.ShowEndPointLabels ) 683 | Handles.Label( point.position, "Point" + pointIndex ); 684 | 685 | if( BezierSettings.ShowControlPoints && BezierSettings.ShowControlPointDirections ) 686 | { 687 | Handles.Label( point.precedingControlPointPosition, PRECEDING_CONTROL_POINT_LABEL ); 688 | Handles.Label( point.followingControlPointPosition, FOLLOWING_CONTROL_POINT_LABEL ); 689 | } 690 | 691 | if( BezierSettings.ShowNormals ) 692 | { 693 | Handles.color = BezierSettings.NormalsPreviewColor; 694 | Handles.DrawLine( point.position, point.position + point.normal * HandleUtility.GetHandleSize( point.position ) * BezierSettings.NormalsPreviewLength ); 695 | 696 | if( BezierSettings.DisplayedIntermediateNormalsCount > 0 && point.spline && point.nextPoint ) 697 | { 698 | BezierSpline.Segment segment = new BezierSpline.Segment( point, point.nextPoint, 0f ); 699 | float localTMultiplier = 1f / ( BezierSettings.DisplayedIntermediateNormalsCount + 1 ); 700 | for( int i = BezierSettings.DisplayedIntermediateNormalsCount; i > 0; i-- ) 701 | { 702 | float localT = i * localTMultiplier; 703 | Vector3 segmentPosition = segment.GetPoint( localT ); 704 | Handles.DrawLine( segmentPosition, segmentPosition + segment.GetNormal( localT ) * HandleUtility.GetHandleSize( segmentPosition ) * BezierSettings.NormalsPreviewLength * 0.75f ); 705 | } 706 | } 707 | 708 | Handles.color = c; 709 | } 710 | } 711 | 712 | public static void QuickEditModeSceneGUI( BezierSpline[] splines ) 713 | { 714 | Event e = Event.current; 715 | GUIContent QUICK_EDIT_MODE_TEXT = new GUIContent( "QUICK EDIT SPLINE MODE" ); 716 | 717 | GUIStyle style = "PreOverlayLabel"; // Taken from: https://github.com/Unity-Technologies/UnityCsReference/blob/f78f4093c8a2b45949a847cdc704cf209dcf2f36/Editor/Mono/EditorGUI.cs#L629 718 | Rect multiEditTipRect = new Rect( new Vector2( 0f, 5f ), style.CalcSize( QUICK_EDIT_MODE_TEXT ) ); 719 | multiEditTipRect.x = ( EditorGUIUtility.currentViewWidth - multiEditTipRect.width ) * 0.5f; // Center the text 720 | 721 | Handles.BeginGUI(); 722 | EditorGUI.DropShadowLabel( multiEditTipRect, QUICK_EDIT_MODE_TEXT, style ); 723 | Handles.EndGUI(); 724 | 725 | if( splines.Length == 0 || e.alt || ( !e.control && !e.command ) || GUIUtility.hotControl != 0 ) 726 | return; 727 | 728 | if( !e.shift ) 729 | { 730 | // Shift isn't held: add new point to the closest spline when LMB is pressed 731 | 732 | // Get a line that starts from Scene camera's position and goes at cursor's direction 733 | Ray ray = HandleUtility.GUIPointToWorldRay( e.mousePosition ); 734 | Vector3 lineStart = ray.origin; 735 | Vector3 lineEnd = lineStart + ray.direction * 2500f; 736 | 737 | // Find the spline end point closest to this line 738 | BezierPoint closestEndPoint = null; 739 | float closestEndPointDistance = float.PositiveInfinity; 740 | for( int i = 0; i < splines.Length; i++ ) 741 | { 742 | BezierPoint point = splines[i][splines[i].Count - 1]; 743 | float pointDistance = HandleUtility.DistancePointLine( point.position, lineStart, lineEnd ); 744 | if( pointDistance <= closestEndPointDistance ) 745 | { 746 | closestEndPoint = point; 747 | closestEndPointDistance = pointDistance; 748 | } 749 | } 750 | 751 | if( closestEndPoint ) 752 | { 753 | Vector3 sceneHitPoint, sceneHitNormal; 754 | if( RaycastAgainstScene( closestEndPoint, out sceneHitPoint, out sceneHitNormal ) ) 755 | { 756 | // Draw a line from the closest end point to the raycast hit point 757 | Color c = Handles.color; 758 | Handles.color = BezierSettings.QuickEditModeNewEndPointColor; 759 | Handles.DotHandleCap( 0, sceneHitPoint, Quaternion.identity, HandleUtility.GetHandleSize( sceneHitPoint ) * BezierSettings.QuickEditModeNewEndPointSize, EventType.Repaint ); 760 | Handles.DrawLine( sceneHitPoint, closestEndPoint.position ); 761 | Handles.color = c; 762 | 763 | // When left clicked, insert a point at the highlighted position 764 | if( e.type == EventType.MouseDown && e.button == 0 ) 765 | { 766 | BezierPoint newPoint = closestEndPoint.spline.InsertNewPointAt( closestEndPoint.spline.Count ); 767 | newPoint.position = sceneHitPoint; 768 | if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && BezierSettings.QuickEditSplineModifyNormals && !closestEndPoint.spline.autoCalculateNormals ) 769 | newPoint.SetNormalAndResetIntermediateNormals( sceneHitNormal, null ); 770 | 771 | // Rotate the previous point's followingControlPointPosition in the direction of the new point and assign the resulting vector 772 | // to the new point's followingControlPointPosition 773 | Vector3 directionToNewPoint = sceneHitPoint - closestEndPoint.position; 774 | Quaternion controlPointDeltaRotation = Quaternion.FromToRotation( closestEndPoint.followingControlPointPosition - closestEndPoint.position, directionToNewPoint ); 775 | newPoint.followingControlPointPosition = sceneHitPoint + controlPointDeltaRotation * ( directionToNewPoint * 0.35f ); 776 | 777 | Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Insert Point" ); 778 | if( newPoint.transform.parent ) 779 | Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Insert Point" ); 780 | 781 | e.Use(); 782 | } 783 | } 784 | } 785 | } 786 | else 787 | { 788 | // Shift is held: insert point to closest spline when LMB is pressed 789 | 790 | // Get a line that starts from Scene camera's position and goes at cursor's direction 791 | Ray ray = HandleUtility.GUIPointToWorldRay( e.mousePosition ); 792 | Vector3 lineStart = ray.origin; 793 | Vector3 lineEnd = lineStart + ray.direction * 2500f; 794 | 795 | // Find the spline point closest to this line 796 | BezierSpline closestSpline = null; 797 | Vector3 closestPointOnSpline = Vector3.zero; 798 | float closestPointDistance = float.PositiveInfinity; 799 | float closestPointNormalizedT = 0f; 800 | for( int i = 0; i < splines.Length; i++ ) 801 | { 802 | Vector3 pointOnLine; 803 | Vector3 pointOnSpline = splines[i].FindNearestPointToLine( lineStart, lineEnd, out pointOnLine, out closestPointNormalizedT ); 804 | float pointDistance = ( pointOnLine - pointOnSpline ).sqrMagnitude; 805 | if( pointDistance <= closestPointDistance ) 806 | { 807 | closestSpline = splines[i]; 808 | closestPointOnSpline = pointOnSpline; 809 | closestPointDistance = pointDistance; 810 | } 811 | } 812 | 813 | if( closestSpline ) 814 | { 815 | Color c = Handles.color; 816 | Handles.color = BezierSettings.QuickEditModeNewEndPointColor; 817 | Handles.DotHandleCap( 0, closestPointOnSpline, Quaternion.identity, HandleUtility.GetHandleSize( closestPointOnSpline ) * BezierSettings.QuickEditModeNewEndPointSize, EventType.Repaint ); 818 | Handles.color = c; 819 | 820 | // When left clicked, insert a point at the highlighted position 821 | if( e.type == EventType.MouseDown && e.button == 0 ) 822 | { 823 | BezierSpline.Segment segment = closestSpline.GetSegmentAt( closestPointNormalizedT ); 824 | bool preserveSplineShape = BezierSettings.QuickEditSplinePreserveShape && closestSpline.autoConstructMode == SplineAutoConstructMode.None; 825 | 826 | Vector3 position, precedingControlPointPosition, followingControlPointPosition; 827 | BezierPointEditor.CalculateInsertedPointPosition( segment.point1, segment.point2, segment.localT, preserveSplineShape, out position, out precedingControlPointPosition, out followingControlPointPosition ); 828 | 829 | BezierPoint newPoint = closestSpline.InsertNewPointAt( segment.point2.index ); 830 | newPoint.position = position; 831 | if( preserveSplineShape ) 832 | { 833 | newPoint.handleMode = BezierPoint.HandleMode.Aligned; 834 | newPoint.precedingControlPointPosition = precedingControlPointPosition; 835 | newPoint.followingControlPointPosition = followingControlPointPosition; 836 | } 837 | else 838 | { 839 | Vector3 precedingDirection = precedingControlPointPosition - position; 840 | Vector3 followingDirection = followingControlPointPosition - position; 841 | newPoint.followingControlPointPosition = position + followingDirection.normalized * Mathf.Min( precedingDirection.magnitude, followingDirection.magnitude ); 842 | } 843 | 844 | Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Insert Point" ); 845 | if( newPoint.transform.parent ) 846 | Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Insert Point" ); 847 | 848 | e.Use(); 849 | } 850 | } 851 | } 852 | } 853 | 854 | private static void ShowAutoConstructButton( BezierSpline[] splines, GUIContent label, SplineAutoConstructMode mode ) 855 | { 856 | GUILayout.BeginHorizontal(); 857 | if( GUILayout.Button( label ) ) 858 | { 859 | for( int i = 0; i < splines.Length; i++ ) 860 | { 861 | BezierSpline spline = splines[i]; 862 | Undo.RecordObject( spline, label.text ); 863 | 864 | try 865 | { 866 | spline.autoConstructMode = mode; 867 | SetSplineDirtyWithUndo( spline, label.text, InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange ); 868 | } 869 | finally 870 | { 871 | spline.autoConstructMode = SplineAutoConstructMode.None; 872 | } 873 | } 874 | 875 | SceneView.RepaintAll(); 876 | } 877 | 878 | EditorGUI.BeginChangeCheck(); 879 | bool autoConstructEnabled = GUILayout.Toggle( Array.Find( splines, ( s ) => s.autoConstructMode == mode ), AUTO_CONSTRUCT_ALWAYS_TEXT, GUI.skin.button, EditorGUIUtility.wideMode ? GL_WIDTH_100 : GL_WIDTH_60 ); 880 | if( EditorGUI.EndChangeCheck() ) 881 | { 882 | for( int i = 0; i < splines.Length; i++ ) 883 | { 884 | BezierSpline spline = splines[i]; 885 | Undo.RecordObject( spline, "Change Autoconstruct Mode" ); 886 | 887 | if( autoConstructEnabled ) 888 | { 889 | spline.autoConstructMode = mode; 890 | SetSplineDirtyWithUndo( spline, "Change Autoconstruct Mode", InternalDirtyFlags.EndPointTransformChange | InternalDirtyFlags.ControlPointPositionChange ); 891 | } 892 | else 893 | spline.autoConstructMode = SplineAutoConstructMode.None; 894 | } 895 | 896 | SceneView.RepaintAll(); 897 | } 898 | GUILayout.EndHorizontal(); 899 | } 900 | 901 | internal static void SetSplineDirtyWithUndo( BezierSpline spline, string undo, InternalDirtyFlags dirtyFlags ) 902 | { 903 | if( spline.autoCalculateNormals || spline.autoConstructMode != SplineAutoConstructMode.None ) 904 | { 905 | for( int i = 0; i < spline.Count; i++ ) 906 | { 907 | Undo.RecordObject( spline[i], undo ); 908 | Undo.RecordObject( spline[i].transform, undo ); 909 | } 910 | } 911 | 912 | spline.dirtyFlags |= dirtyFlags; 913 | spline.CheckDirty(); 914 | } 915 | 916 | private static bool RaycastAgainstScene( BezierPoint referencePoint, out Vector3 position, out Vector3 normal ) 917 | { 918 | EventType eventType = Event.current.type; 919 | Ray ray = HandleUtility.GUIPointToWorldRay( Event.current.mousePosition ); 920 | 921 | if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry ) 922 | { 923 | // First, try raycasting against scene geometry with or without colliders (it doesn't matter) 924 | // Credit: https://forum.unity.com/threads/editor-raycast-against-scene-meshes-without-collider-editor-select-object-using-gui-coordinate.485502 925 | if( intersectRayMeshMethod != null && eventType != EventType.Layout && eventType != EventType.Repaint ) // HandleUtility.PickGameObject doesn't work with Layout and Repaint events in OnSceneGUI 926 | { 927 | GameObject gameObjectUnderCursor = HandleUtility.PickGameObject( Event.current.mousePosition, false ); 928 | if( gameObjectUnderCursor ) 929 | { 930 | Mesh meshUnderCursor = null; 931 | MeshFilter meshFilter = gameObjectUnderCursor.GetComponent(); 932 | if( meshFilter ) 933 | meshUnderCursor = meshFilter.sharedMesh; 934 | 935 | if( !meshUnderCursor ) 936 | { 937 | SkinnedMeshRenderer skinnedMeshRenderer = gameObjectUnderCursor.GetComponent(); 938 | if( skinnedMeshRenderer ) 939 | meshUnderCursor = skinnedMeshRenderer.sharedMesh; 940 | } 941 | 942 | if( meshUnderCursor ) 943 | { 944 | object[] rayMeshParameters = new object[] { ray, meshUnderCursor, gameObjectUnderCursor.transform.localToWorldMatrix, null }; 945 | if( (bool) intersectRayMeshMethod.Invoke( null, rayMeshParameters ) ) 946 | { 947 | RaycastHit hit = (RaycastHit) rayMeshParameters[3]; 948 | position = hit.point; 949 | normal = hit.normal.normalized; 950 | 951 | return true; 952 | } 953 | } 954 | } 955 | } 956 | 957 | // Raycast against scene geometry with colliders 958 | object raycastResult = HandleUtility.RaySnap( ray ); 959 | if( raycastResult != null && raycastResult is RaycastHit ) 960 | { 961 | position = ( (RaycastHit) raycastResult ).point; 962 | normal = ( (RaycastHit) raycastResult ).normal.normalized; 963 | 964 | return true; 965 | } 966 | } 967 | 968 | // Raycast against a plane that goes through referencePoint 969 | if( referencePoint ) 970 | { 971 | Vector3 planeNormal; 972 | switch( BezierSettings.QuickEditPointPlacement ) 973 | { 974 | case QuickEditModePointPlacement.SceneGeometry: planeNormal = ( referencePoint.normal != Vector3.zero ) ? referencePoint.normal : Vector3.up; break; 975 | case QuickEditModePointPlacement.CameraPlane: planeNormal = SceneView.lastActiveSceneView.camera.transform.forward; break; 976 | case QuickEditModePointPlacement.XY: planeNormal = Vector3.forward; break; 977 | case QuickEditModePointPlacement.XZ: planeNormal = Vector3.up; break; 978 | case QuickEditModePointPlacement.YZ: planeNormal = Vector3.right; break; 979 | default: planeNormal = Vector3.up; break; 980 | } 981 | 982 | Plane plane = new Plane( planeNormal, referencePoint.position ); 983 | float enter; 984 | if( plane.Raycast( ray, out enter ) ) 985 | { 986 | position = ray.GetPoint( enter ); 987 | normal = referencePoint.normal; 988 | 989 | return true; 990 | } 991 | } 992 | 993 | position = ray.GetPoint( 5f ); 994 | normal = Vector3.up; 995 | 996 | return false; 997 | } 998 | 999 | public static void DrawSeparator() 1000 | { 1001 | GUILayout.Box( "", GUILayout.Height( 2f ), GUILayout.ExpandWidth( true ) ); 1002 | } 1003 | 1004 | private static void DrawBezier( BezierPoint endPoint0, BezierPoint endPoint1 ) 1005 | { 1006 | Handles.DrawBezier( endPoint0.position, endPoint1.position, 1007 | endPoint0.followingControlPointPosition, 1008 | endPoint1.precedingControlPointPosition, 1009 | BezierSettings.SelectedSplineColor, null, BezierSettings.SplineThickness ); 1010 | } 1011 | 1012 | private static bool HasMultipleDifferentValues( BezierSpline[] splines, Func comparer ) 1013 | { 1014 | if( splines.Length <= 1 ) 1015 | return false; 1016 | 1017 | for( int i = 1; i < splines.Length; i++ ) 1018 | { 1019 | if( !comparer( splines[0], splines[i] ) ) 1020 | return true; 1021 | } 1022 | 1023 | return false; 1024 | } 1025 | } 1026 | } --------------------------------------------------------------------------------