void ICustomVisit <float2x2> .CustomVisit(float2x2 f)
 {
     GUILayout.Label(Property.Name);
     EditorGUILayout.Vector2Field("", (Vector2)f.c0);
     EditorGUILayout.Vector2Field("", (Vector2)f.c1);
 }
Example #2
0
 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);
    }
Example #4
0
        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;
        }
    }
Example #5
0
        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;
                    }
                }
            }
        }
Example #6
0
 public static float dot(this float2x2 f) => math.dot(f.c0, f.c1);
Example #7
0
 ///Self Squared Distance
 public static float distancesq(this float2x2 f) => math.distancesq(f.c0, f.c1);
Example #8
0
        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);
        }