├── .gitignore ├── Editor.meta ├── Editor ├── TrackGeneratorEditor.cs ├── TrackGeneratorEditor.cs.meta ├── rob1997.track-generator.editor.asmdef └── rob1997.track-generator.editor.asmdef.meta ├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── Materials.meta ├── Materials │ ├── Road.mat │ └── Road.mat.meta ├── Scripts.meta ├── Scripts │ ├── Drawer.cs │ ├── Drawer.cs.meta │ ├── Track.meta │ ├── Track │ │ ├── CalculateTrianglesJob.cs │ │ ├── CalculateTrianglesJob.cs.meta │ │ ├── CalculateUVsAndVerticesJob.cs │ │ ├── CalculateUVsAndVerticesJob.cs.meta │ │ ├── EdgeSegmentsJob.cs │ │ ├── EdgeSegmentsJob.cs.meta │ │ ├── Path.cs │ │ ├── Path.cs.meta │ │ ├── RandomPath.cs │ │ ├── RandomPath.cs.meta │ │ ├── RandomTrackGenerator.cs │ │ ├── RandomTrackGenerator.cs.meta │ │ ├── RectPath.cs │ │ ├── RectPath.cs.meta │ │ ├── RectTrackGenerator.cs │ │ ├── RectTrackGenerator.cs.meta │ │ ├── TrackGenerator.cs │ │ ├── TrackGenerator.cs.meta │ │ ├── Utils.cs │ │ └── Utils.cs.meta │ ├── Voronoi.meta │ └── Voronoi │ │ ├── BisectorSegment.cs │ │ ├── BisectorSegment.cs.meta │ │ ├── Cell.cs │ │ ├── Cell.cs.meta │ │ ├── CellJob.cs │ │ ├── CellJob.cs.meta │ │ ├── GetRandomCenterJob.cs │ │ ├── GetRandomCenterJob.cs.meta │ │ ├── Intersection.cs │ │ ├── Intersection.cs.meta │ │ ├── ProjectAndTranslateCenterJob.cs │ │ ├── ProjectAndTranslateCenterJob.cs.meta │ │ ├── ProjectAndTranslateSegmentsJob.cs │ │ ├── ProjectAndTranslateSegmentsJob.cs.meta │ │ ├── README.md │ │ ├── README.md.meta │ │ ├── Rect.cs │ │ ├── Rect.cs.meta │ │ ├── Segment.cs │ │ ├── Segment.cs.meta │ │ ├── Utils.cs │ │ ├── Utils.cs.meta │ │ ├── VoronoiPlane.cs │ │ └── VoronoiPlane.cs.meta ├── Textures.meta ├── Textures │ ├── road.png │ └── road.png.meta ├── rob1997.track-generator.runtime.asmdef └── rob1997.track-generator.runtime.asmdef.meta ├── package.json ├── package.json.meta ├── ~docs.meta └── ~docs ├── spline_0.png ├── spline_0.png.meta ├── spline_1.png ├── spline_1.png.meta ├── spline_2.png ├── spline_2.png.meta ├── spline_3.png ├── spline_3.png.meta ├── spline_4.png ├── spline_4.png.meta ├── voronoi_1.png ├── voronoi_1.png.meta ├── voronoi_2.gif └── voronoi_2.gif.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* 73 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2914184c08f84d543b8cc87386b1b1f8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/TrackGeneratorEditor.cs: -------------------------------------------------------------------------------- 1 | using Track; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Editor 6 | { 7 | [CustomEditor(typeof(TrackGenerator), true)] 8 | public class TrackGeneratorEditor : UnityEditor.Editor 9 | { 10 | private TrackGenerator _trackGenerator; 11 | 12 | public override void OnInspectorGUI() 13 | { 14 | base.OnInspectorGUI(); 15 | 16 | _trackGenerator = (TrackGenerator) target; 17 | 18 | if (GUILayout.Button(new GUIContent("Generate", "Generate track"))) 19 | { 20 | _trackGenerator.Generate(); 21 | } 22 | 23 | if (GUILayout.Button(new GUIContent("Generate Mesh", "Generate the track mesh"))) 24 | { 25 | _trackGenerator.GenerateMesh(); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Editor/TrackGeneratorEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a01bc5e5752448bbbffc1ec0bd80635 3 | timeCreated: 1733655485 -------------------------------------------------------------------------------- /Editor/rob1997.track-generator.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rob1997.track-generator.editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:6b8a2c0415858014b94e09eef3e3fc15" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": true, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/rob1997.track-generator.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7855fc1154d13d74c9cc0f6cd3e24387 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Robel Getnet Geremew 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 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d990fa3dc0c00848a73ca66aef1533d 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Track Generator 2 | [![GitHub release](https://img.shields.io/github/v/release/rob1997/TrackGenerator?include_prereleases)](https://github.com/rob1997/TrackGenerator/releases) 3 | [![Made with Unity](https://img.shields.io/badge/Made%20with-Unity-57b9d3.svg?style=flat&logo=unity)](https://unity3d.com) 4 | [![GitHub license](https://img.shields.io/github/license/rob1997/TrackGenerator)](https://opensource.org/licenses/MIT) 5 | 6 | A package that Procedurally Generates Closed Tracks from Voronoi Diagrams using C# Jobs System, Splines and Procedural Mesh Generation. 7 | 8 | This package also contains a Voronoi Diagram implementation that can be found [here](https://github.com/rob1997/TrackGenerator/tree/main/Runtime/Scripts/Voronoi#voronoi). 9 | 10 | Unity Version: `2022.3.44f1` 11 | 12 | ![spline_4](./~docs/spline_4.png) 13 | 14 | ## Performance 15 | 16 | | Track Size/Cells | Performance in ms | 17 | |------------------|-------------------| 18 | | 10 | 2.5 - 3 | 19 | | 25 | 8 - 8.7 | 20 | | 50 | 21 - 23 | 21 | 22 | Complexity = 1 23 | Smoothness = 500 24 | 25 | ## Installation 26 | You can install the package via UPM (Unity Package Manager) 27 | - Open the Unity Package Manager from `Window > Package Manager` and [Import Package from Git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html). 28 | - Input the following URL: https://github.com/rob1997/TrackGenerator.git 29 | 30 | ## Configuration 31 | 32 | Below are properties of Track Generator that you can configure to generate different types of tracks. 33 | 34 | | **Name** | **Type** | **Description** | 35 | |------------|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| 36 | | Resolution | `int` | Number of triangles that'll be used for mesh generation, the higher the value the smoother the generated mesh. | 37 | | Width | `float` | Width of the generated mesh. | 38 | | Tiling | `Range[0f, 1f] float` | The tiling of the generated mesh relative to the distance of the generated Spline. A value of 1 will tile the texture once per unit of distance. | 39 | | Scale | `float` | Scale of the generated track. | 40 | | Complexity | `Rangle[0f, 1f] float` | The lower the complexity the fewer sides/segments the track will have. | 41 | 42 | There are two types of Track Generators available in the package: 43 | 44 | ### 1. Random Track Generator 45 | 46 | Generates a random track based on size (area). 47 | 48 | | **Name** | **Type** | **Description** | 49 | |----------|------------------------|-----------------------------------------------------------------------------------------------------| 50 | | Size | `int` | Number of voronoi cells used to generate a random track. The higher the value the bigger the track. | 51 | 52 | ### 2. Rect Track Generator 53 | 54 | Generates a rectangular track based on size (width, height). 55 | 56 | | **Name** | **Type** | **Description** | 57 | |----------|--------------|----------------------------------------------------------------------------------------------| 58 | | Size | `Vector2Int` | Determines the dimensions of the track, corresponding to width (Size.x) and height (Size.Y). | 59 | 60 | ## Setup 61 | 62 | - Attach the `RandomTrackGenerator` or `RectTrackGenerator` `MonoBehaviour` to a `GameObject` in the Scene. This will automatically add `SplineContainer`, `MeshRenderer` and `MeshFilter` components to the `GameObject`. 63 | 64 | 65 | - Assign a Material of your choice to the `MeshRenderer` component. You can find a default road material in the Materials folder in the package. 66 | 67 | ## Usage 68 | 69 | First declare a serialized implementation of `TrackGenerator` 70 | 71 | ```csharp 72 | [field: SerializeField] public RandomTrackGenerator TrackGenerator { get; private set; } 73 | ``` 74 | 75 | or 76 | 77 | ```csharp 78 | [field: SerializeField] public RectTrackGenerator TrackGenerator { get; private set; } 79 | ``` 80 | 81 | then generate track 82 | 83 | ```csharp 84 | private void Update() 85 | { 86 | // Generate a Random Track on Space Key Press 87 | if (Input.GetKeyDown(KeyCode.Space)) 88 | { 89 | TrackGenerator.Generate(); 90 | } 91 | } 92 | ``` 93 | The `TrackGenerator.Generate()` method generates a random track based on the `transform` of the `GameObject`. 94 | 95 | You can also alternatively use the Generate Button in the Inspector. 96 | 97 | ![spline_0](./~docs/spline_0.png) 98 | 99 | You can use the Spline Editor tool to edit any generated Track Spline in the Scene View. Once you're done editing the Spline you can press the `Generate Mesh` button to generate a new mesh based on the edited Spline. 100 | 101 | ![spline_1](./~docs/spline_1.png) 102 | 103 | ![spline_2](./~docs/spline_2.png) 104 | 105 | ![spline_3](./~docs/spline_3.png) 106 | 107 | You can get the generated Vertices and Spline via `TrackGenerator.Spline` and `TrackGenerator.Vertices`. 108 | 109 | ## How it Works 110 | 111 | If you would like to know how it works, I've a dev-log entry on it [here](https://rob1997.github.io/devlog/log-2.html) 112 | 113 | ## Contributing 114 | 115 | If you'd like to contribute to the project, you can fork the repository and create a pull request. You can also create an issue if you find any bugs or have any feature requests. 116 | 117 | --- 118 | 119 | ##### **⚠️Caution⚠️** 120 | 121 | As you go higher up in track size significantly you'll start to get a specific exception `Next segment not unique` more and more frequently which happens due to floating point precision. In cases where you need a significantly large track perhaps consider increasing scale instead. More on this [here](Runtime/Scripts/Voronoi/README.md#caution). 122 | 123 | --- -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b378a06c51407a34d9fb1af33b01f2db 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d309db3435c22848917c38bd7961202 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07166caec4f3384478e1be12696c0db4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Materials/Road.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 8 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: Road 11 | m_Shader: {fileID: 10752, guid: 0000000000000000f000000000000000, type: 0} 12 | m_Parent: {fileID: 0} 13 | m_ModifiedSerializedProperties: 0 14 | m_ValidKeywords: [] 15 | m_InvalidKeywords: [] 16 | m_LightmapFlags: 4 17 | m_EnableInstancingVariants: 0 18 | m_DoubleSidedGI: 0 19 | m_CustomRenderQueue: -1 20 | stringTagMap: {} 21 | disabledShaderPasses: [] 22 | m_LockedProperties: 23 | m_SavedProperties: 24 | serializedVersion: 3 25 | m_TexEnvs: 26 | - _BumpMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailAlbedoMap: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailMask: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _DetailNormalMap: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _EmissionMap: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MainTex: 47 | m_Texture: {fileID: 2800000, guid: 36542cd599c6d6a42b55565e66016300, type: 3} 48 | m_Scale: {x: 1, y: 131.1625} 49 | m_Offset: {x: 0, y: 0} 50 | - _MetallicGlossMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _OcclusionMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | - _ParallaxMap: 59 | m_Texture: {fileID: 0} 60 | m_Scale: {x: 1, y: 1} 61 | m_Offset: {x: 0, y: 0} 62 | m_Ints: [] 63 | m_Floats: 64 | - _BumpScale: 1 65 | - _Cutoff: 0.5 66 | - _DetailNormalMapScale: 1 67 | - _DstBlend: 0 68 | - _GlossMapScale: 1 69 | - _Glossiness: 0.5 70 | - _GlossyReflections: 1 71 | - _Metallic: 0 72 | - _Mode: 0 73 | - _OcclusionStrength: 1 74 | - _Parallax: 0.02 75 | - _SmoothnessTextureChannel: 0 76 | - _SpecularHighlights: 1 77 | - _SrcBlend: 1 78 | - _UVSec: 0 79 | - _ZWrite: 1 80 | m_Colors: 81 | - _Color: {r: 1, g: 1, b: 1, a: 1} 82 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 83 | m_BuildTextureStacks: [] 84 | m_AllowLocking: 1 85 | -------------------------------------------------------------------------------- /Runtime/Materials/Road.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 041c1f23e69bbd147b8378589c91a03a 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4a76440f97c8e40458a36346465c6718 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Scripts/Drawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | public delegate void DrawDelegate(); 5 | 6 | public class Drawer : MonoBehaviour 7 | { 8 | public static Drawer Instance { get; private set; } 9 | 10 | private void Awake() 11 | { 12 | if (Instance != null) 13 | { 14 | throw new Exception($"Another instance of {nameof(Drawer)} already exists."); 15 | } 16 | 17 | Instance = this; 18 | } 19 | 20 | public event DrawDelegate OnDraw; 21 | 22 | public event DrawDelegate OnDrawEditMode; 23 | 24 | private void OnDrawGizmos() 25 | { 26 | if (Application.isPlaying) 27 | { 28 | OnDraw?.Invoke(); 29 | } 30 | 31 | OnDrawEditMode?.Invoke(); 32 | } 33 | 34 | private void OnDestroy() 35 | { 36 | RemoveListeners(OnDraw); 37 | 38 | RemoveListeners(OnDrawEditMode); 39 | } 40 | 41 | private void RemoveListeners(DrawDelegate drawEvent) 42 | { 43 | if (drawEvent != null) 44 | { 45 | foreach (Delegate drawDelegate in drawEvent.GetInvocationList()) 46 | { 47 | drawEvent -= (DrawDelegate) drawDelegate; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Runtime/Scripts/Drawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdaf196132985014e9decb995a32e41e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Track.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39c06e0567014008aeb412a737df555e 3 | timeCreated: 1729414193 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/CalculateTrianglesJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | 5 | namespace Track 6 | { 7 | [BurstCompile] 8 | public struct CalculateTrianglesJob : IJobParallelFor 9 | { 10 | public NativeArray Triangles; 11 | 12 | public int VerticesLength; 13 | 14 | public void Execute(int i) 15 | { 16 | int remainder = i % 6; 17 | 18 | int vIndex = i < 6 ? VerticesLength : (i - remainder) / 3; 19 | 20 | switch (remainder) 21 | { 22 | case 1: 23 | case 4: 24 | vIndex -= 1; 25 | break; 26 | case 2: 27 | vIndex -= 2; 28 | break; 29 | case 3: 30 | vIndex += 1; 31 | break; 32 | } 33 | 34 | vIndex %= VerticesLength; 35 | 36 | Triangles[i] = vIndex; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/CalculateTrianglesJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92088a5b968049a3aa922bac49c8ade0 3 | timeCreated: 1733514865 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/CalculateUVsAndVerticesJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Burst; 3 | using Unity.Collections; 4 | using Unity.Jobs; 5 | using Unity.Mathematics; 6 | using UnityEngine.Splines; 7 | 8 | namespace Track 9 | { 10 | [BurstCompile] 11 | public struct CalculateUVsAndVerticesJob : IJobParallelFor 12 | { 13 | public NativeArray Vertices; 14 | 15 | public NativeArray UVs; 16 | 17 | public int Length; 18 | 19 | public NativeSpline Spline; 20 | 21 | public float Width; 22 | 23 | public void Execute(int index) 24 | { 25 | bool even = index % 2 == 0; 26 | 27 | float t = index / (float) Length; 28 | 29 | if (Spline.Evaluate(t, out float3 position, out float3 tangent, out float3 up)) 30 | { 31 | float3 right = math.normalize(math.cross(tangent, up)) * Width; 32 | 33 | // Set Vertices 34 | Vertices[index] = position + (even ? right : - right); 35 | 36 | // Instead of 0 - 1 oscillation, we want 0 - 1 - 0 oscillation to avoid sudden change for the UVs at index 0 37 | t = 1 - math.abs(2 * t - 1); 38 | 39 | // Set UVs 40 | UVs[index] = even ? new float2(0, t) : new float2(1, t); 41 | } 42 | 43 | else 44 | { 45 | throw new Exception($"Failed to evaluate spline at {t}"); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/CalculateUVsAndVerticesJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36189655a489480883d2d1e6120b1a8d 3 | timeCreated: 1733503016 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/EdgeSegmentsJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Voronoi; 5 | 6 | namespace Track 7 | { 8 | [BurstCompile] 9 | public struct EdgeSegmentsJob : IJobParallelFor 10 | { 11 | [ReadOnly] 12 | public NativeList AllSegments; 13 | 14 | public NativeArray EdgeArray; 15 | 16 | public void Execute(int index) 17 | { 18 | for (int i = 0; i < AllSegments.Length; i++) 19 | { 20 | if (i == index) 21 | { 22 | continue; 23 | } 24 | 25 | Segment segment = AllSegments[index]; 26 | 27 | if (segment.SameAs(AllSegments[i])) 28 | { 29 | EdgeArray[index] = 0; 30 | 31 | return; 32 | } 33 | } 34 | 35 | EdgeArray[index] = 1; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/EdgeSegmentsJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e50d62be6af427294260c68d6d6b705 3 | timeCreated: 1733061759 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/Path.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Unity.Collections; 4 | using Unity.Jobs; 5 | using Unity.Mathematics; 6 | using UnityEngine; 7 | using Voronoi; 8 | 9 | namespace Track 10 | { 11 | [Serializable] 12 | public abstract class Path : IDisposable 13 | { 14 | public List Vertices { get; private set; } = new List(); 15 | 16 | private Transform _transform; 17 | 18 | public abstract float Scale { get; } 19 | 20 | // 0 - 1 range 21 | public abstract float NormalizedComplexity { get; } 22 | 23 | public void Generate(Transform transform) 24 | { 25 | _transform = transform; 26 | 27 | List cells = GetCells(transform); 28 | 29 | List segments = GetEdgeSegments(cells); 30 | 31 | GetVertices(segments); 32 | 33 | for (int i = 1; i < Vertices.Count; i++) 34 | { 35 | float3 current = Vertices[i]; 36 | 37 | float3 next = Vertices[(i + 1) % Vertices.Count]; 38 | 39 | float minDistance = Scale - (Scale * NormalizedComplexity); 40 | 41 | if (math.distance(current, next) < minDistance) 42 | { 43 | Vertices.RemoveAt(i); 44 | 45 | i--; 46 | } 47 | } 48 | 49 | #if UNITY_EDITOR 50 | if (!_drawing && Drawer.Instance != null) 51 | { 52 | Drawer.Instance.OnDraw += Draw; 53 | 54 | _drawing = true; 55 | } 56 | } 57 | 58 | private bool _drawing; 59 | 60 | #else 61 | } 62 | #endif 63 | 64 | public abstract List GetCells(Transform transform); 65 | 66 | private List GetEdgeSegments(List interior) 67 | { 68 | NativeList allSegments = new NativeList(Allocator.TempJob); 69 | 70 | foreach (Cell cell in interior) 71 | { 72 | NativeArray array = new NativeArray(cell.Segments, Allocator.Temp); 73 | 74 | allSegments.AddRange(array); 75 | 76 | array.Dispose(); 77 | } 78 | 79 | int length = allSegments.Length; 80 | 81 | NativeArray edgeArray = new NativeArray(length, Allocator.TempJob); 82 | 83 | new EdgeSegmentsJob { AllSegments = allSegments, EdgeArray = edgeArray }.Schedule(length, 16).Complete(); 84 | 85 | List segments = new List(); 86 | 87 | for (int i = 0; i < edgeArray.Length; i++) 88 | { 89 | if (edgeArray[i] == 1) 90 | { 91 | segments.Add(allSegments[i]); 92 | } 93 | } 94 | 95 | allSegments.Dispose(); 96 | 97 | edgeArray.Dispose(); 98 | 99 | return segments; 100 | } 101 | 102 | private void GetVertices(List segments) 103 | { 104 | Vertices.Clear(); 105 | 106 | int first = GetLeftMostSegment(segments); 107 | 108 | Vertices.AddRange(segments[first].Vertices()); 109 | 110 | List visited = new List{ first }; 111 | 112 | int last = GetClosestVertex(false, false); 113 | 114 | while (true) 115 | { 116 | int index = GetClosestVertex(); 117 | 118 | if (index == last) 119 | { 120 | break; 121 | } 122 | 123 | Vertices.Add(segments[index].End); 124 | 125 | visited.Add(index); 126 | } 127 | 128 | int GetClosestVertex(bool clockwise = true, bool visit = true) 129 | { 130 | float3 vertex = clockwise ? Vertices[^1] : Vertices[0]; 131 | 132 | float minimumDistance = float.MaxValue; 133 | 134 | int closest = - 1; 135 | 136 | for (int i = 0; i < segments.Count; i++) 137 | { 138 | if (visited.Contains(i)) 139 | { 140 | continue; 141 | } 142 | 143 | Segment segment = segments[i]; 144 | 145 | float distance = math.distance(vertex, clockwise ? segment.Start : segment.End); 146 | 147 | if (distance < minimumDistance) 148 | { 149 | minimumDistance = distance; 150 | 151 | closest = i; 152 | } 153 | } 154 | 155 | if (visit) 156 | { 157 | visited.Add(closest); 158 | } 159 | 160 | return closest; 161 | } 162 | } 163 | 164 | private int GetLeftMostSegment(List segments) 165 | { 166 | float minimum = float.MaxValue; 167 | 168 | int leftMost = - 1; 169 | 170 | float3 left = - _transform.right; 171 | 172 | for (int i = 0; i < segments.Count; i++) 173 | { 174 | Segment segment = segments[i]; 175 | 176 | foreach (float3 vertex in segment.Vertices()) 177 | { 178 | float3 direction = vertex - (float3) _transform.position; 179 | 180 | float dot = math.dot(left, direction); 181 | 182 | if (dot < minimum) 183 | { 184 | minimum = dot; 185 | 186 | leftMost = i; 187 | } 188 | } 189 | } 190 | 191 | return leftMost; 192 | } 193 | 194 | #region Gizmo 195 | 196 | [Space] [SerializeField] private bool drawGizmos = true; 197 | 198 | [SerializeField] private float centerRadius = .125f; 199 | 200 | private void Draw() 201 | { 202 | if (!drawGizmos) 203 | { 204 | return; 205 | } 206 | 207 | if (Vertices != null) 208 | { 209 | for (int i = 0; i < Vertices.Count; i++) 210 | { 211 | Gizmos.color = Color.green; 212 | 213 | Gizmos.DrawSphere(Vertices[i], centerRadius); 214 | 215 | Gizmos.color = Color.red; 216 | 217 | Gizmos.DrawLine(Vertices[i], Vertices[(i + 1) % Vertices.Count]); 218 | } 219 | } 220 | } 221 | 222 | #endregion 223 | 224 | public void Dispose() 225 | { 226 | #if UNITY_EDITOR 227 | if (_drawing && Drawer.Instance != null) 228 | { 229 | Drawer.Instance.OnDraw -= Draw; 230 | } 231 | #endif 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/Path.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ebf99fcba43c4ee795035130529a7742 3 | timeCreated: 1732395052 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RandomPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using Voronoi; 5 | using Random = UnityEngine.Random; 6 | 7 | namespace Track 8 | { 9 | [Serializable] 10 | public class RandomPath : Path 11 | { 12 | [SerializeField] private int cellCount = 10; 13 | 14 | [SerializeField] private float scale = 5; 15 | 16 | [SerializeField, Range(0f, 1f)] private float complexity = .5f; 17 | 18 | public override float Scale => scale; 19 | 20 | public override float NormalizedComplexity => complexity; 21 | 22 | public RandomPath(int cellCount, float scale, float complexity) 23 | { 24 | this.cellCount = cellCount; 25 | 26 | this.scale = scale; 27 | 28 | this.complexity = complexity; 29 | } 30 | 31 | public override List GetCells(Transform transform) 32 | { 33 | int size = (int) Math.Sqrt(cellCount); 34 | 35 | // 1 for the discarded floating points and 2 for the border cells 36 | size += 3; 37 | 38 | List cells = new List(); 39 | 40 | using (var plane = new VoronoiPlane(size, size, scale)) 41 | { 42 | plane.Generate(transform); 43 | 44 | for (int i = 0; i < plane.Cells.Length; i++) 45 | { 46 | Cell cell = plane.Cells[i]; 47 | 48 | if (IsInnerCell(i, size) && cell.Verify(transform.up)) 49 | { 50 | cells.Add(cell); 51 | } 52 | } 53 | } 54 | 55 | int index = cells.Count / 2; 56 | 57 | List indexed = new List(); 58 | 59 | List interior = new List(); 60 | 61 | HashSet adjacencyHashSet = new HashSet(); 62 | 63 | while (!AddCell()) 64 | { 65 | index++; 66 | } 67 | 68 | for (int i = 1; i < cellCount; i++) 69 | { 70 | while (!AddCell()) 71 | { 72 | index = GetFromHashSet(Random.Range(0, adjacencyHashSet.Count), adjacencyHashSet); 73 | } 74 | } 75 | 76 | return interior; 77 | 78 | bool AddCell() 79 | { 80 | index %= cells.Count; 81 | 82 | if (indexed.Contains(index)) 83 | { 84 | return false; 85 | } 86 | 87 | Cell cell = cells[index]; 88 | 89 | interior.Add(cell); 90 | 91 | indexed.Add(index); 92 | 93 | AddAdjacentCells(); 94 | 95 | if (adjacencyHashSet.Contains(index)) 96 | { 97 | adjacencyHashSet.Remove(index); 98 | } 99 | 100 | return true; 101 | } 102 | 103 | void AddAdjacentCells() 104 | { 105 | Cell cell = cells[index]; 106 | 107 | for (int i = 0; i < cells.Count; i++) 108 | { 109 | if (i == index || indexed.Contains(i)) 110 | { 111 | continue; 112 | } 113 | 114 | if (cell.IsAdjacentTo(cells[i])) 115 | { 116 | adjacencyHashSet.Add(i); 117 | } 118 | } 119 | } 120 | } 121 | 122 | private int GetFromHashSet(int index, HashSet adjacencyHashSet) 123 | { 124 | int count = 0; 125 | 126 | foreach (int value in adjacencyHashSet) 127 | { 128 | if (count == index) 129 | { 130 | return value; 131 | } 132 | 133 | count++; 134 | } 135 | 136 | throw new ArgumentOutOfRangeException(nameof(index)); 137 | } 138 | 139 | // Get cell coordinates then check if it's an inner cell 140 | private bool IsInnerCell(int index, int size) 141 | { 142 | int row = index / size; 143 | 144 | int column = index % size; 145 | 146 | return row > 0 && row < size - 1 && column > 0 && column < size - 1; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RandomPath.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d5cffd958968463397104b7a63d069e5 3 | timeCreated: 1732829098 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RandomTrackGenerator.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Track 4 | { 5 | public class RandomTrackGenerator : TrackGenerator 6 | { 7 | [field: SerializeField, Tooltip("The size of the track. The number of voronoi cells that'll be used to generate the track.")] 8 | public int Size { get; private set; } = 10; 9 | 10 | [field: SerializeField, Tooltip("The scale of the track.")] 11 | public float Scale { get; private set; } = 25; 12 | 13 | [field: SerializeField, Range(0f, 1f)] public float Complexity { get; private set; } = .5f; 14 | 15 | protected override Path GetPath() 16 | { 17 | return new RandomPath(Size, Scale, Complexity); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RandomTrackGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 439e5d7187b549138d61348ffda79078 3 | timeCreated: 1733655816 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RectPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Unity.Mathematics; 4 | using UnityEngine; 5 | using Voronoi; 6 | 7 | namespace Track 8 | { 9 | // TODO: re-implement with Job system 10 | [Serializable] 11 | public class RectPath : Path 12 | { 13 | [SerializeField] private int width = 3; 14 | 15 | [SerializeField] private int height = 3; 16 | 17 | [SerializeField] private float scale = 5f; 18 | 19 | [SerializeField, Range(0f, 1f)] private float complexity = .5f; 20 | 21 | public override float Scale => scale; 22 | 23 | public override float NormalizedComplexity => complexity; 24 | 25 | public RectPath(int width, int height, float scale, float complexity) 26 | { 27 | this.width = width; 28 | 29 | this.height = height; 30 | 31 | this.scale = scale; 32 | 33 | this.complexity = complexity; 34 | } 35 | 36 | public override List GetCells(Transform transform) 37 | { 38 | List interior = new List(); 39 | 40 | List exterior = new List(); 41 | 42 | using (VoronoiPlane plane = new VoronoiPlane(width + 2, height + 2, scale)) 43 | { 44 | plane.Generate(transform); 45 | 46 | Cell[] cells = plane.Cells; 47 | 48 | foreach (Cell cell in cells) 49 | { 50 | if (cell.Verify(transform.up)) 51 | { 52 | if (FilterCell(cell.Segments, cells)) 53 | { 54 | interior.Add(cell); 55 | } 56 | else 57 | { 58 | exterior.Add(cell); 59 | } 60 | } 61 | } 62 | } 63 | 64 | if (interior.Count == 0) 65 | { 66 | interior.Add(exterior[0]); 67 | 68 | exterior.RemoveAt(0); 69 | } 70 | 71 | List> clusters = GetClusters(interior); 72 | 73 | while (clusters.Count > 1) 74 | { 75 | clusters = MergeClusters(clusters, exterior); 76 | } 77 | 78 | return clusters[0]; 79 | } 80 | 81 | private bool FilterCell(Segment[] segments, Cell[] cells) 82 | { 83 | foreach (Segment segment in segments) 84 | { 85 | bool interior = false; 86 | 87 | float3 previous = Voronoi.Utils.Cross(cells[0].Center - segment.Start, segment.Direction).Normalize(); 88 | 89 | for (int i = 1; i < cells.Length; i++) 90 | { 91 | float3 next = Voronoi.Utils.Cross(cells[i].Center - segment.Start, segment.Direction) 92 | .Normalize(); 93 | 94 | if (!previous.Equals(next)) 95 | { 96 | interior = true; 97 | 98 | break; 99 | } 100 | 101 | previous = next; 102 | } 103 | 104 | if (!interior) 105 | { 106 | return false; 107 | } 108 | } 109 | 110 | return true; 111 | } 112 | 113 | private List> GetClusters(List interior) 114 | { 115 | List> groups = new List>(); 116 | 117 | HashSet visited = new HashSet(); 118 | 119 | void DepthFirstSearch(int index, List group) 120 | { 121 | Stack stack = new Stack(); 122 | 123 | stack.Push(index); 124 | 125 | while (stack.Count > 0) 126 | { 127 | int current = stack.Pop(); 128 | 129 | if (!visited.Add(current)) 130 | { 131 | continue; 132 | } 133 | 134 | group.Add(interior[current]); 135 | 136 | for (int i = 0; i < interior.Count; i++) 137 | { 138 | if (!visited.Contains(i) && interior[current].IsAdjacentTo(interior[i])) 139 | { 140 | stack.Push(i); 141 | } 142 | } 143 | } 144 | } 145 | 146 | for (int i = 0; i < interior.Count; i++) 147 | { 148 | if (!visited.Contains(i)) 149 | { 150 | List group = new List(); 151 | 152 | DepthFirstSearch(i, group); 153 | 154 | groups.Add(group); 155 | } 156 | } 157 | 158 | return groups; 159 | } 160 | 161 | private List> MergeClusters(List> clusters, List exterior) 162 | { 163 | List clusterA = new List(clusters[0]); 164 | 165 | List clusterB; 166 | 167 | float minimumDistance = float.MaxValue; 168 | 169 | int index = 0; 170 | 171 | for (var i = 1; i < clusters.Count; i++) 172 | { 173 | clusterB = clusters[i]; 174 | 175 | foreach (Cell cellA in clusterA) 176 | { 177 | foreach (Cell cellB in clusterB) 178 | { 179 | foreach (float3 vertexA in cellA.Vertices) 180 | { 181 | foreach (float3 vertexB in cellB.Vertices) 182 | { 183 | float distance = math.distance(vertexA, vertexB); 184 | 185 | if (distance < minimumDistance) 186 | { 187 | minimumDistance = distance; 188 | 189 | index = i; 190 | } 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | clusterB = clusters[index]; 198 | 199 | clusters.RemoveAt(index); 200 | 201 | while (true) 202 | { 203 | minimumDistance = float.MaxValue; 204 | 205 | index = - 1; 206 | 207 | for (int i = 0; i < exterior.Count; i++) 208 | { 209 | Cell cell = exterior[i]; 210 | 211 | foreach (Cell cellA in clusterA) 212 | { 213 | if (cell.IsAdjacentTo(cellA)) 214 | { 215 | foreach (Cell cellB in clusterB) 216 | { 217 | if (cell.IsAdjacentTo(cellB)) 218 | { 219 | clusterA.AddRange(clusterB); 220 | 221 | clusterA.Add(cell); 222 | 223 | clusters[0] = clusterA; 224 | 225 | return clusters; 226 | } 227 | 228 | foreach (float3 vertex in cell.Vertices) 229 | { 230 | foreach (float3 vertexB in cellB.Vertices) 231 | { 232 | float distance = math.distance(vertex, vertexB); 233 | 234 | if (distance < minimumDistance) 235 | { 236 | minimumDistance = distance; 237 | 238 | index = i; 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | } 246 | 247 | clusterA.Add(exterior[index]); 248 | 249 | exterior.RemoveAt(index); 250 | } 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RectPath.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51fab07ae28048a6946a37a3a1752d9a 3 | timeCreated: 1732828318 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RectTrackGenerator.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Track 4 | { 5 | public class RectTrackGenerator : TrackGenerator 6 | { 7 | [field: SerializeField, Tooltip("Size of the track. The number of cells that'll be used to generate the track. 10 x 10 = 100 cells.")] 8 | public Vector2Int Size { get; private set; } = new Vector2Int(3, 3); 9 | 10 | [field: SerializeField, Tooltip("The scale of the track.")] 11 | public float Scale { get; private set; } = 5f; 12 | 13 | [field: SerializeField, Range(0f, 1f)] public float Complexity { get; private set; } = .5f; 14 | 15 | protected override Path GetPath() 16 | { 17 | return new RectPath(Size.x, Size.y, Scale, Complexity); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Runtime/Scripts/Track/RectTrackGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 787e79c92a3b7f648acdf7ad379405c9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Track/TrackGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Unity.Collections; 4 | using Unity.Jobs; 5 | using Unity.Mathematics; 6 | using UnityEngine; 7 | using UnityEngine.Splines; 8 | 9 | namespace Track 10 | { 11 | [RequireComponent(typeof(SplineContainer), typeof(MeshFilter), typeof(MeshRenderer))] 12 | public abstract class TrackGenerator : MonoBehaviour 13 | { 14 | [field: SerializeField, Tooltip("The resolution of the track. The number of segments that'll be used to generate the track mesh.")] 15 | public int Resolution { get; private set; } = 10000; 16 | 17 | [field: SerializeField, Tooltip("The width of the track.")] 18 | public float Width { get; private set; } = 3f; 19 | 20 | [field: SerializeField, Range(0f, 1f), Tooltip("The tiling of the track's texture.")] 21 | public float Tiling { get; private set; } = 0.25f; 22 | 23 | private SplineContainer _splineContainer; 24 | 25 | private MeshFilter _meshFilter; 26 | 27 | private MeshRenderer _meshRenderer; 28 | 29 | public Spline Spline 30 | { 31 | get => _splineContainer.Spline; 32 | 33 | set => _splineContainer.Spline = value; 34 | } 35 | 36 | public List Vertices { get; private set; } = new List(); 37 | 38 | private void Awake() 39 | { 40 | _splineContainer = GetComponent(); 41 | 42 | _meshFilter = GetComponent(); 43 | 44 | _meshRenderer = GetComponent(); 45 | } 46 | 47 | public void Generate() 48 | { 49 | GenerateVertices(); 50 | 51 | GenerateSpline(); 52 | 53 | GenerateMesh(); 54 | } 55 | 56 | protected abstract Path GetPath(); 57 | 58 | public void GenerateVertices() 59 | { 60 | using (Path path = GetPath()) 61 | { 62 | path.Generate(transform); 63 | 64 | Vertices = path.Vertices; 65 | } 66 | } 67 | 68 | public void GenerateSpline() 69 | { 70 | Spline.Clear(); 71 | 72 | foreach (float3 vertex in Vertices) 73 | { 74 | Spline.Add(transform.InverseTransformPoint(vertex)); 75 | } 76 | 77 | Spline.Closed = true; 78 | } 79 | 80 | public void GenerateMesh() 81 | { 82 | int vLength = (Resolution + 2) * 2; 83 | 84 | NativeArray vertices = new NativeArray(vLength, Allocator.TempJob); 85 | 86 | NativeArray uvs = new NativeArray(vLength, Allocator.TempJob); 87 | 88 | NativeArray triangles = new NativeArray(vLength * 3, Allocator.TempJob); 89 | 90 | NativeSpline spline = new NativeSpline(Spline, Allocator.TempJob); 91 | 92 | new CalculateUVsAndVerticesJob 93 | { 94 | Vertices = vertices, 95 | 96 | UVs = uvs, 97 | 98 | Length = vLength, 99 | 100 | Spline = spline, 101 | 102 | Width = Width 103 | }.Schedule(vLength, 8).Complete(); 104 | 105 | new CalculateTrianglesJob() 106 | { 107 | Triangles = triangles, 108 | 109 | VerticesLength = vLength 110 | }.Schedule(triangles.Length, 8).Complete(); 111 | 112 | Spline = new Spline(spline, true); 113 | 114 | // Set Mesh 115 | _meshFilter.mesh.Clear(); 116 | 117 | _meshFilter.mesh.vertices = Array.ConvertAll(vertices.ToArray(), v => (Vector3) v); 118 | 119 | _meshFilter.mesh.uv = Array.ConvertAll(uvs.ToArray(), v => (Vector2) v); 120 | 121 | _meshFilter.mesh.triangles = triangles.ToArray(); 122 | 123 | // Tiling 124 | if (_meshRenderer.sharedMaterial != null) 125 | { 126 | _meshRenderer.sharedMaterial.mainTextureScale = new Vector2(1, Tiling * Spline.GetLength()); 127 | } 128 | 129 | _meshFilter.mesh.RecalculateNormals(); 130 | 131 | // Dispose 132 | vertices.Dispose(); 133 | 134 | uvs.Dispose(); 135 | 136 | triangles.Dispose(); 137 | 138 | spline.Dispose(); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/TrackGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 621fa2de2c1d45b7b63360073933ae86 3 | timeCreated: 1729414202 -------------------------------------------------------------------------------- /Runtime/Scripts/Track/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Unity.Mathematics; 3 | using Voronoi; 4 | 5 | namespace Track 6 | { 7 | public static class Utils 8 | { 9 | public const float Tolerance = 0.05f; 10 | 11 | public static bool Approximately(this float3 value, float3 other) 12 | { 13 | float tolerance = Tolerance; 14 | 15 | float3 delta = math.abs(value - other); 16 | 17 | return delta.x <= tolerance && delta.y <= tolerance && delta.z <= tolerance; 18 | } 19 | 20 | public static bool Approximately(this Segment segment, Segment other) 21 | { 22 | return segment.Start.Approximately(other.Start) && segment.End.Approximately(other.End); 23 | } 24 | 25 | public static Segment Reverse(this Segment segment) 26 | { 27 | return new Segment(segment.End, segment.Start); 28 | } 29 | 30 | public static bool SameAs(this Segment segment, Segment other) 31 | { 32 | return segment.Approximately(other) || segment.Approximately(other.Reverse()); 33 | } 34 | 35 | public static float3[] Vertices(this Segment segment) 36 | { 37 | return new []{ segment.Start, segment.End }; 38 | } 39 | 40 | public static bool IsAdjacentTo(this Cell cell, Cell other) 41 | { 42 | return cell.Segments.Any(s => other.Segments.Any(same => s.SameAs(same))); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Track/Utils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 07d5c40cc68d4767bc850de22ff3cf65 3 | timeCreated: 1730230501 -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67415e9b0025fa8419a0b06ad9d1756d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/BisectorSegment.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace Voronoi 4 | { 5 | /// 6 | /// A segment containing intersecting and adjacent segments. 7 | /// 8 | public struct BisectorSegment 9 | { 10 | public Intersection Start { get; private set; } 11 | 12 | public Intersection End { get; private set; } 13 | 14 | public Segment Segment { get; private set; } 15 | 16 | public BisectorSegment(Intersection start, Intersection end) 17 | { 18 | Start = Intersection.FromStartIntersection(start); 19 | 20 | End = Intersection.FromEndIntersection(end); 21 | 22 | Segment = new Segment(Start.Point, End.Point); 23 | } 24 | 25 | // Check if bisector segment is oriented correct. 26 | // Checks If Center is on the same side of the bisector. 27 | public bool Verify(float3 center) 28 | { 29 | Segment segment = new Segment(End.Point, End.Segment.End); 30 | 31 | return Utils.Cross(Segment.Direction, center - Start.Point).Normalize() 32 | .Equals(Utils.Cross(segment.Direction, center - segment.Start).Normalize()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/BisectorSegment.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 496c089608a0d704f8a1e614862ef1ff 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Cell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Mathematics; 3 | 4 | namespace Voronoi 5 | { 6 | public struct Cell : IEquatable 7 | { 8 | public float3 Center { get; private set; } 9 | 10 | public Segment[] Segments { get; private set; } 11 | 12 | public float3[] Vertices { get; private set; } 13 | 14 | public Cell(float3 center, Segment[] segments) 15 | { 16 | Center = center; 17 | 18 | Segments = segments; 19 | 20 | Vertices = new float3[segments.Length]; 21 | 22 | for (int i = 0; i < segments.Length; i++) 23 | { 24 | Vertices[i] = segments[i].Start; 25 | } 26 | } 27 | 28 | public override bool Equals(object obj) 29 | { 30 | return obj is Cell other && Equals(other); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | return Center.GetHashCode(); 36 | } 37 | 38 | public bool Equals(Cell other) 39 | { 40 | return Center.Equals(other.Center); 41 | } 42 | 43 | // TODO: this shouldn't happen, it's a bug in Voronoi generation 44 | // where segments aren't properly calculated (center isn't within segments and segments are unusually smaller) 45 | public bool Verify(float3 up) 46 | { 47 | float previous = math.sign(Utils.SignedAngle(Segments[0].Direction, Center - Segments[0].Start, up)); 48 | 49 | for (int i = 1; i < Segments.Length; i++) 50 | { 51 | Segment segment = Segments[i]; 52 | 53 | float current = math.sign(Utils.SignedAngle(segment.Direction, Center - segment.Start, up)); 54 | 55 | if (previous - current != 0) 56 | { 57 | return false; 58 | } 59 | 60 | previous = current; 61 | } 62 | 63 | return true; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Cell.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 86a6304391cbe2a4793da0dd780b1aab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/CellJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Burst; 3 | using Unity.Collections; 4 | using Unity.Jobs; 5 | using Unity.Mathematics; 6 | 7 | namespace Voronoi 8 | { 9 | [BurstCompile] 10 | public struct CellJob : IJob 11 | { 12 | [ReadOnly] 13 | public NativeArray Centers; 14 | 15 | public NativeList Segments; 16 | 17 | public int Index; 18 | 19 | public Rect BoundingRect; 20 | 21 | public void Execute() 22 | { 23 | float3 center = Centers[Index]; 24 | 25 | GetSegmentsFromRect(); 26 | 27 | for (int i = 0; i < Centers.Length; i++) 28 | { 29 | if (i == Index) 30 | { 31 | continue; 32 | } 33 | 34 | float3 other = Centers[i]; 35 | 36 | Segment connectingSegment = new Segment(center, other); 37 | 38 | float3 bisectorDirection = Utils.Cross(connectingSegment.Direction, new float3(0, 0, 1)).Normalize() * BoundingRect.Diagonal; 39 | 40 | float3 bisectorEnd = bisectorDirection + connectingSegment.Center; 41 | 42 | Segment bisector = new Segment(connectingSegment.Center - bisectorDirection, bisectorEnd); 43 | 44 | if (GetIntersections(bisector, out NativeHashSet intersections)) 45 | { 46 | NativeArray intersectionArray = intersections.ToNativeArray(Allocator.Temp); 47 | 48 | intersections.Dispose(); 49 | 50 | GetSegmentsFromIntersections(intersectionArray[0], intersectionArray[1]); 51 | 52 | intersectionArray.Dispose(); 53 | } 54 | } 55 | } 56 | 57 | private void GetSegmentsFromRect() 58 | { 59 | Segments.ResizeUninitialized(4); 60 | 61 | Segment segment = Segments[0] = new Segment(BoundingRect.Min, new float3(BoundingRect.MinX, BoundingRect.MaxY, 0)); 62 | 63 | segment = Segments[1] = new Segment(segment.End, BoundingRect.Max); 64 | 65 | segment = Segments[2] = new Segment(segment.End, new float3(BoundingRect.MaxX, BoundingRect.MinY, 0)); 66 | 67 | Segments[3] = new Segment(segment.End, BoundingRect.Min); 68 | } 69 | 70 | private bool GetIntersections(Segment bisector, out NativeHashSet intersections) 71 | { 72 | intersections = new NativeHashSet(0, Allocator.Temp); 73 | 74 | foreach (var segment in Segments) 75 | { 76 | if (GetIntersection(bisector, segment, out Intersection intersection)) 77 | { 78 | intersections.Add(intersection); 79 | } 80 | } 81 | 82 | return intersections.Count == 2; 83 | } 84 | 85 | private bool GetIntersection(Segment bisector, Segment segment, out Intersection intersection) 86 | { 87 | intersection = default; 88 | 89 | // Segment AB 90 | float3 a = bisector.Start; 91 | float3 b = bisector.End; 92 | 93 | // Segment CD 94 | float3 c = segment.Start; 95 | float3 d = segment.End; 96 | 97 | float t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / 98 | ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)); 99 | 100 | float u = -((a.x - b.x) * (a.y - c.y) - (a.y - b.y) * (a.x - c.x)) / 101 | ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)); 102 | 103 | // Both t & u are between 0 and 1 104 | if (t >= 0 && t <= 1 && u >= 0 && u <= 1) 105 | { 106 | var point = a + t * (b - a); 107 | 108 | intersection = new Intersection(point, segment); 109 | 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | private void GetSegmentsFromIntersections(Intersection first, Intersection second) 117 | { 118 | BisectorSegment bisector = GetBisectorSegment(first, second); 119 | 120 | NativeList segments = new NativeList(Allocator.Temp) 121 | { 122 | bisector.Segment, 123 | 124 | bisector.End.Segment 125 | }; 126 | 127 | Segment segment = segments[1]; 128 | 129 | while (!segment.End.Equals(bisector.Start.Point)) 130 | { 131 | segment = Next(segment); 132 | 133 | // If last segment 134 | if (bisector.Start.Segment.Start.Equals(segment.Start)) 135 | { 136 | segment = bisector.Start.Segment; 137 | } 138 | 139 | segments.Add(segment); 140 | } 141 | 142 | Segments.CopyFrom(segments); 143 | 144 | segments.Dispose(); 145 | } 146 | 147 | // Get the correct bisector segment from two intersections. 148 | private BisectorSegment GetBisectorSegment(Intersection first, Intersection second) 149 | { 150 | float3 center = Centers[Index]; 151 | 152 | BisectorSegment segment = new BisectorSegment(first, second); 153 | 154 | return segment.Verify(center) ? segment : new BisectorSegment(second, first); 155 | } 156 | 157 | private Segment Next(Segment segment) 158 | { 159 | for (int i = 0; i < Segments.Length; i++) 160 | { 161 | Segment current = Segments[i]; 162 | 163 | if (segment.End.Equals(current.Start)) 164 | { 165 | return current; 166 | } 167 | } 168 | 169 | throw new Exception("Next segment not found"); 170 | } 171 | 172 | private Segment NextUnique(Segment segment) 173 | { 174 | int index = - 1; 175 | 176 | for (int i = 0; i < Segments.Length; i++) 177 | { 178 | Segment current = Segments[i]; 179 | 180 | if (segment.End.Equals(current.Start)) 181 | { 182 | if (index != - 1) 183 | { 184 | // TODO: this keeps happening sometimes 185 | throw new Exception("Next segment not unique"); 186 | } 187 | 188 | index = i; 189 | } 190 | } 191 | 192 | if (index == - 1) 193 | { 194 | throw new Exception("Next segment not found"); 195 | } 196 | 197 | return Segments[index]; 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/CellJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aeb43ca05ecf4f0f862f01734d0a814a 3 | timeCreated: 1729838132 -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/GetRandomCenterJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Mathematics; 5 | 6 | namespace Voronoi 7 | { 8 | [BurstCompile] 9 | public struct GetRandomCenterJob : IJobParallelFor 10 | { 11 | public NativeArray Centers; 12 | 13 | public int Width; 14 | 15 | public int Height; 16 | 17 | public float CellSize; 18 | 19 | public int Seed; 20 | 21 | public void Execute(int index) 22 | { 23 | //Get row and column 24 | int column = Width > Height ? (index - (index % Height)) / Height : index % Width; 25 | 26 | int row = Width > Height ? index % Height : (index - (index % Width)) / Width; 27 | 28 | Random random = new Random((uint)(Seed * (index + 1))); 29 | 30 | float x = column * CellSize; 31 | 32 | x += random.NextFloat(0f, CellSize); 33 | 34 | float y = row * CellSize; 35 | 36 | y += random.NextFloat(0f, CellSize); 37 | 38 | Centers[index] = new float3(x, y, 0); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/GetRandomCenterJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 072d874b22e220b4b88b158188f17405 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Intersection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Mathematics; 3 | 4 | namespace Voronoi 5 | { 6 | public struct Intersection : IEquatable 7 | { 8 | public float3 Point { get; private set; } 9 | 10 | public Segment Segment { get; private set; } 11 | 12 | public Intersection(float3 point, Segment segment) 13 | { 14 | Point = point; 15 | 16 | Segment = segment; 17 | } 18 | 19 | public override bool Equals(object obj) 20 | { 21 | if (obj is Intersection other) 22 | { 23 | return Equals(other); 24 | } 25 | 26 | return false; 27 | } 28 | 29 | public bool Equals(Intersection other) 30 | { 31 | return Point.Equals(other.Point); 32 | } 33 | 34 | public override int GetHashCode() 35 | { 36 | return Point.GetHashCode(); 37 | } 38 | 39 | public static Intersection FromStartIntersection(Intersection intersection) 40 | { 41 | return new Intersection(intersection.Point, new Segment(intersection.Segment.Start, intersection.Point)); 42 | } 43 | 44 | public static Intersection FromEndIntersection(Intersection intersection) 45 | { 46 | return new Intersection(intersection.Point, new Segment(intersection.Point, intersection.Segment.End)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Intersection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8eac8d12e7088a4582da6f94d6922b7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/ProjectAndTranslateCenterJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Mathematics; 5 | 6 | namespace Voronoi 7 | { 8 | [BurstCompile] 9 | public struct ProjectAndTranslateCenterJob : IJobParallelFor 10 | { 11 | public NativeArray Centers; 12 | 13 | public float3 Forward; 14 | 15 | public float3 Up; 16 | 17 | public float3 Origin; 18 | 19 | public void Execute(int index) 20 | { 21 | float3 center = Centers[index]; 22 | 23 | Centers[index] = Utils.ProjectAndTranslate(center, Forward, Up, Origin); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/ProjectAndTranslateCenterJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb80c0f5f05b7134eb7b248fe1b321f1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/ProjectAndTranslateSegmentsJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Mathematics; 5 | 6 | namespace Voronoi 7 | { 8 | [BurstCompile] 9 | public struct ProjectAndTranslateSegmentsJob : IJob 10 | { 11 | public NativeList Segments; 12 | 13 | public float3 Forward; 14 | 15 | public float3 Up; 16 | 17 | public float3 Origin; 18 | 19 | public void Execute() 20 | { 21 | for (int i = 0; i < Segments.Length; i++) 22 | { 23 | Segment segment = Segments[i]; 24 | 25 | Segments[i] = new Segment(ProjectAndTranslate(segment.Start), ProjectAndTranslate(segment.End)); 26 | } 27 | } 28 | 29 | private float3 ProjectAndTranslate(float3 value) 30 | { 31 | return Utils.ProjectAndTranslate(value, Forward, Up, Origin); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/ProjectAndTranslateSegmentsJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 205e2666ba3a498da6a7e5fd350ca2f4 3 | timeCreated: 1729931102 -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/README.md: -------------------------------------------------------------------------------- 1 | # Voronoi 2 | 3 | A Vornoi Diagram Generator on Unity using C# Job System (DOTS). 4 | 5 | Unity Version: `2022.3.44f1` 6 | 7 | ![voronoi_1.gif](../../../~docs/voronoi_1.png) ![voronoi_2.gif](../../../~docs/voronoi_2.gif) 8 | 9 | ## Performance 10 | 11 | | Diagram Size | Performance in ms | 12 | |--------------|-------------------| 13 | | 5x5 | 0.4 - 0.5 | 14 | | 25x25 | 98 - 100 | 15 | | 50x50 | 1725 - 1800 | 16 | 17 | ## Usage 18 | - Create a serialized `VoronoiPlane` object in your MonoBehaviour script and Generate it. 19 | ```csharp 20 | [field: SerializeField] public VoronoiPlane VoronoiPlane { get; private set; } 21 | 22 | private void Update() 23 | { 24 | // Generate Voronoi Diagram on Space Key Press 25 | if (Input.GetKeyDown(KeyCode.Space)) 26 | { 27 | VoronoiPlane.Generate(transform); 28 | } 29 | } 30 | 31 | private void OnDestroy() 32 | { 33 | // Dispose the Voronoi Diagram 34 | VoronoiPlane.Dispose(); 35 | } 36 | ``` 37 | - The `VoronoiPlane.Generate()` method takes a `Transform` object as an argument that determines the position and rotation of the diagram, you can also alternatively use `VoronoiDiagram.Generate(Vector3 origin, Vector3 forward, Vector3 up)` where `origin` will be used to position the diagram while `forward` and `up` are used to rotate the diagram. 38 | - You can also use the `VoronoiPlane` object to get the generated Voronoi Diagram Cells. 39 | ```csharp 40 | // Get the Voronoi Diagram data 41 | Cell[] cells = VoronoiPlane.Cells; 42 | ``` 43 | - Each cell contains the center point of each cell as `Cell.Center` and an array of segments and vertices for each cell arranged in a **clockwise** order as `Cell.Segments` and `Cell.Vertices`. 44 | ```csharp 45 | Cell[] cells = VoronoiPlane.Cells; 46 | 47 | for (int i = 0; i < cells.Length; i++) 48 | { 49 | Cell cell = cells[i]; 50 | 51 | // Center point of a Voronoi cell 52 | Vector3 center = cell.Center; 53 | 54 | // Segments of a single Voronoi cell arranged in a clockwise manner 55 | Segment[] segments = cell.Segments; 56 | 57 | // Vertices of a single Voronoi cell arranged in a clockwise manner 58 | float3[] vertices = cell.Vertices; 59 | } 60 | ``` 61 | - Each segment contains the start and end points of the segment as `Segment.Start` and `Segment.End` ordered clockwise. 62 | - You can also visualize the Voronoi Diagram by either adding a `Drawer` instance in your scene and enabling `VoronoiPlane.drawGizmos` in the inspector or by calling `VoronoiPlane.Draw()` in `OnDrawGizmos`. 63 | 64 | --- 65 | 66 | ##### **⚠️Caution⚠️** 67 | 68 | As you go higher up in diagram size significantly (> 2500 cells | 50x50) you'll start to get a specific exception `Next segment not unique` more and more frequently which happens due to floating point precision. In cases where you need a significantly large diagram perhaps consider more than one adjacent diagrams or consider refactoring the source code to use `double` and `double3` instead of `float` and `float3`. 69 | 70 | --- -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a56d016a6e73e5146b9e88e3827208ba 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Rect.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace Voronoi 4 | { 5 | /// 6 | /// Rect implementation that works with Unity Jobs. 7 | /// 8 | public struct Rect 9 | { 10 | public float3 Min; 11 | public float3 Max; 12 | 13 | public readonly float Diagonal; 14 | 15 | private readonly float3 _forward; 16 | 17 | private readonly float3 _up; 18 | 19 | private readonly float _width; 20 | 21 | private readonly float3 _height; 22 | 23 | public float MinX => Min.x; 24 | public float MinY => Min.y; 25 | 26 | public float MaxX => Max.x; 27 | public float MaxY => Max.y; 28 | 29 | public float3 TopLeft => Min + _forward * _height; 30 | 31 | public float3 BottomRight => Min + Utils.Cross(_forward, _up) * _width; 32 | 33 | public Rect(float2 min, float2 max, float3 forward, float3 up) 34 | { 35 | Min = new float3(min.xy, 0); 36 | Max = new float3(max.xy, 0); 37 | 38 | Diagonal = math.distance(Min, Max); 39 | 40 | float2 delta = max - min; 41 | 42 | _width = delta.x; 43 | 44 | _height = delta.y; 45 | 46 | _forward = forward.Normalize(); 47 | 48 | _up = up.Normalize(); 49 | } 50 | 51 | public void ProjectAndTranslate(float3 forward, float3 up, float3 origin) 52 | { 53 | Min = Utils.ProjectAndTranslate(Min, forward, up, origin); 54 | 55 | Max = Utils.ProjectAndTranslate(Max, forward, up, origin); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Rect.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c11062b81397ed84ba3301a7046f1182 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Segment.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace Voronoi 4 | { 5 | public struct Segment 6 | { 7 | public float3 Start { get; private set; } 8 | 9 | public float3 End { get; private set; } 10 | 11 | public float3 Direction => End - Start; 12 | 13 | public float3 Center { get; private set; } 14 | 15 | public Segment(float3 start, float3 end) 16 | { 17 | Start = start; 18 | 19 | End = end; 20 | 21 | Center = (start + end) / 2f; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Segment.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42a6c423a24b5bc47b3ba1ce06177bd8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Utils.cs: -------------------------------------------------------------------------------- 1 | using Unity.Mathematics; 2 | 3 | namespace Voronoi 4 | { 5 | public static class Utils 6 | { 7 | // math.cross isn't consistent with Vector3.Cross 8 | public static float3 Cross(float3 lhs, float3 rhs) 9 | { 10 | return new float3(lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x); 11 | } 12 | 13 | // math.normalize isn't consistent with Vector3.Normalize 14 | public static float3 Normalize(this float3 value) 15 | { 16 | float magnitude = math.length(value); 17 | 18 | return magnitude == 0 ? float3.zero : value / magnitude; 19 | } 20 | 21 | public static float3 ProjectAndTranslate(float3 value, float3 forward, float3 up, float3 origin) 22 | { 23 | var rotation = quaternion.LookRotation(up, forward); 24 | 25 | value = RotateFloat3(rotation, value); 26 | 27 | return value + origin; 28 | } 29 | 30 | // Taken from UnityEngine.Quaternion.operator* 31 | private static float3 RotateFloat3(quaternion rotation, float3 point) 32 | { 33 | float num1 = rotation.value.x * 2f; 34 | float num2 = rotation.value.y * 2f; 35 | float num3 = rotation.value.z * 2f; 36 | float num4 = rotation.value.x * num1; 37 | float num5 = rotation.value.y * num2; 38 | float num6 = rotation.value.z * num3; 39 | float num7 = rotation.value.x * num2; 40 | float num8 = rotation.value.x * num3; 41 | float num9 = rotation.value.y * num3; 42 | float num10 = rotation.value.w * num1; 43 | float num11 = rotation.value.w * num2; 44 | float num12 = rotation.value.w * num3; 45 | float3 rotatedPoint; 46 | rotatedPoint.x = (1f - (num5 + num6)) * point.x + (num7 - num12) * point.y + (num8 + num11) * point.z; 47 | rotatedPoint.y = (num7 + num12) * point.x + (1f - (num4 + num6)) * point.y + (num9 - num10) * point.z; 48 | rotatedPoint.z = (num8 - num11) * point.x + (num9 + num10) * point.y + (1f - (num4 + num5)) * point.z; 49 | return rotatedPoint; 50 | } 51 | 52 | public static float SignedAngle(float3 from, float3 to, float3 axis) 53 | { 54 | float angle = Angle(from, to); 55 | 56 | float x = from.y * to.z - from.z * to.y; 57 | float y = from.z * to.x - from.x * to.z; 58 | float z = from.x * to.y - from.y * to.x; 59 | 60 | float sign = math.sign(axis.x * x + axis.y * y + axis.z * z); 61 | 62 | return angle * sign; 63 | } 64 | 65 | public static float Angle(float3 from, float3 to) 66 | { 67 | float num = math.sqrt(math.lengthsq(from) * math.lengthsq(to)); 68 | 69 | return num < 1.0000000036274937E-15 ? 0.0f : math.acos(math.clamp(math.dot(from, to) / num, -1f, 1f)) * 57.29578f; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/Utils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 338c73aac1bf412ca693ff762163f6b1 3 | timeCreated: 1729017079 -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/VoronoiPlane.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Mathematics; 5 | using UnityEngine; 6 | using Random = UnityEngine.Random; 7 | 8 | namespace Voronoi 9 | { 10 | [Serializable] 11 | 12 | public class VoronoiPlane : IDisposable 13 | { 14 | [SerializeField] private int planeWidth = 5; 15 | 16 | [SerializeField] private int planeHeight = 5; 17 | 18 | [SerializeField] private float cellSize = 5f; 19 | 20 | [SerializeField] private float padding = 1f; 21 | 22 | public Cell[] Cells { get; private set; } 23 | 24 | private Rect _boundingRect; 25 | 26 | public VoronoiPlane(int width, int height, float size) 27 | { 28 | planeWidth = width; 29 | 30 | planeHeight = height; 31 | 32 | cellSize = size; 33 | } 34 | 35 | public void Generate(Transform transform) 36 | { 37 | Vector3 position = transform.position; 38 | 39 | position += transform.right * (planeWidth * cellSize) / 2f; 40 | 41 | position -= transform.forward * (planeHeight * cellSize) / 2f; 42 | 43 | Generate(position, transform.forward, transform.up); 44 | } 45 | 46 | public void Generate(Vector3 origin, Vector3 forward, Vector3 up) 47 | { 48 | int arrayLength = planeWidth * planeHeight; 49 | 50 | var centers = new NativeArray(arrayLength, Allocator.TempJob); 51 | 52 | // Get random voronoi cell center points 53 | new GetRandomCenterJob 54 | { 55 | Centers = centers, 56 | 57 | Width = planeWidth, 58 | 59 | Height = planeHeight, 60 | 61 | CellSize = cellSize, 62 | 63 | Seed = Random.Range(0, int.MaxValue) 64 | }.Schedule(arrayLength, 16).Complete(); 65 | 66 | _boundingRect = GetBoundingRect(forward, up); 67 | 68 | var segmentsArray = new NativeList[arrayLength]; 69 | 70 | var allJobs = new NativeArray(arrayLength, Allocator.Temp); 71 | 72 | for (int i = 0; i < arrayLength; i++) 73 | { 74 | segmentsArray[i] = new NativeList(0, Allocator.TempJob); 75 | 76 | allJobs[i] = new CellJob 77 | { 78 | Centers = centers, 79 | 80 | Segments = segmentsArray[i], 81 | 82 | Index = i, 83 | 84 | BoundingRect = _boundingRect, 85 | }.Schedule(); 86 | } 87 | 88 | JobHandle.CompleteAll(allJobs); 89 | 90 | // Project and translate center points 91 | new ProjectAndTranslateCenterJob 92 | { 93 | Centers = centers, 94 | 95 | Forward = forward, 96 | 97 | Up = up, 98 | 99 | Origin = origin 100 | }.Schedule(arrayLength, 8).Complete(); 101 | 102 | for (int i = 0; i < arrayLength; i++) 103 | { 104 | allJobs[i] = new ProjectAndTranslateSegmentsJob 105 | { 106 | Segments = segmentsArray[i], 107 | 108 | Forward = forward, 109 | 110 | Up = up, 111 | 112 | Origin = origin 113 | }.Schedule(); 114 | } 115 | 116 | JobHandle.CompleteAll(allJobs); 117 | 118 | allJobs.Dispose(); 119 | 120 | _boundingRect.ProjectAndTranslate(forward, up, origin); 121 | 122 | Cells = new Cell[arrayLength]; 123 | 124 | for (int i = 0; i < arrayLength; i++) 125 | { 126 | Cells[i] = new Cell(centers[i], segmentsArray[i].AsArray().ToArray()); 127 | 128 | segmentsArray[i].Dispose(); 129 | } 130 | 131 | centers.Dispose(); 132 | 133 | #if UNITY_EDITOR 134 | if (!_drawing && Drawer.Instance != null) 135 | { 136 | Drawer.Instance.OnDraw += Draw; 137 | 138 | _drawing = true; 139 | } 140 | } 141 | 142 | private bool _drawing; 143 | 144 | #else 145 | } 146 | #endif 147 | 148 | private Rect GetBoundingRect(float3 forward, float3 up) 149 | { 150 | float minX = - padding; 151 | float minY = - padding; 152 | 153 | float maxX = (planeWidth * cellSize) + padding; 154 | float maxY = (planeHeight * cellSize) + padding; 155 | 156 | return new Rect(new float2(minX, minY), new float2(maxX, maxY), forward, up); 157 | } 158 | 159 | #region Gizmo 160 | 161 | [Space] [SerializeField] private bool drawGizmos = true; 162 | 163 | [SerializeField] private float centerRadius = .25f; 164 | 165 | public void Draw() 166 | { 167 | if (!drawGizmos) 168 | { 169 | return; 170 | } 171 | 172 | if (Cells != null) 173 | { 174 | // Draw bounding box 175 | Gizmos.color = Color.white; 176 | 177 | Gizmos.DrawLine(_boundingRect.Min, _boundingRect.TopLeft); 178 | 179 | Gizmos.DrawLine(_boundingRect.TopLeft, _boundingRect.Max); 180 | 181 | Gizmos.DrawLine(_boundingRect.Max, _boundingRect.BottomRight); 182 | 183 | Gizmos.DrawLine(_boundingRect.BottomRight, _boundingRect.Min); 184 | 185 | foreach (var cell in Cells) 186 | { 187 | Gizmos.color = Color.green; 188 | 189 | // Draw cell center 190 | Gizmos.DrawSphere(cell.Center, centerRadius); 191 | 192 | Gizmos.color = Color.white; 193 | 194 | // Draw cell sizes/segments 195 | foreach (var segment in cell.Segments) 196 | { 197 | Gizmos.DrawLine(segment.Start, segment.End); 198 | } 199 | } 200 | } 201 | } 202 | 203 | #endregion 204 | 205 | public void Dispose() 206 | { 207 | #if UNITY_EDITOR 208 | if (_drawing && Drawer.Instance != null) 209 | { 210 | Drawer.Instance.OnDraw -= Draw; 211 | } 212 | #endif 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Runtime/Scripts/Voronoi/VoronoiPlane.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1adb9e3c6cf63db469af4213058de924 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Textures.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0839f91cccb05e438e031b7fc887640 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Textures/road.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/Runtime/Textures/road.png -------------------------------------------------------------------------------- /Runtime/Textures/road.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36542cd599c6d6a42b55565e66016300 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 0 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 0 41 | wrapV: 0 42 | wrapW: 0 43 | nPOTScale: 1 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 0 56 | spriteTessellationDetail: -1 57 | textureType: 0 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 4 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 4 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 4 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 4 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | - serializedVersion: 4 123 | buildTarget: WindowsStoreApps 124 | maxTextureSize: 2048 125 | resizeAlgorithm: 0 126 | textureFormat: -1 127 | textureCompression: 1 128 | compressionQuality: 50 129 | crunchedCompression: 0 130 | allowsAlphaSplitting: 0 131 | overridden: 0 132 | ignorePlatformSupport: 0 133 | androidETC2FallbackOverride: 0 134 | forceMaximumCompressionQuality_BC6H_BC7: 0 135 | - serializedVersion: 4 136 | buildTarget: iOS 137 | maxTextureSize: 2048 138 | resizeAlgorithm: 0 139 | textureFormat: -1 140 | textureCompression: 1 141 | compressionQuality: 50 142 | crunchedCompression: 0 143 | allowsAlphaSplitting: 0 144 | overridden: 0 145 | ignorePlatformSupport: 0 146 | androidETC2FallbackOverride: 0 147 | forceMaximumCompressionQuality_BC6H_BC7: 0 148 | spriteSheet: 149 | serializedVersion: 2 150 | sprites: [] 151 | outline: [] 152 | customData: 153 | physicsShape: [] 154 | bones: [] 155 | spriteID: 156 | internalID: 0 157 | vertices: [] 158 | indices: 159 | edges: [] 160 | weights: [] 161 | secondaryTextures: [] 162 | spriteCustomMetadata: 163 | entries: [] 164 | nameFileIdTable: {} 165 | mipmapLimitGroupName: 166 | pSDRemoveMatte: 0 167 | userData: 168 | assetBundleName: 169 | assetBundleVariant: 170 | -------------------------------------------------------------------------------- /Runtime/rob1997.track-generator.runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rob1997.track-generator.runtime", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:d8b63aba1907145bea998dd612889d6b", 6 | "GUID:21d1eb854b91ade49bc69a263d12bee2", 7 | "GUID:2665a8d13d1b3f18800f46e256720795", 8 | "GUID:e0cd26848372d4e5c891c569017e11f1" 9 | ], 10 | "includePlatforms": [], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Runtime/rob1997.track-generator.runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b8a2c0415858014b94e09eef3e3fc15 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.rob1997.track-generator", 3 | "version": "1.0.0", 4 | "displayName": "Track Generator", 5 | "description": "A package that Procedurally Generates Closed Tracks from Voronoi Diagrams using C# Jobs System, Splines and Procedural Mesh Generation.", 6 | "unity": "2022.3", 7 | "documentationUrl": "https://GitHub.com/rob1997/TrackGenerator", 8 | "dependencies": { 9 | "com.unity.collections": "2.5.1", 10 | "com.unity.splines": "2.6.1" 11 | }, 12 | "keywords": [ 13 | "track", 14 | "procedural", 15 | "voronoi" 16 | ], 17 | "author": { 18 | "name": "rob1997", 19 | "url": "https://rob1997.github.io/" 20 | } 21 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c57129613a69e414ab3f97d60f41ec0f 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /~docs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 747eadf0e4f46c448b81dc78ab3e7561 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /~docs/spline_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/spline_0.png -------------------------------------------------------------------------------- /~docs/spline_0.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 356b4d70bbbc486cab63490e31f95f3e 3 | timeCreated: 1733665879 -------------------------------------------------------------------------------- /~docs/spline_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/spline_1.png -------------------------------------------------------------------------------- /~docs/spline_1.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92a99363a7c441988dd8244a9f24b560 3 | timeCreated: 1733665043 -------------------------------------------------------------------------------- /~docs/spline_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/spline_2.png -------------------------------------------------------------------------------- /~docs/spline_2.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d19cb3cc50134832a9746742ef4d27a2 3 | timeCreated: 1733665730 -------------------------------------------------------------------------------- /~docs/spline_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/spline_3.png -------------------------------------------------------------------------------- /~docs/spline_3.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 33ca1095754640fda4c4f29197de0515 3 | timeCreated: 1733665305 -------------------------------------------------------------------------------- /~docs/spline_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/spline_4.png -------------------------------------------------------------------------------- /~docs/spline_4.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8993b38a4a3448aea9c8b9eef4c292a4 3 | timeCreated: 1733666203 -------------------------------------------------------------------------------- /~docs/voronoi_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/voronoi_1.png -------------------------------------------------------------------------------- /~docs/voronoi_1.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1a2ba36d59aa2cf4598d08b204f71fa6 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 0 41 | wrapV: 0 42 | wrapW: 0 43 | nPOTScale: 1 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 0 56 | spriteTessellationDetail: -1 57 | textureType: 0 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | - serializedVersion: 3 123 | buildTarget: Windows Store Apps 124 | maxTextureSize: 2048 125 | resizeAlgorithm: 0 126 | textureFormat: -1 127 | textureCompression: 1 128 | compressionQuality: 50 129 | crunchedCompression: 0 130 | allowsAlphaSplitting: 0 131 | overridden: 0 132 | ignorePlatformSupport: 0 133 | androidETC2FallbackOverride: 0 134 | forceMaximumCompressionQuality_BC6H_BC7: 0 135 | spriteSheet: 136 | serializedVersion: 2 137 | sprites: [] 138 | outline: [] 139 | physicsShape: [] 140 | bones: [] 141 | spriteID: 142 | internalID: 0 143 | vertices: [] 144 | indices: 145 | edges: [] 146 | weights: [] 147 | secondaryTextures: [] 148 | nameFileIdTable: {} 149 | mipmapLimitGroupName: 150 | pSDRemoveMatte: 0 151 | userData: 152 | assetBundleName: 153 | assetBundleVariant: 154 | -------------------------------------------------------------------------------- /~docs/voronoi_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rob1997/TrackGenerator/39d925917f0ae8bc03c5833ebade463afa0e7c51/~docs/voronoi_2.gif -------------------------------------------------------------------------------- /~docs/voronoi_2.gif.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb784cff2edb7c34a94bc7b332a6b0e9 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 13 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 1 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | flipGreenChannel: 0 24 | isReadable: 0 25 | streamingMipmaps: 0 26 | streamingMipmapsPriority: 0 27 | vTOnly: 0 28 | ignoreMipmapLimit: 0 29 | grayScaleToAlpha: 0 30 | generateCubemap: 6 31 | cubemapConvolution: 0 32 | seamlessCubemap: 0 33 | textureFormat: 1 34 | maxTextureSize: 2048 35 | textureSettings: 36 | serializedVersion: 2 37 | filterMode: 1 38 | aniso: 1 39 | mipBias: 0 40 | wrapU: 0 41 | wrapV: 0 42 | wrapW: 0 43 | nPOTScale: 1 44 | lightmap: 0 45 | compressionQuality: 50 46 | spriteMode: 0 47 | spriteExtrude: 1 48 | spriteMeshType: 1 49 | alignment: 0 50 | spritePivot: {x: 0.5, y: 0.5} 51 | spritePixelsToUnits: 100 52 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 53 | spriteGenerateFallbackPhysicsShape: 1 54 | alphaUsage: 1 55 | alphaIsTransparency: 0 56 | spriteTessellationDetail: -1 57 | textureType: 0 58 | textureShape: 1 59 | singleChannelComponent: 0 60 | flipbookRows: 1 61 | flipbookColumns: 1 62 | maxTextureSizeSet: 0 63 | compressionQualitySet: 0 64 | textureFormatSet: 0 65 | ignorePngGamma: 0 66 | applyGammaDecoding: 0 67 | swizzle: 50462976 68 | cookieLightType: 0 69 | platformSettings: 70 | - serializedVersion: 3 71 | buildTarget: DefaultTexturePlatform 72 | maxTextureSize: 2048 73 | resizeAlgorithm: 0 74 | textureFormat: -1 75 | textureCompression: 1 76 | compressionQuality: 50 77 | crunchedCompression: 0 78 | allowsAlphaSplitting: 0 79 | overridden: 0 80 | ignorePlatformSupport: 0 81 | androidETC2FallbackOverride: 0 82 | forceMaximumCompressionQuality_BC6H_BC7: 0 83 | - serializedVersion: 3 84 | buildTarget: Standalone 85 | maxTextureSize: 2048 86 | resizeAlgorithm: 0 87 | textureFormat: -1 88 | textureCompression: 1 89 | compressionQuality: 50 90 | crunchedCompression: 0 91 | allowsAlphaSplitting: 0 92 | overridden: 0 93 | ignorePlatformSupport: 0 94 | androidETC2FallbackOverride: 0 95 | forceMaximumCompressionQuality_BC6H_BC7: 0 96 | - serializedVersion: 3 97 | buildTarget: WebGL 98 | maxTextureSize: 2048 99 | resizeAlgorithm: 0 100 | textureFormat: -1 101 | textureCompression: 1 102 | compressionQuality: 50 103 | crunchedCompression: 0 104 | allowsAlphaSplitting: 0 105 | overridden: 0 106 | ignorePlatformSupport: 0 107 | androidETC2FallbackOverride: 0 108 | forceMaximumCompressionQuality_BC6H_BC7: 0 109 | - serializedVersion: 3 110 | buildTarget: Android 111 | maxTextureSize: 2048 112 | resizeAlgorithm: 0 113 | textureFormat: -1 114 | textureCompression: 1 115 | compressionQuality: 50 116 | crunchedCompression: 0 117 | allowsAlphaSplitting: 0 118 | overridden: 0 119 | ignorePlatformSupport: 0 120 | androidETC2FallbackOverride: 0 121 | forceMaximumCompressionQuality_BC6H_BC7: 0 122 | - serializedVersion: 3 123 | buildTarget: Windows Store Apps 124 | maxTextureSize: 2048 125 | resizeAlgorithm: 0 126 | textureFormat: -1 127 | textureCompression: 1 128 | compressionQuality: 50 129 | crunchedCompression: 0 130 | allowsAlphaSplitting: 0 131 | overridden: 0 132 | ignorePlatformSupport: 0 133 | androidETC2FallbackOverride: 0 134 | forceMaximumCompressionQuality_BC6H_BC7: 0 135 | spriteSheet: 136 | serializedVersion: 2 137 | sprites: [] 138 | outline: [] 139 | physicsShape: [] 140 | bones: [] 141 | spriteID: 142 | internalID: 0 143 | vertices: [] 144 | indices: 145 | edges: [] 146 | weights: [] 147 | secondaryTextures: [] 148 | nameFileIdTable: {} 149 | mipmapLimitGroupName: 150 | pSDRemoveMatte: 0 151 | userData: 152 | assetBundleName: 153 | assetBundleVariant: 154 | --------------------------------------------------------------------------------