void ICustomVisit <float2x2> .CustomVisit(float2x2 f) { GUILayout.Label(Property.Name); EditorGUILayout.Vector2Field("", (Vector2)f.c0); EditorGUILayout.Vector2Field("", (Vector2)f.c1); }
public static void AreEqual(float2x2 a, float2x2 b, float delta = 0.0f) { AreEqual(a.c0, b.c0, delta); AreEqual(a.c1, b.c1, delta); }
bool Solve_Shape_Matching() { JobHandle jh; // this stride is used to split the linear summation in both the center of mass, and the calculation of matrix Apq, into parts. // these are then calculated in parallel, and finally combined serially int stride = ps.Length / division; // sum up center of mass in parallel var job_sum_center_of_mass = new Job_SumCenterOfMass() { ps = ps, com_sums = com_sums, stride = stride }; jh = job_sum_center_of_mass.Schedule(num_particles, division); jh.Complete(); // after the job is complete, we have the results of each individual summation stored at every "stride'th" array entry of com_sums. // the CoM is a float2, but we store the total weight each batch used in the z component. finally we divide the final value by this lump sum float2 cm = math.float2(0); float sum = 0; for (int i = 0; i < com_sums.Length; i += stride) { cm.x += com_sums[i].x; cm.y += com_sums[i].y; sum += com_sums[i].z; } cm /= sum; // calculating Apq in batches, same idea as used for CoM calculation var job_sum_shape_matrix = new Job_SumShapeMatrix() { cm = cm, shape_matrices = shape_matrices, ps = ps, deltas = deltas, stride = stride }; jh = job_sum_shape_matrix.Schedule(num_particles, division); jh.Complete(); // sum up batches and then normalize by total batch count. float2x2 Apq = math.float2x2(0, 0, 0, 0); for (int i = 0; i < shape_matrices.Length; i += stride) { float2x2 shape_mat = shape_matrices[i]; Apq.c0 += shape_mat.c0; Apq.c1 += shape_mat.c1; } Apq.c0 /= division; Apq.c1 /= division; // calculating the rotation matrix R, using a 2D polar decomposition. // taken from http://www.cs.cornell.edu/courses/cs4620/2014fa/lectures/polarnotes.pdf // this is far more complex in 3D! see e.g. the github PositionBasedDynamics repo for some implementations float2 dir = math.float2(Apq.c0.x + Apq.c1.y, Apq.c0.y - Apq.c1.x); dir = math.normalize(dir); float2x2 R = math.float2x2(dir, math.float2(-dir.y, dir.x)); // Calculate A = Apq * Aqq for linear deformations float2x2 A = math.mul(Apq, inv_rest_matrix); // volume preservation from Müller paper float det_A = math.determinant(A); // if our determinant is < 0 here, our shape is inverted. if it's 0, it's collapsed entirely. if (det_A != 0) { // just using the absolute value here for stability in the case of inverted shapes float sqrt_det = math.sqrt(math.abs(det_A)); A.c0 /= sqrt_det; A.c1 /= sqrt_det; } // blending between simple shape matched rotation (R term) and the area-preserved deformed shape // if linear_deformation_blending = 0, we have "standard" shape matching which only supports small changes from the rest shape. try setting this to 1.0f - pretty amazing float2x2 A_term = A * linear_deformation_blending; float2x2 R_term = R * (1.0f - linear_deformation_blending); // "goal position" matrix composed of a linear blend of A and R float2x2 GM = math.float2x2(A_term.c0 + R_term.c0, A_term.c1 + R_term.c1); // now actually modify particle positions to apply the shape matching var j_get_deltas = new Job_GetDeltas() { cm = cm, ps = ps, deltas = deltas, GM = GM }; jh = j_get_deltas.Schedule(num_particles, division); jh.Complete(); return(true); }
public void Execute(int i) { Particle p = ps[i]; // reset particle velocity. we calculate it from scratch each step using the grid p.v = 0; // quadratic interpolation weights uint2 cell_idx = (uint2)p.x; float2 cell_diff = (p.x - cell_idx) - 0.5f; var weights = stackalloc float2[] { 0.5f * math.pow(0.5f - cell_diff, 2), 0.75f - math.pow(cell_diff, 2), 0.5f * math.pow(0.5f + cell_diff, 2) }; // constructing affine per-particle momentum matrix from APIC / MLS-MPM. // see APIC paper (https://web.archive.org/web/20190427165435/https://www.math.ucla.edu/~jteran/papers/JSSTS15.pdf), page 6 // below equation 11 for clarification. this is calculating C = B * (D^-1) for APIC equation 8, // where B is calculated in the inner loop at (D^-1) = 4 is a constant when using quadratic interpolation functions float2x2 B = 0; for (uint gx = 0; gx < 3; ++gx) { for (uint gy = 0; gy < 3; ++gy) { float weight = weights[gx].x * weights[gy].y; uint2 cell_x = math.uint2(cell_idx.x + gx - 1, cell_idx.y + gy - 1); int cell_index = (int)cell_x.x * grid_res + (int)cell_x.y; float2 dist = (cell_x - p.x) + 0.5f; float2 weighted_velocity = grid[cell_index].v * weight; // APIC paper equation 10, constructing inner term for B var term = math.float2x2(weighted_velocity * dist.x, weighted_velocity * dist.y); B += term; p.v += weighted_velocity; } } p.C = B * 4; // advect particles p.x += p.v * dt; // safety clamp to ensure particles don't exit simulation domain p.x = math.clamp(p.x, 1, grid_res - 2); // mouse interaction if (mouse_down) { var dist = p.x - mouse_pos; if (math.dot(dist, dist) < mouse_radius * mouse_radius) { float norm_factor = (math.length(dist) / mouse_radius); norm_factor = math.pow(math.sqrt(norm_factor), 8); var force = math.normalize(dist) * norm_factor * 0.5f; p.v += force; } } // deformation gradient update - MPM course, equation 181 // Fp' = (I + dt * p.C) * Fp var Fp_new = math.float2x2( 1, 0, 0, 1 ); Fp_new += dt * p.C; Fs[i] = math.mul(Fp_new, Fs[i]); ps[i] = p; } }
public void Execute() { var weights = stackalloc float2[3]; for (int i = 0; i < num_particles; ++i) { var p = ps[i]; float2x2 stress = 0; // deformation gradient var F = Fs[i]; var J = math.determinant(F); // MPM course, page 46 var volume = p.volume_0 * J; // useful matrices for Neo-Hookean model var F_T = math.transpose(F); var F_inv_T = math.inverse(F_T); var F_minus_F_inv_T = F - F_inv_T; // MPM course equation 48 var P_term_0 = elastic_mu * (F_minus_F_inv_T); var P_term_1 = elastic_lambda * math.log(J) * F_inv_T; var P = P_term_0 + P_term_1; // cauchy_stress = (1 / det(F)) * P * F_T // equation 38, MPM course stress = (1.0f / J) * math.mul(P, F_T); // (M_p)^-1 = 4, see APIC paper and MPM course page 42 // this term is used in MLS-MPM paper eq. 16. with quadratic weights, Mp = (1/4) * (delta_x)^2. // in this simulation, delta_x = 1, because i scale the rendering of the domain rather than the domain itself. // we multiply by dt as part of the process of fusing the momentum and force update for MLS-MPM var eq_16_term_0 = -volume * 4 * stress * dt; // quadratic interpolation weights uint2 cell_idx = (uint2)p.x; float2 cell_diff = (p.x - cell_idx) - 0.5f; weights[0] = 0.5f * math.pow(0.5f - cell_diff, 2); weights[1] = 0.75f - math.pow(cell_diff, 2); weights[2] = 0.5f * math.pow(0.5f + cell_diff, 2); // for all surrounding 9 cells for (uint gx = 0; gx < 3; ++gx) { for (uint gy = 0; gy < 3; ++gy) { float weight = weights[gx].x * weights[gy].y; uint2 cell_x = math.uint2(cell_idx.x + gx - 1, cell_idx.y + gy - 1); float2 cell_dist = (cell_x - p.x) + 0.5f; float2 Q = math.mul(p.C, cell_dist); // scatter mass and momentum to the grid int cell_index = (int)cell_x.x * grid_res + (int)cell_x.y; Cell cell = grid[cell_index]; // MPM course, equation 172 float weighted_mass = weight * p.mass; cell.mass += weighted_mass; // APIC P2G momentum contribution cell.v += weighted_mass * (p.v + Q); // fused force/momentum update from MLS-MPM // see MLS-MPM paper, equation listed after eqn. 28 float2 momentum = math.mul(eq_16_term_0 * weight, cell_dist); cell.v += momentum; // total update on cell.v is now: // weight * (dt * M^-1 * p.volume * p.stress + p.mass * p.C) // this is the fused momentum + force from MLS-MPM. however, instead of our stress being derived from the energy density, // i use the weak form with cauchy stress. converted: // p.volume_0 * (dΨ/dF)(Fp)*(Fp_transposed) // is equal to p.volume * σ // note: currently "cell.v" refers to MOMENTUM, not velocity! // this gets converted in the UpdateGrid step below. grid[cell_index] = cell; } } } }
public static float dot(this float2x2 f) => math.dot(f.c0, f.c1);
///Self Squared Distance public static float distancesq(this float2x2 f) => math.distancesq(f.c0, f.c1);
protected UvTransform?TextureRotationSlider( Material material, UvTransform?uvTransform, int scaleTransformPropertyId, int rotationPropertyId, bool freezeScale = false ) { UvTransform oldUvTransform; UvTransform newUvTransform; if (uvTransform.HasValue) { oldUvTransform = uvTransform.Value; newUvTransform = uvTransform.Value; } else { GetUvTransform(material, scaleTransformPropertyId, rotationPropertyId, out oldUvTransform); newUvTransform = new UvTransform(); } GUILayout.BeginHorizontal(); GUILayout.Label("Texture Rotation"); var newUvRotation = EditorGUILayout.Slider(oldUvTransform.rotation, 0, 360); GUILayout.EndHorizontal(); float2 newUvScale = new float2(1, 1); if (!freezeScale) { GUILayout.BeginHorizontal(); newUvScale = EditorGUILayout.Vector2Field("Scale", oldUvTransform.scale); GUILayout.EndHorizontal(); } if (!uvTransform.HasValue) { newUvTransform.rotation = newUvRotation; newUvTransform.scale = newUvScale; } bool update = false; if (Math.Abs(newUvRotation - oldUvTransform.rotation) > TOLERANCE) { newUvTransform.rotation = newUvRotation; update = true; } if (!freezeScale && !newUvScale.Equals(oldUvTransform.scale)) { newUvTransform.scale = newUvScale; update = true; } if (update) { var cos = math.cos(newUvTransform.rotation * Mathf.Deg2Rad); var sin = math.sin(newUvTransform.rotation * Mathf.Deg2Rad); var currentScaleTransform = material.GetVector(scaleTransformPropertyId); float2x2 rotScale = math.mul(new float2x2(cos, sin, -sin, cos), new float2x2(newUvTransform.scale.x, 0, 0, newUvTransform.scale.y)); material.SetVector(scaleTransformPropertyId, new Vector4(rotScale.c0.x, rotScale.c1.y, currentScaleTransform.z, currentScaleTransform.w)); material.SetVector(rotationPropertyId, new Vector4(rotScale.c1.x, rotScale.c0.y, 0, 0)); if (newUvTransform.rotation == 0) { material.DisableKeyword(KW_UV_ROTATION); } else { material.EnableKeyword(KW_UV_ROTATION); } return(newUvTransform); } return(uvTransform); }