public override IObiConstraintsBatch Clone()
        {
            var clone = new ObiPinConstraintsBatch(this);

            // careful here: since IntPtr is not serializable and the pinBodies array can be null, use offsets count instead.
            clone.pinBodies.ResizeUninitialized(offsets.count);

            clone.particleIndices.ResizeUninitialized(particleIndices.count);
            clone.offsets.ResizeUninitialized(offsets.count);
            clone.restDarbouxVectors.ResizeUninitialized(restDarbouxVectors.count);
            clone.stiffnesses.ResizeUninitialized(stiffnesses.count);
            clone.breakThresholds.ResizeUninitialized(breakThresholds.count);

            if (pinBodies != null)
            {
                clone.pinBodies.CopyFrom(pinBodies);
            }

            clone.particleIndices.CopyFrom(particleIndices);
            clone.offsets.CopyFrom(offsets);
            clone.restDarbouxVectors.CopyFrom(restDarbouxVectors);
            clone.stiffnesses.CopyFrom(stiffnesses);
            clone.breakThresholds.CopyFrom(breakThresholds);

            return(clone);
        }
Пример #2
0
        private void DisableAttachment(AttachmentType type)
        {
            if (isBound)
            {
                switch (type)
                {
                case AttachmentType.Dynamic:

                    if (pinBatch != null)
                    {
                        var pins = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints <ObiPinConstraintsBatch>;
                        if (pins != null)
                        {
                            pins.RemoveBatch(pinBatch);
                            if (actor.isLoaded)
                            {
                                m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin);
                            }
                        }

                        attachedCollider            = null;
                        pinBatch                    = null;
                        attachedColliderHandleIndex = -1;
                    }

                    break;

                case AttachmentType.Static:

                    var solver    = m_Actor.solver;
                    var blueprint = m_Actor.sourceBlueprint;

                    for (int i = 0; i < m_SolverIndices.Length; ++i)
                    {
                        int solverIndex = m_SolverIndices[i];
                        if (solverIndex >= 0 && solverIndex < solver.invMasses.count)
                        {
                            solver.invMasses[solverIndex] = blueprint.invMasses[i];
                        }
                    }

                    if (m_Actor.usesOrientedParticles)
                    {
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            int solverIndex = m_SolverIndices[i];
                            if (solverIndex >= 0 && solverIndex < solver.invRotationalMasses.count)
                            {
                                solver.invRotationalMasses[solverIndex] = blueprint.invRotationalMasses[i];
                            }
                        }
                    }

                    m_Actor.UpdateParticleProperties();

                    break;
                }
            }
        }
Пример #3
0
        private void Disable(AttachmentType type)
        {
            var solver    = m_Actor.solver;
            var blueprint = m_Actor.blueprint;

            if (isBound && blueprint != null && solver != null)
            {
                switch (type)
                {
                case AttachmentType.Dynamic:

                    var pins = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints <ObiPinConstraintsBatch>;
                    if (pins != null && pinBatch != null)
                    {
                        pinBatch.SetEnabled(false);
                        pinBatch.RemoveFromSolver();
                        pins.RemoveBatch(pinBatch);
                        pinBatch = null;
                    }

                    break;

                case AttachmentType.Static:

                    for (int i = 0; i < m_SolverIndices.Length; ++i)
                    {
                        if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invMasses.count)
                        {
                            solver.invMasses[m_SolverIndices[i]] = blueprint.invMasses[i];
                        }
                    }

                    if (m_Actor.usesOrientedParticles)
                    {
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invRotationalMasses.count)
                            {
                                solver.invRotationalMasses[m_SolverIndices[i]] = blueprint.invRotationalMasses[i];
                            }
                        }
                    }

                    m_Actor.UpdateParticleProperties();

                    break;
                }
            }
        }
Пример #4
0
        public override IObiConstraintsBatch Clone(IObiConstraints constraints)
        {
            var clone = new ObiPinConstraintsBatch(constraints as ObiPinConstraintsData, this);

            // careful here: since IntPtr is not serializable and the pinBodies array can be null, use offsets count instead.
            clone.pinBodies.Capacity = offsets.count;
            clone.pinBodies.Clear();

            if (pinBodies != null)
            {
                for (int i = 0; i < offsets.count; ++i)
                {
                    clone.pinBodies.Add(pinBodies[i]);
                }
            }
            else
            {
                for (int i = 0; i < offsets.count; ++i)
                {
                    clone.pinBodies.Add(new ObiColliderHandle());
                }
            }


            clone.particleIndices.ResizeUninitialized(particleIndices.count);
            clone.offsets.ResizeUninitialized(offsets.count);
            clone.restDarbouxVectors.ResizeUninitialized(restDarbouxVectors.count);
            clone.stiffnesses.ResizeUninitialized(stiffnesses.count);
            clone.breakThresholds.ResizeUninitialized(breakThresholds.count);

            clone.particleIndices.CopyFrom(particleIndices);
            clone.offsets.CopyFrom(offsets);
            clone.restDarbouxVectors.CopyFrom(restDarbouxVectors);
            clone.stiffnesses.CopyFrom(stiffnesses);
            clone.breakThresholds.CopyFrom(breakThresholds);

            return(clone);
        }
Пример #5
0
 public ObiPinConstraintsBatch(ObiPinConstraintsData constraints = null, ObiPinConstraintsBatch source = null) : base(source)
 {
     m_Constraints = constraints;
 }
Пример #6
0
        protected override IEnumerator Initialize()
        {
            if (inputMesh == null || !inputMesh.isReadable)
            {
                // TODO: return an error in the coroutine.
                Debug.LogError("The input mesh is null, or not readable.");
                yield break;
            }

            ClearParticleGroups();

            Vector3[] vertices = inputMesh.vertices;
            Vector3[] normals  = inputMesh.normals;
            vertexToParticle = new int[vertices.Length];
            List <Vector3> particles = new List <Vector3>();

            // Add particles to every vertex, as long as they are not too close to the already added ones:
            for (int i = 0; i < vertices.Length; ++i)
            {
                bool    intersects   = false;
                Vector3 vertexScaled = Vector3.Scale(scale, vertices[i]);

                for (int j = 0; j < particles.Count; ++j)
                {
                    if (Vector3.Distance(vertexScaled, particles[j]) < particleRadius * 2 * (1 - particleOverlap))
                    {
                        intersects = true;
                        break;
                    }
                }
                if (intersects)
                {
                    continue;
                }

                particles.Add(vertexScaled);

                if (i % 100 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiSoftbody: sampling mesh...", i / (float)vertices.Length));
                }
            }

            // Find out the closes particle to each vertex:
            for (int i = 0; i < vertices.Length; ++i)
            {
                Vector3 vertexScaled = Vector3.Scale(scale, vertices[i]);
                float   minDistance  = float.MaxValue;
                vertexToParticle[i] = 0;

                for (int j = 0; j < particles.Count; ++j)
                {
                    float distance = Vector3.SqrMagnitude(vertexScaled - particles[j]);
                    if (distance < minDistance)
                    {
                        minDistance         = distance;
                        vertexToParticle[i] = j;
                    }
                }

                if (i % 100 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiSoftbody: mapping vertices to particles...", i / (float)vertices.Length));
                }
            }

            positions           = new Vector3[particles.Count];
            orientations        = new Quaternion[particles.Count];
            restPositions       = new Vector4[particles.Count];
            restOrientations    = new Quaternion[particles.Count];
            velocities          = new Vector3[particles.Count];
            angularVelocities   = new Vector3[particles.Count];
            invMasses           = new float[particles.Count];
            invRotationalMasses = new float[particles.Count];
            principalRadii      = new Vector3[particles.Count];
            phases = new int[particles.Count];
            colors = new Color[particles.Count];

            m_ActiveParticleCount = particles.Count;

            for (int i = 0; i < particles.Count; ++i)
            {
                // Perform ellipsoid fitting:
                Vector3        avgNormal         = Vector3.zero;
                List <Vector3> neighbourVertices = new List <Vector3>();

                for (int j = 0; j < vertices.Length; ++j)
                {
                    Vector3 vertexScaled = Vector3.Scale(scale, vertices[j]);

                    if (Vector3.Distance(vertexScaled, particles[i]) < anisotropyNeighborhood)
                    {
                        neighbourVertices.Add(vertexScaled);
                        avgNormal += normals[j];
                    }
                }
                if (neighbourVertices.Count > 0)
                {
                    avgNormal /= neighbourVertices.Count;
                }

                Vector3    centroid        = particles[i];
                Quaternion orientation     = Quaternion.identity;
                Vector3    principalValues = Vector3.one;
                Oni.GetPointCloudAnisotropy(neighbourVertices.ToArray(), neighbourVertices.Count, maxAnisotropy, particleRadius, ref avgNormal, ref centroid, ref orientation, ref principalValues);

                invRotationalMasses[i] = invMasses[i] = 1.0f;
                positions[i]           = Vector3.Lerp(particles[i], centroid, shapeSmoothing);
                restPositions[i]       = positions[i];
                restPositions[i][3]    = 1; // activate rest position.
                orientations[i]        = orientation;
                restOrientations[i]    = orientation;
                principalRadii[i]      = principalValues;
                phases[i] = Oni.MakePhase(1, oneSided ? Oni.ParticleFlags.OneSided : 0);
                colors[i] = Color.white;

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


            //if (makeSolidCluster){

            /*indices.Clear();
             * for (int i = 0; i < particles.Count; ++i)
             *  indices.Add(i);
             * shapeBatch.AddConstraint(indices.ToArray(),1,0,0,true);
             * }*/

            IEnumerator sc = CreateShapeMatchingConstraints(particles);

            while (sc.MoveNext())
            {
                yield return(sc.Current);
            }

            // Initialize pin constraints:
            pinConstraintsData = new ObiPinConstraintsData();
            ObiPinConstraintsBatch pinBatch = new ObiPinConstraintsBatch();

            pinConstraintsData.AddBatch(pinBatch);

            generatedMesh = inputMesh;
        }
Пример #7
0
        private void EnableAttachment(AttachmentType type)
        {
            if (enabled && m_Actor.isLoaded && isBound)
            {
                var solver = m_Actor.solver;

                switch (type)
                {
                case AttachmentType.Dynamic:

                    var pins = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiPinConstraintsData;
                    attachedCollider = m_Target.GetComponent <ObiColliderBase>();

                    if (pins != null && attachedCollider != null && pinBatch == null)
                    {
                        // create a new data batch with all our pin constraints:
                        pinBatch = new ObiPinConstraintsBatch(pins);
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            pinBatch.AddConstraint(m_SolverIndices[i],
                                                   attachedCollider,
                                                   m_PositionOffsets[i],
                                                   m_OrientationOffsets[i],
                                                   m_Compliance,
                                                   constrainOrientation ? 0 : 10000,
                                                   m_BreakThreshold);

                            pinBatch.activeConstraintCount++;
                        }

                        // add the batch to the actor:
                        pins.AddBatch(pinBatch);

                        // store the attached collider's handle:
                        attachedColliderHandleIndex = -1;
                        if (attachedCollider.Handle != null)
                        {
                            attachedColliderHandleIndex = attachedCollider.Handle.index;
                        }

                        m_Actor.SetConstraintsDirty(Oni.ConstraintType.Pin);
                    }

                    break;

                case AttachmentType.Static:

                    for (int i = 0; i < m_SolverIndices.Length; ++i)
                    {
                        if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invMasses.count)
                        {
                            solver.invMasses[m_SolverIndices[i]] = 0;
                        }
                    }

                    if (m_Actor.usesOrientedParticles && m_ConstrainOrientation)
                    {
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            if (m_SolverIndices[i] >= 0 && m_SolverIndices[i] < solver.invRotationalMasses.count)
                            {
                                solver.invRotationalMasses[m_SolverIndices[i]] = 0;
                            }
                        }
                    }

                    m_Actor.UpdateParticleProperties();

                    break;
                }
            }
        }
Пример #8
0
        protected override IEnumerator Initialize()
        {
            if (inputMesh == null || !inputMesh.isReadable)
            {
                // TODO: return an error in the coroutine.
                Debug.LogError("The input mesh is null, or not readable.");
                yield break;
            }

            ClearParticleGroups();

            List <Vector3> particles = new List <Vector3>();
            List <Vector3> normals   = new List <Vector3>();

            // Calculate voxel size so that no more than 32^3 particles are created:
            Vector3 boundsSize = Vector3.Scale(inputMesh.bounds.size, Vector3.one);
            float   voxelSize  = Mathf.Max(boundsSize.x / 32.0f, boundsSize.y / 32.0f, boundsSize.z / 32.0f, particleRadius * 2 * (1 - particleOverlap));

            // Voxelize mesh and calculate discrete distance field:
            MeshVoxelizer      voxelizer = new MeshVoxelizer(inputMesh, voxelSize);
            VoxelDistanceField df        = new VoxelDistanceField(voxelizer);

            voxelizer.Voxelize(scale);
            df.JumpFlood();

            MeshVoxelizer.Voxel[,,] voxels = voxelizer.voxels;

            for (int x = 0; x < voxels.GetLength(0); ++x)
            {
                for (int y = 0; y < voxels.GetLength(1); ++y)
                {
                    for (int z = 0; z < voxels.GetLength(2); ++z)
                    {
                        if (voxels[x, y, z] != MeshVoxelizer.Voxel.Outside)
                        {
                            particles.Add(new Vector3(voxelizer.Origin.x + x + 0.5f, voxelizer.Origin.y + y + 0.5f, voxelizer.Origin.z + z + 0.5f) * voxelSize);
                            normals.Add(df.distanceField[x, y, z] - new Vector3Int(x, y, z));
                        }
                    }
                }
            }

            positions           = new Vector3[particles.Count];
            orientations        = new Quaternion[particles.Count];
            restPositions       = new Vector4[particles.Count];
            restOrientations    = new Quaternion[particles.Count];
            velocities          = new Vector3[particles.Count];
            angularVelocities   = new Vector3[particles.Count];
            invMasses           = new float[particles.Count];
            invRotationalMasses = new float[particles.Count];
            principalRadii      = new Vector3[particles.Count];
            phases = new int[particles.Count];
            colors = new Color[particles.Count];

            m_ActiveParticleCount = particles.Count;

            for (int i = 0; i < particles.Count; ++i)
            {
                // Perform ellipsoid fitting:
                Vector3        avgNormal         = Vector3.zero;
                List <Vector3> neighbourVertices = new List <Vector3>();

                Vector3    centroid        = particles[i];
                Quaternion orientation     = Quaternion.LookRotation(normals[i]);
                Vector3    principalValues = Vector3.one * voxelSize * (0.5f + particleOverlap);

                invRotationalMasses[i] = invMasses[i] = 1.0f;
                positions[i]           = Vector3.Lerp(particles[i], centroid, shapeSmoothing);
                restPositions[i]       = positions[i];
                orientations[i]        = orientation;
                restOrientations[i]    = orientation;
                restPositions[i][3]    = 1;           // activate rest position.
                principalRadii[i]      = principalValues;
                phases[i] = ObiUtils.MakePhase(1, 0); //Oni.MakePhase(1, (selfCollisions ? Oni.ParticlePhase.SelfCollide : 0) | (oneSided ? Oni.ParticlePhase.OneSided : 0));
                colors[i] = Color.white;

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

            IEnumerator sc = CreateShapeMatchingConstraints(particles);

            while (sc.MoveNext())
            {
                yield return(sc.Current);
            }

            // Initialize pin constraints:
            pinConstraintsData = new ObiPinConstraintsData();
            ObiPinConstraintsBatch pinBatch = new ObiPinConstraintsBatch();

            pinConstraintsData.AddBatch(pinBatch);
        }
        protected override IEnumerator Initialize()
        {
            if (inputMesh == null || !inputMesh.isReadable)
            {
                // TODO: return an error in the coroutine.
                Debug.LogError("The input mesh is null, or not readable.");
                yield break;
            }

            ClearParticleGroups();

            topology           = new HalfEdgeMesh();
            topology.inputMesh = inputMesh;
            topology.Generate();

            positions      = new Vector3[topology.vertices.Count];
            restPositions  = new Vector4[topology.vertices.Count];
            velocities     = new Vector3[topology.vertices.Count];
            invMasses      = new float[topology.vertices.Count];
            principalRadii = new Vector3[topology.vertices.Count];
            phases         = new int[topology.vertices.Count];
            colors         = new Color[topology.vertices.Count];

            areaContribution = new float[topology.vertices.Count];

            // Create a particle for each vertex:
            m_ActiveParticleCount = topology.vertices.Count;
            for (int i = 0; i < topology.vertices.Count; i++)
            {
                HalfEdgeMesh.Vertex vertex = topology.vertices[i];

                // Get the particle's area contribution.
                areaContribution[i] = 0;
                foreach (HalfEdgeMesh.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 (HalfEdgeMesh.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex))
                {
                    // vertices at each end of the edge:
                    Vector3 v1 = Vector3.Scale(scale, topology.vertices[topology.GetHalfEdgeStartVertex(edge)].position);
                    Vector3 v2 = Vector3.Scale(scale, topology.vertices[edge.endVertex].position);

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

                invMasses[i]        = (/*skinnedMeshRenderer == null &&*/ areaContribution[i] > 0) ? (1.0f / (DEFAULT_PARTICLE_MASS * areaContribution[i])) : 0;
                positions[i]        = Vector3.Scale(scale, 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*/ 0);
                colors[i]           = Color.white;

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

            IEnumerator dt = GenerateDeformableTriangles();

            while (dt.MoveNext())
            {
                yield return(dt.Current);
            }

            //Create distance constraints:
            IEnumerator dc = CreateDistanceConstraints();

            while (dc.MoveNext())
            {
                yield return(dc.Current);
            }

            // Create aerodynamic constraints:
            IEnumerator ac = CreateAerodynamicConstraints();

            while (ac.MoveNext())
            {
                yield return(ac.Current);
            }


            //Create bending constraints:
            IEnumerator bc = CreateBendingConstraints();

            while (bc.MoveNext())
            {
                yield return(bc.Current);
            }


            // Create skin constraints:
            IEnumerator sc = CreateSkinConstraints();

            while (sc.MoveNext())
            {
                yield return(sc.Current);
            }

            pinConstraintsData = new ObiPinConstraintsData();
            ObiPinConstraintsBatch pinBatch = new ObiPinConstraintsBatch();

            pinConstraintsData.AddBatch(pinBatch);
        }
        private void Enable(AttachmentType type)
        {
            var solver    = m_Actor.solver;
            var blueprint = m_Actor.blueprint;

            if (isBound && blueprint != null && m_Actor.solver != null)
            {
                switch (type)
                {
                case AttachmentType.Dynamic:

                    var             pins             = m_Actor.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints <ObiPinConstraintsBatch>;
                    ObiColliderBase attachedCollider = m_Target.GetComponent <ObiColliderBase>();

                    if (pins != null && attachedCollider != null)
                    {
                        // create a new data batch with all our pin constraints:
                        pinBatch = new ObiPinConstraintsBatch();
                        for (int i = 0; i < m_PositionOffsets.Length; ++i)
                        {
                            pinBatch.AddConstraint(0, attachedCollider, m_PositionOffsets[i], m_OrientationOffsets[i]);
                            pinBatch.activeConstraintCount++;
                        }

                        // add the batch to the solver:
                        pins.AddBatch(pinBatch);
                        pinBatch.AddToSolver(pins);

                        // override the pin indices with the ones we got at bind time:
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            pinBatch.particleIndices[i]     = m_SolverIndices[i];
                            pinBatch.stiffnesses[i * 2]     = m_Compliance;
                            pinBatch.stiffnesses[i * 2 + 1] = constrainOrientation?0:10000;
                            pinBatch.breakThresholds[i]     = m_BreakThreshold;
                        }

                        // enable the batch:
                        pinBatch.SetEnabled(true);
                    }

                    break;

                case AttachmentType.Static:

                    for (int i = 0; i < m_SolverIndices.Length; ++i)
                    {
                        solver.invMasses[m_SolverIndices[i]] = 0;
                    }

                    if (m_Actor.usesOrientedParticles && m_ConstrainOrientation)
                    {
                        for (int i = 0; i < m_SolverIndices.Length; ++i)
                        {
                            solver.invRotationalMasses[m_SolverIndices[i]] = 0;
                        }
                    }

                    m_Actor.UpdateParticleProperties();

                    break;
                }
            }
        }
 public ObiPinConstraintsBatch(ObiPinConstraintsBatch source = null) : base(source)
 {
 }
Пример #12
0
        protected override IEnumerator Initialize()
        {
            path.OnPathChanged.RemoveAllListeners();
            path.OnControlPointAdded.RemoveAllListeners();
            path.OnControlPointRemoved.RemoveAllListeners();
            path.OnControlPointRenamed.RemoveAllListeners();

            path.OnPathChanged.AddListener(GenerateImmediate);
            path.OnControlPointAdded.AddListener(ControlPointAdded);
            path.OnControlPointRemoved.AddListener(ControlPointRemoved);
            path.OnControlPointRenamed.AddListener(ControlPointRenamed);

            if (path.ControlPointCount < 2)
            {
                ClearParticleGroups();
                path.InsertControlPoint(0, Vector3.left, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, 1, 1, 1, Color.white, "control point");
                path.InsertControlPoint(1, Vector3.right, Vector3.left * 0.25f, Vector3.right * 0.25f, Vector3.up, DEFAULT_PARTICLE_MASS, 1, 1, 1, Color.white, "control point");
            }

            path.RecalculateLenght(Matrix4x4.identity, 0.00001f, 7);

            List <Vector3> particlePositions   = new List <Vector3>();
            List <float>   particleThicknesses = new List <float>();
            List <float>   particleInvMasses   = new List <float>();
            List <int>     particlePhases      = new List <int>();
            List <Color>   particleColors      = new List <Color>();

            // In case the path is open, add a first particle. In closed paths, the last particle is also the first one.
            if (!path.Closed)
            {
                particlePositions.Add(path.points.GetPositionAtMu(path.Closed, 0));
                particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, 0));
                particleInvMasses.Add(ObiUtils.MassToInvMass(path.thicknesses.GetAtMu(path.Closed, 0)));
                particlePhases.Add(path.phases.GetAtMu(path.Closed, 0));
                particleColors.Add(path.colors.GetAtMu(path.Closed, 0));
            }

            // Create a particle group for the first control point:
            groups[0].particleIndices.Clear();
            groups[0].particleIndices.Add(0);

            ReadOnlyCollection <float> lengthTable = path.ArcLengthTable;
            int spans = path.GetSpanCount();

            for (int i = 0; i < spans; i++)
            {
                int firstArcLengthSample = i * (path.ArcLengthSamples + 1);
                int lastArcLengthSample  = (i + 1) * (path.ArcLengthSamples + 1);

                float upToSpanLength = lengthTable[firstArcLengthSample];
                float spanLength     = lengthTable[lastArcLengthSample] - upToSpanLength;

                int   particlesInSpan = 1 + Mathf.FloorToInt(spanLength / thickness * resolution);
                float distance        = spanLength / particlesInSpan;

                for (int j = 0; j < particlesInSpan; ++j)
                {
                    float mu = path.GetMuAtLenght(upToSpanLength + distance * (j + 1));
                    particlePositions.Add(path.points.GetPositionAtMu(path.Closed, mu));
                    particleThicknesses.Add(path.thicknesses.GetAtMu(path.Closed, mu));
                    particleInvMasses.Add(ObiUtils.MassToInvMass(path.masses.GetAtMu(path.Closed, mu)));
                    particlePhases.Add(path.phases.GetAtMu(path.Closed, mu));
                    particleColors.Add(path.colors.GetAtMu(path.Closed, mu));
                }

                // Create a particle group for each control point:
                if (!(path.Closed && i == spans - 1))
                {
                    groups[i + 1].particleIndices.Clear();
                    groups[i + 1].particleIndices.Add(particlePositions.Count - 1);
                }

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

            m_ActiveParticleCount = particlePositions.Count;
            totalParticles        = m_ActiveParticleCount + pooledParticles;

            int numSegments = m_ActiveParticleCount - (path.Closed ? 0 : 1);

            if (numSegments > 0)
            {
                m_InterParticleDistance = path.Length / (float)numSegments;
            }
            else
            {
                m_InterParticleDistance = 0;
            }

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

            for (int i = 0; i < m_ActiveParticleCount; i++)
            {
                invMasses[i]        = particleInvMasses[i];
                positions[i]        = particlePositions[i];
                restPositions[i]    = positions[i];
                restPositions[i][3] = 1; // activate rest position.
                principalRadii[i]   = Vector3.one * particleThicknesses[i] * thickness;
                phases[i]           = Oni.MakePhase(particlePhases[i], 0);
                colors[i]           = particleColors[i];

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

            //Create distance constraints for the total number of particles, but only activate for the used ones.
            IEnumerator dc = CreateDistanceConstraints();

            while (dc.MoveNext())
            {
                yield return(dc.Current);
            }

            //Create bending constraints:
            IEnumerator bc = CreateBendingConstraints();

            while (bc.MoveNext())
            {
                yield return(bc.Current);
            }

            //Create pin constraints:
            pinConstraintsData = new ObiPinConstraintsData();
            ObiPinConstraintsBatch pinBatch = new ObiPinConstraintsBatch();

            pinConstraintsData.AddBatch(pinBatch);

            // Recalculate rest length:
            m_RestLength = 0;
            foreach (float length in restLengths)
            {
                m_RestLength += length;
            }
        }