Beispiel #1
0
        private void UpdateBordersMesh()
        {
            if (bordersMesh == null || halfEdge.InputMesh == null || !halfEdge.Initialized)
            {
                return;
            }

            bordersMesh.Clear();

            Vector3[] vertices    = new Vector3[halfEdge.BorderEdgeCount * 2];
            int[]     edgeIndices = new int[halfEdge.BorderEdgeCount * 2];

            int borderIndex = 0;

            for (int i = 0; i < halfEdge.heHalfEdges.Length; i++)
            {
                if (halfEdge.heHalfEdges[i].face == -1)
                {
                    Oni.Vertex v1 = halfEdge.heVertices[halfEdge.heHalfEdges[i].endVertex];
                    Oni.Vertex v2 = halfEdge.heVertices[halfEdge.GetHalfEdgeStartVertex(halfEdge.heHalfEdges[i])];

                    vertices[borderIndex * 2]     = v1.position;
                    vertices[borderIndex * 2 + 1] = v2.position;

                    edgeIndices[borderIndex * 2]     = borderIndex * 2;
                    edgeIndices[borderIndex * 2 + 1] = borderIndex * 2 + 1;

                    borderIndex++;
                }
            }

            bordersMesh.vertices = vertices;
            bordersMesh.SetIndices(edgeIndices, UnityEngine.MeshTopology.Lines, 0);
        }
Beispiel #2
0
        public IEnumerable <Oni.Vertex> GetNeighbourVerticesEnumerator(Oni.Vertex vertex)
        {
            Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge];
            Oni.HalfEdge edge      = startEdge;

            do
            {
                yield return(heVertices[edge.endVertex]);

                edge = heHalfEdges[edge.pair];
                edge = heHalfEdges[edge.nextHalfEdge];
            } while (edge.index != startEdge.index);
        }
Beispiel #3
0
        public IEnumerable <Oni.HalfEdge> GetNeighbourEdgesEnumerator(Oni.Vertex vertex)
        {
            Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge];
            Oni.HalfEdge edge      = startEdge;

            do
            {
                edge = heHalfEdges[edge.pair];
                yield return(edge);

                edge = heHalfEdges[edge.nextHalfEdge];
                yield return(edge);
            } while (edge.index != startEdge.index);
        }
Beispiel #4
0
        public IEnumerable <Oni.Face> GetNeighbourFacesEnumerator(Oni.Vertex vertex)
        {
            Oni.HalfEdge startEdge = heHalfEdges[vertex.halfEdge];
            Oni.HalfEdge edge      = startEdge;

            do
            {
                edge = heHalfEdges[edge.pair];
                if (edge.face > -1)
                {
                    yield return(heFaces[edge.face]);
                }
                edge = heHalfEdges[edge.nextHalfEdge];
            } while (edge.index != startEdge.index);
        }
Beispiel #5
0
        /**
         * Deactivates all fixed particles that are attached to fixed particles only, and all the constraints
         * affecting them.
         */
        public void Optimize()
        {
            // Iterate over all particles and get those fixed ones that are only linked to fixed particles.
            for (int i = 0; i < topology.heVertices.Length; ++i)
            {
                Oni.Vertex vertex = topology.heVertices[i];
                if (invMasses[i] > 0)
                {
                    continue;
                }

                active[i] = false;
                foreach (Oni.Vertex n in topology.GetNeighbourVerticesEnumerator(vertex))
                {
                    // If at least one neighbour particle is not fixed, then the particle we are considering for optimization should not be removed.
                    if (invMasses[n.index] > 0)
                    {
                        active[i] = true;
                        break;
                    }
                }

                // Deactivate all constraints involving this inactive particle:
                if (!active[i])
                {
                    // for each constraint type:
                    foreach (ObiBatchedConstraints constraint in constraints)
                    {
                        // for each constraint batch (usually only one)
                        if (constraint != null)
                        {
                            foreach (ObiConstraintBatch batch in constraint.GetBatches())
                            {
                                // deactivate constraints that affect the particle:
                                List <int> affectedConstraints = batch.GetConstraintsInvolvingParticle(i);
                                foreach (int j in affectedConstraints)
                                {
                                    batch.DeactivateConstraint(j);
                                }
                                batch.SetActiveConstraints();
                            }
                        }
                    }
                }
            }

            PushDataToSolver(ParticleData.ACTIVE_STATUS);
        }
Beispiel #6
0
        public bool IsParticleFacingCamera(Camera cam, int particleIndex)
        {
            if (cam == null)
            {
                return(false);
            }

            Oni.Vertex vertex        = cloth.topology.heVertices[particleIndex];
            Vector3    camToParticle = cam.transform.position - wsPositions[particleIndex];

            foreach (int index in cloth.topology.visualVertexBuffer[particleIndex])
            {
                if (Vector3.Dot(cloth.transform.TransformVector(cloth.MeshNormals[index]), camToParticle) > 0)
                {
                    return(true);
                }
            }

            return(false);
        }
Beispiel #7
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.
         */
        public override IEnumerator GeneratePhysicRepresentationForMesh()
        {
            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);

            ResetTopology();

            maxVertexValency = 0;

            pooledParticles = (int)((topology.heFaces.Length * 3 - topology.heVertices.Length) * tearCapacity);
            usedParticles   = topology.heVertices.Length;

            int totalParticles = usedParticles + pooledParticles;

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

            // Create a particle for each vertex, and gather per-vertex data (area, valency)
            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;
                }

                // Calculate particle's valency:
                int valency = 0;
                foreach (Oni.HalfEdge edge in topology.GetNeighbourEdgesEnumerator(vertex))
                {
                    valency++;
                }
                maxVertexValency = Mathf.Max(maxVertexValency, valency);

                // 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))
                {
                    minEdgeLength = Mathf.Min(minEdgeLength, Vector3.Distance(topology.heVertices[topology.GetHalfEdgeStartVertex(edge)].position,
                                                                              topology.heVertices[edge.endVertex].position));
                }
                active[i]           = true;
                tearResistance[i]   = 1;
                invMasses[i]        = (areaContribution[i] > 0) ? (1.0f / (0.05f * areaContribution[i])) : 0;
                positions[i]        = vertex.position;
                restPositions[i]    = positions[i];
                restPositions[i][3] = 0;         // activate rest position.
                solidRadii[i]       = minEdgeLength * 0.5f;
                phases[i]           = Oni.MakePhase(gameObject.layer, selfCollisions?Oni.ParticlePhase.SelfCollide:0);

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

            // Initialize basic data for pooled particles:
            for (int i = topology.heVertices.Length; i < pooledParticles; i++)
            {
                active[i]         = false;
                tearResistance[i] = 1;
                invMasses[i]      = 1.0f / 0.05f;
                solidRadii[i]     = 0.1f;
                phases[i]         = Oni.MakePhase(gameObject.layer, selfCollisions?Oni.ParticlePhase.SelfCollide:0);

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

            // 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(false, false);

            DistanceConstraints.AddBatch(distanceBatch);

            // Initialize constraint-halfedge map for cloth tearing purposes: TODO: reset on awake!!!
            distanceConstraintMap = new int[topology.heHalfEdges.Length];
            for (int i = 0; i < distanceConstraintMap.Length; i++)
            {
                distanceConstraintMap[i] = -1;
            }

            // Create distance springs:
            for (int i = 0; i < edges.Count; i++)
            {
                distanceConstraintMap[edges[i].halfEdgeIndex] = 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(startVertex.position, endVertex.position), 1, 1);

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

            // 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));
                }
            }

            BendingConstraints.Clear();
            ObiBendConstraintBatch bendBatch = new ObiBendConstraintBatch(false, false);

            BendingConstraints.AddBatch(bendBatch);

            bendConstraintOffsets = new int[topology.heVertices.Length + 1];

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

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

                bendConstraintOffsets[i] = bendBatch.ConstraintCount;

                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;

                        float[] restPos = new float[] { n1.position[0], n1.position[1], n1.position[2],
                                                        vBest.position[0], vBest.position[1], vBest.position[2],
                                                        vertex.position[0], vertex.position[1], vertex.position[2] };
                        float restBend = Oni.BendingConstraintRest(restPos);
                        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));
                }
            }
            bendConstraintOffsets[topology.heVertices.Length] = bendBatch.ConstraintCount;

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

            PinConstraints.AddBatch(pinBatch);

            AddToSolver(null);

            initializing = false;
            initialized  = true;

            InitializeWithRegularMesh();

            pooledVertices = (int)((topology.heFaces.Length * 3 - sharedMesh.vertexCount) * tearCapacity);
        }
Beispiel #8
0
        /**
         * Calculates angle-weighted normals for the input mesh, taking into account shared vertices.
         */
        /*public Vector3[] AngleWeightedNormals(){
         *
         *      if (input == null) return null;
         *
         *      Vector3[] normals = input.normals;
         *      Vector3[] vertices = input.vertices;
         *
         *      for(int i = 0; i < normals.Length; i++)
         *              normals[i] = Vector3.zero;
         *
         *      int i1,i2,i3;
         *      Vector3 e1, e2;
         *      foreach(HEFace face in heFaces){
         *
         *              HEVertex hv1 = heVertices[heHalfEdges[face.edges[0]].endVertex];
         *              HEVertex hv2 = heVertices[heHalfEdges[face.edges[1]].endVertex];
         *              HEVertex hv3 = heVertices[heHalfEdges[face.edges[2]].endVertex];
         *
         *              i1 = hv1.physicalIDs[0];
         *              i2 = hv2.physicalIDs[0];
         *              i3 = hv3.physicalIDs[0];
         *
         *              e1 = vertices[i2]-vertices[i1];
         *              e2 = vertices[i3]-vertices[i1];
         *              foreach(int pi in hv1.physicalIDs)
         *                      normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized));
         *
         *              e1 = vertices[i3]-vertices[i2];
         *              e2 = vertices[i1]-vertices[i2];
         *              foreach(int pi in hv2.physicalIDs)
         *                      normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized));
         *
         *              e1 = vertices[i1]-vertices[i3];
         *              e2 = vertices[i2]-vertices[i3];
         *              foreach(int pi in hv3.physicalIDs)
         *                      normals[pi] += Vector3.Cross(e1,e2) * Mathf.Acos(Vector3.Dot(e1.normalized,e2.normalized));
         *
         *      }
         *
         *      for(int i = 0; i < normals.Length; i++)
         *              normals[i].Normalize();
         *
         *      return normals;
         * }*/

        /**
         * Splits a vertex in two along a plane. Returns true if the vertex can be split, false otherwise.
         * \param vertex the vertex to split.
         * \param splitPlane plane to split the vertex at.
         * \param newVertex the newly created vertex after the split operation has been performed.
         * \param vertices new mesh vertices list after the split operation.
         * \param updatedEdges indices of half-edges that need some kind of constraint update.
         */
        public bool SplitVertex(Oni.Vertex vertex, Plane splitPlane, MeshBuffer meshBuffer, Vector4[] positions, List <int> particleIndices, out Oni.Vertex newVertex, out HashSet <int> updatedEdges, out HashSet <int> addedEdges)
        {
            // initialize return values:
            updatedEdges = new HashSet <int>();
            addedEdges   = new HashSet <int>();
            newVertex    = new Oni.Vertex();

            // initialize face lists for each side of the split plane.
            List <Oni.Face> side1Faces = new List <Oni.Face>();
            List <Oni.Face> side2Faces = new List <Oni.Face>();
            HashSet <int>   side2Edges = new HashSet <int>();

            // classify adjacent faces depending on which side of the cut plane they reside in:
            foreach (Oni.Face face in GetNeighbourFacesEnumerator(vertex))
            {
                Oni.HalfEdge e1 = heHalfEdges[face.halfEdge];
                Oni.HalfEdge e2 = heHalfEdges[e1.nextHalfEdge];
                Oni.HalfEdge e3 = heHalfEdges[e2.nextHalfEdge];

                // Skip this face if it doesnt contain the splitted vertex.
                // This can happen because edge pair links are not updated, and so a vertex in the cut stil "sees"
                // the faces at the other side like neighbour faces.
                if (e1.endVertex != vertex.index && e2.endVertex != vertex.index && e3.endVertex != vertex.index)
                {
                    continue;
                }

                // Average positions to get the center of the face:
                Vector3 faceCenter = (positions[particleIndices[e1.endVertex]] +
                                      positions[particleIndices[e2.endVertex]] +
                                      positions[particleIndices[e3.endVertex]]) / 3.0f;

                if (splitPlane.GetSide(faceCenter))
                {
                    side1Faces.Add(face);
                }
                else
                {
                    side2Faces.Add(face);
                    side2Edges.Add(e1.index);
                    side2Edges.Add(e2.index);
                    side2Edges.Add(e3.index);
                }
            }

            // If the vertex cant be split, return false.
            if (side1Faces.Count == 0 || side2Faces.Count == 0)
            {
                return(false);
            }

            // create a new vertex:
            newVertex = new Oni.Vertex(vertex.position, heVertices.Count, vertex.halfEdge);

            // add a new vertex to the mesh too, if needed.
            if (meshBuffer != null)
            {
                visualVertexBuffer.Add(new List <int>()
                {
                    meshBuffer.vertexCount
                });
                meshBuffer.AddVertex(visualVertexBuffer[vertex.index][0]);
            }

            // rearrange edges at side 1:
            foreach (Oni.Face face in side1Faces)
            {
                // find half edges that start or end at the split vertex:
                int[]        faceEdges = GetFaceEdges(face);
                Oni.HalfEdge edgeIn    = heHalfEdges[Array.Find <int>(faceEdges, e => heHalfEdges[e].endVertex == vertex.index)];
                Oni.HalfEdge edgeOut   = heHalfEdges[Array.Find <int>(faceEdges, e => this.GetHalfEdgeStartVertex(heHalfEdges[e]) == vertex.index)];

                // Edges whose pair is on the other side of the cut and share the same vertices, will spawn a new constraint.
                if (side2Edges.Contains(edgeIn.pair) && GetHalfEdgeStartVertex(edgeIn) == heHalfEdges[edgeIn.pair].endVertex)
                {
                    addedEdges.Add(Mathf.Max(edgeIn.index, edgeIn.pair));
                }

                if (side2Edges.Contains(edgeOut.pair) && GetHalfEdgeStartVertex(heHalfEdges[edgeOut.pair]) == edgeOut.endVertex)
                {
                    addedEdges.Add(Mathf.Max(edgeOut.index, edgeOut.pair));
                }

                // Constraints for these edges should be updated. (There's no guarantee the constraint exists!).
                updatedEdges.Add(edgeIn.index);
                updatedEdges.Add(edgeIn.pair);
                updatedEdges.Add(edgeOut.index);
                updatedEdges.Add(edgeOut.pair);

                // stitch in half edge to new vertex
                edgeIn.endVertex   = newVertex.index;
                newVertex.halfEdge = edgeOut.index;

                heHalfEdges[edgeIn.index]  = edgeIn;
                heHalfEdges[edgeOut.index] = edgeOut;

                // update mesh triangle buffer to point at new vertex where needed:
                if (meshBuffer != null)
                {
                    if (meshBuffer.triangles[face.index * 3] == visualVertexBuffer[vertex.index][0])
                    {
                        meshBuffer.triangles[face.index * 3] = meshBuffer.vertexCount - 1;
                    }
                    if (meshBuffer.triangles[face.index * 3 + 1] == visualVertexBuffer[vertex.index][0])
                    {
                        meshBuffer.triangles[face.index * 3 + 1] = meshBuffer.vertexCount - 1;
                    }
                    if (meshBuffer.triangles[face.index * 3 + 2] == visualVertexBuffer[vertex.index][0])
                    {
                        meshBuffer.triangles[face.index * 3 + 2] = meshBuffer.vertexCount - 1;
                    }
                }
            }

            // Add the nex vertex to the half-edge.
            heVertices.Add(newVertex);

            meshInfo.closed = false;

            return(true);
        }
Beispiel #9
0
        private List <HashSet <int> > GenerateIslands(IEnumerable <int> particles, bool onlyFixed)
        {
            List <HashSet <int> > islands = new List <HashSet <int> >();

            // Partition fixed particles into islands:
            foreach (int i in particles)
            {
                Oni.Vertex vertex = topology.heVertices[i];

                if ((onlyFixed && 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
                    });
                }
            }

            return(islands);
        }
Beispiel #10
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();
            }
        }
Beispiel #11
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);
        }