Skip to content

Instantly share code, notes, and snippets.

@mitu217
Last active June 29, 2017 15:28
Show Gist options
  • Save mitu217/a1ac977cea29ee370dbd to your computer and use it in GitHub Desktop.
Save mitu217/a1ac977cea29ee370dbd to your computer and use it in GitHub Desktop.

Revisions

  1. mitu217 revised this gist Jun 29, 2015. 1 changed file with 348 additions and 0 deletions.
    348 changes: 348 additions & 0 deletions GenerateMesh.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,348 @@
    using UnityEngine;
    using System.Collections.Generic;

    [ExecuteInEditMode]
    [RequireComponent(typeof(MeshRenderer))]
    [RequireComponent(typeof(MeshFilter))]
    public class GenerateMesh : MonoBehaviour
    {
    /// <summary>
    /// パス座標
    /// </summary>
    public List<Vector3> keyPoints = new List<Vector3>();
    /// <summary>
    /// 頂点パス座標
    /// </summary>
    public List<Vector3> curvePoints = new List<Vector3>();
    /// <summary>
    /// 各頂点間が曲線を持つかどうか
    /// </summary>
    public List<bool> isCurve = new List<bool>();
    /// <summary>
    /// Mesh Collider
    /// </summary>
    public MeshCollider meshCollider;
    [Range(0.01f, 1)] public float curveDetail = 0.1f;
    public float colliderDepth = 1;
    public bool buildColliderEdges = true;
    public bool buildColliderFront;
    /// <summary>
    /// テクスチャの描画位置
    /// </summary>
    public Vector2 uvPosition;
    /// <summary>
    /// UVの拡大率
    /// </summary>
    public float uvScale = 1;
    /// <summary>
    /// UVの回転率
    /// </summary>
    public float uvRotation;

    public List<Vector3> GetEdgePoints() {
    //Build the point list and calculate curves
    var points = new List<Vector3>();
    for (int i = 0; i < keyPoints.Count; i++) {
    if (isCurve[i]) {
    //Get the curve control point
    var a = keyPoints[i];
    var c = keyPoints[(i + 1) % keyPoints.Count];
    var b = Bezier.Control(a, c, curvePoints[i]);

    //Build the curve
    var count = Mathf.Ceil(1 / curveDetail);
    for (int j = 0; j < count; j++)
    {
    var t = (float)j / count;
    points.Add(Bezier.Curve(a, b, c, t));
    }
    }
    else
    points.Add(keyPoints[i]);
    }
    return points;
    }

    public void BuildMesh()
    {
    var points = GetEdgePoints();
    var vertices = points.ToArray();

    //Build the index array
    var indices = new List<int>();
    while (indices.Count < points.Count)
    indices.Add(indices.Count);

    //Build the triangle array
    var triangles = Triangulate.Points(points);

    //Build the uv array
    var scale = uvScale != 0 ? (1 / uvScale) : 0;
    var matrix = Matrix4x4.TRS(-uvPosition, Quaternion.Euler(0, 0, uvRotation), new Vector3(scale, scale, 1));
    var uv = new Vector2[points.Count];
    for (int i = 0; i < uv.Length; i++)
    {
    var p = matrix.MultiplyPoint(points[i]);
    uv[i] = new Vector2(p.x, p.y);
    }

    //Find the mesh (create it if it doesn't exist)
    var meshFilter = GetComponent<MeshFilter>();
    var mesh = meshFilter.sharedMesh;
    if (mesh == null)
    {
    mesh = new Mesh();
    mesh.name = "PolySprite_Mesh";
    meshFilter.mesh = mesh;
    }

    //Update the mesh
    mesh.Clear();
    mesh.vertices = vertices;
    mesh.uv = uv;
    mesh.triangles = triangles;
    mesh.RecalculateNormals();
    mesh.Optimize();

    //Update collider after the mesh is updated
    UpdateCollider(points, triangles);
    }

    void UpdateCollider(List<Vector3> points, int[] tris)
    {
    //Update the mesh collider if there is one
    if (meshCollider != null)
    {
    var vertices = new List<Vector3>();
    var triangles = new List<int>();

    if (buildColliderEdges)
    {
    //Build vertices array
    var offset = new Vector3(0, 0, colliderDepth / 2);
    for (int i = 0; i < points.Count; i++)
    {
    vertices.Add(points[i] + offset);
    vertices.Add(points[i] - offset);
    }

    //Build triangles array
    for (int a = 0; a < vertices.Count; a += 2)
    {
    var b = (a + 1) % vertices.Count;
    var c = (a + 2) % vertices.Count;
    var d = (a + 3) % vertices.Count;
    triangles.Add(a);
    triangles.Add(c);
    triangles.Add(b);
    triangles.Add(c);
    triangles.Add(d);
    triangles.Add(b);
    }
    }

    if (buildColliderFront)
    {
    for (int i = 0; i < tris.Length; i++)
    tris[i] += vertices.Count;
    vertices.AddRange(points);
    triangles.AddRange(tris);
    }

    //Find the mesh (create it if it doesn't exist)
    var mesh = meshCollider.sharedMesh;
    if (mesh == null)
    {
    mesh = new Mesh();
    mesh.name = "PolySprite_Collider";
    }

    //Update the mesh
    mesh.Clear();
    mesh.vertices = vertices.ToArray();
    mesh.triangles = triangles.ToArray();
    mesh.RecalculateNormals();
    mesh.Optimize();
    meshCollider.sharedMesh = null;
    meshCollider.sharedMesh = mesh;
    }
    }

    bool IsRightTurn(List<Vector3> points, int a, int b, int c)
    {
    var ab = points[b] - points[a];
    var bc = points[c] - points[b];
    return (ab.x * bc.y - ab.y * bc.x) < 0;
    }

    bool IntersectsExistingLines(List<Vector3> points, Vector3 a, Vector3 b)
    {
    for (int i = 0; i < points.Count; i++)
    if (LinesIntersect(points, a, b, points[i], points[(i + 1) % points.Count]))
    return true;
    return false;
    }

    bool LinesIntersect(List<Vector3> points, Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4)
    {
    if (point1 == point3 || point1 == point4 || point2 == point3 || point2 == point4)
    return false;

    float ua = (point4.x - point3.x) * (point1.y - point3.y) - (point4.y - point3.y) * (point1.x - point3.x);
    float ub = (point2.x - point1.x) * (point1.y - point3.y) - (point2.y - point1.y) * (point1.x - point3.x);
    float denominator = (point4.y - point3.y) * (point2.x - point1.x) - (point4.x - point3.x) * (point2.y - point1.y);

    if (Mathf.Abs(denominator) <= 0.00001f)
    {
    if (Mathf.Abs(ua) <= 0.00001f && Mathf.Abs(ub) <= 0.00001f)
    return true;
    }
    else
    {
    ua /= denominator;
    ub /= denominator;

    if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
    return true;
    }

    return false;
    }
    }

    public static class Bezier
    {
    public static float Curve(float from, float control, float to, float t)
    {
    return from * (1 - t) * (1 - t) + control * 2 * (1 - t) * t + to * t * t;
    }
    public static Vector3 Curve(Vector3 from, Vector3 control, Vector3 to, float t)
    {
    from.x = Curve(from.x, control.x, to.x, t);
    from.y = Curve(from.y, control.y, to.y, t);
    from.z = Curve(from.z, control.z, to.z, t);
    return from;
    }

    public static Vector3 Control(Vector3 from, Vector3 to, Vector3 curve)
    {
    //var center = Vector3.Lerp(from, to, 0.5f);
    //return center + (curve - center) * 2;
    var axis = Vector3.Normalize(to - from);
    var dot = Vector3.Dot(axis, curve - from);
    var linePoint = from + axis * dot;
    return linePoint + (curve - linePoint) * 2;
    }
    }

    public static class Triangulate
    {
    public static int[] Points(List<Vector3> points)
    {
    var indices = new List<int>();

    int n = points.Count;
    if (n < 3)
    return indices.ToArray();

    int[] V = new int[n];
    if (Area(points) > 0)
    {
    for (int v = 0; v < n; v++)
    V[v] = v;
    }
    else
    {
    for (int v = 0; v < n; v++)
    V[v] = (n - 1) - v;
    }

    int nv = n;
    int count = 2 * nv;
    for (int m = 0, v = nv - 1; nv > 2; )
    {
    if ((count--) <= 0)
    return indices.ToArray();

    int u = v;
    if (nv <= u)
    u = 0;
    v = u + 1;
    if (nv <= v)
    v = 0;
    int w = v + 1;
    if (nv <= w)
    w = 0;

    if (Snip(points, u, v, w, nv, V))
    {
    int a, b, c, s, t;
    a = V[u];
    b = V[v];
    c = V[w];
    indices.Add(a);
    indices.Add(b);
    indices.Add(c);
    m++;
    for (s = v, t = v + 1; t < nv; s++, t++)
    V[s] = V[t];
    nv--;
    count = 2 * nv;
    }
    }

    indices.Reverse();
    return indices.ToArray();
    }

    static float Area(List<Vector3> points)
    {
    int n = points.Count;
    float A = 0.0f;
    for (int p = n - 1, q = 0; q < n; p = q++)
    {
    Vector3 pval = points[p];
    Vector3 qval = points[q];
    A += pval.x * qval.y - qval.x * pval.y;
    }
    return (A * 0.5f);
    }

    static bool Snip(List<Vector3> points, int u, int v, int w, int n, int[] V)
    {
    int p;
    Vector3 A = points[V[u]];
    Vector3 B = points[V[v]];
    Vector3 C = points[V[w]];
    if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
    return false;
    for (p = 0; p < n; p++)
    {
    if ((p == u) || (p == v) || (p == w))
    continue;
    Vector3 P = points[V[p]];
    if (InsideTriangle(A, B, C, P))
    return false;
    }
    return true;
    }

    static bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
    {
    float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
    float cCROSSap, bCROSScp, aCROSSbp;

    ax = C.x - B.x; ay = C.y - B.y;
    bx = A.x - C.x; by = A.y - C.y;
    cx = B.x - A.x; cy = B.y - A.y;
    apx = P.x - A.x; apy = P.y - A.y;
    bpx = P.x - B.x; bpy = P.y - B.y;
    cpx = P.x - C.x; cpy = P.y - C.y;

    aCROSSbp = ax * bpy - ay * bpx;
    cCROSSap = cx * apy - cy * apx;
    bCROSScp = bx * cpy - by * cpx;

    return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
    }
    }
  2. mitu217 created this gist Jun 29, 2015.
    372 changes: 372 additions & 0 deletions PolyMesh.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,372 @@
    using UnityEngine;
    using System.Collections.Generic;

    //コンポーネントを自動的に追加
    [RequireComponent(typeof(MeshFilter))]
    public class PolyMesh : MonoBehaviour
    {
    /// <summary>
    ///
    /// </summary>
    public List<Vector3> keyPoints = new List<Vector3>();

    /// <summary>
    ///
    /// </summary>
    public List<Vector3> curvePoints = new List<Vector3>();

    /// <summary>
    ///
    /// </summary>
    public List<bool> isCurve = new List<bool>();

    /// <summary>
    ///
    /// </summary>
    public MeshCollider meshCollider;

    /// <summary>
    ///
    /// </summary>
    [Range(0.01f, 1)] public float curveDetail = 0.1f;

    /// <summary>
    ///
    /// </summary>
    public float colliderDepth = 1;

    /// <summary>
    ///
    /// </summary>
    public bool buildColliderEdges = true;

    /// <summary>
    ///
    /// </summary>
    public bool buildColliderFront;

    /// <summary>
    ///
    /// </summary>
    public Vector2 uvPosition;

    /// <summary>
    ///
    /// </summary>
    public float uvScale = 1;

    /// <summary>
    ///
    /// </summary>
    public float uvRotation;

    public List<Vector3> GetEdgePoints()
    {
    //Build the point list and calculate curves
    var points = new List<Vector3>();
    for (int i = 0; i < keyPoints.Count; i++)
    {
    if (isCurve[i])
    {
    //Get the curve control point
    var a = keyPoints[i];
    var c = keyPoints[(i + 1) % keyPoints.Count];
    var b = Bezier.Control(a, c, curvePoints[i]);

    //Build the curve
    var count = Mathf.Ceil(1 / curveDetail);
    for (int j = 0; j < count; j++)
    {
    var t = (float)j / count;
    points.Add(Bezier.Curve(a, b, c, t));
    }
    }
    else
    points.Add(keyPoints[i]);
    }
    return points;
    }

    public void BuildMesh()
    {
    var points = GetEdgePoints();
    var vertices = points.ToArray();

    //Build the index array
    var indices = new List<int>();
    while (indices.Count < points.Count)
    indices.Add(indices.Count);

    //Build the triangle array
    var triangles = Triangulate.Points(points);

    //Build the uv array
    var scale = uvScale != 0 ? (1 / uvScale) : 0;
    var matrix = Matrix4x4.TRS(-uvPosition, Quaternion.Euler(0, 0, uvRotation), new Vector3(scale, scale, 1));
    var uv = new Vector2[points.Count];
    for (int i = 0; i < uv.Length; i++)
    {
    var p = matrix.MultiplyPoint(points[i]);
    uv[i] = new Vector2(p.x, p.y);
    }

    //Find the mesh (create it if it doesn't exist)
    var meshFilter = GetComponent<MeshFilter>();
    var mesh = meshFilter.sharedMesh;
    if (mesh == null)
    {
    mesh = new Mesh();
    mesh.name = "PolySprite_Mesh";
    meshFilter.mesh = mesh;
    }

    //Update the mesh
    mesh.Clear();
    mesh.vertices = vertices;
    mesh.uv = uv;
    mesh.triangles = triangles;
    mesh.RecalculateNormals();
    mesh.Optimize();

    //Update collider after the mesh is updated
    UpdateCollider(points, triangles);
    }

    void UpdateCollider(List<Vector3> points, int[] tris)
    {
    //Update the mesh collider if there is one
    if (meshCollider != null)
    {
    var vertices = new List<Vector3>();
    var triangles = new List<int>();

    if (buildColliderEdges)
    {
    //Build vertices array
    var offset = new Vector3(0, 0, colliderDepth / 2);
    for (int i = 0; i < points.Count; i++)
    {
    vertices.Add(points[i] + offset);
    vertices.Add(points[i] - offset);
    }

    //Build triangles array
    for (int a = 0; a < vertices.Count; a += 2)
    {
    var b = (a + 1) % vertices.Count;
    var c = (a + 2) % vertices.Count;
    var d = (a + 3) % vertices.Count;
    triangles.Add(a);
    triangles.Add(c);
    triangles.Add(b);
    triangles.Add(c);
    triangles.Add(d);
    triangles.Add(b);
    }
    }

    if (buildColliderFront)
    {
    for (int i = 0; i < tris.Length; i++)
    tris[i] += vertices.Count;
    vertices.AddRange(points);
    triangles.AddRange(tris);
    }

    //Find the mesh (create it if it doesn't exist)
    var mesh = meshCollider.sharedMesh;
    if (mesh == null)
    {
    mesh = new Mesh();
    mesh.name = "PolySprite_Collider";
    }

    //Update the mesh
    mesh.Clear();
    mesh.vertices = vertices.ToArray();
    mesh.triangles = triangles.ToArray();
    mesh.RecalculateNormals();
    mesh.Optimize();
    meshCollider.sharedMesh = null;
    meshCollider.sharedMesh = mesh;
    }
    }

    bool IsRightTurn(List<Vector3> points, int a, int b, int c)
    {
    var ab = points[b] - points[a];
    var bc = points[c] - points[b];
    return (ab.x * bc.y - ab.y * bc.x) < 0;
    }

    bool IntersectsExistingLines(List<Vector3> points, Vector3 a, Vector3 b)
    {
    for (int i = 0; i < points.Count; i++)
    if (LinesIntersect(points, a, b, points[i], points[(i + 1) % points.Count]))
    return true;
    return false;
    }

    bool LinesIntersect(List<Vector3> points, Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4)
    {
    if (point1 == point3 || point1 == point4 || point2 == point3 || point2 == point4)
    return false;

    float ua = (point4.x - point3.x) * (point1.y - point3.y) - (point4.y - point3.y) * (point1.x - point3.x);
    float ub = (point2.x - point1.x) * (point1.y - point3.y) - (point2.y - point1.y) * (point1.x - point3.x);
    float denominator = (point4.y - point3.y) * (point2.x - point1.x) - (point4.x - point3.x) * (point2.y - point1.y);

    if (Mathf.Abs(denominator) <= 0.00001f)
    {
    if (Mathf.Abs(ua) <= 0.00001f && Mathf.Abs(ub) <= 0.00001f)
    return true;
    }
    else
    {
    ua /= denominator;
    ub /= denominator;

    if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
    return true;
    }

    return false;
    }
    }

    public static class Bezier
    {
    public static float Curve(float from, float control, float to, float t)
    {
    return from * (1 - t) * (1 - t) + control * 2 * (1 - t) * t + to * t * t;
    }
    public static Vector3 Curve(Vector3 from, Vector3 control, Vector3 to, float t)
    {
    from.x = Curve(from.x, control.x, to.x, t);
    from.y = Curve(from.y, control.y, to.y, t);
    from.z = Curve(from.z, control.z, to.z, t);
    return from;
    }

    public static Vector3 Control(Vector3 from, Vector3 to, Vector3 curve)
    {
    //var center = Vector3.Lerp(from, to, 0.5f);
    //return center + (curve - center) * 2;
    var axis = Vector3.Normalize(to - from);
    var dot = Vector3.Dot(axis, curve - from);
    var linePoint = from + axis * dot;
    return linePoint + (curve - linePoint) * 2;
    }
    }

    public static class Triangulate
    {
    public static int[] Points(List<Vector3> points)
    {
    var indices = new List<int>();

    int n = points.Count;
    if (n < 3)
    return indices.ToArray();

    int[] V = new int[n];
    if (Area(points) > 0)
    {
    for (int v = 0; v < n; v++)
    V[v] = v;
    }
    else
    {
    for (int v = 0; v < n; v++)
    V[v] = (n - 1) - v;
    }

    int nv = n;
    int count = 2 * nv;
    for (int m = 0, v = nv - 1; nv > 2; )
    {
    if ((count--) <= 0)
    return indices.ToArray();

    int u = v;
    if (nv <= u)
    u = 0;
    v = u + 1;
    if (nv <= v)
    v = 0;
    int w = v + 1;
    if (nv <= w)
    w = 0;

    if (Snip(points, u, v, w, nv, V))
    {
    int a, b, c, s, t;
    a = V[u];
    b = V[v];
    c = V[w];
    indices.Add(a);
    indices.Add(b);
    indices.Add(c);
    m++;
    for (s = v, t = v + 1; t < nv; s++, t++)
    V[s] = V[t];
    nv--;
    count = 2 * nv;
    }
    }

    indices.Reverse();
    return indices.ToArray();
    }

    static float Area(List<Vector3> points)
    {
    int n = points.Count;
    float A = 0.0f;
    for (int p = n - 1, q = 0; q < n; p = q++)
    {
    Vector3 pval = points[p];
    Vector3 qval = points[q];
    A += pval.x * qval.y - qval.x * pval.y;
    }
    return (A * 0.5f);
    }

    static bool Snip(List<Vector3> points, int u, int v, int w, int n, int[] V)
    {
    int p;
    Vector3 A = points[V[u]];
    Vector3 B = points[V[v]];
    Vector3 C = points[V[w]];
    if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
    return false;
    for (p = 0; p < n; p++)
    {
    if ((p == u) || (p == v) || (p == w))
    continue;
    Vector3 P = points[V[p]];
    if (InsideTriangle(A, B, C, P))
    return false;
    }
    return true;
    }

    static bool InsideTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
    {
    float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
    float cCROSSap, bCROSScp, aCROSSbp;

    ax = C.x - B.x; ay = C.y - B.y;
    bx = A.x - C.x; by = A.y - C.y;
    cx = B.x - A.x; cy = B.y - A.y;
    apx = P.x - A.x; apy = P.y - A.y;
    bpx = P.x - B.x; bpy = P.y - B.y;
    cpx = P.x - C.x; cpy = P.y - C.y;

    aCROSSbp = ax * bpy - ay * bpx;
    cCROSSap = cx * apy - cy * apx;
    bCROSScp = bx * cpy - by * cpx;

    return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
    }
    }
    1,091 changes: 1,091 additions & 0 deletions PolyMeshEditor.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1091 @@
    using UnityEngine;
    using UnityEditor;
    using System.Collections.Generic;
    using System.Reflection;

    [CustomEditor(typeof(PolyMesh))]
    public class PolyMeshEditor : Editor
    {
    /// <summary>
    /// Sceneビュー上でのステータスを示す構造体
    /// </summary>
    enum State { Hover, Drag, BoxSelect, DragSelected, RotateSelected, ScaleSelected, Extrude }

    /// <summary>
    /// クリック判定領域
    /// </summary>
    const float clickRadius = 0.12f;

    /// <summary>
    /// ???
    /// </summary>
    FieldInfo undoCallback;
    /// <summary>
    /// Editモードかどうか
    /// </summary>
    bool editing;
    /// <summary>
    /// ???
    /// </summary>
    bool tabDown;
    /// <summary>
    /// ???
    /// </summary>
    State state;

    /// <summary>
    /// 直線パス
    /// </summary>
    List<Vector3> keyPoints;
    /// <summary>
    /// 曲線パス
    /// </summary>
    List<Vector3> curvePoints;
    /// <summary>
    /// カーブするかどうか???
    /// </summary>
    List<bool> isCurve;

    /// <summary>
    /// global座標をlocal座標で持つ???
    /// </summary>
    Matrix4x4 worldToLocal;
    /// <summary>
    /// 回転角度 Quaternionでもつの???
    /// </summary>
    Quaternion inverseRotation;

    /// <summary>
    /// マウス座標
    /// </sumamry>
    Vector3 mousePosition;
    /// <summary>
    /// クリック座標
    /// </summary>
    Vector3 clickPosition;
    /// <summary>
    /// スクリーン上でのマウスの座標
    /// </summary>
    Vector3 screenMousePosition;
    /// <summary>
    /// マウスカーソルの状態をArrowに指定
    /// </summary>
    MouseCursor mouseCursor = MouseCursor.Arrow;
    /// <summary>
    /// スナップ???
    /// </summary>
    float snap;

    /// <summary>
    /// ???
    /// </summary>
    int dragIndex;
    /// <summary>
    /// ???
    /// </summary>
    List<int> selectedIndices = new List<int>();
    /// <summary>
    /// ???
    /// </summary>
    int nearestLine;
    /// <summary>
    /// ???
    /// </summary>
    Vector3 splitPosition;
    /// <summary>
    /// ???
    /// </summary>
    bool extrudeKeyDown;
    /// <summary>
    /// ???
    /// </summary>
    bool doExtrudeUpdate;
    /// <summary>
    /// ???
    /// </summary>
    bool draggingCurve;

    #region Inspector GUI
    public override void OnInspectorGUI() {
    if (target == null) {
    return;
    }

    if (polyMesh.keyPoints.Count == 0) {
    CreateSquare(polyMesh, 0.5f);
    }

    // Editモードに入るかどうか
    if (editing) {
    if (GUILayout.Button("Stop Editing"))
    {
    editing = false;
    HideWireframe(false);
    }
    }
    else if (GUILayout.Button("Edit PolyMesh")) {
    editing = true;
    HideWireframe(hideWireframe);
    }

    // UV設定
    if (uvSettings = EditorGUILayout.Foldout(uvSettings, "UVs")) {
    var uvPosition = EditorGUILayout.Vector2Field("Position", polyMesh.uvPosition);
    var uvScale = EditorGUILayout.FloatField("Scale", polyMesh.uvScale);
    var uvRotation = EditorGUILayout.Slider("Rotation", polyMesh.uvRotation, -180, 180) % 360;
    if (uvRotation < -180)
    uvRotation += 360;
    if (GUI.changed) {
    RecordUndo();
    polyMesh.uvPosition = uvPosition;
    polyMesh.uvScale = uvScale;
    polyMesh.uvRotation = uvRotation;
    }
    if (GUILayout.Button("Reset UVs")) {
    polyMesh.uvPosition = Vector3.zero;
    polyMesh.uvScale = 1;
    polyMesh.uvRotation = 0;
    }
    }

    // Mesh設定
    if (meshSettings = EditorGUILayout.Foldout(meshSettings, "Mesh")) {
    var curveDetail = EditorGUILayout.Slider("Curve Detail", polyMesh.curveDetail, 0.01f, 1f);
    curveDetail = Mathf.Clamp(curveDetail, 0.01f, 1f);
    if (GUI.changed) {
    RecordUndo();
    polyMesh.curveDetail = curveDetail;
    }

    //Buttons
    EditorGUILayout.BeginHorizontal();
    if (GUILayout.Button("Build Mesh")) {
    polyMesh.BuildMesh();
    }
    if (GUILayout.Button("Make Mesh Unique")) {
    RecordUndo();
    polyMesh.GetComponent<MeshFilter>().mesh = null;
    polyMesh.BuildMesh();
    }
    EditorGUILayout.EndHorizontal();
    }

    // Colliderの生成
    if (colliderSettings = EditorGUILayout.Foldout(colliderSettings, "Collider")) {
    // Colliderの深さ設定(z軸の長さ)
    var colliderDepth = EditorGUILayout.FloatField("Depth", polyMesh.colliderDepth);
    colliderDepth = Mathf.Max(colliderDepth, 0.01f);
    var buildColliderEdges = EditorGUILayout.Toggle("Build Edges", polyMesh.buildColliderEdges);
    var buildColliderFront = EditorGUILayout.Toggle("Build Font", polyMesh.buildColliderFront);
    if (GUI.changed) {
    RecordUndo();
    polyMesh.colliderDepth = colliderDepth;
    polyMesh.buildColliderEdges = buildColliderEdges;
    polyMesh.buildColliderFront = buildColliderFront;
    }

    // Colliderの破壊
    if (polyMesh.meshCollider == null) {
    if (GUILayout.Button("Create Collider")) {
    RecordDeepUndo();
    var obj = new GameObject("Collider", typeof(MeshCollider));
    polyMesh.meshCollider = obj.GetComponent<MeshCollider>();
    obj.transform.parent = polyMesh.transform;
    obj.transform.localPosition = Vector3.zero;
    }
    } else if (GUILayout.Button("Destroy Collider")) {
    RecordDeepUndo();
    DestroyImmediate(polyMesh.meshCollider.gameObject);
    }
    }

    //Update mesh
    if (GUI.changed) {
    polyMesh.BuildMesh();
    }

    //Editor settings
    if (editorSettings = EditorGUILayout.Foldout(editorSettings, "Editor")) {
    gridSnap = EditorGUILayout.FloatField("Grid Snap", gridSnap);
    autoSnap = EditorGUILayout.Toggle("Auto Snap", autoSnap);
    globalSnap = EditorGUILayout.Toggle("Global Snap", globalSnap);
    EditorGUI.BeginChangeCheck();
    hideWireframe = EditorGUILayout.Toggle("Hide Wireframe", hideWireframe);
    if (EditorGUI.EndChangeCheck()) {
    HideWireframe(hideWireframe);
    }

    editKey = (KeyCode)EditorGUILayout.EnumPopup("[Toggle Edit] Key", editKey);
    selectAllKey = (KeyCode)EditorGUILayout.EnumPopup("[Select All] Key", selectAllKey);
    splitKey = (KeyCode)EditorGUILayout.EnumPopup("[Split] Key", splitKey);
    extrudeKey = (KeyCode)EditorGUILayout.EnumPopup("[Extrude] Key", extrudeKey);
    }
    }
    #endregion

    #region Scene GUI
    void OnSceneGUI() {
    if (target == null) {
    return;
    }

    if (KeyPressed(editKey)) {
    editing = !editing;
    }

    if (editing) {
    // 各種パスを取得
    if (keyPoints == null) {
    keyPoints = new List<Vector3>(polyMesh.keyPoints);
    curvePoints = new List<Vector3>(polyMesh.curvePoints);
    isCurve = new List<bool>(polyMesh.isCurve);
    }

    // Undo時の動作を設定
    if (undoCallback == null) {
    undoCallback = typeof(EditorApplication).GetField("undoRedoPerformed", BindingFlags.NonPublic | BindingFlags.Static);
    if (undoCallback != null) {
    undoCallback.SetValue(null, new EditorApplication.CallbackFunction(OnUndoRedo));
    }
    }

    // ハンドルの座標を読み込む
    Handles.matrix = polyMesh.transform.localToWorldMatrix;

    // パスと線の描画
    DrawAxis();
    Handles.color = Color.white;
    for (int i = 0; i < keyPoints.Count; i++) {
    // PolyMeshの外枠を描画
    Handles.color = nearestLine == i ? Color.green : Color.white;
    DrawSegment(i);
    // 選択中のパスの描画
    if (selectedIndices.Contains(i)) {
    Handles.color = Color.green;
    DrawCircle(keyPoints[i], 0.08f);
    } else {
    Handles.color = Color.white;
    }
    DrawKeyPoint(i);
    // 曲線パスについての描画???
    if (isCurve[i]) {
    Handles.color = (draggingCurve && dragIndex == i) ? Color.white : Color.blue;
    DrawCurvePoint(i);
    }
    }

    // ツール切り替え時はキャンセル
    if (e.type == EventType.KeyDown) {
    switch (e.keyCode) {
    case KeyCode.Q:
    case KeyCode.W:
    case KeyCode.E:
    case KeyCode.R:
    return;
    }
    }

    //パニング中、もしくはカメラがシーン中にない場合はキャンセル
    if (Tools.current == Tool.View || (e.isMouse && e.button > 0) || Camera.current == null || e.type == EventType.ScrollWheel) {
    return;
    }
    //レイアウトの更新時はキャンセル
    if (e.type == EventType.Layout) {
    HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
    return;
    }

    //Cursor rectangle
    EditorGUIUtility.AddCursorRect(new Rect(0, 0, Camera.current.pixelWidth, Camera.current.pixelHeight), mouseCursor);
    mouseCursor = MouseCursor.Arrow;

    //Extrude key state
    if (e.keyCode == extrudeKey) {
    if (extrudeKeyDown) {
    if (e.type == EventType.KeyUp)
    extrudeKeyDown = false;
    } else if (e.type == EventType.KeyDown) {
    extrudeKeyDown = true;
    }
    }

    //Update matrices and snap
    worldToLocal = polyMesh.transform.worldToLocalMatrix;
    inverseRotation = Quaternion.Inverse(polyMesh.transform.rotation) * Camera.current.transform.rotation;
    snap = gridSnap;

    //Update mouse position
    screenMousePosition = new Vector3(e.mousePosition.x, Camera.current.pixelHeight - e.mousePosition.y);
    var plane = new Plane(-polyMesh.transform.forward, polyMesh.transform.position);
    var ray = Camera.current.ScreenPointToRay(screenMousePosition);
    float hit;
    if (plane.Raycast(ray, out hit)) {
    mousePosition = worldToLocal.MultiplyPoint(ray.GetPoint(hit));
    } else {
    return;
    }

    //Update nearest line and split position
    nearestLine = NearestLine(out splitPosition);

    //Update the state and repaint
    var newState = UpdateState();
    if (state != newState)
    SetState(newState);
    HandleUtility.Repaint();
    e.Use();
    }
    }

    void HideWireframe(bool hide)
    {
    Renderer renderer = polyMesh.GetComponent<Renderer>();
    if (renderer != null) {
    EditorUtility.SetSelectedWireframeHidden(renderer, hide);
    }
    }

    void RecordUndo() {
    Undo.RecordObject(target, "PolyMesh Changed");
    }

    void RecordDeepUndo() {
    Undo.RegisterFullObjectHierarchyUndo(target, "PolyMesh Changed");
    }

    #endregion

    #region State Control

    //Initialize state
    void SetState(State newState) {
    state = newState;
    switch (state) {
    case State.Hover:
    break;
    }
    }

    //Update state
    State UpdateState() {
    switch (state) {
    //Hovering
    case State.Hover:
    DrawNearestLineAndSplit();

    if (Tools.current == Tool.Move && TryDragSelected())
    return State.DragSelected;
    if (Tools.current == Tool.Rotate && TryRotateSelected())
    return State.RotateSelected;
    if (Tools.current == Tool.Scale && TryScaleSelected())
    return State.ScaleSelected;
    if (Tools.current == Tool.Move && TryExtrude())
    return State.Extrude;

    if (TrySelectAll())
    return State.Hover;
    if (TrySplitLine())
    return State.Hover;
    if (TryDeleteSelected())
    return State.Hover;

    if (TryHoverCurvePoint(out dragIndex) && TryDragCurvePoint(dragIndex))
    return State.Drag;
    if (TryHoverKeyPoint(out dragIndex) && TryDragKeyPoint(dragIndex))
    return State.Drag;
    if (TryBoxSelect())
    return State.BoxSelect;

    break;

    //Dragging
    case State.Drag:
    mouseCursor = MouseCursor.MoveArrow;
    DrawCircle(keyPoints[dragIndex], clickRadius);
    if (draggingCurve)
    MoveCurvePoint(dragIndex, mousePosition - clickPosition);
    else
    MoveKeyPoint(dragIndex, mousePosition - clickPosition);
    if (TryStopDrag())
    return State.Hover;
    break;

    //Box Selecting
    case State.BoxSelect:
    if (TryBoxSelectEnd())
    return State.Hover;
    break;

    //Dragging selected
    case State.DragSelected:
    mouseCursor = MouseCursor.MoveArrow;
    MoveSelected(mousePosition - clickPosition);
    if (TryStopDrag())
    return State.Hover;
    break;

    //Rotating selected
    case State.RotateSelected:
    mouseCursor = MouseCursor.RotateArrow;
    RotateSelected();
    if (TryStopDrag())
    return State.Hover;
    break;

    //Scaling selected
    case State.ScaleSelected:
    mouseCursor = MouseCursor.ScaleArrow;
    ScaleSelected();
    if (TryStopDrag())
    return State.Hover;
    break;

    //Extruding
    case State.Extrude:
    mouseCursor = MouseCursor.MoveArrow;
    MoveSelected(mousePosition - clickPosition);
    if (doExtrudeUpdate && mousePosition != clickPosition)
    {
    UpdatePoly(false, false);
    doExtrudeUpdate = false;
    }
    if (TryStopDrag())
    return State.Hover;
    break;
    }
    return state;
    }

    //Update the mesh on undo/redo
    void OnUndoRedo() {
    keyPoints = new List<Vector3>(polyMesh.keyPoints);
    curvePoints = new List<Vector3>(polyMesh.curvePoints);
    isCurve = new List<bool>(polyMesh.isCurve);
    polyMesh.BuildMesh();
    }

    void LoadPoly() {
    for (int i = 0; i < keyPoints.Count; i++) {
    keyPoints[i] = polyMesh.keyPoints[i];
    curvePoints[i] = polyMesh.curvePoints[i];
    isCurve[i] = polyMesh.isCurve[i];
    }
    }

    void TransformPoly(Matrix4x4 matrix) {
    for (int i = 0; i < keyPoints.Count; i++) {
    keyPoints[i] = matrix.MultiplyPoint(polyMesh.keyPoints[i]);
    curvePoints[i] = matrix.MultiplyPoint(polyMesh.curvePoints[i]);
    }
    }

    void UpdatePoly(bool sizeChanged, bool recordUndo) {
    if (recordUndo) {
    RecordUndo();
    }
    if (sizeChanged) {
    polyMesh.keyPoints = new List<Vector3>(keyPoints);
    polyMesh.curvePoints = new List<Vector3>(curvePoints);
    polyMesh.isCurve = new List<bool>(isCurve);
    } else {
    for (int i = 0; i < keyPoints.Count; i++) {
    polyMesh.keyPoints[i] = keyPoints[i];
    polyMesh.curvePoints[i] = curvePoints[i];
    polyMesh.isCurve[i] = isCurve[i];
    }
    }
    for (int i = 0; i < keyPoints.Count; i++){
    if (!isCurve[i]) {
    polyMesh.curvePoints[i] = curvePoints[i] = Vector3.Lerp(keyPoints[i], keyPoints[(i + 1) % keyPoints.Count], 0.5f);
    }
    }
    polyMesh.BuildMesh();
    }

    void MoveKeyPoint(int index, Vector3 amount) {
    var moveCurve = selectedIndices.Contains((index + 1) % keyPoints.Count);
    if (doSnap) {
    if (globalSnap) {
    keyPoints[index] = Snap(polyMesh.keyPoints[index] + amount);
    if (moveCurve){
    curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount);
    }
    } else {
    amount = Snap(amount);
    keyPoints[index] = polyMesh.keyPoints[index] + amount;
    if (moveCurve) {
    curvePoints[index] = polyMesh.curvePoints[index] + amount;
    }
    }
    } else {
    keyPoints[index] = polyMesh.keyPoints[index] + amount;
    if (moveCurve) {
    curvePoints[index] = polyMesh.curvePoints[index] + amount;
    }
    }
    }

    void MoveCurvePoint(int index, Vector3 amount) {
    isCurve[index] = true;
    if (doSnap) {
    if (globalSnap){
    curvePoints[index] = Snap(polyMesh.curvePoints[index] + amount);
    } else {
    curvePoints[index] = polyMesh.curvePoints[index] + amount;
    }
    } else {
    curvePoints[index] = polyMesh.curvePoints[index] + amount;
    }
    }

    void MoveSelected(Vector3 amount) {
    foreach (var i in selectedIndices) {
    MoveKeyPoint(i, amount);
    }
    }

    void RotateSelected() {
    var center = GetSelectionCenter();

    Handles.color = Color.white;
    Handles.DrawLine(center, clickPosition);
    Handles.color = Color.green;
    Handles.DrawLine(center, mousePosition);

    var clickOffset = clickPosition - center;
    var mouseOffset = mousePosition - center;
    var clickAngle = Mathf.Atan2(clickOffset.y, clickOffset.x);
    var mouseAngle = Mathf.Atan2(mouseOffset.y, mouseOffset.x);
    var angleOffset = mouseAngle - clickAngle;

    foreach (var i in selectedIndices) {
    var point = polyMesh.keyPoints[i];
    var pointOffset = point - center;
    var a = Mathf.Atan2(pointOffset.y, pointOffset.x) + angleOffset;
    var d = pointOffset.magnitude;
    keyPoints[i] = center + new Vector3(Mathf.Cos(a) * d, Mathf.Sin(a) * d);
    }
    }

    void ScaleSelected() {
    Handles.color = Color.green;
    Handles.DrawLine(clickPosition, mousePosition);

    var center = GetSelectionCenter();
    var scale = mousePosition - clickPosition;

    //Uniform scaling if shift pressed
    if (e.shift) {
    if (Mathf.Abs(scale.x) > Mathf.Abs(scale.y)) {
    scale.y = scale.x;
    } else {
    scale.x = scale.y;
    }
    }

    //Determine direction of scaling
    if (scale.x < 0) {
    scale.x = 1 / (-scale.x + 1);
    }else {
    scale.x = 1 + scale.x;
    }
    if (scale.y < 0) {
    scale.y = 1 / (-scale.y + 1);
    }else {
    scale.y = 1 + scale.y;
    }

    foreach (var i in selectedIndices) {
    var point = polyMesh.keyPoints[i];
    var offset = point - center;
    offset.x *= scale.x;
    offset.y *= scale.y;
    keyPoints[i] = center + offset;
    }
    }

    #endregion

    #region Drawing

    void DrawAxis() {
    Handles.color = Color.red;
    var size = HandleUtility.GetHandleSize(Vector3.zero) * 0.1f;
    Handles.DrawLine(new Vector3(-size, 0), new Vector3(size, 0));
    Handles.DrawLine(new Vector3(0, -size), new Vector2(0, size));
    }

    void DrawKeyPoint(int index) {
    Handles.DotCap(0, keyPoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f);
    }

    void DrawCurvePoint(int index) {
    Handles.DotCap(0, curvePoints[index], Quaternion.identity, HandleUtility.GetHandleSize(keyPoints[index]) * 0.03f);
    }

    void DrawSegment(int index) {
    var from = keyPoints[index];
    var to = keyPoints[(index + 1) % keyPoints.Count];
    if (isCurve[index]) {
    var control = Bezier.Control(from, to, curvePoints[index]);
    var count = Mathf.Ceil(1 / polyMesh.curveDetail);
    for (int i = 0; i < count; i++) {
    Handles.DrawLine(Bezier.Curve(from, control, to, i / count), Bezier.Curve(from, control, to, (i + 1) / count));
    }
    } else {
    Handles.DrawLine(from, to);
    }
    }

    void DrawCircle(Vector3 position, float size) {
    Handles.CircleCap(0, position, inverseRotation, HandleUtility.GetHandleSize(position) * size);
    }

    void DrawNearestLineAndSplit() {
    if (nearestLine >= 0) {
    Handles.color = Color.green;
    DrawSegment(nearestLine);
    Handles.color = Color.red;
    Handles.DotCap(0, splitPosition, Quaternion.identity, HandleUtility.GetHandleSize(splitPosition) * 0.03f);
    }
    }

    #endregion

    #region State Checking

    bool TryHoverKeyPoint(out int index) {
    if (TryHover(keyPoints, Color.white, out index)) {
    mouseCursor = MouseCursor.MoveArrow;
    return true;
    }
    return false;
    }

    bool TryHoverCurvePoint(out int index) {
    if (TryHover(curvePoints, Color.white, out index)) {
    mouseCursor = MouseCursor.MoveArrow;
    return true;
    }
    return false;
    }

    bool TryDragKeyPoint(int index) {
    if (TryDrag(keyPoints, index)) {
    draggingCurve = false;
    return true;
    }
    return false;
    }

    bool TryDragCurvePoint(int index) {
    if (TryDrag(curvePoints, index)) {
    draggingCurve = true;
    return true;
    }
    return false;
    }

    bool TryHover(List<Vector3> points, Color color, out int index) {
    if (Tools.current == Tool.Move) {
    index = NearestPoint(points);
    if (index >= 0 && IsHovering(points[index])) {
    Handles.color = color;
    DrawCircle(points[index], clickRadius);
    return true;
    }
    }
    index = -1;
    return false;
    }

    bool TryDrag(List<Vector3> points, int index) {
    if (e.type == EventType.MouseDown && IsHovering(points[index])) {
    clickPosition = mousePosition;
    return true;
    }
    return false;
    }

    bool TryStopDrag() {
    if (e.type == EventType.MouseUp) {
    dragIndex = -1;
    UpdatePoly(false, state != State.Extrude);
    return true;
    }
    return false;
    }

    bool TryBoxSelect() {
    if (e.type == EventType.MouseDown) {
    clickPosition = mousePosition;
    return true;
    }
    return false;
    }

    bool TryBoxSelectEnd() {
    var min = new Vector3(Mathf.Min(clickPosition.x, mousePosition.x), Mathf.Min(clickPosition.y, mousePosition.y));
    var max = new Vector3(Mathf.Max(clickPosition.x, mousePosition.x), Mathf.Max(clickPosition.y, mousePosition.y));
    Handles.color = Color.white;
    Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(max.x, min.y));
    Handles.DrawLine(new Vector3(min.x, max.y), new Vector3(max.x, max.y));
    Handles.DrawLine(new Vector3(min.x, min.y), new Vector3(min.x, max.y));
    Handles.DrawLine(new Vector3(max.x, min.y), new Vector3(max.x, max.y));

    if (e.type == EventType.MouseUp) {
    var rect = new Rect(min.x, min.y, max.x - min.x, max.y - min.y);

    if (!control) {
    selectedIndices.Clear();
    }
    for (int i = 0; i < keyPoints.Count; i++){
    if (rect.Contains(keyPoints[i])) {
    selectedIndices.Add(i);
    }
    }
    return true;
    }
    return false;
    }

    bool TryDragSelected() {
    if (selectedIndices.Count > 0 && TryDragButton(GetSelectionCenter(), 0.2f)) {
    clickPosition = mousePosition;
    return true;
    }
    return false;
    }

    bool TryRotateSelected() {
    if (selectedIndices.Count > 0 && TryRotateButton(GetSelectionCenter(), 0.3f)) {
    clickPosition = mousePosition;
    return true;
    }
    return false;
    }

    bool TryScaleSelected() {
    if (selectedIndices.Count > 0 && TryScaleButton(GetSelectionCenter(), 0.3f)) {
    clickPosition = mousePosition;
    return true;
    }
    return false;
    }

    bool TryDragButton(Vector3 position, float size) {
    size *= HandleUtility.GetHandleSize(position);
    if (Vector3.Distance(mousePosition, position) < size) {
    if (e.type == EventType.MouseDown) {
    return true;
    } else {
    mouseCursor = MouseCursor.MoveArrow;
    Handles.color = Color.green;
    }
    } else {
    Handles.color = Color.white;
    }
    var buffer = size / 2;
    Handles.DrawLine(new Vector3(position.x - buffer, position.y), new Vector3(position.x + buffer, position.y));
    Handles.DrawLine(new Vector3(position.x, position.y - buffer), new Vector3(position.x, position.y + buffer));
    Handles.RectangleCap(0, position, Quaternion.identity, size);
    return false;
    }

    bool TryRotateButton(Vector3 position, float size) {
    size *= HandleUtility.GetHandleSize(position);
    var dist = Vector3.Distance(mousePosition, position);
    var buffer = size / 4;
    if (dist < size + buffer && dist > size - buffer) {
    if (e.type == EventType.MouseDown) {
    return true;
    }else {
    mouseCursor = MouseCursor.RotateArrow;
    Handles.color = Color.green;
    }
    } else {
    Handles.color = Color.white;
    }
    Handles.CircleCap(0, position, inverseRotation, size - buffer / 2);
    Handles.CircleCap(0, position, inverseRotation, size + buffer / 2);
    return false;
    }

    bool TryScaleButton(Vector3 position, float size) {
    size *= HandleUtility.GetHandleSize(position);
    if (Vector3.Distance(mousePosition, position) < size) {
    if (e.type == EventType.MouseDown) {
    return true;
    }else {
    mouseCursor = MouseCursor.ScaleArrow;
    Handles.color = Color.green;
    }
    }
    else
    Handles.color = Color.white;
    var buffer = size / 4;
    Handles.DrawLine(new Vector3(position.x - size - buffer, position.y), new Vector3(position.x - size + buffer, position.y));
    Handles.DrawLine(new Vector3(position.x + size - buffer, position.y), new Vector3(position.x + size + buffer, position.y));
    Handles.DrawLine(new Vector3(position.x, position.y - size - buffer), new Vector3(position.x, position.y - size + buffer));
    Handles.DrawLine(new Vector3(position.x, position.y + size - buffer), new Vector3(position.x, position.y + size + buffer));
    Handles.RectangleCap(0, position, Quaternion.identity, size);
    return false;
    }

    bool TrySelectAll(){
    if (KeyPressed(selectAllKey)) {
    selectedIndices.Clear();
    for (int i = 0; i < keyPoints.Count; i++){
    selectedIndices.Add(i);
    }
    return true;
    }
    return false;
    }

    bool TrySplitLine() {
    if (nearestLine >= 0 && KeyPressed(splitKey)) {
    if (nearestLine == keyPoints.Count - 1) {
    keyPoints.Add(splitPosition);
    curvePoints.Add(Vector3.zero);
    isCurve.Add(false);
    } else {
    keyPoints.Insert(nearestLine + 1, splitPosition);
    curvePoints.Insert(nearestLine + 1, Vector3.zero);
    isCurve.Insert(nearestLine + 1, false);
    }
    isCurve[nearestLine] = false;
    UpdatePoly(true, true);
    return true;
    }
    return false;
    }

    bool TryExtrude() {
    if (nearestLine >= 0 && extrudeKeyDown && e.type == EventType.MouseDown) {
    var a = nearestLine;
    var b = (nearestLine + 1) % keyPoints.Count;
    if (b == 0 && a == keyPoints.Count - 1) {
    //Extrude between the first and last points
    keyPoints.Add(polyMesh.keyPoints[a]);
    keyPoints.Add(polyMesh.keyPoints[b]);
    curvePoints.Add(Vector3.zero);
    curvePoints.Add(Vector3.zero);
    isCurve.Add(false);
    isCurve.Add(false);

    selectedIndices.Clear();
    selectedIndices.Add(keyPoints.Count - 2);
    selectedIndices.Add(keyPoints.Count - 1);
    } else {
    //Extrude between two inner points
    var pointA = keyPoints[a];
    var pointB = keyPoints[b];
    keyPoints.Insert(a + 1, pointA);
    keyPoints.Insert(a + 2, pointB);
    curvePoints.Insert(a + 1, Vector3.zero);
    curvePoints.Insert(a + 2, Vector3.zero);
    isCurve.Insert(a + 1, false);
    isCurve.Insert(a + 2, false);

    selectedIndices.Clear();
    selectedIndices.Add(a + 1);
    selectedIndices.Add(a + 2);
    }
    isCurve[nearestLine] = false;

    clickPosition = mousePosition;
    doExtrudeUpdate = true;
    UpdatePoly(true, true);
    return true;
    }
    return false;
    }

    bool TryDeleteSelected() {
    if (KeyPressed(KeyCode.Backspace) || KeyPressed(KeyCode.Delete)) {
    if (selectedIndices.Count > 0) {
    if (keyPoints.Count - selectedIndices.Count >= 3) {
    for (int i = selectedIndices.Count - 1; i >= 0; i--) {
    var index = selectedIndices[i];
    keyPoints.RemoveAt(index);
    curvePoints.RemoveAt(index);
    isCurve.RemoveAt(index);
    }
    selectedIndices.Clear();
    UpdatePoly(true, true);
    return true;
    }
    } else if (IsHovering(curvePoints[nearestLine])) {
    isCurve[nearestLine] = false;
    UpdatePoly(false, true);
    }
    }
    return false;
    }

    bool IsHovering(Vector3 point) {
    return Vector3.Distance(mousePosition, point) < HandleUtility.GetHandleSize(point) * clickRadius;
    }

    int NearestPoint(List<Vector3> points) {
    var near = -1;
    var nearDist = float.MaxValue;
    for (int i = 0; i < points.Count; i++) {
    var dist = Vector3.Distance(points[i], mousePosition);
    if (dist < nearDist) {
    nearDist = dist;
    near = i;
    }
    }
    return near;
    }

    int NearestLine(out Vector3 position) {
    var near = -1;
    var nearDist = float.MaxValue;
    position = keyPoints[0];
    var linePos = Vector3.zero;
    for (int i = 0; i < keyPoints.Count; i++) {
    var j = (i + 1) % keyPoints.Count;
    var line = keyPoints[j] - keyPoints[i];
    var offset = mousePosition - keyPoints[i];
    var dot = Vector3.Dot(line.normalized, offset);
    if (dot >= 0 && dot <= line.magnitude) {
    if (isCurve[i]) {
    linePos = Bezier.Curve(keyPoints[i], Bezier.Control(keyPoints[i], keyPoints[j], curvePoints[i]), keyPoints[j], dot / line.magnitude);
    } else {
    linePos = keyPoints[i] + line.normalized * dot;
    }
    var dist = Vector3.Distance(linePos, mousePosition);
    if (dist < nearDist) {
    nearDist = dist;
    position = linePos;
    near = i;
    }
    }
    }
    return near;
    }

    bool KeyPressed(KeyCode key) {
    return e.type == EventType.KeyDown && e.keyCode == key;
    }

    bool KeyReleased(KeyCode key) {
    return e.type == EventType.KeyUp && e.keyCode == key;
    }

    Vector3 Snap(Vector3 value) {
    value.x = Mathf.Round(value.x / snap) * snap;
    value.y = Mathf.Round(value.y / snap) * snap;
    return value;
    }

    Vector3 GetSelectionCenter() {
    var center = Vector3.zero;
    foreach (var i in selectedIndices)
    center += polyMesh.keyPoints[i];
    return center / selectedIndices.Count;
    }

    #endregion

    #region Properties

    PolyMesh polyMesh {
    get { return (PolyMesh)target; }
    }

    Event e {
    get { return Event.current; }
    }

    bool control {
    get { return Application.platform == RuntimePlatform.OSXEditor ? e.command : e.control; }
    }

    bool doSnap {
    get { return autoSnap ? !control : control; }
    }

    static bool meshSettings {
    get { return EditorPrefs.GetBool("PolyMeshEditor_meshSettings", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_meshSettings", value); }
    }

    static bool colliderSettings {
    get { return EditorPrefs.GetBool("PolyMeshEditor_colliderSettings", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_colliderSettings", value); }
    }

    static bool uvSettings {
    get { return EditorPrefs.GetBool("PolyMeshEditor_uvSettings", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_uvSettings", value); }
    }

    static bool editorSettings {
    get { return EditorPrefs.GetBool("PolyMeshEditor_editorSettings", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_editorSettings", value); }
    }

    static bool autoSnap {
    get { return EditorPrefs.GetBool("PolyMeshEditor_autoSnap", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_autoSnap", value); }
    }

    static bool globalSnap {
    get { return EditorPrefs.GetBool("PolyMeshEditor_globalSnap", false); }
    set { EditorPrefs.SetBool("PolyMeshEditor_globalSnap", value); }
    }

    static float gridSnap {
    get { return EditorPrefs.GetFloat("PolyMeshEditor_gridSnap", 1); }
    set { EditorPrefs.SetFloat("PolyMeshEditor_gridSnap", value); }
    }

    static bool hideWireframe {
    get { return EditorPrefs.GetBool("PolyMeshEditor_hideWireframe", true); }
    set { EditorPrefs.SetBool("PolyMeshEditor_hideWireframe", value); }
    }

    public KeyCode editKey {
    get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_editKey", (int)KeyCode.Tab); }
    set { EditorPrefs.SetInt("PolyMeshEditor_editKey", (int)value); }
    }

    public KeyCode selectAllKey {
    get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_selectAllKey", (int)KeyCode.A); }
    set { EditorPrefs.SetInt("PolyMeshEditor_selectAllKey", (int)value); }
    }

    public KeyCode splitKey {
    get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_splitKey", (int)KeyCode.S); }
    set { EditorPrefs.SetInt("PolyMeshEditor_splitKey", (int)value); }
    }

    public KeyCode extrudeKey {
    get { return (KeyCode)EditorPrefs.GetInt("PolyMeshEditor_extrudeKey", (int)KeyCode.D); }
    set { EditorPrefs.SetInt("PolyMeshEditor_extrudeKey", (int)value); }
    }

    #endregion

    #region Menu Items

    [MenuItem("GameObject/Create Other/PolyMesh", false, 1000)]
    static void CreatePolyMesh() {
    var obj = new GameObject("PolyMesh", typeof(MeshFilter), typeof(MeshRenderer));
    var polyMesh = obj.AddComponent<PolyMesh>();
    CreateSquare(polyMesh, 0.5f);
    }

    static void CreateSquare(PolyMesh polyMesh, float size) {
    polyMesh.keyPoints.AddRange(new Vector3[] { new Vector3(size, size), new Vector3(size, -size), new Vector3(-size, -size), new Vector3(-size, size)} );
    polyMesh.curvePoints.AddRange(new Vector3[] { Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero } );
    polyMesh.isCurve.AddRange(new bool[] { false, false, false, false } );
    polyMesh.BuildMesh();
    }

    #endregion
    }