Exemplo n.º 1
0
        /**
         * Automatically generates tether constraints for the cloth.
         * Partitions fixed particles into "islands", then generates up to maxTethers constraints for each
         * particle, linking it to the closest point in each island.
         */
        public override bool GenerateTethers()
        {
            if (!Initialized)
            {
                return(false);
            }

            TetherConstraints.Clear();

            GenerateFixedTethers(2);

            return(true);
        }
Exemplo n.º 2
0
        /**
         * Automatically generates tether constraints for the cloth.
         * Partitions fixed particles into "islands", then generates up to maxTethers constraints for each
         * particle, linking it to the closest point in each island.
         */
        public override bool GenerateTethers()
        {
            if (!Initialized)
            {
                return(false);
            }

            TetherConstraints.Clear();

            // generate disjoint islands:
            List <HashSet <int> > islands = GenerateIslands(System.Linq.Enumerable.Range(0, topology.heVertices.Length), false);

            // generate tethers for each one:
            foreach (HashSet <int> island in islands)
            {
                GenerateTethersForIsland(island, 4);
            }

            return(true);
        }
Exemplo n.º 3
0
        /**
         * Automatically generates tether constraints for the cloth.
         * Partitions fixed particles into "islands", then generates up to maxTethers constraints for each
         * particle, linking it to the closest point in each island.
         */
        public override bool GenerateTethers(TetherType type)
        {
            if (!Initialized)
            {
                return(false);
            }

            TetherConstraints.Clear();

            if (type == TetherType.Hierarchical)
            {
                GenerateHierarchicalTethers(5);
            }
            else
            {
                GenerateFixedTethers(2);
            }

            return(true);
        }
Exemplo n.º 4
0
        /**
         * Generates the particle based physical representation of the rope. This is the initialization method for the rope object
         * and should not be called directly once the object has been created.
         */
        protected override IEnumerator Initialize()
        {
            initialized           = false;
            initializing          = true;
            interParticleDistance = -1;

            RemoveFromSolver(null);

            if (ropePath == null)
            {
                Debug.LogError("Cannot initialize rope. There's no ropePath present. Please provide a spline to define the shape of the rope");
                yield break;
            }

            ropePath.RecalculateSplineLenght(0.00001f, 7);
            closed     = ropePath.closed;
            restLength = ropePath.Length;

            usedParticles  = Mathf.CeilToInt(restLength / thickness * resolution) + (closed ? 0:1);
            totalParticles = usedParticles + pooledParticles;             //allocate extra particles to allow for lenght change and tearing.

            active         = new bool[totalParticles];
            positions      = new Vector3[totalParticles];
            velocities     = new Vector3[totalParticles];
            invMasses      = new float[totalParticles];
            principalRadii = new Vector3[totalParticles];
            phases         = new int[totalParticles];
            restPositions  = new Vector4[totalParticles];
            tearResistance = new float[totalParticles];
            colors         = new Color[totalParticles];

            int numSegments = usedParticles - (closed ? 0:1);

            if (numSegments > 0)
            {
                interParticleDistance = restLength / (float)numSegments;
            }
            else
            {
                interParticleDistance = 0;
            }

            float radius = interParticleDistance * resolution;

            for (int i = 0; i < usedParticles; i++)
            {
                active[i]    = true;
                invMasses[i] = 1.0f / DEFAULT_PARTICLE_MASS;
                float mu = ropePath.GetMuAtLenght(interParticleDistance * i);
                positions[i]      = transform.InverseTransformPoint(ropePath.transform.TransformPoint(ropePath.GetPositionAt(mu)));
                principalRadii[i] = Vector3.one * radius;
                phases[i]         = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0);
                tearResistance[i] = 1;
                colors[i]         = Color.white;

                if (i % 100 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)usedParticles));
                }
            }

            // Initialize basic data for pooled particles:
            for (int i = usedParticles; i < totalParticles; i++)
            {
                active[i]         = false;
                invMasses[i]      = 1.0f / DEFAULT_PARTICLE_MASS;
                principalRadii[i] = Vector3.one * radius;
                phases[i]         = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0);
                tearResistance[i] = 1;
                colors[i]         = Color.white;

                if (i % 100 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRope: generating particles...", i / (float)usedParticles));
                }
            }

            DistanceConstraints.Clear();
            ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            DistanceConstraints.AddBatch(distanceBatch);

            for (int i = 0; i < numSegments; i++)
            {
                distanceBatch.AddConstraint(i, (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1), interParticleDistance, 1, 1);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRope: generating structural constraints...", i / (float)numSegments));
                }
            }

            BendingConstraints.Clear();
            ObiBendConstraintBatch bendingBatch = new ObiBendConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            BendingConstraints.AddBatch(bendingBatch);
            for (int i = 0; i < usedParticles - (closed?0:2); i++)
            {
                // rope bending constraints always try to keep it completely straight:
                bendingBatch.AddConstraint(i, (i + 2) % usedParticles, (i + 1) % usedParticles, 0, 0, 1);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRope: adding bend constraints...", i / (float)usedParticles));
                }
            }

            // Initialize tether constraints:
            TetherConstraints.Clear();

            // Initialize pin constraints:
            PinConstraints.Clear();
            ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false, 0, MAX_YOUNG_MODULUS);

            PinConstraints.AddBatch(pinBatch);

            initializing = false;
            initialized  = true;

            RegenerateRestPositions();
        }
Exemplo n.º 5
0
        /**
         * Generates the particle based physical representation of the rope. This is the initialization method for the rope object
         * and should not be called directly once the object has been created.
         */
        protected override IEnumerator Initialize()
        {
            initialized           = false;
            initializing          = true;
            interParticleDistance = -1;

            RemoveFromSolver(null);

            if (ropePath == null)
            {
                Debug.LogError("Cannot initialize rope. There's no ropePath present. Please provide a spline to define the shape of the rope");
                yield break;
            }

            ropePath.RecalculateSplineLenght(0.00001f, 7);
            closed     = ropePath.closed;
            restLength = ropePath.Length;

            usedParticles  = Mathf.CeilToInt(restLength / thickness * resolution) + (closed ? 0:1);
            totalParticles = usedParticles;

            active              = new bool[totalParticles];
            positions           = new Vector3[totalParticles];
            orientations        = new Quaternion[totalParticles];
            velocities          = new Vector3[totalParticles];
            angularVelocities   = new Vector3[totalParticles];
            invMasses           = new float[totalParticles];
            invRotationalMasses = new float[totalParticles];
            principalRadii      = new Vector3[totalParticles];
            phases              = new int[totalParticles];
            restPositions       = new Vector4[totalParticles];
            restOrientations    = new Quaternion[totalParticles];
            colors              = new Color[totalParticles];

            int numSegments = usedParticles - (closed ? 0:1);

            if (numSegments > 0)
            {
                interParticleDistance = restLength / (float)numSegments;
            }
            else
            {
                interParticleDistance = 0;
            }

            float radius = interParticleDistance * resolution;

            for (int i = 0; i < usedParticles; i++)
            {
                active[i]              = true;
                invMasses[i]           = 1.0f / DEFAULT_PARTICLE_MASS;
                invRotationalMasses[i] = 1.0f / DEFAULT_PARTICLE_ROTATIONAL_MASS;
                float mu = ropePath.GetMuAtLenght(interParticleDistance * i);
                positions[i]      = transform.InverseTransformPoint(ropePath.transform.TransformPoint(ropePath.GetPositionAt(mu)));
                principalRadii[i] = Vector3.one * radius;
                phases[i]         = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0);
                colors[i]         = Color.white;

                if (i % 100 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)usedParticles));
                }
            }

            StretchShearConstraints.Clear();
            ObiStretchShearConstraintBatch stretchBatch = new ObiStretchShearConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            StretchShearConstraints.AddBatch(stretchBatch);

            // rotation minimizing frame:
            ObiCurveFrame frame = new ObiCurveFrame();

            frame.Reset();

            for (int i = 0; i < numSegments; i++)
            {
                int next = (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1);

                float   mu     = ropePath.GetMuAtLenght(interParticleDistance * i);
                Vector3 normal = transform.InverseTransformVector(ropePath.transform.TransformVector(ropePath.GetNormalAt(mu)));

                frame.Transport(positions[i], (positions[next] - positions[i]).normalized, 0);

                orientations[i]     = Quaternion.LookRotation(frame.tangent, normal);
                restOrientations[i] = orientations[i];

                // Also set the orientation of the next particle. If it is not the last one, we will overwrite it.
                // This makes sure that open rods provide an orientation for their last particle (or rather, a phantom segment past the last particle).

                orientations[next]     = orientations[i];
                restOrientations[next] = orientations[i];

                stretchBatch.AddConstraint(i, next, interParticleDistance, Quaternion.identity, Vector3.one);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)numSegments));
                }
            }

            BendTwistConstraints.Clear();
            ObiBendTwistConstraintBatch twistBatch = new ObiBendTwistConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            BendTwistConstraints.AddBatch(twistBatch);

            // the last bend constraint couples the last segment and a phantom segment past the last particle.
            for (int i = 0; i < numSegments; i++)
            {
                int next = (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1);

                Quaternion darboux = keepInitialShape ? ObiUtils.RestDarboux(orientations[i], orientations[next]) : Quaternion.identity;
                twistBatch.AddConstraint(i, next, darboux, Vector3.one);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)numSegments));
                }
            }

            ChainConstraints.Clear();
            ObiChainConstraintBatch chainBatch = new ObiChainConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            ChainConstraints.AddBatch(chainBatch);

            int[] indices = new int[usedParticles + (closed ? 1:0)];

            for (int i = 0; i < usedParticles; ++i)
            {
                indices[i] = i;
            }

            // Add the first particle as the last index of the chain, if closed.
            if (closed)
            {
                indices[usedParticles] = 0;
            }

            chainBatch.AddConstraint(indices, interParticleDistance, 1, 1);


            // Initialize tether constraints:
            TetherConstraints.Clear();

            // Initialize pin constraints:
            PinConstraints.Clear();
            ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS);

            PinConstraints.AddBatch(pinBatch);

            initializing = false;
            initialized  = true;

            RegenerateRestPositions();
        }
Exemplo n.º 6
0
        /**
         * Generates the particle based physical representation of the cloth mesh. This is the initialization method for the cloth object
         * and should not be called directly once the object has been created.
         */
        protected override IEnumerator Initialize()
        {
            initialized  = false;
            initializing = false;

            if (sharedTopology == null)
            {
                Debug.LogError("No ObiMeshTopology provided. Cannot initialize physical representation.");
                yield break;
            }
            else if (!sharedTopology.Initialized)
            {
                Debug.LogError("The provided ObiMeshTopology contains no data. Cannot initialize physical representation.");
                yield break;
            }

            initializing = true;

            RemoveFromSolver(null);

            GameObject.DestroyImmediate(topology);
            topology = GameObject.Instantiate(sharedTopology);

            active              = new bool[topology.heVertices.Length];
            positions           = new Vector3[topology.heVertices.Length];
            restPositions       = new Vector4[topology.heVertices.Length];
            velocities          = new Vector3[topology.heVertices.Length];
            invMasses           = new float[topology.heVertices.Length];
            principalRadii      = new Vector3[topology.heVertices.Length];
            phases              = new int[topology.heVertices.Length];
            areaContribution    = new float[topology.heVertices.Length];
            deformableTriangles = new int[topology.heFaces.Length * 3];

            initialScaleMatrix.SetTRS(Vector3.zero, Quaternion.identity, transform.lossyScale);

            // Create a particle for each vertex:
            for (int i = 0; i < topology.heVertices.Length; i++)
            {
                Oni.Vertex vertex = topology.heVertices[i];

                // Get the particle's area contribution.
                areaContribution[i] = 0;
                foreach (Oni.Face face in topology.GetNeighbourFacesEnumerator(vertex))
                {
                    areaContribution[i] += topology.GetFaceArea(face) / 3;
                }

                // Get the shortest neighbour edge, particle radius will be half of its length.
                float minEdgeLength = Single.MaxValue;
                foreach (Oni.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex))
                {
                    // vertices at each end of the edge:
                    Vector3 v1 = initialScaleMatrix * topology.heVertices[topology.GetHalfEdgeStartVertex(edge)].position;
                    Vector3 v2 = initialScaleMatrix * topology.heVertices[edge.endVertex].position;

                    minEdgeLength = Mathf.Min(minEdgeLength, Vector3.Distance(v1, v2));
                }

                active[i]           = true;
                invMasses[i]        = (skinnedMeshRenderer == null && areaContribution[i] > 0) ? (1.0f / (DEFAULT_PARTICLE_MASS * areaContribution[i])) : 0;
                positions[i]        = initialScaleMatrix * vertex.position;
                restPositions[i]    = positions[i];
                restPositions[i][3] = 1;         // activate rest position.
                principalRadii[i]   = Vector3.one * minEdgeLength * 0.5f;
                phases[i]           = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating particles...", i / (float)topology.heVertices.Length));
                }
            }

            // Generate deformable triangles:
            for (int i = 0; i < topology.heFaces.Length; i++)
            {
                Oni.Face face = topology.heFaces[i];

                Oni.HalfEdge e1 = topology.heHalfEdges[face.halfEdge];
                Oni.HalfEdge e2 = topology.heHalfEdges[e1.nextHalfEdge];
                Oni.HalfEdge e3 = topology.heHalfEdges[e2.nextHalfEdge];

                deformableTriangles[i * 3]     = e1.endVertex;
                deformableTriangles[i * 3 + 1] = e2.endVertex;
                deformableTriangles[i * 3 + 2] = e3.endVertex;

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating deformable geometry...", i / (float)topology.heFaces.Length));
                }
            }

            List <ObiMeshTopology.HEEdge> edges = topology.GetEdgeList();

            DistanceConstraints.Clear();
            ObiDistanceConstraintBatch distanceBatch = new ObiDistanceConstraintBatch(true, false);

            DistanceConstraints.AddBatch(distanceBatch);

            // Create distance springs:
            for (int i = 0; i < edges.Count; i++)
            {
                Oni.HalfEdge hedge       = topology.heHalfEdges[edges[i].halfEdgeIndex];
                Oni.Vertex   startVertex = topology.heVertices[topology.GetHalfEdgeStartVertex(hedge)];
                Oni.Vertex   endVertex   = topology.heVertices[hedge.endVertex];

                distanceBatch.AddConstraint(topology.GetHalfEdgeStartVertex(hedge), hedge.endVertex, Vector3.Distance(initialScaleMatrix * startVertex.position, initialScaleMatrix * endVertex.position), 1, 1);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating structural constraints...", i / (float)topology.heHalfEdges.Length));
                }
            }

            // Cook distance constraints, for better cache and SIMD use:
            distanceBatch.Cook();

            // Create aerodynamic constraints:
            AerodynamicConstraints.Clear();
            ObiAerodynamicConstraintBatch aeroBatch = new ObiAerodynamicConstraintBatch(false, false);

            AerodynamicConstraints.AddBatch(aeroBatch);

            for (int i = 0; i < topology.heVertices.Length; i++)
            {
                aeroBatch.AddConstraint(i,
                                        areaContribution[i],
                                        AerodynamicConstraints.dragCoefficient,
                                        AerodynamicConstraints.liftCoefficient);

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating aerodynamic constraints...", i / (float)topology.heFaces.Length));
                }
            }

            //Create skin constraints (if needed)
            if (skinnedMeshRenderer != null)
            {
                SkinConstraints.Clear();
                ObiSkinConstraintBatch skinBatch = new ObiSkinConstraintBatch(true, false);
                SkinConstraints.AddBatch(skinBatch);

                for (int i = 0; i < topology.heVertices.Length; ++i)
                {
                    skinBatch.AddConstraint(i, initialScaleMatrix * topology.heVertices[i].position, Vector3.up, 0.05f, 0.1f, 0, 1);

                    if (i % 500 == 0)
                    {
                        yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating skin constraints...", i / (float)topology.heVertices.Length));
                    }
                }

                for (int i = 0; i < topology.normals.Length; ++i)
                {
                    skinBatch.skinNormals[topology.visualMap[i]] = topology.normals[i];
                }

                skinBatch.Cook();
            }

            //Create pressure constraints if the mesh is closed:
            VolumeConstraints.Clear();

            if (topology.IsClosed)
            {
                ObiVolumeConstraintBatch volumeBatch = new ObiVolumeConstraintBatch(false, false);
                VolumeConstraints.AddBatch(volumeBatch);

                float avgInitialScale = (initialScaleMatrix.m00 + initialScaleMatrix.m11 + initialScaleMatrix.m22) * 0.33f;

                int[] triangleIndices = new int[topology.heFaces.Length * 3];
                for (int i = 0; i < topology.heFaces.Length; i++)
                {
                    Oni.Face face = topology.heFaces[i];

                    Oni.HalfEdge e1 = topology.heHalfEdges[face.halfEdge];
                    Oni.HalfEdge e2 = topology.heHalfEdges[e1.nextHalfEdge];
                    Oni.HalfEdge e3 = topology.heHalfEdges[e2.nextHalfEdge];

                    triangleIndices[i * 3]     = e1.endVertex;
                    triangleIndices[i * 3 + 1] = e2.endVertex;
                    triangleIndices[i * 3 + 2] = e3.endVertex;

                    if (i % 500 == 0)
                    {
                        yield return(new CoroutineJob.ProgressInfo("ObiCloth: generating volume constraints...", i / (float)topology.heFaces.Length));
                    }
                }

                volumeBatch.AddConstraint(triangleIndices, topology.MeshVolume * avgInitialScale, 1, 1);
            }

            //Create bending constraints:
            BendingConstraints.Clear();
            ObiBendConstraintBatch bendBatch = new ObiBendConstraintBatch(true, false);

            BendingConstraints.AddBatch(bendBatch);

            Dictionary <int, int> cons = new Dictionary <int, int>();

            for (int i = 0; i < topology.heVertices.Length; i++)
            {
                Oni.Vertex vertex = topology.heVertices[i];

                foreach (Oni.Vertex n1 in topology.GetNeighbourVerticesEnumerator(vertex))
                {
                    float      cosBest = 0;
                    Oni.Vertex vBest   = n1;

                    foreach (Oni.Vertex n2 in topology.GetNeighbourVerticesEnumerator(vertex))
                    {
                        float cos = Vector3.Dot((n1.position - vertex.position).normalized,
                                                (n2.position - vertex.position).normalized);
                        if (cos < cosBest)
                        {
                            cosBest = cos;
                            vBest   = n2;
                        }
                    }

                    if (!cons.ContainsKey(vBest.index) || cons[vBest.index] != n1.index)
                    {
                        cons[n1.index] = vBest.index;

                        Vector3 n1Pos     = initialScaleMatrix * n1.position;
                        Vector3 bestPos   = initialScaleMatrix * vBest.position;
                        Vector3 vertexPos = initialScaleMatrix * vertex.position;

                        float[] bendRestPositions = new float[] { n1Pos[0], n1Pos[1], n1Pos[2],
                                                                  bestPos[0], bestPos[1], bestPos[2],
                                                                  vertexPos[0], vertexPos[1], vertexPos[2] };

                        float restBend = Oni.BendingConstraintRest(bendRestPositions);
                        bendBatch.AddConstraint(n1.index, vBest.index, vertex.index, restBend, 0, 1);
                    }
                }

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiCloth: adding bend constraints...", i / (float)sharedTopology.heVertices.Length));
                }
            }

            bendBatch.Cook();

            // Initialize tether constraints:
            TetherConstraints.Clear();

            // Initialize pin constraints:
            PinConstraints.Clear();
            ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false);

            PinConstraints.AddBatch(pinBatch);

            initializing = false;
            initialized  = true;

            if (skinnedMeshRenderer == null)
            {
                InitializeWithRegularMesh();
            }
            else
            {
                InitializeWithSkinnedMesh();
            }
        }
Exemplo n.º 7
0
        /**
         * Automatically generates tether constraints for the cloth.
         * Partitions fixed particles into "islands", then generates up to maxTethers constraints for each
         * particle, linking it to the closest point in each island.
         */
        public override bool GenerateTethers(int maxTethers)
        {
            if (!Initialized)
            {
                return(false);
            }

            TetherConstraints.Clear();

            if (maxTethers > 0)
            {
                ObiTetherConstraintBatch tetherBatch = new ObiTetherConstraintBatch(true, false);
                TetherConstraints.AddBatch(tetherBatch);

                List <HashSet <int> > islands = new List <HashSet <int> >();

                // Partition fixed particles into islands:
                for (int i = 0; i < topology.heVertices.Length; i++)
                {
                    Oni.Vertex vertex = topology.heVertices[i];
                    if (invMasses[i] > 0 || !active[i])
                    {
                        continue;
                    }

                    int assignedIsland = -1;

                    // keep a list of islands to merge with ours:
                    List <int> mergeableIslands = new List <int>();

                    // See if any of our neighbors is part of an island:
                    foreach (Oni.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex))
                    {
                        if (!active[n.index])
                        {
                            continue;
                        }

                        for (int k = 0; k < islands.Count; ++k)
                        {
                            if (islands[k].Contains(n.index))
                            {
                                // if we are not in an island yet, pick this one:
                                if (assignedIsland < 0)
                                {
                                    assignedIsland = k;
                                    islands[k].Add(i);
                                }
                                // if we already are in an island, we will merge this newfound island with ours:
                                else if (assignedIsland != k && !mergeableIslands.Contains(k))
                                {
                                    mergeableIslands.Add(k);
                                }
                            }
                        }
                    }

                    // merge islands with the assigned one:
                    foreach (int merge in mergeableIslands)
                    {
                        islands[assignedIsland].UnionWith(islands[merge]);
                    }

                    // remove merged islands:
                    mergeableIslands.Sort();
                    mergeableIslands.Reverse();
                    foreach (int merge in mergeableIslands)
                    {
                        islands.RemoveAt(merge);
                    }

                    // If no adjacent particle is in an island, create a new one:
                    if (assignedIsland < 0)
                    {
                        islands.Add(new HashSet <int>()
                        {
                            i
                        });
                    }
                }

                // Generate tether constraints:
                for (int i = 0; i < invMasses.Length; ++i)
                {
                    if (invMasses[i] == 0 || !active[i])
                    {
                        continue;
                    }

                    List <KeyValuePair <float, int> > tethers = new List <KeyValuePair <float, int> >(islands.Count * maxTethers);

                    // Find the closest particle in each island, and add it to tethers.
                    foreach (HashSet <int> island in islands)
                    {
                        int   closest     = -1;
                        float minDistance = Mathf.Infinity;
                        foreach (int j in island)
                        {
                            float distance = (topology.heVertices[i].position - topology.heVertices[j].position).sqrMagnitude;
                            if (distance < minDistance)
                            {
                                minDistance = distance;
                                closest     = j;
                            }
                        }
                        if (closest >= 0)
                        {
                            tethers.Add(new KeyValuePair <float, int>(minDistance, closest));
                        }
                    }

                    // Sort tether indices by distance:
                    tethers.Sort(
                        delegate(KeyValuePair <float, int> x, KeyValuePair <float, int> y)
                    {
                        return(x.Key.CompareTo(y.Key));
                    }
                        );

                    // Create constraints for "maxTethers" closest anchor particles:
                    for (int k = 0; k < Mathf.Min(maxTethers, tethers.Count); ++k)
                    {
                        tetherBatch.AddConstraint(i, tethers[k].Value, Mathf.Sqrt(tethers[k].Key),
                                                  TetherConstraints.tetherScale,
                                                  TetherConstraints.stiffness);
                    }
                }

                tetherBatch.Cook();
            }

            return(true);
        }