├── 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 |
--------------------------------------------------------------------------------