├── ProjectSettings ├── TagManager.asset ├── AudioManager.asset ├── InputManager.asset ├── NavMeshLayers.asset ├── TimeManager.asset ├── DynamicsManager.asset ├── EditorSettings.asset ├── NetworkManager.asset ├── ProjectSettings.asset ├── QualitySettings.asset ├── GraphicsSettings.asset ├── Physics2DSettings.asset └── EditorBuildSettings.asset ├── Assets ├── PolyMesh │ ├── Demo │ │ ├── Textures │ │ │ ├── Sky.png │ │ │ ├── Grass.png │ │ │ ├── Stone.png │ │ │ ├── Grass.png.meta │ │ │ ├── Sky.png.meta │ │ │ └── Stone.png.meta │ │ ├── Materials │ │ │ ├── Grass.mat │ │ │ ├── Sky.mat │ │ │ ├── Stone.mat │ │ │ ├── Grass.mat.meta │ │ │ ├── Sky.mat.meta │ │ │ └── Stone.mat.meta │ │ ├── Scenes │ │ │ ├── PolyMeshDemo.unity │ │ │ └── PolyMeshDemo.unity.meta │ │ ├── Materials.meta │ │ ├── Scenes.meta │ │ ├── Scripts.meta │ │ ├── Textures.meta │ │ └── Scripts │ │ │ ├── RollingRock.cs.meta │ │ │ └── RollingRock.cs │ ├── Demo.meta │ ├── Scripts.meta │ └── Scripts │ │ ├── Editor.meta │ │ ├── PolyMesh.cs.meta │ │ ├── Editor │ │ ├── PolyMeshEditor.cs.meta │ │ └── PolyMeshEditor.cs │ │ └── PolyMesh.cs └── PolyMesh.meta ├── .gitignore ├── README.md ├── PolyMesh.userprefs └── LICENSE /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/NavMeshLayers.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/NavMeshLayers.asset -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/TimeManager.asset -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Textures/Sky.png -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/EditorSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/ProjectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Grass.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Materials/Grass.mat -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Sky.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Materials/Sky.mat -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Stone.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Materials/Stone.mat -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Textures/Grass.png -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Textures/Stone.png -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/GraphicsSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/Physics2DSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/ProjectSettings/EditorBuildSettings.asset -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Ll]ibrary/ 2 | [Tt]emp/ 3 | [Oo]bj/ 4 | 5 | # Autogenerated VS/MD solution and project files 6 | *.csproj 7 | *.unityproj 8 | *.sln 9 | -------------------------------------------------------------------------------- /Assets/PolyMesh.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3522c05081dd9a64d94555e2266683da 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 83e2ef8009251924f8caf6bee96d4e73 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scenes/PolyMeshDemo.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UnityPatterns/PolyMesh/HEAD/Assets/PolyMesh/Demo/Scenes/PolyMeshDemo.unity -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Grass.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 026708813f2426d4886bf43c1c07e1b6 3 | NativeFormatImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Sky.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1d81cda1210f5c1499862c88c6481fe0 3 | NativeFormatImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials/Stone.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 78ae3d72b30d3a9449b2ee29c7aa43a3 3 | NativeFormatImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scenes/PolyMeshDemo.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37893d05af456524888ef7d1db90d79a 3 | DefaultImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87b8d49a144c85340bd2a874e9101a5c 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a185e3e2a8aa9a4db175344c300dd87 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 395ff5654cae9ee49b352bad484be910 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f35f34bf0ac46444924449b5036cbc8 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 86c90e8b7b7760d47afb7b0cb1dd2b6c 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c87a26565d59de4980628de60a32119 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts/PolyMesh.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e465a4fb08594de49a41e213281fe691 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scripts/RollingRock.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 452b3350b7a3f1b418db0476f75a05f1 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts/Editor/PolyMeshEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e9b2a4e328304c34cb212d1680c87b3f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Scripts/RollingRock.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class RollingRock : MonoBehaviour 5 | { 6 | public float rollTorque; 7 | 8 | void FixedUpdate() 9 | { 10 | rigidbody.AddTorque(0, 0, rollTorque, ForceMode.Acceleration); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PolyMesh 2 | ======== 3 | 4 | Create 2D shapes in an instant with the PolyMesh editor! Right in Unity you can create polygons, add, split, extrude, and move around vertices to build the shape you want. If straight edges aren’t good enough, connect vertices with curves to get nice smooth shapes. Great for developing 2D games, quick prototyping, or even just creating odd-shaped colliders! 5 | -------------------------------------------------------------------------------- /PolyMesh.userprefs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Grass.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: da711322f7fd361479640b562fc29e71 3 | TextureImporter: 4 | serializedVersion: 2 5 | mipmaps: 6 | mipMapMode: 0 7 | enableMipMap: 1 8 | linearTexture: 0 9 | correctGamma: 0 10 | fadeOut: 0 11 | borderMipMap: 0 12 | mipMapFadeDistanceStart: 1 13 | mipMapFadeDistanceEnd: 3 14 | bumpmap: 15 | convertToNormalMap: 0 16 | externalNormalMap: 0 17 | heightScale: .25 18 | normalMapFilter: 0 19 | isReadable: 0 20 | grayScaleToAlpha: 0 21 | generateCubemap: 0 22 | seamlessCubemap: 0 23 | textureFormat: -3 24 | maxTextureSize: 256 25 | textureSettings: 26 | filterMode: -1 27 | aniso: -1 28 | mipBias: -1 29 | wrapMode: -1 30 | nPOTScale: 1 31 | lightmap: 0 32 | compressionQuality: 50 33 | spriteMode: 0 34 | spriteExtrude: 1 35 | alignment: 0 36 | spritePivot: {x: .5, y: .5} 37 | spritePixelsToUnits: 100 38 | alphaIsTransparency: 0 39 | textureType: -1 40 | buildTargetSettings: [] 41 | spriteSheet: 42 | sprites: [] 43 | spriteAtlasHint: 44 | userData: 45 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Sky.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc659dec2c1231843a6adc9e2e673668 3 | TextureImporter: 4 | serializedVersion: 2 5 | mipmaps: 6 | mipMapMode: 0 7 | enableMipMap: 1 8 | linearTexture: 0 9 | correctGamma: 0 10 | fadeOut: 0 11 | borderMipMap: 0 12 | mipMapFadeDistanceStart: 1 13 | mipMapFadeDistanceEnd: 3 14 | bumpmap: 15 | convertToNormalMap: 0 16 | externalNormalMap: 0 17 | heightScale: .25 18 | normalMapFilter: 0 19 | isReadable: 0 20 | grayScaleToAlpha: 0 21 | generateCubemap: 0 22 | seamlessCubemap: 0 23 | textureFormat: -3 24 | maxTextureSize: 1024 25 | textureSettings: 26 | filterMode: -1 27 | aniso: -1 28 | mipBias: -1 29 | wrapMode: -1 30 | nPOTScale: 1 31 | lightmap: 0 32 | compressionQuality: 50 33 | spriteMode: 0 34 | spriteExtrude: 1 35 | alignment: 0 36 | spritePivot: {x: .5, y: .5} 37 | spritePixelsToUnits: 100 38 | alphaIsTransparency: 0 39 | textureType: -1 40 | buildTargetSettings: [] 41 | spriteSheet: 42 | sprites: [] 43 | spriteAtlasHint: 44 | userData: 45 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Demo/Textures/Stone.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4170921eb8fa2744f96eaa4d2cf85954 3 | TextureImporter: 4 | serializedVersion: 2 5 | mipmaps: 6 | mipMapMode: 0 7 | enableMipMap: 1 8 | linearTexture: 0 9 | correctGamma: 0 10 | fadeOut: 0 11 | borderMipMap: 0 12 | mipMapFadeDistanceStart: 1 13 | mipMapFadeDistanceEnd: 3 14 | bumpmap: 15 | convertToNormalMap: 0 16 | externalNormalMap: 0 17 | heightScale: .25 18 | normalMapFilter: 0 19 | isReadable: 0 20 | grayScaleToAlpha: 0 21 | generateCubemap: 0 22 | seamlessCubemap: 0 23 | textureFormat: -3 24 | maxTextureSize: 256 25 | textureSettings: 26 | filterMode: -1 27 | aniso: -1 28 | mipBias: -1 29 | wrapMode: -1 30 | nPOTScale: 1 31 | lightmap: 0 32 | compressionQuality: 50 33 | spriteMode: 0 34 | spriteExtrude: 1 35 | alignment: 0 36 | spritePivot: {x: .5, y: .5} 37 | spritePixelsToUnits: 100 38 | alphaIsTransparency: 0 39 | textureType: -1 40 | buildTargetSettings: [] 41 | spriteSheet: 42 | sprites: [] 43 | spriteAtlasHint: 44 | userData: 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 UnityPatterns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts/PolyMesh.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | [RequireComponent(typeof(MeshFilter))] 6 | public class PolyMesh : MonoBehaviour 7 | { 8 | public List keyPoints = new List(); 9 | public List curvePoints = new List(); 10 | public List isCurve = new List(); 11 | public MeshCollider meshCollider; 12 | [Range(0.01f, 1)] public float curveDetail = 0.1f; 13 | public float colliderDepth = 1; 14 | public bool buildColliderEdges = true; 15 | public bool buildColliderFront; 16 | public Vector2 uvPosition; 17 | public float uvScale = 1; 18 | public float uvRotation; 19 | 20 | public List GetEdgePoints() 21 | { 22 | //Build the point list and calculate curves 23 | var points = new List(); 24 | for (int i = 0; i < keyPoints.Count; i++) 25 | { 26 | if (isCurve[i]) 27 | { 28 | //Get the curve control point 29 | var a = keyPoints[i]; 30 | var c = keyPoints[(i + 1) % keyPoints.Count]; 31 | var b = Bezier.Control(a, c, curvePoints[i]); 32 | 33 | //Build the curve 34 | var count = Mathf.Ceil(1 / curveDetail); 35 | for (int j = 0; j < count; j++) 36 | { 37 | var t = (float)j / count; 38 | points.Add(Bezier.Curve(a, b, c, t)); 39 | } 40 | } 41 | else 42 | points.Add(keyPoints[i]); 43 | } 44 | return points; 45 | } 46 | 47 | public void BuildMesh() 48 | { 49 | var points = GetEdgePoints(); 50 | var vertices = points.ToArray(); 51 | 52 | //Build the index array 53 | var indices = new List(); 54 | while (indices.Count < points.Count) 55 | indices.Add(indices.Count); 56 | 57 | //Build the triangle array 58 | var triangles = Triangulate.Points(points); 59 | 60 | //Build the uv array 61 | var scale = uvScale != 0 ? (1 / uvScale) : 0; 62 | var matrix = Matrix4x4.TRS(-uvPosition, Quaternion.Euler(0, 0, uvRotation), new Vector3(scale, scale, 1)); 63 | var uv = new Vector2[points.Count]; 64 | for (int i = 0; i < uv.Length; i++) 65 | { 66 | var p = matrix.MultiplyPoint(points[i]); 67 | uv[i] = new Vector2(p.x, p.y); 68 | } 69 | 70 | //Find the mesh (create it if it doesn't exist) 71 | var meshFilter = GetComponent(); 72 | var mesh = meshFilter.sharedMesh; 73 | if (mesh == null) 74 | { 75 | mesh = new Mesh(); 76 | mesh.name = "PolySprite_Mesh"; 77 | meshFilter.mesh = mesh; 78 | } 79 | 80 | //Update the mesh 81 | mesh.Clear(); 82 | mesh.vertices = vertices; 83 | mesh.uv = uv; 84 | mesh.triangles = triangles; 85 | mesh.RecalculateNormals(); 86 | mesh.Optimize(); 87 | 88 | //Update collider after the mesh is updated 89 | UpdateCollider(points, triangles); 90 | } 91 | 92 | void UpdateCollider(List points, int[] tris) 93 | { 94 | //Update the mesh collider if there is one 95 | if (meshCollider != null) 96 | { 97 | var vertices = new List(); 98 | var triangles = new List(); 99 | 100 | if (buildColliderEdges) 101 | { 102 | //Build vertices array 103 | var offset = new Vector3(0, 0, colliderDepth / 2); 104 | for (int i = 0; i < points.Count; i++) 105 | { 106 | vertices.Add(points[i] + offset); 107 | vertices.Add(points[i] - offset); 108 | } 109 | 110 | //Build triangles array 111 | for (int a = 0; a < vertices.Count; a += 2) 112 | { 113 | var b = (a + 1) % vertices.Count; 114 | var c = (a + 2) % vertices.Count; 115 | var d = (a + 3) % vertices.Count; 116 | triangles.Add(a); 117 | triangles.Add(c); 118 | triangles.Add(b); 119 | triangles.Add(c); 120 | triangles.Add(d); 121 | triangles.Add(b); 122 | } 123 | } 124 | 125 | if (buildColliderFront) 126 | { 127 | for (int i = 0; i < tris.Length; i++) 128 | tris[i] += vertices.Count; 129 | vertices.AddRange(points); 130 | triangles.AddRange(tris); 131 | } 132 | 133 | //Find the mesh (create it if it doesn't exist) 134 | var mesh = meshCollider.sharedMesh; 135 | if (mesh == null) 136 | { 137 | mesh = new Mesh(); 138 | mesh.name = "PolySprite_Collider"; 139 | } 140 | 141 | //Update the mesh 142 | mesh.Clear(); 143 | mesh.vertices = vertices.ToArray(); 144 | mesh.triangles = triangles.ToArray(); 145 | mesh.RecalculateNormals(); 146 | mesh.Optimize(); 147 | meshCollider.sharedMesh = null; 148 | meshCollider.sharedMesh = mesh; 149 | } 150 | } 151 | 152 | bool IsRightTurn(List points, int a, int b, int c) 153 | { 154 | var ab = points[b] - points[a]; 155 | var bc = points[c] - points[b]; 156 | return (ab.x * bc.y - ab.y * bc.x) < 0; 157 | } 158 | 159 | bool IntersectsExistingLines(List points, Vector3 a, Vector3 b) 160 | { 161 | for (int i = 0; i < points.Count; i++) 162 | if (LinesIntersect(points, a, b, points[i], points[(i + 1) % points.Count])) 163 | return true; 164 | return false; 165 | } 166 | 167 | bool LinesIntersect(List points, Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4) 168 | { 169 | if (point1 == point3 || point1 == point4 || point2 == point3 || point2 == point4) 170 | return false; 171 | 172 | float ua = (point4.x - point3.x) * (point1.y - point3.y) - (point4.y - point3.y) * (point1.x - point3.x); 173 | float ub = (point2.x - point1.x) * (point1.y - point3.y) - (point2.y - point1.y) * (point1.x - point3.x); 174 | float denominator = (point4.y - point3.y) * (point2.x - point1.x) - (point4.x - point3.x) * (point2.y - point1.y); 175 | 176 | if (Mathf.Abs(denominator) <= 0.00001f) 177 | { 178 | if (Mathf.Abs(ua) <= 0.00001f && Mathf.Abs(ub) <= 0.00001f) 179 | return true; 180 | } 181 | else 182 | { 183 | ua /= denominator; 184 | ub /= denominator; 185 | 186 | if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) 187 | return true; 188 | } 189 | 190 | return false; 191 | } 192 | } 193 | 194 | public static class Bezier 195 | { 196 | public static float Curve(float from, float control, float to, float t) 197 | { 198 | return from * (1 - t) * (1 - t) + control * 2 * (1 - t) * t + to * t * t; 199 | } 200 | public static Vector3 Curve(Vector3 from, Vector3 control, Vector3 to, float t) 201 | { 202 | from.x = Curve(from.x, control.x, to.x, t); 203 | from.y = Curve(from.y, control.y, to.y, t); 204 | from.z = Curve(from.z, control.z, to.z, t); 205 | return from; 206 | } 207 | 208 | public static Vector3 Control(Vector3 from, Vector3 to, Vector3 curve) 209 | { 210 | //var center = Vector3.Lerp(from, to, 0.5f); 211 | //return center + (curve - center) * 2; 212 | var axis = Vector3.Normalize(to - from); 213 | var dot = Vector3.Dot(axis, curve - from); 214 | var linePoint = from + axis * dot; 215 | return linePoint + (curve - linePoint) * 2; 216 | } 217 | } 218 | 219 | public static class Triangulate 220 | { 221 | public static int[] Points(List points) 222 | { 223 | var indices = new List(); 224 | 225 | int n = points.Count; 226 | if (n < 3) 227 | return indices.ToArray(); 228 | 229 | int[] V = new int[n]; 230 | if (Area(points) > 0) 231 | { 232 | for (int v = 0; v < n; v++) 233 | V[v] = v; 234 | } 235 | else 236 | { 237 | for (int v = 0; v < n; v++) 238 | V[v] = (n - 1) - v; 239 | } 240 | 241 | int nv = n; 242 | int count = 2 * nv; 243 | for (int m = 0, v = nv - 1; nv > 2; ) 244 | { 245 | if ((count--) <= 0) 246 | return indices.ToArray(); 247 | 248 | int u = v; 249 | if (nv <= u) 250 | u = 0; 251 | v = u + 1; 252 | if (nv <= v) 253 | v = 0; 254 | int w = v + 1; 255 | if (nv <= w) 256 | w = 0; 257 | 258 | if (Snip(points, u, v, w, nv, V)) 259 | { 260 | int a, b, c, s, t; 261 | a = V[u]; 262 | b = V[v]; 263 | c = V[w]; 264 | indices.Add(a); 265 | indices.Add(b); 266 | indices.Add(c); 267 | m++; 268 | for (s = v, t = v + 1; t < nv; s++, t++) 269 | V[s] = V[t]; 270 | nv--; 271 | count = 2 * nv; 272 | } 273 | } 274 | 275 | indices.Reverse(); 276 | return indices.ToArray(); 277 | } 278 | 279 | static float Area(List points) 280 | { 281 | int n = points.Count; 282 | float A = 0.0f; 283 | for (int p = n - 1, q = 0; q < n; p = q++) 284 | { 285 | Vector3 pval = points[p]; 286 | Vector3 qval = points[q]; 287 | A += pval.x * qval.y - qval.x * pval.y; 288 | } 289 | return (A * 0.5f); 290 | } 291 | 292 | static bool Snip(List points, int u, int v, int w, int n, int[] V) 293 | { 294 | int p; 295 | Vector3 A = points[V[u]]; 296 | Vector3 B = points[V[v]]; 297 | Vector3 C = points[V[w]]; 298 | if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x)))) 299 | return false; 300 | for (p = 0; p < n; p++) 301 | { 302 | if ((p == u) || (p == v) || (p == w)) 303 | continue; 304 | Vector3 P = points[V[p]]; 305 | if (InsideTriangle(A, B, C, P)) 306 | return false; 307 | } 308 | return true; 309 | } 310 | 311 | static bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) 312 | { 313 | float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; 314 | float cCROSSap, bCROSScp, aCROSSbp; 315 | 316 | ax = C.x - B.x; ay = C.y - B.y; 317 | bx = A.x - C.x; by = A.y - C.y; 318 | cx = B.x - A.x; cy = B.y - A.y; 319 | apx = P.x - A.x; apy = P.y - A.y; 320 | bpx = P.x - B.x; bpy = P.y - B.y; 321 | cpx = P.x - C.x; cpy = P.y - C.y; 322 | 323 | aCROSSbp = ax * bpy - ay * bpx; 324 | cCROSSap = cx * apy - cy * apx; 325 | bCROSScp = bx * cpy - by * cpx; 326 | 327 | return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Assets/PolyMesh/Scripts/Editor/PolyMeshEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | 7 | [CustomEditor(typeof(PolyMesh))] 8 | public class PolyMeshEditor : Editor 9 | { 10 | enum State { Hover, Drag, BoxSelect, DragSelected, RotateSelected, ScaleSelected, Extrude } 11 | 12 | const float clickRadius = 0.12f; 13 | 14 | FieldInfo undoCallback; 15 | bool editing; 16 | bool tabDown; 17 | State state; 18 | 19 | List keyPoints; 20 | List curvePoints; 21 | List isCurve; 22 | 23 | Matrix4x4 worldToLocal; 24 | Quaternion inverseRotation; 25 | 26 | Vector3 mousePosition; 27 | Vector3 clickPosition; 28 | Vector3 screenMousePosition; 29 | MouseCursor mouseCursor = MouseCursor.Arrow; 30 | float snap; 31 | 32 | int dragIndex; 33 | List selectedIndices = new List(); 34 | int nearestLine; 35 | Vector3 splitPosition; 36 | bool extrudeKeyDown; 37 | bool doExtrudeUpdate; 38 | bool draggingCurve; 39 | 40 | #region Inspector GUI 41 | 42 | public override void OnInspectorGUI() 43 | { 44 | if (target == null) 45 | return; 46 | 47 | if (polyMesh.keyPoints.Count == 0) 48 | CreateSquare(polyMesh, 0.5f); 49 | 50 | //Toggle editing mode 51 | if (editing) 52 | { 53 | if (GUILayout.Button("Stop Editing")) 54 | { 55 | editing = false; 56 | HideWireframe(false); 57 | } 58 | } 59 | else if (GUILayout.Button("Edit PolyMesh")) 60 | { 61 | editing = true; 62 | HideWireframe(hideWireframe); 63 | } 64 | 65 | //UV settings 66 | if (uvSettings = EditorGUILayout.Foldout(uvSettings, "UVs")) 67 | { 68 | var uvPosition = EditorGUILayout.Vector2Field("Position", polyMesh.uvPosition); 69 | var uvScale = EditorGUILayout.FloatField("Scale", polyMesh.uvScale); 70 | var uvRotation = EditorGUILayout.Slider("Rotation", polyMesh.uvRotation, -180, 180) % 360; 71 | if (uvRotation < -180) 72 | uvRotation += 360; 73 | if (GUI.changed) 74 | { 75 | RecordUndo(); 76 | polyMesh.uvPosition = uvPosition; 77 | polyMesh.uvScale = uvScale; 78 | polyMesh.uvRotation = uvRotation; 79 | } 80 | if (GUILayout.Button("Reset UVs")) 81 | { 82 | polyMesh.uvPosition = Vector3.zero; 83 | polyMesh.uvScale = 1; 84 | polyMesh.uvRotation = 0; 85 | } 86 | } 87 | 88 | //Mesh settings 89 | if (meshSettings = EditorGUILayout.Foldout(meshSettings, "Mesh")) 90 | { 91 | var curveDetail = EditorGUILayout.Slider("Curve Detail", polyMesh.curveDetail, 0.01f, 1f); 92 | curveDetail = Mathf.Clamp(curveDetail, 0.01f, 1f); 93 | if (GUI.changed) 94 | { 95 | RecordUndo(); 96 | polyMesh.curveDetail = curveDetail; 97 | } 98 | 99 | //Buttons 100 | EditorGUILayout.BeginHorizontal(); 101 | if (GUILayout.Button("Build Mesh")) 102 | polyMesh.BuildMesh(); 103 | if (GUILayout.Button("Make Mesh Unique")) 104 | { 105 | RecordUndo(); 106 | polyMesh.GetComponent().mesh = null; 107 | polyMesh.BuildMesh(); 108 | } 109 | EditorGUILayout.EndHorizontal(); 110 | } 111 | 112 | //Create collider 113 | if (colliderSettings = EditorGUILayout.Foldout(colliderSettings, "Collider")) 114 | { 115 | //Collider depth 116 | var colliderDepth = EditorGUILayout.FloatField("Depth", polyMesh.colliderDepth); 117 | colliderDepth = Mathf.Max(colliderDepth, 0.01f); 118 | var buildColliderEdges = EditorGUILayout.Toggle("Build Edges", polyMesh.buildColliderEdges); 119 | var buildColliderFront = EditorGUILayout.Toggle("Build Font", polyMesh.buildColliderFront); 120 | if (GUI.changed) 121 | { 122 | RecordUndo(); 123 | polyMesh.colliderDepth = colliderDepth; 124 | polyMesh.buildColliderEdges = buildColliderEdges; 125 | polyMesh.buildColliderFront = buildColliderFront; 126 | } 127 | 128 | //Destroy collider 129 | if (polyMesh.meshCollider == null) 130 | { 131 | if (GUILayout.Button("Create Collider")) 132 | { 133 | RecordDeepUndo(); 134 | var obj = new GameObject("Collider", typeof(MeshCollider)); 135 | polyMesh.meshCollider = obj.GetComponent(); 136 | obj.transform.parent = polyMesh.transform; 137 | obj.transform.localPosition = Vector3.zero; 138 | } 139 | } 140 | else if (GUILayout.Button("Destroy Collider")) 141 | { 142 | RecordDeepUndo(); 143 | DestroyImmediate(polyMesh.meshCollider.gameObject); 144 | } 145 | } 146 | 147 | //Update mesh 148 | if (GUI.changed) 149 | polyMesh.BuildMesh(); 150 | 151 | //Editor settings 152 | if (editorSettings = EditorGUILayout.Foldout(editorSettings, "Editor")) 153 | { 154 | gridSnap = EditorGUILayout.FloatField("Grid Snap", gridSnap); 155 | autoSnap = EditorGUILayout.Toggle("Auto Snap", autoSnap); 156 | globalSnap = EditorGUILayout.Toggle("Global Snap", globalSnap); 157 | EditorGUI.BeginChangeCheck(); 158 | hideWireframe = EditorGUILayout.Toggle("Hide Wireframe", hideWireframe); 159 | if (EditorGUI.EndChangeCheck()) 160 | HideWireframe(hideWireframe); 161 | 162 | editKey = (KeyCode)EditorGUILayout.EnumPopup("[Toggle Edit] Key", editKey); 163 | selectAllKey = (KeyCode)EditorGUILayout.EnumPopup("[Select All] Key", selectAllKey); 164 | splitKey = (KeyCode)EditorGUILayout.EnumPopup("[Split] Key", splitKey); 165 | extrudeKey = (KeyCode)EditorGUILayout.EnumPopup("[Extrude] Key", extrudeKey); 166 | } 167 | } 168 | 169 | #endregion 170 | 171 | #region Scene GUI 172 | 173 | void OnSceneGUI() 174 | { 175 | if (target == null) 176 | return; 177 | 178 | if (KeyPressed(editKey)) 179 | editing = !editing; 180 | 181 | if (editing) 182 | { 183 | //Update lists 184 | if (keyPoints == null) 185 | { 186 | keyPoints = new List(polyMesh.keyPoints); 187 | curvePoints = new List(polyMesh.curvePoints); 188 | isCurve = new List(polyMesh.isCurve); 189 | } 190 | 191 | //Crazy hack to register undo 192 | if (undoCallback == null) 193 | { 194 | undoCallback = typeof(EditorApplication).GetField("undoRedoPerformed", BindingFlags.NonPublic | BindingFlags.Static); 195 | if (undoCallback != null) 196 | undoCallback.SetValue(null, new EditorApplication.CallbackFunction(OnUndoRedo)); 197 | } 198 | 199 | //Load handle matrix 200 | Handles.matrix = polyMesh.transform.localToWorldMatrix; 201 | 202 | //Draw points and lines 203 | DrawAxis(); 204 | Handles.color = Color.white; 205 | for (int i = 0; i < keyPoints.Count; i++) 206 | { 207 | Handles.color = nearestLine == i ? Color.green : Color.white; 208 | DrawSegment(i); 209 | if (selectedIndices.Contains(i)) 210 | { 211 | Handles.color = Color.green; 212 | DrawCircle(keyPoints[i], 0.08f); 213 | } 214 | else 215 | Handles.color = Color.white; 216 | DrawKeyPoint(i); 217 | if (isCurve[i]) 218 | { 219 | Handles.color = (draggingCurve && dragIndex == i) ? Color.white : Color.blue; 220 | DrawCurvePoint(i); 221 | } 222 | } 223 | 224 | //Quit on tool change 225 | if (e.type == EventType.KeyDown) 226 | { 227 | switch (e.keyCode) 228 | { 229 | case KeyCode.Q: 230 | case KeyCode.W: 231 | case KeyCode.E: 232 | case KeyCode.R: 233 | return; 234 | } 235 | } 236 | 237 | //Quit if panning or no camera exists 238 | if (Tools.current == Tool.View || (e.isMouse && e.button > 0) || Camera.current == null || e.type == EventType.ScrollWheel) 239 | return; 240 | 241 | //Quit if laying out 242 | if (e.type == EventType.Layout) 243 | { 244 | HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive)); 245 | return; 246 | } 247 | 248 | //Cursor rectangle 249 | EditorGUIUtility.AddCursorRect(new Rect(0, 0, Camera.current.pixelWidth, Camera.current.pixelHeight), mouseCursor); 250 | mouseCursor = MouseCursor.Arrow; 251 | 252 | //Extrude key state 253 | if (e.keyCode == extrudeKey) 254 | { 255 | if (extrudeKeyDown) 256 | { 257 | if (e.type == EventType.KeyUp) 258 | extrudeKeyDown = false; 259 | } 260 | else if (e.type == EventType.KeyDown) 261 | extrudeKeyDown = true; 262 | } 263 | 264 | //Update matrices and snap 265 | worldToLocal = polyMesh.transform.worldToLocalMatrix; 266 | inverseRotation = Quaternion.Inverse(polyMesh.transform.rotation) * Camera.current.transform.rotation; 267 | snap = gridSnap; 268 | 269 | //Update mouse position 270 | screenMousePosition = new Vector3(e.mousePosition.x, Camera.current.pixelHeight - e.mousePosition.y); 271 | var plane = new Plane(-polyMesh.transform.forward, polyMesh.transform.position); 272 | var ray = Camera.current.ScreenPointToRay(screenMousePosition); 273 | float hit; 274 | if (plane.Raycast(ray, out hit)) 275 | mousePosition = worldToLocal.MultiplyPoint(ray.GetPoint(hit)); 276 | else 277 | return; 278 | 279 | //Update nearest line and split position 280 | nearestLine = NearestLine(out splitPosition); 281 | 282 | //Update the state and repaint 283 | var newState = UpdateState(); 284 | if (state != newState) 285 | SetState(newState); 286 | HandleUtility.Repaint(); 287 | e.Use(); 288 | } 289 | } 290 | 291 | void HideWireframe(bool hide) 292 | { 293 | if (polyMesh.renderer != null) 294 | EditorUtility.SetSelectedWireframeHidden(polyMesh.renderer, hide); 295 | } 296 | 297 | void RecordUndo() 298 | { 299 | #if UNITY_4_3 300 | Undo.RecordObject(target, "PolyMesh Changed"); 301 | #else 302 | Undo.RegisterUndo(target, "PolyMesh Changed"); 303 | #endif 304 | } 305 | 306 | void RecordDeepUndo() 307 | { 308 | #if UNITY_4_3 309 | Undo.RegisterFullObjectHierarchyUndo(target); 310 | #else 311 | Undo.RegisterSceneUndo("PolyMesh Changed"); 312 | #endif 313 | 314 | } 315 | 316 | #endregion 317 | 318 | #region State Control 319 | 320 | //Initialize state 321 | void SetState(State newState) 322 | { 323 | state = newState; 324 | switch (state) 325 | { 326 | case State.Hover: 327 | break; 328 | } 329 | } 330 | 331 | //Update state 332 | State UpdateState() 333 | { 334 | switch (state) 335 | { 336 | //Hovering 337 | case State.Hover: 338 | 339 | DrawNearestLineAndSplit(); 340 | 341 | if (Tools.current == Tool.Move && TryDragSelected()) 342 | return State.DragSelected; 343 | if (Tools.current == Tool.Rotate && TryRotateSelected()) 344 | return State.RotateSelected; 345 | if (Tools.current == Tool.Scale && TryScaleSelected()) 346 | return State.ScaleSelected; 347 | if (Tools.current == Tool.Move && TryExtrude()) 348 | return State.Extrude; 349 | 350 | if (TrySelectAll()) 351 | return State.Hover; 352 | if (TrySplitLine()) 353 | return State.Hover; 354 | if (TryDeleteSelected()) 355 | return State.Hover; 356 | 357 | if (TryHoverCurvePoint(out dragIndex) && TryDragCurvePoint(dragIndex)) 358 | return State.Drag; 359 | if (TryHoverKeyPoint(out dragIndex) && TryDragKeyPoint(dragIndex)) 360 | return State.Drag; 361 | if (TryBoxSelect()) 362 | return State.BoxSelect; 363 | 364 | break; 365 | 366 | //Dragging 367 | case State.Drag: 368 | mouseCursor = MouseCursor.MoveArrow; 369 | DrawCircle(keyPoints[dragIndex], clickRadius); 370 | if (draggingCurve) 371 | MoveCurvePoint(dragIndex, mousePosition - clickPosition); 372 | else 373 | MoveKeyPoint(dragIndex, mousePosition - clickPosition); 374 | if (TryStopDrag()) 375 | return State.Hover; 376 | break; 377 | 378 | //Box Selecting 379 | case State.BoxSelect: 380 | if (TryBoxSelectEnd()) 381 | return State.Hover; 382 | break; 383 | 384 | //Dragging selected 385 | case State.DragSelected: 386 | mouseCursor = MouseCursor.MoveArrow; 387 | MoveSelected(mousePosition - clickPosition); 388 | if (TryStopDrag()) 389 | return State.Hover; 390 | break; 391 | 392 | //Rotating selected 393 | case State.RotateSelected: 394 | mouseCursor = MouseCursor.RotateArrow; 395 | RotateSelected(); 396 | if (TryStopDrag()) 397 | return State.Hover; 398 | break; 399 | 400 | //Scaling selected 401 | case State.ScaleSelected: 402 | mouseCursor = MouseCursor.ScaleArrow; 403 | ScaleSelected(); 404 | if (TryStopDrag()) 405 | return State.Hover; 406 | break; 407 | 408 | //Extruding 409 | case State.Extrude: 410 | mouseCursor = MouseCursor.MoveArrow; 411 | MoveSelected(mousePosition - clickPosition); 412 | if (doExtrudeUpdate && mousePosition != clickPosition) 413 | { 414 | UpdatePoly(false, false); 415 | doExtrudeUpdate = false; 416 | } 417 | if (TryStopDrag()) 418 | return State.Hover; 419 | break; 420 | } 421 | return state; 422 | } 423 | 424 | //Update the mesh on undo/redo 425 | void OnUndoRedo() 426 | { 427 | keyPoints = new List(polyMesh.keyPoints); 428 | curvePoints = new List(polyMesh.curvePoints); 429 | isCurve = new List(polyMesh.isCurve); 430 | polyMesh.BuildMesh(); 431 | } 432 | 433 | void LoadPoly() 434 | { 435 | for (int i = 0; i < keyPoints.Count; i++) 436 | { 437 | keyPoints[i] = polyMesh.keyPoints[i]; 438 | curvePoints[i] = polyMesh.curvePoints[i]; 439 | isCurve[i] = polyMesh.isCurve[i]; 440 | } 441 | } 442 | 443 | void TransformPoly(Matrix4x4 matrix) 444 | { 445 | for (int i = 0; i < keyPoints.Count; i++) 446 | { 447 | keyPoints[i] = matrix.MultiplyPoint(polyMesh.keyPoints[i]); 448 | curvePoints[i] = matrix.MultiplyPoint(polyMesh.curvePoints[i]); 449 | } 450 | } 451 | 452 | void UpdatePoly(bool sizeChanged, bool recordUndo) 453 | { 454 | if (recordUndo) 455 | RecordUndo(); 456 | if (sizeChanged) 457 | { 458 | polyMesh.keyPoints = new List(keyPoints); 459 | polyMesh.curvePoints = new List(curvePoints); 460 | polyMesh.isCurve = new List(isCurve); 461 | } 462 | else 463 | { 464 | for (int i = 0; i < keyPoints.Count; i++) 465 | { 466 | polyMesh.keyPoints[i] = keyPoints[i]; 467 | polyMesh.curvePoints[i] = curvePoints[i]; 468 | polyMesh.isCurve[i] = isCurve[i]; 469 | } 470 | } 471 | for (int i = 0; i < keyPoints.Count; i++) 472 | if (!isCurve[i]) 473 | polyMesh.curvePoints[i] = curvePoints[i] = Vector3.Lerp(keyPoints[i], keyPoints[(i + 1) % keyPoints.Count], 0.5f); 474 | polyMesh.BuildMesh(); 475 | } 476 | 477 | void MoveKeyPoint(int index, Vector3 amount) 478 | { 479 | var moveCurve = selectedIndices.Contains((index + 1) % keyPoints.Count); 480 | if (doSnap) 481 | { 482 | if (globalSnap) 483 | { 484 | keyPoints[index] = Snap(polyMesh.keyPoints[index] + amount); 485 | if (moveCurve) 486 | curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount); 487 | } 488 | else 489 | { 490 | amount = Snap(amount); 491 | keyPoints[index] = polyMesh.keyPoints[index] + amount; 492 | if (moveCurve) 493 | curvePoints[index] = polyMesh.curvePoints[index] + amount; 494 | } 495 | } 496 | else 497 | { 498 | keyPoints[index] = polyMesh.keyPoints[index] + amount; 499 | if (moveCurve) 500 | curvePoints[index] = polyMesh.curvePoints[index] + amount; 501 | } 502 | } 503 | 504 | void MoveCurvePoint(int index, Vector3 amount) 505 | { 506 | isCurve[index] = true; 507 | if (doSnap) 508 | { 509 | if (globalSnap) 510 | curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount); 511 | else 512 | curvePoints[index] = polyMesh.curvePoints[index] + amount; 513 | } 514 | else 515 | curvePoints[index] = polyMesh.curvePoints[index] + amount; 516 | } 517 | 518 | 519 | void MoveSelected(Vector3 amount) 520 | { 521 | foreach (var i in selectedIndices) 522 | MoveKeyPoint(i, amount); 523 | } 524 | 525 | void RotateSelected() 526 | { 527 | var center = GetSelectionCenter(); 528 | 529 | Handles.color = Color.white; 530 | Handles.DrawLine(center, clickPosition); 531 | Handles.color = Color.green; 532 | Handles.DrawLine(center, mousePosition); 533 | 534 | var clickOffset = clickPosition - center; 535 | var mouseOffset = mousePosition - center; 536 | var clickAngle = Mathf.Atan2(clickOffset.y, clickOffset.x); 537 | var mouseAngle = Mathf.Atan2(mouseOffset.y, mouseOffset.x); 538 | var angleOffset = mouseAngle - clickAngle; 539 | 540 | foreach (var i in selectedIndices) 541 | { 542 | var point = polyMesh.keyPoints[i]; 543 | var pointOffset = point - center; 544 | var a = Mathf.Atan2(pointOffset.y, pointOffset.x) + angleOffset; 545 | var d = pointOffset.magnitude; 546 | keyPoints[i] = center + new Vector3(Mathf.Cos(a) * d, Mathf.Sin(a) * d); 547 | } 548 | } 549 | 550 | void ScaleSelected() 551 | { 552 | Handles.color = Color.green; 553 | Handles.DrawLine(clickPosition, mousePosition); 554 | 555 | var center = GetSelectionCenter(); 556 | var scale = mousePosition - clickPosition; 557 | 558 | //Uniform scaling if shift pressed 559 | if (e.shift) 560 | { 561 | if (Mathf.Abs(scale.x) > Mathf.Abs(scale.y)) 562 | scale.y = scale.x; 563 | else 564 | scale.x = scale.y; 565 | } 566 | 567 | //Determine direction of scaling 568 | if (scale.x < 0) 569 | scale.x = 1 / (-scale.x + 1); 570 | else 571 | scale.x = 1 + scale.x; 572 | if (scale.y < 0) 573 | scale.y = 1 / (-scale.y + 1); 574 | else 575 | scale.y = 1 + scale.y; 576 | 577 | foreach (var i in selectedIndices) 578 | { 579 | var point = polyMesh.keyPoints[i]; 580 | var offset = point - center; 581 | offset.x *= scale.x; 582 | offset.y *= scale.y; 583 | keyPoints[i] = center + offset; 584 | } 585 | } 586 | 587 | #endregion 588 | 589 | #region Drawing 590 | 591 | void DrawAxis() 592 | { 593 | Handles.color = Color.red; 594 | var size = HandleUtility.GetHandleSize(Vector3.zero) * 0.1f; 595 | Handles.DrawLine(new Vector3(-size, 0), new Vector3(size, 0)); 596 | Handles.DrawLine(new Vector3(0, -size), new Vector2(0, size)); 597 | } 598 | 599 | void DrawKeyPoint(int index) 600 | { 601 | Handles.DotCap(0, keyPoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f); 602 | } 603 | 604 | void DrawCurvePoint(int index) 605 | { 606 | Handles.DotCap(0, curvePoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f); 607 | } 608 | 609 | void DrawSegment(int index) 610 | { 611 | var from = keyPoints[index]; 612 | var to = keyPoints[(index + 1) % keyPoints.Count]; 613 | if (isCurve[index]) 614 | { 615 | var control = Bezier.Control(from, to, curvePoints[index]); 616 | var count = Mathf.Ceil(1 / polyMesh.curveDetail); 617 | for (int i = 0; i < count; i++) 618 | Handles.DrawLine(Bezier.Curve(from, control, to, i / count), Bezier.Curve(from, control, to, (i + 1) / count)); 619 | } 620 | else 621 | Handles.DrawLine(from, to); 622 | } 623 | 624 | void DrawCircle(Vector3 position, float size) 625 | { 626 | Handles.CircleCap(0, position, inverseRotation, HandleUtility.GetHandleSize(position) * size); 627 | } 628 | 629 | void DrawNearestLineAndSplit() 630 | { 631 | if (nearestLine >= 0) 632 | { 633 | Handles.color = Color.green; 634 | DrawSegment(nearestLine); 635 | Handles.color = Color.red; 636 | Handles.DotCap(0, splitPosition, Quaternion.identity, HandleUtility.GetHandleSize(splitPosition) * 0.03f); 637 | } 638 | } 639 | 640 | #endregion 641 | 642 | #region State Checking 643 | 644 | bool TryHoverKeyPoint(out int index) 645 | { 646 | if (TryHover(keyPoints, Color.white, out index)) 647 | { 648 | mouseCursor = MouseCursor.MoveArrow; 649 | return true; 650 | } 651 | return false; 652 | } 653 | 654 | bool TryHoverCurvePoint(out int index) 655 | { 656 | if (TryHover(curvePoints, Color.white, out index)) 657 | { 658 | mouseCursor = MouseCursor.MoveArrow; 659 | return true; 660 | } 661 | return false; 662 | } 663 | 664 | bool TryDragKeyPoint(int index) 665 | { 666 | if (TryDrag(keyPoints, index)) 667 | { 668 | draggingCurve = false; 669 | return true; 670 | } 671 | return false; 672 | } 673 | 674 | bool TryDragCurvePoint(int index) 675 | { 676 | if (TryDrag(curvePoints, index)) 677 | { 678 | draggingCurve = true; 679 | return true; 680 | } 681 | return false; 682 | } 683 | 684 | bool TryHover(List points, Color color, out int index) 685 | { 686 | if (Tools.current == Tool.Move) 687 | { 688 | index = NearestPoint(points); 689 | if (index >= 0 && IsHovering(points[index])) 690 | { 691 | Handles.color = color; 692 | DrawCircle(points[index], clickRadius); 693 | return true; 694 | } 695 | } 696 | index = -1; 697 | return false; 698 | } 699 | 700 | bool TryDrag(List points, int index) 701 | { 702 | if (e.type == EventType.MouseDown && IsHovering(points[index])) 703 | { 704 | clickPosition = mousePosition; 705 | return true; 706 | } 707 | return false; 708 | } 709 | 710 | bool TryStopDrag() 711 | { 712 | if (e.type == EventType.MouseUp) 713 | { 714 | dragIndex = -1; 715 | UpdatePoly(false, state != State.Extrude); 716 | return true; 717 | } 718 | return false; 719 | } 720 | 721 | bool TryBoxSelect() 722 | { 723 | if (e.type == EventType.MouseDown) 724 | { 725 | clickPosition = mousePosition; 726 | return true; 727 | } 728 | return false; 729 | } 730 | 731 | bool TryBoxSelectEnd() 732 | { 733 | var min = new Vector3(Mathf.Min(clickPosition.x, mousePosition.x), Mathf.Min(clickPosition.y, mousePosition.y)); 734 | var max = new Vector3(Mathf.Max(clickPosition.x, mousePosition.x), Mathf.Max(clickPosition.y, mousePosition.y)); 735 | Handles.color = Color.white; 736 | Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(max.x, min.y)); 737 | Handles.DrawLine(new Vector3(min.x, max.y), new Vector3(max.x, max.y)); 738 | Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(min.x, max.y)); 739 | Handles.DrawLine(new Vector3(max.x, min.y), new Vector3(max.x, max.y)); 740 | 741 | if (e.type == EventType.MouseUp) 742 | { 743 | var rect = new Rect(min.x, min.y, max.x - min.x, max.y - min.y); 744 | 745 | if (!control) 746 | selectedIndices.Clear(); 747 | for (int i = 0; i < keyPoints.Count; i++) 748 | if (rect.Contains(keyPoints[i])) 749 | selectedIndices.Add(i); 750 | 751 | return true; 752 | } 753 | return false; 754 | } 755 | 756 | bool TryDragSelected() 757 | { 758 | if (selectedIndices.Count > 0 && TryDragButton(GetSelectionCenter(), 0.2f)) 759 | { 760 | clickPosition = mousePosition; 761 | return true; 762 | } 763 | return false; 764 | } 765 | 766 | bool TryRotateSelected() 767 | { 768 | if (selectedIndices.Count > 0 && TryRotateButton(GetSelectionCenter(), 0.3f)) 769 | { 770 | clickPosition = mousePosition; 771 | return true; 772 | } 773 | return false; 774 | } 775 | 776 | bool TryScaleSelected() 777 | { 778 | if (selectedIndices.Count > 0 && TryScaleButton(GetSelectionCenter(), 0.3f)) 779 | { 780 | clickPosition = mousePosition; 781 | return true; 782 | } 783 | return false; 784 | } 785 | 786 | bool TryDragButton(Vector3 position, float size) 787 | { 788 | size *= HandleUtility.GetHandleSize(position); 789 | if (Vector3.Distance(mousePosition, position) < size) 790 | { 791 | if (e.type == EventType.MouseDown) 792 | return true; 793 | else 794 | { 795 | mouseCursor = MouseCursor.MoveArrow; 796 | Handles.color = Color.green; 797 | } 798 | } 799 | else 800 | Handles.color = Color.white; 801 | var buffer = size / 2; 802 | Handles.DrawLine(new Vector3(position.x - buffer, position.y), new Vector3(position.x + buffer, position.y)); 803 | Handles.DrawLine(new Vector3(position.x, position.y - buffer), new Vector3(position.x, position.y + buffer)); 804 | Handles.RectangleCap(0, position, Quaternion.identity, size); 805 | return false; 806 | } 807 | 808 | bool TryRotateButton(Vector3 position, float size) 809 | { 810 | size *= HandleUtility.GetHandleSize(position); 811 | var dist = Vector3.Distance(mousePosition, position); 812 | var buffer = size / 4; 813 | if (dist < size + buffer && dist > size - buffer) 814 | { 815 | if (e.type == EventType.MouseDown) 816 | return true; 817 | else 818 | { 819 | mouseCursor = MouseCursor.RotateArrow; 820 | Handles.color = Color.green; 821 | } 822 | } 823 | else 824 | Handles.color = Color.white; 825 | Handles.CircleCap(0, position, inverseRotation, size - buffer / 2); 826 | Handles.CircleCap(0, position, inverseRotation, size + buffer / 2); 827 | return false; 828 | } 829 | 830 | bool TryScaleButton(Vector3 position, float size) 831 | { 832 | size *= HandleUtility.GetHandleSize(position); 833 | if (Vector3.Distance(mousePosition, position) < size) 834 | { 835 | if (e.type == EventType.MouseDown) 836 | return true; 837 | else 838 | { 839 | mouseCursor = MouseCursor.ScaleArrow; 840 | Handles.color = Color.green; 841 | } 842 | } 843 | else 844 | Handles.color = Color.white; 845 | var buffer = size / 4; 846 | Handles.DrawLine(new Vector3(position.x - size - buffer, position.y), new Vector3(position.x - size + buffer, position.y)); 847 | Handles.DrawLine(new Vector3(position.x + size - buffer, position.y), new Vector3(position.x + size + buffer, position.y)); 848 | Handles.DrawLine(new Vector3(position.x, position.y - size - buffer), new Vector3(position.x, position.y - size + buffer)); 849 | Handles.DrawLine(new Vector3(position.x, position.y + size - buffer), new Vector3(position.x, position.y + size + buffer)); 850 | Handles.RectangleCap(0, position, Quaternion.identity, size); 851 | return false; 852 | } 853 | 854 | bool TrySelectAll() 855 | { 856 | if (KeyPressed(selectAllKey)) 857 | { 858 | selectedIndices.Clear(); 859 | for (int i = 0; i < keyPoints.Count; i++) 860 | selectedIndices.Add(i); 861 | return true; 862 | } 863 | return false; 864 | } 865 | 866 | bool TrySplitLine() 867 | { 868 | if (nearestLine >= 0 && KeyPressed(splitKey)) 869 | { 870 | if (nearestLine == keyPoints.Count - 1) 871 | { 872 | keyPoints.Add(splitPosition); 873 | curvePoints.Add(Vector3.zero); 874 | isCurve.Add(false); 875 | } 876 | else 877 | { 878 | keyPoints.Insert(nearestLine + 1, splitPosition); 879 | curvePoints.Insert(nearestLine + 1, Vector3.zero); 880 | isCurve.Insert(nearestLine + 1, false); 881 | } 882 | isCurve[nearestLine] = false; 883 | UpdatePoly(true, true); 884 | return true; 885 | } 886 | return false; 887 | } 888 | 889 | bool TryExtrude() 890 | { 891 | if (nearestLine >= 0 && extrudeKeyDown && e.type == EventType.MouseDown) 892 | { 893 | var a = nearestLine; 894 | var b = (nearestLine + 1) % keyPoints.Count; 895 | if (b == 0 && a == keyPoints.Count - 1) 896 | { 897 | //Extrude between the first and last points 898 | keyPoints.Add(polyMesh.keyPoints[a]); 899 | keyPoints.Add(polyMesh.keyPoints[b]); 900 | curvePoints.Add(Vector3.zero); 901 | curvePoints.Add(Vector3.zero); 902 | isCurve.Add(false); 903 | isCurve.Add(false); 904 | 905 | selectedIndices.Clear(); 906 | selectedIndices.Add(keyPoints.Count - 2); 907 | selectedIndices.Add(keyPoints.Count - 1); 908 | } 909 | else 910 | { 911 | //Extrude between two inner points 912 | var pointA = keyPoints[a]; 913 | var pointB = keyPoints[b]; 914 | keyPoints.Insert(a + 1, pointA); 915 | keyPoints.Insert(a + 2, pointB); 916 | curvePoints.Insert(a + 1, Vector3.zero); 917 | curvePoints.Insert(a + 2, Vector3.zero); 918 | isCurve.Insert(a + 1, false); 919 | isCurve.Insert(a + 2, false); 920 | 921 | selectedIndices.Clear(); 922 | selectedIndices.Add(a + 1); 923 | selectedIndices.Add(a + 2); 924 | } 925 | isCurve[nearestLine] = false; 926 | 927 | clickPosition = mousePosition; 928 | doExtrudeUpdate = true; 929 | UpdatePoly(true, true); 930 | return true; 931 | } 932 | return false; 933 | } 934 | 935 | bool TryDeleteSelected() 936 | { 937 | if (KeyPressed(KeyCode.Backspace) || KeyPressed(KeyCode.Delete)) 938 | { 939 | if (selectedIndices.Count > 0) 940 | { 941 | if (keyPoints.Count - selectedIndices.Count >= 3) 942 | { 943 | for (int i = selectedIndices.Count - 1; i >= 0; i--) 944 | { 945 | var index = selectedIndices[i]; 946 | keyPoints.RemoveAt(index); 947 | curvePoints.RemoveAt(index); 948 | isCurve.RemoveAt(index); 949 | } 950 | selectedIndices.Clear(); 951 | UpdatePoly(true, true); 952 | return true; 953 | } 954 | } 955 | else if (IsHovering(curvePoints[nearestLine])) 956 | { 957 | isCurve[nearestLine] = false; 958 | UpdatePoly(false, true); 959 | } 960 | } 961 | return false; 962 | } 963 | 964 | bool IsHovering(Vector3 point) 965 | { 966 | return Vector3.Distance(mousePosition, point) < HandleUtility.GetHandleSize(point) * clickRadius; 967 | } 968 | 969 | int NearestPoint(List points) 970 | { 971 | var near = -1; 972 | var nearDist = float.MaxValue; 973 | for (int i = 0; i < points.Count; i++) 974 | { 975 | var dist = Vector3.Distance(points[i], mousePosition); 976 | if (dist < nearDist) 977 | { 978 | nearDist = dist; 979 | near = i; 980 | } 981 | } 982 | return near; 983 | } 984 | 985 | int NearestLine(out Vector3 position) 986 | { 987 | var near = -1; 988 | var nearDist = float.MaxValue; 989 | position = keyPoints[0]; 990 | var linePos = Vector3.zero; 991 | for (int i = 0; i < keyPoints.Count; i++) 992 | { 993 | var j = (i + 1) % keyPoints.Count; 994 | var line = keyPoints[j] - keyPoints[i]; 995 | var offset = mousePosition - keyPoints[i]; 996 | var dot = Vector3.Dot(line.normalized, offset); 997 | if (dot >= 0 && dot <= line.magnitude) 998 | { 999 | if (isCurve[i]) 1000 | linePos = Bezier.Curve(keyPoints[i], Bezier.Control(keyPoints[i], keyPoints[j], curvePoints[i]), keyPoints[j], dot / line.magnitude); 1001 | else 1002 | linePos = keyPoints[i] + line.normalized * dot; 1003 | var dist = Vector3.Distance(linePos, mousePosition); 1004 | if (dist < nearDist) 1005 | { 1006 | nearDist = dist; 1007 | position = linePos; 1008 | near = i; 1009 | } 1010 | } 1011 | } 1012 | return near; 1013 | } 1014 | 1015 | bool KeyPressed(KeyCode key) 1016 | { 1017 | return e.type == EventType.KeyDown && e.keyCode == key; 1018 | } 1019 | 1020 | bool KeyReleased(KeyCode key) 1021 | { 1022 | return e.type == EventType.KeyUp && e.keyCode == key; 1023 | } 1024 | 1025 | Vector3 Snap(Vector3 value) 1026 | { 1027 | value.x = Mathf.Round(value.x / snap) * snap; 1028 | value.y = Mathf.Round(value.y / snap) * snap; 1029 | return value; 1030 | } 1031 | 1032 | Vector3 GetSelectionCenter() 1033 | { 1034 | if (polyMesh.keyPoints.Count > 1) 1035 | { 1036 | var center = Vector3.zero; 1037 | var area = 0f; 1038 | var b = polyMesh.keyPoints[polyMesh.keyPoints.Count - 1]; 1039 | foreach (var i in selectedIndices) 1040 | { 1041 | var a = polyMesh.keyPoints[i]; 1042 | var k = a.y * b.x - a.x * b.y; 1043 | area += k; 1044 | center.x += (a.x + b.x) * k; 1045 | center.y += (a.y + b.y) * k; 1046 | b = a; 1047 | } 1048 | area *= 3; 1049 | if (Mathf.Approximately(area, 0)) 1050 | return Vector3.zero; 1051 | else 1052 | return center / area; 1053 | } 1054 | else 1055 | return polyMesh.keyPoints[0]; 1056 | } 1057 | 1058 | #endregion 1059 | 1060 | #region Properties 1061 | 1062 | PolyMesh polyMesh 1063 | { 1064 | get { return (PolyMesh)target; } 1065 | } 1066 | 1067 | Event e 1068 | { 1069 | get { return Event.current; } 1070 | } 1071 | 1072 | bool control 1073 | { 1074 | get { return Application.platform == RuntimePlatform.OSXEditor ? e.command : e.control; } 1075 | } 1076 | 1077 | bool doSnap 1078 | { 1079 | get { return autoSnap ? !control : control; } 1080 | } 1081 | 1082 | static bool meshSettings 1083 | { 1084 | get { return EditorPrefs.GetBool("PolyMeshEditor_meshSettings", false); } 1085 | set { EditorPrefs.SetBool("PolyMeshEditor_meshSettings", value); } 1086 | } 1087 | 1088 | static bool colliderSettings 1089 | { 1090 | get { return EditorPrefs.GetBool("PolyMeshEditor_colliderSettings", false); } 1091 | set { EditorPrefs.SetBool("PolyMeshEditor_colliderSettings", value); } 1092 | } 1093 | 1094 | static bool uvSettings 1095 | { 1096 | get { return EditorPrefs.GetBool("PolyMeshEditor_uvSettings", false); } 1097 | set { EditorPrefs.SetBool("PolyMeshEditor_uvSettings", value); } 1098 | } 1099 | 1100 | static bool editorSettings 1101 | { 1102 | get { return EditorPrefs.GetBool("PolyMeshEditor_editorSettings", false); } 1103 | set { EditorPrefs.SetBool("PolyMeshEditor_editorSettings", value); } 1104 | } 1105 | 1106 | static bool autoSnap 1107 | { 1108 | get { return EditorPrefs.GetBool("PolyMeshEditor_autoSnap", false); } 1109 | set { EditorPrefs.SetBool("PolyMeshEditor_autoSnap", value); } 1110 | } 1111 | 1112 | static bool globalSnap 1113 | { 1114 | get { return EditorPrefs.GetBool("PolyMeshEditor_globalSnap", false); } 1115 | set { EditorPrefs.SetBool("PolyMeshEditor_globalSnap", value); } 1116 | } 1117 | 1118 | static float gridSnap 1119 | { 1120 | get { return EditorPrefs.GetFloat("PolyMeshEditor_gridSnap", 1); } 1121 | set { EditorPrefs.SetFloat("PolyMeshEditor_gridSnap", value); } 1122 | } 1123 | 1124 | static bool hideWireframe 1125 | { 1126 | get { return EditorPrefs.GetBool("PolyMeshEditor_hideWireframe", true); } 1127 | set { EditorPrefs.SetBool("PolyMeshEditor_hideWireframe", value); } 1128 | } 1129 | 1130 | public KeyCode editKey 1131 | { 1132 | get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_editKey", (int)KeyCode.Tab); } 1133 | set { EditorPrefs.SetInt("PolyMeshEditor_editKey", (int)value); } 1134 | } 1135 | 1136 | public KeyCode selectAllKey 1137 | { 1138 | get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_selectAllKey", (int)KeyCode.A); } 1139 | set { EditorPrefs.SetInt("PolyMeshEditor_selectAllKey", (int)value); } 1140 | } 1141 | 1142 | public KeyCode splitKey 1143 | { 1144 | get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_splitKey", (int)KeyCode.S); } 1145 | set { EditorPrefs.SetInt("PolyMeshEditor_splitKey", (int)value); } 1146 | } 1147 | 1148 | public KeyCode extrudeKey 1149 | { 1150 | get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_extrudeKey", (int)KeyCode.D); } 1151 | set { EditorPrefs.SetInt("PolyMeshEditor_extrudeKey", (int)value); } 1152 | } 1153 | 1154 | #endregion 1155 | 1156 | #region Menu Items 1157 | 1158 | [MenuItem("GameObject/Create Other/PolyMesh", false, 1000)] 1159 | static void CreatePolyMesh() 1160 | { 1161 | var obj = new GameObject("PolyMesh", typeof(MeshFilter), typeof(MeshRenderer)); 1162 | var polyMesh = obj.AddComponent(); 1163 | CreateSquare(polyMesh, 0.5f); 1164 | } 1165 | 1166 | static void CreateSquare(PolyMesh polyMesh, float size) 1167 | { 1168 | polyMesh.keyPoints.AddRange(new Vector3[] { new Vector3(size, size), new Vector3(size, -size), new Vector3(-size, -size), new Vector3(-size, size)} ); 1169 | polyMesh.curvePoints.AddRange(new Vector3[] { Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero } ); 1170 | polyMesh.isCurve.AddRange(new bool[] { false, false, false, false } ); 1171 | polyMesh.BuildMesh(); 1172 | } 1173 | 1174 | #endregion 1175 | } 1176 | --------------------------------------------------------------------------------