using UnityEngine; using System.Collections; using System.Collections.Generic; /// <summary> /// Helper class that represents a parameterized vertex /// </summary> public class Vector3Param { ///bernstein polynomial packing public List<List<float>> 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; } }; /// <summary> /// 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 /// </summary> public class FreeFormDeformer : MonoBehaviour { /// <summary> /// Allow FixedUpdate to modify the mesh. /// </summary> public bool AllowMeshUpdate = false; /// <summary> /// Target to be morphed /// </summary> Mesh MorphTarget = null; /// <summary> /// Target to be filtered (assumed to contain a meshfilter and valid mesh) /// </summary> public MeshFilter MorphTargetFilter = null; /// <summary> /// Update frequency in seconds /// </summary> public float UpdateFrequency = 1.0f; /// <summary> /// Game object to represent a control point. Can be anything really, I suggest spheres. /// </summary> public GameObject ControlPointPrefab; /// <summary> /// Local coordinate system /// </summary> Vector3 S, T, U; /// <summary> /// Number of controls for S, T, & U respectively. (L,M, and N MUST be >= 1) /// </summary> public int L=1, M=1, N=1; /// <summary> /// Time elapsed since last update /// </summary> float elapsedTime = 0.0f; /// <summary> /// Grid of controls points. Stored as 3D grid for easier because width,height, and depth can randomly vary. /// </summary> GameObject[, ,] controlPoints; /// <summary> /// Original vertices from MorphTarget /// </summary> Vector3[] originalVertices; /// <summary> /// Current updated vertices for MorphTarget /// </summary> Vector3[] transformedVertices; /// <summary> /// Vertex parameters /// /// Each vertex is given a set of parameters that will define /// its final position based on a local coordinate system. /// </summary> List<Vector3Param> vertexParams = new List<Vector3Param>(); void Start () { MorphTarget = MorphTargetFilter.mesh ; originalVertices = MorphTarget.vertices; transformedVertices = new Vector3[originalVertices.Length]; Parameterize(); } /// <summary> /// Calculate a binomial coefficient using the multiplicative formula /// </summary> 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; } /// <summary> /// Calculate a bernstein polynomial /// </summary> 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)); } /// <summary> /// Calculate local coordinates /// </summary> 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); } /// <summary> /// 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). /// </summary> /// <links> /// [1] - Method based on: http://pages.cpsc.ucalgary.ca/~blob/papers/others/ffd.pdf /// </links> /// <param name="p0">Origin of our coordinate system (where STU meet)</param> 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<List<float>>(); { // Reserve room for each bernstein polynomial pack. tmp.bernPolyPack.Add(new List<float>(L)); //outer bernstein poly tmp.bernPolyPack.Add(new List<float>(M)); //middle bernstein poly tmp.bernPolyPack.Add(new List<float>(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"); } } } /// <summary> /// Parameterize MorphTarget's vertices /// </summary> 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); } /// <summary> /// Create grid of control points. /// </summary> 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); } } } } /// <summary> /// Create a single control point. /// </summary> 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); return (GameObject)Instantiate(ControlPointPrefab, position, Quaternion.identity); } /// <summary> /// Convert parameterized vertex in to a world coordinate /// </summary> 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.position; } 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 FixedUpdate() { elapsedTime += Time.fixedDeltaTime; if (AllowMeshUpdate) { if (elapsedTime >= UpdateFrequency) UpdateMesh(); } } // Update is called once per frame void Update () { } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using VRTK; using VRTK.GrabAttachMechanics; using ArgosTweenSpacePuppy; public class Tetra_MAJOR : MonoBehaviour { public enum State { FREE, ATTACHED, } public bool bTurn_Off_Collider = false; public State state; public enum SpokeState { OPEN, OCCUPIED, } public class Spoke_Tracker { public SpokeState spokeState = SpokeState.OPEN; public GameObject go_occupying = null; public float touchTimer = 0; public GameObject genSpoke; } private int id = 0; public GameObject[] vertsHT = new GameObject[4]; public Spoke_Tracker[] spoke_Tracker = new Spoke_Tracker[4]; [Range(0, 10f)] public float Radius_of_Action = 7f; [Range(0, 1.61803f)] public float tDurr = 0.7f; private bool bForce_update = false; private float tAccum = 0f; private float tween_Val = 0f; private float touchTimer = 0; private Spoke_Tracker attachedSpoke; private GameObject attachedTetra_go; private Vector3 start_Pos; private Vector3 end_Pos; private Quaternion qStart; private Quaternion qEnd; private HMD_Ctrl_Tracking hmd_Ctrl_Tracking; private VU_UI_MANAGER VU_UI; private ALL_Shape_Instancer all_shape_instancer; private bool bTouchGrabEnabled = true; private bool bGrabbed = false; private VRTK_InteractableObject vrtk_interact; public bool bInteractive = true; void Start() { hmd_Ctrl_Tracking = HMD_Ctrl_Tracking.Instance; VU_UI = VU_UI_MANAGER.Instance; all_shape_instancer = VU_UI.GetComponent<ALL_Shape_Instancer>(); vrtk_interact = GetComponent<VRTK_InteractableObject>(); vrtk_interact.InteractableObjectGrabbed += new InteractableObjectEventHandler(DoObjectGrabbed); vrtk_interact.InteractableObjectUngrabbed += new InteractableObjectEventHandler(DoObjectUnGrabbed); Vector3[] face_Center = new Vector3[4]; face_Center[0] = (vertsHT[0].transform.localPosition + vertsHT[1].transform.localPosition + vertsHT[2].transform.localPosition) / 3f; face_Center[1] = (vertsHT[1].transform.localPosition + vertsHT[2].transform.localPosition + vertsHT[3].transform.localPosition) / 3f; face_Center[2] = (vertsHT[0].transform.localPosition + vertsHT[2].transform.localPosition + vertsHT[3].transform.localPosition) / 3f; face_Center[3] = (vertsHT[0].transform.localPosition + vertsHT[1].transform.localPosition + vertsHT[3].transform.localPosition) / 3f; float avg_FC = (face_Center[0].magnitude + face_Center[1].magnitude + face_Center[2].magnitude + face_Center[3].magnitude) / 4f; Vector3 axis = new Vector3(); Quaternion q; for (int i = 0; i < 4; i++) { spoke_Tracker[i] = new Spoke_Tracker(); spoke_Tracker[i].spokeState = SpokeState.OPEN; spoke_Tracker[i].genSpoke = new GameObject(); spoke_Tracker[i].genSpoke.name = "genSpoke_" + i.ToString(); spoke_Tracker[i].genSpoke.transform.parent = transform; spoke_Tracker[i].genSpoke.transform.localPosition = face_Center[i].normalized * 2f*avg_FC; axis = vertsHT[i].transform.localPosition - face_Center[i]; q = Quaternion.AngleAxis(180f, axis); spoke_Tracker[i].genSpoke.transform.localRotation = q; } //if (bInteractive) //{ // for (int i = 0; i < tet.Length; i++) // { // tet[i].id = i; // tetras.Add(tet[i]); // } //} } private void DoObjectGrabbed(object sender, InteractableObjectEventArgs e) { bGrabbed = true; touchTimer = 1f; //if (state == State.ATTACHED) //{ // //octa_Major.Detach(id); // state = State.FREE; //} } private void DoObjectUnGrabbed(object sender, InteractableObjectEventArgs e) { bGrabbed = false; } public void Detach(int id) { //for (int i = 0; i < spoke_Tracker.Length; i++) //{ // if (spoke_Tracker[i].go_occupying != null) // { // if (spoke_Tracker[i].go_occupying.GetComponent<Tetra_MAJOR>().id == id) // { // spoke_Tracker[i].touchTimer = 2f; // spoke_Tracker[i].go_occupying = null; // print("OPEN HAPPENED"); // } // } //} } void Update() { touchTimer -= Time.deltaTime; if (bGrabbed) { float dist; foreach (GameObject g in all_shape_instancer.getList()) { Spoke_Tracker[] sp = g.GetComponent<Tetra_MAJOR>().spoke_Tracker; if (g != gameObject) { for (int i = 0; i < 4; i++) { dist = (sp[i].genSpoke.transform.position - transform.position).magnitude; if (dist < Radius_of_Action && sp[i].spokeState == SpokeState.OPEN && touchTimer < 0) { print("Within Range of idx: " + i.ToString()); //disconnect GetComponent<VRTK_FixedJointGrabAttach>().StopGrab(false); //sp[i].spokeState = SpokeState.OCCUPIED; sp[i].go_occupying = this.gameObject; //t.state = Tetra_MAJOR.State.ATTACHED; attachedSpoke = sp[i]; attachedTetra_go = gameObject; start_Pos = transform.position; end_Pos = sp[i].genSpoke.transform.position; qStart = transform.rotation; qEnd = sp[i].genSpoke.transform.rotation; tAccum = 0; StartCoroutine(Tween_Move_to_Spoke()); } } } } //Switch off Touch Grab if in the VU //if (!bTouchGrabEnabled && hmd_Ctrl_Tracking.Get_Current_USER_Location() != HMD_Ctrl_Tracking.USER_LOCATION.IN_THE_VU) //{ // Enable_Touch_Grab(); // bTouchGrabEnabled = true; //} //else if (bTouchGrabEnabled && hmd_Ctrl_Tracking.Get_Current_USER_Location() == HMD_Ctrl_Tracking.USER_LOCATION.IN_THE_VU) //{ // Disable_Touch_Grab(); // bTouchGrabEnabled = false; //} } } private void Enable_Touch_Grab() { //GetComponent<SphereCollider>().enabled = true; //foreach (Tetra_MAJOR t in tetras) //{ // t.GetComponent<SphereCollider>().enabled = true; //} } private void Disable_Touch_Grab() { //GetComponent<SphereCollider>().enabled = false; //foreach (GameObject g in all_shape_instancer.getList()) //{ // g.GetComponent<SphereCollider>().enabled = false; //} } private IEnumerator Tween_Move_to_Spoke() { int i = 0; //Sanity check for infinite loops float linProg = tAccum / tDurr; while (i < 180 && (linProg < 1)) { bForce_update = true; tAccum += Time.deltaTime; linProg = tAccum / tDurr; Mathf.Clamp(tAccum, 0, tDurr); tween_Val = EaseMethods.QuintEaseOut(tAccum, 0, 1, tDurr); Move_To_Spoke(tween_Val); yield return true; i++; } tAccum = tDurr; tween_Val = 1; Move_To_Spoke(tween_Val); bForce_update = false; StopCoroutine("Tween_State"); } private void Move_To_Spoke(float val) { transform.position = Vector3.Lerp(start_Pos, end_Pos, val); transform.rotation = Quaternion.Slerp(qStart, qEnd, val); } }