using UnityEngine; using System.Collections; using System.Collections.Generic; /// /// Helper class that represents a parameterized vertex /// public class Vector3Param { ///bernstein polynomial packing public List> bernPolyPack; ///Point after applying s,t,u to p0, should result in original point public Vector3 p = Vector3.zero; ///Origin public Vector3 p0 = Vector3.zero; ///Distances along S/T/U axes public float s,t,u; public Vector3Param() { s = 0.0f; t = 0.0f; u = 0.0f; } public Vector3Param(Vector3Param v) { s = v.s; t = v.t; u = v.u; p = v.p; p0 = v.p0; } }; /// /// Free form deformation class /// /// Based off of the paper 'Free-Form Deformation of Solid Geometric Models'[1] this class /// creates a system of control points that can deform a mesh as if that mesh was embedded /// in a flexible parallelpiped. /// /// Confused? Yeah, who uses the term parallelpiped. The idea is to create a uniformly spaced /// grid of control points around some mesh. Each control point has some effect on the mesh, like /// bone weighting in animation. The effect each control point has is directly proportional to the /// total number of control points in the entire grid, each exerts some control. /// /// [1] - http://pages.cpsc.ucalgary.ca/~blob/papers/others/ffd.pdf /// public class FreeFormDeformer : MonoBehaviour { /// /// Allow FixedUpdate to modify the mesh. /// public bool AllowMeshUpdate = false; /// /// Animate control points /// public bool AnimateControlPoints = false; /// /// Target to be morphed /// Mesh MorphTarget = null; /// /// Target to be filtered (assumed to contain a meshfilter and valid mesh) /// public MeshFilter MorphTargetFilter = null; /// /// Update frequency in seconds /// public float UpdateFrequency = 1.0f; /// /// Game object to represent a control point. Can be anything really, I suggest spheres. /// public GameObject ControlPointPrefab; /// /// Local coordinate system /// Vector3 S, T, U; /// /// Number of controls for S, T, & U respectively. (L,M, and N MUST be >= 1) /// public int L=1, M=1, N=1; /// /// Time elapsed since last update /// float elapsedTime = 0.0f; float elapsedAnimationTime = 0.0f; int animationDirection = 1; /// /// Grid of controls points. Stored as 3D grid for easier because width,height, and depth can randomly vary. /// GameObject[, ,] controlPoints; /// /// Original vertices from MorphTarget /// Vector3[] originalVertices; /// /// Current updated vertices for MorphTarget /// Vector3[] transformedVertices; /// /// Vertex parameters /// /// Each vertex is given a set of parameters that will define /// its final position based on a local coordinate system. /// List vertexParams = new List(); void Start () { MorphTarget = MorphTargetFilter.mesh ; originalVertices = MorphTarget.vertices; transformedVertices = new Vector3[originalVertices.Length]; Parameterize(); } /// /// Calculate a binomial coefficient using the multiplicative formula /// float binomialCoeff(int n, int k){ float total = 1.0f; for(int i = 1; i <= k; i++){ total *= (n - (k - i)) / (float)i; } return total; } /// /// Calculate a bernstein polynomial /// float bernsteinPoly(int n, int v, float x) { return binomialCoeff(n,v) * Mathf.Pow(x, (float)v) * Mathf.Pow((float)(1.0f - x), (float)(n - v)); } /// /// Calculate local coordinates /// void calculateSTU(Vector3 max, Vector3 min){ S = new Vector3(max.x - min.x, 0.0f, 0.0f); T = new Vector3(0.0f, max.y - min.y, 0.0f); U = new Vector3(0.0f, 0.0f, max.z - min.z); } /// /// Calculate the trivariate bernstein polynomial as described by [1] /// /// My method adapts [1] slightly by precalculating the BP coefficients and storing /// them in Vector3Param. When it comes time to extract a world coordinate, /// it's just a matter of summing up multiplications through each polynomial from eq (2). /// /// /// [1] - Method based on: http://pages.cpsc.ucalgary.ca/~blob/papers/others/ffd.pdf /// /// Origin of our coordinate system (where STU meet) void calculateTrivariateBernsteinPolynomial(Vector3 p0){ Vector3 TcU = Vector3.Cross(T, U); Vector3 ScU = Vector3.Cross(S, U); Vector3 ScT = Vector3.Cross(S, T); float TcUdS = Vector3.Dot(TcU, S); float ScUdT = Vector3.Dot(ScU, T); float ScTdU = Vector3.Dot(ScT, U); for (int v = 0; v < originalVertices.Length; v++) { Vector3 diff = originalVertices[v] - p0; Vector3Param tmp = new Vector3Param(); tmp.s = Vector3.Dot(TcU, diff / TcUdS); tmp.t = Vector3.Dot(ScU, diff / ScUdT); tmp.u = Vector3.Dot(ScT, diff / ScTdU); tmp.p = p0 + (tmp.s * S) + (tmp.t * T) + (tmp.u * U); tmp.p0 = p0; tmp.bernPolyPack = new List>(); { // Reserve room for each bernstein polynomial pack. tmp.bernPolyPack.Add(new List(L)); //outer bernstein poly tmp.bernPolyPack.Add(new List(M)); //middle bernstein poly tmp.bernPolyPack.Add(new List(N)); //inner bernstein poly } { // Pre-calculate bernstein polynomial expansion. It only needs to be done once per parameterization for (int i = 0; i <= L; i++) { for (int j = 0; j <= M; j++) { for (int k = 0; k <= N; k++) { tmp.bernPolyPack[2].Add(bernsteinPoly(N, k, tmp.u)); } tmp.bernPolyPack[1].Add(bernsteinPoly(M, j, tmp.t)); } tmp.bernPolyPack[0].Add(bernsteinPoly(L, i, tmp.s)); } } vertexParams.Add(tmp); if (Vector3.Distance(tmp.p, originalVertices[v]) > 0.001f) { //Debug.Log("Warning, mismatched parameterization"); } } } /// /// Parameterize MorphTarget's vertices /// void Parameterize(){ Vector3 min = new Vector3(Mathf.Infinity,Mathf.Infinity,Mathf.Infinity); Vector3 max = new Vector3(-Mathf.Infinity,-Mathf.Infinity,-Mathf.Infinity); foreach(Vector3 v in originalVertices){ max = Vector3.Max(v,max); min = Vector3.Min(v,min); } calculateSTU(max, min); calculateTrivariateBernsteinPolynomial(min); createControlPoints(min); } /// /// Create grid of control points. /// void createControlPoints(Vector3 origin){ controlPoints = new GameObject[L + 1, M + 1, N + 1]; for(int i = 0; i <= L; i++){ for(int j = 0; j <= M; j++){ for(int k = 0; k <= N; k++){ controlPoints[i, j, k] = createControlPoint(origin, i, j, k); } } } } /// /// Create a single control point. /// GameObject createControlPoint(Vector3 p0, int i, int j, int k) { Vector3 position = p0 + (i / (float)L * S) + (j / (float)M * T) + (k / (float)N * U); GameObject go = (GameObject)Instantiate(ControlPointPrefab, position, Quaternion.identity); go.transform.parent = transform; return go; } /// /// Convert parameterized vertex in to a world coordinate /// Vector3 getWorldVector3(Vector3Param r){ int l = L; int m = M; int n = N; Vector3 tS = Vector3.zero; for(int i = 0; i <= l; i++){ Vector3 tM = Vector3.zero; for(int j = 0; j <= m; j++){ Vector3 tK = Vector3.zero; for(int k = 0; k <= n; k++){ tK += r.bernPolyPack[2][k] * controlPoints[i,j,k].transform.localPosition; } tM += r.bernPolyPack[1][j] * tK; } tS += r.bernPolyPack[0][i] * tM; } return tS; } void UpdateMesh(){ elapsedTime = 0.0f; int idx = 0; foreach(Vector3Param vp in vertexParams){ Vector3 p = getWorldVector3(vp); transformedVertices[idx++] = p; } MorphTarget.vertices = transformedVertices; MorphTarget.RecalculateBounds(); MorphTarget.RecalculateNormals(); MorphTarget.Optimize(); } void OnGUI(){ AllowMeshUpdate = GUI.Toggle(new Rect(5, 5, 150, 25), AllowMeshUpdate, "Allow Mesh Updates"); GUI.TextArea(new Rect(25, 25, 225, 225), "Controls\n--------\n- Left Click + Mouse Move: Rotate\n- Left Click on Points: Select Point\n- WASD: Translate X/Y\n- ZX: Zoom In/Out"); } void FixedUpdate() { elapsedTime += Time.fixedDeltaTime; if (AllowMeshUpdate) { if (elapsedTime >= UpdateFrequency) UpdateMesh(); } } void Animate(){ // elapsedTime += (Time.fixedDeltaTime * animationDirection); // if (elapsedTime < 0.0f | elapsedTime > 4.0f) animationDirection = -animationDirection; //foreach(GameObject go in controlPoints){ // Vector3 dir = // } } // Update is called once per frame void Update () { if(AnimateControlPoints)Animate(); } }