Deformation
Free-form Deformation
MeshGenerator.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TwoDMeshGenerator
{
public class MeshVertexProperty
{
public int[] cornerPoints = new int[] { 0, 0, 0, 0 };
public MeshVertexProperty(int lb, int rb, int rt, int lt)
{
cornerPoints[0] = lb;
cornerPoints[1] = rb;
cornerPoints[2] = rt;
cornerPoints[3] = lt;
}
}
public class MeshGenerator
{
public Vector3[] fixedVertices;
public Vector3[] vertices;
public int[] triangles;
public MeshVertexProperty[] properties;
void InitializeMeshData()
{
// create an array of viertices
fixedVertices = new Vector3[]
{
new Vector3(-6, 0.01f, -2),
new Vector3(-2, 0.01f, 1),
new Vector3(0, 0.01f, -2),
new Vector3(1, 0.01f, 2),
new Vector3(3, 0.01f, -1),
new Vector3(6, 0.01f, 1)
};
vertices = new Vector3[]
{
new Vector3(-6, 0.01f, -2),
new Vector3(-2, 0.01f, 1),
new Vector3(0, 0.01f, -2),
new Vector3(1, 0.01f, 2),
new Vector3(3, 0.01f, -1),
new Vector3(6, 0.01f, 1)
};
// create an array of indices
triangles = new[]
{
0, 1, 2,
1, 3, 2,
2, 3, 4,
4, 3, 5
};
// create mesh vertex property
properties = new[]
{
new MeshVertexProperty(0, 0, 0, 0),
new MeshVertexProperty(0, 0, 0, 0),
new MeshVertexProperty(0, 0, 0, 0),
new MeshVertexProperty(0, 0, 0, 0),
new MeshVertexProperty(0, 0, 0, 0),
new MeshVertexProperty(0, 0, 0, 0)
};
}
public void CreateMesh()
{
// initialize the mesh
InitializeMeshData();
}
}
}
FFD.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// third-party namespace
using QuikGraph;
// self-defined namespace
using Property;
using TwoDMeshGenerator;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class FFD : MonoBehaviour
{
// variables for FFD control points
public int xPointNumber = 5;
public int zPointNumber = 3;
private AdjacencyGraph<VertexProperty, TaggedEdge<VertexProperty, EdgeProperty>> graph;
private List<GameObject> _controlPoints;
private string _controlPointName = "ControlPoint";
private const float _shift = 4.0f;
// mesh for deformation
private Mesh _mesh;
private MeshGenerator _gen;
// Start is called before the first frame update
void Start()
{
initializeGridGraph();
initializeControlPoints();
initialize2DMesh();
}
// Update is called once per frame
void Update()
{
if (graph != null && _mesh != null)
{
// update control points
updateControlPointPosition();
// update mesh boundary presentation
updateMeshPosition();
}
}
void initializeControlPoints()
{
_controlPoints = new List<GameObject>();
for (int i = 0; i < xPointNumber * zPointNumber; i++)
{
// create sphere game objects
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Sphere);
obj.name = _controlPointName + "[" + i.ToString() + "]";
obj.transform.parent = transform;
Vector3 position = graph.Vertices.ElementAt(i).position;
obj.transform.position = new Vector3(position.x, position.y, position.z);
obj.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
_controlPoints.Add(obj);
}
}
void initialize2DMesh()
{
_gen = new MeshGenerator();
_mesh = GetComponent<MeshFilter>().mesh;
_gen.CreateMesh();
_mesh.vertices = _gen.vertices;
_mesh.triangles = _gen.triangles;
// initialize the corner points of each mesh vertex
for (int i = 0; i < _mesh.vertices.Length; i++)
{
Vector3 position = _mesh.vertices[i];
Vector3 diff = (position - graph.Vertices.ElementAt(0).position);
// Debug.Log("diff.x/shift = " + (diff.x/shift));
// Debug.Log("diff.z/shift = " + (diff.z/shift));
// store the corner point id of each vertex in a mesh
int id = (int)(diff.z / _shift) + zPointNumber * (int)(diff.x / _shift);
_gen.properties[i].cornerPoints[0] = id; // lb
_gen.properties[i].cornerPoints[1] = id + 1; // rb
_gen.properties[i].cornerPoints[2] = id + zPointNumber + 1; // rt
_gen.properties[i].cornerPoints[3] = id + zPointNumber; // lt
}
}
void initializeGridGraph()
{
graph = new AdjacencyGraph<VertexProperty, TaggedEdge<VertexProperty, EdgeProperty>>();
int vID = 0, eID = 0;
int xDim = xPointNumber, zDim = zPointNumber;
for (int i = 0; i < xDim; i++)
{
for (int j = 0; j < zDim; j++)
{
VertexProperty v = new VertexProperty();
v.index = vID;
float x = _shift * ((float)i - ((float)xDim - 1.0f) / 2.0f);
float y = 0;
float z = _shift * ((float)j - ((float)zDim - 1.0f) / 2.0f);
v.position = new Vector3(x, y, z);
v.fixedPosition = new Vector3(x, y, z);
graph.AddVertex(v);
vID++;
}
}
// Add horizontal edges
for (int i = 0; i < xDim; i++)
{
for (int j = 1; j < zDim; j++)
{
int id = i * zDim + j - 1;
int idNext = i * zDim + j;
VertexProperty v = graph.Vertices.ElementAt(id);
VertexProperty vNext = graph.Vertices.ElementAt(idNext);
EdgeProperty ep = new EdgeProperty();
TaggedEdge<VertexProperty, EdgeProperty> edgeF =
new TaggedEdge<VertexProperty, EdgeProperty>(v, vNext, ep);
edgeF.Tag.index = eID;
edgeF.Tag.source = v;
edgeF.Tag.target = vNext;
graph.AddEdge(edgeF);
eID++;
TaggedEdge<VertexProperty, EdgeProperty> edgeB =
new TaggedEdge<VertexProperty, EdgeProperty>(vNext, v, ep);
edgeB.Tag.index = eID;
edgeB.Tag.source = vNext;
edgeB.Tag.target = v;
graph.AddEdge(edgeB);
eID++;
}
}
// Add vertical edges
var rand = new System.Random();
for (int j = 0; j < zDim; j++)
{
for (int i = 1; i < xDim; i++)
{
int id = (i - 1) * zDim + j;
int idNext = i * zDim + j;
VertexProperty v = graph.Vertices.ElementAt(id);
VertexProperty vNext = graph.Vertices.ElementAt(idNext);
EdgeProperty ep = new EdgeProperty();
TaggedEdge<VertexProperty, EdgeProperty> edgeF =
new TaggedEdge<VertexProperty, EdgeProperty>(v, vNext, ep);
edgeF.Tag.index = eID;
edgeF.Tag.source = v;
edgeF.Tag.target = vNext;
graph.AddEdge(edgeF);
eID++;
TaggedEdge<VertexProperty, EdgeProperty> edgeB =
new TaggedEdge<VertexProperty, EdgeProperty>(vNext, v, ep);
edgeB.Tag.index = eID;
edgeB.Tag.source = vNext;
edgeB.Tag.target = v;
graph.AddEdge(edgeB);
eID++;
}
}
}
private void updateControlPointPosition()
{
// get the position of game objects from the unity ui
for (int i = 0; i < _controlPoints.Count; i++)
{
VertexProperty v = graph.Vertices.ElementAt(i);
v.position = _controlPoints[i].transform.position;
}
}
private void updateMeshPosition()
{
for (int i = 0; i < _mesh.vertices.Length; i++)
{
int lb = _gen.properties[i].cornerPoints[0]; // lb
int rb = _gen.properties[i].cornerPoints[1]; // rb
int rt = _gen.properties[i].cornerPoints[2]; // rt
int lt = _gen.properties[i].cornerPoints[3]; // lt
Vector3 fixedC = _gen.fixedVertices[i];
Vector3[] oldCorners = // lb, rb, rt, lt
{
graph.Vertices.ElementAt(lb).fixedPosition,
graph.Vertices.ElementAt(rb).fixedPosition,
graph.Vertices.ElementAt(rt).fixedPosition,
graph.Vertices.ElementAt(lt).fixedPosition
};
Vector3[] corners = // lb, rb, rt, lt
{
graph.Vertices.ElementAt(lb).position,
graph.Vertices.ElementAt(rb).position,
graph.Vertices.ElementAt(rt).position,
graph.Vertices.ElementAt(lt).position
};
Vector3[] oldMiddles = // bottom, right, top, left
{
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 0)
};
Vector3[] middles = // bottom, right, top, left
{
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, 0)
};
// calculate old bottom, right, top, left
for (int j = 0; j < 4; j++)
{
Vector3 a = oldCorners[j];
Vector3 b = oldCorners[(j + 1) % 4];
oldMiddles[j] = Vector3.Dot(fixedC - a, b - a) / (b - a).magnitude / (b - a).magnitude * (b - a) + a;
// Debug.Log("oldMiddles[j] = " + oldMiddles[j]);
}
// calculate middles
for (int j = 0; j < 4; j++)
{
Vector3 oa = oldCorners[j];
Vector3 ob = oldCorners[(j + 1) % 4];
Vector3 a = corners[j];
Vector3 b = corners[(j + 1) % 4];
//Debug.Log("oldMiddles[" + j + "] = " + oldMiddles[j]);
float ratio = (oldMiddles[j] - oa).magnitude / (ob - oa).magnitude;
middles[j] = ratio * (b - a) + a;
}
// Line AB represented as a1x + b1y = c1
double a1 = middles[2].z - middles[0].z;
double b1 = middles[0].x - middles[2].x;
double c1 = a1 * (middles[0].x) + b1 * (middles[0].z);
// Line CD represented as a2x + b2y = c2
double a2 = middles[3].z - middles[1].z;
double b2 = middles[1].x - middles[3].x;
double c2 = a2 * (middles[1].x) + b2 * (middles[1].z);
double determinant = a1 * b2 - a2 * b1;
double x = 0, z = 0;
if (determinant == 0)
{
Debug.Log("something is wrong here!");
}
else
{
x = (b2 * c1 - b1 * c2) / determinant;
z = (a1 * c2 - a2 * c1) / determinant;
}
// 0.01f let us see the mesh properly
Vector3 position = new Vector3((float)x, 0.01f, (float)z);
// update mesh vertex position
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
vertices[i] = position;
//Debug.Log("mesh size = " + mesh.vertices.Length);
mesh.vertices = vertices;
mesh.RecalculateNormals();
}
}
private void OnDrawGizmos()
{
if (graph != null && _mesh != null)
{
// draw the graph
// Debug.Log("VertexNum: " + graph.VertexCount);
// Debug.Log("EdgeNum: " + graph.EdgeCount);
// Debug.Log(graph.Vertices.Count());
// draw edges
foreach (TaggedEdge<VertexProperty, EdgeProperty> edge in graph.Edges)
{
Gizmos.color = edge.Tag.color;
Gizmos.DrawLine(edge.Source.position, edge.Target.position);
}
// draw vertices
foreach (VertexProperty vertex in graph.Vertices)
{
Gizmos.color = vertex.color;
Gizmos.DrawSphere(vertex.position, 0.25f);
}
}
}
}